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
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
Flooring
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 {UUPSUpgradeable} from "@openzeppelin-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol"; import "chainlink/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; import "chainlink/v0.8/VRFConsumerBaseV2.sol"; import "./interface/IFlooring.sol"; import "./interface/IFlooringEvent.sol"; import "./interface/IFragmentToken.sol"; import "./logic/User.sol"; import "./logic/Collection.sol"; import "./logic/Auction.sol"; import "./logic/Raffle.sol"; import "./logic/PrivateOffer.sol"; import { CollectionState, SafeBox, AuctionInfo, RaffleInfo, PrivateOffer, UserFloorAccount, FeeConfig } from "./logic/Structs.sol"; import "./Multicall.sol"; import "./Errors.sol"; import {TrustedUpgradeable} from "./library/TrustedUpgradeable.sol"; contract Flooring is IFlooring, IFlooringEvent, Multicall, TrustedUpgradeable, UUPSUpgradeable, VRFConsumerBaseV2 { using CollectionLib for CollectionState; using AuctionLib for CollectionState; using RaffleLib for CollectionState; using PrivateOfferLib for CollectionState; using UserLib for UserFloorAccount; struct RandomRequestInfo { uint96 typ; address collection; bytes data; } /// Information related to Chainlink VRF Randomness Oracle. /// The keyhash, which is network dependent. bytes32 internal immutable keyHash; /// Subscription Id, need to get from the Chainlink UI. uint64 internal immutable subId; /// Chainlink VRF Coordinator. VRFCoordinatorV2Interface internal immutable COORDINATOR; /// A mapping from VRF request Id to raffle. mapping(uint256 => RandomRequestInfo) internal randomnessRequestToReceiver; /// This should be the FLC token. address public immutable creditToken; /// A mapping from collection address to `CollectionState`. mapping(address => CollectionState) internal collectionStates; /// A mapping from user address to the `UserFloorAccount`s. mapping(address => UserFloorAccount) internal userFloorAccounts; /// A mapping of supported ERC-20 token. mapping(address => bool) internal supportedTokens; /// A mapping from Proxy Collection(wrapped) to underlying Collection. /// eg. Paraspace Derivative Token BAYC(nBAYC) -> BAYC /// Note. we only use proxy collection to transfer NFTs, /// all other operations should use underlying Collection.(State, Log, CollectionAccount) /// proxy collection has not `CollectionState`, but use underlying collection's state. /// proxy collection only is used to lock infinitly. /// `fragmentNFTs` and `claimRandomNFT` don't support proxy collection mapping(address => address) internal collectionProxy; /// A mapping of collection fees configuration /// collectionFees[collection][token] /// For different tokens, we may charge different fees mapping(address => mapping(address => FeeConfig)) internal collectionFees; constructor(bytes32 _keyHash, uint64 _subId, address _vrfCoordinator, address flcToken) payable VRFConsumerBaseV2(_vrfCoordinator) { keyHash = _keyHash; subId = _subId; COORDINATOR = VRFCoordinatorV2Interface(_vrfCoordinator); creditToken = flcToken; _disableInitializers(); } /// required by the OZ UUPS module function _authorizeUpgrade(address) internal override onlyOwner {} /// @dev just declare this as payable to reduce gas and bytecode function initialize() public payable initializer { __Trusted_init(); __UUPSUpgradeable_init(); } function supportNewCollection(address _originalNFT, address fragmentToken) public payable onlyTrusted { CollectionState storage collection = collectionStates[_originalNFT]; require(collection.nextKeyId == 0); collection.nextKeyId = 1; collection.nextActivityId = 1; collection.floorToken = IFragmentToken(fragmentToken); emit NewCollectionSupported(_originalNFT, fragmentToken); } function supportNewToken(address _token, bool addOrRemove) public payable onlyTrusted { if (supportedTokens[_token] == addOrRemove) { return; } else { /// true - add /// false - remove supportedTokens[_token] = addOrRemove; emit UpdateTokenSupported(_token, addOrRemove); } } function updateCollectionFees(address collection, address[] calldata tokens, FeeConfig[] calldata fees) public payable onlyTrusted { for (uint256 i; i < tokens.length;) { collectionFees[collection][tokens[i]] = fees[i]; unchecked { ++i; } } } function setCollectionProxy(address proxyCollection, address underlying) public payable onlyTrusted { if (collectionProxy[proxyCollection] == underlying) { return; } else { collectionProxy[proxyCollection] = underlying; emit ProxyCollectionChanged(proxyCollection, underlying); } } function withdrawPlatformFee(address token, uint256 amount) public payable onlyTrusted { /// track platform fee with account, only can withdraw fee accumulated during tx. /// no need to check credit token balance for the account. UserFloorAccount storage userFloorAccount = userFloorAccounts[address(this)]; userFloorAccount.withdraw(msg.sender, token, amount, false); } function addAndLockCredit(address onBehalfOf, uint256 amount) public payable onlyTrusted { UserFloorAccount storage userFloorAccount = userFloorAccounts[onBehalfOf]; userFloorAccount.deposit(onBehalfOf, creditToken, amount, true); } function unlockCredit(address receiver, uint256 amount) public payable onlyTrusted { UserFloorAccount storage userFloorAccount = userFloorAccounts[receiver]; userFloorAccount.unlockCredit(amount); } function addTokens(address onBehalfOf, address token, uint256 amount) public payable { mustSupportedToken(token); UserFloorAccount storage userFloorAccount = userFloorAccounts[onBehalfOf]; userFloorAccount.deposit(onBehalfOf, token, amount, false); } function removeTokens(address token, uint256 amount, address receiver) public { UserFloorAccount storage userFloorAccount = userFloorAccounts[msg.sender]; userFloorAccount.withdraw(receiver, token, amount, token == creditToken); } function lockNFTs( address collection, uint256[] memory nftIds, uint256 expiryTs, uint256 vipLevel, uint256 maxCreditCost, address onBehalfOf ) public returns (uint256) { mustValidNftIds(nftIds); mustValidExpiryTs(expiryTs); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collection); return collectionState.lockNfts( userFloorAccounts[onBehalfOf], LockParam({ proxyCollection: collection, collection: underlying, creditToken: creditToken, nftIds: nftIds, expiryTs: expiryTs, vipLevel: uint8(vipLevel), maxCreditCost: maxCreditCost }), onBehalfOf ); } function unlockNFTs(address collection, uint256 expiryTs, uint256[] memory nftIds, address receiver) public { mustValidNftIds(nftIds); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collection); collectionState.unlockNfts(userFloorAccounts[msg.sender], collection, underlying, nftIds, expiryTs, receiver); } function removeExpiredKeyAndRestoreCredit( address collection, uint256[] memory nftIds, address onBehalfOf, bool verifyLocking ) public returns (uint256) { mustValidNftIds(nftIds); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collection); return collectionState.removeExpiredKeysAndRestoreCredits( userFloorAccounts[onBehalfOf], underlying, nftIds, onBehalfOf, verifyLocking ); } function recalculateAvailableCredit(address onBehalfOf) public returns (uint256) { UserFloorAccount storage account = userFloorAccounts[onBehalfOf]; uint256 minMaintCredit = account.recalculateMinMaintCredit(onBehalfOf); unchecked { /// when locking or extending, we ensure that `minMaintCredit` is less than `totalCredit` /// availableCredit = totalCredit - minMaintCredit return account.tokenBalance(creditToken) - minMaintCredit; } } function extendKeys( address collection, uint256[] memory nftIds, uint256 expiryTs, uint256 newVipLevel, uint256 maxCreditCost ) public returns (uint256) { mustValidNftIds(nftIds); mustValidExpiryTs(expiryTs); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collection); return collectionState.extendLockingForKeys( userFloorAccounts[msg.sender], LockParam({ proxyCollection: collection, collection: underlying, creditToken: creditToken, nftIds: nftIds, expiryTs: expiryTs, vipLevel: uint8(newVipLevel), maxCreditCost: maxCreditCost }) ); } function tidyExpiredNFTs(address collection, uint256[] memory nftIds) public { mustValidNftIds(nftIds); /// expired safeboxes must not be collection CollectionState storage collectionState = useCollectionState(collection); collectionState.tidyExpiredNFTs(nftIds, collection); } function fragmentNFTs(address collection, uint256[] memory nftIds, address onBehalfOf) public { mustValidNftIds(nftIds); CollectionState storage collectionState = useCollectionState(collection); collectionState.fragmentNFTs(userFloorAccounts[onBehalfOf], collection, nftIds, onBehalfOf); } function claimRandomNFT(address collection, uint256 claimCnt, uint256 maxCreditCost, address receiver) public returns (uint256) { CollectionState storage collectionState = useCollectionState(collection); return collectionState.claimRandomNFT( userFloorAccounts, collectionFees[collection], collection, claimCnt, maxCreditCost, receiver ); } function initAuctionOnVault(address collection, uint256[] memory vaultIdx, address bidToken, uint96 bidAmount) public { mustValidNftIds(vaultIdx); CollectionState storage collectionState = useCollectionState(collection); collectionState.initAuctionOnVault( userFloorAccounts, collectionFees[collection][bidToken], creditToken, collection, vaultIdx, bidToken, bidAmount ); } function initAuctionOnExpiredSafeBoxes( address collection, uint256[] memory nftIds, address bidToken, uint256 bidAmount ) public { mustValidNftIds(nftIds); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collection); collectionState.initAuctionOnExpiredSafeBoxes( userFloorAccounts, collectionFees[underlying][bidToken], creditToken, underlying, nftIds, bidToken, bidAmount ); } function ownerInitAuctions( address collection, uint256[] memory nftIds, uint256 maxExpiry, address token, uint256 minimumBid ) public { mustValidNftIds(nftIds); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collection); collectionState.ownerInitAuctions( userFloorAccounts, collectionFees[underlying][token], creditToken, underlying, nftIds, maxExpiry, token, minimumBid ); } function placeBidOnAuction(address collection, uint256 nftId, uint256 bidAmount, uint256 bidOptionIdx) public payable { if (msg.value > 0) addTokens(msg.sender, CurrencyTransfer.NATIVE, msg.value); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collection); collectionState.placeBidOnAuction(userFloorAccounts, creditToken, underlying, nftId, bidAmount, bidOptionIdx); } function settleAuctions(address collection, uint256[] memory nftIds) public { mustValidNftIds(nftIds); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collection); collectionState.settleAuctions(userFloorAccounts, underlying, nftIds); } function ownerInitRaffles(RaffleInitParam memory param) public { mustValidNftIds(param.nftIds); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(param.collection); param.collection = underlying; collectionState.ownerInitRaffles( userFloorAccounts, collectionFees[underlying][param.ticketToken], param, creditToken ); } function buyRaffleTickets(address collectionId, uint256 nftId, uint256 ticketCnt) public payable { if (msg.value > 0) addTokens(msg.sender, CurrencyTransfer.NATIVE, msg.value); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collectionId); collectionState.buyRaffleTickets(userFloorAccounts, creditToken, underlying, nftId, ticketCnt); } function settleRaffles(address collectionId, uint256[] memory nftIds) public { mustValidNftIds(nftIds); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collectionId); (bytes memory toSettleNftIds, uint256 len) = collectionState.prepareSettleRaffles(nftIds); if (len > 0) { uint256 requestId = COORDINATOR.requestRandomWords(keyHash, subId, 3, 800_000, uint32(len)); randomnessRequestToReceiver[requestId] = RandomRequestInfo({typ: 1, collection: underlying, data: toSettleNftIds}); } } function _completeSettleRaffles(address collectionId, bytes memory data, uint256[] memory randoms) private { CollectionState storage collection = collectionStates[collectionId]; collection.settleRaffles(userFloorAccounts, collectionId, data, randoms); } function ownerInitPrivateOffers(PrivateOfferInitParam memory param) public { mustValidNftIds(param.nftIds); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(param.collection); param.collection = underlying; collectionState.ownerInitPrivateOffers( userFloorAccounts, collectionFees[underlying][param.token], creditToken, param ); } function modifyOffers(address collectionId, uint256[] memory nftIds, OfferOpType opTy, bytes calldata data) public { (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collectionId); collectionState.modifyOffers(userFloorAccounts, underlying, nftIds, opTy, data); } function buyerAcceptPrivateOffers(address collectionId, uint256[] memory nftIds, uint256 maxSafeboxExpiry) public payable { mustValidNftIds(nftIds); if (msg.value > 0) addTokens(msg.sender, CurrencyTransfer.NATIVE, msg.value); (CollectionState storage collectionState, address underlying) = useUnderlyingCollectionState(collectionId); collectionState.buyerAcceptPrivateOffers(userFloorAccounts, underlying, creditToken, nftIds, maxSafeboxExpiry); } function extMulticall(CallData[] calldata calls) external override(Multicall, IMulticall) onlyTrusted returns (bytes[] memory) { return multicall2(calls); } function onERC721Received(address, /*operator*/ address, /*from*/ uint256, /*tokenId*/ bytes calldata /*data*/ ) external pure override returns (bytes4) { return this.onERC721Received.selector; } function useUnderlyingCollectionState(address collectionId) private view returns (CollectionState storage, address) { address underlying = collectionProxy[collectionId]; if (underlying == address(0)) { underlying = collectionId; } return (useCollectionState(underlying), underlying); } function useCollectionState(address collectionId) private view returns (CollectionState storage) { CollectionState storage collection = collectionStates[collectionId]; if (collection.nextKeyId == 0) revert Errors.NftCollectionNotSupported(); return collection; } function mustSupportedToken(address token) private view { if (!supportedTokens[token]) revert Errors.TokenNotSupported(); } function mustValidNftIds(uint256[] memory nftIds) private pure { if (nftIds.length == 0) revert Errors.InvalidParam(); /// nftIds should be ordered and there should be no duplicate elements. for (uint256 i = 1; i < nftIds.length;) { unchecked { if (nftIds[i] <= nftIds[i - 1]) { revert Errors.InvalidParam(); } ++i; } } } function mustValidExpiryTs(uint256 expiryTs) private view { if (expiryTs != 0 && expiryTs <= block.timestamp) revert Errors.InvalidParam(); } function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { RandomRequestInfo storage info = randomnessRequestToReceiver[requestId]; _completeSettleRaffles(info.collection, info.data, randomWords); delete randomnessRequestToReceiver[requestId]; } function collectionLockingAt(address collection, uint256 startTimestamp, uint256 endTimestamp) public view returns (uint256[] memory) { return collectionStates[collection].getLockingBuckets(startTimestamp, endTimestamp); } function extsload(bytes32 slot) external view returns (bytes32 value) { /// @solidity memory-safe-assembly assembly { value := sload(slot) } } function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes memory) { bytes memory value = new bytes(nSlots << 5); /// @solidity memory-safe-assembly assembly { for { let i := 0 } lt(i, nSlots) { i := add(i, 1) } { mstore(add(value, shl(5, add(i, 1))), sload(add(startSlot, i))) } } return value; } receive() external payable { addTokens(msg.sender, CurrencyTransfer.NATIVE, msg.value); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol) pragma solidity ^0.8.20; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in * case an upgrade adds a module that needs to be initialized. * * For example: * * [.hljs-theme-light.nopadding] * ```solidity * contract MyToken is ERC20Upgradeable { * function initialize() initializer public { * __ERC20_init("MyToken", "MTK"); * } * } * * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { * function initializeV2() reinitializer(2) public { * __ERC20Permit_init("MyToken"); * } * } * ``` * * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. * * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. * * [CAUTION] * ==== * Avoid leaving a contract uninitialized. * * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: * * [.hljs-theme-light.nopadding] * ``` * /// @custom:oz-upgrades-unsafe-allow constructor * constructor() { * _disableInitializers(); * } * ``` * ==== */ abstract contract Initializable { /** * @dev Storage of the initializable contract. * * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions * when using with upgradeable contracts. * * @custom:storage-location erc7201:openzeppelin.storage.Initializable */ struct InitializableStorage { /** * @dev Indicates that the contract has been initialized. */ uint64 _initialized; /** * @dev Indicates that the contract is in the process of being initialized. */ bool _initializing; } // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; /** * @dev The contract is already initialized. */ error InvalidInitialization(); /** * @dev The contract is not initializing. */ error NotInitializing(); /** * @dev Triggered when the contract has been initialized or reinitialized. */ event Initialized(uint64 version); /** * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, * `onlyInitializing` functions can be used to initialize parent contracts. * * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in * production. * * Emits an {Initialized} event. */ modifier initializer() { // solhint-disable-next-line var-name-mixedcase InitializableStorage storage $ = _getInitializableStorage(); // Cache values to avoid duplicated sloads bool isTopLevelCall = !$._initializing; uint64 initialized = $._initialized; // Allowed calls: // - initialSetup: the contract is not in the initializing state and no previous version was // initialized // - construction: the contract is initialized at version 1 (no reininitialization) and the // current contract is just being deployed bool initialSetup = initialized == 0 && isTopLevelCall; bool construction = initialized == 1 && address(this).code.length == 0; if (!initialSetup && !construction) { revert InvalidInitialization(); } $._initialized = 1; if (isTopLevelCall) { $._initializing = true; } _; if (isTopLevelCall) { $._initializing = false; emit Initialized(1); } } /** * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be * used to initialize parent contracts. * * A reinitializer may be used after the original initialization step. This is essential to configure modules that * are added through upgrades and that require initialization. * * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` * cannot be nested. If one is invoked in the context of another, execution will revert. * * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in * a contract, executing them in the right order is up to the developer or operator. * * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization. * * Emits an {Initialized} event. */ modifier reinitializer(uint64 version) { // solhint-disable-next-line var-name-mixedcase InitializableStorage storage $ = _getInitializableStorage(); if ($._initializing || $._initialized >= version) { revert InvalidInitialization(); } $._initialized = version; $._initializing = true; _; $._initializing = false; emit Initialized(version); } /** * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the * {initializer} and {reinitializer} modifiers, directly or indirectly. */ modifier onlyInitializing() { _checkInitializing(); _; } /** * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}. */ function _checkInitializing() internal view virtual { if (!_isInitializing()) { revert NotInitializing(); } } /** * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized * to any version. It is recommended to use this to lock implementation contracts that are designed to be called * through proxies. * * Emits an {Initialized} event the first time it is successfully executed. */ function _disableInitializers() internal virtual { // solhint-disable-next-line var-name-mixedcase InitializableStorage storage $ = _getInitializableStorage(); if ($._initializing) { revert InvalidInitialization(); } if ($._initialized != type(uint64).max) { $._initialized = type(uint64).max; emit Initialized(type(uint64).max); } } /** * @dev Returns the highest version that has been initialized. See {reinitializer}. */ function _getInitializedVersion() internal view returns (uint64) { return _getInitializableStorage()._initialized; } /** * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. */ function _isInitializing() internal view returns (bool) { return _getInitializableStorage()._initializing; } /** * @dev Returns a pointer to the storage namespace. */ // solhint-disable-next-line var-name-mixedcase function _getInitializableStorage() private pure returns (InitializableStorage storage $) { assembly { $.slot := INITIALIZABLE_STORAGE } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol) pragma solidity ^0.8.20; import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol"; import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; import {Initializable} from "./Initializable.sol"; /** * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy. * * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing * `UUPSUpgradeable` with a custom implementation of upgrades. * * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism. */ abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address private immutable __self = address(this); /** * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)` * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string. * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function * during an upgrade. */ string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; /** * @dev The call is from an unauthorized context. */ error UUPSUnauthorizedCallContext(); /** * @dev The storage `slot` is unsupported as a UUID. */ error UUPSUnsupportedProxiableUUID(bytes32 slot); /** * @dev Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to * fail. */ modifier onlyProxy() { _checkProxy(); _; } /** * @dev Check that the execution is not being performed through a delegate call. This allows a function to be * callable on the implementing contract but not through proxies. */ modifier notDelegated() { _checkNotDelegated(); _; } function __UUPSUpgradeable_init() internal onlyInitializing { } function __UUPSUpgradeable_init_unchained() internal onlyInitializing { } /** * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the * implementation. It is used to validate the implementation's compatibility when performing an upgrade. * * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier. */ function proxiableUUID() external view virtual notDelegated returns (bytes32) { return ERC1967Utils.IMPLEMENTATION_SLOT; } /** * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call * encoded in `data`. * * Calls {_authorizeUpgrade}. * * Emits an {Upgraded} event. * * @custom:oz-upgrades-unsafe-allow-reachable delegatecall */ function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, data); } /** * @dev Reverts if the execution is not performed via delegatecall or the execution * context is not of a proxy with an ERC1967-compliant implementation pointing to self. * See {_onlyProxy}. */ function _checkProxy() internal view virtual { if ( address(this) == __self || // Must be called through delegatecall ERC1967Utils.getImplementation() != __self // Must be called through an active proxy ) { revert UUPSUnauthorizedCallContext(); } } /** * @dev Reverts if the execution is performed via delegatecall. * See {notDelegated}. */ function _checkNotDelegated() internal view virtual { if (address(this) != __self) { // Must not be called through delegatecall revert UUPSUnauthorizedCallContext(); } } /** * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by * {upgradeToAndCall}. * * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}. * * ```solidity * function _authorizeUpgrade(address) internal onlyOwner {} * ``` */ function _authorizeUpgrade(address newImplementation) internal virtual; /** * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call. * * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value * is expected to be the implementation slot in ERC1967. * * Emits an {IERC1967-Upgraded} event. */ function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private { try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) { revert UUPSUnsupportedProxiableUUID(slot); } ERC1967Utils.upgradeToAndCall(newImplementation, data); } catch { // The implementation is not UUPS revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC1822.sol) pragma solidity ^0.8.20; /** * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified * proxy whose upgrades are fully controlled by the current implementation. */ interface IERC1822Proxiable { /** * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation * address. * * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this * function revert if invoked through a proxy. */ function proxiableUUID() external view returns (bytes32); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol) pragma solidity ^0.8.20; /** * @dev This is the interface that {BeaconProxy} expects of its beacon. */ interface IBeacon { /** * @dev Must return an address that can be used as a delegate call target. * * {UpgradeableBeacon} will check that this address is a contract. */ function implementation() external view returns (address); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol) pragma solidity ^0.8.20; import {IBeacon} from "../beacon/IBeacon.sol"; import {Address} from "../../utils/Address.sol"; import {StorageSlot} from "../../utils/StorageSlot.sol"; /** * @dev This abstract contract provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. */ library ERC1967Utils { // We re-declare ERC-1967 events here because they can't be used directly from IERC1967. // This will be fixed in Solidity 0.8.21. At that point we should remove these events. /** * @dev Emitted when the implementation is upgraded. */ event Upgraded(address indexed implementation); /** * @dev Emitted when the admin account has changed. */ event AdminChanged(address previousAdmin, address newAdmin); /** * @dev Emitted when the beacon is changed. */ event BeaconUpgraded(address indexed beacon); /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. */ // solhint-disable-next-line private-vars-leading-underscore bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev The `implementation` of the proxy is invalid. */ error ERC1967InvalidImplementation(address implementation); /** * @dev The `admin` of the proxy is invalid. */ error ERC1967InvalidAdmin(address admin); /** * @dev The `beacon` of the proxy is invalid. */ error ERC1967InvalidBeacon(address beacon); /** * @dev An upgrade function sees `msg.value > 0` that may be lost. */ error ERC1967NonPayable(); /** * @dev Returns the current implementation address. */ function getImplementation() internal view returns (address) { return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value; } /** * @dev Stores a new address in the EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { if (newImplementation.code.length == 0) { revert ERC1967InvalidImplementation(newImplementation); } StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation; } /** * @dev Performs implementation upgrade with additional setup call if data is nonempty. * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected * to avoid stuck value in the contract. * * Emits an {IERC1967-Upgraded} event. */ function upgradeToAndCall(address newImplementation, bytes memory data) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); if (data.length > 0) { Address.functionDelegateCall(newImplementation, data); } else { _checkNonPayable(); } } /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1. */ // solhint-disable-next-line private-vars-leading-underscore bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** * @dev Returns the current admin. * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` */ function getAdmin() internal view returns (address) { return StorageSlot.getAddressSlot(ADMIN_SLOT).value; } /** * @dev Stores a new address in the EIP1967 admin slot. */ function _setAdmin(address newAdmin) private { if (newAdmin == address(0)) { revert ERC1967InvalidAdmin(address(0)); } StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin; } /** * @dev Changes the admin of the proxy. * * Emits an {IERC1967-AdminChanged} event. */ function changeAdmin(address newAdmin) internal { emit AdminChanged(getAdmin(), newAdmin); _setAdmin(newAdmin); } /** * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1. */ // solhint-disable-next-line private-vars-leading-underscore bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; /** * @dev Returns the current beacon. */ function getBeacon() internal view returns (address) { return StorageSlot.getAddressSlot(BEACON_SLOT).value; } /** * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { if (newBeacon.code.length == 0) { revert ERC1967InvalidBeacon(newBeacon); } StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon; address beaconImplementation = IBeacon(newBeacon).implementation(); if (beaconImplementation.code.length == 0) { revert ERC1967InvalidImplementation(beaconImplementation); } } /** * @dev Change the beacon and trigger a setup call if data is nonempty. * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected * to avoid stuck value in the contract. * * Emits an {IERC1967-BeaconUpgraded} event. * * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for * efficiency. */ function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0) { Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); } else { _checkNonPayable(); } } /** * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract * if an upgrade doesn't perform an initialization call. */ function _checkNonPayable() private { if (msg.value > 0) { revert ERC1967NonPayable(); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.20; import {IERC165} from "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC721 compliant contract. */ interface IERC721 is IERC165 { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon * a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or * {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon * a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must * understand this adds an external call which potentially creates a reentrancy vulnerability. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 tokenId) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the address zero. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool approved) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol) pragma solidity ^0.8.20; /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ interface IERC721Receiver { /** * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} * by `operator` from `from`, this function is called. * * It must return its Solidity selector to confirm the token transfer. * If any other value is returned or the interface is not implemented by the recipient, the transfer will be * reverted. * * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol) pragma solidity ^0.8.20; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev The ETH balance of the account is not enough to perform the operation. */ error AddressInsufficientBalance(address account); /** * @dev There's no code at `target` (it is not a contract). */ error AddressEmptyCode(address target); /** * @dev A call to an address target failed. The target may have reverted. */ error FailedInnerCall(); /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { if (address(this).balance < amount) { revert AddressInsufficientBalance(address(this)); } (bool success, ) = recipient.call{value: amount}(""); if (!success) { revert FailedInnerCall(); } } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason or custom error, it is bubbled * up by this function (like regular Solidity function calls). However, if * the call reverted with no returned reason, this function reverts with a * {FailedInnerCall} error. * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { if (address(this).balance < value) { revert AddressInsufficientBalance(address(this)); } (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an * unsuccessful call. */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata ) internal view returns (bytes memory) { if (!success) { _revert(returndata); } else { // only check if target is a contract if the call was successful and the return data is empty // otherwise we already know that it was a contract if (returndata.length == 0 && target.code.length == 0) { revert AddressEmptyCode(target); } return returndata; } } /** * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the * revert reason or with a default {FailedInnerCall} error. */ function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { if (!success) { _revert(returndata); } else { return returndata; } } /** * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. */ function _revert(bytes memory returndata) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert FailedInnerCall(); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) pragma solidity ^0.8.20; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { /** * @dev Muldiv operation overflow. */ error MathOverflowedMulDiv(); enum Rounding { Floor, // Toward negative infinity Ceil, // Toward positive infinity Trunc, // Toward zero Expand // Away from zero } /** * @dev Returns the addition of two unsigned integers, with an overflow flag. */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } } /** * @dev Returns the subtraction of two unsigned integers, with an overflow flag. */ function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b > a) return (false, 0); return (true, a - b); } } /** * @dev Returns the multiplication of two unsigned integers, with an overflow flag. */ function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) return (true, 0); uint256 c = a * b; if (c / a != b) return (false, 0); return (true, c); } } /** * @dev Returns the division of two unsigned integers, with a division by zero flag. */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a / b); } } /** * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. */ function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a % b); } } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds towards infinity instead * of rounding towards zero. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { if (b == 0) { // Guarantee the same behavior as in a regular Solidity division. return a / b; } // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } /** * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or * denominator == 0. * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by * Uniswap Labs also under MIT license. */ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0 = x * y; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { // Solidity will revert if denominator == 0, unlike the div opcode on its own. // The surrounding unchecked block does not change this fact. // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. if (denominator <= prod1) { revert MathOverflowedMulDiv(); } /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. uint256 twos = denominator & (0 - denominator); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also // works in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded * towards zero. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ function sqrt(uint256 a) internal pure returns (uint256) { if (a == 0) { return 0; } // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. // // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. // // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` // // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1 << (log2(a) >> 1); // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision // into the expected uint128 result. unchecked { result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; return min(result, a / result); } } /** * @notice Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); } } /** * @dev Return the log in base 2 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 128; } if (value >> 64 > 0) { value >>= 64; result += 64; } if (value >> 32 > 0) { value >>= 32; result += 32; } if (value >> 16 > 0) { value >>= 16; result += 16; } if (value >> 8 > 0) { value >>= 8; result += 8; } if (value >> 4 > 0) { value >>= 4; result += 4; } if (value >> 2 > 0) { value >>= 2; result += 2; } if (value >> 1 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); } } /** * @dev Return the log in base 10 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10 ** 64) { value /= 10 ** 64; result += 64; } if (value >= 10 ** 32) { value /= 10 ** 32; result += 32; } if (value >= 10 ** 16) { value /= 10 ** 16; result += 16; } if (value >= 10 ** 8) { value /= 10 ** 8; result += 8; } if (value >= 10 ** 4) { value /= 10 ** 4; result += 4; } if (value >= 10 ** 2) { value /= 10 ** 2; result += 2; } if (value >= 10 ** 1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); } } /** * @dev Return the log in base 256 of a positive value rounded towards zero. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 16; } if (value >> 64 > 0) { value >>= 64; result += 8; } if (value >> 32 > 0) { value >>= 32; result += 4; } if (value >> 16 > 0) { value >>= 16; result += 2; } if (value >> 8 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 256, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); } } /** * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. */ function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { return uint8(rounding) % 2 == 1; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol) // This file was procedurally generated from scripts/generate/templates/SafeCast.js. pragma solidity ^0.8.20; /** * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow * checks. * * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can * easily result in undesired exploitation or bugs, since developers usually * assume that overflows raise errors. `SafeCast` restores this intuition by * reverting the transaction when such an operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeCast { /** * @dev Value doesn't fit in an uint of `bits` size. */ error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); /** * @dev An int value doesn't fit in an uint of `bits` size. */ error SafeCastOverflowedIntToUint(int256 value); /** * @dev Value doesn't fit in an int of `bits` size. */ error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); /** * @dev An uint value doesn't fit in an int of `bits` size. */ error SafeCastOverflowedUintToInt(uint256 value); /** * @dev Returns the downcasted uint248 from uint256, reverting on * overflow (when the input is greater than largest uint248). * * Counterpart to Solidity's `uint248` operator. * * Requirements: * * - input must fit into 248 bits */ function toUint248(uint256 value) internal pure returns (uint248) { if (value > type(uint248).max) { revert SafeCastOverflowedUintDowncast(248, value); } return uint248(value); } /** * @dev Returns the downcasted uint240 from uint256, reverting on * overflow (when the input is greater than largest uint240). * * Counterpart to Solidity's `uint240` operator. * * Requirements: * * - input must fit into 240 bits */ function toUint240(uint256 value) internal pure returns (uint240) { if (value > type(uint240).max) { revert SafeCastOverflowedUintDowncast(240, value); } return uint240(value); } /** * @dev Returns the downcasted uint232 from uint256, reverting on * overflow (when the input is greater than largest uint232). * * Counterpart to Solidity's `uint232` operator. * * Requirements: * * - input must fit into 232 bits */ function toUint232(uint256 value) internal pure returns (uint232) { if (value > type(uint232).max) { revert SafeCastOverflowedUintDowncast(232, value); } return uint232(value); } /** * @dev Returns the downcasted uint224 from uint256, reverting on * overflow (when the input is greater than largest uint224). * * Counterpart to Solidity's `uint224` operator. * * Requirements: * * - input must fit into 224 bits */ function toUint224(uint256 value) internal pure returns (uint224) { if (value > type(uint224).max) { revert SafeCastOverflowedUintDowncast(224, value); } return uint224(value); } /** * @dev Returns the downcasted uint216 from uint256, reverting on * overflow (when the input is greater than largest uint216). * * Counterpart to Solidity's `uint216` operator. * * Requirements: * * - input must fit into 216 bits */ function toUint216(uint256 value) internal pure returns (uint216) { if (value > type(uint216).max) { revert SafeCastOverflowedUintDowncast(216, value); } return uint216(value); } /** * @dev Returns the downcasted uint208 from uint256, reverting on * overflow (when the input is greater than largest uint208). * * Counterpart to Solidity's `uint208` operator. * * Requirements: * * - input must fit into 208 bits */ function toUint208(uint256 value) internal pure returns (uint208) { if (value > type(uint208).max) { revert SafeCastOverflowedUintDowncast(208, value); } return uint208(value); } /** * @dev Returns the downcasted uint200 from uint256, reverting on * overflow (when the input is greater than largest uint200). * * Counterpart to Solidity's `uint200` operator. * * Requirements: * * - input must fit into 200 bits */ function toUint200(uint256 value) internal pure returns (uint200) { if (value > type(uint200).max) { revert SafeCastOverflowedUintDowncast(200, value); } return uint200(value); } /** * @dev Returns the downcasted uint192 from uint256, reverting on * overflow (when the input is greater than largest uint192). * * Counterpart to Solidity's `uint192` operator. * * Requirements: * * - input must fit into 192 bits */ function toUint192(uint256 value) internal pure returns (uint192) { if (value > type(uint192).max) { revert SafeCastOverflowedUintDowncast(192, value); } return uint192(value); } /** * @dev Returns the downcasted uint184 from uint256, reverting on * overflow (when the input is greater than largest uint184). * * Counterpart to Solidity's `uint184` operator. * * Requirements: * * - input must fit into 184 bits */ function toUint184(uint256 value) internal pure returns (uint184) { if (value > type(uint184).max) { revert SafeCastOverflowedUintDowncast(184, value); } return uint184(value); } /** * @dev Returns the downcasted uint176 from uint256, reverting on * overflow (when the input is greater than largest uint176). * * Counterpart to Solidity's `uint176` operator. * * Requirements: * * - input must fit into 176 bits */ function toUint176(uint256 value) internal pure returns (uint176) { if (value > type(uint176).max) { revert SafeCastOverflowedUintDowncast(176, value); } return uint176(value); } /** * @dev Returns the downcasted uint168 from uint256, reverting on * overflow (when the input is greater than largest uint168). * * Counterpart to Solidity's `uint168` operator. * * Requirements: * * - input must fit into 168 bits */ function toUint168(uint256 value) internal pure returns (uint168) { if (value > type(uint168).max) { revert SafeCastOverflowedUintDowncast(168, value); } return uint168(value); } /** * @dev Returns the downcasted uint160 from uint256, reverting on * overflow (when the input is greater than largest uint160). * * Counterpart to Solidity's `uint160` operator. * * Requirements: * * - input must fit into 160 bits */ function toUint160(uint256 value) internal pure returns (uint160) { if (value > type(uint160).max) { revert SafeCastOverflowedUintDowncast(160, value); } return uint160(value); } /** * @dev Returns the downcasted uint152 from uint256, reverting on * overflow (when the input is greater than largest uint152). * * Counterpart to Solidity's `uint152` operator. * * Requirements: * * - input must fit into 152 bits */ function toUint152(uint256 value) internal pure returns (uint152) { if (value > type(uint152).max) { revert SafeCastOverflowedUintDowncast(152, value); } return uint152(value); } /** * @dev Returns the downcasted uint144 from uint256, reverting on * overflow (when the input is greater than largest uint144). * * Counterpart to Solidity's `uint144` operator. * * Requirements: * * - input must fit into 144 bits */ function toUint144(uint256 value) internal pure returns (uint144) { if (value > type(uint144).max) { revert SafeCastOverflowedUintDowncast(144, value); } return uint144(value); } /** * @dev Returns the downcasted uint136 from uint256, reverting on * overflow (when the input is greater than largest uint136). * * Counterpart to Solidity's `uint136` operator. * * Requirements: * * - input must fit into 136 bits */ function toUint136(uint256 value) internal pure returns (uint136) { if (value > type(uint136).max) { revert SafeCastOverflowedUintDowncast(136, value); } return uint136(value); } /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). * * Counterpart to Solidity's `uint128` operator. * * Requirements: * * - input must fit into 128 bits */ function toUint128(uint256 value) internal pure returns (uint128) { if (value > type(uint128).max) { revert SafeCastOverflowedUintDowncast(128, value); } return uint128(value); } /** * @dev Returns the downcasted uint120 from uint256, reverting on * overflow (when the input is greater than largest uint120). * * Counterpart to Solidity's `uint120` operator. * * Requirements: * * - input must fit into 120 bits */ function toUint120(uint256 value) internal pure returns (uint120) { if (value > type(uint120).max) { revert SafeCastOverflowedUintDowncast(120, value); } return uint120(value); } /** * @dev Returns the downcasted uint112 from uint256, reverting on * overflow (when the input is greater than largest uint112). * * Counterpart to Solidity's `uint112` operator. * * Requirements: * * - input must fit into 112 bits */ function toUint112(uint256 value) internal pure returns (uint112) { if (value > type(uint112).max) { revert SafeCastOverflowedUintDowncast(112, value); } return uint112(value); } /** * @dev Returns the downcasted uint104 from uint256, reverting on * overflow (when the input is greater than largest uint104). * * Counterpart to Solidity's `uint104` operator. * * Requirements: * * - input must fit into 104 bits */ function toUint104(uint256 value) internal pure returns (uint104) { if (value > type(uint104).max) { revert SafeCastOverflowedUintDowncast(104, value); } return uint104(value); } /** * @dev Returns the downcasted uint96 from uint256, reverting on * overflow (when the input is greater than largest uint96). * * Counterpart to Solidity's `uint96` operator. * * Requirements: * * - input must fit into 96 bits */ function toUint96(uint256 value) internal pure returns (uint96) { if (value > type(uint96).max) { revert SafeCastOverflowedUintDowncast(96, value); } return uint96(value); } /** * @dev Returns the downcasted uint88 from uint256, reverting on * overflow (when the input is greater than largest uint88). * * Counterpart to Solidity's `uint88` operator. * * Requirements: * * - input must fit into 88 bits */ function toUint88(uint256 value) internal pure returns (uint88) { if (value > type(uint88).max) { revert SafeCastOverflowedUintDowncast(88, value); } return uint88(value); } /** * @dev Returns the downcasted uint80 from uint256, reverting on * overflow (when the input is greater than largest uint80). * * Counterpart to Solidity's `uint80` operator. * * Requirements: * * - input must fit into 80 bits */ function toUint80(uint256 value) internal pure returns (uint80) { if (value > type(uint80).max) { revert SafeCastOverflowedUintDowncast(80, value); } return uint80(value); } /** * @dev Returns the downcasted uint72 from uint256, reverting on * overflow (when the input is greater than largest uint72). * * Counterpart to Solidity's `uint72` operator. * * Requirements: * * - input must fit into 72 bits */ function toUint72(uint256 value) internal pure returns (uint72) { if (value > type(uint72).max) { revert SafeCastOverflowedUintDowncast(72, value); } return uint72(value); } /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). * * Counterpart to Solidity's `uint64` operator. * * Requirements: * * - input must fit into 64 bits */ function toUint64(uint256 value) internal pure returns (uint64) { if (value > type(uint64).max) { revert SafeCastOverflowedUintDowncast(64, value); } return uint64(value); } /** * @dev Returns the downcasted uint56 from uint256, reverting on * overflow (when the input is greater than largest uint56). * * Counterpart to Solidity's `uint56` operator. * * Requirements: * * - input must fit into 56 bits */ function toUint56(uint256 value) internal pure returns (uint56) { if (value > type(uint56).max) { revert SafeCastOverflowedUintDowncast(56, value); } return uint56(value); } /** * @dev Returns the downcasted uint48 from uint256, reverting on * overflow (when the input is greater than largest uint48). * * Counterpart to Solidity's `uint48` operator. * * Requirements: * * - input must fit into 48 bits */ function toUint48(uint256 value) internal pure returns (uint48) { if (value > type(uint48).max) { revert SafeCastOverflowedUintDowncast(48, value); } return uint48(value); } /** * @dev Returns the downcasted uint40 from uint256, reverting on * overflow (when the input is greater than largest uint40). * * Counterpart to Solidity's `uint40` operator. * * Requirements: * * - input must fit into 40 bits */ function toUint40(uint256 value) internal pure returns (uint40) { if (value > type(uint40).max) { revert SafeCastOverflowedUintDowncast(40, value); } return uint40(value); } /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). * * Counterpart to Solidity's `uint32` operator. * * Requirements: * * - input must fit into 32 bits */ function toUint32(uint256 value) internal pure returns (uint32) { if (value > type(uint32).max) { revert SafeCastOverflowedUintDowncast(32, value); } return uint32(value); } /** * @dev Returns the downcasted uint24 from uint256, reverting on * overflow (when the input is greater than largest uint24). * * Counterpart to Solidity's `uint24` operator. * * Requirements: * * - input must fit into 24 bits */ function toUint24(uint256 value) internal pure returns (uint24) { if (value > type(uint24).max) { revert SafeCastOverflowedUintDowncast(24, value); } return uint24(value); } /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). * * Counterpart to Solidity's `uint16` operator. * * Requirements: * * - input must fit into 16 bits */ function toUint16(uint256 value) internal pure returns (uint16) { if (value > type(uint16).max) { revert SafeCastOverflowedUintDowncast(16, value); } return uint16(value); } /** * @dev Returns the downcasted uint8 from uint256, reverting on * overflow (when the input is greater than largest uint8). * * Counterpart to Solidity's `uint8` operator. * * Requirements: * * - input must fit into 8 bits */ function toUint8(uint256 value) internal pure returns (uint8) { if (value > type(uint8).max) { revert SafeCastOverflowedUintDowncast(8, value); } return uint8(value); } /** * @dev Converts a signed int256 into an unsigned uint256. * * Requirements: * * - input must be greater than or equal to 0. */ function toUint256(int256 value) internal pure returns (uint256) { if (value < 0) { revert SafeCastOverflowedIntToUint(value); } return uint256(value); } /** * @dev Returns the downcasted int248 from int256, reverting on * overflow (when the input is less than smallest int248 or * greater than largest int248). * * Counterpart to Solidity's `int248` operator. * * Requirements: * * - input must fit into 248 bits */ function toInt248(int256 value) internal pure returns (int248 downcasted) { downcasted = int248(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(248, value); } } /** * @dev Returns the downcasted int240 from int256, reverting on * overflow (when the input is less than smallest int240 or * greater than largest int240). * * Counterpart to Solidity's `int240` operator. * * Requirements: * * - input must fit into 240 bits */ function toInt240(int256 value) internal pure returns (int240 downcasted) { downcasted = int240(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(240, value); } } /** * @dev Returns the downcasted int232 from int256, reverting on * overflow (when the input is less than smallest int232 or * greater than largest int232). * * Counterpart to Solidity's `int232` operator. * * Requirements: * * - input must fit into 232 bits */ function toInt232(int256 value) internal pure returns (int232 downcasted) { downcasted = int232(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(232, value); } } /** * @dev Returns the downcasted int224 from int256, reverting on * overflow (when the input is less than smallest int224 or * greater than largest int224). * * Counterpart to Solidity's `int224` operator. * * Requirements: * * - input must fit into 224 bits */ function toInt224(int256 value) internal pure returns (int224 downcasted) { downcasted = int224(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(224, value); } } /** * @dev Returns the downcasted int216 from int256, reverting on * overflow (when the input is less than smallest int216 or * greater than largest int216). * * Counterpart to Solidity's `int216` operator. * * Requirements: * * - input must fit into 216 bits */ function toInt216(int256 value) internal pure returns (int216 downcasted) { downcasted = int216(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(216, value); } } /** * @dev Returns the downcasted int208 from int256, reverting on * overflow (when the input is less than smallest int208 or * greater than largest int208). * * Counterpart to Solidity's `int208` operator. * * Requirements: * * - input must fit into 208 bits */ function toInt208(int256 value) internal pure returns (int208 downcasted) { downcasted = int208(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(208, value); } } /** * @dev Returns the downcasted int200 from int256, reverting on * overflow (when the input is less than smallest int200 or * greater than largest int200). * * Counterpart to Solidity's `int200` operator. * * Requirements: * * - input must fit into 200 bits */ function toInt200(int256 value) internal pure returns (int200 downcasted) { downcasted = int200(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(200, value); } } /** * @dev Returns the downcasted int192 from int256, reverting on * overflow (when the input is less than smallest int192 or * greater than largest int192). * * Counterpart to Solidity's `int192` operator. * * Requirements: * * - input must fit into 192 bits */ function toInt192(int256 value) internal pure returns (int192 downcasted) { downcasted = int192(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(192, value); } } /** * @dev Returns the downcasted int184 from int256, reverting on * overflow (when the input is less than smallest int184 or * greater than largest int184). * * Counterpart to Solidity's `int184` operator. * * Requirements: * * - input must fit into 184 bits */ function toInt184(int256 value) internal pure returns (int184 downcasted) { downcasted = int184(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(184, value); } } /** * @dev Returns the downcasted int176 from int256, reverting on * overflow (when the input is less than smallest int176 or * greater than largest int176). * * Counterpart to Solidity's `int176` operator. * * Requirements: * * - input must fit into 176 bits */ function toInt176(int256 value) internal pure returns (int176 downcasted) { downcasted = int176(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(176, value); } } /** * @dev Returns the downcasted int168 from int256, reverting on * overflow (when the input is less than smallest int168 or * greater than largest int168). * * Counterpart to Solidity's `int168` operator. * * Requirements: * * - input must fit into 168 bits */ function toInt168(int256 value) internal pure returns (int168 downcasted) { downcasted = int168(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(168, value); } } /** * @dev Returns the downcasted int160 from int256, reverting on * overflow (when the input is less than smallest int160 or * greater than largest int160). * * Counterpart to Solidity's `int160` operator. * * Requirements: * * - input must fit into 160 bits */ function toInt160(int256 value) internal pure returns (int160 downcasted) { downcasted = int160(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(160, value); } } /** * @dev Returns the downcasted int152 from int256, reverting on * overflow (when the input is less than smallest int152 or * greater than largest int152). * * Counterpart to Solidity's `int152` operator. * * Requirements: * * - input must fit into 152 bits */ function toInt152(int256 value) internal pure returns (int152 downcasted) { downcasted = int152(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(152, value); } } /** * @dev Returns the downcasted int144 from int256, reverting on * overflow (when the input is less than smallest int144 or * greater than largest int144). * * Counterpart to Solidity's `int144` operator. * * Requirements: * * - input must fit into 144 bits */ function toInt144(int256 value) internal pure returns (int144 downcasted) { downcasted = int144(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(144, value); } } /** * @dev Returns the downcasted int136 from int256, reverting on * overflow (when the input is less than smallest int136 or * greater than largest int136). * * Counterpart to Solidity's `int136` operator. * * Requirements: * * - input must fit into 136 bits */ function toInt136(int256 value) internal pure returns (int136 downcasted) { downcasted = int136(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(136, value); } } /** * @dev Returns the downcasted int128 from int256, reverting on * overflow (when the input is less than smallest int128 or * greater than largest int128). * * Counterpart to Solidity's `int128` operator. * * Requirements: * * - input must fit into 128 bits */ function toInt128(int256 value) internal pure returns (int128 downcasted) { downcasted = int128(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(128, value); } } /** * @dev Returns the downcasted int120 from int256, reverting on * overflow (when the input is less than smallest int120 or * greater than largest int120). * * Counterpart to Solidity's `int120` operator. * * Requirements: * * - input must fit into 120 bits */ function toInt120(int256 value) internal pure returns (int120 downcasted) { downcasted = int120(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(120, value); } } /** * @dev Returns the downcasted int112 from int256, reverting on * overflow (when the input is less than smallest int112 or * greater than largest int112). * * Counterpart to Solidity's `int112` operator. * * Requirements: * * - input must fit into 112 bits */ function toInt112(int256 value) internal pure returns (int112 downcasted) { downcasted = int112(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(112, value); } } /** * @dev Returns the downcasted int104 from int256, reverting on * overflow (when the input is less than smallest int104 or * greater than largest int104). * * Counterpart to Solidity's `int104` operator. * * Requirements: * * - input must fit into 104 bits */ function toInt104(int256 value) internal pure returns (int104 downcasted) { downcasted = int104(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(104, value); } } /** * @dev Returns the downcasted int96 from int256, reverting on * overflow (when the input is less than smallest int96 or * greater than largest int96). * * Counterpart to Solidity's `int96` operator. * * Requirements: * * - input must fit into 96 bits */ function toInt96(int256 value) internal pure returns (int96 downcasted) { downcasted = int96(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(96, value); } } /** * @dev Returns the downcasted int88 from int256, reverting on * overflow (when the input is less than smallest int88 or * greater than largest int88). * * Counterpart to Solidity's `int88` operator. * * Requirements: * * - input must fit into 88 bits */ function toInt88(int256 value) internal pure returns (int88 downcasted) { downcasted = int88(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(88, value); } } /** * @dev Returns the downcasted int80 from int256, reverting on * overflow (when the input is less than smallest int80 or * greater than largest int80). * * Counterpart to Solidity's `int80` operator. * * Requirements: * * - input must fit into 80 bits */ function toInt80(int256 value) internal pure returns (int80 downcasted) { downcasted = int80(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(80, value); } } /** * @dev Returns the downcasted int72 from int256, reverting on * overflow (when the input is less than smallest int72 or * greater than largest int72). * * Counterpart to Solidity's `int72` operator. * * Requirements: * * - input must fit into 72 bits */ function toInt72(int256 value) internal pure returns (int72 downcasted) { downcasted = int72(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(72, value); } } /** * @dev Returns the downcasted int64 from int256, reverting on * overflow (when the input is less than smallest int64 or * greater than largest int64). * * Counterpart to Solidity's `int64` operator. * * Requirements: * * - input must fit into 64 bits */ function toInt64(int256 value) internal pure returns (int64 downcasted) { downcasted = int64(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(64, value); } } /** * @dev Returns the downcasted int56 from int256, reverting on * overflow (when the input is less than smallest int56 or * greater than largest int56). * * Counterpart to Solidity's `int56` operator. * * Requirements: * * - input must fit into 56 bits */ function toInt56(int256 value) internal pure returns (int56 downcasted) { downcasted = int56(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(56, value); } } /** * @dev Returns the downcasted int48 from int256, reverting on * overflow (when the input is less than smallest int48 or * greater than largest int48). * * Counterpart to Solidity's `int48` operator. * * Requirements: * * - input must fit into 48 bits */ function toInt48(int256 value) internal pure returns (int48 downcasted) { downcasted = int48(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(48, value); } } /** * @dev Returns the downcasted int40 from int256, reverting on * overflow (when the input is less than smallest int40 or * greater than largest int40). * * Counterpart to Solidity's `int40` operator. * * Requirements: * * - input must fit into 40 bits */ function toInt40(int256 value) internal pure returns (int40 downcasted) { downcasted = int40(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(40, value); } } /** * @dev Returns the downcasted int32 from int256, reverting on * overflow (when the input is less than smallest int32 or * greater than largest int32). * * Counterpart to Solidity's `int32` operator. * * Requirements: * * - input must fit into 32 bits */ function toInt32(int256 value) internal pure returns (int32 downcasted) { downcasted = int32(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(32, value); } } /** * @dev Returns the downcasted int24 from int256, reverting on * overflow (when the input is less than smallest int24 or * greater than largest int24). * * Counterpart to Solidity's `int24` operator. * * Requirements: * * - input must fit into 24 bits */ function toInt24(int256 value) internal pure returns (int24 downcasted) { downcasted = int24(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(24, value); } } /** * @dev Returns the downcasted int16 from int256, reverting on * overflow (when the input is less than smallest int16 or * greater than largest int16). * * Counterpart to Solidity's `int16` operator. * * Requirements: * * - input must fit into 16 bits */ function toInt16(int256 value) internal pure returns (int16 downcasted) { downcasted = int16(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(16, value); } } /** * @dev Returns the downcasted int8 from int256, reverting on * overflow (when the input is less than smallest int8 or * greater than largest int8). * * Counterpart to Solidity's `int8` operator. * * Requirements: * * - input must fit into 8 bits */ function toInt8(int256 value) internal pure returns (int8 downcasted) { downcasted = int8(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(8, value); } } /** * @dev Converts an unsigned uint256 into a signed int256. * * Requirements: * * - input must be less than or equal to maxInt256. */ function toInt256(uint256 value) internal pure returns (int256) { // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive if (value > uint256(type(int256).max)) { revert SafeCastOverflowedUintToInt(value); } return int256(value); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. pragma solidity ^0.8.20; /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC1967 implementation slot: * ```solidity * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(newImplementation.code.length > 0); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` */ library StorageSlot { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } struct StringSlot { string value; } struct BytesSlot { bytes value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `StringSlot` with member `value` located at `slot`. */ function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `StringSlot` representation of the string storage pointer `store`. */ function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := store.slot } } /** * @dev Returns an `BytesSlot` with member `value` located at `slot`. */ function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. */ function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := store.slot } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface VRFCoordinatorV2Interface { /** * @notice Get configuration relevant for making requests * @return minimumRequestConfirmations global min for request confirmations * @return maxGasLimit global max for request gas limit * @return s_provingKeyHashes list of registered key hashes */ function getRequestConfig() external view returns (uint16, uint32, bytes32[] memory); /** * @notice Request a set of random words. * @param keyHash - Corresponds to a particular oracle job which uses * that key for generating the VRF proof. Different keyHash's have different gas price * ceilings, so you can select a specific one to bound your maximum per request cost. * @param subId - The ID of the VRF subscription. Must be funded * with the minimum subscription balance required for the selected keyHash. * @param minimumRequestConfirmations - How many blocks you'd like the * oracle to wait before responding to the request. See SECURITY CONSIDERATIONS * for why you may want to request more. The acceptable range is * [minimumRequestBlockConfirmations, 200]. * @param callbackGasLimit - How much gas you'd like to receive in your * fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords * may be slightly less than this amount because of gas used calling the function * (argument decoding etc.), so you may need to request slightly more than you expect * to have inside fulfillRandomWords. The acceptable range is * [0, maxGasLimit] * @param numWords - The number of uint256 random values you'd like to receive * in your fulfillRandomWords callback. Note these numbers are expanded in a * secure way by the VRFCoordinator from a single random value supplied by the oracle. * @return requestId - A unique identifier of the request. Can be used to match * a request to a response in fulfillRandomWords. */ function requestRandomWords( bytes32 keyHash, uint64 subId, uint16 minimumRequestConfirmations, uint32 callbackGasLimit, uint32 numWords ) external returns (uint256 requestId); /** * @notice Create a VRF subscription. * @return subId - A unique subscription id. * @dev You can manage the consumer set dynamically with addConsumer/removeConsumer. * @dev Note to fund the subscription, use transferAndCall. For example * @dev LINKTOKEN.transferAndCall( * @dev address(COORDINATOR), * @dev amount, * @dev abi.encode(subId)); */ function createSubscription() external returns (uint64 subId); /** * @notice Get a VRF subscription. * @param subId - ID of the subscription * @return balance - LINK balance of the subscription in juels. * @return reqCount - number of requests for this subscription, determines fee tier. * @return owner - owner of the subscription. * @return consumers - list of consumer address which are able to use this subscription. */ function getSubscription( uint64 subId ) external view returns (uint96 balance, uint64 reqCount, address owner, address[] memory consumers); /** * @notice Request subscription owner transfer. * @param subId - ID of the subscription * @param newOwner - proposed new owner of the subscription */ function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external; /** * @notice Request subscription owner transfer. * @param subId - ID of the subscription * @dev will revert if original owner of subId has * not requested that msg.sender become the new owner. */ function acceptSubscriptionOwnerTransfer(uint64 subId) external; /** * @notice Add a consumer to a VRF subscription. * @param subId - ID of the subscription * @param consumer - New consumer which can use the subscription */ function addConsumer(uint64 subId, address consumer) external; /** * @notice Remove a consumer from a VRF subscription. * @param subId - ID of the subscription * @param consumer - Consumer to remove from the subscription */ function removeConsumer(uint64 subId, address consumer) external; /** * @notice Cancel a subscription * @param subId - ID of the subscription * @param to - Where to send the remaining LINK to */ function cancelSubscription(uint64 subId, address to) external; /* * @notice Check to see if there exists a request commitment consumers * for all consumers and keyhashes for a given sub. * @param subId - ID of the subscription * @return true if there exists at least one unfulfilled request for the subscription, false * otherwise. */ function pendingRequestExists(uint64 subId) external view returns (bool); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /** **************************************************************************** * @notice Interface for contracts using VRF randomness * ***************************************************************************** * @dev PURPOSE * * @dev Reggie the Random Oracle (not his real job) wants to provide randomness * @dev to Vera the verifier in such a way that Vera can be sure he's not * @dev making his output up to suit himself. Reggie provides Vera a public key * @dev to which he knows the secret key. Each time Vera provides a seed to * @dev Reggie, he gives back a value which is computed completely * @dev deterministically from the seed and the secret key. * * @dev Reggie provides a proof by which Vera can verify that the output was * @dev correctly computed once Reggie tells it to her, but without that proof, * @dev the output is indistinguishable to her from a uniform random sample * @dev from the output space. * * @dev The purpose of this contract is to make it easy for unrelated contracts * @dev to talk to Vera the verifier about the work Reggie is doing, to provide * @dev simple access to a verifiable source of randomness. It ensures 2 things: * @dev 1. The fulfillment came from the VRFCoordinator * @dev 2. The consumer contract implements fulfillRandomWords. * ***************************************************************************** * @dev USAGE * * @dev Calling contracts must inherit from VRFConsumerBase, and can * @dev initialize VRFConsumerBase's attributes in their constructor as * @dev shown: * * @dev contract VRFConsumer { * @dev constructor(<other arguments>, address _vrfCoordinator, address _link) * @dev VRFConsumerBase(_vrfCoordinator) public { * @dev <initialization with other arguments goes here> * @dev } * @dev } * * @dev The oracle will have given you an ID for the VRF keypair they have * @dev committed to (let's call it keyHash). Create subscription, fund it * @dev and your consumer contract as a consumer of it (see VRFCoordinatorInterface * @dev subscription management functions). * @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations, * @dev callbackGasLimit, numWords), * @dev see (VRFCoordinatorInterface for a description of the arguments). * * @dev Once the VRFCoordinator has received and validated the oracle's response * @dev to your request, it will call your contract's fulfillRandomWords method. * * @dev The randomness argument to fulfillRandomWords is a set of random words * @dev generated from your requestId and the blockHash of the request. * * @dev If your contract could have concurrent requests open, you can use the * @dev requestId returned from requestRandomWords to track which response is associated * @dev with which randomness request. * @dev See "SECURITY CONSIDERATIONS" for principles to keep in mind, * @dev if your contract could have multiple requests in flight simultaneously. * * @dev Colliding `requestId`s are cryptographically impossible as long as seeds * @dev differ. * * ***************************************************************************** * @dev SECURITY CONSIDERATIONS * * @dev A method with the ability to call your fulfillRandomness method directly * @dev could spoof a VRF response with any random value, so it's critical that * @dev it cannot be directly called by anything other than this base contract * @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method). * * @dev For your users to trust that your contract's random behavior is free * @dev from malicious interference, it's best if you can write it so that all * @dev behaviors implied by a VRF response are executed *during* your * @dev fulfillRandomness method. If your contract must store the response (or * @dev anything derived from it) and use it later, you must ensure that any * @dev user-significant behavior which depends on that stored value cannot be * @dev manipulated by a subsequent VRF request. * * @dev Similarly, both miners and the VRF oracle itself have some influence * @dev over the order in which VRF responses appear on the blockchain, so if * @dev your contract could have multiple VRF requests in flight simultaneously, * @dev you must ensure that the order in which the VRF responses arrive cannot * @dev be used to manipulate your contract's user-significant behavior. * * @dev Since the block hash of the block which contains the requestRandomness * @dev call is mixed into the input to the VRF *last*, a sufficiently powerful * @dev miner could, in principle, fork the blockchain to evict the block * @dev containing the request, forcing the request to be included in a * @dev different block with a different hash, and therefore a different input * @dev to the VRF. However, such an attack would incur a substantial economic * @dev cost. This cost scales with the number of blocks the VRF oracle waits * @dev until it calls responds to a request. It is for this reason that * @dev that you can signal to an oracle you'd like them to wait longer before * @dev responding to the request (however this is not enforced in the contract * @dev and so remains effective only in the case of unmodified oracle software). */ abstract contract VRFConsumerBaseV2 { error OnlyCoordinatorCanFulfill(address have, address want); address private immutable vrfCoordinator; /** * @param _vrfCoordinator address of VRFCoordinator contract */ constructor(address _vrfCoordinator) { vrfCoordinator = _vrfCoordinator; } /** * @notice fulfillRandomness handles the VRF response. Your contract must * @notice implement it. See "SECURITY CONSIDERATIONS" above for important * @notice principles to keep in mind when implementing your fulfillRandomness * @notice method. * * @dev VRFConsumerBaseV2 expects its subcontracts to have a method with this * @dev signature, and will call it once it has verified the proof * @dev associated with the randomness. (It is triggered via a call to * @dev rawFulfillRandomness, below.) * * @param requestId The Id initially returned by requestRandomness * @param randomWords the VRF output expanded to the requested number of words */ function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual; // rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF // proof. rawFulfillRandomness then calls fulfillRandomness, after validating // the origin of the call function rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) external { if (msg.sender != vrfCoordinator) { revert OnlyCoordinatorCanFulfill(msg.sender, vrfCoordinator); } fulfillRandomWords(requestId, randomWords); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; library Constants { /// @notice Flooring protocol /// @dev floor token amount of 1 NFT (with 18 decimals) uint256 public constant FLOOR_TOKEN_AMOUNT = 1_000_000 ether; /// @dev The minimum vip level required to use `proxy collection` uint8 public constant PROXY_COLLECTION_VIP_THRESHOLD = 3; /// @notice Rolling Bucket Constant Conf uint256 public constant BUCKET_SPAN_1 = 259199 seconds; // BUCKET_SPAN minus 1, used for rounding up uint256 public constant BUCKET_SPAN = 3 days; uint256 public constant MAX_LOCKING_BUCKET = 240; uint256 public constant MAX_LOCKING_PERIOD = 720 days; // MAX LOCKING BUCKET * BUCKET_SPAN /// @notice Auction Config uint256 public constant FREE_AUCTION_PERIOD = 24 hours; uint256 public constant AUCTION_INITIAL_PERIODS = 24 hours; uint256 public constant AUCTION_COMPLETE_GRACE_PERIODS = 2 days; /// @dev admin fee charged per NFT when someone starts aution on expired safebox uint256 public constant AUCTION_ON_EXPIRED_SAFEBOX_COST = 0; /// @dev admin fee charged per NFT when owner starts aution on himself safebox uint256 public constant AUCTION_COST = 100 ether; /// @notice Raffle Config uint256 public constant RAFFLE_COST = 500 ether; uint256 public constant RAFFLE_COMPLETE_GRACE_PERIODS = 2 days; /// @notice Private offer Config uint256 public constant PRIVATE_OFFER_DURATION = 24 hours; uint256 public constant PRIVATE_OFFER_COMPLETE_GRACE_DURATION = 2 days; uint256 public constant PRIVATE_OFFER_COST = 0; uint256 public constant ADD_FREE_NFT_REWARD = 0; /// @notice Lock/Unlock config uint256 public constant USER_SAFEBOX_QUOTA_REFRESH_DURATION = 1 days; uint256 public constant USER_REDEMPTION_WAIVER_REFRESH_DURATION = 1 days; /// @notice The max percentage of the collection that one user can lock uint256 public constant USER_COLLECTION_LOCKED_BOUND_PCT = 50; /// @notice The max locking ratio of the collection that the NFTs in the vault can be redeemed uint256 public constant VAULT_REDEMPTION_MAX_LOKING_RATIO = 80; uint256 public constant VAULT_QUOTA_RESET_PERIOD = 3 days; /// @notice Activities Fee Rate /// @notice Fee rate used to distribute funds that collected from Auctions on expired safeboxes. /// these auction would be settled using credit token uint256 public constant FREE_AUCTION_FEE_RATE_BIPS = 2000; // 20% uint256 public constant VIP_LEVEL_COUNT = 8; struct AuctionBidOption { uint256 extendDurationSecs; uint256 minimumRaisePct; uint256 vipLevel; } function getVipLockingBuckets(uint256 vipLevel) internal pure returns (uint256 buckets) { require(vipLevel < VIP_LEVEL_COUNT); assembly { switch vipLevel case 1 { buckets := 1 } case 2 { buckets := 5 } case 3 { buckets := 20 } case 4 { buckets := 60 } case 5 { buckets := 120 } case 6 { buckets := 180 } case 7 { buckets := MAX_LOCKING_BUCKET } } } function getVipLevel(uint256 totalCredit) internal pure returns (uint8) { if (totalCredit < 30_000 ether) { return 0; } else if (totalCredit < 100_000 ether) { return 1; } else if (totalCredit < 300_000 ether) { return 2; } else if (totalCredit < 1_000_000 ether) { return 3; } else if (totalCredit < 3_000_000 ether) { return 4; } else if (totalCredit < 10_000_000 ether) { return 5; } else if (totalCredit < 30_000_000 ether) { return 6; } else { return 7; } } function getVipBalanceRequirements(uint256 vipLevel) internal pure returns (uint256 required) { require(vipLevel < VIP_LEVEL_COUNT); assembly { switch vipLevel case 1 { required := 30000 } case 2 { required := 100000 } case 3 { required := 300000 } case 4 { required := 1000000 } case 5 { required := 3000000 } case 6 { required := 10000000 } case 7 { required := 30000000 } } /// credit token should be scaled with 18 decimals(1 ether == 10**18) unchecked { return required * 1 ether; } } function getBidOption(uint256 idx) internal pure returns (AuctionBidOption memory) { require(idx < 4); AuctionBidOption[4] memory bidOptions = [ AuctionBidOption({extendDurationSecs: 5 minutes, minimumRaisePct: 1, vipLevel: 0}), AuctionBidOption({extendDurationSecs: 8 hours, minimumRaisePct: 10, vipLevel: 3}), AuctionBidOption({extendDurationSecs: 16 hours, minimumRaisePct: 20, vipLevel: 5}), AuctionBidOption({extendDurationSecs: 24 hours, minimumRaisePct: 40, vipLevel: 7}) ]; return bidOptions[idx]; } function raffleDurations(uint256 idx) internal pure returns (uint256 vipLevel, uint256 duration) { require(idx < 6); vipLevel = idx; assembly { switch idx case 1 { duration := 1 } case 2 { duration := 2 } case 3 { duration := 3 } case 4 { duration := 5 } case 5 { duration := 7 } } unchecked { duration *= 1 days; } } /// return locking ratio restrictions indicates that the vipLevel can utility infinite lock NFTs at corresponding ratio function getLockingRatioForInfinite(uint8 vipLevel) internal pure returns (uint256 ratio) { assembly { switch vipLevel case 1 { ratio := 0 } case 2 { ratio := 0 } case 3 { ratio := 20 } case 4 { ratio := 30 } case 5 { ratio := 40 } case 6 { ratio := 50 } case 7 { ratio := 80 } } } /// return locking ratio restrictions indicates that the vipLevel can utility safebox to lock NFTs at corresponding ratio function getLockingRatioForSafebox(uint8 vipLevel) internal pure returns (uint256 ratio) { assembly { switch vipLevel case 1 { ratio := 10 } case 2 { ratio := 20 } case 3 { ratio := 30 } case 4 { ratio := 40 } case 5 { ratio := 50 } case 6 { ratio := 60 } case 7 { ratio := 70 } } } function getRequiredStakingWithSelfRatio(uint256 requiredStaking, uint256 selfRatio) internal pure returns (uint256) { if (selfRatio < 10) { return requiredStaking; } return (selfRatio + 1) * requiredStaking / 10; } function getVipRequiredStakingWithDiscount(uint256 requiredStaking, uint8 vipLevel) internal pure returns (uint256) { if (vipLevel < 3) { return requiredStaking; } unchecked { /// the higher vip level, more discount for staking /// discount range: 5% - 25% return requiredStaking * (100 - (vipLevel - 2) * 5) / 100; } } function getRequiredStakingForLockRatio(uint256 locked, uint256 totalManaged) internal pure returns (uint256) { if (totalManaged <= 0) { return 1200 ether; } unchecked { uint256 lockingRatioPct = locked * 100 / totalManaged; if (lockingRatioPct <= 40) { return 1200 ether; } else if (lockingRatioPct < 60) { return 1320 ether + ((lockingRatioPct - 40) >> 1) * 120 ether; } else if (lockingRatioPct < 70) { return 2640 ether + ((lockingRatioPct - 60) >> 1) * 240 ether; } else if (lockingRatioPct < 80) { return 4080 ether + ((lockingRatioPct - 70) >> 1) * 480 ether; } else if (lockingRatioPct < 90) { return 6960 ether + ((lockingRatioPct - 80) >> 1) * 960 ether; } else if (lockingRatioPct < 100) { /// 108000 * 2^x return (108000 ether << ((lockingRatioPct - 90) >> 1)) / 5; } else { return 345600 ether; } } } function getVaultAuctionDurationAtLR(uint256 lockingRatio) internal pure returns (uint256) { if (lockingRatio < 80) return 1 hours; else if (lockingRatio < 85) return 3 hours; else if (lockingRatio < 90) return 6 hours; else if (lockingRatio < 95) return 12 hours; else return 24 hours; } function getSafeboxPeriodQuota(uint8 vipLevel) internal pure returns (uint16 quota) { assembly { switch vipLevel case 0 { quota := 0 } case 1 { quota := 1 } case 2 { quota := 2 } case 3 { quota := 4 } case 4 { quota := 8 } case 5 { quota := 16 } case 6 { quota := 32 } case 7 { quota := 64 } } } function getSafeboxUserQuota(uint8 vipLevel) internal pure returns (uint16 quota) { assembly { switch vipLevel case 0 { quota := 0 } case 1 { quota := 4 } case 2 { quota := 8 } case 3 { quota := 16 } case 4 { quota := 32 } case 5 { quota := 64 } case 6 { quota := 128 } case 7 { quota := 256 } } } function getVaultContQuotaAtLR(uint256 lockingRatio) internal pure returns (uint32 contQuota) { if (lockingRatio <= 70) { return 1; } else if (lockingRatio <= 80) { return 2; } else if (lockingRatio <= 90) { return 4; } else { return 8; } } /// two options to redeem from vault /// pay fee with fragment token or consume quota function getVaultFeeAtLR(uint256 lockingRatio) internal pure returns (uint256 fee, uint32 quota) { if (lockingRatio <= 50) { return (20000 ether, 1); } else if (lockingRatio <= 60) { return (40000 ether, 2); } else if (lockingRatio <= 70) { return (60000 ether, 4); } else if (lockingRatio <= 80) { return (80000 ether, 8); } else { /// note. above 80, can not redeem from the vault return (100000 ether, 10); } } /// @return protocol fee after discount function getListingProtocolFeeWithDiscount(uint256 protocolFee, uint8 vipLevel) internal pure returns (uint256) { if (vipLevel < 3) { return protocolFee; } unchecked { /// the higher vip level, more discount for protocol fee /// discount range: 5% - 25% return protocolFee * (100 - (vipLevel - 2) * 5) / 100; } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; library Errors { /// @notice Safe Box error error SafeBoxHasExpire(); error SafeBoxNotExist(); error SafeBoxHasNotExpire(); error SafeBoxAlreadyExist(); error NoMatchingSafeBoxKey(); error SafeBoxKeyAlreadyExist(); /// @notice Auction error error AuctionHasNotCompleted(); error AuctionHasExpire(); error AuctionBidIsNotHighEnough(); error AuctionBidTokenMismatch(); error AuctionSelfBid(); error AuctionInvalidBidAmount(); error AuctionNotExist(); error SafeBoxAuctionWindowHasPassed(); /// @notice Activity common error error NftHasActiveActivities(); error ActivityHasNotCompleted(); error ActivityHasExpired(); error ActivityNotExist(); /// @notice User account error error InsufficientCredit(); error InsufficientBalanceForVipLevel(); error NoPrivilege(); /// @notice Parameter error error InvalidParam(); error NftCollectionNotSupported(); error NftCollectionAlreadySupported(); error ClaimableNftInsufficient(); error TokenNotSupported(); error PeriodQuotaExhausted(); error UserQuotaExhausted(); }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "./IMulticall.sol"; interface IFlooring is IERC721Receiver, IMulticall { /// Admin Operations /// @notice Add new collection for Flooring Protocol function supportNewCollection(address _originalNFT, address fragmentToken) external payable; /// @notice Add new token which will be used as settlement token in Flooring Protocol /// @param addOrRemove `true` means add token, `false` means remove token function supportNewToken(address _tokenAddress, bool addOrRemove) external payable; /// @notice set proxy collection config /// Note. the `tokenId`s of the proxy collection and underlying collection must be correspond one by one /// eg. Paraspace Derivative Token BAYC(nBAYC) -> BAYC function setCollectionProxy(address proxyCollection, address underlyingCollection) external payable; /// @notice withdraw platform fee accumulated. /// Note. withdraw from `address(this)`'s account. function withdrawPlatformFee(address token, uint256 amount) external payable; /// @notice Deposit and lock credit token on behalf of receiver /// user can not withdraw these tokens until `unlockCredit` is called. function addAndLockCredit(address receiver, uint256 amount) external payable; /// @notice Unlock user credit token to allow withdraw /// used to release investors' funds as time goes /// Note. locked credit can be used to operate safeboxes(lock/unlock...) function unlockCredit(address receiver, uint256 amount) external payable; /// User Operations /// @notice User deposits token to the Floor Contract /// @param onBehalfOf deposit token into `onBehalfOf`'s account.(note. the tokens of msg.sender will be transfered) function addTokens(address onBehalfOf, address token, uint256 amount) external payable; /// @notice User removes token from Floor Contract /// @param receiver who will receive the funds.(note. the token of msg.sender will be transfered) function removeTokens(address token, uint256 amount, address receiver) external; /// @notice Lock specified `nftIds` into Flooring Safeboxes and receive corresponding Fragment Tokens of the `collection` /// @param expiryTs when the safeboxes expired, `0` means infinite lock without expiry /// @param vipLevel vip tier required in this lock operation /// @param maxCredit maximum credit can be locked in this operation, if real cost exceeds this limit, the tx will fail /// @param onBehalfOf who will receive the safebox and fragment tokens.(note. the NFTs of the msg.sender will be transfered) function lockNFTs( address collection, uint256[] memory nftIds, uint256 expiryTs, uint256 vipLevel, uint256 maxCredit, address onBehalfOf ) external returns (uint256); /// @notice Extend the exist safeboxes with longer lock duration with more credit token staked /// @param expiryTs new expiry timestamp, should bigger than previous expiry function extendKeys( address collection, uint256[] memory nftIds, uint256 expiryTs, uint256 vipLevel, uint256 maxCredit ) external returns (uint256); /// @notice Unlock specified `nftIds` which had been locked previously /// sender's wallet should have enough Fragment Tokens of the `collection` which will be burned to redeem the NFTs /// @param expiryTs the latest nft's expiry, we need this to clear locking records /// if the value smaller than the latest nft's expiry, the tx will fail /// if part of `nftIds` were locked infinitely, just skip these expiry /// @param receiver who will receive the NFTs. /// note. - The safeboxes of the msg.sender will be removed. /// - The Fragment Tokens of the msg.sender will be burned. function unlockNFTs(address collection, uint256 expiryTs, uint256[] memory nftIds, address receiver) external; /// @notice Fragment specified `nftIds` into Floor Vault and receive Fragment Tokens without any locking /// after fragmented, any one has enough Fragment Tokens can redeem there `nftIds` /// @param onBehalfOf who will receive the fragment tokens and the vault contribution quota.(note. the NFTs of the msg.sender will be transfered) /// if onBehalfOf == address(this), it means msg.sender intends to swap the same quantity of NFTs from the vault as the `nftIds` function fragmentNFTs(address collection, uint256[] memory nftIds, address onBehalfOf) external; /// @notice Kick expired safeboxes to the vault function tidyExpiredNFTs(address collection, uint256[] memory nftIds) external; /// @notice Randomly claim `claimCnt` NFTs from Floor Vault /// sender's wallet should have enough Fragment Tokens of the `collection` which will be burned to redeem the NFTs /// @param maxCredit maximum credit can be costed in this operation, if real cost exceeds this limit, the tx will fail /// @param receiver who will receive the NFTs. /// note. - the msg.sender will pay the redemption cost. /// - The Fragment Tokens of the msg.sender will be burned. function claimRandomNFT(address collection, uint256 claimCnt, uint256 maxCredit, address receiver) external returns (uint256); /// @notice Start auctions on specified `nftIds` with an initial bid price(`bidAmount`) /// This kind of auctions will be settled with Floor Credit Token /// @param bidAmount initial bid price function initAuctionOnExpiredSafeBoxes( address collection, uint256[] memory nftIds, address bidToken, uint256 bidAmount ) external; /// @notice Start auctions on specified `nftIds` index in the vault with an initial bid price(`bidAmount`) /// This kind of auctions will be settled with Fragment Token of the collection /// @param bidAmount initial bid price function initAuctionOnVault(address collection, uint256[] memory vaultIdx, address bidToken, uint96 bidAmount) external; /// @notice Owner starts auctions on his locked Safeboxes /// @param maxExpiry the latest nft's expiry, we need this to clear locking records /// @param token which token should be used to settle auctions(bid, settle) /// @param minimumBid minimum bid price when someone place a bid on the auction function ownerInitAuctions( address collection, uint256[] memory nftIds, uint256 maxExpiry, address token, uint256 minimumBid ) external; /// @notice Place a bid on specified `nftId`'s action /// @param bidAmount bid price /// @param bidOptionIdx which option used to extend auction expiry and bid price function placeBidOnAuction(address collection, uint256 nftId, uint256 bidAmount, uint256 bidOptionIdx) external payable; /// @notice Settle auctions of `nftIds` function settleAuctions(address collection, uint256[] memory nftIds) external; struct RaffleInitParam { address collection; uint256[] nftIds; /// @notice which token used to buy and settle raffle address ticketToken; /// @notice price per ticket uint96 ticketPrice; /// @notice max tickets amount can be sold uint32 maxTickets; /// @notice durationIdx used to get how long does raffles last uint256 duration; /// @notice the largest epxiry of nfts, we need this to clear locking records uint256 maxExpiry; } /// @notice Owner start raffles on locked `nftIds` function ownerInitRaffles(RaffleInitParam memory param) external; /// @notice Buy `nftId`'s raffle tickets /// @param ticketCnt how many tickets should be bought in this operation function buyRaffleTickets(address collectionId, uint256 nftId, uint256 ticketCnt) external payable; /// @notice Settle raffles of `nftIds` function settleRaffles(address collectionId, uint256[] memory nftIds) external; struct PrivateOfferInitParam { address collection; uint256[] nftIds; /// @notice the largest epxiry of nfts, we need this to clear locking records uint256 maxExpiry; /// @notice who will receive the otc offers address receiver; /// @notice which token used to settle offers address token; /// @notice price of the offers uint96 price; } /// @notice Owner start private offers(otc) on locked `nftIds` function ownerInitPrivateOffers(PrivateOfferInitParam memory param) external; enum OfferOpType { Cancel, Decline, ChangePrice } struct ChangeOfferPriceData { uint96[] priceList; } /// @notice Owner or Receiver cancel the private offers of `nftIds` function modifyOffers(address collectionId, uint256[] memory nftIds, OfferOpType opTy, bytes calldata data) external; /// @notice Receiver accept the private offers of `nftIds` function buyerAcceptPrivateOffers(address collectionId, uint256[] memory nftIds, uint256 maxSafeboxExpiry) external payable; /// @notice Clear expired or mismatching safeboxes of `nftIds` in user account /// @param onBehalfOf whose account will be recalculated /// @return credit amount has been released function removeExpiredKeyAndRestoreCredit( address collection, uint256[] memory nftIds, address onBehalfOf, bool verifyLocking ) external returns (uint256); /// @notice Update user's staking credit status by iterating all active collections in user account /// @param onBehalfOf whose account will be recalculated /// @return availableCredit how many credit available to use after this opeartion function recalculateAvailableCredit(address onBehalfOf) external returns (uint256 availableCredit); /// Util operations /// @notice Called by external contracts to access granular pool state /// @param slot Key of slot to sload /// @return value The value of the slot as bytes32 function extsload(bytes32 slot) external view returns (bytes32 value); /// @notice Called by external contracts to access granular pool state /// @param slot Key of slot to start sloading from /// @param nSlots Number of slots to load into return value /// @return value The value of the sload-ed slots concatenated as dynamic bytes function extsload(bytes32 slot, uint256 nSlots) external view returns (bytes memory value); function creditToken() external view returns (address); }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import {AuctionType, Fees} from "../logic/Structs.sol"; interface IFlooringEvent { event NewCollectionSupported(address indexed collection, address indexed floorToken); event UpdateTokenSupported(address indexed token, bool addOrRemove); event ProxyCollectionChanged(address indexed proxyCollection, address indexed underlyingCollection); /// @notice `sender` deposit `token` into Flooring on behalf of `receiver`. `receiver`'s account will be updated. event DepositToken(address indexed sender, address indexed receiver, address indexed token, uint256 amount); /// @notice `sender` withdraw `token` from Flooring and transfer it to `receiver`. event WithdrawToken(address indexed sender, address indexed receiver, address indexed token, uint256 amount); /// @notice update the account maintain credit on behalfOf `onBehalfOf` event UpdateMaintainCredit(address indexed onBehalfOf, uint256 minMaintCredit); /// @notice Lock NFTs /// @param sender who send the tx and pay the NFTs /// @param onBehalfOf who will hold the safeboxes and receive the Fragment Tokens /// @param collection contract addr of the collection /// @param tokenIds nft ids to lock /// @param safeBoxKeys compacted safe box keys with same order of `tokenIds` /// for each key, its format is: [167-160:vipLevel][159-96:keyId][95-0:lockedCredit] /// @param safeBoxExpiryTs expiry timestamp of safeboxes /// @param minMaintCredit `onBehalfOf`'s minMaintCredit after the lock event LockNft( address indexed sender, address indexed onBehalfOf, address indexed collection, uint256[] tokenIds, uint256[] safeBoxKeys, uint256 safeBoxExpiryTs, uint256 minMaintCredit, address proxyCollection ); /// @notice Extend keys /// @param operator who extend the keys /// @param collection contract addr of the collection /// @param tokenIds nft ids to lock /// @param safeBoxKeys compacted safe box keys with same order of `tokenIds` /// for each key, its format is: [167-160:vipLevel][159-96:keyId][95-0:lockedCredit] /// @param safeBoxExpiryTs expiry timestamp of safeboxes /// @param minMaintCredit `operator`'s minMaintCredit after the lock event ExtendKey( address indexed operator, address indexed collection, uint256[] tokenIds, uint256[] safeBoxKeys, uint256 safeBoxExpiryTs, uint256 minMaintCredit ); /// @notice Unlock NFTs /// @param operator who hold the safeboxes that will be unlocked /// @param receiver who will receive the NFTs event UnlockNft( address indexed operator, address indexed receiver, address indexed collection, uint256[] tokenIds, address proxyCollection ); /// @notice `operator` remove invalid keys on behalf of `onBehalfOf`. /// `onBehalfOf`'s account will be updated. event RemoveExpiredKey( address indexed operator, address indexed onBehalfOf, address indexed collection, uint256[] tokenIds, uint256[] safeBoxKeys ); /// @notice [Deprecated] Claim expired safeboxes that maintain NFTs /// @param operator who will pay the redemption cost /// @param receiver who will receive the NFTs /// @param creditCost how many credit token cost in this claim event ClaimExpiredNft( address indexed operator, address indexed receiver, address indexed collection, uint256[] tokenIds, uint256 creditCost, address proxyCollection ); /// @notice Kick expired safeboxes to the vault event ExpiredNftToVault(address indexed operator, address indexed collection, uint256[] tokenIds); /// @notice Fragment NFTs to free pool /// @param operator who will pay the NFTs /// @param onBehalfOf who will receive the Fragment Tokens event FragmentNft( address indexed operator, address indexed onBehalfOf, address indexed collection, uint256[] tokenIds ); /// @notice Claim random NFTs from free pool /// @param operator who will pay the redemption cost /// @param receiver who will receive the NFTs /// @param creditCost how many credit token cost in this claim event ClaimRandomNft( address indexed operator, address indexed receiver, address indexed collection, uint256[] tokenIds, uint256 creditCost ); /// @notice [Deprecated] 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 AuctionStartedV2( address indexed trigger, address indexed collection, uint64[] activityIds, uint256[] tokenIds, AuctionType typ, Fees fees, address settleToken, uint256 minimumBid, uint256 auctionEndTime, uint256 safeBoxExpiryTs, uint256 adminFee ); event NewTopBidOnAuction( address indexed bidder, address indexed collection, uint64 activityId, uint256 tokenId, uint256 bidAmount, uint256 auctionEndTime, uint256 safeBoxExpiryTs ); event AuctionEnded( address indexed winner, address indexed collection, uint64 activityId, uint256 tokenId, uint256 safeBoxKeyId, uint256 collectedFunds ); event RaffleStarted( address indexed owner, address indexed collection, uint64[] activityIds, uint256[] nftIds, uint48 maxTickets, address settleToken, uint96 ticketPrice, Fees fees, uint48 raffleEndTime, uint256 safeBoxExpiryTs, uint256 adminFee ); event RaffleTicketsSold( address indexed buyer, address indexed collection, uint64 activityId, uint256 nftId, uint256 ticketsSold, uint256 cost ); event RaffleSettled( address indexed winner, address indexed collection, uint64 activityId, uint256 nftId, uint256 safeBoxKeyId, uint256 collectedFunds ); event PrivateOfferStarted( address indexed seller, address indexed buyer, address indexed collection, uint64[] activityIds, uint256[] nftIds, address settleToken, uint96 price, uint256 offerEndTime, uint256 safeBoxExpiryTs, uint256 adminFee, Fees fees ); event PrivateOfferCanceled( address indexed operator, address indexed collection, uint64[] activityIds, uint256[] nftIds ); event OfferMetaChanged( address indexed operator, address indexed collection, uint64[] activityIds, uint256[] nftIds, uint96[] price, address settleToken, uint32 offerEndTime, uint32 safeboxExpiryTs ); event PrivateOfferAccepted( address indexed buyer, address indexed collection, uint64[] activityIds, uint256[] nftIds, uint256[] safeBoxKeyIds, uint32 safeboxExpiryTs ); }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; interface IFragmentToken { error CallerIsNotTrustedContract(); function mint(address account, uint256 amount) external; function burn(address account, uint256 amount) external; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; /// @title Multicall interface /// @notice Enables calling multiple methods in a single call to the contract interface IMulticall { /** * @dev A call to an address target failed. The target may have reverted. */ error FailedMulticall(); struct CallData { address target; bytes callData; } /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed /// @param data The encoded function data for each of the calls to make to this contract /// @return results The results from each of the calls passed in via data function multicall(bytes[] calldata data) external returns (bytes[] memory results); /// @notice Allow trusted caller to call specified addresses through the Contract /// @dev The `msg.value` should not be trusted for any method callable from multicall. /// @param calls The encoded function data and target for each of the calls to make to this contract /// @return results The results from each of the calls passed in via calls function extMulticall(CallData[] calldata calls) external returns (bytes[] memory); }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; library Array { /// @notice Compress `data` to [Length]:{[BytesLength][val...]} /// eg. [0, 255, 256] will be convert to bytes series: 0x03 0x00 0x01 0xFF 0x02 0x00 0x01 /// 0x03 means there are 3 numbers /// 0x00 means first number is 0 /// 0x01 means next number(255) has 1 byte to store the real value /// 0xFF equals 255 /// 256 need 2 bytes(0x02) to store, and its value represented in hex is 0x0100 function encodeUints(uint256[] memory data) internal pure returns (bytes memory res) { uint256 dataLen = data.length; require(dataLen <= type(uint8).max); unchecked { uint256 totalBytes; for (uint256 i; i < dataLen; ++i) { uint256 val = data[i]; while (val > 0) { val >>= 8; ++totalBytes; } } res = new bytes(dataLen + totalBytes + 1); assembly { /// skip res's length, store data length mstore8(add(res, 0x20), dataLen) } /// start from the second element idx uint256 resIdx = 0x21; for (uint256 i; i < dataLen; ++i) { uint256 val = data[i]; uint256 byteLen; while (val > 0) { val >>= 8; ++byteLen; } assembly { /// store bytes length of the `i`th element mstore8(add(res, resIdx), byteLen) } ++resIdx; val = data[i]; for (uint256 j; j < byteLen; ++j) { assembly { mstore8(add(res, resIdx), val) } val >>= 8; ++resIdx; } } } } function decodeUints(bytes memory data) internal pure returns (uint256[] memory res) { uint256 dataLen = data.length; require(dataLen > 0); res = new uint256[](uint8(data[0])); uint256 k; unchecked { for (uint256 i = 1; i < dataLen; ++i) { uint256 byteLen = uint8(data[i]); /// if byteLen is zero, it means current element is zero, no need to update `res`, just increment `k` if (byteLen > 0) { uint256 tmp; /// combine next `byteLen` bytes to `tmp` for (uint256 j; j < byteLen; ++j) { /// skip `byteLen` ++i; tmp |= ((uint256(uint8(data[i]))) << (j * 8)); } res[k] = tmp; } ++k; } } } function slice(uint256[] memory data, uint256 start, uint256 length) internal pure returns (uint256[] memory res) { require(start + length <= data.length); res = new uint256[](length); unchecked { start *= 0x20; length *= 0x20; } for (uint256 i = 0x20; i <= length;) { assembly { mstore(add(res, i), mload(add(data, add(i, start)))) } unchecked { i += 0x20; } } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; library CurrencyTransfer { /// @notice Thrown when an ERC20 transfer fails error ERC20TransferFailed(); /// @notice Thrown when an NATIVE transfer fails error NativeTransferFailed(); address public constant NATIVE = address(0); function safeTransfer(address token, address to, uint256 amount) internal { // ref // https://docs.soliditylang.org/en/latest/internals/layout_in_memory.html // implementation from // https://github.com/transmissions11/solmate/blob/v7/src/utils/SafeTransferLib.sol // https://github.com/Uniswap/v4-core/blob/main/contracts/types/Currency.sol bool success; if (token == NATIVE) { assembly { // Transfer the ETH and store if it succeeded or not. success := call(gas(), to, amount, 0, 0, 0, 0) } if (!success) revert NativeTransferFailed(); } else { /// @solidity memory-safe-assembly assembly { // We'll write our calldata to this slot below, but restore it later. let memPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(0, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) mstore(4, to) // Append the "to" argument. mstore(36, amount) // Append the "amount" argument. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because that's the total length of our calldata (4 + 32 * 2) // Counterintuitively, this call() must be positioned after the or() in the // surrounding and() because and() evaluates its arguments from right to left. call(gas(), token, 0, 0, 68, 0, 32) ) mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, memPointer) // Restore the memPointer. } if (!success) revert ERC20TransferFailed(); } } function safeTransferFrom(address token, address from, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let memPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000) mstore(4, from) // Append and mask the "from" argument. mstore(36, to) // Append and mask the "to" argument. // Append the "amount" argument. Masking not required as it's a full 32 byte type. mstore(68, amount) success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, 0, 100, 0, 32) ) mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, memPointer) // Restore the memPointer. } if (!success) revert ERC20TransferFailed(); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; library ERC721Transfer { /// @notice Thrown when an ERC721 transfer fails error ERC721TransferFailed(); function safeTransferFrom(address collection, address from, address to, uint256 tokenId) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let memPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(0, 0x42842e0e00000000000000000000000000000000000000000000000000000000) mstore(4, from) // Append and mask the "from" argument. mstore(36, to) // Append and mask the "to" argument. // Append the "tokenId" argument. Masking not required as it's a full 32 byte type. mstore(68, tokenId) success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), collection, 0, 0, 100, 0, 32) ) mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, memPointer) // Restore the memPointer. } if (!success) revert ERC721TransferFailed(); } function safeBatchTransferFrom(address collection, address from, address to, uint256[] memory tokenIds) internal { unchecked { uint256 len = tokenIds.length; for (uint256 i; i < len; ++i) { safeTransferFrom(collection, from, to, tokenIds[i]); } } } }
// SPDX-License-Identifier: MIT pragma solidity >=0.8.0; /// @notice Simple single owner authorization mixin. /// @author modified from Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol) abstract contract OwnedUpgradeable { error Unauthorized(); event OwnerUpdated(address indexed user, address indexed newOwner); address public owner; modifier onlyOwner() virtual { checkOwner(); _; } function checkOwner() internal view { if (msg.sender != owner) revert Unauthorized(); } function __Owned_init() internal { owner = msg.sender; } function setOwner(address newOwner) public virtual onlyOwner { owner = newOwner; emit OwnerUpdated(msg.sender, newOwner); } uint256[49] private __gap; }
// 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: MIT pragma solidity >=0.8.0; import {OwnedUpgradeable} from "./OwnedUpgradeable.sol"; abstract contract TrustedUpgradeable is OwnedUpgradeable { event TrustedUpdated(address trusted, bool setOrUnset); mapping(address => uint256) public whitelist; modifier onlyTrusted() virtual { checkTrusted(); _; } function checkTrusted() internal view { if (whitelist[msg.sender] == 0) revert Unauthorized(); } function __Trusted_init() internal { __Owned_init(); whitelist[owner] = 1; } function setTrusted(address trusted, bool trust) public virtual onlyOwner { if (trust) whitelist[trusted] = 1; else delete whitelist[trusted]; emit TrustedUpdated(trusted, trust); } uint256[49] private __gap; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {SafeBox, CollectionState, AuctionInfo, FeeConfig, Fees, FeeRate, AuctionType} from "./Structs.sol"; import "./User.sol"; import "./Collection.sol"; import "./Helper.sol"; import "../Errors.sol"; import "../Constants.sol"; import "../interface/IFlooring.sol"; import {SafeBoxLib} from "./SafeBox.sol"; import "../library/RollingBuckets.sol"; library AuctionLib { using SafeCast for uint256; using CollectionLib for CollectionState; using SafeBoxLib for SafeBox; using RollingBuckets for mapping(uint256 => uint256); using UserLib for UserFloorAccount; using UserLib for CollectionAccount; using Helper for CollectionState; event AuctionStartedV2( address indexed trigger, address indexed collection, uint64[] activityIds, uint256[] tokenIds, AuctionType typ, Fees fees, address settleToken, uint256 minimumBid, uint256 auctionEndTime, uint256 safeBoxExpiryTs, uint256 adminFee ); event NewTopBidOnAuction( address indexed bidder, address indexed collection, uint64 activityId, uint256 tokenId, uint256 bidAmount, uint256 auctionEndTime, uint256 safeBoxExpiryTs ); event AuctionEnded( address indexed winner, address indexed collection, uint64 activityId, uint256 tokenId, uint256 safeBoxKeyId, uint256 collectedFunds ); function ownerInitAuctions( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, FeeConfig storage feeConf, address creditToken, address collectionId, uint256[] memory nftIds, uint256 maxExpiry, address token, uint256 minimumBid ) public { if (feeConf.safeboxFee.receipt == address(0)) revert Errors.TokenNotSupported(); UserFloorAccount storage userAccount = userAccounts[msg.sender]; uint256 adminFee = Constants.AUCTION_COST * nftIds.length; /// transfer fee to contract account userAccount.transferToken(userAccounts[address(this)], creditToken, adminFee, true); AuctionInfo memory auctionTemplate; auctionTemplate.endTime = uint96(block.timestamp + Constants.AUCTION_INITIAL_PERIODS); auctionTemplate.bidTokenAddress = token; auctionTemplate.minimumBid = minimumBid.toUint96(); auctionTemplate.triggerAddress = msg.sender; auctionTemplate.lastBidAmount = 0; auctionTemplate.lastBidder = address(0); auctionTemplate.typ = AuctionType.Owned; auctionTemplate.fees = Fees({ royalty: FeeRate({receipt: address(0), rateBips: 0}), protocol: FeeRate({receipt: feeConf.safeboxFee.receipt, rateBips: feeConf.safeboxFee.auctionOwned}) }); (uint64[] memory activityIds, uint192 newExpiryTs) = _ownerInitAuctions(collection, userAccount.getByKey(collectionId), nftIds, maxExpiry, auctionTemplate); emit AuctionStartedV2( msg.sender, collectionId, activityIds, nftIds, auctionTemplate.typ, auctionTemplate.fees, token, minimumBid, auctionTemplate.endTime, newExpiryTs, adminFee ); } function _ownerInitAuctions( CollectionState storage collectionState, CollectionAccount storage userAccount, uint256[] memory nftIds, uint256 maxExpiry, AuctionInfo memory auctionTemplate ) private returns (uint64[] memory activityIds, uint32 newExpiryTs) { newExpiryTs = uint32(auctionTemplate.endTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS); uint256 firstIdx = Helper.counterStamp(newExpiryTs) - Helper.counterStamp(block.timestamp); uint256[] memory toUpdateBucket; /// if maxExpiryTs == 0, it means all nftIds in this batch being locked infinitely that we don't need to update countingBuckets if (maxExpiry > 0) { toUpdateBucket = collectionState.countingBuckets.batchGet( Helper.counterStamp(block.timestamp), Math.min(Helper.counterStamp(maxExpiry), collectionState.lastUpdatedBucket) ); } activityIds = new uint64[](nftIds.length); for (uint256 i = 0; i < nftIds.length;) { if (collectionState.hasActiveActivities(nftIds[i])) revert Errors.NftHasActiveActivities(); (SafeBox storage safeBox,) = collectionState.useSafeBoxAndKey(userAccount, nftIds[i]); if (safeBox.isInfiniteSafeBox()) { --collectionState.infiniteCnt; } else { uint256 oldExpiryTs = safeBox.expiryTs; if (oldExpiryTs < newExpiryTs) { revert Errors.InvalidParam(); } uint256 lastIdx = Helper.counterStamp(oldExpiryTs) - Helper.counterStamp(block.timestamp); if (firstIdx > lastIdx || lastIdx > toUpdateBucket.length) revert Errors.InvalidParam(); for (uint256 k = firstIdx; k < lastIdx;) { --toUpdateBucket[k]; unchecked { ++k; } } } safeBox.expiryTs = newExpiryTs; activityIds[i] = collectionState.generateNextActivityId(); auctionTemplate.activityId = activityIds[i]; collectionState.activeAuctions[nftIds[i]] = auctionTemplate; unchecked { ++i; } } if (toUpdateBucket.length > 0) { collectionState.countingBuckets.batchSet(Helper.counterStamp(block.timestamp), toUpdateBucket); } } function initAuctionOnExpiredSafeBoxes( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, FeeConfig storage feeConf, address creditToken, address collectionId, uint256[] memory nftIds, address bidToken, uint256 bidAmount ) public { if (feeConf.safeboxFee.receipt == address(0)) revert Errors.TokenNotSupported(); { uint256 lockingRatio = Helper.calculateLockingRatio(collection, 0); (uint256 currentFee,) = Constants.getVaultFeeAtLR(lockingRatio); if (bidAmount < currentFee) revert Errors.AuctionInvalidBidAmount(); } AuctionInfo memory auctionTemplate; auctionTemplate.endTime = uint96(block.timestamp + Constants.AUCTION_INITIAL_PERIODS); auctionTemplate.bidTokenAddress = bidToken; auctionTemplate.minimumBid = bidAmount.toUint96(); auctionTemplate.triggerAddress = msg.sender; auctionTemplate.lastBidAmount = bidAmount.toUint96(); auctionTemplate.lastBidder = msg.sender; auctionTemplate.typ = AuctionType.Expired; auctionTemplate.fees = Fees({ royalty: FeeRate({receipt: address(0), rateBips: 0}), protocol: FeeRate({receipt: feeConf.safeboxFee.receipt, rateBips: feeConf.safeboxFee.auctionExpired}) }); (uint64[] memory activityIds, uint192 newExpiry) = _initAuctionOnExpiredSafeBoxes(collection, nftIds, auctionTemplate); uint256 adminFee = Constants.AUCTION_ON_EXPIRED_SAFEBOX_COST * nftIds.length; if (bidToken == creditToken) { userAccounts[msg.sender].transferToken( userAccounts[address(this)], bidToken, bidAmount * nftIds.length + adminFee, true ); } else { userAccounts[msg.sender].transferToken( userAccounts[address(this)], bidToken, bidAmount * nftIds.length, false ); if (adminFee > 0) { userAccounts[msg.sender].transferToken(userAccounts[address(this)], creditToken, adminFee, true); } } emit AuctionStartedV2( msg.sender, collectionId, activityIds, nftIds, auctionTemplate.typ, auctionTemplate.fees, bidToken, bidAmount, auctionTemplate.endTime, newExpiry, adminFee ); } function _initAuctionOnExpiredSafeBoxes( CollectionState storage collectionState, uint256[] memory nftIds, AuctionInfo memory auctionTemplate ) private returns (uint64[] memory activityIds, uint32 newExpiry) { newExpiry = uint32(auctionTemplate.endTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS); activityIds = new uint64[](nftIds.length); for (uint256 idx; idx < nftIds.length;) { uint256 nftId = nftIds[idx]; if (collectionState.hasActiveActivities(nftId)) revert Errors.NftHasActiveActivities(); SafeBox storage safeBox = collectionState.useSafeBox(nftId); if (!safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasNotExpire(); if (Helper.isAuctionPeriodOver(safeBox)) revert Errors.SafeBoxAuctionWindowHasPassed(); activityIds[idx] = collectionState.generateNextActivityId(); auctionTemplate.activityId = activityIds[idx]; collectionState.activeAuctions[nftId] = auctionTemplate; /// We keep the owner of safebox unchanged, and it will be used to distribute auction funds safeBox.expiryTs = newExpiry; safeBox.keyId = SafeBoxLib.SAFEBOX_KEY_NOTATION; unchecked { ++idx; } } Helper.applyDiffToCounters( collectionState, Helper.counterStamp(block.timestamp), Helper.counterStamp(newExpiry), int256(nftIds.length) ); } function initAuctionOnVault( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, FeeConfig storage feeConf, address creditToken, address collectionId, uint256[] memory vaultIdx, address bidToken, uint96 bidAmount ) public { if (vaultIdx.length != 1) revert Errors.InvalidParam(); if (feeConf.vaultFee.receipt == address(0)) revert Errors.TokenNotSupported(); { /// check auction period and bid price uint256 lockingRatio = Helper.calculateLockingRatio(collection, 0); uint256 periodDuration = Constants.getVaultAuctionDurationAtLR(lockingRatio); if (block.timestamp - collection.lastVaultAuctionPeriodTs <= periodDuration) { revert Errors.PeriodQuotaExhausted(); } (uint256 currentFee,) = Constants.getVaultFeeAtLR(lockingRatio); if (bidAmount < currentFee) revert Errors.AuctionInvalidBidAmount(); } AuctionInfo memory auctionTemplate; auctionTemplate.endTime = uint96(block.timestamp + Constants.AUCTION_INITIAL_PERIODS); auctionTemplate.bidTokenAddress = bidToken; auctionTemplate.minimumBid = bidAmount; auctionTemplate.triggerAddress = msg.sender; auctionTemplate.lastBidAmount = bidAmount; auctionTemplate.lastBidder = msg.sender; auctionTemplate.typ = AuctionType.Vault; auctionTemplate.fees = Fees({ royalty: FeeRate({receipt: feeConf.royalty.receipt, rateBips: feeConf.royalty.vault}), protocol: FeeRate({receipt: feeConf.vaultFee.receipt, rateBips: feeConf.vaultFee.vaultAuction}) }); SafeBox memory safeboxTemplate = SafeBox({ keyId: SafeBoxLib.SAFEBOX_KEY_NOTATION, expiryTs: uint32(auctionTemplate.endTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS), owner: address(this) }); uint64[] memory activityIds = new uint64[](vaultIdx.length); /// vaultIdx keeps asc order for (uint256 i = vaultIdx.length; i > 0;) { unchecked { --i; } if (vaultIdx[i] >= collection.freeTokenIds.length) revert Errors.InvalidParam(); uint256 nftId = collection.freeTokenIds[vaultIdx[i]]; collection.addSafeBox(nftId, safeboxTemplate); auctionTemplate.activityId = collection.generateNextActivityId(); collection.activeAuctions[nftId] = auctionTemplate; activityIds[i] = auctionTemplate.activityId; collection.freeTokenIds[vaultIdx[i]] = collection.freeTokenIds[collection.freeTokenIds.length - 1]; collection.freeTokenIds.pop(); /// reuse the array vaultIdx[i] = nftId; } userAccounts[msg.sender].transferToken( userAccounts[address(this)], auctionTemplate.bidTokenAddress, bidAmount * vaultIdx.length, bidToken == creditToken ); Helper.applyDiffToCounters( collection, Helper.counterStamp(block.timestamp), Helper.counterStamp(safeboxTemplate.expiryTs), int256(vaultIdx.length) ); /// update auction timestamp collection.lastVaultAuctionPeriodTs = uint32(block.timestamp); emit AuctionStartedV2( msg.sender, collectionId, activityIds, vaultIdx, auctionTemplate.typ, auctionTemplate.fees, auctionTemplate.bidTokenAddress, bidAmount, auctionTemplate.endTime, safeboxTemplate.expiryTs, 0 ); } struct BidParam { uint256 nftId; uint96 bidAmount; address bidder; uint256 extendDuration; uint256 minIncrPct; } function placeBidOnAuction( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, address creditToken, address collectionId, uint256 nftId, uint256 bidAmount, uint256 bidOptionIdx ) public { uint256 prevBidAmount; address prevBidder; { Constants.AuctionBidOption memory bidOption = Constants.getBidOption(bidOptionIdx); userAccounts[msg.sender].ensureVipCredit(uint8(bidOption.vipLevel), creditToken); (prevBidAmount, prevBidder) = _placeBidOnAuction( collection, BidParam( nftId, bidAmount.toUint96(), msg.sender, bidOption.extendDurationSecs, bidOption.minimumRaisePct ) ); } AuctionInfo memory auction = collection.activeAuctions[nftId]; address bidToken = auction.bidTokenAddress; userAccounts[msg.sender].transferToken( userAccounts[address(this)], bidToken, bidAmount, bidToken == creditToken ); if (prevBidAmount > 0) { /// refund previous bid /// contract account no need to check credit requirements userAccounts[address(this)].transferToken(userAccounts[prevBidder], bidToken, prevBidAmount, false); } SafeBox memory safebox = collection.safeBoxes[nftId]; emit NewTopBidOnAuction( msg.sender, collectionId, auction.activityId, nftId, bidAmount, auction.endTime, safebox.expiryTs ); } function _placeBidOnAuction(CollectionState storage collectionState, BidParam memory param) private returns (uint128 prevBidAmount, address prevBidder) { AuctionInfo storage auctionInfo = collectionState.activeAuctions[param.nftId]; SafeBox storage safeBox = collectionState.useSafeBox(param.nftId); uint256 endTime = auctionInfo.endTime; { (prevBidAmount, prevBidder) = (auctionInfo.lastBidAmount, auctionInfo.lastBidder); // param check if (endTime == 0) revert Errors.AuctionNotExist(); if (endTime <= block.timestamp) revert Errors.AuctionHasExpire(); if (prevBidAmount >= param.bidAmount || auctionInfo.minimumBid > param.bidAmount) { revert Errors.AuctionBidIsNotHighEnough(); } if (prevBidder == param.bidder) revert Errors.AuctionSelfBid(); // owner starts auction, can not bid by himself if (auctionInfo.typ == AuctionType.Owned && param.bidder == safeBox.owner) revert Errors.AuctionSelfBid(); if (prevBidAmount > 0 && !isValidNewBid(param.bidAmount, prevBidAmount, param.minIncrPct)) { revert Errors.AuctionInvalidBidAmount(); } } /// Changing safebox key id which means the corresponding safebox key doesn't hold the safebox now safeBox.keyId = SafeBoxLib.SAFEBOX_KEY_NOTATION; uint256 newAuctionEndTime = block.timestamp + param.extendDuration; if (newAuctionEndTime > endTime) { uint256 newSafeBoxExpiryTs = newAuctionEndTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS; Helper.applyDiffToCounters( collectionState, Helper.counterStamp(safeBox.expiryTs), Helper.counterStamp(newSafeBoxExpiryTs), 1 ); safeBox.expiryTs = uint32(newSafeBoxExpiryTs); auctionInfo.endTime = uint96(newAuctionEndTime); } auctionInfo.lastBidAmount = param.bidAmount; auctionInfo.lastBidder = param.bidder; } function settleAuctions( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, address collectionId, uint256[] memory nftIds ) public { for (uint256 i; i < nftIds.length;) { uint256 nftId = nftIds[i]; SafeBox storage safebox = Helper.useSafeBox(collection, nftId); if (safebox.isSafeBoxExpired()) revert Errors.SafeBoxHasExpire(); AuctionInfo memory auctionInfo = collection.activeAuctions[nftId]; if (auctionInfo.endTime == 0) revert Errors.AuctionNotExist(); if (auctionInfo.endTime > block.timestamp) revert Errors.AuctionHasNotCompleted(); /// noone bid on the aciton, can not be settled if (auctionInfo.lastBidder == address(0)) revert Errors.AuctionHasNotCompleted(); distributeFunds(userAccounts, auctionInfo, safebox.owner); /// transfer safebox address winner = auctionInfo.lastBidder; SafeBoxKey memory key = SafeBoxKey({keyId: collection.generateNextKeyId(), lockingCredit: 0, vipLevel: 0}); safebox.keyId = key.keyId; safebox.owner = winner; UserFloorAccount storage account = userAccounts[winner]; CollectionAccount storage userCollectionAccount = account.getByKey(collectionId); userCollectionAccount.addSafeboxKey(nftId, key); delete collection.activeAuctions[nftId]; emit AuctionEnded(winner, collectionId, auctionInfo.activityId, nftId, key.keyId, auctionInfo.lastBidAmount); unchecked { ++i; } } } function distributeFunds( mapping(address => UserFloorAccount) storage accounts, AuctionInfo memory auction, address owner ) private { /// contract account no need to check credit requirements address token = auction.bidTokenAddress; Fees memory fees = auction.fees; (uint256 priceWithoutFee, uint256 protocolFee, uint256 royalty) = Helper.computeFees(auction.lastBidAmount, fees); UserFloorAccount storage contractAccount = accounts[address(this)]; if (royalty > 0) { contractAccount.transferToken(accounts[fees.royalty.receipt], token, royalty, false); } if (protocolFee > 0) { contractAccount.transferToken(accounts[fees.protocol.receipt], token, protocolFee, false); } if (priceWithoutFee > 0) { contractAccount.transferToken(accounts[owner], token, priceWithoutFee, false); } } function isValidNewBid(uint256 newBid, uint256 previousBid, uint256 minRaisePct) private pure returns (bool) { uint256 minIncrement = previousBid * minRaisePct / 100; if (minIncrement < 1) { minIncrement = 1; } if (newBid < previousBid + minIncrement) { return false; } // think: always thought this should be previousBid.... uint256 newIncrementAmount = newBid / 100; if (newIncrementAmount < 1) { newIncrementAmount = 1; } return newBid % newIncrementAmount == 0; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "../library/RollingBuckets.sol"; import "../library/ERC721Transfer.sol"; import "../library/Array.sol"; import "../Errors.sol"; import "../Constants.sol"; import "./User.sol"; import "./Helper.sol"; import "./Structs.sol"; import {SafeBoxLib} from "./SafeBox.sol"; import "../interface/IFlooring.sol"; library CollectionLib { using SafeBoxLib for SafeBox; using SafeCast for uint256; using RollingBuckets for mapping(uint256 => uint256); using UserLib for CollectionAccount; using UserLib for UserFloorAccount; event LockNft( address indexed sender, address indexed onBehalfOf, address indexed collection, uint256[] tokenIds, uint256[] safeBoxKeys, uint256 safeBoxExpiryTs, uint256 minMaintCredit, address proxyCollection ); event ExtendKey( address indexed operator, address indexed collection, uint256[] tokenIds, uint256[] safeBoxKeys, uint256 safeBoxExpiryTs, uint256 minMaintCredit ); event UnlockNft( address indexed operator, address indexed receiver, address indexed collection, uint256[] tokenIds, address proxyCollection ); event RemoveExpiredKey( address indexed operator, address indexed onBehalfOf, address indexed collection, uint256[] tokenIds, uint256[] safeBoxKeys ); event ExpiredNftToVault(address indexed operator, address indexed collection, uint256[] tokenIds); event FragmentNft( address indexed operator, address indexed onBehalfOf, address indexed collection, uint256[] tokenIds ); event ClaimRandomNft( address indexed operator, address indexed receiver, address indexed collection, uint256[] tokenIds, uint256 creditCost ); function fragmentNFTs( CollectionState storage collectionState, UserFloorAccount storage account, address collection, uint256[] memory nftIds, address onBehalfOf ) public { uint256 nftLen = nftIds.length; /// tricky logic: if `onBehalfOf` is the contract, shaffle the correspounding amount of NFTs bool shuffle = onBehalfOf == address(this); uint256[] memory pickedTokenIds; if (shuffle) { uint256 vaultCnt = collectionState.freeTokenIds.length; /// no enough nft to shuffle if (nftLen > vaultCnt) revert Errors.ClaimableNftInsufficient(); (pickedTokenIds,) = pickRandomFromVault( collectionState, vaultCnt, vaultCnt + collectionState.activeSafeBoxCnt, nftLen, true ); } /// after shuffling, supply new NFTs to vault uint32 contQuota; for (uint256 i; i < nftLen;) { collectionState.freeTokenIds.push(nftIds[i]); if (!shuffle) { contQuota += Constants.getVaultContQuotaAtLR(Helper.calculateLockingRatio(collectionState, 0)); } unchecked { ++i; } } if (!shuffle) { /// if no shuffling, give back the Fragment Tokens collectionState.floorToken.mint(onBehalfOf, Constants.FLOOR_TOKEN_AMOUNT * nftLen); Helper.checkAndUpdateVaultQuota(account.getByKey(collection), int32(contQuota)); ERC721Transfer.safeBatchTransferFrom(collection, msg.sender, address(this), nftIds); } else { /// if shuffling, transfer user's NFTs first to avoid repetition. ERC721Transfer.safeBatchTransferFrom(collection, msg.sender, address(this), nftIds); /// if shuffling, give back the picked NFTs ERC721Transfer.safeBatchTransferFrom(collection, address(this), msg.sender, pickedTokenIds); /// tracking the out NFTs emit ClaimRandomNft(msg.sender, msg.sender, collection, pickedTokenIds, 0); } emit FragmentNft(msg.sender, onBehalfOf, collection, nftIds); } struct LockInfo { bool isInfinite; uint256 currentBucket; uint256 newExpiryBucket; uint256 selfLocked; uint256 totalManaged; uint256 newRequireLockCredit; uint64 infiniteCnt; } function lockNfts( CollectionState storage collection, UserFloorAccount storage account, LockParam memory param, address onBehalfOf ) public returns (uint256 totalCreditCost) { if (onBehalfOf == address(this)) revert Errors.InvalidParam(); /// proxy collection only enabled when infinity lock if (param.collection != param.proxyCollection && param.expiryTs != 0) revert Errors.InvalidParam(); uint256 totalCredit = account.ensureVipCredit(param.vipLevel, param.creditToken); Helper.ensureMaxLocking(collection, param.vipLevel, param.expiryTs, param.nftIds.length, false); { uint8 maxVipLevel = Constants.getVipLevel(totalCredit); uint256 newLocked = param.nftIds.length; Helper.ensureProxyVipLevel(maxVipLevel, param.collection != param.proxyCollection); /// check period quota and global quota for the account Helper.checkAndUpdateUserSafeboxQuota(account, maxVipLevel, newLocked.toUint16()); /// don't try to add the collection account Helper.checkCollectionSafeboxQuota(account.getByKey(param.collection), collection, newLocked); } /// cache value to avoid multi-reads uint256 minMaintCredit = account.minMaintCredit; uint256[] memory nftIds = param.nftIds; uint256[] memory newKeys; { CollectionAccount storage userCollectionAccount = account.getOrAddCollection(param.collection); (totalCreditCost, newKeys) = _lockNfts(collection, userCollectionAccount, nftIds, param.expiryTs, param.vipLevel); // compute max credit for locking cost uint96 totalLockingCredit = userCollectionAccount.totalLockingCredit; { uint256 creditBuffer; unchecked { creditBuffer = totalCredit - totalLockingCredit; } if (totalCreditCost > creditBuffer || totalCreditCost > param.maxCreditCost) { revert Errors.InsufficientCredit(); } } totalLockingCredit += totalCreditCost.toUint96(); userCollectionAccount.totalLockingCredit = totalLockingCredit; if (totalLockingCredit > minMaintCredit) { account.minMaintCredit = totalLockingCredit; minMaintCredit = totalLockingCredit; } } account.updateVipKeyCount(param.vipLevel, int256(nftIds.length)); /// mint for `onBehalfOf`, transfer from msg.sender collection.floorToken.mint(onBehalfOf, Constants.FLOOR_TOKEN_AMOUNT * nftIds.length); ERC721Transfer.safeBatchTransferFrom(param.proxyCollection, msg.sender, address(this), nftIds); emit LockNft( msg.sender, onBehalfOf, param.collection, nftIds, newKeys, param.expiryTs, minMaintCredit, param.proxyCollection ); } function _lockNfts( CollectionState storage collectionState, CollectionAccount storage account, uint256[] memory nftIds, uint256 expiryTs, // treat 0 as infinite lock. uint8 vipLevel ) private returns (uint256, uint256[] memory) { LockInfo memory info = LockInfo({ isInfinite: expiryTs == 0, currentBucket: Helper.counterStamp(block.timestamp), newExpiryBucket: Helper.counterStamp(expiryTs), selfLocked: account.keyCnt, totalManaged: collectionState.activeSafeBoxCnt + collectionState.freeTokenIds.length, newRequireLockCredit: 0, infiniteCnt: collectionState.infiniteCnt }); if (info.isInfinite) { /// if it is infinite lock, we need load all buckets to calculate the staking cost info.newExpiryBucket = Helper.counterStamp(block.timestamp + Constants.MAX_LOCKING_PERIOD); } uint256[] memory buckets = Helper.prepareBucketUpdate(collectionState, info.currentBucket, info.newExpiryBucket); /// @dev `keys` used to log info, we just compact its fields into one 256 bits number uint256[] memory keys = new uint256[](nftIds.length); for (uint256 idx; idx < nftIds.length;) { uint256 lockedCredit = updateCountersAndGetSafeboxCredit(buckets, info, vipLevel); if (info.isInfinite) ++info.infiniteCnt; SafeBoxKey memory key = SafeBoxKey({ keyId: Helper.generateNextKeyId(collectionState), lockingCredit: lockedCredit.toUint96(), vipLevel: vipLevel }); account.addSafeboxKey(nftIds[idx], key); addSafeBox( collectionState, nftIds[idx], SafeBox({keyId: key.keyId, expiryTs: uint32(expiryTs), owner: msg.sender}) ); keys[idx] = SafeBoxLib.encodeSafeBoxKey(key); info.newRequireLockCredit += lockedCredit; unchecked { ++info.totalManaged; ++info.selfLocked; ++idx; } } if (info.isInfinite) { collectionState.infiniteCnt = info.infiniteCnt; } else { collectionState.countingBuckets.batchSet(info.currentBucket, buckets); if (info.newExpiryBucket > collectionState.lastUpdatedBucket) { collectionState.lastUpdatedBucket = uint64(info.newExpiryBucket); } } return (info.newRequireLockCredit, keys); } function unlockNfts( CollectionState storage collection, UserFloorAccount storage userAccount, address proxyCollection, address collectionId, uint256[] memory nftIds, uint256 maxExpiryTs, address receiver ) public { CollectionAccount storage userCollectionAccount = userAccount.getByKey(collectionId); SafeBoxKey[] memory releasedKeys = _unlockNfts(collection, maxExpiryTs, nftIds, userCollectionAccount); for (uint256 i = 0; i < releasedKeys.length;) { userAccount.updateVipKeyCount(releasedKeys[i].vipLevel, -1); unchecked { ++i; } } /// @dev if the receiver is the contract self, then unlock the safeboxes and dump the NFTs to the vault if (receiver == address(this)) { uint256 nftLen = nftIds.length; for (uint256 i; i < nftLen;) { collection.freeTokenIds.push(nftIds[i]); unchecked { ++i; } } emit FragmentNft(msg.sender, msg.sender, collectionId, nftIds); } else { collection.floorToken.burn(msg.sender, Constants.FLOOR_TOKEN_AMOUNT * nftIds.length); ERC721Transfer.safeBatchTransferFrom(proxyCollection, address(this), receiver, nftIds); } emit UnlockNft(msg.sender, receiver, collectionId, nftIds, proxyCollection); } function _unlockNfts( CollectionState storage collectionState, uint256 maxExpiryTs, uint256[] memory nftIds, CollectionAccount storage userCollectionAccount ) private returns (SafeBoxKey[] memory) { if (maxExpiryTs > 0 && maxExpiryTs < block.timestamp) revert Errors.SafeBoxHasExpire(); SafeBoxKey[] memory expiredKeys = new SafeBoxKey[](nftIds.length); uint256 currentBucketTime = Helper.counterStamp(block.timestamp); uint256 creditToRelease = 0; uint256[] memory buckets; /// if maxExpiryTs == 0, it means all nftIds in this batch being locked infinitely that we don't need to update countingBuckets if (maxExpiryTs > 0) { uint256 maxExpiryBucketTime = Math.min(Helper.counterStamp(maxExpiryTs), collectionState.lastUpdatedBucket); buckets = collectionState.countingBuckets.batchGet(currentBucketTime, maxExpiryBucketTime); } for (uint256 i; i < nftIds.length;) { uint256 nftId = nftIds[i]; if (Helper.hasActiveActivities(collectionState, nftId)) revert Errors.NftHasActiveActivities(); (SafeBox storage safeBox, SafeBoxKey storage safeBoxKey) = Helper.useSafeBoxAndKey(collectionState, userCollectionAccount, nftId); creditToRelease += safeBoxKey.lockingCredit; if (safeBox.isInfiniteSafeBox()) { --collectionState.infiniteCnt; } else { uint256 limit = Helper.counterStamp(safeBox.expiryTs) - currentBucketTime; if (limit > buckets.length) revert(); for (uint256 idx; idx < limit;) { --buckets[idx]; unchecked { ++idx; } } } expiredKeys[i] = safeBoxKey; removeSafeBox(collectionState, nftId); userCollectionAccount.removeSafeboxKey(nftId); unchecked { ++i; } } userCollectionAccount.totalLockingCredit -= creditToRelease.toUint96(); if (buckets.length > 0) { collectionState.countingBuckets.batchSet(currentBucketTime, buckets); } return expiredKeys; } function extendLockingForKeys( CollectionState storage collection, UserFloorAccount storage userAccount, LockParam memory param ) public returns (uint256 totalCreditCost) { uint8 newVipLevel = uint8(param.vipLevel); uint256 totalCredit = userAccount.ensureVipCredit(newVipLevel, param.creditToken); Helper.ensureMaxLocking(collection, newVipLevel, param.expiryTs, param.nftIds.length, true); uint256 minMaintCredit = userAccount.minMaintCredit; uint256[] memory safeBoxKeys; { CollectionAccount storage collectionAccount = userAccount.getOrAddCollection(param.collection); // extend lock duration int256[] memory vipLevelDiffs; (vipLevelDiffs, totalCreditCost, safeBoxKeys) = _extendLockingForKeys(collection, collectionAccount, param.nftIds, param.expiryTs, uint8(newVipLevel)); // compute max credit for locking cost uint96 totalLockingCredit = collectionAccount.totalLockingCredit; { uint256 creditBuffer; unchecked { creditBuffer = totalCredit - totalLockingCredit; } if (totalCreditCost > creditBuffer || totalCreditCost > param.maxCreditCost) { revert Errors.InsufficientCredit(); } } // update user vip key counts for (uint256 vipLevel = 0; vipLevel < vipLevelDiffs.length;) { userAccount.updateVipKeyCount(uint8(vipLevel), vipLevelDiffs[vipLevel]); unchecked { ++vipLevel; } } totalLockingCredit += totalCreditCost.toUint96(); collectionAccount.totalLockingCredit = totalLockingCredit; if (totalLockingCredit > minMaintCredit) { userAccount.minMaintCredit = totalLockingCredit; minMaintCredit = totalLockingCredit; } } emit ExtendKey(msg.sender, param.collection, param.nftIds, safeBoxKeys, param.expiryTs, minMaintCredit); } function _extendLockingForKeys( CollectionState storage collectionState, CollectionAccount storage userCollectionAccount, uint256[] memory nftIds, uint256 newExpiryTs, // expiryTs of 0 is infinite. uint8 newVipLevel ) private returns (int256[] memory, uint256, uint256[] memory) { LockInfo memory info = LockInfo({ isInfinite: newExpiryTs == 0, currentBucket: Helper.counterStamp(block.timestamp), newExpiryBucket: Helper.counterStamp(newExpiryTs), selfLocked: userCollectionAccount.keyCnt, totalManaged: collectionState.activeSafeBoxCnt + collectionState.freeTokenIds.length, newRequireLockCredit: 0, infiniteCnt: collectionState.infiniteCnt }); if (info.isInfinite) { info.newExpiryBucket = Helper.counterStamp(block.timestamp + Constants.MAX_LOCKING_PERIOD); } uint256[] memory buckets = Helper.prepareBucketUpdate(collectionState, info.currentBucket, info.newExpiryBucket); int256[] memory vipLevelDiffs = new int256[](Constants.VIP_LEVEL_COUNT); /// @dev `keys` used to log info, we just compact its fields into one 256 bits number uint256[] memory keys = new uint256[](nftIds.length); for (uint256 idx; idx < nftIds.length;) { if ( Helper.hasActiveActivities(collectionState, nftIds[idx]) /// listing safebox can be extended && !Helper.hasActiveListOffer(collectionState, nftIds[idx]) ) { revert Errors.NftHasActiveActivities(); } (SafeBox storage safeBox, SafeBoxKey storage safeBoxKey) = Helper.useSafeBoxAndKey(collectionState, userCollectionAccount, nftIds[idx]); { uint256 extendOffset = Helper.counterStamp(safeBox.expiryTs) - info.currentBucket; unchecked { for (uint256 i; i < extendOffset; ++i) { if (buckets[i] == 0) revert Errors.InvalidParam(); --buckets[i]; } } } uint256 safeboxQuote = updateCountersAndGetSafeboxCredit(buckets, info, newVipLevel); if (safeboxQuote > safeBoxKey.lockingCredit) { info.newRequireLockCredit += (safeboxQuote - safeBoxKey.lockingCredit); safeBoxKey.lockingCredit = safeboxQuote.toUint96(); } uint8 oldVipLevel = safeBoxKey.vipLevel; if (newVipLevel > oldVipLevel) { safeBoxKey.vipLevel = newVipLevel; --vipLevelDiffs[oldVipLevel]; ++vipLevelDiffs[newVipLevel]; } if (info.isInfinite) { safeBox.expiryTs = 0; ++info.infiniteCnt; } else { safeBox.expiryTs = uint32(newExpiryTs); } keys[idx] = SafeBoxLib.encodeSafeBoxKey(safeBoxKey); unchecked { ++idx; } } if (info.isInfinite) { collectionState.infiniteCnt = info.infiniteCnt; } else { collectionState.countingBuckets.batchSet(info.currentBucket, buckets); if (info.newExpiryBucket > collectionState.lastUpdatedBucket) { collectionState.lastUpdatedBucket = uint64(info.newExpiryBucket); } } return (vipLevelDiffs, info.newRequireLockCredit, keys); } function updateCountersAndGetSafeboxCredit(uint256[] memory counters, LockInfo memory lockInfo, uint8 vipLevel) private pure returns (uint256 result) { unchecked { uint256 infiniteCnt = lockInfo.infiniteCnt; uint256 totalManaged = lockInfo.totalManaged; uint256 counterOffsetEnd = (counters.length + 1) * 0x20; uint256 tmpCount; if (lockInfo.isInfinite) { for (uint256 i = 0x20; i < counterOffsetEnd; i += 0x20) { assembly { tmpCount := mload(add(counters, i)) } result += Constants.getRequiredStakingForLockRatio(infiniteCnt + tmpCount, totalManaged); } } else { for (uint256 i = 0x20; i < counterOffsetEnd; i += 0x20) { assembly { tmpCount := mload(add(counters, i)) } result += Constants.getRequiredStakingForLockRatio(infiniteCnt + tmpCount, totalManaged); assembly { /// increase counters[i] mstore(add(counters, i), add(tmpCount, 1)) } } result = Constants.getVipRequiredStakingWithDiscount(result, vipLevel); } result = Constants.getRequiredStakingWithSelfRatio( result, Helper.calculateSelfLockingRatio(lockInfo.selfLocked, totalManaged) ); } } function removeExpiredKeysAndRestoreCredits( CollectionState storage collectionState, UserFloorAccount storage account, address collectionId, uint256[] memory nftIds, address onBehalfOf, bool verifyLocking ) public returns (uint256 releasedCredit) { CollectionAccount storage collectionAccount = account.getByKey(collectionId); if (verifyLocking) { /// tricky logic, when verifying, we assume the keyCnt maybe incorrect and prevent it from underflow by increasing it collectionAccount.keyCnt = uint32(nftIds.length); } uint256 lockingCredit; uint256 removedCnt; uint256[] memory removedKeys = new uint256[](nftIds.length); for (uint256 i; i < nftIds.length;) { SafeBoxKey memory safeBoxKey = collectionAccount.getByKey(nftIds[i]); SafeBox memory safeBox = collectionState.safeBoxes[nftIds[i]]; if (safeBoxKey.keyId == 0) { revert Errors.InvalidParam(); } if (safeBox._isSafeBoxExpired() || !safeBox._isKeyMatchingSafeBox(safeBoxKey)) { /// reuse the array nftIds[removedCnt] = nftIds[i]; removedKeys[removedCnt] = SafeBoxLib.encodeSafeBoxKey(safeBoxKey); unchecked { ++removedCnt; releasedCredit += safeBoxKey.lockingCredit; } account.updateVipKeyCount(safeBoxKey.vipLevel, -1); collectionAccount.removeSafeboxKey(nftIds[i]); } else if (verifyLocking) { unchecked { lockingCredit += safeBoxKey.lockingCredit; } } unchecked { ++i; } } if (releasedCredit > 0) { collectionAccount.totalLockingCredit -= releasedCredit.toUint96(); } if (verifyLocking) { if (collectionAccount.totalLockingCredit != lockingCredit) revert Errors.InvalidParam(); /// when locking credit are matching, the amount of keys with credit locked can be computed directly /// the keys without credit locked can be ignored temporarily collectionAccount.keyCnt = (nftIds.length - removedCnt).toUint32(); } emit RemoveExpiredKey( msg.sender, onBehalfOf, collectionId, removedCnt == nftIds.length ? nftIds : Array.slice(nftIds, 0, removedCnt), removedCnt == nftIds.length ? removedKeys : Array.slice(removedKeys, 0, removedCnt) ); } function tidyExpiredNFTs(CollectionState storage collection, uint256[] memory nftIds, address collectionId) public { uint256 nftLen = nftIds.length; for (uint256 i; i < nftLen;) { uint256 nftId = nftIds[i]; SafeBox storage safeBox = Helper.useSafeBox(collection, nftId); if (!safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasNotExpire(); if (!Helper.isAuctionPeriodOver(safeBox)) revert Errors.AuctionHasNotCompleted(); /// remove expired safebox, and dump it to vault removeSafeBox(collection, nftId); collection.freeTokenIds.push(nftId); unchecked { ++i; } } emit ExpiredNftToVault(msg.sender, collectionId, nftIds); } function claimRandomNFT( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, mapping(address => FeeConfig) storage feeConfs, address collectionId, uint256 claimCnt, uint256 maxCreditCost, address receiver ) public returns (uint256 totalCreditCost) { if (claimCnt == 0 || collection.freeTokenIds.length < claimCnt) revert Errors.ClaimableNftInsufficient(); uint256 freeAmount = collection.freeTokenIds.length; uint256 totalManaged = collection.activeSafeBoxCnt + freeAmount; /// when locking ratio greater than xx%, stop random redemption if ( Helper.calculateLockingRatioRaw(freeAmount - claimCnt, totalManaged - claimCnt) > Constants.VAULT_REDEMPTION_MAX_LOKING_RATIO ) { revert Errors.ClaimableNftInsufficient(); } /// quota represented with 32 bits /// fragment token fee with 10^18 decimals bool useQuotaTx = (maxCreditCost >> 32) == 0; uint256[] memory selectedTokenIds; (selectedTokenIds, totalCreditCost) = pickRandomFromVault(collection, freeAmount, totalManaged, claimCnt, useQuotaTx); if (totalCreditCost > maxCreditCost) { revert Errors.InsufficientCredit(); } if (useQuotaTx) { UserFloorAccount storage userAccount = userAccounts[msg.sender]; Helper.checkAndUpdateVaultQuota(userAccount.getByKey(collectionId), -int32(uint32(totalCreditCost))); } else { distributeRedemptionFunds(userAccounts, feeConfs, address(collection.floorToken), claimCnt, totalCreditCost); } collection.floorToken.burn(msg.sender, Constants.FLOOR_TOKEN_AMOUNT * selectedTokenIds.length); ERC721Transfer.safeBatchTransferFrom(collectionId, address(this), receiver, selectedTokenIds); emit ClaimRandomNft(msg.sender, receiver, collectionId, selectedTokenIds, totalCreditCost); } function pickRandomFromVault( CollectionState storage collection, uint256 vaultCnt, uint256 totalManaged, uint256 pickCnt, bool useQuotaTax ) private returns (uint256[] memory pickedTokenIds, uint256 totalCost) { pickedTokenIds = new uint256[](pickCnt); while (pickCnt > 0) { { (uint256 fee, uint32 quota) = Constants.getVaultFeeAtLR(Helper.calculateLockingRatioRaw(vaultCnt, totalManaged)); if (useQuotaTax) totalCost += quota; else totalCost += fee; } uint256 freeLength = collection.freeTokenIds.length; /// just compute a deterministic random number uint256 chosenNftIdx = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, totalManaged))) % freeLength; unchecked { --pickCnt; --totalManaged; --vaultCnt; } pickedTokenIds[pickCnt] = collection.freeTokenIds[chosenNftIdx]; collection.freeTokenIds[chosenNftIdx] = collection.freeTokenIds[freeLength - 1]; collection.freeTokenIds.pop(); } } function distributeRedemptionFunds( mapping(address => UserFloorAccount) storage accounts, mapping(address => FeeConfig) storage feeConfs, address token, uint256 redeemCnt, uint256 totalPrice ) private { address protocolReceipt = feeConfs[token].vaultFee.receipt; address royaltyReceipt = feeConfs[token].royalty.receipt; /// a bit difference, for vault redemption with fee, we treat royaltyRate as bips of the Fragment Token amount of 1 NFT /// so the dust will be the incoming to the protocol uint256 royaltyRate = feeConfs[token].royalty.vault; uint256 royalty = redeemCnt * (Constants.FLOOR_TOKEN_AMOUNT / 10000 * royaltyRate); if (totalPrice < royalty) { royalty = totalPrice; } UserFloorAccount storage userAccount = accounts[msg.sender]; userAccount.transferToken(accounts[protocolReceipt], token, totalPrice - royalty, false); userAccount.transferToken(accounts[royaltyReceipt], token, royalty, false); } function getLockingBuckets(CollectionState storage collection, uint256 startTimestamp, uint256 endTimestamp) public view returns (uint256[] memory) { return Helper.prepareBucketUpdate( collection, Helper.counterStamp(startTimestamp), Math.min(collection.lastUpdatedBucket, Helper.counterStamp(endTimestamp)) ); } function addSafeBox(CollectionState storage collectionState, uint256 nftId, SafeBox memory safebox) internal { if (collectionState.safeBoxes[nftId].keyId > 0) revert Errors.SafeBoxAlreadyExist(); collectionState.safeBoxes[nftId] = safebox; ++collectionState.activeSafeBoxCnt; } function removeSafeBox(CollectionState storage collectionState, uint256 nftId) internal { delete collectionState.safeBoxes[nftId]; --collectionState.activeSafeBoxCnt; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "../Constants.sol"; import "../Errors.sol"; import "./SafeBox.sol"; import "./User.sol"; import { SafeBox, CollectionState, AuctionInfo, CollectionAccount, SafeBoxKey, FeeRate, PrivateOffer, Fees } from "./Structs.sol"; import "../library/RollingBuckets.sol"; library Helper { using SafeBoxLib for SafeBox; using UserLib for CollectionAccount; using RollingBuckets for mapping(uint256 => uint256); function counterStamp(uint256 timestamp) internal pure returns (uint96) { unchecked { return uint96((timestamp + Constants.BUCKET_SPAN_1) / Constants.BUCKET_SPAN); } } function checkCollectionSafeboxQuota( CollectionAccount storage account, CollectionState storage collection, uint256 newLocked ) internal view { uint256 selfRatio = calculateSelfLockingRatio( account.keyCnt + newLocked, collection.activeSafeBoxCnt + collection.freeTokenIds.length + newLocked ); if (selfRatio > Constants.USER_COLLECTION_LOCKED_BOUND_PCT) revert Errors.UserQuotaExhausted(); } function checkAndUpdateUserSafeboxQuota(UserFloorAccount storage account, uint8 vipLevel, uint16 newLocked) internal { uint16 used = updateUserSafeboxQuota(account); uint16 totalQuota = Constants.getSafeboxPeriodQuota(vipLevel); uint16 nextUsed = used + newLocked; if (nextUsed > totalQuota) revert Errors.PeriodQuotaExhausted(); account.safeboxQuotaUsed = nextUsed; checkSafeboxQuotaGlobal(account, vipLevel, newLocked); } function checkSafeboxQuotaGlobal(UserFloorAccount storage account, uint8 vipLevel, uint256 newLocked) internal view { uint256 totalQuota = Constants.getSafeboxUserQuota(vipLevel); if (totalQuota < newLocked) { revert Errors.UserQuotaExhausted(); } else { unchecked { totalQuota -= newLocked; } } (, uint256[] memory keyCnts) = UserLib.getMinLevelAndVipKeyCounts(account.vipInfo); for (uint256 i; i < Constants.VIP_LEVEL_COUNT;) { if (totalQuota >= keyCnts[i]) { unchecked { totalQuota -= keyCnts[i]; } } else { revert Errors.UserQuotaExhausted(); } unchecked { ++i; } } } function ensureProxyVipLevel(uint8 vipLevel, bool proxy) internal pure { if (proxy && vipLevel < Constants.PROXY_COLLECTION_VIP_THRESHOLD) { revert Errors.InvalidParam(); } } function ensureMaxLocking( CollectionState storage collection, uint8 vipLevel, uint256 requireExpiryTs, uint256 requireLockCnt, bool extend ) internal view { /// vip level 0 can not use safebox utilities. if (vipLevel >= Constants.VIP_LEVEL_COUNT || vipLevel == 0) { revert Errors.InvalidParam(); } uint256 lockingRatio = calculateLockingRatio(collection, requireLockCnt); uint256 restrictRatio; if (extend) { /// try to extend exist safebox /// only restrict infinity locking, normal safebox with expiry should be skipped restrictRatio = requireExpiryTs == 0 ? Constants.getLockingRatioForInfinite(vipLevel) : 100; } else { /// try to lock(create new safebox) /// restrict maximum locking ratio to use safebox restrictRatio = Constants.getLockingRatioForSafebox(vipLevel); if (requireExpiryTs == 0) { uint256 extraRatio = Constants.getLockingRatioForInfinite(vipLevel); if (restrictRatio > extraRatio) restrictRatio = extraRatio; } } if (lockingRatio > restrictRatio) revert Errors.InvalidParam(); /// only check when it is not infinite lock if (requireExpiryTs > 0) { uint256 deltaBucket; unchecked { deltaBucket = counterStamp(requireExpiryTs) - counterStamp(block.timestamp); } if (deltaBucket == 0 || deltaBucket > Constants.getVipLockingBuckets(vipLevel)) { revert Errors.InvalidParam(); } } } function useSafeBoxAndKey(CollectionState storage collection, CollectionAccount storage userAccount, uint256 nftId) internal view returns (SafeBox storage safeBox, SafeBoxKey storage key) { safeBox = collection.safeBoxes[nftId]; if (safeBox.keyId == 0) revert Errors.SafeBoxNotExist(); if (safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasExpire(); key = userAccount.getByKey(nftId); if (!safeBox.isKeyMatchingSafeBox(key)) revert Errors.NoMatchingSafeBoxKey(); } function useSafeBox(CollectionState storage collection, uint256 nftId) internal view returns (SafeBox storage safeBox) { safeBox = collection.safeBoxes[nftId]; if (safeBox.keyId == 0) revert Errors.SafeBoxNotExist(); } function generateNextKeyId(CollectionState storage collectionState) internal returns (uint64 nextKeyId) { nextKeyId = collectionState.nextKeyId; ++collectionState.nextKeyId; } function generateNextActivityId(CollectionState storage collection) internal returns (uint64 nextActivityId) { nextActivityId = collection.nextActivityId; ++collection.nextActivityId; } function isAuctionPeriodOver(SafeBox storage safeBox) internal view returns (bool) { return safeBox.expiryTs + Constants.FREE_AUCTION_PERIOD < block.timestamp; } function hasActiveActivities(CollectionState storage collection, uint256 nftId) internal view returns (bool) { return hasActiveAuction(collection, nftId) || hasActiveRaffle(collection, nftId) || hasActivePrivateOffer(collection, nftId) || hasActiveListOffer(collection, nftId); } function hasActiveAuction(CollectionState storage collection, uint256 nftId) internal view returns (bool) { return collection.activeAuctions[nftId].endTime >= block.timestamp; } function hasActiveRaffle(CollectionState storage collection, uint256 nftId) internal view returns (bool) { return collection.activeRaffles[nftId].endTime >= block.timestamp; } function hasActivePrivateOffer(CollectionState storage collection, uint256 nftId) internal view returns (bool) { return collection.activePrivateOffers[nftId].endTime >= block.timestamp; } function hasActiveListOffer(CollectionState storage collection, uint256 nftId) internal view returns (bool) { PrivateOffer storage offer = collection.activePrivateOffers[nftId]; return offer.activityId > 0 && offer.buyer == address(0) && !useSafeBox(collection, nftId).isSafeBoxExpired(); } function calculateActivityFee(uint256 settleAmount, uint256 feeRateBips) internal pure returns (uint256 afterFee, uint256 fee) { fee = settleAmount * feeRateBips / 10000; unchecked { afterFee = settleAmount - fee; } } function prepareBucketUpdate(CollectionState storage collection, uint256 startBucket, uint256 endBucket) internal view returns (uint256[] memory buckets) { uint256 validEnd = collection.lastUpdatedBucket; uint256 padding; if (endBucket < validEnd) { validEnd = endBucket; } else { unchecked { padding = endBucket - validEnd; } } if (startBucket < validEnd) { if (padding == 0) { buckets = collection.countingBuckets.batchGet(startBucket, validEnd); } else { uint256 validLen; unchecked { validLen = validEnd - startBucket; } buckets = new uint256[](validLen + padding); uint256[] memory tmp = collection.countingBuckets.batchGet(startBucket, validEnd); for (uint256 i; i < validLen;) { buckets[i] = tmp[i]; unchecked { ++i; } } } } else { buckets = new uint256[](endBucket - startBucket); } } function getActiveSafeBoxes(CollectionState storage collectionState, uint256 timestamp) internal view returns (uint256) { uint256 bucketStamp = counterStamp(timestamp); if (collectionState.lastUpdatedBucket < bucketStamp) { return 0; } return collectionState.countingBuckets.get(bucketStamp); } function calculateLockingRatio(CollectionState storage collection, uint256 newLocked) internal view returns (uint256) { uint256 freeAmount = collection.freeTokenIds.length; uint256 totalManaged = newLocked + collection.activeSafeBoxCnt + freeAmount; return calculateLockingRatioRaw(freeAmount, totalManaged); } function calculateLockingRatioRaw(uint256 freeAmount, uint256 totalManaged) internal pure returns (uint256) { if (totalManaged == 0) { return 0; } else { unchecked { return (100 - freeAmount * 100 / totalManaged); } } } function calculateSelfLockingRatio(uint256 selfLocked, uint256 collectionTatalManaged) internal pure returns (uint256) { if (collectionTatalManaged == 0) { return 0; } else { unchecked { return (selfLocked * 100 / collectionTatalManaged); } } } function updateUserSafeboxQuota(UserFloorAccount storage account) internal returns (uint16) { if (block.timestamp - account.lastQuotaPeriodTs <= Constants.USER_SAFEBOX_QUOTA_REFRESH_DURATION) { return account.safeboxQuotaUsed; } else { unchecked { account.lastQuotaPeriodTs = uint32( block.timestamp / Constants.USER_SAFEBOX_QUOTA_REFRESH_DURATION * Constants.USER_SAFEBOX_QUOTA_REFRESH_DURATION ); } account.safeboxQuotaUsed = 0; return 0; } } function applyDiffToCounters( CollectionState storage collectionState, uint256 startBucket, uint256 endBucket, int256 diff ) internal { if (startBucket == endBucket) return; uint256[] memory buckets = Helper.prepareBucketUpdate(collectionState, startBucket, endBucket); unchecked { uint256 bucketLen = buckets.length; if (diff > 0) { uint256 tmp = uint256(diff); for (uint256 i; i < bucketLen; ++i) { buckets[i] += tmp; } } else { uint256 tmp = uint256(-diff); for (uint256 i; i < bucketLen; ++i) { buckets[i] -= tmp; } } } collectionState.countingBuckets.batchSet(startBucket, buckets); if (endBucket > collectionState.lastUpdatedBucket) { collectionState.lastUpdatedBucket = uint64(endBucket); } } function computeFees(uint256 price, Fees memory fees) internal pure returns (uint256 priceWithoutFee, uint256 protocolFee, uint256 royalty) { protocolFee = price * fees.protocol.rateBips / 10_000; royalty = price * fees.royalty.rateBips / 10_000; unchecked { priceWithoutFee = price - protocolFee - royalty; } } function checkAndUpdateVaultQuota(CollectionAccount storage account, int32 diff) internal { if (block.timestamp - account.lastVaultActiveTs > Constants.VAULT_QUOTA_RESET_PERIOD) { // exceeds the reset period if (diff < 0) revert Errors.UserQuotaExhausted(); else account.vaultContQuota = uint32(diff); } else { int32 res = int32(account.vaultContQuota) + diff; if (res < 0) revert Errors.UserQuotaExhausted(); else account.vaultContQuota = uint32(res); } if (diff > 0) { /// only refresh when increasing quota account.lastVaultActiveTs = uint32(block.timestamp); } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../Errors.sol"; import "../interface/IFlooring.sol"; import "../library/RollingBuckets.sol"; import {SafeBox, CollectionState, PrivateOffer, Fees, FeeRate, FeeConfig} from "./Structs.sol"; import {SafeBoxLib} from "./SafeBox.sol"; import "./User.sol"; import "./Helper.sol"; library PrivateOfferLib { using SafeBoxLib for SafeBox; using RollingBuckets for mapping(uint256 => uint256); using UserLib for UserFloorAccount; using UserLib for CollectionAccount; using Helper for CollectionState; // todo: event should be moved to Interface as far as Solidity 0.8.22 ready. // https://github.com/ethereum/solidity/pull/14274 // https://github.com/ethereum/solidity/issues/14430 event PrivateOfferStarted( address indexed seller, address indexed buyer, address indexed collection, uint64[] activityIds, uint256[] nftIds, address settleToken, uint96 price, uint256 offerEndTime, uint256 safeBoxExpiryTs, uint256 adminFee, Fees fees ); event PrivateOfferCanceled( address indexed operator, address indexed collection, uint64[] activityIds, uint256[] nftIds ); event OfferMetaChanged( address indexed operator, address indexed collection, uint64[] activityIds, uint256[] nftIds, uint96[] price, address settleToken, uint32 offerEndTime, uint32 safeboxExpiryTs ); event PrivateOfferAccepted( address indexed buyer, address indexed collection, uint64[] activityIds, uint256[] nftIds, uint256[] safeBoxKeyIds, uint32 safeboxExpiryTs ); function ownerInitPrivateOffers( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, FeeConfig storage feeConf, address creditToken, IFlooring.PrivateOfferInitParam memory param ) public { if (param.receiver == msg.sender) revert Errors.InvalidParam(); /// if receiver is none, means list and anyone can buy it if (param.receiver == address(0)) { return startListOffer(collection, userAccounts, feeConf, param); } UserFloorAccount storage userAccount = userAccounts[msg.sender]; PrivateOffer memory offerTemplate = PrivateOffer({ endTime: uint96(block.timestamp + Constants.PRIVATE_OFFER_DURATION), activityId: 0, token: param.token, price: param.price, owner: msg.sender, buyer: param.receiver, fees: Fees(FeeRate(address(0), 0), FeeRate(address(0), 0)) }); (uint64[] memory offerActivityIds, uint192 safeBoxExpiryTs) = _ownerInitPrivateOffers( collection, userAccount.getByKey(param.collection), param.nftIds, offerTemplate, param.maxExpiry ); uint256 totalFeeCost = param.nftIds.length * Constants.PRIVATE_OFFER_COST; userAccount.transferToken(userAccounts[address(this)], creditToken, totalFeeCost, true); emit PrivateOfferStarted( msg.sender, param.receiver, param.collection, offerActivityIds, param.nftIds, param.token, param.price, offerTemplate.endTime, safeBoxExpiryTs, totalFeeCost, offerTemplate.fees ); } function _ownerInitPrivateOffers( CollectionState storage collection, CollectionAccount storage userAccount, uint256[] memory nftIds, PrivateOffer memory offerTemplate, uint256 maxExpiry ) private returns (uint64[] memory offerActivityIds, uint32 safeBoxExpiryTs) { safeBoxExpiryTs = uint32(offerTemplate.endTime + Constants.PRIVATE_OFFER_COMPLETE_GRACE_DURATION); uint256 nowBucketCnt = Helper.counterStamp(block.timestamp); uint256[] memory toUpdateBucket; if (maxExpiry > 0) { toUpdateBucket = collection.countingBuckets.batchGet( nowBucketCnt, Math.min(collection.lastUpdatedBucket, Helper.counterStamp(maxExpiry)) ); } offerActivityIds = new uint64[](nftIds.length); uint256 firstIdx = Helper.counterStamp(safeBoxExpiryTs) - nowBucketCnt; for (uint256 i; i < nftIds.length;) { uint256 nftId = nftIds[i]; if (collection.hasActiveActivities(nftId)) revert Errors.NftHasActiveActivities(); (SafeBox storage safeBox,) = collection.useSafeBoxAndKey(userAccount, nftId); if (safeBox.isInfiniteSafeBox()) { --collection.infiniteCnt; } else { uint256 oldExpiryTs = safeBox.expiryTs; if (oldExpiryTs < safeBoxExpiryTs) { revert Errors.InvalidParam(); } uint256 lastIdx = Helper.counterStamp(oldExpiryTs) - nowBucketCnt; if (firstIdx > lastIdx || lastIdx > toUpdateBucket.length) revert Errors.InvalidParam(); for (uint256 k = firstIdx; k < lastIdx;) { --toUpdateBucket[k]; unchecked { ++k; } } } safeBox.expiryTs = safeBoxExpiryTs; offerTemplate.activityId = collection.generateNextActivityId(); collection.activePrivateOffers[nftId] = offerTemplate; offerActivityIds[i] = offerTemplate.activityId; unchecked { ++i; } } if (toUpdateBucket.length > 0) { collection.countingBuckets.batchSet(nowBucketCnt, toUpdateBucket); } } function modifyOffers( CollectionState storage collection, mapping(address => UserFloorAccount) storage accounts, address collectionId, uint256[] memory nftIds, IFlooring.OfferOpType opTy, bytes calldata data ) public { if (opTy == IFlooring.OfferOpType.Cancel || opTy == IFlooring.OfferOpType.Decline) { removePrivateOffers(collection, collectionId, nftIds); } else if (opTy == IFlooring.OfferOpType.ChangePrice) { IFlooring.ChangeOfferPriceData memory priceData = abi.decode(data, (IFlooring.ChangeOfferPriceData)); modifyOfferPrice(collection, accounts[msg.sender], collectionId, nftIds, priceData.priceList); } else { revert Errors.InvalidParam(); } } function removePrivateOffers(CollectionState storage collection, address collectionId, uint256[] memory nftIds) internal { uint64[] memory offerActivityIds = new uint64[](nftIds.length); for (uint256 i; i < nftIds.length;) { uint256 nftId = nftIds[i]; PrivateOffer storage offer = collection.activePrivateOffers[nftId]; if (offer.owner != msg.sender && offer.buyer != msg.sender) revert Errors.NoPrivilege(); offerActivityIds[i] = offer.activityId; delete collection.activePrivateOffers[nftId]; unchecked { ++i; } } emit PrivateOfferCanceled(msg.sender, collectionId, offerActivityIds, nftIds); } function buyerAcceptPrivateOffers( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, address collectionId, address creditToken, uint256[] memory nftIds, uint256 maxExpiry ) public { uint256[] memory safeBoxKeyIds = new uint256[](nftIds.length); uint64[] memory activityIds = new uint64[](nftIds.length); uint32 newExpiryTs = uint32(block.timestamp + Constants.PRIVATE_OFFER_COMPLETE_GRACE_DURATION); uint256[] memory toUpdateBucket; if (maxExpiry > 0) { toUpdateBucket = Helper.prepareBucketUpdate( collection, Helper.counterStamp(block.timestamp), Math.max(Helper.counterStamp(maxExpiry), Helper.counterStamp(newExpiryTs)) ); } for (uint256 i; i < nftIds.length;) { uint256 nftId = nftIds[i]; if (!Helper.hasActiveListOffer(collection, nftId) && !Helper.hasActivePrivateOffer(collection, nftId)) { revert Errors.ActivityNotExist(); } { PrivateOffer memory offer = collection.activePrivateOffers[nftId]; if (offer.endTime > 0 && offer.endTime <= block.timestamp) revert Errors.ActivityHasExpired(); if (offer.buyer != address(0) && offer.buyer != msg.sender) revert Errors.NoPrivilege(); if (offer.owner == msg.sender) revert Errors.NoPrivilege(); activityIds[i] = offer.activityId; distributeFunds(userAccounts, offer, creditToken); } SafeBoxKey memory newKey = SafeBoxKey({keyId: collection.generateNextKeyId(), vipLevel: 0, lockingCredit: 0}); safeBoxKeyIds[i] = newKey.keyId; { /// transfer safebox key CollectionAccount storage buyerCollectionAccount = userAccounts[msg.sender].getByKey(collectionId); buyerCollectionAccount.addSafeboxKey(nftId, newKey); } /// update safebox SafeBox storage safeBox = collection.useSafeBox(nftId); /// this revert couldn't happen but just leaving it (we have checked offer'EndTime before) if (safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasExpire(); safeBox.keyId = newKey.keyId; safeBox.owner = msg.sender; if (safeBox.isInfiniteSafeBox()) { --collection.infiniteCnt; /// infinite locked safebox should be shorten to the newExpiryTs safeBox.expiryTs = uint32(block.timestamp); } updateSafeboxBuckets(safeBox, toUpdateBucket, newExpiryTs); delete collection.activePrivateOffers[nftId]; unchecked { ++i; } } if (toUpdateBucket.length > 0) { collection.countingBuckets.batchSet(Helper.counterStamp(block.timestamp), toUpdateBucket); uint96 newBucket = Helper.counterStamp(newExpiryTs); if (collection.lastUpdatedBucket < newBucket) { collection.lastUpdatedBucket = uint64(newBucket); } } emit PrivateOfferAccepted(msg.sender, collectionId, activityIds, nftIds, safeBoxKeyIds, newExpiryTs); } function modifyOfferPrice( CollectionState storage collection, UserFloorAccount storage ownerAccount, address collectionId, uint256[] memory nftIds, uint96[] memory newPriceList ) internal { if (nftIds.length != newPriceList.length) revert Errors.InvalidParam(); CollectionAccount storage ownerCollection = ownerAccount.getByKey(collectionId); uint64[] memory activityIds = new uint64[](nftIds.length); for (uint256 i; i < nftIds.length;) { uint256 nftId = nftIds[i]; PrivateOffer storage offer = collection.activePrivateOffers[nftId]; if (offer.owner != msg.sender) revert Errors.NoPrivilege(); if (offer.endTime > 0 && offer.endTime <= block.timestamp) revert Errors.ActivityHasExpired(); /// dummy check ownership /// when offer.endTime is zero, we check the safebox expiry collection.useSafeBoxAndKey(ownerCollection, nftIds[i]); offer.price = newPriceList[i]; activityIds[i] = offer.activityId; unchecked { ++i; } } /// time no change emit OfferMetaChanged(msg.sender, collectionId, activityIds, nftIds, newPriceList, address(0), 0, 0); } function startListOffer( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, FeeConfig storage feeConf, IFlooring.PrivateOfferInitParam memory param ) internal { if (feeConf.safeboxFee.receipt == address(0)) revert Errors.TokenNotSupported(); PrivateOffer memory template = PrivateOffer({ /// leave endTime empty as it is same as safebox expiry endTime: 0, activityId: 0, token: param.token, price: param.price, owner: msg.sender, buyer: address(0), fees: Fees({ royalty: FeeRate({receipt: feeConf.royalty.receipt, rateBips: feeConf.royalty.marketlist}), protocol: FeeRate({receipt: feeConf.safeboxFee.receipt, rateBips: feeConf.safeboxFee.marketlist}) }) }); CollectionAccount storage ownerCollection = userAccounts[msg.sender].getByKey(param.collection); uint64[] memory activityIds = new uint64[](param.nftIds.length); for (uint256 i; i < param.nftIds.length;) { uint256 nftId = param.nftIds[i]; if (collection.hasActiveActivities(nftId)) revert Errors.NftHasActiveActivities(); /// dummy check collection.useSafeBoxAndKey(ownerCollection, nftId); template.activityId = Helper.generateNextActivityId(collection); collection.activePrivateOffers[nftId] = template; activityIds[i] = template.activityId; unchecked { ++i; } } emit PrivateOfferStarted( msg.sender, address(0), // buyer no restrictions param.collection, activityIds, param.nftIds, param.token, param.price, 0, // same with safebox expiry each other 0, // same with safebox expiry 0, // no admin fee template.fees ); } function updateSafeboxBuckets(SafeBox storage safebox, uint256[] memory buckets, uint32 newExpireTs) private { uint256 offset = Helper.counterStamp(block.timestamp); /// the safebox isn't expired, so that the bucket should be greater or equeal than `offset` uint256 safeboxExpiryBucketIdx = Helper.counterStamp(safebox.expiryTs) - offset; uint256 expectBucketIdx = Helper.counterStamp(newExpireTs) - offset; if (safeboxExpiryBucketIdx < expectBucketIdx) { /// expend the key, if (expectBucketIdx > buckets.length) revert Errors.InvalidParam(); for (uint256 i = safeboxExpiryBucketIdx; i < expectBucketIdx;) { ++buckets[i]; unchecked { ++i; } } } else if (safeboxExpiryBucketIdx > expectBucketIdx) { /// shorten the key, if (safeboxExpiryBucketIdx > buckets.length) revert Errors.InvalidParam(); for (uint256 i = expectBucketIdx; i < safeboxExpiryBucketIdx;) { --buckets[i]; unchecked { ++i; } } } safebox.expiryTs = newExpireTs; } function distributeFunds( mapping(address => UserFloorAccount) storage accounts, PrivateOffer memory offer, address creditToken ) private { if (offer.price > 0) { UserFloorAccount storage buyerAccount = accounts[msg.sender]; address token = offer.token; (uint256 priceWithoutFee, uint256 protocolFee, uint256 royalty) = Helper.computeFees(offer.price, offer.fees); { /// calculate owner vip tier discounts uint8 ownerVipLevel = Constants.getVipLevel(accounts[offer.owner].tokenBalance(creditToken)); uint256 protocolFeeAfterDiscount = Constants.getListingProtocolFeeWithDiscount(protocolFee, ownerVipLevel); priceWithoutFee += (protocolFee - protocolFeeAfterDiscount); protocolFee = protocolFeeAfterDiscount; } buyerAccount.transferToken(accounts[offer.owner], token, priceWithoutFee, token == creditToken); buyerAccount.transferToken( accounts[offer.fees.protocol.receipt], offer.token, protocolFee, token == creditToken ); buyerAccount.transferToken(accounts[offer.fees.royalty.receipt], offer.token, royalty, token == creditToken); } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import {SafeBox, CollectionState, RaffleInfo, TicketRecord, FeeConfig, Fees, FeeRate} from "./Structs.sol"; import "./User.sol"; import "./Collection.sol"; import "./Helper.sol"; import "../Errors.sol"; import "../library/RollingBuckets.sol"; import "../library/Array.sol"; library RaffleLib { 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 RaffleStarted( address indexed owner, address indexed collection, uint64[] activityIds, uint256[] nftIds, uint48 maxTickets, address settleToken, uint96 ticketPrice, Fees fees, uint48 raffleEndTime, uint256 safeBoxExpiryTs, uint256 adminFee ); event RaffleTicketsSold( address indexed buyer, address indexed collection, uint64 activityId, uint256 nftId, uint256 ticketsSold, uint256 cost ); event RaffleSettled( address indexed winner, address indexed collection, uint64 activityId, uint256 nftId, uint256 safeBoxKeyId, uint256 collectedFunds ); function ownerInitRaffles( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, FeeConfig storage feeConf, IFlooring.RaffleInitParam memory param, address creditToken ) public { if (feeConf.safeboxFee.receipt == address(0)) revert Errors.TokenNotSupported(); UserFloorAccount storage userAccount = userAccounts[msg.sender]; { if (uint256(param.maxTickets) * param.ticketPrice > type(uint96).max) revert Errors.InvalidParam(); (uint256 vipLevel, uint256 duration) = Constants.raffleDurations(param.duration); userAccount.ensureVipCredit(uint8(vipLevel), creditToken); param.duration = duration; } uint256 adminFee = Constants.RAFFLE_COST * param.nftIds.length; userAccount.transferToken(userAccounts[address(this)], creditToken, adminFee, true); Fees memory fees = Fees({ royalty: FeeRate({receipt: feeConf.royalty.receipt, rateBips: feeConf.royalty.raffle}), protocol: FeeRate({receipt: feeConf.safeboxFee.receipt, rateBips: feeConf.safeboxFee.raffle}) }); (uint64[] memory raffleActivityIds, uint48 raffleEndTime, uint192 safeBoxExpiryTs) = _ownerInitRaffles(collection, userAccount.getByKey(param.collection), param, fees); emit RaffleStarted( msg.sender, param.collection, raffleActivityIds, param.nftIds, param.maxTickets, param.ticketToken, param.ticketPrice, fees, raffleEndTime, safeBoxExpiryTs, adminFee ); } function _ownerInitRaffles( CollectionState storage collection, CollectionAccount storage userAccount, IFlooring.RaffleInitParam memory param, Fees memory fees ) private returns (uint64[] memory raffleActivityIds, uint48 raffleEndTime, uint32 safeBoxExpiryTs) { raffleEndTime = uint48(block.timestamp + param.duration); safeBoxExpiryTs = uint32(raffleEndTime + Constants.RAFFLE_COMPLETE_GRACE_PERIODS); uint256 startBucket = Helper.counterStamp(block.timestamp); uint256[] memory toUpdateBucket; if (param.maxExpiry > 0) { toUpdateBucket = collection.countingBuckets.batchGet( startBucket, Math.min(Helper.counterStamp(param.maxExpiry), collection.lastUpdatedBucket) ); } raffleActivityIds = new uint64[](param.nftIds.length); uint256 firstIdx = Helper.counterStamp(safeBoxExpiryTs) - startBucket; for (uint256 i; i < param.nftIds.length;) { uint256 nftId = param.nftIds[i]; if (collection.hasActiveActivities(nftId)) revert Errors.NftHasActiveActivities(); (SafeBox storage safeBox,) = collection.useSafeBoxAndKey(userAccount, nftId); if (safeBox.isInfiniteSafeBox()) { --collection.infiniteCnt; } else { uint256 oldExpiryTs = safeBox.expiryTs; if (oldExpiryTs < safeBoxExpiryTs) { revert Errors.InvalidParam(); } uint256 lastIdx = Helper.counterStamp(oldExpiryTs) - startBucket; if (firstIdx > lastIdx || lastIdx > toUpdateBucket.length) revert Errors.InvalidParam(); for (uint256 k = firstIdx; k < lastIdx;) { --toUpdateBucket[k]; unchecked { ++k; } } } safeBox.expiryTs = safeBoxExpiryTs; raffleActivityIds[i] = collection.generateNextActivityId(); RaffleInfo storage newRaffle = collection.activeRaffles[nftId]; newRaffle.endTime = raffleEndTime; newRaffle.token = param.ticketToken; newRaffle.ticketPrice = param.ticketPrice; newRaffle.maxTickets = param.maxTickets; newRaffle.owner = msg.sender; newRaffle.activityId = raffleActivityIds[i]; newRaffle.fees = fees; unchecked { ++i; } } if (toUpdateBucket.length > 0) { collection.countingBuckets.batchSet(startBucket, toUpdateBucket); } } function buyRaffleTickets( CollectionState storage collection, mapping(address => UserFloorAccount) storage accounts, address creditToken, address collectionId, uint256 nftId, uint256 ticketCnt ) public { RaffleInfo storage raffle = collection.activeRaffles[nftId]; if (raffle.owner == address(0) || raffle.owner == msg.sender) revert Errors.NoPrivilege(); if (raffle.endTime < block.timestamp) revert Errors.ActivityHasExpired(); if (raffle.maxTickets < raffle.ticketSold + ticketCnt) revert Errors.InvalidParam(); SafeBox storage safeBox = collection.useSafeBox(nftId); safeBox.keyId = SafeBoxLib.SAFEBOX_KEY_NOTATION; // buyer buy tickets idx in [startIdx, endIdx) raffle.tickets.push( TicketRecord({ buyer: msg.sender, startIdx: uint48(raffle.ticketSold), endIdx: uint48(raffle.ticketSold + ticketCnt) }) ); uint256 cost = raffle.ticketPrice * ticketCnt; raffle.ticketSold += uint48(ticketCnt); raffle.collectedFund += uint96(cost); address token = raffle.token; accounts[msg.sender].transferToken(accounts[address(this)], token, cost, token == creditToken); emit RaffleTicketsSold(msg.sender, collectionId, raffle.activityId, nftId, ticketCnt, cost); } function prepareSettleRaffles(CollectionState storage collection, uint256[] calldata nftIds) public returns (bytes memory compactedNftIds, uint256 nftLen) { nftLen = nftIds.length; uint256[] memory tmpNftIds = new uint256[](nftLen); uint256 cnt; for (uint256 i; i < nftLen; ++i) { uint256 nftId = nftIds[i]; RaffleInfo storage raffle = collection.activeRaffles[nftId]; if (raffle.endTime >= block.timestamp) revert Errors.ActivityHasNotCompleted(); if (raffle.isSettling) revert Errors.InvalidParam(); if (raffle.ticketSold == 0) { continue; } SafeBox storage safeBox = collection.useSafeBox(nftId); // raffle must be settled before safebox expired // otherwise it maybe conflict with auction if (safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasExpire(); tmpNftIds[cnt] = nftId; raffle.isSettling = true; unchecked { ++cnt; } } /// only allow up to 8 raffles can be settled if (cnt > 8) revert Errors.InvalidParam(); if (cnt == nftLen) { compactedNftIds = Array.encodeUints(tmpNftIds); } else { nftLen = cnt; compactedNftIds = Array.encodeUints(Array.slice(tmpNftIds, 0, cnt)); } } function settleRaffles( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, address collectionId, bytes memory compactedNftIds, uint256[] memory randoms ) public { uint256[] memory nftIds = Array.decodeUints(compactedNftIds); for (uint256 i; i < nftIds.length;) { uint256 nftId = nftIds[i]; RaffleInfo storage raffle = collection.activeRaffles[nftId]; TicketRecord memory winTicket = getWinTicket(raffle.tickets, uint48(randoms[i] % raffle.ticketSold)); SafeBoxKey memory key = SafeBoxKey({keyId: collection.generateNextKeyId(), vipLevel: 0, lockingCredit: 0}); { /// we don't check whether the safebox is exist, it had done in the `prepareSettleRaffles` SafeBox storage safeBox = collection.safeBoxes[nftId]; safeBox.keyId = key.keyId; safeBox.owner = winTicket.buyer; } { /// transfer safebox key to winner account CollectionAccount storage winnerCollectionAccount = userAccounts[winTicket.buyer].getByKey(collectionId); winnerCollectionAccount.addSafeboxKey(nftId, key); } distributeFunds(userAccounts, raffle.fees, raffle.owner, raffle.token, raffle.collectedFund); emit RaffleSettled(winTicket.buyer, collectionId, raffle.activityId, nftId, key.keyId, raffle.collectedFund); delete collection.activeRaffles[nftId]; unchecked { ++i; } } } function getWinTicket(TicketRecord[] storage tickets, uint48 idx) private view returns (TicketRecord memory ticket) { uint256 low; uint256 high = tickets.length; unchecked { while (low <= high) { // Note that mid will always be strictly less than high (i.e. it will be a valid array index) // because Math.average rounds down (it does integer division with truncation). uint256 mid = Math.average(low, high); ticket = tickets[mid]; if (ticket.startIdx <= idx && idx < ticket.endIdx) { return ticket; } if (ticket.startIdx < idx) { high = mid; } else { low = mid + 1; } } } } function distributeFunds( mapping(address => UserFloorAccount) storage accounts, Fees memory fees, address owner, address token, uint256 price ) private { /// contract account no need to check credit requirements (uint256 priceWithoutFee, uint256 protocolFee, uint256 royalty) = Helper.computeFees(price, fees); UserFloorAccount storage contractAccount = accounts[address(this)]; if (royalty > 0) { contractAccount.transferToken(accounts[fees.royalty.receipt], token, royalty, false); } if (protocolFee > 0) { contractAccount.transferToken(accounts[fees.protocol.receipt], token, protocolFee, false); } if (priceWithoutFee > 0) { contractAccount.transferToken(accounts[owner], token, priceWithoutFee, false); } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import {SafeBox, SafeBoxKey} from "./Structs.sol"; library SafeBoxLib { uint64 public constant SAFEBOX_KEY_NOTATION = type(uint64).max; function isInfiniteSafeBox(SafeBox storage safeBox) internal view returns (bool) { return safeBox.expiryTs == 0; } function isSafeBoxExpired(SafeBox storage safeBox) internal view returns (bool) { return safeBox.expiryTs != 0 && safeBox.expiryTs < block.timestamp; } function _isSafeBoxExpired(SafeBox memory safeBox) internal view returns (bool) { return safeBox.expiryTs != 0 && safeBox.expiryTs < block.timestamp; } function isKeyMatchingSafeBox(SafeBox storage safeBox, SafeBoxKey storage safeBoxKey) internal view returns (bool) { return safeBox.keyId == safeBoxKey.keyId; } function _isKeyMatchingSafeBox(SafeBox memory safeBox, SafeBoxKey memory safeBoxKey) internal pure returns (bool) { return safeBox.keyId == safeBoxKey.keyId; } function encodeSafeBoxKey(SafeBoxKey memory key) internal pure returns (uint256) { uint256 val = key.lockingCredit; val |= (uint256(key.keyId) << 96); val |= (uint256(key.vipLevel) << 160); return val; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "../interface/IFragmentToken.sol"; struct SafeBox { /// Either matching a key OR Constants.SAFEBOX_KEY_NOTATION meaning temporarily /// held by a bidder in auction. uint64 keyId; /// The timestamp that the safe box expires. uint32 expiryTs; /// The owner of the safebox. It maybe outdated due to expiry address owner; } struct PrivateOffer { /// private offer end time uint96 endTime; /// which token used to accpet the offer address token; /// price of the offer uint96 price; address owner; /// who should receive the offer address buyer; uint64 activityId; Fees fees; } enum AuctionType { Owned, Expired, Vault } struct AuctionInfo { /// The end time for the auction. uint96 endTime; /// Bid token address. address bidTokenAddress; /// Minimum Bid. uint96 minimumBid; /// The person who trigger the auction at the beginning. address triggerAddress; uint96 lastBidAmount; address lastBidder; /// [Deprecated] Whether the auction is triggered by the NFT owner itself? /// Note. Don't remove it directly as we need keep mainnet contract layout bool isSelfTriggered; uint64 activityId; /// [Deprecated] fee config /// Note. Don't remove it directly as we need keep mainnet contract layout uint32 oldFeeRateBips; AuctionType typ; Fees fees; } struct TicketRecord { /// who buy the tickets address buyer; /// Start index of tickets /// [startIdx, endIdx) uint48 startIdx; /// End index of tickets uint48 endIdx; } struct RaffleInfo { /// raffle end time uint48 endTime; /// max tickets amount the raffle can sell uint48 maxTickets; /// which token used to buy the raffle tickets address token; /// owner of raffle address owner; /// price per ticket uint96 ticketPrice; uint64 activityId; /// total funds collected by selling tickets uint96 collectedFund; /// total sold tickets amount uint48 ticketSold; /// whether the raffle is being settling bool isSettling; /// tickets sold records TicketRecord[] tickets; Fees fees; } struct CollectionState { /// The address of the Floor Token cooresponding to the NFTs. IFragmentToken floorToken; /// Records the active safe box in each time bucket. mapping(uint256 => uint256) countingBuckets; /// Stores all of the NFTs that has been fragmented but *without* locked up limit. uint256[] freeTokenIds; /// Huge map for all the `SafeBox`es in one collection. mapping(uint256 => SafeBox) safeBoxes; /// Stores all the ongoing auctions: nftId => `AuctionInfo`. mapping(uint256 => AuctionInfo) activeAuctions; /// Stores all the ongoing raffles: nftId => `RaffleInfo`. mapping(uint256 => RaffleInfo) activeRaffles; /// Stores all the ongoing private offers: nftId => `PrivateOffer`. mapping(uint256 => PrivateOffer) activePrivateOffers; /// The last bucket time the `countingBuckets` is updated. uint64 lastUpdatedBucket; /// Next Key Id. This should start from 1, we treat key id `SafeboxLib.SAFEBOX_KEY_NOTATION` as temporarily /// being used for activities(auction/raffle). uint64 nextKeyId; /// Active Safe Box Count. uint64 activeSafeBoxCnt; /// The number of infinite lock count. uint64 infiniteCnt; /// Next Activity Id. This should start from 1 uint64 nextActivityId; uint32 lastVaultAuctionPeriodTs; } struct UserFloorAccount { /// @notice it should be maximum of the `totalLockingCredit` across all collections uint96 minMaintCredit; /// @notice used to iterate collection accounts /// packed with `minMaintCredit` to reduce storage slot access address firstCollection; /// @notice user vip level related info /// 0 - 239 bits: store SafeBoxKey Count per vip level, per level using 24 bits /// 240 - 247 bits: store minMaintVipLevel /// 248 - 255 bits: remaining uint256 vipInfo; /// @notice Locked Credit amount which cannot be withdrawn and will be released as time goes. uint256 lockedCredit; mapping(address => CollectionAccount) accounts; mapping(address => uint256) tokenAmounts; /// Each account has safebox quota to use per period uint32 lastQuotaPeriodTs; uint16 safeboxQuotaUsed; /// [Deprecated] Each account has vault redemption waiver per period uint32 lastWaiverPeriodTs; uint96 creditWaiverUsed; } struct SafeBoxKey { /// locked credit amount of this safebox uint96 lockingCredit; /// corresponding key id of the safebox uint64 keyId; /// which vip level the safebox locked uint8 vipLevel; } struct CollectionAccount { mapping(uint256 => SafeBoxKey) keys; /// total locking credit of all `keys` in this collection uint96 totalLockingCredit; /// track next collection as linked list address next; /// tracking total locked of the collection uint32 keyCnt; /// Depositing to vault gets quota, redepmtion consumes quota uint32 vaultContQuota; /// Used to track and clear vault contribution quota when the quota is inactive for a certain duration uint32 lastVaultActiveTs; } struct Fees { FeeRate royalty; FeeRate protocol; } struct FeeConfig { RoyaltyFeeRate royalty; SafeboxFeeRate safeboxFee; VaultFeeRate vaultFee; } struct RoyaltyFeeRate { address receipt; uint16 marketlist; uint16 vault; uint16 raffle; } struct VaultFeeRate { address receipt; uint16 vaultAuction; } struct SafeboxFeeRate { address receipt; uint16 auctionOwned; uint16 auctionExpired; uint16 raffle; uint16 marketlist; } struct FeeRate { address receipt; uint16 rateBips; } /// Internal Structure struct LockParam { address proxyCollection; address collection; uint256[] nftIds; uint256 expiryTs; uint8 vipLevel; uint256 maxCreditCost; address creditToken; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import "../library/CurrencyTransfer.sol"; import "../Constants.sol"; import "../Errors.sol"; import {UserFloorAccount, CollectionAccount, SafeBoxKey} from "./Structs.sol"; library UserLib { using SafeCast for uint256; /// @notice `sender` deposit `token` into Flooring on behalf of `receiver`. `receiver`'s account will be updated. event DepositToken(address indexed sender, address indexed receiver, address indexed token, uint256 amount); /// @notice `sender` withdraw `token` from Flooring and transfer it to `receiver`. event WithdrawToken(address indexed sender, address indexed receiver, address indexed token, uint256 amount); /// @notice update the account maintain credit on behalfOf `onBehalfOf` event UpdateMaintainCredit(address indexed onBehalfOf, uint256 minMaintCredit); address internal constant LIST_GUARD = address(1); function ensureVipCredit(UserFloorAccount storage account, uint8 requireVipLevel, address creditToken) internal view returns (uint256) { uint256 totalCredit = tokenBalance(account, creditToken); if (Constants.getVipBalanceRequirements(requireVipLevel) > totalCredit) { revert Errors.InsufficientBalanceForVipLevel(); } return totalCredit; } function getMinMaintVipLevel(UserFloorAccount storage account) internal view returns (uint8) { unchecked { return uint8(account.vipInfo >> 240); } } function getMinLevelAndVipKeyCounts(uint256 vipInfo) internal pure returns (uint8 minLevel, uint256[] memory counts) { unchecked { counts = new uint256[](Constants.VIP_LEVEL_COUNT); minLevel = uint8(vipInfo >> 240); for (uint256 i; i < Constants.VIP_LEVEL_COUNT; ++i) { counts[i] = (vipInfo >> (i * 24)) & 0xFFFFFF; } } } function storeMinLevelAndVipKeyCounts( UserFloorAccount storage account, uint8 minMaintVipLevel, uint256[] memory keyCounts ) internal { unchecked { uint256 _data = (uint256(minMaintVipLevel) << 240); for (uint256 i; i < Constants.VIP_LEVEL_COUNT; ++i) { _data |= ((keyCounts[i] & 0xFFFFFF) << (i * 24)); } account.vipInfo = _data; } } function getOrAddCollection(UserFloorAccount storage user, address collection) internal returns (CollectionAccount storage) { CollectionAccount storage entry = user.accounts[collection]; if (entry.next == address(0)) { if (user.firstCollection == address(0)) { user.firstCollection = collection; entry.next = LIST_GUARD; } else { entry.next = user.firstCollection; user.firstCollection = collection; } } return entry; } function removeCollection(UserFloorAccount storage userAccount, address collection, address prev) internal { CollectionAccount storage cur = userAccount.accounts[collection]; if (cur.next == address(0)) revert Errors.InvalidParam(); if (collection == userAccount.firstCollection) { if (cur.next == LIST_GUARD) { userAccount.firstCollection = address(0); } else { userAccount.firstCollection = cur.next; } } else { CollectionAccount storage prevAccount = userAccount.accounts[prev]; if (prevAccount.next != collection) revert Errors.InvalidParam(); prevAccount.next = cur.next; } delete userAccount.accounts[collection]; } function getByKey(UserFloorAccount storage userAccount, address collection) internal view returns (CollectionAccount storage) { return userAccount.accounts[collection]; } function addSafeboxKey(CollectionAccount storage account, uint256 nftId, SafeBoxKey memory key) internal { if (account.keys[nftId].keyId > 0) { revert Errors.SafeBoxKeyAlreadyExist(); } ++account.keyCnt; account.keys[nftId] = key; } function removeSafeboxKey(CollectionAccount storage account, uint256 nftId) internal { --account.keyCnt; delete account.keys[nftId]; } function getByKey(CollectionAccount storage account, uint256 nftId) internal view returns (SafeBoxKey storage) { return account.keys[nftId]; } function tokenBalance(UserFloorAccount storage account, address token) internal view returns (uint256) { return account.tokenAmounts[token]; } function lockCredit(UserFloorAccount storage account, uint256 amount) internal { unchecked { account.lockedCredit += amount; } } function unlockCredit(UserFloorAccount storage account, uint256 amount) internal { unchecked { account.lockedCredit -= amount; } } function deposit( UserFloorAccount storage account, address onBehalfOf, address token, uint256 amount, bool isLockCredit ) public { depositToken(account, token, amount); if (isLockCredit) lockCredit(account, amount); if (token == CurrencyTransfer.NATIVE) { require(amount == msg.value); } else { CurrencyTransfer.safeTransferFrom(token, msg.sender, address(this), amount); } emit DepositToken(msg.sender, onBehalfOf, token, amount); } function withdraw(UserFloorAccount storage account, address receiver, address token, uint256 amount, bool isCredit) public { withdrawToken(account, token, amount, isCredit); CurrencyTransfer.safeTransfer(token, receiver, amount); emit WithdrawToken(msg.sender, receiver, token, amount); } function depositToken(UserFloorAccount storage account, address token, uint256 amount) internal { account.tokenAmounts[token] += amount; } function withdrawToken(UserFloorAccount storage account, address token, uint256 amount, bool isCreditToken) internal { uint256 balance = account.tokenAmounts[token]; if (balance < amount) { revert Errors.InsufficientCredit(); } if (isCreditToken) { uint256 avaiableBuf; unchecked { avaiableBuf = balance - amount; } if ( avaiableBuf < Constants.getVipBalanceRequirements(getMinMaintVipLevel(account)) || avaiableBuf < account.minMaintCredit || avaiableBuf < account.lockedCredit ) { revert Errors.InsufficientCredit(); } account.tokenAmounts[token] = avaiableBuf; } else { unchecked { account.tokenAmounts[token] = balance - amount; } } } function transferToken( UserFloorAccount storage from, UserFloorAccount storage to, address token, uint256 amount, bool isCreditToken ) internal { if (amount > 0) { withdrawToken(from, token, amount, isCreditToken); depositToken(to, token, amount); } } function updateVipKeyCount(UserFloorAccount storage account, uint8 vipLevel, int256 diff) internal { if (vipLevel > 0 && diff != 0) { (uint8 minMaintVipLevel, uint256[] memory keyCounts) = getMinLevelAndVipKeyCounts(account.vipInfo); if (diff < 0) { keyCounts[vipLevel] -= uint256(-diff); if (vipLevel == minMaintVipLevel && keyCounts[vipLevel] == 0) { uint8 newVipLevel = vipLevel; do { unchecked { --newVipLevel; } } while (newVipLevel > 0 && keyCounts[newVipLevel] == 0); minMaintVipLevel = newVipLevel; } } else { keyCounts[vipLevel] += uint256(diff); if (vipLevel > minMaintVipLevel) { minMaintVipLevel = vipLevel; } } storeMinLevelAndVipKeyCounts(account, minMaintVipLevel, keyCounts); } } function recalculateMinMaintCredit(UserFloorAccount storage account, address onBehalfOf) public returns (uint256 maxLocking) { address prev = account.firstCollection; for (address collection = account.firstCollection; collection != LIST_GUARD && collection != address(0);) { (uint256 locking, address next) = (getByKey(account, collection).totalLockingCredit, getByKey(account, collection).next); if (locking == 0) { removeCollection(account, collection, prev); collection = next; } else { if (locking > maxLocking) { maxLocking = locking; } prev = collection; collection = next; } } account.minMaintCredit = uint96(maxLocking); emit UpdateMaintainCredit(onBehalfOf, maxLocking); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "./interface/IMulticall.sol"; /// @title Multicall /// @notice Enables calling multiple methods in a single call to the contract abstract contract Multicall is IMulticall { /// @inheritdoc IMulticall function multicall(bytes[] calldata data) external virtual override returns (bytes[] memory) { bytes[] memory results = new bytes[](data.length); for (uint256 i; i < data.length;) { /// @custom:oz-upgrades-unsafe-allow-reachable delegatecall (bool success, bytes memory result) = address(this).delegatecall(data[i]); if (success) { results[i] = result; } else { // Next 4 lines from // https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/AddressUpgradeable.sol#L229 if (result.length > 0) { assembly { let returndata_size := mload(result) revert(add(32, result), returndata_size) } } else { revert FailedMulticall(); } } unchecked { ++i; } } return results; } function extMulticall(CallData[] calldata calls) external virtual override returns (bytes[] memory) { return multicall2(calls); } /// @notice Aggregate calls, ensuring each returns success if required /// @param calls An array of CallData structs /// @return returnData An array of bytes function multicall2(CallData[] calldata calls) internal returns (bytes[] memory) { bytes[] memory results = new bytes[](calls.length); CallData calldata calli; for (uint256 i = 0; i < calls.length;) { calli = calls[i]; (bool success, bytes memory result) = calli.target.call(calli.callData); if (success) { results[i] = result; } else { // Next 4 lines from // https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/AddressUpgradeable.sol#L229 if (result.length > 0) { assembly { let returndata_size := mload(result) revert(add(32, result), returndata_size) } } else { revert FailedMulticall(); } } unchecked { ++i; } } return results; } }
{ "metadata": { "bytecodeHash": "none", "useLiteralContent": true }, "optimizer": { "enabled": true, "runs": 800 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": { "src/logic/User.sol": { "UserLib": "0xe55b4c920df2473622be36e38e183691a6124279" }, "src/logic/Collection.sol": { "CollectionLib": "0x325a28e02a1cd6a9ddd1a8d107125639513d8bf8" }, "src/logic/Auction.sol": { "AuctionLib": "0x2f0af2d3e8ba7b3d77c8128dd00a3b3894d95f46" }, "src/logic/PrivateOffer.sol": { "PrivateOfferLib": "0xe43da036d82e8d667ab2e8414a7017f6aa728d34" }, "src/logic/Raffle.sol": { "RaffleLib": "0x6afb8d582e306da5646adaa568a5f04281e08632" } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"bytes32","name":"_keyHash","type":"bytes32"},{"internalType":"uint64","name":"_subId","type":"uint64"},{"internalType":"address","name":"_vrfCoordinator","type":"address"},{"internalType":"address","name":"flcToken","type":"address"}],"stateMutability":"payable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"name":"ERC1967InvalidImplementation","type":"error"},{"inputs":[],"name":"ERC1967NonPayable","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"FailedMulticall","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"InvalidParam","type":"error"},{"inputs":[],"name":"NftCollectionNotSupported","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"have","type":"address"},{"internalType":"address","name":"want","type":"address"}],"name":"OnlyCoordinatorCanFulfill","type":"error"},{"inputs":[],"name":"TokenNotSupported","type":"error"},{"inputs":[],"name":"UUPSUnauthorizedCallContext","type":"error"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"name":"UUPSUnsupportedProxiableUUID","type":"error"},{"inputs":[],"name":"Unauthorized","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":"trigger","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint64[]","name":"activityIds","type":"uint64[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"enum AuctionType","name":"typ","type":"uint8"},{"components":[{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"rateBips","type":"uint16"}],"internalType":"struct FeeRate","name":"royalty","type":"tuple"},{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"rateBips","type":"uint16"}],"internalType":"struct FeeRate","name":"protocol","type":"tuple"}],"indexed":false,"internalType":"struct Fees","name":"fees","type":"tuple"},{"indexed":false,"internalType":"address","name":"settleToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"minimumBid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"auctionEndTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"safeBoxExpiryTs","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"adminFee","type":"uint256"}],"name":"AuctionStartedV2","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"creditCost","type":"uint256"},{"indexed":false,"internalType":"address","name":"proxyCollection","type":"address"}],"name":"ClaimExpiredNft","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"creditCost","type":"uint256"}],"name":"ClaimRandomNft","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DepositToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"ExpiredNftToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"safeBoxKeys","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"safeBoxExpiryTs","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minMaintCredit","type":"uint256"}],"name":"ExtendKey","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"onBehalfOf","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"FragmentNft","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"onBehalfOf","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"safeBoxKeys","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"safeBoxExpiryTs","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minMaintCredit","type":"uint256"},{"indexed":false,"internalType":"address","name":"proxyCollection","type":"address"}],"name":"LockNft","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":true,"internalType":"address","name":"floorToken","type":"address"}],"name":"NewCollectionSupported","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"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint64[]","name":"activityIds","type":"uint64[]"},{"indexed":false,"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"indexed":false,"internalType":"uint96[]","name":"price","type":"uint96[]"},{"indexed":false,"internalType":"address","name":"settleToken","type":"address"},{"indexed":false,"internalType":"uint32","name":"offerEndTime","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"safeboxExpiryTs","type":"uint32"}],"name":"OfferMetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint64[]","name":"activityIds","type":"uint64[]"},{"indexed":false,"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"safeBoxKeyIds","type":"uint256[]"},{"indexed":false,"internalType":"uint32","name":"safeboxExpiryTs","type":"uint32"}],"name":"PrivateOfferAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint64[]","name":"activityIds","type":"uint64[]"},{"indexed":false,"internalType":"uint256[]","name":"nftIds","type":"uint256[]"}],"name":"PrivateOfferCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"seller","type":"address"},{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint64[]","name":"activityIds","type":"uint64[]"},{"indexed":false,"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"indexed":false,"internalType":"address","name":"settleToken","type":"address"},{"indexed":false,"internalType":"uint96","name":"price","type":"uint96"},{"indexed":false,"internalType":"uint256","name":"offerEndTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"safeBoxExpiryTs","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"adminFee","type":"uint256"},{"components":[{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"rateBips","type":"uint16"}],"internalType":"struct FeeRate","name":"royalty","type":"tuple"},{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"rateBips","type":"uint16"}],"internalType":"struct FeeRate","name":"protocol","type":"tuple"}],"indexed":false,"internalType":"struct Fees","name":"fees","type":"tuple"}],"name":"PrivateOfferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"proxyCollection","type":"address"},{"indexed":true,"internalType":"address","name":"underlyingCollection","type":"address"}],"name":"ProxyCollectionChanged","type":"event"},{"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":"nftId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"safeBoxKeyId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collectedFunds","type":"uint256"}],"name":"RaffleSettled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint64[]","name":"activityIds","type":"uint64[]"},{"indexed":false,"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"indexed":false,"internalType":"uint48","name":"maxTickets","type":"uint48"},{"indexed":false,"internalType":"address","name":"settleToken","type":"address"},{"indexed":false,"internalType":"uint96","name":"ticketPrice","type":"uint96"},{"components":[{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"rateBips","type":"uint16"}],"internalType":"struct FeeRate","name":"royalty","type":"tuple"},{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"rateBips","type":"uint16"}],"internalType":"struct FeeRate","name":"protocol","type":"tuple"}],"indexed":false,"internalType":"struct Fees","name":"fees","type":"tuple"},{"indexed":false,"internalType":"uint48","name":"raffleEndTime","type":"uint48"},{"indexed":false,"internalType":"uint256","name":"safeBoxExpiryTs","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"adminFee","type":"uint256"}],"name":"RaffleStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint64","name":"activityId","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"nftId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"ticketsSold","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"cost","type":"uint256"}],"name":"RaffleTicketsSold","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"onBehalfOf","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"safeBoxKeys","type":"uint256[]"}],"name":"RemoveExpiredKey","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"trusted","type":"address"},{"indexed":false,"internalType":"bool","name":"setOrUnset","type":"bool"}],"name":"TrustedUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"address","name":"proxyCollection","type":"address"}],"name":"UnlockNft","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"onBehalfOf","type":"address"},{"indexed":false,"internalType":"uint256","name":"minMaintCredit","type":"uint256"}],"name":"UpdateMaintainCredit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"bool","name":"addOrRemove","type":"bool"}],"name":"UpdateTokenSupported","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawToken","type":"event"},{"inputs":[],"name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"onBehalfOf","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"addAndLockCredit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"onBehalfOf","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"addTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"collectionId","type":"address"},{"internalType":"uint256","name":"nftId","type":"uint256"},{"internalType":"uint256","name":"ticketCnt","type":"uint256"}],"name":"buyRaffleTickets","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"collectionId","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"uint256","name":"maxSafeboxExpiry","type":"uint256"}],"name":"buyerAcceptPrivateOffers","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"claimCnt","type":"uint256"},{"internalType":"uint256","name":"maxCreditCost","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"claimRandomNFT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"startTimestamp","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"}],"name":"collectionLockingAt","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"creditToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct IMulticall.CallData[]","name":"calls","type":"tuple[]"}],"name":"extMulticall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"uint256","name":"expiryTs","type":"uint256"},{"internalType":"uint256","name":"newVipLevel","type":"uint256"},{"internalType":"uint256","name":"maxCreditCost","type":"uint256"}],"name":"extendKeys","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"name":"extsload","outputs":[{"internalType":"bytes32","name":"value","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"startSlot","type":"bytes32"},{"internalType":"uint256","name":"nSlots","type":"uint256"}],"name":"extsload","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"address","name":"onBehalfOf","type":"address"}],"name":"fragmentNFTs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"address","name":"bidToken","type":"address"},{"internalType":"uint256","name":"bidAmount","type":"uint256"}],"name":"initAuctionOnExpiredSafeBoxes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"vaultIdx","type":"uint256[]"},{"internalType":"address","name":"bidToken","type":"address"},{"internalType":"uint96","name":"bidAmount","type":"uint96"}],"name":"initAuctionOnVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"uint256","name":"expiryTs","type":"uint256"},{"internalType":"uint256","name":"vipLevel","type":"uint256"},{"internalType":"uint256","name":"maxCreditCost","type":"uint256"},{"internalType":"address","name":"onBehalfOf","type":"address"}],"name":"lockNFTs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collectionId","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"enum IFlooring.OfferOpType","name":"opTy","type":"uint8"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"modifyOffers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"uint256","name":"maxExpiry","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"minimumBid","type":"uint256"}],"name":"ownerInitAuctions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"uint256","name":"maxExpiry","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint96","name":"price","type":"uint96"}],"internalType":"struct IFlooring.PrivateOfferInitParam","name":"param","type":"tuple"}],"name":"ownerInitPrivateOffers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"address","name":"ticketToken","type":"address"},{"internalType":"uint96","name":"ticketPrice","type":"uint96"},{"internalType":"uint32","name":"maxTickets","type":"uint32"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"maxExpiry","type":"uint256"}],"internalType":"struct IFlooring.RaffleInitParam","name":"param","type":"tuple"}],"name":"ownerInitRaffles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"nftId","type":"uint256"},{"internalType":"uint256","name":"bidAmount","type":"uint256"},{"internalType":"uint256","name":"bidOptionIdx","type":"uint256"}],"name":"placeBidOnAuction","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256[]","name":"randomWords","type":"uint256[]"}],"name":"rawFulfillRandomWords","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"onBehalfOf","type":"address"}],"name":"recalculateAvailableCredit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"address","name":"onBehalfOf","type":"address"},{"internalType":"bool","name":"verifyLocking","type":"bool"}],"name":"removeExpiredKeyAndRestoreCredit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"removeTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"proxyCollection","type":"address"},{"internalType":"address","name":"underlying","type":"address"}],"name":"setCollectionProxy","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"trusted","type":"address"},{"internalType":"bool","name":"trust","type":"bool"}],"name":"setTrusted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"}],"name":"settleAuctions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collectionId","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"}],"name":"settleRaffles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_originalNFT","type":"address"},{"internalType":"address","name":"fragmentToken","type":"address"}],"name":"supportNewCollection","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"bool","name":"addOrRemove","type":"bool"}],"name":"supportNewToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"}],"name":"tidyExpiredNFTs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unlockCredit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"expiryTs","type":"uint256"},{"internalType":"uint256[]","name":"nftIds","type":"uint256[]"},{"internalType":"address","name":"receiver","type":"address"}],"name":"unlockNFTs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"components":[{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"marketlist","type":"uint16"},{"internalType":"uint16","name":"vault","type":"uint16"},{"internalType":"uint16","name":"raffle","type":"uint16"}],"internalType":"struct RoyaltyFeeRate","name":"royalty","type":"tuple"},{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"auctionOwned","type":"uint16"},{"internalType":"uint16","name":"auctionExpired","type":"uint16"},{"internalType":"uint16","name":"raffle","type":"uint16"},{"internalType":"uint16","name":"marketlist","type":"uint16"}],"internalType":"struct SafeboxFeeRate","name":"safeboxFee","type":"tuple"},{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"vaultAuction","type":"uint16"}],"internalType":"struct VaultFeeRate","name":"vaultFee","type":"tuple"}],"internalType":"struct FeeConfig[]","name":"fees","type":"tuple[]"}],"name":"updateCollectionFees","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelist","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawPlatformFee","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]
Contract Creation Code
6101406040819052306080526200444a388190039081908339810160408190526200002a916200013a565b6001600160a01b0380831660a081905260c08690526001600160401b03851660e05261010052811661012052620000606200006a565b5050505062000198565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620000bb5760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b03908116146200011b5780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b80516001600160a01b038116811462000135575f80fd5b919050565b5f805f80608085870312156200014e575f80fd5b845160208601519094506001600160401b03811681146200016d575f80fd5b92506200017d604086016200011e565b91506200018d606086016200011e565b905092959194509250565b60805160a05160c05160e05161010051610120516141fa620002505f395f818161057e0152818161098d01528181610a6b01528181610b7d01528181610c57015281816114f2015281816115ca015281816116ec0152818161195001528181611a4401528181611b7c01528181611cec01528181611ef70152611fbb01525f610e6201525f610e1d01525f610dee01525f8181610cbb0152610cfd01525f81816123bd015281816123e6015261254b01526141fa5ff3fe6080604052600436106102da575f3560e01c80638da5cb5b1161017b578063c80e5af6116100d1578063ee64bec111610087578063fbc717d911610062578063fbc717d914610823578063fd6010d214610836578063fdbed69914610849575f80fd5b8063ee64bec1146107c5578063f32a12ac146107f1578063f86f502414610810575f80fd5b8063d613d98c116100b7578063d613d98c14610780578063dbdc3e2314610793578063dd82d0d7146107b2575f80fd5b8063c80e5af614610742578063cb21c98d14610761575f80fd5b806398548e4811610131578063ad16f3bc1161010c578063ad16f3bc146106bc578063ad3cb1cc146106db578063c40ab4e114610723575f80fd5b806398548e48146106535780639b19251a14610672578063ac9650d81461069d575f80fd5b806394bca9ed1161016157806394bca9ed146105e957806397a998f31461060857806397e4ee2a14610627575f80fd5b80638da5cb5b146105b85780638daaf0a2146105d6575f80fd5b806335fd631a1161023057806364d5d9fa116101e65780638129fc1c116101c15780638129fc1c1461055257806388d81ab71461055a578063899e5d851461056d575f80fd5b806364d5d9fa146104f55780636b10101814610514578063811f4dce14610533575f80fd5b80634f1ef286116102165780634f1ef286146104bb57806352d1902d146104ce57806358200611146104e2575f80fd5b806335fd631a146104705780633964aa401461049c575f80fd5b806317a89bde116102905780631fe543e31161026b5780631fe543e31461041f57806322bf8c2f1461043e5780632b2aa8c41461045d575f80fd5b806317a89bde146103cd5780631e2eaeaf146103e05780631fb695391461040c575f80fd5b806313af4035116102c057806313af40351461032e578063150b7a021461034d5780631745f562146103ae575f80fd5b806308e316de146102f0578063127257341461030f575f80fd5b366102ec576102ea335f34610868565b005b5f80fd5b3480156102fb575f80fd5b506102ea61030a366004612b49565b61090b565b34801561031a575f80fd5b506102ea610329366004612c1e565b6109e8565b348015610339575f80fd5b506102ea610348366004612ce1565b610a93565b348015610358575f80fd5b50610378610367366004612d41565b630a85bd0160e11b95945050505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020015b60405180910390f35b3480156103b9575f80fd5b506102ea6103c8366004612daf565b610ae5565b6102ea6103db366004612dfc565b610b3c565b3480156103eb575f80fd5b506103fe6103fa366004612e34565b5490565b6040519081526020016103a5565b6102ea61041a366004612e4b565b610c18565b34801561042a575f80fd5b506102ea610439366004612e75565b610cb0565b348015610449575f80fd5b506102ea610458366004612daf565b610d3d565b6102ea61046b366004612e4b565b610f4f565b34801561047b575f80fd5b5061048f61048a366004612ea3565b610fc2565b6040516103a59190612f10565b3480156104a7575f80fd5b506102ea6104b6366004612f22565b61103a565b6102ea6104c9366004612fa9565b6110a6565b3480156104d9575f80fd5b506103fe6110c1565b6102ea6104f0366004613040565b6110ef565b348015610500575f80fd5b506102ea61050f366004612daf565b61117a565b34801561051f575f80fd5b506102ea61052e366004613073565b6111cb565b34801561053e575f80fd5b506102ea61054d3660046130db565b611236565b6102ea6112bf565b6102ea61056836600461319a565b6113dd565b348015610578575f80fd5b506105a07f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016103a5565b3480156105c3575f80fd5b505f546105a0906001600160a01b031681565b6102ea6105e4366004613242565b610868565b3480156105f4575f80fd5b506102ea610603366004613280565b61147f565b348015610613575f80fd5b506103fe6106223660046132e6565b611522565b348015610632575f80fd5b5061064661064136600461334c565b61165e565b6040516103a5919061338b565b34801561065e575f80fd5b506102ea61066d3660046133eb565b611679565b34801561067d575f80fd5b506103fe61068c366004612ce1565b60326020525f908152604090205481565b3480156106a8575f80fd5b506106466106b736600461334c565b61171e565b3480156106c7575f80fd5b506103fe6106d636600461345a565b61184d565b3480156106e6575f80fd5b5061048f6040518060400160405280600581526020017f352e302e3000000000000000000000000000000000000000000000000000000081525081565b34801561072e575f80fd5b506102ea61073d3660046134c9565b61190a565b34801561074d575f80fd5b506103fe61075c3660046134fd565b61199c565b34801561076c575f80fd5b506103fe61077b366004612ce1565b611add565b6102ea61078e366004613578565b611bba565b34801561079e575f80fd5b506102ea6107ad3660046135af565b611c7d565b6102ea6107c0366004612e4b565b611d4f565b3480156107d0575f80fd5b506107e46107df366004613613565b611d82565b6040516103a59190613645565b3480156107fc575f80fd5b506102ea61080b366004613040565b611e28565b6102ea61081e366004613613565b611eb6565b6102ea610831366004613688565b611f5b565b6102ea610844366004613578565b611fe7565b348015610854575f80fd5b506103fe6108633660046136dd565b612068565b6108718261212e565b6001600160a01b038381165f8181526066602052604080822090516375de5a7760e11b8152600481018290526024810193909352928516604483015260648201849052608482015273e55b4c920df2473622be36e38e183691a61242799063ebbcb4ee9060a4015b5f6040518083038186803b1580156108ef575f80fd5b505af4158015610901573d5f803e3d5ffd5b5050505050505050565b6109188160200151612169565b5f80610926835f01516121f3565b6001600160a01b038082168087525f90815260696020908152604080832060808a01519094168352929052819020905163137d407b60e21b815292945090925073e43da036d82e8d667ab2e8414a7017f6aa728d3491634df501ec916109b79186916066917f0000000000000000000000000000000000000000000000000000000000000000908a90600401613752565b5f6040518083038186803b1580156109cd575f80fd5b505af41580156109df573d5f803e3d5ffd5b50505050505050565b6109f58160200151612169565b5f80610a03835f01516121f3565b6001600160a01b038082168087525f908152606960209081526040808320818a015190941683529290528190209051634d59baa160e01b8152929450909250736afb8d582e306da5646adaa568a5f04281e0863291634d59baa1916109b791869160669189907f0000000000000000000000000000000000000000000000000000000000000000906004016137e8565b610a9b61222b565b5f80546001600160a01b0319166001600160a01b0383169081178255604051909133917f8292fce18fa69edf4db7b94ea2e58241df0ae57f97e0a6c9b29067028bf92d769190a350565b610aee81612169565b5f80610af9846121f3565b604051635d2b6fa760e11b81529193509150732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f469063ba56df4e906108d990859060669086908990600401613898565b3415610b4d57610b4d335f34610868565b5f80610b58866121f3565b604051632d905ae360e11b815260048101839052606660248201526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166044830152821660648201526084810188905260a4810187905260c481018690529193509150732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f4690635b20b5c69060e4015b5f6040518083038186803b158015610bfa575f80fd5b505af4158015610c0c573d5f803e3d5ffd5b50505050505050505050565b610c20612256565b6001600160a01b038281165f818152606660205260409081902090516375de5a7760e11b81526004810182905260248101929092527f00000000000000000000000000000000000000000000000000000000000000009092166044820152606481018390526001608482015273e55b4c920df2473622be36e38e183691a61242799063ebbcb4ee9060a4016109b7565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610d2f5760405163073e64fd60e21b81523360048201526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660248201526044015b60405180910390fd5b610d398282612283565b5050565b610d4681612169565b5f80610d51846121f3565b6040516362e8b2b960e01b815291935091505f908190736afb8d582e306da5646adaa568a5f04281e08632906362e8b2b990610d9390879089906004016138c5565b5f60405180830381865af4158015610dad573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052610dd491908101906138dd565b90925090508015610f47576040516305d3b1d360e41b81527f0000000000000000000000000000000000000000000000000000000000000000600482015267ffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016602482015260036044820152620c3500606482015263ffffffff821660848201525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690635d3b1d309060a4016020604051808303815f875af1158015610eb0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ed49190613959565b6040805160608101825260018082526001600160a01b0380891660208085019182528486018a81525f8881526064909252959020845191519092166c01000000000000000000000000026bffffffffffffffffffffffff919091161781559251939450909290820190610c0c90826139ed565b505050505050565b610f57612256565b305f9081526066602052604080822090516390dfbef160e01b8152600481018290523360248201526001600160a01b03851660448201526064810184905260848101929092529073e55b4c920df2473622be36e38e183691a6124279906390dfbef19060a4016109b7565b60605f600583901b67ffffffffffffffff811115610fe257610fe26129ee565b6040519080825280601f01601f19166020018201604052801561100c576020820181803683370190505b5090505f5b8381101561103257808501546001820160051b830152600181019050611011565b509392505050565b61104382612169565b5f61104d84612360565b6001600160a01b0383165f9081526066602052604090819020905163f51fda0d60e01b815291925073325a28e02a1cd6a9ddd1a8d107125639513d8bf89163f51fda0d916108d991859190899089908990600401613aa9565b6110ae6123b2565b6110b782612469565b610d398282612471565b5f6110ca612540565b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc90565b6110f7612256565b6001600160a01b0382165f9081526067602052604090205460ff16151581151514610d39576001600160a01b0382165f81815260676020908152604091829020805460ff191685151590811790915591519182527fb7e6048e8f02a75253b4e7aa2cfedf45e7f80999934aa6e73bfca1214fde0a61910160405180910390a25050565b61118381612169565b5f61118d83612360565b604051636ceb7d5d60e01b815290915073325a28e02a1cd6a9ddd1a8d107125639513d8bf890636ceb7d5d906109b790849086908890600401613aec565b6111d482612169565b5f806111df866121f3565b335f90815260666020526040908190209051630a5f4f7560e31b815292945090925073325a28e02a1cd6a9ddd1a8d107125639513d8bf8916352fa7ba891610be4918691908b9087908b908d908c90600401613b1d565b5f80611241876121f3565b6040516310416ec760e01b8152919350915073e43da036d82e8d667ab2e8414a7017f6aa728d34906310416ec79061128a90859060669086908c908c908c908c90600401613b6f565b5f6040518083038186803b1580156112a0575f80fd5b505af41580156112b2573d5f803e3d5ffd5b5050505050505050505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156113045750825b90505f8267ffffffffffffffff1660011480156113205750303b155b90508115801561132e575080155b1561134c5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561137b57845468ff00000000000000001916600160401b1785555b611383612589565b61138b6125bc565b83156113d657845468ff000000000000000019168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050565b6113e5612256565b5f5b83811015610f475782828281811061140157611401613bf4565b9050610160020160695f886001600160a01b03166001600160a01b031681526020019081526020015f205f87878581811061143e5761143e613bf4565b90506020020160208101906114539190612ce1565b6001600160a01b0316815260208101919091526040015f206114758282613c95565b50506001016113e7565b61148883612169565b5f80611493866121f3565b6001600160a01b038082165f908152606960209081526040808320938a16835292905281902090516303e4298f60e01b8152929450909250732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f46916303e4298f91610be49186916066917f00000000000000000000000000000000000000000000000000000000000000009088908d908d908d90600401613e3d565b5f61152c85612169565b611535846125c4565b5f80611540886121f3565b915091508173325a28e02a1cd6a9ddd1a8d107125639513d8bf863de8b49b5909160665f336001600160a01b03166001600160a01b031681526020019081526020015f206040518060e001604052808d6001600160a01b03168152602001866001600160a01b031681526020018c81526020018b81526020018a60ff1681526020018981526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152506040518463ffffffff1660e01b815260040161161393929190613eff565b602060405180830381865af415801561162e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116529190613959565b98975050505050505050565b6060611668612256565b61167283836125f1565b9392505050565b61168284612169565b5f8061168d876121f3565b6001600160a01b038082165f908152606960209081526040808320938a16835292905281902090516307fe595d60e11b8152929450909250732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f4691630ffcb2ba9161128a9186916066917f00000000000000000000000000000000000000000000000000000000000000009088908e908e908e908e90600401613f1d565b60605f8267ffffffffffffffff81111561173a5761173a6129ee565b60405190808252806020026020018201604052801561176d57816020015b60608152602001906001900390816117585790505b5090505f5b83811015611032575f803087878581811061178f5761178f613bf4565b90506020028101906117a19190613f7c565b6040516117af929190613fbf565b5f60405180830381855af49150503d805f81146117e7576040519150601f19603f3d011682016040523d82523d5f602084013e6117ec565b606091505b5091509150811561181a578084848151811061180a5761180a613bf4565b6020026020010181905250611843565b80511561182a5780518082602001fd5b6040516302cf089f60e51b815260040160405180910390fd5b5050600101611772565b5f61185784612169565b5f80611862876121f3565b6001600160a01b0387165f90815260666020526040908190209051637bd9d52160e11b815292945090925073325a28e02a1cd6a9ddd1a8d107125639513d8bf89163f7b3aa42916118c09186919086908c908c908c90600401613fce565b602060405180830381865af41580156118db573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118ff9190613959565b979650505050505050565b335f908152606660205260409081902090516390dfbef160e01b8152600481018290526001600160a01b03808416602483015280861660448301819052606483018690527f000000000000000000000000000000000000000000000000000000000000000090911614608482015273e55b4c920df2473622be36e38e183691a6124279906390dfbef19060a4016108d9565b5f6119a686612169565b6119af856125c4565b5f806119ba896121f3565b915091508173325a28e02a1cd6a9ddd1a8d107125639513d8bf863f6815346909160665f886001600160a01b03166001600160a01b031681526020019081526020015f206040518060e001604052808e6001600160a01b03168152602001866001600160a01b031681526020018d81526020018c81526020018b60ff1681526020018a81526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316815250886040518563ffffffff1660e01b8152600401611a8f9493929190614015565b602060405180830381865af4158015611aaa573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ace9190613959565b925050505b9695505050505050565b6001600160a01b0381165f81815260666020526040808220905163c449bd9160e01b81526004810182905260248101939093529091829073e55b4c920df2473622be36e38e183691a61242799063c449bd9190604401602060405180830381865af4158015611b4e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b729190613959565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165f908152600484016020526040902054909150819003949350505050565b611bc2612256565b6001600160a01b0382165f9081526065602052604090206007810154600160401b900467ffffffffffffffff1615611bf8575f80fd5b6007810180546fffffffffffffffff00000000000000001916600160401b17905560088101805467ffffffffffffffff1916600117905580546001600160a01b038084166001600160a01b031990921682178355604051908516907f48c2aef7c236e185cfd4d3fe7248c47fd93ec0d6f550e4ccec5c5cb6b0009c03905f90a3505050565b611c8683612169565b5f611c9085612360565b6001600160a01b038087165f90815260696020908152604080832093881683529290528190209051631c1a270f60e01b8152919250732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f4691631c1a270f91611d1c9185916066917f0000000000000000000000000000000000000000000000000000000000000000908c908c908c908c9060040161404d565b5f6040518083038186803b158015611d32575f80fd5b505af4158015611d44573d5f803e3d5ffd5b505050505050505050565b611d57612256565b6001600160a01b03919091165f9081526066602052604090206002018054919091039055565b505050565b6001600160a01b0383165f9081526065602052604090819020905163f1cd5ef360e01b81526004810191909152602481018390526044810182905260609073325a28e02a1cd6a9ddd1a8d107125639513d8bf89063f1cd5ef3906064015f60405180830381865af4158015611df9573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052611e2091908101906140b4565b949350505050565b611e3061222b565b8015611e56576001600160a01b0382165f90815260326020526040902060019055611e6f565b6001600160a01b0382165f908152603260205260408120555b604080516001600160a01b038416815282151560208201527f547820db618200fb54f457140ffaf92a642299d95533c33c155b13ba68203f96910160405180910390a15050565b3415611ec757611ec7335f34610868565b5f80611ed2856121f3565b604051630848963560e41b815260048101839052606660248201526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166044830152821660648201526084810187905260a481018690529193509150736afb8d582e306da5646adaa568a5f04281e086329063848963509060c401611d1c565b611f6482612169565b3415611f7557611f75335f34610868565b5f80611f80856121f3565b604051639fc46d9b60e01b8152919350915073e43da036d82e8d667ab2e8414a7017f6aa728d3490639fc46d9b90611d1c90859060669086907f0000000000000000000000000000000000000000000000000000000000000000908b908b90600401614135565b611fef612256565b6001600160a01b038281165f90815260686020526040902054811690821614610d39576001600160a01b038281165f8181526068602052604080822080546001600160a01b0319169486169485179055517f39b72dbfaede10f9f18a885eb02789166a1e35bc946c38a5fcf1121475cd06659190a35050565b5f8061207386612360565b6001600160a01b038781165f81815260696020526040908190209051630c58df2560e01b81526004810185905260666024820152604481019190915260648101919091526084810188905260a4810187905290851660c482015290915073325a28e02a1cd6a9ddd1a8d107125639513d8bf890630c58df259060e401602060405180830381865af415801561210a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ad39190613959565b6001600160a01b0381165f9081526067602052604090205460ff1661216657604051633dd1b30560e01b815260040160405180910390fd5b50565b80515f0361218a57604051633494a40d60e21b815260040160405180910390fd5b60015b8151811015610d39578160018203815181106121ab576121ab613bf4565b60200260200101518282815181106121c5576121c5613bf4565b6020026020010151116121eb57604051633494a40d60e21b815260040160405180910390fd5b60010161218d565b6001600160a01b038082165f908152606860205260408120549091829116806122195750825b61222281612360565b94909350915050565b5f546001600160a01b03163314612254576040516282b42960e81b815260040160405180910390fd5b565b335f908152603260205260408120549003612254576040516282b42960e81b815260040160405180910390fd5b5f8281526064602052604090208054600182018054612342926c0100000000000000000000000090046001600160a01b031691906122c090613970565b80601f01602080910402602001604051908101604052809291908181526020018280546122ec90613970565b80156123375780601f1061230e57610100808354040283529160200191612337565b820191905f5260205f20905b81548152906001019060200180831161231a57829003601f168201915b505050505084612722565b5f838152606460205260408120818155906113d660018301826129a4565b6001600160a01b0381165f9081526065602052604081206007810154600160401b900467ffffffffffffffff1682036123ac576040516309d3a9d960e31b815260040160405180910390fd5b92915050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061244b57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661243f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b6001600160a01b031614155b156122545760405163703e46dd60e11b815260040160405180910390fd5b61216661222b565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156124cb575060408051601f3d908101601f191682019092526124c891810190613959565b60015b6124f357604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610d26565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc811461253657604051632a87526960e21b815260048101829052602401610d26565b611d7d838361277a565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146122545760405163703e46dd60e11b815260040160405180910390fd5b61259f5f80546001600160a01b03191633179055565b5f80546001600160a01b0316815260326020526040902060019055565b6122546127cf565b80158015906125d35750428111155b1561216657604051633494a40d60e21b815260040160405180910390fd5b60605f8267ffffffffffffffff81111561260d5761260d6129ee565b60405190808252806020026020018201604052801561264057816020015b606081526020019060019003908161262b5790505b509050365f5b848110156127185785858281811061266057612660613bf4565b9050602002810190612672919061417f565b91505f806126836020850185612ce1565b6001600160a01b03166126996020860186613f7c565b6040516126a7929190613fbf565b5f604051808303815f865af19150503d805f81146126e0576040519150601f19603f3d011682016040523d82523d5f602084013e6126e5565b606091505b5091509150811561181a578085848151811061270357612703613bf4565b60200260200101819052505050600101612646565b5090949350505050565b6001600160a01b0383165f9081526065602052604090819020905163175a8f4360e01b8152736afb8d582e306da5646adaa568a5f04281e086329063175a8f43906108d990849060669089908990899060040161419d565b61278382612818565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156127c757611d7d828261288e565b610d39612900565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661225457604051631afcd79f60e31b815260040160405180910390fd5b806001600160a01b03163b5f0361284d57604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610d26565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516128aa91906141dc565b5f60405180830381855af49150503d805f81146128e2576040519150601f19603f3d011682016040523d82523d5f602084013e6128e7565b606091505b50915091506128f785838361291f565b95945050505050565b34156122545760405163b398979f60e01b815260040160405180910390fd5b6060826129345761292f8261297b565b611672565b815115801561294b57506001600160a01b0384163b155b1561297457604051639996b31560e01b81526001600160a01b0385166004820152602401610d26565b5080611672565b80511561298b5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5080546129b090613970565b5f825580601f106129bf575050565b601f0160209004905f5260205f209081019061216691905b808211156129ea575f81556001016129d7565b5090565b634e487b7160e01b5f52604160045260245ffd5b60405160c0810167ffffffffffffffff81118282101715612a2557612a256129ee565b60405290565b60405160e0810167ffffffffffffffff81118282101715612a2557612a256129ee565b604051601f8201601f1916810167ffffffffffffffff81118282101715612a7757612a776129ee565b604052919050565b6001600160a01b0381168114612166575f80fd5b8035612a9e81612a7f565b919050565b5f67ffffffffffffffff821115612abc57612abc6129ee565b5060051b60200190565b5f82601f830112612ad5575f80fd5b81356020612aea612ae583612aa3565b612a4e565b82815260059290921b84018101918181019086841115612b08575f80fd5b8286015b84811015612b235780358352918301918301612b0c565b509695505050505050565b80356bffffffffffffffffffffffff81168114612a9e575f80fd5b5f60208284031215612b59575f80fd5b813567ffffffffffffffff80821115612b70575f80fd5b9083019060c08286031215612b83575f80fd5b612b8b612a02565b8235612b9681612a7f565b8152602083013582811115612ba9575f80fd5b612bb587828601612ac6565b6020830152506040830135604082015260608301359150612bd582612a7f565b81606082015260808301359150612beb82612a7f565b816080820152612bfd60a08401612b2e565b60a082015295945050505050565b803563ffffffff81168114612a9e575f80fd5b5f60208284031215612c2e575f80fd5b813567ffffffffffffffff80821115612c45575f80fd5b9083019060e08286031215612c58575f80fd5b612c60612a2b565b612c6983612a93565b8152602083013582811115612c7c575f80fd5b612c8887828601612ac6565b602083015250612c9a60408401612a93565b6040820152612cab60608401612b2e565b6060820152612cbc60808401612c0b565b608082015260a083013560a082015260c083013560c082015280935050505092915050565b5f60208284031215612cf1575f80fd5b813561167281612a7f565b5f8083601f840112612d0c575f80fd5b50813567ffffffffffffffff811115612d23575f80fd5b602083019150836020828501011115612d3a575f80fd5b9250929050565b5f805f805f60808688031215612d55575f80fd5b8535612d6081612a7f565b94506020860135612d7081612a7f565b935060408601359250606086013567ffffffffffffffff811115612d92575f80fd5b612d9e88828901612cfc565b969995985093965092949392505050565b5f8060408385031215612dc0575f80fd5b8235612dcb81612a7f565b9150602083013567ffffffffffffffff811115612de6575f80fd5b612df285828601612ac6565b9150509250929050565b5f805f8060808587031215612e0f575f80fd5b8435612e1a81612a7f565b966020860135965060408601359560600135945092505050565b5f60208284031215612e44575f80fd5b5035919050565b5f8060408385031215612e5c575f80fd5b8235612e6781612a7f565b946020939093013593505050565b5f8060408385031215612e86575f80fd5b82359150602083013567ffffffffffffffff811115612de6575f80fd5b5f8060408385031215612eb4575f80fd5b50508035926020909101359150565b5f5b83811015612edd578181015183820152602001612ec5565b50505f910152565b5f8151808452612efc816020860160208601612ec3565b601f01601f19169290920160200192915050565b602081525f6116726020830184612ee5565b5f805f60608486031215612f34575f80fd5b8335612f3f81612a7f565b9250602084013567ffffffffffffffff811115612f5a575f80fd5b612f6686828701612ac6565b9250506040840135612f7781612a7f565b809150509250925092565b5f67ffffffffffffffff821115612f9b57612f9b6129ee565b50601f01601f191660200190565b5f8060408385031215612fba575f80fd5b8235612fc581612a7f565b9150602083013567ffffffffffffffff811115612fe0575f80fd5b8301601f81018513612ff0575f80fd5b8035612ffe612ae582612f82565b818152866020838501011115613012575f80fd5b816020840160208301375f602083830101528093505050509250929050565b80358015158114612a9e575f80fd5b5f8060408385031215613051575f80fd5b823561305c81612a7f565b915061306a60208401613031565b90509250929050565b5f805f8060808587031215613086575f80fd5b843561309181612a7f565b935060208501359250604085013567ffffffffffffffff8111156130b3575f80fd5b6130bf87828801612ac6565b92505060608501356130d081612a7f565b939692955090935050565b5f805f805f608086880312156130ef575f80fd5b85356130fa81612a7f565b9450602086013567ffffffffffffffff80821115613116575f80fd5b61312289838a01612ac6565b95506040880135915060038210613137575f80fd5b9093506060870135908082111561314c575f80fd5b50612d9e88828901612cfc565b5f8083601f840112613169575f80fd5b50813567ffffffffffffffff811115613180575f80fd5b6020830191508360208260051b8501011115612d3a575f80fd5b5f805f805f606086880312156131ae575f80fd5b85356131b981612a7f565b9450602086013567ffffffffffffffff808211156131d5575f80fd5b6131e189838a01613159565b909650945060408801359150808211156131f9575f80fd5b818801915088601f83011261320c575f80fd5b81358181111561321a575f80fd5b8960206101608302850101111561322f575f80fd5b9699959850939650602001949392505050565b5f805f60608486031215613254575f80fd5b833561325f81612a7f565b9250602084013561326f81612a7f565b929592945050506040919091013590565b5f805f8060808587031215613293575f80fd5b843561329e81612a7f565b9350602085013567ffffffffffffffff8111156132b9575f80fd5b6132c587828801612ac6565b93505060408501356132d681612a7f565b9396929550929360600135925050565b5f805f805f60a086880312156132fa575f80fd5b853561330581612a7f565b9450602086013567ffffffffffffffff811115613320575f80fd5b61332c88828901612ac6565b959895975050505060408401359360608101359360809091013592509050565b5f806020838503121561335d575f80fd5b823567ffffffffffffffff811115613373575f80fd5b61337f85828601613159565b90969095509350505050565b5f602080830181845280855180835260408601915060408160051b87010192508387015f5b828110156133de57603f198886030184526133cc858351612ee5565b945092850192908501906001016133b0565b5092979650505050505050565b5f805f805f60a086880312156133ff575f80fd5b853561340a81612a7f565b9450602086013567ffffffffffffffff811115613425575f80fd5b61343188828901612ac6565b94505060408601359250606086013561344981612a7f565b949793965091946080013592915050565b5f805f806080858703121561346d575f80fd5b843561347881612a7f565b9350602085013567ffffffffffffffff811115613493575f80fd5b61349f87828801612ac6565b93505060408501356134b081612a7f565b91506134be60608601613031565b905092959194509250565b5f805f606084860312156134db575f80fd5b83356134e681612a7f565b9250602084013591506040840135612f7781612a7f565b5f805f805f8060c08789031215613512575f80fd5b863561351d81612a7f565b9550602087013567ffffffffffffffff811115613538575f80fd5b61354489828a01612ac6565b95505060408701359350606087013592506080870135915060a087013561356a81612a7f565b809150509295509295509295565b5f8060408385031215613589575f80fd5b823561359481612a7f565b915060208301356135a481612a7f565b809150509250929050565b5f805f80608085870312156135c2575f80fd5b84356135cd81612a7f565b9350602085013567ffffffffffffffff8111156135e8575f80fd5b6135f487828801612ac6565b935050604085013561360581612a7f565b91506134be60608601612b2e565b5f805f60608486031215613625575f80fd5b833561363081612a7f565b95602085013595506040909401359392505050565b602080825282518282018190525f9190848201906040850190845b8181101561367c57835183529284019291840191600101613660565b50909695505050505050565b5f805f6060848603121561369a575f80fd5b83356136a581612a7f565b9250602084013567ffffffffffffffff8111156136c0575f80fd5b6136cc86828701612ac6565b925050604084013590509250925092565b5f805f80608085870312156136f0575f80fd5b84356136fb81612a7f565b9350602085013592506040850135915060608501356130d081612a7f565b5f8151808452602080850194508084015f5b838110156137475781518752958201959082019060010161372b565b509495945050505050565b8581528460208201528360408201525f6001600160a01b03808516606084015260a060808401528084511660a0840152602084015160c08085015261379b610160850182613719565b604086015160e08601526060860151831661010086015260808601519092166101208501525060a0909301516bffffffffffffffffffffffff166101409092019190915250949350505050565b85815284602082015283604082015260a060608201525f6001600160a01b038085511660a0840152602085015160e060c085015261382a610180850182613719565b604087015190921660e08501525060608501516bffffffffffffffffffffffff1661010084015260808501519061386a61012085018363ffffffff169052565b60a086015161014085015260c08601516101608501526001600160a01b03851660808501529150611ad39050565b8481528360208201526001600160a01b0383166040820152608060608201525f611ad36080830184613719565b828152604060208201525f611e206040830184613719565b5f80604083850312156138ee575f80fd5b825167ffffffffffffffff811115613904575f80fd5b8301601f81018513613914575f80fd5b8051613922612ae582612f82565b818152866020838501011115613936575f80fd5b613947826020830160208601612ec3565b60209590950151949694955050505050565b5f60208284031215613969575f80fd5b5051919050565b600181811c9082168061398457607f821691505b6020821081036139a257634e487b7160e01b5f52602260045260245ffd5b50919050565b601f821115611d7d575f81815260208120601f850160051c810160208610156139ce5750805b601f850160051c820191505b81811015610f47578281556001016139da565b815167ffffffffffffffff811115613a0757613a076129ee565b613a1b81613a158454613970565b846139a8565b602080601f831160018114613a4e575f8415613a375750858301515b5f19600386901b1c1916600185901b178555610f47565b5f85815260208120601f198616915b82811015613a7c57888601518255948401946001909101908401613a5d565b5085821015613a9957878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b8581528460208201525f6001600160a01b03808616604084015260a06060840152613ad760a0840186613719565b91508084166080840152509695505050505050565b838152606060208201525f613b046060830185613719565b90506001600160a01b0383166040830152949350505050565b8781528660208201525f6001600160a01b038088166040840152808716606084015260e06080840152613b5360e0840187613719565b60a08401959095529290921660c0909101525095945050505050565b8781528660208201526001600160a01b038616604082015260c060608201525f613b9c60c0830187613719565b60038610613bb857634e487b7160e01b5f52602160045260245ffd5b85608084015282810360a0840152838152838560208301375f602085830101526020601f19601f86011682010191505098975050505050505050565b634e487b7160e01b5f52603260045260245ffd5b5f813561ffff811681146123ac575f80fd5b8135613c2581612a7f565b81546001600160a01b0319166001600160a01b03821617825550610d39613c4e60208401613c08565b82547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff1660a09190911b75ffff000000000000000000000000000000000000000016178255565b8135613ca081612a7f565b81546001600160a01b0319166001600160a01b03821617825550613cc9613c4e60208401613c08565b613d21613cd860408401613c08565b82547fffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffffffff1660b09190911b77ffff0000000000000000000000000000000000000000000016178255565b613d7b613d3060608401613c08565b82547fffffffffffff0000ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b79ffff00000000000000000000000000000000000000000000000016178255565b600181016080830135613d8d81612a7f565b81546001600160a01b0319166001600160a01b03821617825550613db6613c4e60a08501613c08565b613dc5613cd860c08501613c08565b613dd4613d3060e08501613c08565b613de16101008401613c08565b81547fffffffff0000ffffffffffffffffffffffffffffffffffffffffffffffffffff1660d09190911b7bffff000000000000000000000000000000000000000000000000000016179055610d39610120830160028301613c1a565b5f6101008a83528960208401528860408401526001600160a01b03808916606085015280881660808501528160a0850152613e7a82850188613719565b951660c0840152505060e001529695505050505050565b5f6001600160a01b03808351168452806020840151166020850152604083015160e06040860152613ec560e0860182613719565b90506060840151606086015260ff608085015116608086015260a084015160a08601528160c08501511660c0860152809250505092915050565b838152826020820152606060408201525f6128f76060830184613e91565b5f6101208b83528a60208401528960408401526001600160a01b03808a16606085015280891660808501528160a0850152613f5a82850189613719565b60c08501979097529490941660e0830152506101000152509695505050505050565b5f808335601e19843603018112613f91575f80fd5b83018035915067ffffffffffffffff821115613fab575f80fd5b602001915036819003821315612d3a575f80fd5b818382375f9101908152919050565b8681528560208201525f6001600160a01b03808716604084015260c06060840152613ffc60c0840187613719565b941660808301525090151560a090910152949350505050565b848152836020820152608060408201525f6140336080830185613e91565b90506001600160a01b038316606083015295945050505050565b5f6101008a83528960208401528860408401526001600160a01b03808916606085015280881660808501528160a085015261408a82850188613719565b951660c084015250506bffffffffffffffffffffffff9190911660e0909101529695505050505050565b5f60208083850312156140c5575f80fd5b825167ffffffffffffffff8111156140db575f80fd5b8301601f810185136140eb575f80fd5b80516140f9612ae582612aa3565b81815260059190911b82018301908381019087831115614117575f80fd5b928401925b828410156118ff5783518252928401929084019061411c565b8681528560208201525f6001600160a01b03808716604084015280861660608401525060c0608083015261416c60c0830185613719565b90508260a0830152979650505050505050565b5f8235603e19833603018112614193575f80fd5b9190910192915050565b8581528460208201526001600160a01b038416604082015260a060608201525f6141ca60a0830185612ee5565b82810360808401526116528185613719565b5f8251614193818460208701612ec356fea164736f6c6343000814000a8af398995b04c28e9951adb9721ef74c74f93e6a478f39e7e0777be13527e7ef0000000000000000000000000000000000000000000000000000000000000356000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e69909000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f
Deployed Bytecode
0x6080604052600436106102da575f3560e01c80638da5cb5b1161017b578063c80e5af6116100d1578063ee64bec111610087578063fbc717d911610062578063fbc717d914610823578063fd6010d214610836578063fdbed69914610849575f80fd5b8063ee64bec1146107c5578063f32a12ac146107f1578063f86f502414610810575f80fd5b8063d613d98c116100b7578063d613d98c14610780578063dbdc3e2314610793578063dd82d0d7146107b2575f80fd5b8063c80e5af614610742578063cb21c98d14610761575f80fd5b806398548e4811610131578063ad16f3bc1161010c578063ad16f3bc146106bc578063ad3cb1cc146106db578063c40ab4e114610723575f80fd5b806398548e48146106535780639b19251a14610672578063ac9650d81461069d575f80fd5b806394bca9ed1161016157806394bca9ed146105e957806397a998f31461060857806397e4ee2a14610627575f80fd5b80638da5cb5b146105b85780638daaf0a2146105d6575f80fd5b806335fd631a1161023057806364d5d9fa116101e65780638129fc1c116101c15780638129fc1c1461055257806388d81ab71461055a578063899e5d851461056d575f80fd5b806364d5d9fa146104f55780636b10101814610514578063811f4dce14610533575f80fd5b80634f1ef286116102165780634f1ef286146104bb57806352d1902d146104ce57806358200611146104e2575f80fd5b806335fd631a146104705780633964aa401461049c575f80fd5b806317a89bde116102905780631fe543e31161026b5780631fe543e31461041f57806322bf8c2f1461043e5780632b2aa8c41461045d575f80fd5b806317a89bde146103cd5780631e2eaeaf146103e05780631fb695391461040c575f80fd5b806313af4035116102c057806313af40351461032e578063150b7a021461034d5780631745f562146103ae575f80fd5b806308e316de146102f0578063127257341461030f575f80fd5b366102ec576102ea335f34610868565b005b5f80fd5b3480156102fb575f80fd5b506102ea61030a366004612b49565b61090b565b34801561031a575f80fd5b506102ea610329366004612c1e565b6109e8565b348015610339575f80fd5b506102ea610348366004612ce1565b610a93565b348015610358575f80fd5b50610378610367366004612d41565b630a85bd0160e11b95945050505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020015b60405180910390f35b3480156103b9575f80fd5b506102ea6103c8366004612daf565b610ae5565b6102ea6103db366004612dfc565b610b3c565b3480156103eb575f80fd5b506103fe6103fa366004612e34565b5490565b6040519081526020016103a5565b6102ea61041a366004612e4b565b610c18565b34801561042a575f80fd5b506102ea610439366004612e75565b610cb0565b348015610449575f80fd5b506102ea610458366004612daf565b610d3d565b6102ea61046b366004612e4b565b610f4f565b34801561047b575f80fd5b5061048f61048a366004612ea3565b610fc2565b6040516103a59190612f10565b3480156104a7575f80fd5b506102ea6104b6366004612f22565b61103a565b6102ea6104c9366004612fa9565b6110a6565b3480156104d9575f80fd5b506103fe6110c1565b6102ea6104f0366004613040565b6110ef565b348015610500575f80fd5b506102ea61050f366004612daf565b61117a565b34801561051f575f80fd5b506102ea61052e366004613073565b6111cb565b34801561053e575f80fd5b506102ea61054d3660046130db565b611236565b6102ea6112bf565b6102ea61056836600461319a565b6113dd565b348015610578575f80fd5b506105a07f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f81565b6040516001600160a01b0390911681526020016103a5565b3480156105c3575f80fd5b505f546105a0906001600160a01b031681565b6102ea6105e4366004613242565b610868565b3480156105f4575f80fd5b506102ea610603366004613280565b61147f565b348015610613575f80fd5b506103fe6106223660046132e6565b611522565b348015610632575f80fd5b5061064661064136600461334c565b61165e565b6040516103a5919061338b565b34801561065e575f80fd5b506102ea61066d3660046133eb565b611679565b34801561067d575f80fd5b506103fe61068c366004612ce1565b60326020525f908152604090205481565b3480156106a8575f80fd5b506106466106b736600461334c565b61171e565b3480156106c7575f80fd5b506103fe6106d636600461345a565b61184d565b3480156106e6575f80fd5b5061048f6040518060400160405280600581526020017f352e302e3000000000000000000000000000000000000000000000000000000081525081565b34801561072e575f80fd5b506102ea61073d3660046134c9565b61190a565b34801561074d575f80fd5b506103fe61075c3660046134fd565b61199c565b34801561076c575f80fd5b506103fe61077b366004612ce1565b611add565b6102ea61078e366004613578565b611bba565b34801561079e575f80fd5b506102ea6107ad3660046135af565b611c7d565b6102ea6107c0366004612e4b565b611d4f565b3480156107d0575f80fd5b506107e46107df366004613613565b611d82565b6040516103a59190613645565b3480156107fc575f80fd5b506102ea61080b366004613040565b611e28565b6102ea61081e366004613613565b611eb6565b6102ea610831366004613688565b611f5b565b6102ea610844366004613578565b611fe7565b348015610854575f80fd5b506103fe6108633660046136dd565b612068565b6108718261212e565b6001600160a01b038381165f8181526066602052604080822090516375de5a7760e11b8152600481018290526024810193909352928516604483015260648201849052608482015273e55b4c920df2473622be36e38e183691a61242799063ebbcb4ee9060a4015b5f6040518083038186803b1580156108ef575f80fd5b505af4158015610901573d5f803e3d5ffd5b5050505050505050565b6109188160200151612169565b5f80610926835f01516121f3565b6001600160a01b038082168087525f90815260696020908152604080832060808a01519094168352929052819020905163137d407b60e21b815292945090925073e43da036d82e8d667ab2e8414a7017f6aa728d3491634df501ec916109b79186916066917f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f908a90600401613752565b5f6040518083038186803b1580156109cd575f80fd5b505af41580156109df573d5f803e3d5ffd5b50505050505050565b6109f58160200151612169565b5f80610a03835f01516121f3565b6001600160a01b038082168087525f908152606960209081526040808320818a015190941683529290528190209051634d59baa160e01b8152929450909250736afb8d582e306da5646adaa568a5f04281e0863291634d59baa1916109b791869160669189907f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f906004016137e8565b610a9b61222b565b5f80546001600160a01b0319166001600160a01b0383169081178255604051909133917f8292fce18fa69edf4db7b94ea2e58241df0ae57f97e0a6c9b29067028bf92d769190a350565b610aee81612169565b5f80610af9846121f3565b604051635d2b6fa760e11b81529193509150732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f469063ba56df4e906108d990859060669086908990600401613898565b3415610b4d57610b4d335f34610868565b5f80610b58866121f3565b604051632d905ae360e11b815260048101839052606660248201526001600160a01b037f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f81166044830152821660648201526084810188905260a4810187905260c481018690529193509150732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f4690635b20b5c69060e4015b5f6040518083038186803b158015610bfa575f80fd5b505af4158015610c0c573d5f803e3d5ffd5b50505050505050505050565b610c20612256565b6001600160a01b038281165f818152606660205260409081902090516375de5a7760e11b81526004810182905260248101929092527f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f9092166044820152606481018390526001608482015273e55b4c920df2473622be36e38e183691a61242799063ebbcb4ee9060a4016109b7565b336001600160a01b037f000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e699091614610d2f5760405163073e64fd60e21b81523360048201526001600160a01b037f000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e699091660248201526044015b60405180910390fd5b610d398282612283565b5050565b610d4681612169565b5f80610d51846121f3565b6040516362e8b2b960e01b815291935091505f908190736afb8d582e306da5646adaa568a5f04281e08632906362e8b2b990610d9390879089906004016138c5565b5f60405180830381865af4158015610dad573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052610dd491908101906138dd565b90925090508015610f47576040516305d3b1d360e41b81527f8af398995b04c28e9951adb9721ef74c74f93e6a478f39e7e0777be13527e7ef600482015267ffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000035616602482015260036044820152620c3500606482015263ffffffff821660848201525f907f000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e699096001600160a01b031690635d3b1d309060a4016020604051808303815f875af1158015610eb0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ed49190613959565b6040805160608101825260018082526001600160a01b0380891660208085019182528486018a81525f8881526064909252959020845191519092166c01000000000000000000000000026bffffffffffffffffffffffff919091161781559251939450909290820190610c0c90826139ed565b505050505050565b610f57612256565b305f9081526066602052604080822090516390dfbef160e01b8152600481018290523360248201526001600160a01b03851660448201526064810184905260848101929092529073e55b4c920df2473622be36e38e183691a6124279906390dfbef19060a4016109b7565b60605f600583901b67ffffffffffffffff811115610fe257610fe26129ee565b6040519080825280601f01601f19166020018201604052801561100c576020820181803683370190505b5090505f5b8381101561103257808501546001820160051b830152600181019050611011565b509392505050565b61104382612169565b5f61104d84612360565b6001600160a01b0383165f9081526066602052604090819020905163f51fda0d60e01b815291925073325a28e02a1cd6a9ddd1a8d107125639513d8bf89163f51fda0d916108d991859190899089908990600401613aa9565b6110ae6123b2565b6110b782612469565b610d398282612471565b5f6110ca612540565b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc90565b6110f7612256565b6001600160a01b0382165f9081526067602052604090205460ff16151581151514610d39576001600160a01b0382165f81815260676020908152604091829020805460ff191685151590811790915591519182527fb7e6048e8f02a75253b4e7aa2cfedf45e7f80999934aa6e73bfca1214fde0a61910160405180910390a25050565b61118381612169565b5f61118d83612360565b604051636ceb7d5d60e01b815290915073325a28e02a1cd6a9ddd1a8d107125639513d8bf890636ceb7d5d906109b790849086908890600401613aec565b6111d482612169565b5f806111df866121f3565b335f90815260666020526040908190209051630a5f4f7560e31b815292945090925073325a28e02a1cd6a9ddd1a8d107125639513d8bf8916352fa7ba891610be4918691908b9087908b908d908c90600401613b1d565b5f80611241876121f3565b6040516310416ec760e01b8152919350915073e43da036d82e8d667ab2e8414a7017f6aa728d34906310416ec79061128a90859060669086908c908c908c908c90600401613b6f565b5f6040518083038186803b1580156112a0575f80fd5b505af41580156112b2573d5f803e3d5ffd5b5050505050505050505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156113045750825b90505f8267ffffffffffffffff1660011480156113205750303b155b90508115801561132e575080155b1561134c5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561137b57845468ff00000000000000001916600160401b1785555b611383612589565b61138b6125bc565b83156113d657845468ff000000000000000019168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050565b6113e5612256565b5f5b83811015610f475782828281811061140157611401613bf4565b9050610160020160695f886001600160a01b03166001600160a01b031681526020019081526020015f205f87878581811061143e5761143e613bf4565b90506020020160208101906114539190612ce1565b6001600160a01b0316815260208101919091526040015f206114758282613c95565b50506001016113e7565b61148883612169565b5f80611493866121f3565b6001600160a01b038082165f908152606960209081526040808320938a16835292905281902090516303e4298f60e01b8152929450909250732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f46916303e4298f91610be49186916066917f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f9088908d908d908d90600401613e3d565b5f61152c85612169565b611535846125c4565b5f80611540886121f3565b915091508173325a28e02a1cd6a9ddd1a8d107125639513d8bf863de8b49b5909160665f336001600160a01b03166001600160a01b031681526020019081526020015f206040518060e001604052808d6001600160a01b03168152602001866001600160a01b031681526020018c81526020018b81526020018a60ff1681526020018981526020017f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f6001600160a01b03168152506040518463ffffffff1660e01b815260040161161393929190613eff565b602060405180830381865af415801561162e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116529190613959565b98975050505050505050565b6060611668612256565b61167283836125f1565b9392505050565b61168284612169565b5f8061168d876121f3565b6001600160a01b038082165f908152606960209081526040808320938a16835292905281902090516307fe595d60e11b8152929450909250732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f4691630ffcb2ba9161128a9186916066917f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f9088908e908e908e908e90600401613f1d565b60605f8267ffffffffffffffff81111561173a5761173a6129ee565b60405190808252806020026020018201604052801561176d57816020015b60608152602001906001900390816117585790505b5090505f5b83811015611032575f803087878581811061178f5761178f613bf4565b90506020028101906117a19190613f7c565b6040516117af929190613fbf565b5f60405180830381855af49150503d805f81146117e7576040519150601f19603f3d011682016040523d82523d5f602084013e6117ec565b606091505b5091509150811561181a578084848151811061180a5761180a613bf4565b6020026020010181905250611843565b80511561182a5780518082602001fd5b6040516302cf089f60e51b815260040160405180910390fd5b5050600101611772565b5f61185784612169565b5f80611862876121f3565b6001600160a01b0387165f90815260666020526040908190209051637bd9d52160e11b815292945090925073325a28e02a1cd6a9ddd1a8d107125639513d8bf89163f7b3aa42916118c09186919086908c908c908c90600401613fce565b602060405180830381865af41580156118db573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118ff9190613959565b979650505050505050565b335f908152606660205260409081902090516390dfbef160e01b8152600481018290526001600160a01b03808416602483015280861660448301819052606483018690527f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f90911614608482015273e55b4c920df2473622be36e38e183691a6124279906390dfbef19060a4016108d9565b5f6119a686612169565b6119af856125c4565b5f806119ba896121f3565b915091508173325a28e02a1cd6a9ddd1a8d107125639513d8bf863f6815346909160665f886001600160a01b03166001600160a01b031681526020019081526020015f206040518060e001604052808e6001600160a01b03168152602001866001600160a01b031681526020018d81526020018c81526020018b60ff1681526020018a81526020017f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f6001600160a01b0316815250886040518563ffffffff1660e01b8152600401611a8f9493929190614015565b602060405180830381865af4158015611aaa573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ace9190613959565b925050505b9695505050505050565b6001600160a01b0381165f81815260666020526040808220905163c449bd9160e01b81526004810182905260248101939093529091829073e55b4c920df2473622be36e38e183691a61242799063c449bd9190604401602060405180830381865af4158015611b4e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b729190613959565b6001600160a01b037f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f165f908152600484016020526040902054909150819003949350505050565b611bc2612256565b6001600160a01b0382165f9081526065602052604090206007810154600160401b900467ffffffffffffffff1615611bf8575f80fd5b6007810180546fffffffffffffffff00000000000000001916600160401b17905560088101805467ffffffffffffffff1916600117905580546001600160a01b038084166001600160a01b031990921682178355604051908516907f48c2aef7c236e185cfd4d3fe7248c47fd93ec0d6f550e4ccec5c5cb6b0009c03905f90a3505050565b611c8683612169565b5f611c9085612360565b6001600160a01b038087165f90815260696020908152604080832093881683529290528190209051631c1a270f60e01b8152919250732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f4691631c1a270f91611d1c9185916066917f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f908c908c908c908c9060040161404d565b5f6040518083038186803b158015611d32575f80fd5b505af4158015611d44573d5f803e3d5ffd5b505050505050505050565b611d57612256565b6001600160a01b03919091165f9081526066602052604090206002018054919091039055565b505050565b6001600160a01b0383165f9081526065602052604090819020905163f1cd5ef360e01b81526004810191909152602481018390526044810182905260609073325a28e02a1cd6a9ddd1a8d107125639513d8bf89063f1cd5ef3906064015f60405180830381865af4158015611df9573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052611e2091908101906140b4565b949350505050565b611e3061222b565b8015611e56576001600160a01b0382165f90815260326020526040902060019055611e6f565b6001600160a01b0382165f908152603260205260408120555b604080516001600160a01b038416815282151560208201527f547820db618200fb54f457140ffaf92a642299d95533c33c155b13ba68203f96910160405180910390a15050565b3415611ec757611ec7335f34610868565b5f80611ed2856121f3565b604051630848963560e41b815260048101839052606660248201526001600160a01b037f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f81166044830152821660648201526084810187905260a481018690529193509150736afb8d582e306da5646adaa568a5f04281e086329063848963509060c401611d1c565b611f6482612169565b3415611f7557611f75335f34610868565b5f80611f80856121f3565b604051639fc46d9b60e01b8152919350915073e43da036d82e8d667ab2e8414a7017f6aa728d3490639fc46d9b90611d1c90859060669086907f000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f908b908b90600401614135565b611fef612256565b6001600160a01b038281165f90815260686020526040902054811690821614610d39576001600160a01b038281165f8181526068602052604080822080546001600160a01b0319169486169485179055517f39b72dbfaede10f9f18a885eb02789166a1e35bc946c38a5fcf1121475cd06659190a35050565b5f8061207386612360565b6001600160a01b038781165f81815260696020526040908190209051630c58df2560e01b81526004810185905260666024820152604481019190915260648101919091526084810188905260a4810187905290851660c482015290915073325a28e02a1cd6a9ddd1a8d107125639513d8bf890630c58df259060e401602060405180830381865af415801561210a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ad39190613959565b6001600160a01b0381165f9081526067602052604090205460ff1661216657604051633dd1b30560e01b815260040160405180910390fd5b50565b80515f0361218a57604051633494a40d60e21b815260040160405180910390fd5b60015b8151811015610d39578160018203815181106121ab576121ab613bf4565b60200260200101518282815181106121c5576121c5613bf4565b6020026020010151116121eb57604051633494a40d60e21b815260040160405180910390fd5b60010161218d565b6001600160a01b038082165f908152606860205260408120549091829116806122195750825b61222281612360565b94909350915050565b5f546001600160a01b03163314612254576040516282b42960e81b815260040160405180910390fd5b565b335f908152603260205260408120549003612254576040516282b42960e81b815260040160405180910390fd5b5f8281526064602052604090208054600182018054612342926c0100000000000000000000000090046001600160a01b031691906122c090613970565b80601f01602080910402602001604051908101604052809291908181526020018280546122ec90613970565b80156123375780601f1061230e57610100808354040283529160200191612337565b820191905f5260205f20905b81548152906001019060200180831161231a57829003601f168201915b505050505084612722565b5f838152606460205260408120818155906113d660018301826129a4565b6001600160a01b0381165f9081526065602052604081206007810154600160401b900467ffffffffffffffff1682036123ac576040516309d3a9d960e31b815260040160405180910390fd5b92915050565b306001600160a01b037f0000000000000000000000009ebc37b4fa47bbff2258b4cbb66cd929db14446016148061244b57507f0000000000000000000000009ebc37b4fa47bbff2258b4cbb66cd929db1444606001600160a01b031661243f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b6001600160a01b031614155b156122545760405163703e46dd60e11b815260040160405180910390fd5b61216661222b565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156124cb575060408051601f3d908101601f191682019092526124c891810190613959565b60015b6124f357604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610d26565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc811461253657604051632a87526960e21b815260048101829052602401610d26565b611d7d838361277a565b306001600160a01b037f0000000000000000000000009ebc37b4fa47bbff2258b4cbb66cd929db14446016146122545760405163703e46dd60e11b815260040160405180910390fd5b61259f5f80546001600160a01b03191633179055565b5f80546001600160a01b0316815260326020526040902060019055565b6122546127cf565b80158015906125d35750428111155b1561216657604051633494a40d60e21b815260040160405180910390fd5b60605f8267ffffffffffffffff81111561260d5761260d6129ee565b60405190808252806020026020018201604052801561264057816020015b606081526020019060019003908161262b5790505b509050365f5b848110156127185785858281811061266057612660613bf4565b9050602002810190612672919061417f565b91505f806126836020850185612ce1565b6001600160a01b03166126996020860186613f7c565b6040516126a7929190613fbf565b5f604051808303815f865af19150503d805f81146126e0576040519150601f19603f3d011682016040523d82523d5f602084013e6126e5565b606091505b5091509150811561181a578085848151811061270357612703613bf4565b60200260200101819052505050600101612646565b5090949350505050565b6001600160a01b0383165f9081526065602052604090819020905163175a8f4360e01b8152736afb8d582e306da5646adaa568a5f04281e086329063175a8f43906108d990849060669089908990899060040161419d565b61278382612818565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156127c757611d7d828261288e565b610d39612900565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661225457604051631afcd79f60e31b815260040160405180910390fd5b806001600160a01b03163b5f0361284d57604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610d26565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516128aa91906141dc565b5f60405180830381855af49150503d805f81146128e2576040519150601f19603f3d011682016040523d82523d5f602084013e6128e7565b606091505b50915091506128f785838361291f565b95945050505050565b34156122545760405163b398979f60e01b815260040160405180910390fd5b6060826129345761292f8261297b565b611672565b815115801561294b57506001600160a01b0384163b155b1561297457604051639996b31560e01b81526001600160a01b0385166004820152602401610d26565b5080611672565b80511561298b5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5080546129b090613970565b5f825580601f106129bf575050565b601f0160209004905f5260205f209081019061216691905b808211156129ea575f81556001016129d7565b5090565b634e487b7160e01b5f52604160045260245ffd5b60405160c0810167ffffffffffffffff81118282101715612a2557612a256129ee565b60405290565b60405160e0810167ffffffffffffffff81118282101715612a2557612a256129ee565b604051601f8201601f1916810167ffffffffffffffff81118282101715612a7757612a776129ee565b604052919050565b6001600160a01b0381168114612166575f80fd5b8035612a9e81612a7f565b919050565b5f67ffffffffffffffff821115612abc57612abc6129ee565b5060051b60200190565b5f82601f830112612ad5575f80fd5b81356020612aea612ae583612aa3565b612a4e565b82815260059290921b84018101918181019086841115612b08575f80fd5b8286015b84811015612b235780358352918301918301612b0c565b509695505050505050565b80356bffffffffffffffffffffffff81168114612a9e575f80fd5b5f60208284031215612b59575f80fd5b813567ffffffffffffffff80821115612b70575f80fd5b9083019060c08286031215612b83575f80fd5b612b8b612a02565b8235612b9681612a7f565b8152602083013582811115612ba9575f80fd5b612bb587828601612ac6565b6020830152506040830135604082015260608301359150612bd582612a7f565b81606082015260808301359150612beb82612a7f565b816080820152612bfd60a08401612b2e565b60a082015295945050505050565b803563ffffffff81168114612a9e575f80fd5b5f60208284031215612c2e575f80fd5b813567ffffffffffffffff80821115612c45575f80fd5b9083019060e08286031215612c58575f80fd5b612c60612a2b565b612c6983612a93565b8152602083013582811115612c7c575f80fd5b612c8887828601612ac6565b602083015250612c9a60408401612a93565b6040820152612cab60608401612b2e565b6060820152612cbc60808401612c0b565b608082015260a083013560a082015260c083013560c082015280935050505092915050565b5f60208284031215612cf1575f80fd5b813561167281612a7f565b5f8083601f840112612d0c575f80fd5b50813567ffffffffffffffff811115612d23575f80fd5b602083019150836020828501011115612d3a575f80fd5b9250929050565b5f805f805f60808688031215612d55575f80fd5b8535612d6081612a7f565b94506020860135612d7081612a7f565b935060408601359250606086013567ffffffffffffffff811115612d92575f80fd5b612d9e88828901612cfc565b969995985093965092949392505050565b5f8060408385031215612dc0575f80fd5b8235612dcb81612a7f565b9150602083013567ffffffffffffffff811115612de6575f80fd5b612df285828601612ac6565b9150509250929050565b5f805f8060808587031215612e0f575f80fd5b8435612e1a81612a7f565b966020860135965060408601359560600135945092505050565b5f60208284031215612e44575f80fd5b5035919050565b5f8060408385031215612e5c575f80fd5b8235612e6781612a7f565b946020939093013593505050565b5f8060408385031215612e86575f80fd5b82359150602083013567ffffffffffffffff811115612de6575f80fd5b5f8060408385031215612eb4575f80fd5b50508035926020909101359150565b5f5b83811015612edd578181015183820152602001612ec5565b50505f910152565b5f8151808452612efc816020860160208601612ec3565b601f01601f19169290920160200192915050565b602081525f6116726020830184612ee5565b5f805f60608486031215612f34575f80fd5b8335612f3f81612a7f565b9250602084013567ffffffffffffffff811115612f5a575f80fd5b612f6686828701612ac6565b9250506040840135612f7781612a7f565b809150509250925092565b5f67ffffffffffffffff821115612f9b57612f9b6129ee565b50601f01601f191660200190565b5f8060408385031215612fba575f80fd5b8235612fc581612a7f565b9150602083013567ffffffffffffffff811115612fe0575f80fd5b8301601f81018513612ff0575f80fd5b8035612ffe612ae582612f82565b818152866020838501011115613012575f80fd5b816020840160208301375f602083830101528093505050509250929050565b80358015158114612a9e575f80fd5b5f8060408385031215613051575f80fd5b823561305c81612a7f565b915061306a60208401613031565b90509250929050565b5f805f8060808587031215613086575f80fd5b843561309181612a7f565b935060208501359250604085013567ffffffffffffffff8111156130b3575f80fd5b6130bf87828801612ac6565b92505060608501356130d081612a7f565b939692955090935050565b5f805f805f608086880312156130ef575f80fd5b85356130fa81612a7f565b9450602086013567ffffffffffffffff80821115613116575f80fd5b61312289838a01612ac6565b95506040880135915060038210613137575f80fd5b9093506060870135908082111561314c575f80fd5b50612d9e88828901612cfc565b5f8083601f840112613169575f80fd5b50813567ffffffffffffffff811115613180575f80fd5b6020830191508360208260051b8501011115612d3a575f80fd5b5f805f805f606086880312156131ae575f80fd5b85356131b981612a7f565b9450602086013567ffffffffffffffff808211156131d5575f80fd5b6131e189838a01613159565b909650945060408801359150808211156131f9575f80fd5b818801915088601f83011261320c575f80fd5b81358181111561321a575f80fd5b8960206101608302850101111561322f575f80fd5b9699959850939650602001949392505050565b5f805f60608486031215613254575f80fd5b833561325f81612a7f565b9250602084013561326f81612a7f565b929592945050506040919091013590565b5f805f8060808587031215613293575f80fd5b843561329e81612a7f565b9350602085013567ffffffffffffffff8111156132b9575f80fd5b6132c587828801612ac6565b93505060408501356132d681612a7f565b9396929550929360600135925050565b5f805f805f60a086880312156132fa575f80fd5b853561330581612a7f565b9450602086013567ffffffffffffffff811115613320575f80fd5b61332c88828901612ac6565b959895975050505060408401359360608101359360809091013592509050565b5f806020838503121561335d575f80fd5b823567ffffffffffffffff811115613373575f80fd5b61337f85828601613159565b90969095509350505050565b5f602080830181845280855180835260408601915060408160051b87010192508387015f5b828110156133de57603f198886030184526133cc858351612ee5565b945092850192908501906001016133b0565b5092979650505050505050565b5f805f805f60a086880312156133ff575f80fd5b853561340a81612a7f565b9450602086013567ffffffffffffffff811115613425575f80fd5b61343188828901612ac6565b94505060408601359250606086013561344981612a7f565b949793965091946080013592915050565b5f805f806080858703121561346d575f80fd5b843561347881612a7f565b9350602085013567ffffffffffffffff811115613493575f80fd5b61349f87828801612ac6565b93505060408501356134b081612a7f565b91506134be60608601613031565b905092959194509250565b5f805f606084860312156134db575f80fd5b83356134e681612a7f565b9250602084013591506040840135612f7781612a7f565b5f805f805f8060c08789031215613512575f80fd5b863561351d81612a7f565b9550602087013567ffffffffffffffff811115613538575f80fd5b61354489828a01612ac6565b95505060408701359350606087013592506080870135915060a087013561356a81612a7f565b809150509295509295509295565b5f8060408385031215613589575f80fd5b823561359481612a7f565b915060208301356135a481612a7f565b809150509250929050565b5f805f80608085870312156135c2575f80fd5b84356135cd81612a7f565b9350602085013567ffffffffffffffff8111156135e8575f80fd5b6135f487828801612ac6565b935050604085013561360581612a7f565b91506134be60608601612b2e565b5f805f60608486031215613625575f80fd5b833561363081612a7f565b95602085013595506040909401359392505050565b602080825282518282018190525f9190848201906040850190845b8181101561367c57835183529284019291840191600101613660565b50909695505050505050565b5f805f6060848603121561369a575f80fd5b83356136a581612a7f565b9250602084013567ffffffffffffffff8111156136c0575f80fd5b6136cc86828701612ac6565b925050604084013590509250925092565b5f805f80608085870312156136f0575f80fd5b84356136fb81612a7f565b9350602085013592506040850135915060608501356130d081612a7f565b5f8151808452602080850194508084015f5b838110156137475781518752958201959082019060010161372b565b509495945050505050565b8581528460208201528360408201525f6001600160a01b03808516606084015260a060808401528084511660a0840152602084015160c08085015261379b610160850182613719565b604086015160e08601526060860151831661010086015260808601519092166101208501525060a0909301516bffffffffffffffffffffffff166101409092019190915250949350505050565b85815284602082015283604082015260a060608201525f6001600160a01b038085511660a0840152602085015160e060c085015261382a610180850182613719565b604087015190921660e08501525060608501516bffffffffffffffffffffffff1661010084015260808501519061386a61012085018363ffffffff169052565b60a086015161014085015260c08601516101608501526001600160a01b03851660808501529150611ad39050565b8481528360208201526001600160a01b0383166040820152608060608201525f611ad36080830184613719565b828152604060208201525f611e206040830184613719565b5f80604083850312156138ee575f80fd5b825167ffffffffffffffff811115613904575f80fd5b8301601f81018513613914575f80fd5b8051613922612ae582612f82565b818152866020838501011115613936575f80fd5b613947826020830160208601612ec3565b60209590950151949694955050505050565b5f60208284031215613969575f80fd5b5051919050565b600181811c9082168061398457607f821691505b6020821081036139a257634e487b7160e01b5f52602260045260245ffd5b50919050565b601f821115611d7d575f81815260208120601f850160051c810160208610156139ce5750805b601f850160051c820191505b81811015610f47578281556001016139da565b815167ffffffffffffffff811115613a0757613a076129ee565b613a1b81613a158454613970565b846139a8565b602080601f831160018114613a4e575f8415613a375750858301515b5f19600386901b1c1916600185901b178555610f47565b5f85815260208120601f198616915b82811015613a7c57888601518255948401946001909101908401613a5d565b5085821015613a9957878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b8581528460208201525f6001600160a01b03808616604084015260a06060840152613ad760a0840186613719565b91508084166080840152509695505050505050565b838152606060208201525f613b046060830185613719565b90506001600160a01b0383166040830152949350505050565b8781528660208201525f6001600160a01b038088166040840152808716606084015260e06080840152613b5360e0840187613719565b60a08401959095529290921660c0909101525095945050505050565b8781528660208201526001600160a01b038616604082015260c060608201525f613b9c60c0830187613719565b60038610613bb857634e487b7160e01b5f52602160045260245ffd5b85608084015282810360a0840152838152838560208301375f602085830101526020601f19601f86011682010191505098975050505050505050565b634e487b7160e01b5f52603260045260245ffd5b5f813561ffff811681146123ac575f80fd5b8135613c2581612a7f565b81546001600160a01b0319166001600160a01b03821617825550610d39613c4e60208401613c08565b82547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff1660a09190911b75ffff000000000000000000000000000000000000000016178255565b8135613ca081612a7f565b81546001600160a01b0319166001600160a01b03821617825550613cc9613c4e60208401613c08565b613d21613cd860408401613c08565b82547fffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffffffff1660b09190911b77ffff0000000000000000000000000000000000000000000016178255565b613d7b613d3060608401613c08565b82547fffffffffffff0000ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b79ffff00000000000000000000000000000000000000000000000016178255565b600181016080830135613d8d81612a7f565b81546001600160a01b0319166001600160a01b03821617825550613db6613c4e60a08501613c08565b613dc5613cd860c08501613c08565b613dd4613d3060e08501613c08565b613de16101008401613c08565b81547fffffffff0000ffffffffffffffffffffffffffffffffffffffffffffffffffff1660d09190911b7bffff000000000000000000000000000000000000000000000000000016179055610d39610120830160028301613c1a565b5f6101008a83528960208401528860408401526001600160a01b03808916606085015280881660808501528160a0850152613e7a82850188613719565b951660c0840152505060e001529695505050505050565b5f6001600160a01b03808351168452806020840151166020850152604083015160e06040860152613ec560e0860182613719565b90506060840151606086015260ff608085015116608086015260a084015160a08601528160c08501511660c0860152809250505092915050565b838152826020820152606060408201525f6128f76060830184613e91565b5f6101208b83528a60208401528960408401526001600160a01b03808a16606085015280891660808501528160a0850152613f5a82850189613719565b60c08501979097529490941660e0830152506101000152509695505050505050565b5f808335601e19843603018112613f91575f80fd5b83018035915067ffffffffffffffff821115613fab575f80fd5b602001915036819003821315612d3a575f80fd5b818382375f9101908152919050565b8681528560208201525f6001600160a01b03808716604084015260c06060840152613ffc60c0840187613719565b941660808301525090151560a090910152949350505050565b848152836020820152608060408201525f6140336080830185613e91565b90506001600160a01b038316606083015295945050505050565b5f6101008a83528960208401528860408401526001600160a01b03808916606085015280881660808501528160a085015261408a82850188613719565b951660c084015250506bffffffffffffffffffffffff9190911660e0909101529695505050505050565b5f60208083850312156140c5575f80fd5b825167ffffffffffffffff8111156140db575f80fd5b8301601f810185136140eb575f80fd5b80516140f9612ae582612aa3565b81815260059190911b82018301908381019087831115614117575f80fd5b928401925b828410156118ff5783518252928401929084019061411c565b8681528560208201525f6001600160a01b03808716604084015280861660608401525060c0608083015261416c60c0830185613719565b90508260a0830152979650505050505050565b5f8235603e19833603018112614193575f80fd5b9190910192915050565b8581528460208201526001600160a01b038416604082015260a060608201525f6141ca60a0830185612ee5565b82810360808401526116528185613719565b5f8251614193818460208701612ec356fea164736f6c6343000814000a
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
8af398995b04c28e9951adb9721ef74c74f93e6a478f39e7e0777be13527e7ef0000000000000000000000000000000000000000000000000000000000000356000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e69909000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f
-----Decoded View---------------
Arg [0] : _keyHash (bytes32): 0x8af398995b04c28e9951adb9721ef74c74f93e6a478f39e7e0777be13527e7ef
Arg [1] : _subId (uint64): 854
Arg [2] : _vrfCoordinator (address): 0x271682DEB8C4E0901D1a1550aD2e64D568E69909
Arg [3] : flcToken (address): 0x102c776DDB30C754dEd4fDcC77A19230A60D4e4f
-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 8af398995b04c28e9951adb9721ef74c74f93e6a478f39e7e0777be13527e7ef
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000356
Arg [2] : 000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e69909
Arg [3] : 000000000000000000000000102c776ddb30c754ded4fdcc77a19230a60d4e4f
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.