Token
Wrapped TestAzuki (wTAZUKI)
ERC-721
Overview
Max Total Supply
0 wTAZUKI
Holders
0
Market
Onchain Market Cap
$0.00
Circulating Supply Market Cap
-
Other Info
Token Contract
Balance
0 wTAZUKILoading...
Loading
Loading...
Loading
Loading...
Loading
Minimal Proxy Contract for 0xcb72b1736e29104466ddf4a218081842c599742a
Contract Name:
BT404MirrorBridged
Compiler Version
v0.8.26+commit.8a97fa7a
Optimization Enabled:
Yes with 1000 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import {BT404} from "../BT404.sol"; import {BT404Mirror} from "../BT404Mirror.sol"; import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol"; import {OwnableRoles} from "solady/auth/OwnableRoles.sol"; contract BT404MirrorBridged is BT404Mirror, UUPSUpgradeable { /// @notice Keep the ROLE constants the same as the base contract. /// @dev the role that can upgrade the implementation. uint256 private constant _UPGRADE_MANAGER_ROLE = 1 << 61; /// @dev the role that can mint or burn the NFTs. uint256 private constant _MINT_BURN_MANAGER_ROLE = 1 << 71; // init with `address(1)` to prevent double-initializing constructor() payable BT404Mirror(address(1)) {} modifier onlyRoles(uint256 roles) { _checkRole(roles); _; } function _checkRole(uint256 roles) internal view { if (!OwnableRoles(baseERC20()).hasAnyRole(msg.sender, roles)) { revert Unauthorized(); } } function _authorizeUpgrade(address) internal override onlyRoles(_UPGRADE_MANAGER_ROLE) {} function initialize() public payable { // if deployer was set, can not initialize again if (_getBT404NFTStorage().deployer != address(0)) revert Unauthorized(); _initializeBT404Mirror(msg.sender); } function mint(address recipient, uint256[] memory ids, bool lock) public payable onlyRoles(_MINT_BURN_MANAGER_ROLE) { address base = baseERC20(); (bool success, bytes memory result) = base.call( abi.encodeWithSignature( "mintNFT(uint256,uint256[])", uint256(uint160(recipient)) << 96 | (lock ? 1 : 0), ids ) ); // @solidity memory-safe-assembly assembly { if iszero(and(eq(mload(add(result, 0x20)), 1), success)) { revert(add(result, 0x20), mload(result)) } let idLen := mload(ids) mstore(0x00, lock) for { let s := add(ids, 0x20) let end := add(s, shl(5, idLen)) } iszero(eq(s, end)) { s := add(s, 0x20) } { // Emit the {Transfer} event. log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, recipient, mload(s)) // Emit the {UpdateLockState} event. if lock { log3(0x00, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, recipient, mload(s)) } } } } function burn(address owner, uint256[] memory ids) public payable onlyRoles(_MINT_BURN_MANAGER_ROLE) { address base = baseERC20(); (bool success, bytes memory result) = base.call(abi.encodeWithSignature("burnNFT(address,uint256[])", owner, ids)); // @solidity memory-safe-assembly assembly { if iszero(and(eq(mload(add(result, 0x20)), 1), success)) { revert(add(result, 0x20), mload(result)) } let idLen := mload(ids) for { let s := add(ids, 0x20) let end := add(s, shl(5, idLen)) } iszero(eq(s, end)) { s := add(s, 0x20) } { // Don't emit the {UpdateLockState} event, as the NFTs already are burned. // Emit the {Transfer} event. log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, owner, 0, mload(s)) } } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import {LibBitmap} from "solady/utils/LibBitmap.sol"; import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol"; /// @title BT404 /// @notice BT404 is a hybrid ERC20 and ERC721 implementation that mints /// and burns NFTs based on an account's ERC20 token balance. /// /// @author FlooringLab /// @author Modified from DN404(https://github.com/Vectorized/dn404/src/DN404.sol) /// /// @dev Note: /// - The ERC721 data is stored in this base BT404 contract, however a /// BT404Mirror contract ***MUST*** be deployed and linked during /// initialization. abstract contract BT404 { using LibBitmap for LibBitmap.Bitmap; /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* EVENTS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. event Transfer(address indexed from, address indexed to, uint256 amount); /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. event Approval(address indexed owner, address indexed spender, uint256 amount); /// @dev Emitted when `target` sets their skipNFT flag to `status`. event SkipNFTSet(address indexed target, bool status); /// @dev Emitted when `exchangeNFTFeeBips` is set. event ExchangeMarketFeeSet(uint256 feeBips); /// @dev Emitted when `listMarketFeeBips` event ListMarketFeeSet(uint256 feeBips); /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. uint256 internal constant _TRANSFER_EVENT_SIGNATURE = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. uint256 internal constant _APPROVAL_EVENT_SIGNATURE = 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; /// @dev `keccak256(bytes("SkipNFTSet(address,bool)"))`. uint256 internal constant _SKIP_NFT_SET_EVENT_SIGNATURE = 0xb5a1de456fff688115a4f75380060c23c8532d14ff85f687cc871456d6420393; /// @dev `keccak256(bytes("ExchangeMarketFeeSet(uint256)"))`. uint256 internal constant _EXCHANGE_MARKET_FEE_SET_EVENT_SIGNATURE = 0xe10129be59d54095da8caee0e01e0b82530bb6275510fbb843816dda3a5921d6; /// @dev `keccak256(bytes("ListMarketFeeSet(uint256)"))`. uint256 internal constant _LIST_MARKET_FEE_SET_EVENT_SIGNATURE = 0xdf10c155355452a496e5ffa2e30708bc26ccb58e654d0b145ec6056bce9af822; /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* CUSTOM ERRORS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Thrown when attempting to double-initialize the contract. error DNAlreadyInitialized(); /// @dev Thrown when attempting to transfer or burn more tokens than sender's balance. error InsufficientBalance(); /// @dev Thrown when a spender attempts to transfer tokens with an insufficient allowance. error InsufficientAllowance(); /// @dev Thrown when minting an amount of tokens that would overflow the max tokens. error TotalSupplyOverflow(); /// @dev The unit cannot be zero. error InvalidUnit(); /// @dev Thrown when the caller for a fallback NFT function is not the mirror contract. error SenderNotMirror(); /// @dev Thrown when attempting to transfer tokens to the zero address. error TransferToZeroAddress(); /// @dev Thrown when the mirror address provided for initialization is the zero address. error MirrorAddressIsZero(); /// @dev Thrown when the link call to the mirror contract reverts. error LinkMirrorContractFailed(); /// @dev Thrown when setting an NFT token approval /// and the caller is not the owner or an approved operator. error ApprovalCallerNotOwnerNorApproved(); /// @dev Thrown when transferring an NFT /// and the caller is not the owner or an approved operator. error TransferCallerNotOwnerNorApproved(); /// @dev Thrown when transferring an NFT and the from address is not the current owner. error TransferFromIncorrectOwner(); /// @dev Thrown when checking the owner or approved address for a non-existent NFT. error TokenDoesNotExist(); /// @dev Thrown when exchanging the NFTs that locked. error ExchangeTokenLocked(); /// @dev Thrown when exchanging the same NFTs error ExchangeSameToken(); /// @dev Thrown when attempting to lock the NFTs that locked, /// or to unlock the NFTs that unlocked. error TokenLockStatusNoChange(); /// @dev Thrown when transferring tokens but the balance is insufficient to to maintain locked NFTs. error InsufficientBalanceToMaintainLockedTokens(); /// @dev Thrown when buy/sell with invalid price. error InvalidSalePrice(); /// @dev Thrown when buy/sell with invalid token. error InvalidOrderToken(); /// @dev Throw when NFT is not locked. error TokenNotLocked(); /// @dev Throw when buy/sell but the address is not matched. error InvalidSellerOrBuyer(); /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* CONSTANTS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev The flag to denote that the address data is initialized. uint8 internal constant _ADDRESS_DATA_INITIALIZED_FLAG = 1 << 0; /// @dev The flag to denote that the address should skip NFTs. uint8 internal constant _ADDRESS_DATA_SKIP_NFT_FLAG = 1 << 1; /// @dev The alias of the burned pool which will be used in `oo` map. /// It is the largest alias. uint32 internal constant _ADDRESS_ALIAS_BURNED_POOL = type(uint32).max; /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* STORAGE */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Struct containing an address's token data and settings. struct AddressData { // Auxiliary data. uint56 aux; // Flags for `initialized` and `skipNFT`. uint8 flags; // The alias for the address. Zero means absence of an alias. uint32 addressAlias; // The number of NFT tokens locked. uint32 lockedLength; // The number of NFT tokens owned. uint32 ownedLength; // The token balance in wei. uint96 balance; // snapshot of `accFeePerNFT` when the account fee accrued uint96 feePerNFTSnap; } /// @dev Struct represents the offer to sell an NFT. struct NFTOffer { uint32 seller; uint32 sellTo; uint96 minTokens; address offerToken; } /// @dev Struct represents the bid to buy an NFT. struct NFTBid { uint96 tokens; address bidToken; } /// @dev A uint32 map in storage. struct Uint32Map { mapping(uint256 => uint256) map; } /// @dev A struct to wrap a uint256 in storage. struct Uint256Ref { uint256 value; } /// @dev Struct containing the base token contract storage. struct BT404Storage { // Current number of address aliases assigned. uint32 numAliases; // Next NFT ID to assign for a mint. uint32 nextTokenId; // Total number of NFT IDs in the burned pool. uint32 burnedPoolSize; // Total supply of minted NFTs. uint32 totalNFTSupply; // Total supply of tokens. uint96 totalSupply; // Address of the NFT mirror contract. address mirrorERC721; // Mapping of a user alias number to their address. mapping(uint32 => address) aliasToAddress; // Mapping of user operator approvals for NFTs. mapping(address => mapping(address => Uint256Ref)) operatorApprovals; // Mapping of NFT approvals to approved operators. mapping(uint256 => address) nftApprovals; // Bitmap of whether an non-zero NFT approval may exist. LibBitmap.Bitmap mayHaveNFTApproval; // Mapping of user allowances for ERC20 spenders. mapping(address => mapping(address => Uint256Ref)) allowance; // Mapping of NFT IDs owned by an address. mapping(address => Uint32Map) owned; // Mapping of NFT token IDs locked by an address. mapping(address => Uint32Map) locked; // The pool of burned NFT IDs. Uint32Map burnedPool; // Even indices: owner aliases. Odd indices: owned indices. // if NFT token was locked, owned indices are ref to `locked`, otherwise `owned` Uint32Map oo; // Mapping of user account AddressData. mapping(address => AddressData) addressData; // Mapping of NFT token to locked flag LibBitmap.Bitmap tokenLocks; // The number of NFT tokens locked globally. uint32 numLockedNFT; // The number of NFT tokens approved to `this` globally. uint32 numExchangableNFT; // Fee rate to charged per NFT when exchange unlocking NFTs uint16 exchangeNFTFeeBips; // accumulated fee per unlocked NFT should receive uint96 accFeePerNFT; // Slot gap. uint80 __gap; // Fee rate to charged per NFT when trading through market(bid/ask). uint16 listMarketFeeBips; // Mapping of NFT to sale offers. mapping(uint256 => NFTOffer) offers; // Mapping of NFT to buy bids. // NFTId => bidder => Bid mapping(uint256 => mapping(address => NFTBid)) bids; // Mapping of token address to accounted fees. mapping(address => Uint256Ref) accountedFees; } /// @dev Returns a storage pointer for BT404Storage. function _getBT404Storage() internal pure virtual returns (BT404Storage storage $) { /// @solidity memory-safe-assembly assembly { // `uint72(bytes9(keccak256("DN404_STORAGE")))`. $.slot := 0xa20d6e21d0e5255308 // Truncate to 9 bytes to reduce bytecode size. } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* INITIALIZER */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Initializes the BT404 contract with an /// `initialTokenSupply`, `initialTokenOwner` and `mirror` NFT contract address. function _initializeBT404( uint256 initialTokenSupply, address initialSupplyOwner, address mirror ) internal virtual { BT404Storage storage $ = _getBT404Storage(); if ($.mirrorERC721 != address(0)) revert DNAlreadyInitialized(); if (mirror == address(0)) revert MirrorAddressIsZero(); /// @solidity memory-safe-assembly assembly { // Make the call to link the mirror contract. mstore(0x00, 0x0f4599e5) // `linkMirrorContract(address)`. mstore(0x20, caller()) if iszero(and(eq(mload(0x00), 1), call(gas(), mirror, 0, 0x1c, 0x24, 0x00, 0x20))) { mstore(0x00, 0xd125259c) // `LinkMirrorContractFailed()`. revert(0x1c, 0x04) } } $.mirrorERC721 = mirror; if (_unit() < 10 ** decimals() || _unit() > 10 ** 24) revert InvalidUnit(); if (initialTokenSupply != 0) { if (initialSupplyOwner == address(0)) { revert TransferToZeroAddress(); } if (_totalSupplyOverflows(initialTokenSupply)) { revert TotalSupplyOverflow(); } $.totalSupply = uint96(initialTokenSupply); AddressData storage initialOwnerAddressData = _addressData(initialSupplyOwner); initialOwnerAddressData.balance = uint96(initialTokenSupply); /// @solidity memory-safe-assembly assembly { // Emit the {Transfer} event. mstore(0x00, initialTokenSupply) log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, initialSupplyOwner))) } _setSkipNFT(initialSupplyOwner, true); } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* BASE UNIT FUNCTION TO OVERRIDE */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Amount of token balance that is equal to one NFT. function _unit() internal view virtual returns (uint256) { return 10 ** 18; } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* METADATA FUNCTIONS TO OVERRIDE */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns the name of the token. function name() public view virtual returns (string memory); /// @dev Returns the symbol of the token. function symbol() public view virtual returns (string memory); /// @dev Returns the Uniform Resource Identifier (URI) for token `id`. function tokenURI(uint256 id) public view virtual returns (string memory); /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* ERC20 OPERATIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns the decimals places of the token. Always 18. function decimals() public pure returns (uint8) { return 18; } /// @dev Returns the amount of tokens in existence. function totalSupply() public view virtual returns (uint256) { return uint256(_getBT404Storage().totalSupply); } /// @dev Returns the amount of tokens owned by `owner`. function balanceOf(address owner) public view virtual returns (uint256) { return _getBT404Storage().addressData[owner].balance; } /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. function allowance(address owner, address spender) public view returns (uint256) { return _getBT404Storage().allowance[owner][spender].value; } /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. /// /// Emits a {Approval} event. function approve(address spender, uint256 amount) public virtual returns (bool) { _approve(msg.sender, spender, amount); return true; } /// @dev Transfer `amount` tokens from the caller to `to`. /// /// Will burn sender NFTs if balance after transfer is less than /// the amount required to support the current NFT balance. /// /// Will mint NFTs to `to` if the recipient's new balance supports /// additional NFTs ***AND*** the `to` address's skipNFT flag is /// set to false. /// /// Requirements: /// - `from` must at least have `amount`. /// /// Emits a {Transfer} event. function transfer(address to, uint256 amount) public virtual returns (bool) { BT404Storage storage $ = _getBT404Storage(); _pullFeeForTwo($, msg.sender, to); _transfer(msg.sender, to, amount); return true; } /// @dev Transfers `amount` tokens from `from` to `to`. /// /// Note: Does not update the allowance if it is the maximum uint256 value. /// /// Will burn sender NFTs if balance after transfer is less than /// the amount required to support the current NFT balance. /// /// Will mint NFTs to `to` if the recipient's new balance supports /// additional NFTs ***AND*** the `to` address's skipNFT flag is /// set to false. /// /// Requirements: /// - `from` must at least have `amount`. /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. /// /// Emits a {Transfer} event. function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { BT404Storage storage $ = _getBT404Storage(); Uint256Ref storage a = $.allowance[from][msg.sender]; uint256 allowed = a.value; if (allowed != type(uint256).max) { if (amount > allowed) revert InsufficientAllowance(); unchecked { a.value = allowed - amount; } } _pullFeeForTwo($, from, to); _transfer(from, to, amount); return true; } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* INTERNAL TRANSFER FUNCTIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Moves `amount` of tokens from `from` to `to`. /// /// Will burn sender NFTs if balance after transfer is less than /// the amount required to support the current NFT balance. /// /// Will mint NFTs to `to` if the recipient's new balance supports /// additional NFTs ***AND*** the `to` address's skipNFT flag is /// set to false. /// /// Emits a {Transfer} event. function _transfer(address from, address to, uint256 amount) internal virtual { if (to == address(0)) revert TransferToZeroAddress(); BT404Storage storage $ = _getBT404Storage(); AddressData storage fromAddressData = _addressData(from); AddressData storage toAddressData = _addressData(to); _TransferTemps memory t; t.fromOwnedLength = fromAddressData.ownedLength; t.toOwnedLength = toAddressData.ownedLength; t.totalSupply = $.totalSupply; if (amount > (t.fromBalance = fromAddressData.balance)) { revert InsufficientBalance(); } unchecked { t.fromBalance -= amount; t.fromLockedLength = fromAddressData.lockedLength; // need enough token to maintain locked NFTs if (t.fromBalance < t.fromLockedLength * _unit()) { revert InsufficientBalanceToMaintainLockedTokens(); } fromAddressData.balance = uint96(t.fromBalance); toAddressData.balance = uint96(t.toBalance = toAddressData.balance + amount); t.numNFTBurns = _zeroFloorSub(t.fromOwnedLength + t.fromLockedLength, t.fromBalance / _unit()); if (toAddressData.flags & _ADDRESS_DATA_SKIP_NFT_FLAG == 0) { if (from == to) t.toOwnedLength = t.fromOwnedLength - t.numNFTBurns; t.numNFTMints = _zeroFloorSub( t.toBalance / _unit(), t.toOwnedLength + toAddressData.lockedLength // balance needed for locked and owned ); } { // cache `address(this)` approvals mapping(address => Uint256Ref) storage thisOperatorApprovals = $.operatorApprovals[address(this)]; // `from` burns NFTs if (thisOperatorApprovals[from].value != 0) { $.numExchangableNFT -= uint32(t.numNFTBurns); } // `to`mints NFTs if (thisOperatorApprovals[to].value != 0) { $.numExchangableNFT += uint32(t.numNFTMints); } } $.totalNFTSupply = uint32(uint256($.totalNFTSupply) + t.numNFTMints - t.numNFTBurns); Uint32Map storage oo = $.oo; { uint256 n = _min(t.numNFTBurns, t.numNFTMints); if (n != 0) { t.numNFTBurns -= n; t.numNFTMints -= n; if (from == to) { t.toOwnedLength += n; } else { _DNDirectLogs memory directLogs = _directLogsMalloc(n, from, to); Uint32Map storage fromOwned = $.owned[from]; Uint32Map storage toOwned = $.owned[to]; uint32 toAlias = _registerAndResolveAlias(toAddressData, to); // Direct transfer loop. do { uint256 id = _get(fromOwned, --t.fromOwnedLength); _set(toOwned, t.toOwnedLength, uint32(id)); _setOwnerAliasAndOwnedIndex(oo, id, toAlias, uint32(t.toOwnedLength++)); _removeNFTApproval($, id); _directLogsAppend(directLogs, id); } while (--n != 0); _directLogsSend(directLogs, $.mirrorERC721); fromAddressData.ownedLength = uint32(t.fromOwnedLength); toAddressData.ownedLength = uint32(t.toOwnedLength); } } } _PackedLogs memory packedLogs = _packedLogsMalloc(t.numNFTBurns + t.numNFTMints); uint256 burnedPoolSize = $.burnedPoolSize; if (t.numNFTBurns != 0) { _packedLogsSet(packedLogs, from, 1); Uint32Map storage fromOwned = $.owned[from]; uint256 fromIndex = t.fromOwnedLength; uint256 fromEnd = fromIndex - t.numNFTBurns; fromAddressData.ownedLength = uint32(fromEnd); // Burn loop. do { uint256 id = _get(fromOwned, --fromIndex); _setOwnerAliasAndOwnedIndex( oo, id, _ADDRESS_ALIAS_BURNED_POOL, uint32(burnedPoolSize) ); _set($.burnedPool, burnedPoolSize++, uint32(id)); _removeNFTApproval($, id); _packedLogsAppend(packedLogs, id); } while (fromIndex != fromEnd); } if (t.numNFTMints != 0) { _packedLogsSet(packedLogs, to, 0); t.maxNFTId = t.totalSupply / _unit(); uint256 nextTokenId = $.nextTokenId; Uint32Map storage toOwned = $.owned[to]; uint256 toIndex = t.toOwnedLength; uint256 toEnd = toIndex + t.numNFTMints; t.toAlias = _registerAndResolveAlias(toAddressData, to); toAddressData.ownedLength = uint32(toEnd); // Mint loop. do { uint256 id; if (nextTokenId < t.maxNFTId) { id = nextTokenId++; } else { id = _get($.burnedPool, --burnedPoolSize); } _set(toOwned, toIndex, uint32(id)); _setOwnerAliasAndOwnedIndex(oo, id, t.toAlias, uint32(toIndex++)); _packedLogsAppend(packedLogs, id); } while (toIndex != toEnd); $.nextTokenId = uint32(nextTokenId); } if (packedLogs.logs.length != 0) { $.burnedPoolSize = uint32(burnedPoolSize); _packedLogsSend(packedLogs, $.mirrorERC721); } /// @solidity memory-safe-assembly assembly { // Emit the {Transfer} event. mstore(0x00, amount) // forgefmt: disable-next-item log3( 0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), shr(96, shl(96, to)) ) } } } /// @dev Transfers token `id` from `from` to `to`. /// /// Requirements: /// /// - Call must originate from the mirror contract. /// - Token `id` must exist. /// - `from` must be the owner of the token. /// - `to` cannot be the zero address. /// `msgSender` must be the owner of the token, or be approved to manage the token. /// /// Emits a {Transfer} event. function _transferFromNFT(address from, address to, uint256 id, address msgSender) internal virtual { if (to == address(0)) revert TransferToZeroAddress(); BT404Storage storage $ = _getBT404Storage(); Uint32Map storage oo = $.oo; if (from != $.aliasToAddress[_get(oo, _ownershipIndex(id))]) { revert TransferFromIncorrectOwner(); } if (msgSender != from) { if ($.operatorApprovals[msgSender][from].value == 0) { if (msgSender != $.nftApprovals[id]) { revert TransferCallerNotOwnerNorApproved(); } } } AddressData storage fromAddressData = _addressData(from); AddressData storage toAddressData = _addressData(to); uint256 unit = _unit(); fromAddressData.balance -= uint96(unit); unchecked { toAddressData.balance += uint96(unit); _removeNFTApproval($, id); _clearNFTOffer($, id); uint32 toTransferIdx = _get(oo, _ownedIndex(id)); if (LibBitmap.get($.tokenLocks, id)) { // operate `locked` map // delete transferred NFT _delNFTAt($.locked[from], oo, toTransferIdx, --fromAddressData.lockedLength); } else { if ($.operatorApprovals[address(this)][from].value != 0) { // The unlocked NFTs amount of account `from` will decrease, collecting fees first _pullFeeForTwo($, from, from); // `from` lock 1 NFT --$.numExchangableNFT; } // operate `owned` map // delete transferred NFT _delNFTAt($.owned[from], oo, toTransferIdx, --fromAddressData.ownedLength); // lock LibBitmap.setTo($.tokenLocks, id, true); ++$.numLockedNFT; } // transfer ownership // lock the NFT by default for ERC721 transfer uint256 n = toAddressData.lockedLength++; _set($.locked[to], n, uint32(id)); _setOwnerAliasAndOwnedIndex( oo, id, _registerAndResolveAlias(toAddressData, to), uint32(n) ); } /// @solidity memory-safe-assembly assembly { // Emit the {Transfer} event. mstore(0x00, unit) // forgefmt: disable-next-item log3( 0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), shr(96, shl(96, to)) ) } } function _exchangeNFT(uint256 idX, uint256 idY, address msgSender) internal virtual returns (address x, address y, uint256 exchangeFee) { if (idX == idY) revert ExchangeSameToken(); BT404Storage storage $ = _getBT404Storage(); if ( _toUint(LibBitmap.get($.tokenLocks, idX)) | _toUint(LibBitmap.get($.tokenLocks, idY)) != 0 ) { revert ExchangeTokenLocked(); } x = _ownerOf(idX); y = _ownerAt(idY); // Only owner or spender can operate the token `idX` if (msgSender != x) { if ($.operatorApprovals[msgSender][x].value == 0) { if (msgSender != $.nftApprovals[idX]) { revert TransferCallerNotOwnerNorApproved(); } } } Uint32Map storage oo = $.oo; bool exchangeBurned = _get(oo, _ownershipIndex(idY)) == _ADDRESS_ALIAS_BURNED_POOL; mapping(address => Uint256Ref) storage thisOperatorApprovals = $.operatorApprovals[address(this)]; /// Only Burned or Approved NFT can be exchanged. if (!exchangeBurned && thisOperatorApprovals[y].value == 0) { revert ApprovalCallerNotOwnerNorApproved(); } _removeNFTApproval($, idX); if (!exchangeBurned) _removeNFTApproval($, idY); // collecting fees for account `x` and `y` first _pullFeeForTwo($, x, exchangeBurned ? x : y); // Will be used to snapshot owned index of `idY` uint256 yIndex; // idY to account x, then lock // must transfer `idY` firstly, otherwise the ownedIndex of `idX` is wrong unchecked { uint256 xIndex = _get(oo, _ownedIndex(idX)); AddressData storage xAddressData = _addressData(x); // remove NFT `idX` from account `x` _delNFTAt($.owned[x], oo, xIndex, --xAddressData.ownedLength); yIndex = _get(oo, _ownedIndex(idY)); // append `idY` to `locked` uint256 n = xAddressData.lockedLength++; _set($.locked[x], n, uint32(idY)); _setOwnerAliasAndOwnedIndex(oo, idY, xAddressData.addressAlias, uint32(n)); // lock `idY` LibBitmap.setTo($.tokenLocks, idY, true); ++$.numLockedNFT; } // idX to account y { uint32 yAlias = exchangeBurned ? _ADDRESS_ALIAS_BURNED_POOL : _addressData(y).addressAlias; _setOwnerAliasAndOwnedIndex(oo, idX, yAlias, uint32(yIndex)); } { Uint32Map storage ownedMap = exchangeBurned ? $.burnedPool : $.owned[y]; _set(ownedMap, yIndex, uint32(idX)); } // transfer nft first, then token, otherwise specified NFT transfer may not success // fee charges in percentage of the unit exchangeFee = $.exchangeNFTFeeBips; if (exchangeFee > 0) { // Only refresh when the balance of `msgSender` will be changed. if (msgSender != x) _pullFeeForTwo($, msgSender, msgSender); unchecked { exchangeFee *= _unit() / 10000; _transfer(msgSender, address(this), exchangeFee); uint256 num = $.numExchangableNFT; // In case no one is seeding, users can also exchange the burned NFTs. // These fees will not be tracked because: // - The fees will be distributed to the seeding users, unless no one is interested in the profit. if (num > 0) $.accFeePerNFT += uint96(exchangeFee / $.numExchangableNFT); } } // If `msgSender` exchanged on behalf of `x`, `msgSender` receive the NFT. if (msgSender != x) _transferFromNFT(x, msgSender, idY, x); // x lock 1 NFT if (!exchangeBurned && thisOperatorApprovals[x].value != 0) { unchecked { --$.numExchangableNFT; } } } function _pullFeeForTwo(BT404Storage storage $, address account1, address account2) internal virtual { // Cannot receive fee if `address(this)` has no operator approvals mapping(address => Uint256Ref) storage thisOperatorApprovals = $.operatorApprovals[address(this)]; uint256 accFeePerNFT; uint256 accruedFee1; if (thisOperatorApprovals[account1].value > 0) { accFeePerNFT = $.accFeePerNFT; AddressData storage addressData = $.addressData[account1]; // only unlocked NFTs receive fee accruedFee1 = accFeePerNFT - addressData.feePerNFTSnap; if (accruedFee1 > 0) addressData.feePerNFTSnap = uint96(accFeePerNFT); accruedFee1 *= addressData.ownedLength; } if (account2 != account1) { if (thisOperatorApprovals[account2].value > 0) { if (accFeePerNFT == 0) { accFeePerNFT = $.accFeePerNFT; } AddressData storage addressData = $.addressData[account2]; // only unlocked NFTs receive fee uint256 accrued = (accFeePerNFT - addressData.feePerNFTSnap); if (accrued > 0) addressData.feePerNFTSnap = uint96(accFeePerNFT); accrued *= (addressData.ownedLength); if (accrued > 0) { _transfer(address(this), account2, accrued); } } } if (accruedFee1 > 0) { _transfer(address(this), account1, accruedFee1); } } /// @dev Internal function for minting new NFTs. function _mintNFT(address, uint256[] memory, bool) internal virtual { // implementation should be provided by inheriting contracts. } /// @dev Internal function for burning existing NFTs. function _burnNFT(address, uint256[] memory) internal virtual { // implementation should be provided by inheriting contracts. } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* INTERNAL APPROVE FUNCTIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. /// /// Emits a {Approval} event. function _approve(address owner, address spender, uint256 amount) internal virtual { Uint256Ref storage ref = _getBT404Storage().allowance[owner][spender]; if (amount > 0 && ref.value > 0) revert(); ref.value = amount; /// @solidity memory-safe-assembly assembly { // Emit the {Approval} event. mstore(0x00, amount) // forgefmt: disable-next-item log3( 0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, shl(96, owner)), shr(96, shl(96, spender)) ) } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* DATA HITCHHIKING FUNCTIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns the auxiliary data for `owner`. /// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data. /// Auxiliary data can be set for any address, even if it does not have any tokens. function _getAux(address owner) internal view virtual returns (uint56) { return _getBT404Storage().addressData[owner].aux; } /// @dev Set the auxiliary data for `owner` to `value`. /// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data. /// Auxiliary data can be set for any address, even if it does not have any tokens. function _setAux(address owner, uint56 value) internal virtual { _getBT404Storage().addressData[owner].aux = value; } function _setExchangeNFTFeeRate(uint256 feeBips) internal virtual { if (feeBips > 10000) revert(); _getBT404Storage().exchangeNFTFeeBips = uint16(feeBips); /// @solidity memory-safe-assembly assembly { mstore(0x00, feeBips) log1(0x00, 0x20, _EXCHANGE_MARKET_FEE_SET_EVENT_SIGNATURE) } } function _setListMarketFeeRate(uint256 feeBips) internal virtual { if (feeBips > 10000) revert(); _getBT404Storage().listMarketFeeBips = uint16(feeBips); /// @solidity memory-safe-assembly assembly { mstore(0x00, feeBips) log1(0x00, 0x20, _LIST_MARKET_FEE_SET_EVENT_SIGNATURE) } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* SKIP NFT FUNCTIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns true if minting and transferring ERC20s to `owner` will skip minting NFTs. /// Returns false otherwise. function getSkipNFT(address owner) public view virtual returns (bool) { AddressData storage d = _getBT404Storage().addressData[owner]; if (d.flags & _ADDRESS_DATA_INITIALIZED_FLAG == 0) { return true; } return d.flags & _ADDRESS_DATA_SKIP_NFT_FLAG != 0; } /// @dev Sets the caller's skipNFT flag to `skipNFT`. Returns true. /// /// Emits a {SkipNFTSet} event. function setSkipNFT(bool skipNFT) public virtual returns (bool) { _setSkipNFT(msg.sender, skipNFT); return true; } /// @dev Internal function to set account `owner` skipNFT flag to `state` /// /// Initializes account `owner` AddressData if it is not currently initialized. /// /// Emits a {SkipNFTSet} event. function _setSkipNFT(address owner, bool state) internal virtual { AddressData storage d = _addressData(owner); if ((d.flags & _ADDRESS_DATA_SKIP_NFT_FLAG != 0) != state) { d.flags ^= _ADDRESS_DATA_SKIP_NFT_FLAG; } /// @solidity memory-safe-assembly assembly { mstore(0x00, iszero(iszero(state))) log2(0x00, 0x20, _SKIP_NFT_SET_EVENT_SIGNATURE, shr(96, shl(96, owner))) } } /// @dev Returns a storage data pointer for account `owner` AddressData /// /// Initializes account `owner` AddressData if it is not currently initialized. function _addressData(address owner) internal virtual returns (AddressData storage d) { d = _getBT404Storage().addressData[owner]; unchecked { if (d.flags & _ADDRESS_DATA_INITIALIZED_FLAG == 0) { d.flags = uint8(_ADDRESS_DATA_SKIP_NFT_FLAG | _ADDRESS_DATA_INITIALIZED_FLAG); } } } /// @dev Returns the `addressAlias` of account `to`. /// /// Assigns and registers the next alias if `to` alias was not previously registered. function _registerAndResolveAlias(AddressData storage toAddressData, address to) internal virtual returns (uint32 addressAlias) { addressAlias = toAddressData.addressAlias; if (addressAlias == 0) { BT404Storage storage $ = _getBT404Storage(); unchecked { addressAlias = ++$.numAliases; } toAddressData.addressAlias = addressAlias; $.aliasToAddress[addressAlias] = to; if (addressAlias == _ADDRESS_ALIAS_BURNED_POOL) revert(); // Overflow. } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* MIRROR OPERATIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns the address of the mirror NFT contract. function mirrorERC721() public view virtual returns (address) { return _getBT404Storage().mirrorERC721; } /// @dev Returns the total NFT supply. function _totalNFTSupply() internal view virtual returns (uint256) { return _getBT404Storage().totalNFTSupply; } /// @dev Returns `owner` NFT balance. function _balanceOfNFT(address owner) internal view virtual returns (uint256) { AddressData storage addressData = _getBT404Storage().addressData[owner]; unchecked { return addressData.ownedLength + addressData.lockedLength; } } /// @dev Returns the owner of token `id`. /// Returns the zero address instead of reverting if the token does not exist. function _ownerAt(uint256 id) internal view virtual returns (address) { BT404Storage storage $ = _getBT404Storage(); return $.aliasToAddress[_get($.oo, _ownershipIndex(id))]; } /// @dev Returns the owner of token `id`. /// /// Requirements: /// - Token `id` must exist. function _ownerOf(uint256 id) internal view virtual returns (address) { address owner = _ownerAt(id); if (owner == address(0)) revert TokenDoesNotExist(); return owner; } /// @dev Returns if token `id` exists. function _exists(uint256 id) internal view virtual returns (bool) { return _ownerAt(id) != address(0); } /// @dev Returns the account approved to manage token `id`. /// /// Requirements: /// - Token `id` must exist. function _getApproved(uint256 id) internal view virtual returns (address) { if (!_exists(id)) revert TokenDoesNotExist(); return _getBT404Storage().nftApprovals[id]; } /// @dev Sets `spender` as the approved account to manage token `id`, using `msgSender`. /// /// Requirements: /// - `msgSender` must be the owner or an approved operator for the token owner. function _approveNFT(address spender, uint256 id, address msgSender) internal virtual returns (address owner) { BT404Storage storage $ = _getBT404Storage(); owner = $.aliasToAddress[_get($.oo, _ownershipIndex(id))]; if (msgSender != owner) { if ($.operatorApprovals[msgSender][owner].value == 0) { revert ApprovalCallerNotOwnerNorApproved(); } } $.nftApprovals[id] = spender; LibBitmap.setTo($.mayHaveNFTApproval, id, spender != address(0)); } function _removeNFTApproval(BT404Storage storage $, uint256 id) internal virtual { if (LibBitmap.get($.mayHaveNFTApproval, id)) { LibBitmap.setTo($.mayHaveNFTApproval, id, false); delete $.nftApprovals[id]; } } /// @dev Approve or remove the `operator` as an operator for `msgSender`, /// without authorization checks. function _setApprovalForAll(address operator, bool approved, address msgSender) internal virtual { BT404Storage storage $ = _getBT404Storage(); Uint256Ref storage ref = $.operatorApprovals[operator][msgSender]; if (operator == address(this)) { bool status = ref.value != 0; AddressData storage senderAddressData = $.addressData[msgSender]; if (_toUint(approved) & _toUint(!status) != 0) { // initialize when approving senderAddressData.feePerNFTSnap = $.accFeePerNFT; unchecked { $.numExchangableNFT += senderAddressData.ownedLength; } } else if (_toUint(!approved) & _toUint(status) != 0) { // refresh when removing approval _pullFeeForTwo($, msgSender, msgSender); unchecked { $.numExchangableNFT -= senderAddressData.ownedLength; } } } ref.value = _toUint(approved); // `approved ? 1 : 0` } /// @dev Lock or unlock the `id`, /// `msgSener` should be authorized as the operator of the owner of the NFT function _setNFTLockState(uint256[] memory ids, bool lock, address msgSender) internal virtual { BT404Storage storage $ = _getBT404Storage(); _pullFeeForTwo($, msgSender, msgSender); Uint32Map storage oo = $.oo; LibBitmap.Bitmap storage tokenLocks = $.tokenLocks; AddressData storage ownerAddressData = _addressData(msgSender); Uint32Map storage ownerLocked = $.locked[msgSender]; Uint32Map storage ownerOwned = $.owned[msgSender]; uint32 ownerAlias = _registerAndResolveAlias(ownerAddressData, msgSender); uint256 idLen = ids.length; unchecked { for (uint256 i; i < idLen; ++i) { uint256 id = ids[i]; if (_get(oo, _ownershipIndex(id)) != ownerAlias) { revert ApprovalCallerNotOwnerNorApproved(); } uint32 ownedIndex = _get(oo, _ownedIndex(id)); if (LibBitmap.get(tokenLocks, id) == lock) revert TokenLockStatusNoChange(); if (!lock) { // already locked, to unlock LibBitmap.setTo(tokenLocks, id, false); // swap with last NFT and pop the last _delNFTAt(ownerLocked, oo, ownedIndex, --ownerAddressData.lockedLength); _clearNFTOffer($, id); uint256 n = ownerAddressData.ownedLength++; _set(ownerOwned, n, uint32(id)); _set(oo, _ownedIndex(id), uint32(n)); } else { // not locked, to lock LibBitmap.setTo(tokenLocks, id, true); // swap with last NFT and pop the last _delNFTAt(ownerOwned, oo, ownedIndex, --ownerAddressData.ownedLength); uint256 n = ownerAddressData.lockedLength++; _set(ownerLocked, n, uint32(id)); _set(oo, _ownedIndex(id), uint32(n)); } } } unchecked { if (lock) $.numLockedNFT += uint32(idLen); else $.numLockedNFT -= uint32(idLen); if ($.operatorApprovals[address(this)][msgSender].value != 0) { if (lock) $.numExchangableNFT -= uint32(ids.length); else $.numExchangableNFT += uint32(ids.length); } } } /// @dev Returns the NFT IDs of `owner` in range `[begin, end)`. /// Optimized for smaller bytecode size, as this function is intended for off-chain calling. function _ownedIds(address owner, uint256 begin, uint256 end, bool locked) internal view virtual returns (uint256[] memory ids) { BT404Storage storage $ = _getBT404Storage(); (Uint32Map storage owned, uint256 n) = locked ? ($.locked[owner], $.addressData[owner].lockedLength) : ($.owned[owner], $.addressData[owner].ownedLength); n = _min(n, end); /// @solidity memory-safe-assembly assembly { // Allocate one more word to store the offset when returning with assembly. ids := mload(0x40) mstore(0x20, owned.slot) let i := begin for {} lt(i, n) { i := add(i, 1) } { mstore(0x00, shr(3, i)) let s := keccak256(0x00, 0x40) // Storage slot. let id := and(0xffffffff, shr(shl(5, and(i, 7)), sload(s))) mstore(add(add(ids, 0x20), shl(5, sub(i, begin))), id) // Append to. } mstore(ids, sub(i, begin)) // Store the length. mstore(0x40, add(add(ids, 0x20), shl(5, sub(i, begin)))) // Allocate memory. } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* NFT OFFER BID FUNCTIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ struct NFTOrder { uint256 id; uint256 tokenUnits; address token; // 1. `saleTo` in `offerForSale`. // 2. `seller` in `acceptOffer`. // 3. None in `bidForBuy`. // 4. `bidder` in `acceptBid`. address trader; } function _offerForSale(address msgSender, NFTOrder[] memory orders) internal { BT404Storage storage $ = _getBT404Storage(); mapping(uint256 => NFTOffer) storage offers = $.offers; uint32 senderAlias = _registerAndResolveAlias(_addressData(msgSender), msgSender); for (uint256 i; i < orders.length;) { uint256 id; uint256 minTokenUnits; address token; address saleTo; { NFTOrder memory order = orders[i]; (id, minTokenUnits, token, saleTo) = (order.id, order.tokenUnits, order.token, order.trader); } uint32 ownerAlias = _get($.oo, _ownershipIndex(id)); // Only the owner can make an offer because // if the owner revokes approval after making an offer, the ownership will become incorrect. if (senderAlias != ownerAlias) revert ApprovalCallerNotOwnerNorApproved(); // Only locked NFTs can be offered to sale. if (!LibBitmap.get($.tokenLocks, id)) revert TokenNotLocked(); if (minTokenUnits == 0 || minTokenUnits > type(uint96).max) revert InvalidSalePrice(); offers[id] = NFTOffer({ seller: ownerAlias, sellTo: saleTo == address(0) ? 0 : _registerAndResolveAlias($.addressData[saleTo], saleTo), minTokens: uint96(minTokenUnits), offerToken: token }); unchecked { ++i; } } } function _acceptOffer(address msgSender, NFTOrder[] memory orders) internal { BT404Storage storage $ = _getBT404Storage(); mapping(uint256 => NFTOffer) storage offers = $.offers; uint32 senderAlias = _registerAndResolveAlias(_addressData(msgSender), msgSender); uint256 nativeOfferTokens; uint256 feeBips = $.listMarketFeeBips; for (uint256 i; i < orders.length;) { uint256 id; uint256 tokenUnits; address token; address seller; // Cache the variables. { NFTOrder memory order = orders[i]; (id, tokenUnits, token, seller) = (order.id, order.tokenUnits, order.token, order.trader); } // Check parameters. { NFTOffer memory offer = offers[id]; // check if nft owner and seller are matched. { uint32 sellerAlias = offer.seller; // 1. NFT isn't for sale. // 2. Seller isn't the current NFT owner. // 3. Seller isn't equal to the order trader. if ( sellerAlias == 0 || sellerAlias != _get($.oo, _ownershipIndex(id)) || sellerAlias != $.addressData[seller].addressAlias ) { revert InvalidSellerOrBuyer(); } } uint32 sellToAlias = offer.sellTo; // exclusive address was set but is not matched. if (sellToAlias != 0 && sellToAlias != senderAlias) { revert InvalidSellerOrBuyer(); } if (offer.minTokens > tokenUnits) revert InvalidSalePrice(); if (!LibBitmap.get($.tokenLocks, id)) revert TokenNotLocked(); if (token != offer.offerToken) revert InvalidOrderToken(); } { uint256 fee = tokenUnits * feeBips / 10000; // Sender receives the NFT.(The offer will be cleaned) _transferFromNFT(seller, msgSender, id, seller); // Seller receives the funds _transferToken(token, msgSender, seller, tokenUnits - fee); if (fee > 0) { $.accountedFees[token].value += fee; _transferToken(token, msgSender, address(this), fee); } if (token == address(0)) nativeOfferTokens += tokenUnits; } NFTBid memory bid = $.bids[id][msgSender]; if (bid.tokens > 0) { delete $.bids[id][msgSender]; _transferToken(bid.bidToken, address(this), msgSender, bid.tokens); } unchecked { ++i; } } if (nativeOfferTokens != msg.value) revert InvalidSalePrice(); } function _cancelOffer(address msgSender, uint256[] memory ids) internal { BT404Storage storage $ = _getBT404Storage(); mapping(uint256 => NFTOffer) storage offers = $.offers; uint32 senderAlias = _registerAndResolveAlias(_addressData(msgSender), msgSender); for (uint256 i; i < ids.length;) { uint256 id = ids[i]; if (senderAlias != _get($.oo, _ownershipIndex(id))) { revert InvalidSellerOrBuyer(); } delete offers[id]; unchecked { ++i; } } } function _clearNFTOffer(BT404Storage storage $, uint256 id) internal { // Clear exist offer if needed. if ($.offers[id].seller != 0) delete $.offers[id]; } function _bidForBuy(address msgSender, NFTOrder[] memory orders) internal { BT404Storage storage $ = _getBT404Storage(); mapping(uint256 => mapping(address => NFTBid)) storage bids = $.bids; uint32 senderAlias = _registerAndResolveAlias($.addressData[msgSender], msgSender); uint256 nativeBidTokens; for (uint256 i; i < orders.length;) { uint256 id; uint256 tokenUnits; address token; { NFTOrder memory order = orders[i]; (id, tokenUnits, token) = (order.id, order.tokenUnits, order.token); } { // Owner can't bid. if (senderAlias == _get($.oo, _ownershipIndex(id))) revert InvalidSellerOrBuyer(); if (tokenUnits == 0 || tokenUnits > type(uint96).max) revert InvalidSalePrice(); } { NFTBid memory bid = bids[id][msgSender]; // Bidder can change his bid. if (tokenUnits == bid.tokens && bid.bidToken == token) revert InvalidSalePrice(); // Update bid firstly. bids[id][msgSender] = NFTBid({tokens: uint96(tokenUnits), bidToken: token}); // Refund exist bid.(Prevent Reentrancy externally) _transferToken(bid.bidToken, address(this), msgSender, bid.tokens); // Receive new bid funds. _transferToken(token, msgSender, address(this), tokenUnits); if (token == address(0)) nativeBidTokens += tokenUnits; } unchecked { ++i; } } if (nativeBidTokens != msg.value) revert InvalidSalePrice(); } function _acceptBid(address msgSender, NFTOrder[] memory orders) internal { BT404Storage storage $ = _getBT404Storage(); mapping(uint256 => mapping(address => NFTBid)) storage bids = $.bids; uint32 senderAlias = _registerAndResolveAlias(_addressData(msgSender), msgSender); uint256 feeBips = $.listMarketFeeBips; for (uint256 i; i < orders.length;) { uint256 id; uint256 tokenUnits; address token; address bidder; { NFTOrder memory order = orders[i]; (id, tokenUnits, token, bidder) = (order.id, order.tokenUnits, order.token, order.trader); } { // Only owner can sell. if (senderAlias != _get($.oo, _ownershipIndex(id))) revert InvalidSellerOrBuyer(); NFTBid memory bid = bids[id][bidder]; if (tokenUnits == 0 || bid.tokens < tokenUnits) revert InvalidSalePrice(); if (token != bid.bidToken) revert InvalidOrderToken(); delete bids[id][bidder]; // Take full bid. tokenUnits = bid.tokens; } // The exist offer will be cleaned inner. _transferFromNFT(msgSender, bidder, id, msgSender); uint256 fee = tokenUnits * feeBips / 10000; _transferToken(token, address(this), msgSender, tokenUnits - fee); if (fee > 0) $.accountedFees[token].value += fee; unchecked { ++i; } } } function _cancelBid(address msgSender, uint256[] memory ids) internal { BT404Storage storage $ = _getBT404Storage(); mapping(uint256 => mapping(address => NFTBid)) storage bids = $.bids; for (uint256 i; i < ids.length;) { uint256 id = ids[i]; NFTBid memory bid = bids[id][msgSender]; if (bid.tokens == 0) revert InvalidSellerOrBuyer(); delete bids[id][msgSender]; _transferToken(bid.bidToken, address(this), msgSender, bid.tokens); unchecked { ++i; } } } function _transferToken(address token, address from, address to, uint256 amount) private { if (token == address(0)) { if (to != address(this)) { SafeTransferLib.safeTransferETH(to, amount); } } else if (token == address(this)) { _pullFeeForTwo( _getBT404Storage(), from == address(this) ? to : from, to == address(this) ? from : to ); _transfer(from, to, amount); } else { if (from == address(this)) { SafeTransferLib.safeTransfer(token, to, amount); } else { SafeTransferLib.safeTransferFrom(token, from, to, amount); } } } /// @dev Fallback modifier to dispatch calls from the mirror NFT contract /// to internal functions in this contract. modifier bt404Fallback() virtual { BT404Storage storage $ = _getBT404Storage(); uint256 fnSelector = _calldataload(0x00) >> 224; // `transferFromNFT(address,address,uint256,address)`. if (fnSelector == 0xe5eb36c8) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); _transferFromNFT( address(uint160(_calldataload(0x04))), // `from`. address(uint160(_calldataload(0x24))), // `to`. _calldataload(0x44), // `id`. address(uint160(_calldataload(0x64))) // `msgSender`. ); _return(1); } // `setApprovalForAll(address,bool,address)`. if (fnSelector == 0x813500fc) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); _setApprovalForAll( address(uint160(_calldataload(0x04))), // `spender`. _calldataload(0x24) != 0, // `status`. address(uint160(_calldataload(0x44))) // `msgSender`. ); _return(1); } // `exchangeNFT(uint256,uint256,address)`. if (fnSelector == 0x2c5966af) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); (address x, address y, uint256 fee) = _exchangeNFT( _calldataload(0x04), // `idX` _calldataload(0x24), // `idY` address(uint160(_calldataload(0x44))) // `msgSender` ); /// @solidity memory-safe-assembly assembly { mstore(0x00, x) mstore(0x20, y) mstore(0x40, fee) return(0x00, 0x60) } } // `setNFTLockState(uint256,uint256[])`. if (fnSelector == 0xb79cc1bd) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); uint256 senderAndLockFlag = _calldataload(0x04); _setNFTLockState( _calldatacopyArray(_calldataload(0x24) + 0x04), // `ids` uint8(senderAndLockFlag) != 0, // `lock` address(uint160(senderAndLockFlag >> 96)) // `msgSender` ); _return(1); } // `mintNFT(uint256,uint256[])` if (fnSelector == 0x3e0446a1) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); uint256 senderAndLockFlag = _calldataload(0x04); _mintNFT( address(uint160(senderAndLockFlag >> 96)), // `to` _calldatacopyArray(_calldataload(0x24) + 0x04), // `ids` uint8(senderAndLockFlag) != 0 // `lock` ); _return(1); } // `burnNFT(address,uint256[])` if (fnSelector == 0x86529a61) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); _burnNFT( address(uint160(_calldataload(0x04))), // `from` _calldatacopyArray(_calldataload(0x24) + 0x04) // `ids` ); _return(1); } // `offerForSale(address,(uint256,uint256,address,address)[])` if (fnSelector == 0x73e63d89) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); _offerForSale( address(uint160(_calldataload(0x04))), _calldatacopyOrders(_calldataload(0x24) + 0x04) ); _return(1); } // `acceptOffer(address,(uint256,uint256,address,address)[])` if (fnSelector == 0x53ffa071) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); _acceptOffer( address(uint160(_calldataload(0x04))), _calldatacopyOrders(_calldataload(0x24) + 0x04) ); _return(1); } // `cancelOffer(address,uint256[])` if (fnSelector == 0x2da2a859) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); _cancelOffer( address(uint160(_calldataload(0x04))), // `from` _calldatacopyArray(_calldataload(0x24) + 0x04) // `ids` ); _return(1); } // `bidForBuy(address,(uint256,uint256,address,address)[])` if (fnSelector == 0xb5a1305b) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); _bidForBuy( address(uint160(_calldataload(0x04))), _calldatacopyOrders(_calldataload(0x24) + 0x04) ); _return(1); } // `acceptBid(address,(uint256,uint256,address,address)[])` if (fnSelector == 0xb6ebe103) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); _acceptBid( address(uint160(_calldataload(0x04))), _calldatacopyOrders(_calldataload(0x24) + 0x04) ); _return(1); } // `cancelBid(address,uint256[])` if (fnSelector == 0xa38beee1) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); _cancelBid( address(uint160(_calldataload(0x04))), // `from` _calldatacopyArray(_calldataload(0x24) + 0x04) // `ids` ); _return(1); } // `isApprovedForAll(address,address)`. if (fnSelector == 0xe985e9c5) { address owner = address(uint160(_calldataload(0x04))); address spender = address(uint160(_calldataload(0x24))); Uint256Ref storage ref = $.operatorApprovals[spender][owner]; _return(ref.value); } // `ownerOf(uint256)`. if (fnSelector == 0x6352211e) { _return(uint160(_ownerOf(_calldataload(0x04)))); } // `ownerAt(uint256)`. if (fnSelector == 0x24359879) { _return(uint160(_ownerAt(_calldataload(0x04)))); } // `approveNFT(address,uint256,address)`. if (fnSelector == 0xd10b6e0c) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); address owner = _approveNFT( address(uint160(_calldataload(0x04))), // `spender`. _calldataload(0x24), // `id`. address(uint160(_calldataload(0x44))) // `msgSender`. ); _return(uint160(owner)); } // `ownedIds(uint256,uint256,uint256)`. if (fnSelector == 0xf9b4b328) { uint256 addrAndFlag = _calldataload(0x04); /// @solidity memory-safe-assembly assembly { // Allocate one word to store the offset of the array in returndata. mstore(0x40, add(mload(0x40), 0x20)) } uint256[] memory ids = _ownedIds( address(uint160(addrAndFlag >> 96)), _calldataload(0x24), _calldataload(0x44), uint8(addrAndFlag) != 0 ); /// @solidity memory-safe-assembly assembly { // Memory safe, as we've advanced the free memory pointer by a word. let p := sub(ids, 0x20) mstore(p, 0x20) // Store the offset of the array in returndata. return(p, add(0x40, shl(5, mload(ids)))) } } // `getApproved(uint256)`. if (fnSelector == 0x081812fc) { _return(uint160(_getApproved(_calldataload(0x04)))); } // `balanceOfNFT(address)`. if (fnSelector == 0xf5b100ea) { _return(_balanceOfNFT(address(uint160(_calldataload(0x04))))); } // `totalNFTSupply()`. if (fnSelector == 0xe2c79281) { _return(_totalNFTSupply()); } // `implementsBT404()`, `implementsDN404()`. if (fnSelector == 0xc89e2ab1 || fnSelector == 0xb7a94eb8) { _return(1); } _; } /// @dev Fallback function for calls from mirror NFT contract. fallback() external payable virtual bt404Fallback {} receive() external payable virtual {} /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* INTERNAL / PRIVATE HELPERS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns `i << 1`. function _ownershipIndex(uint256 i) internal pure returns (uint256) { unchecked { return i << 1; } } /// @dev Returns `(i << 1) + 1`. function _ownedIndex(uint256 i) internal pure returns (uint256) { unchecked { return (i << 1) + 1; } } function _delNFTAt( Uint32Map storage owned, Uint32Map storage oo, uint256 toDelIndex, uint256 lastIndex ) internal { if (toDelIndex != lastIndex) { uint256 updatedId = _get(owned, lastIndex); _set(owned, toDelIndex, uint32(updatedId)); _set(oo, _ownedIndex(updatedId), uint32(toDelIndex)); } } /// @dev Returns whether `amount` is a valid `totalSupply`. function _totalSupplyOverflows(uint256 amount) internal view returns (bool result) { uint256 unit = _unit(); /// @solidity memory-safe-assembly assembly { result := iszero(iszero(or(shr(96, amount), lt(0xfffffffe, div(amount, unit))))) } } /// @dev Struct containing direct transfer log data for {Transfer} events to be /// emitted by the mirror NFT contract. struct _DNDirectLogs { uint256 offset; address from; address to; uint256[] logs; } /// @dev Initiates memory allocation for direct logs with `n` log items. function _directLogsMalloc(uint256 n, address from, address to) private pure returns (_DNDirectLogs memory p) { /// @solidity memory-safe-assembly assembly { // Note that `p` implicitly allocates and advances the free memory pointer by // 4 words, which we can safely mutate in `_packedLogsSend`. let logs := mload(0x40) mstore(logs, n) // Store the length. let offset := add(0x20, logs) // Skip the word for `p.logs.length`. mstore(0x40, add(offset, shl(5, n))) // Allocate memory. mstore(add(0x60, p), logs) // Set `p.logs`. mstore(add(0x40, p), shr(96, shl(96, to))) // Set `p.to`. mstore(add(0x20, p), shr(96, shl(96, from))) // Set `p.from`. mstore(p, offset) // Set `p.offset`. } } /// @dev Adds a direct log item to `p` with token `id`. function _directLogsAppend(_DNDirectLogs memory p, uint256 id) private pure { /// @solidity memory-safe-assembly assembly { let offset := mload(p) mstore(offset, id) mstore(p, add(offset, 0x20)) } } /// @dev Calls the `mirror` NFT contract to emit {Transfer} events for packed logs `p`. function _directLogsSend(_DNDirectLogs memory p, address mirror) private { /// @solidity memory-safe-assembly assembly { let logs := mload(add(p, 0x60)) let n := add(0x84, shl(5, mload(logs))) // Length of calldata to send. let o := sub(logs, 0x80) // Start of calldata to send. mstore(o, 0x144027d3) // `logDirectTransfer(address,address,uint256[])`. mstore(add(o, 0x20), mload(add(0x20, p))) mstore(add(o, 0x40), mload(add(0x40, p))) mstore(add(o, 0x60), 0x60) // Offset of `logs` in the calldata to send. if iszero(and(eq(mload(o), 1), call(gas(), mirror, 0, add(o, 0x1c), n, o, 0x20))) { revert(o, 0x00) } } } /// emitted by the mirror NFT contract. struct _PackedLogs { uint256 offset; uint256 addressAndBit; uint256[] logs; } /// @dev Initiates memory allocation for packed logs with `n` log items. function _packedLogsMalloc(uint256 n) internal pure returns (_PackedLogs memory p) { /// @solidity memory-safe-assembly assembly { // Note that `p` implicitly allocates and advances the free memory pointer by // 2 words, which we can safely mutate in `_packedLogsSend`. let logs := mload(0x40) mstore(logs, n) // Store the length. let offset := add(0x20, logs) mstore(0x40, add(offset, shl(5, n))) // Allocate memory. mstore(add(0x40, p), logs) // Set `p.logs`. mstore(p, offset) // Set `p.offset`. } } /// @dev Set the current address and the burn bit. function _packedLogsSet(_PackedLogs memory p, address a, uint256 burnBit) internal pure { /// @solidity memory-safe-assembly assembly { mstore(add(p, 0x20), or(shl(96, a), burnBit)) } } /// @dev Adds a packed log item to `p` with token `id`. function _packedLogsAppend(_PackedLogs memory p, uint256 id) internal pure { /// @solidity memory-safe-assembly assembly { let offset := mload(p) mstore(offset, or(mload(add(p, 0x20)), shl(8, id))) mstore(p, add(offset, 0x20)) } } function _packedLogsSend(_PackedLogs memory p, address mirror) internal { /// @solidity memory-safe-assembly assembly { let logs := mload(add(p, 0x40)) let o := sub(logs, 0x40) // Start of calldata to send. mstore(o, 0x263c69d6) // `logTransfer(uint256[])`. mstore(add(o, 0x20), 0x20) // Offset of `logs` in the calldata to send. let n := add(0x44, shl(5, mload(logs))) // Length of calldata to send. if iszero(and(eq(mload(o), 1), call(gas(), mirror, 0, add(o, 0x1c), n, o, 0x20))) { revert(o, 0x00) } } } /// @dev Struct of temporary variables for transfers. struct _TransferTemps { uint256 numNFTBurns; uint256 numNFTMints; uint256 fromBalance; uint256 toBalance; uint256 fromOwnedLength; uint256 toOwnedLength; uint256 totalSupply; uint256 fromLockedLength; uint256 toLockedLength; uint256 maxNFTId; uint32 toAlias; } /// @dev Returns the calldata value at `offset`. function _calldataload(uint256 offset) private pure returns (uint256 value) { /// @solidity memory-safe-assembly assembly { value := calldataload(offset) } } function _calldatacopyArray(uint256 offset) private pure returns (uint256[] memory value) { /// @solidity memory-safe-assembly assembly { let length := calldataload(offset) value := mload(0x40) mstore(0x40, add(add(value, 0x20), shl(5, length))) // Allocate memory. mstore(value, length) // Store array length calldatacopy(add(value, 0x20), add(offset, 0x20), shl(5, length)) // Copy array elements } } function _calldatacopyOrders(uint256 offset) private pure returns (NFTOrder[] memory orders) { // For array of `NFTOrder`, the layoutes between `calldata` and `memory` are different. // In memory, it contains the elements offset. uint256 length; /// @solidity memory-safe-assembly assembly { length := calldataload(offset) offset := add(offset, 0x20) // Skip length. } orders = new NFTOrder[](length); for (uint256 i; i < length;) { NFTOrder memory tmp; // @solidity memory-safe-assembly assembly { calldatacopy(tmp, offset, 0x80) // Copy array element offset := add(offset, 0x80) mstore(add(tmp, 0x40), shr(96, shl(96, mload(add(tmp, 0x40))))) mstore(add(tmp, 0x60), shr(96, shl(96, mload(add(tmp, 0x60))))) } orders[i] = tmp; unchecked { ++i; } } } /// @dev Executes a return opcode to return `x` and end the current call frame. function _return(uint256 x) private pure { /// @solidity memory-safe-assembly assembly { mstore(0x00, x) return(0x00, 0x20) } } /// @dev Returns `max(0, x - y)`. function _zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mul(gt(x, y), sub(x, y)) } } /// @dev Returns `x < y ? x : y`. function _min(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, y), lt(y, x))) } } /// @dev Returns `b ? 1 : 0`. function _toUint(bool b) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { result := iszero(iszero(b)) } } /// @dev Returns the uint32 value at `index` in `map`. function _get(Uint32Map storage map, uint256 index) internal view returns (uint32 result) { result = uint32(map.map[index >> 3] >> ((index & 7) << 5)); } /// @dev Updates the uint32 value at `index` in `map`. function _set(Uint32Map storage map, uint256 index, uint32 value) internal { /// @solidity memory-safe-assembly assembly { mstore(0x20, map.slot) mstore(0x00, shr(3, index)) let s := keccak256(0x00, 0x40) // Storage slot. let o := shl(5, and(index, 7)) // Storage slot offset (bits). let v := sload(s) // Storage slot value. let m := 0xffffffff // Value mask. sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value))))) } } /// @dev Sets the owner alias and the owned index together. function _setOwnerAliasAndOwnedIndex( Uint32Map storage map, uint256 id, uint32 ownership, uint32 ownedIndex ) internal { /// @solidity memory-safe-assembly assembly { let value := or(shl(32, ownedIndex), and(0xffffffff, ownership)) mstore(0x20, map.slot) mstore(0x00, shr(2, id)) let s := keccak256(0x00, 0x40) // Storage slot. let o := shl(6, and(id, 3)) // Storage slot offset (bits). let v := sload(s) // Storage slot value. let m := 0xffffffffffffffff // Value mask. sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value))))) } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.22; /// @title BT404Mirror /// @notice BT404Mirror provides an interface for interacting with the /// NFT tokens in a BT404 implementation. /// /// @author FlooringLab /// @author Modified from DN404(https://github.com/Vectorized/dn404/src/DN404Mirror.sol) /// /// @dev Note: /// - The ERC721 data is stored in the base BT404 contract. contract BT404Mirror { /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* EVENTS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Emitted when token `id` is transferred from `from` to `to`. event Transfer(address indexed from, address indexed to, uint256 indexed id); /// @dev Emitted when `owner` enables `account` to manage the `id` token. event Approval(address indexed owner, address indexed account, uint256 indexed id); /// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens. event ApprovalForAll(address indexed owner, address indexed operator, bool isApproved); /// @dev The ownership is transferred from `oldOwner` to `newOwner`. /// This is for marketplace signaling purposes. This contract has a `pullOwner()` /// function that will sync the owner from the base contract. event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); /// @dev Emitted when `owner` lock or unlock token `id`. event UpdateLockState(address indexed owner, uint256 indexed id, bool lockStatus); /// @dev Emitted when token `idX` and `idY` exchanged. event Exchange(uint256 indexed idX, uint256 indexed idY, uint256 exchangeFee); /// @dev Emitted when token `id` offered for sale. event Offer(uint256 indexed id, address indexed to, uint256 minPrice, address offerToken); /// @dev Emitted when token `id` offered for sale. event CancelOffer(uint256 indexed id, address indexed owner); /// @dev Emitted when token `id` offered for sale. event Bid(uint256 indexed id, address indexed from, uint256 price, address bidToken); /// @dev Emitted when token `id` offered for sale. event CancelBid(uint256 indexed id, address indexed from); /// @dev Emitted when token `id` bought. event Bought( uint256 indexed id, address indexed from, address indexed to, uint256 price, address token, address maker ); /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. uint256 internal constant _TRANSFER_EVENT_SIGNATURE = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. uint256 internal constant _APPROVAL_EVENT_SIGNATURE = 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; /// @dev `keccak256(bytes("ApprovalForAll(address,address,bool)"))`. uint256 internal constant _APPROVAL_FOR_ALL_EVENT_SIGNATURE = 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31; /// @dev `keccak256(bytes("UpdateLockState(address,uint256,bool)"))`. uint256 internal constant _UPDATE_LOCK_STATE_EVENT_SIGNATURE = 0xcc3a1bd7e528af8582cd3578d82ae22e309de7c3663c9d0fa5b5ce79c1a346ac; /// @dev `keccak256(bytes("Exchange(uint256,uint256,uint256)"))` uint256 internal constant _EXCHANGE_EVENT_SIGNATURE = 0xbc43d7c0945f5a13a7bfa8ca7309e55f903f01d66c38c6d1353fe7ff9335d776; /// @dev `keccak256(bytes("Offer(uint256,address,uint256,address)"))` uint256 private constant _OFFER_EVENT_SIGNATURE = 0xc56f8610599b5a39311e36563ef3386394748f787ef5efc116d960d77def8050; /// @dev `keccak256(bytes("CancelOffer(uint256,address)"))` uint256 private constant _CANCEL_OFFER_EVENT_SIGNATURE = 0xc4caef7e3533865382e608c341581a5e2a1b0d1ac37b0aaf58023ccd4eedfd8e; /// @dev `keccak256(bytes("Bid(uint256,address,uint256,address)"))` uint256 private constant _BID_EVENT_SIGNATURE = 0xec85e6e86fabc4c703529b570fb5eb567dad69ddbf7901bc0fd28b38b93de7f3; /// @dev `keccak256(bytes("CancelBid(uint256,address)"))` uint256 private constant _CANCEL_BID_EVENT_SIGNATURE = 0x874afcdd5e90b2329b3c1601e613dcdc6abb6deb62ce61339a8337b48c053e51; /// @dev `keccak256(bytes("Bought(uint256,address,address,uint256,address,address)"))` uint256 private constant _BOUGHT_EVENT_SIGNATURE = 0xd9882bc1ac8e78c918b907fa0ff79cc9d866091c5eb450ebed79e9d147541d5b; /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* CUSTOM ERRORS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Thrown when a call for an NFT function did not originate /// from the base BT404 contract. error SenderNotBase(); /// @dev Thrown when a call for an NFT function did not originate from the deployer. error SenderNotDeployer(); /// @dev Thrown when transferring an NFT to a contract address that /// does not implement ERC721Receiver. error TransferToNonERC721ReceiverImplementer(); /// @dev Thrown when linking to the BT404 base contract and the /// BT404 supportsInterface check fails or the call reverts. error CannotLink(); /// @dev Thrown when a linkMirrorContract call is received and the /// NFT mirror contract has already been linked to a BT404 base contract. error AlreadyLinked(); /// @dev Thrown when retrieving the base BT404 address when a link has not /// been established. error NotLinked(); /// @dev The caller is not authorized to call the function. error Unauthorized(); /// @dev Unauthorized reentrant call. error Reentrancy(); /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* STORAGE */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Struct contain the NFT mirror contract storage. struct BT404NFTStorage { address baseERC20; bool locked; address deployer; address owner; } /// @dev Returns a storage pointer for BT404NFTStorage. function _getBT404NFTStorage() internal pure virtual returns (BT404NFTStorage storage $) { /// @solidity memory-safe-assembly assembly { // `uint72(bytes9(keccak256("DN404_MIRROR_STORAGE")))`. $.slot := 0x3602298b8c10b01230 // Truncate to 9 bytes to reduce bytecode size. } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* REENTRANCY GUARD */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Guards a function from reentrancy. modifier nonReentrant() virtual { BT404NFTStorage storage $ = _getBT404NFTStorage(); if ($.locked) revert Reentrancy(); $.locked = true; _; $.locked = false; } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* CONSTRUCTOR */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ constructor(address deployer) { // For non-proxies, we will store the deployer so that only the deployer can // link the base contract. _getBT404NFTStorage().deployer = deployer; } function _initializeBT404Mirror(address deployer) internal { // For non-proxies, we will store the deployer so that only the deployer can // link the base contract. _getBT404NFTStorage().deployer = deployer; } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* ERC721 OPERATIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns the token collection name from the base BT404 contract. function name() public view virtual returns (string memory result) { return _readString(0x06fdde03, 0); // `name()`. } /// @dev Returns the token collection symbol from the base BT404 contract. function symbol() public view virtual returns (string memory result) { return _readString(0x95d89b41, 0); // `symbol()`. } /// @dev Returns the Uniform Resource Identifier (URI) for token `id` from /// the base BT404 contract. function tokenURI(uint256 id) public view virtual returns (string memory result) { return _readString(0xc87b56dd, id); // `tokenURI()`. } /// @dev Returns the total NFT supply from the base BT404 contract. function totalSupply() public view virtual returns (uint256 result) { return _readWord(0xe2c79281, 0, 0); // `totalNFTSupply()`. } /// @dev Returns the number of NFT tokens owned by `nftOwner` from the base BT404 contract. /// /// Requirements: /// - `nftOwner` must not be the zero address. function balanceOf(address nftOwner) public view virtual returns (uint256 result) { return _readWord(0xf5b100ea, uint160(nftOwner), 0); // `balanceOfNFT(address)`. } /// @dev Returns the owner of token `id` from the base BT404 contract. /// /// Requirements: /// - Token `id` must exist. function ownerOf(uint256 id) public view virtual returns (address result) { return address(uint160(_readWord(0x6352211e, id, 0))); // `ownerOf(uint256)`. } /// @dev Returns the owner of token `id` from the base BT404 contract. /// Returns `address(0)` instead of reverting if the token does not exist. function ownerAt(uint256 id) public view virtual returns (address result) { return address(uint160(_readWord(0x24359879, id, 0))); // `ownerAt(uint256)`. } /// @dev Sets `spender` as the approved account to manage token `id` in /// the base BT404 contract. /// /// Requirements: /// - Token `id` must exist. /// - The caller must be the owner of the token, /// or an approved operator for the token owner. /// /// Emits an {Approval} event. function approve(address spender, uint256 id) public virtual { address base = baseERC20(); /// @solidity memory-safe-assembly assembly { spender := shr(96, shl(96, spender)) let m := mload(0x40) mstore(0x00, 0xd10b6e0c) // `approveNFT(address,uint256,address)`. mstore(0x20, spender) mstore(0x40, id) mstore(0x60, caller()) if iszero( and( gt(returndatasize(), 0x1f), call(gas(), base, callvalue(), 0x1c, 0x64, 0x00, 0x20) ) ) { returndatacopy(m, 0x00, returndatasize()) revert(m, returndatasize()) } mstore(0x40, m) // Restore the free memory pointer. mstore(0x60, 0) // Restore the zero pointer. // Emit the {Approval} event. log4(codesize(), 0x00, _APPROVAL_EVENT_SIGNATURE, shr(96, mload(0x0c)), spender, id) } } /// @dev Returns the account approved to manage token `id` from /// the base BT404 contract. /// /// Requirements: /// - Token `id` must exist. function getApproved(uint256 id) public view virtual returns (address) { return address(uint160(_readWord(0x081812fc, id, 0))); // `getApproved(uint256)`. } /// @dev Sets whether `operator` is approved to manage the tokens of the caller in /// the base BT404 contract. /// /// Emits an {ApprovalForAll} event. function setApprovalForAll(address operator, bool approved) public virtual { address base = baseERC20(); /// @solidity memory-safe-assembly assembly { operator := shr(96, shl(96, operator)) let m := mload(0x40) mstore(0x00, 0x813500fc) // `setApprovalForAll(address,bool,address)`. mstore(0x20, operator) mstore(0x40, iszero(iszero(approved))) mstore(0x60, caller()) if iszero( and(eq(mload(0x00), 1), call(gas(), base, callvalue(), 0x1c, 0x64, 0x00, 0x20)) ) { returndatacopy(m, 0x00, returndatasize()) revert(m, returndatasize()) } // Emit the {ApprovalForAll} event. // The `approved` value is already at 0x40. log3(0x40, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), operator) mstore(0x40, m) // Restore the free memory pointer. mstore(0x60, 0) // Restore the zero pointer. } } /// @dev Returns whether `operator` is approved to manage the tokens of `nftOwner` from /// the base BT404 contract. function isApprovedForAll(address nftOwner, address operator) public view virtual returns (bool result) { // `isApprovedForAll(address,address)`. return _readWord(0xe985e9c5, uint160(nftOwner), uint160(operator)) != 0; } /// @dev Returns the owned token ids of `account` from the base BT404 contract. function ownedIds(address account, uint256 begin, uint256 end) public view virtual returns (uint256[] memory) { return _ownedIds(account, begin, end, false); } /// @dev Returns the locked token ids of `account` from the base BT404 contract. function lockedIds(address account, uint256 begin, uint256 end) public view virtual returns (uint256[] memory) { return _ownedIds(account, begin, end, true); } /// @dev Transfers token `id` from `from` to `to`. /// /// Requirements: /// /// - Token `id` must exist. /// - `from` must be the owner of the token. /// - `to` cannot be the zero address. /// - The caller must be the owner of the token, or be approved to manage the token. /// /// Emits a {Transfer} event. function transferFrom(address from, address to, uint256 id) public virtual { address base = baseERC20(); /// @solidity memory-safe-assembly assembly { from := shr(96, shl(96, from)) to := shr(96, shl(96, to)) let m := mload(0x40) mstore(m, 0xe5eb36c8) // `transferFromNFT(address,address,uint256,address)`. mstore(add(m, 0x20), from) mstore(add(m, 0x40), to) mstore(add(m, 0x60), id) mstore(add(m, 0x80), caller()) if iszero( and(eq(mload(m), 1), call(gas(), base, callvalue(), add(m, 0x1c), 0x84, m, 0x20)) ) { returndatacopy(m, 0x00, returndatasize()) revert(m, returndatasize()) } // Emit the `from` unlock event. mstore(m, 0x00) log3(m, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, from, id) // Emit the {Transfer} event. log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id) // Emit the `to` lock event mstore(m, 0x01) log3(m, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, to, id) } } /// @dev Equivalent to `safeTransferFrom(from, to, id, "")`. function safeTransferFrom(address from, address to, uint256 id) public virtual { transferFrom(from, to, id); if (_hasCode(to)) _checkOnERC721Received(from, to, id, ""); } /// @dev Transfers token `id` from `from` to `to`. /// /// Requirements: /// /// - Token `id` must exist. /// - `from` must be the owner of the token. /// - `to` cannot be the zero address. /// - The caller must be the owner of the token, or be approved to manage the token. /// - 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 id, bytes calldata data) public virtual { transferFrom(from, to, id); if (_hasCode(to)) _checkOnERC721Received(from, to, id, data); } function updateLockState(uint256[] memory ids, bool lock) public virtual { address base = baseERC20(); (bool success, bytes memory result) = base.call( abi.encodeWithSignature( "setNFTLockState(uint256,uint256[])", uint256(uint160(msg.sender)) << 96 | (lock ? 1 : 0), ids ) ); // @solidity memory-safe-assembly assembly { if iszero(and(eq(mload(add(result, 0x20)), 1), success)) { revert(add(result, 0x20), mload(result)) } let idLen := mload(ids) mstore(0x00, lock) for { let s := add(ids, 0x20) let end := add(s, shl(5, idLen)) } iszero(eq(s, end)) { s := add(s, 0x20) } { log3(0x00, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, caller(), mload(s)) } } } function exchange(uint256 idX, uint256 idY) public virtual returns (uint256 exchangeFee) { address base = baseERC20(); /// @solidity memory-safe-assembly assembly { let m := mload(0x40) mstore(0x00, 0x2c5966af) // `exchangeNFT(uint256,uint256,address)`. mstore(0x20, idX) mstore(0x40, idY) mstore(0x60, caller()) if iszero( and( gt(returndatasize(), 0x5F), call(gas(), base, callvalue(), 0x1c, 0x64, 0x00, 0x60) ) ) { returndatacopy(m, 0x00, returndatasize()) revert(m, returndatasize()) } // store return value let x := mload(0x00) let y := mload(0x20) exchangeFee := mload(0x40) // Emit the {Transfer} event. log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, x, y, idX) log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, y, caller(), idY) // Emit the {Exchange} event. log3(0x40, 0x20, _EXCHANGE_EVENT_SIGNATURE, idX, idY) // Emit the `caller` lock event. mstore(0x40, 0x01) log3(0x40, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, caller(), idY) mstore(0x40, m) // Restore the free memory pointer. mstore(0x60, 0) // Restore the zero pointer. } } struct NFTOrder { uint256 id; uint256 price; address token; address trader; } function offerForSale(NFTOrder[] memory orders) public virtual nonReentrant { _callBaseRetWord( abi.encodeWithSignature( "offerForSale(address,(uint256,uint256,address,address)[])", msg.sender, orders ) ); /// @solidity memory-safe-assembly assembly { for { let s := add(orders, add(0x20, shl(5, mload(orders)))) let end := add(s, mul(0x80, mload(orders))) } iszero(eq(s, end)) { s := add(s, 0x80) } { log3(add(s, 0x20), 0x40, _OFFER_EVENT_SIGNATURE, mload(s), mload(add(s, 0x60))) } } } function acceptOffer(NFTOrder[] memory orders) public payable virtual nonReentrant { _callBaseRetWord( abi.encodeWithSignature( "acceptOffer(address,(uint256,uint256,address,address)[])", msg.sender, orders ) ); /// @solidity memory-safe-assembly assembly { for { let s := add(orders, add(0x20, shl(5, mload(orders)))) let end := add(s, mul(0x80, mload(orders))) } iszero(eq(s, end)) { s := add(s, 0x80) } { let from := mload(add(s, 0x60)) mstore(0x00, 0x01) log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, caller(), mload(s)) log3(0x00, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, caller(), mload(s)) log4(add(s, 0x20), 0x60, _BOUGHT_EVENT_SIGNATURE, mload(s), from, caller()) } } } function cancelOffer(uint256[] memory ids) public virtual nonReentrant { _callBaseRetWord(abi.encodeWithSignature("cancelOffer(address,uint256[])", msg.sender, ids)); /// @solidity memory-safe-assembly assembly { for { let s := add(ids, 0x20) let end := add(s, shl(5, mload(ids))) } iszero(eq(s, end)) { s := add(s, 0x20) } { log3(codesize(), 0x00, _CANCEL_OFFER_EVENT_SIGNATURE, mload(s), caller()) } } } function bidForBuy(NFTOrder[] memory orders) public payable virtual nonReentrant { _callBaseRetWord( abi.encodeWithSignature( "bidForBuy(address,(uint256,uint256,address,address)[])", msg.sender, orders ) ); /// @solidity memory-safe-assembly assembly { for { let s := add(orders, add(0x20, shl(5, mload(orders)))) let end := add(s, mul(0x80, mload(orders))) } iszero(eq(s, end)) { s := add(s, 0x80) } { log3(add(s, 0x20), 0x40, _BID_EVENT_SIGNATURE, mload(s), caller()) } } } function acceptBid(NFTOrder[] memory orders) public virtual nonReentrant { _callBaseRetWord( abi.encodeWithSignature( "acceptBid(address,(uint256,uint256,address,address)[])", msg.sender, orders ) ); /// @solidity memory-safe-assembly assembly { for { let s := add(orders, add(0x20, shl(5, mload(orders)))) let end := add(s, mul(0x80, mload(orders))) } iszero(eq(s, end)) { s := add(s, 0x80) } { let to := mload(add(s, 0x60)) mstore(0x00, 0x01) log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, caller(), to, mload(s)) log3(0x00, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, to, mload(s)) log4(add(s, 0x20), 0x60, _BOUGHT_EVENT_SIGNATURE, mload(s), caller(), to) } } } function cancelBid(uint256[] memory ids) public virtual nonReentrant { _callBaseRetWord(abi.encodeWithSignature("cancelBid(address,uint256[])", msg.sender, ids)); /// @solidity memory-safe-assembly assembly { for { let s := add(ids, 0x20) let end := add(s, shl(5, mload(ids))) } iszero(eq(s, end)) { s := add(s, 0x20) } { log3(codesize(), 0x00, _CANCEL_BID_EVENT_SIGNATURE, mload(s), caller()) } } } /// @dev Returns true if this contract implements the interface defined by `interfaceId`. /// See: https://eips.ethereum.org/EIPS/eip-165 /// This function call must use less than 30000 gas. function supportsInterface(bytes4 interfaceId) public view virtual returns (bool result) { /// @solidity memory-safe-assembly assembly { let s := shr(224, interfaceId) // ERC165: 0x01ffc9a7, ERC721: 0x80ac58cd, ERC721Metadata: 0x5b5e139f. result := or(or(eq(s, 0x01ffc9a7), eq(s, 0x80ac58cd)), eq(s, 0x5b5e139f)) } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* OWNER SYNCING OPERATIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns the `owner` of the contract, for marketplace signaling purposes. function owner() public view virtual returns (address) { return _getBT404NFTStorage().owner; } /// @dev Permissionless function to pull the owner from the base BT404 contract /// if it implements ownable, for marketplace signaling purposes. function pullOwner() public virtual { address newOwner; address base = baseERC20(); /// @solidity memory-safe-assembly assembly { mstore(0x00, 0x8da5cb5b) // `owner()`. if and(gt(returndatasize(), 0x1f), staticcall(gas(), base, 0x1c, 0x04, 0x00, 0x20)) { newOwner := shr(96, mload(0x0c)) } } BT404NFTStorage storage $ = _getBT404NFTStorage(); address oldOwner = $.owner; if (oldOwner != newOwner) { $.owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* MIRROR OPERATIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns the address of the base BT404 contract. function baseERC20() public view virtual returns (address base) { base = _getBT404NFTStorage().baseERC20; if (base == address(0)) revert NotLinked(); } /// @dev Fallback modifier to execute calls from the base BT404 contract. modifier bt404NFTFallback() virtual { BT404NFTStorage storage $ = _getBT404NFTStorage(); uint256 fnSelector = _calldataload(0x00) >> 224; // `logTransfer(uint256[])`. if (fnSelector == 0x263c69d6) { if (msg.sender != $.baseERC20) revert SenderNotBase(); /// @solidity memory-safe-assembly assembly { // When returndatacopy copies 1 or more out-of-bounds bytes, it reverts. returndatacopy(0x00, returndatasize(), lt(calldatasize(), 0x20)) let o := add(0x24, calldataload(0x04)) // Packed logs offset. returndatacopy(0x00, returndatasize(), lt(calldatasize(), o)) let end := add(o, shl(5, calldataload(sub(o, 0x20)))) returndatacopy(0x00, returndatasize(), lt(calldatasize(), end)) for {} iszero(eq(o, end)) { o := add(0x20, o) } { let d := calldataload(o) // Entry in the packed logs. let a := shr(96, d) // The address. let b := and(1, d) // Whether it is a burn. log4( codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, mul(a, b), // `from`. mul(a, iszero(b)), // `to`. shr(168, shl(160, d)) // `id`. ) } mstore(0x00, 0x01) return(0x00, 0x20) } } // `logDirectTransfer(address,address,uint256[])`. if (fnSelector == 0x144027d3) { if (msg.sender != $.baseERC20) revert SenderNotBase(); /// @solidity memory-safe-assembly assembly { let from := calldataload(0x04) let to := calldataload(0x24) let o := add(0x24, calldataload(0x44)) // Direct logs offset. let end := add(o, shl(5, calldataload(sub(o, 0x20)))) for {} iszero(eq(o, end)) { o := add(0x20, o) } { log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, calldataload(o)) } mstore(0x00, 0x01) return(0x00, 0x20) } } // `linkMirrorContract(address)`. if (fnSelector == 0x0f4599e5) { if ($.deployer != address(0)) { if (address(uint160(_calldataload(0x04))) != $.deployer) { revert SenderNotDeployer(); } } if ($.baseERC20 != address(0)) revert AlreadyLinked(); $.baseERC20 = msg.sender; /// @solidity memory-safe-assembly assembly { mstore(0x00, 0x01) return(0x00, 0x20) } } _; } /// @dev Fallback function for calls from base BT404 contract. fallback() external payable virtual bt404NFTFallback {} receive() external payable virtual {} /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* PRIVATE HELPERS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Helper to read owned ids of a account from the base BT404 contract function _ownedIds(address account, uint256 begin, uint256 end, bool locked) private view returns (uint256[] memory result) { address base = baseERC20(); /// @solidity memory-safe-assembly assembly { result := mload(0x40) mstore(0x00, 0xf9b4b328) // `ownedIds(uint256,uint256,uint256)`. mstore(0x20, or(shl(96, account), iszero(iszero(locked)))) mstore(0x40, begin) mstore(0x60, end) if iszero(staticcall(gas(), base, 0x1c, 0x64, 0x00, 0x00)) { returndatacopy(result, 0x00, returndatasize()) revert(result, returndatasize()) } returndatacopy(0x00, 0x00, 0x20) // Copy the offset of the array in returndata. returndatacopy(result, mload(0x00), 0x20) // Copy the length of the array. returndatacopy(add(result, 0x20), add(mload(0x00), 0x20), shl(5, mload(result))) // Copy the array elements. mstore(0x40, add(add(result, 0x20), shl(5, mload(result)))) // Allocate memory. mstore(0x60, 0) // Restore the zero pointer. } } /// @dev Helper to read a string from the base BT404 contract. function _readString(uint256 fnSelector, uint256 arg0) private view returns (string memory result) { address base = baseERC20(); /// @solidity memory-safe-assembly assembly { result := mload(0x40) mstore(0x00, fnSelector) mstore(0x20, arg0) if iszero(staticcall(gas(), base, 0x1c, 0x24, 0x00, 0x00)) { returndatacopy(result, 0x00, returndatasize()) revert(result, returndatasize()) } returndatacopy(0x00, 0x00, 0x20) // Copy the offset of the string in returndata. returndatacopy(result, mload(0x00), 0x20) // Copy the length of the string. returndatacopy(add(result, 0x20), add(mload(0x00), 0x20), mload(result)) // Copy the string. mstore(0x40, add(add(result, 0x20), mload(result))) // Allocate memory. } } /// @dev Helper to read a word from the base BT404 contract. function _readWord(uint256 fnSelector, uint256 arg0, uint256 arg1) private view returns (uint256 result) { address base = baseERC20(); /// @solidity memory-safe-assembly assembly { let m := mload(0x40) mstore(0x00, fnSelector) mstore(0x20, arg0) mstore(0x40, arg1) if iszero( and(gt(returndatasize(), 0x1f), staticcall(gas(), base, 0x1c, 0x44, 0x00, 0x20)) ) { returndatacopy(m, 0x00, returndatasize()) revert(m, returndatasize()) } mstore(0x40, m) // Restore the free memory pointer. result := mload(0x00) } } /// @dev Helper to call a function and return a word value. function _callBaseRetWord(bytes memory _calldata) private returns (uint256 result) { address base = baseERC20(); /// @solidity memory-safe-assembly assembly { let m := mload(0x40) if iszero( and( gt(returndatasize(), 0x1f), call( gas(), base, callvalue(), add(_calldata, 0x20), mload(_calldata), 0x00, 0x20 ) ) ) { returndatacopy(m, 0x00, returndatasize()) revert(m, returndatasize()) } mstore(0x40, m) // Restore the free memory pointer. mstore(0x60, 0) // Restore the zero pointer. result := mload(0x00) } } /// @dev Returns the calldata value at `offset`. function _calldataload(uint256 offset) private pure returns (uint256 value) { /// @solidity memory-safe-assembly assembly { value := calldataload(offset) } } /// @dev Returns if `a` has bytecode of non-zero length. function _hasCode(address a) private view returns (bool result) { /// @solidity memory-safe-assembly assembly { result := extcodesize(a) // Can handle dirty upper bits. } } /// @dev Perform a call to invoke {IERC721Receiver-onERC721Received} on `to`. /// Reverts if the target does not support the function correctly. function _checkOnERC721Received(address from, address to, uint256 id, bytes memory data) private { /// @solidity memory-safe-assembly assembly { // Prepare the calldata. let m := mload(0x40) let onERC721ReceivedSelector := 0x150b7a02 mstore(m, onERC721ReceivedSelector) mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`. mstore(add(m, 0x40), shr(96, shl(96, from))) mstore(add(m, 0x60), id) mstore(add(m, 0x80), 0x80) let n := mload(data) mstore(add(m, 0xa0), n) if n { pop(staticcall(gas(), 4, add(data, 0x20), n, add(m, 0xc0), n)) } // Revert if the call reverts. if iszero(call(gas(), to, 0, add(m, 0x1c), add(n, 0xa4), m, 0x20)) { if returndatasize() { // Bubble up the revert if the call reverts. returndatacopy(m, 0x00, returndatasize()) revert(m, returndatasize()) } } // Load the returndata and compare it. if iszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) { mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`. revert(0x1c, 0x04) } } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice UUPS proxy mixin. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/UUPSUpgradeable.sol) /// @author Modified from OpenZeppelin /// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol) /// /// Note: /// - This implementation is intended to be used with ERC1967 proxies. /// See: `LibClone.deployERC1967` and related functions. /// - This implementation is NOT compatible with legacy OpenZeppelin proxies /// which do not store the implementation at `_ERC1967_IMPLEMENTATION_SLOT`. abstract contract UUPSUpgradeable { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The upgrade failed. error UpgradeFailed(); /// @dev The call is from an unauthorized call context. error UnauthorizedCallContext(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* IMMUTABLES */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev For checking if the context is a delegate call. uint256 private immutable __self = uint256(uint160(address(this))); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EVENTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Emitted when the proxy's implementation is upgraded. event Upgraded(address indexed implementation); /// @dev `keccak256(bytes("Upgraded(address)"))`. uint256 private constant _UPGRADED_EVENT_SIGNATURE = 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The ERC-1967 storage slot for the implementation in the proxy. /// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. bytes32 internal constant _ERC1967_IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* UUPS OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Please override this function to check if `msg.sender` is authorized /// to upgrade the proxy to `newImplementation`, reverting if not. /// ``` /// function _authorizeUpgrade(address) internal override onlyOwner {} /// ``` function _authorizeUpgrade(address newImplementation) internal virtual; /// @dev Returns the storage slot used by the implementation, /// as specified in [ERC1822](https://eips.ethereum.org/EIPS/eip-1822). /// /// Note: The `notDelegated` modifier prevents accidental upgrades to /// an implementation that is a proxy contract. function proxiableUUID() public view virtual notDelegated returns (bytes32) { // This function must always return `_ERC1967_IMPLEMENTATION_SLOT` to comply with ERC1967. return _ERC1967_IMPLEMENTATION_SLOT; } /// @dev Upgrades the proxy's implementation to `newImplementation`. /// Emits a {Upgraded} event. /// /// Note: Passing in empty `data` skips the delegatecall to `newImplementation`. function upgradeToAndCall(address newImplementation, bytes calldata data) public payable virtual onlyProxy { _authorizeUpgrade(newImplementation); /// @solidity memory-safe-assembly assembly { newImplementation := shr(96, shl(96, newImplementation)) // Clears upper 96 bits. mstore(0x01, 0x52d1902d) // `proxiableUUID()`. let s := _ERC1967_IMPLEMENTATION_SLOT // Check if `newImplementation` implements `proxiableUUID` correctly. if iszero(eq(mload(staticcall(gas(), newImplementation, 0x1d, 0x04, 0x01, 0x20)), s)) { mstore(0x01, 0x55299b49) // `UpgradeFailed()`. revert(0x1d, 0x04) } // Emit the {Upgraded} event. log2(codesize(), 0x00, _UPGRADED_EVENT_SIGNATURE, newImplementation) sstore(s, newImplementation) // Updates the implementation. // Perform a delegatecall to `newImplementation` if `data` is non-empty. if data.length { // Forwards the `data` to `newImplementation` via delegatecall. let m := mload(0x40) calldatacopy(m, data.offset, data.length) if iszero(delegatecall(gas(), newImplementation, m, data.length, codesize(), 0x00)) { // Bubble up the revert if the call reverts. returndatacopy(m, 0x00, returndatasize()) revert(m, returndatasize()) } } } } /// @dev Requires that the execution is performed through a proxy. modifier onlyProxy() { uint256 s = __self; /// @solidity memory-safe-assembly assembly { // To enable use cases with an immutable default implementation in the bytecode, // (see: ERC6551Proxy), we don't require that the proxy address must match the // value stored in the implementation slot, which may not be initialized. if eq(s, address()) { mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`. revert(0x1c, 0x04) } } _; } /// @dev Requires that the execution is NOT performed via delegatecall. /// This is the opposite of `onlyProxy`. modifier notDelegated() { uint256 s = __self; /// @solidity memory-safe-assembly assembly { if iszero(eq(s, address())) { mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`. revert(0x1c, 0x04) } } _; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import {Ownable} from "./Ownable.sol"; /// @notice Simple single owner and multiroles authorization mixin. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol) /// @dev While the ownable portion follows [EIP-173](https://eips.ethereum.org/EIPS/eip-173) /// for compatibility, the nomenclature for the 2-step ownership handover and roles /// may be unique to this codebase. abstract contract OwnableRoles is Ownable { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EVENTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The `user`'s roles is updated to `roles`. /// Each bit of `roles` represents whether the role is set. event RolesUpdated(address indexed user, uint256 indexed roles); /// @dev `keccak256(bytes("RolesUpdated(address,uint256)"))`. uint256 private constant _ROLES_UPDATED_EVENT_SIGNATURE = 0x715ad5ce61fc9595c7b415289d59cf203f23a94fa06f04af7e489a0a76e1fe26; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The role slot of `user` is given by: /// ``` /// mstore(0x00, or(shl(96, user), _ROLE_SLOT_SEED)) /// let roleSlot := keccak256(0x00, 0x20) /// ``` /// This automatically ignores the upper bits of the `user` in case /// they are not clean, as well as keep the `keccak256` under 32-bytes. /// /// Note: This is equivalent to `uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))`. uint256 private constant _ROLE_SLOT_SEED = 0x8b78c6d8; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Overwrite the roles directly without authorization guard. function _setRoles(address user, uint256 roles) internal virtual { /// @solidity memory-safe-assembly assembly { mstore(0x0c, _ROLE_SLOT_SEED) mstore(0x00, user) // Store the new value. sstore(keccak256(0x0c, 0x20), roles) // Emit the {RolesUpdated} event. log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), roles) } } /// @dev Updates the roles directly without authorization guard. /// If `on` is true, each set bit of `roles` will be turned on, /// otherwise, each set bit of `roles` will be turned off. function _updateRoles(address user, uint256 roles, bool on) internal virtual { /// @solidity memory-safe-assembly assembly { mstore(0x0c, _ROLE_SLOT_SEED) mstore(0x00, user) let roleSlot := keccak256(0x0c, 0x20) // Load the current value. let current := sload(roleSlot) // Compute the updated roles if `on` is true. let updated := or(current, roles) // Compute the updated roles if `on` is false. // Use `and` to compute the intersection of `current` and `roles`, // `xor` it with `current` to flip the bits in the intersection. if iszero(on) { updated := xor(current, and(current, roles)) } // Then, store the new value. sstore(roleSlot, updated) // Emit the {RolesUpdated} event. log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), updated) } } /// @dev Grants the roles directly without authorization guard. /// Each bit of `roles` represents the role to turn on. function _grantRoles(address user, uint256 roles) internal virtual { _updateRoles(user, roles, true); } /// @dev Removes the roles directly without authorization guard. /// Each bit of `roles` represents the role to turn off. function _removeRoles(address user, uint256 roles) internal virtual { _updateRoles(user, roles, false); } /// @dev Throws if the sender does not have any of the `roles`. function _checkRoles(uint256 roles) internal view virtual { /// @solidity memory-safe-assembly assembly { // Compute the role slot. mstore(0x0c, _ROLE_SLOT_SEED) mstore(0x00, caller()) // Load the stored value, and if the `and` intersection // of the value and `roles` is zero, revert. if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) { mstore(0x00, 0x82b42900) // `Unauthorized()`. revert(0x1c, 0x04) } } } /// @dev Throws if the sender is not the owner, /// and does not have any of the `roles`. /// Checks for ownership first, then lazily checks for roles. function _checkOwnerOrRoles(uint256 roles) internal view virtual { /// @solidity memory-safe-assembly assembly { // If the caller is not the stored owner. // Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`. if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) { // Compute the role slot. mstore(0x0c, _ROLE_SLOT_SEED) mstore(0x00, caller()) // Load the stored value, and if the `and` intersection // of the value and `roles` is zero, revert. if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) { mstore(0x00, 0x82b42900) // `Unauthorized()`. revert(0x1c, 0x04) } } } } /// @dev Throws if the sender does not have any of the `roles`, /// and is not the owner. /// Checks for roles first, then lazily checks for ownership. function _checkRolesOrOwner(uint256 roles) internal view virtual { /// @solidity memory-safe-assembly assembly { // Compute the role slot. mstore(0x0c, _ROLE_SLOT_SEED) mstore(0x00, caller()) // Load the stored value, and if the `and` intersection // of the value and `roles` is zero, revert. if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) { // If the caller is not the stored owner. // Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`. if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) { mstore(0x00, 0x82b42900) // `Unauthorized()`. revert(0x1c, 0x04) } } } } /// @dev Convenience function to return a `roles` bitmap from an array of `ordinals`. /// This is meant for frontends like Etherscan, and is therefore not fully optimized. /// Not recommended to be called on-chain. /// Made internal to conserve bytecode. Wrap it in a public function if needed. function _rolesFromOrdinals(uint8[] memory ordinals) internal pure returns (uint256 roles) { /// @solidity memory-safe-assembly assembly { for { let i := shl(5, mload(ordinals)) } i { i := sub(i, 0x20) } { // We don't need to mask the values of `ordinals`, as Solidity // cleans dirty upper bits when storing variables into memory. roles := or(shl(mload(add(ordinals, i)), 1), roles) } } } /// @dev Convenience function to return an array of `ordinals` from the `roles` bitmap. /// This is meant for frontends like Etherscan, and is therefore not fully optimized. /// Not recommended to be called on-chain. /// Made internal to conserve bytecode. Wrap it in a public function if needed. function _ordinalsFromRoles(uint256 roles) internal pure returns (uint8[] memory ordinals) { /// @solidity memory-safe-assembly assembly { // Grab the pointer to the free memory. ordinals := mload(0x40) let ptr := add(ordinals, 0x20) let o := 0 // The absence of lookup tables, De Bruijn, etc., here is intentional for // smaller bytecode, as this function is not meant to be called on-chain. for { let t := roles } 1 {} { mstore(ptr, o) // `shr` 5 is equivalent to multiplying by 0x20. // Push back into the ordinals array if the bit is set. ptr := add(ptr, shl(5, and(t, 1))) o := add(o, 1) t := shr(o, roles) if iszero(t) { break } } // Store the length of `ordinals`. mstore(ordinals, shr(5, sub(ptr, add(ordinals, 0x20)))) // Allocate the memory. mstore(0x40, ptr) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PUBLIC UPDATE FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Allows the owner to grant `user` `roles`. /// If the `user` already has a role, then it will be an no-op for the role. function grantRoles(address user, uint256 roles) public payable virtual onlyOwner { _grantRoles(user, roles); } /// @dev Allows the owner to remove `user` `roles`. /// If the `user` does not have a role, then it will be an no-op for the role. function revokeRoles(address user, uint256 roles) public payable virtual onlyOwner { _removeRoles(user, roles); } /// @dev Allow the caller to remove their own roles. /// If the caller does not have a role, then it will be an no-op for the role. function renounceRoles(uint256 roles) public payable virtual { _removeRoles(msg.sender, roles); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PUBLIC READ FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the roles of `user`. function rolesOf(address user) public view virtual returns (uint256 roles) { /// @solidity memory-safe-assembly assembly { // Compute the role slot. mstore(0x0c, _ROLE_SLOT_SEED) mstore(0x00, user) // Load the stored value. roles := sload(keccak256(0x0c, 0x20)) } } /// @dev Returns whether `user` has any of `roles`. function hasAnyRole(address user, uint256 roles) public view virtual returns (bool) { return rolesOf(user) & roles != 0; } /// @dev Returns whether `user` has all of `roles`. function hasAllRoles(address user, uint256 roles) public view virtual returns (bool) { return rolesOf(user) & roles == roles; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* MODIFIERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Marks a function as only callable by an account with `roles`. modifier onlyRoles(uint256 roles) virtual { _checkRoles(roles); _; } /// @dev Marks a function as only callable by the owner or by an account /// with `roles`. Checks for ownership first, then lazily checks for roles. modifier onlyOwnerOrRoles(uint256 roles) virtual { _checkOwnerOrRoles(roles); _; } /// @dev Marks a function as only callable by an account with `roles` /// or the owner. Checks for roles first, then lazily checks for ownership. modifier onlyRolesOrOwner(uint256 roles) virtual { _checkRolesOrOwner(roles); _; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ROLE CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // IYKYK uint256 internal constant _ROLE_0 = 1 << 0; uint256 internal constant _ROLE_1 = 1 << 1; uint256 internal constant _ROLE_2 = 1 << 2; uint256 internal constant _ROLE_3 = 1 << 3; uint256 internal constant _ROLE_4 = 1 << 4; uint256 internal constant _ROLE_5 = 1 << 5; uint256 internal constant _ROLE_6 = 1 << 6; uint256 internal constant _ROLE_7 = 1 << 7; uint256 internal constant _ROLE_8 = 1 << 8; uint256 internal constant _ROLE_9 = 1 << 9; uint256 internal constant _ROLE_10 = 1 << 10; uint256 internal constant _ROLE_11 = 1 << 11; uint256 internal constant _ROLE_12 = 1 << 12; uint256 internal constant _ROLE_13 = 1 << 13; uint256 internal constant _ROLE_14 = 1 << 14; uint256 internal constant _ROLE_15 = 1 << 15; uint256 internal constant _ROLE_16 = 1 << 16; uint256 internal constant _ROLE_17 = 1 << 17; uint256 internal constant _ROLE_18 = 1 << 18; uint256 internal constant _ROLE_19 = 1 << 19; uint256 internal constant _ROLE_20 = 1 << 20; uint256 internal constant _ROLE_21 = 1 << 21; uint256 internal constant _ROLE_22 = 1 << 22; uint256 internal constant _ROLE_23 = 1 << 23; uint256 internal constant _ROLE_24 = 1 << 24; uint256 internal constant _ROLE_25 = 1 << 25; uint256 internal constant _ROLE_26 = 1 << 26; uint256 internal constant _ROLE_27 = 1 << 27; uint256 internal constant _ROLE_28 = 1 << 28; uint256 internal constant _ROLE_29 = 1 << 29; uint256 internal constant _ROLE_30 = 1 << 30; uint256 internal constant _ROLE_31 = 1 << 31; uint256 internal constant _ROLE_32 = 1 << 32; uint256 internal constant _ROLE_33 = 1 << 33; uint256 internal constant _ROLE_34 = 1 << 34; uint256 internal constant _ROLE_35 = 1 << 35; uint256 internal constant _ROLE_36 = 1 << 36; uint256 internal constant _ROLE_37 = 1 << 37; uint256 internal constant _ROLE_38 = 1 << 38; uint256 internal constant _ROLE_39 = 1 << 39; uint256 internal constant _ROLE_40 = 1 << 40; uint256 internal constant _ROLE_41 = 1 << 41; uint256 internal constant _ROLE_42 = 1 << 42; uint256 internal constant _ROLE_43 = 1 << 43; uint256 internal constant _ROLE_44 = 1 << 44; uint256 internal constant _ROLE_45 = 1 << 45; uint256 internal constant _ROLE_46 = 1 << 46; uint256 internal constant _ROLE_47 = 1 << 47; uint256 internal constant _ROLE_48 = 1 << 48; uint256 internal constant _ROLE_49 = 1 << 49; uint256 internal constant _ROLE_50 = 1 << 50; uint256 internal constant _ROLE_51 = 1 << 51; uint256 internal constant _ROLE_52 = 1 << 52; uint256 internal constant _ROLE_53 = 1 << 53; uint256 internal constant _ROLE_54 = 1 << 54; uint256 internal constant _ROLE_55 = 1 << 55; uint256 internal constant _ROLE_56 = 1 << 56; uint256 internal constant _ROLE_57 = 1 << 57; uint256 internal constant _ROLE_58 = 1 << 58; uint256 internal constant _ROLE_59 = 1 << 59; uint256 internal constant _ROLE_60 = 1 << 60; uint256 internal constant _ROLE_61 = 1 << 61; uint256 internal constant _ROLE_62 = 1 << 62; uint256 internal constant _ROLE_63 = 1 << 63; uint256 internal constant _ROLE_64 = 1 << 64; uint256 internal constant _ROLE_65 = 1 << 65; uint256 internal constant _ROLE_66 = 1 << 66; uint256 internal constant _ROLE_67 = 1 << 67; uint256 internal constant _ROLE_68 = 1 << 68; uint256 internal constant _ROLE_69 = 1 << 69; uint256 internal constant _ROLE_70 = 1 << 70; uint256 internal constant _ROLE_71 = 1 << 71; uint256 internal constant _ROLE_72 = 1 << 72; uint256 internal constant _ROLE_73 = 1 << 73; uint256 internal constant _ROLE_74 = 1 << 74; uint256 internal constant _ROLE_75 = 1 << 75; uint256 internal constant _ROLE_76 = 1 << 76; uint256 internal constant _ROLE_77 = 1 << 77; uint256 internal constant _ROLE_78 = 1 << 78; uint256 internal constant _ROLE_79 = 1 << 79; uint256 internal constant _ROLE_80 = 1 << 80; uint256 internal constant _ROLE_81 = 1 << 81; uint256 internal constant _ROLE_82 = 1 << 82; uint256 internal constant _ROLE_83 = 1 << 83; uint256 internal constant _ROLE_84 = 1 << 84; uint256 internal constant _ROLE_85 = 1 << 85; uint256 internal constant _ROLE_86 = 1 << 86; uint256 internal constant _ROLE_87 = 1 << 87; uint256 internal constant _ROLE_88 = 1 << 88; uint256 internal constant _ROLE_89 = 1 << 89; uint256 internal constant _ROLE_90 = 1 << 90; uint256 internal constant _ROLE_91 = 1 << 91; uint256 internal constant _ROLE_92 = 1 << 92; uint256 internal constant _ROLE_93 = 1 << 93; uint256 internal constant _ROLE_94 = 1 << 94; uint256 internal constant _ROLE_95 = 1 << 95; uint256 internal constant _ROLE_96 = 1 << 96; uint256 internal constant _ROLE_97 = 1 << 97; uint256 internal constant _ROLE_98 = 1 << 98; uint256 internal constant _ROLE_99 = 1 << 99; uint256 internal constant _ROLE_100 = 1 << 100; uint256 internal constant _ROLE_101 = 1 << 101; uint256 internal constant _ROLE_102 = 1 << 102; uint256 internal constant _ROLE_103 = 1 << 103; uint256 internal constant _ROLE_104 = 1 << 104; uint256 internal constant _ROLE_105 = 1 << 105; uint256 internal constant _ROLE_106 = 1 << 106; uint256 internal constant _ROLE_107 = 1 << 107; uint256 internal constant _ROLE_108 = 1 << 108; uint256 internal constant _ROLE_109 = 1 << 109; uint256 internal constant _ROLE_110 = 1 << 110; uint256 internal constant _ROLE_111 = 1 << 111; uint256 internal constant _ROLE_112 = 1 << 112; uint256 internal constant _ROLE_113 = 1 << 113; uint256 internal constant _ROLE_114 = 1 << 114; uint256 internal constant _ROLE_115 = 1 << 115; uint256 internal constant _ROLE_116 = 1 << 116; uint256 internal constant _ROLE_117 = 1 << 117; uint256 internal constant _ROLE_118 = 1 << 118; uint256 internal constant _ROLE_119 = 1 << 119; uint256 internal constant _ROLE_120 = 1 << 120; uint256 internal constant _ROLE_121 = 1 << 121; uint256 internal constant _ROLE_122 = 1 << 122; uint256 internal constant _ROLE_123 = 1 << 123; uint256 internal constant _ROLE_124 = 1 << 124; uint256 internal constant _ROLE_125 = 1 << 125; uint256 internal constant _ROLE_126 = 1 << 126; uint256 internal constant _ROLE_127 = 1 << 127; uint256 internal constant _ROLE_128 = 1 << 128; uint256 internal constant _ROLE_129 = 1 << 129; uint256 internal constant _ROLE_130 = 1 << 130; uint256 internal constant _ROLE_131 = 1 << 131; uint256 internal constant _ROLE_132 = 1 << 132; uint256 internal constant _ROLE_133 = 1 << 133; uint256 internal constant _ROLE_134 = 1 << 134; uint256 internal constant _ROLE_135 = 1 << 135; uint256 internal constant _ROLE_136 = 1 << 136; uint256 internal constant _ROLE_137 = 1 << 137; uint256 internal constant _ROLE_138 = 1 << 138; uint256 internal constant _ROLE_139 = 1 << 139; uint256 internal constant _ROLE_140 = 1 << 140; uint256 internal constant _ROLE_141 = 1 << 141; uint256 internal constant _ROLE_142 = 1 << 142; uint256 internal constant _ROLE_143 = 1 << 143; uint256 internal constant _ROLE_144 = 1 << 144; uint256 internal constant _ROLE_145 = 1 << 145; uint256 internal constant _ROLE_146 = 1 << 146; uint256 internal constant _ROLE_147 = 1 << 147; uint256 internal constant _ROLE_148 = 1 << 148; uint256 internal constant _ROLE_149 = 1 << 149; uint256 internal constant _ROLE_150 = 1 << 150; uint256 internal constant _ROLE_151 = 1 << 151; uint256 internal constant _ROLE_152 = 1 << 152; uint256 internal constant _ROLE_153 = 1 << 153; uint256 internal constant _ROLE_154 = 1 << 154; uint256 internal constant _ROLE_155 = 1 << 155; uint256 internal constant _ROLE_156 = 1 << 156; uint256 internal constant _ROLE_157 = 1 << 157; uint256 internal constant _ROLE_158 = 1 << 158; uint256 internal constant _ROLE_159 = 1 << 159; uint256 internal constant _ROLE_160 = 1 << 160; uint256 internal constant _ROLE_161 = 1 << 161; uint256 internal constant _ROLE_162 = 1 << 162; uint256 internal constant _ROLE_163 = 1 << 163; uint256 internal constant _ROLE_164 = 1 << 164; uint256 internal constant _ROLE_165 = 1 << 165; uint256 internal constant _ROLE_166 = 1 << 166; uint256 internal constant _ROLE_167 = 1 << 167; uint256 internal constant _ROLE_168 = 1 << 168; uint256 internal constant _ROLE_169 = 1 << 169; uint256 internal constant _ROLE_170 = 1 << 170; uint256 internal constant _ROLE_171 = 1 << 171; uint256 internal constant _ROLE_172 = 1 << 172; uint256 internal constant _ROLE_173 = 1 << 173; uint256 internal constant _ROLE_174 = 1 << 174; uint256 internal constant _ROLE_175 = 1 << 175; uint256 internal constant _ROLE_176 = 1 << 176; uint256 internal constant _ROLE_177 = 1 << 177; uint256 internal constant _ROLE_178 = 1 << 178; uint256 internal constant _ROLE_179 = 1 << 179; uint256 internal constant _ROLE_180 = 1 << 180; uint256 internal constant _ROLE_181 = 1 << 181; uint256 internal constant _ROLE_182 = 1 << 182; uint256 internal constant _ROLE_183 = 1 << 183; uint256 internal constant _ROLE_184 = 1 << 184; uint256 internal constant _ROLE_185 = 1 << 185; uint256 internal constant _ROLE_186 = 1 << 186; uint256 internal constant _ROLE_187 = 1 << 187; uint256 internal constant _ROLE_188 = 1 << 188; uint256 internal constant _ROLE_189 = 1 << 189; uint256 internal constant _ROLE_190 = 1 << 190; uint256 internal constant _ROLE_191 = 1 << 191; uint256 internal constant _ROLE_192 = 1 << 192; uint256 internal constant _ROLE_193 = 1 << 193; uint256 internal constant _ROLE_194 = 1 << 194; uint256 internal constant _ROLE_195 = 1 << 195; uint256 internal constant _ROLE_196 = 1 << 196; uint256 internal constant _ROLE_197 = 1 << 197; uint256 internal constant _ROLE_198 = 1 << 198; uint256 internal constant _ROLE_199 = 1 << 199; uint256 internal constant _ROLE_200 = 1 << 200; uint256 internal constant _ROLE_201 = 1 << 201; uint256 internal constant _ROLE_202 = 1 << 202; uint256 internal constant _ROLE_203 = 1 << 203; uint256 internal constant _ROLE_204 = 1 << 204; uint256 internal constant _ROLE_205 = 1 << 205; uint256 internal constant _ROLE_206 = 1 << 206; uint256 internal constant _ROLE_207 = 1 << 207; uint256 internal constant _ROLE_208 = 1 << 208; uint256 internal constant _ROLE_209 = 1 << 209; uint256 internal constant _ROLE_210 = 1 << 210; uint256 internal constant _ROLE_211 = 1 << 211; uint256 internal constant _ROLE_212 = 1 << 212; uint256 internal constant _ROLE_213 = 1 << 213; uint256 internal constant _ROLE_214 = 1 << 214; uint256 internal constant _ROLE_215 = 1 << 215; uint256 internal constant _ROLE_216 = 1 << 216; uint256 internal constant _ROLE_217 = 1 << 217; uint256 internal constant _ROLE_218 = 1 << 218; uint256 internal constant _ROLE_219 = 1 << 219; uint256 internal constant _ROLE_220 = 1 << 220; uint256 internal constant _ROLE_221 = 1 << 221; uint256 internal constant _ROLE_222 = 1 << 222; uint256 internal constant _ROLE_223 = 1 << 223; uint256 internal constant _ROLE_224 = 1 << 224; uint256 internal constant _ROLE_225 = 1 << 225; uint256 internal constant _ROLE_226 = 1 << 226; uint256 internal constant _ROLE_227 = 1 << 227; uint256 internal constant _ROLE_228 = 1 << 228; uint256 internal constant _ROLE_229 = 1 << 229; uint256 internal constant _ROLE_230 = 1 << 230; uint256 internal constant _ROLE_231 = 1 << 231; uint256 internal constant _ROLE_232 = 1 << 232; uint256 internal constant _ROLE_233 = 1 << 233; uint256 internal constant _ROLE_234 = 1 << 234; uint256 internal constant _ROLE_235 = 1 << 235; uint256 internal constant _ROLE_236 = 1 << 236; uint256 internal constant _ROLE_237 = 1 << 237; uint256 internal constant _ROLE_238 = 1 << 238; uint256 internal constant _ROLE_239 = 1 << 239; uint256 internal constant _ROLE_240 = 1 << 240; uint256 internal constant _ROLE_241 = 1 << 241; uint256 internal constant _ROLE_242 = 1 << 242; uint256 internal constant _ROLE_243 = 1 << 243; uint256 internal constant _ROLE_244 = 1 << 244; uint256 internal constant _ROLE_245 = 1 << 245; uint256 internal constant _ROLE_246 = 1 << 246; uint256 internal constant _ROLE_247 = 1 << 247; uint256 internal constant _ROLE_248 = 1 << 248; uint256 internal constant _ROLE_249 = 1 << 249; uint256 internal constant _ROLE_250 = 1 << 250; uint256 internal constant _ROLE_251 = 1 << 251; uint256 internal constant _ROLE_252 = 1 << 252; uint256 internal constant _ROLE_253 = 1 << 253; uint256 internal constant _ROLE_254 = 1 << 254; uint256 internal constant _ROLE_255 = 1 << 255; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import {LibBit} from "./LibBit.sol"; /// @notice Library for storage of packed unsigned booleans. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBitmap.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibBitmap.sol) /// @author Modified from Solidity-Bits (https://github.com/estarriolvetch/solidity-bits/blob/main/contracts/BitMaps.sol) library LibBitmap { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The constant returned when a bitmap scan does not find a result. uint256 internal constant NOT_FOUND = type(uint256).max; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STRUCTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev A bitmap in storage. struct Bitmap { mapping(uint256 => uint256) map; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the boolean value of the bit at `index` in `bitmap`. function get(Bitmap storage bitmap, uint256 index) internal view returns (bool isSet) { // It is better to set `isSet` to either 0 or 1, than zero vs non-zero. // Both cost the same amount of gas, but the former allows the returned value // to be reused without cleaning the upper bits. uint256 b = (bitmap.map[index >> 8] >> (index & 0xff)) & 1; /// @solidity memory-safe-assembly assembly { isSet := b } } /// @dev Updates the bit at `index` in `bitmap` to true. function set(Bitmap storage bitmap, uint256 index) internal { bitmap.map[index >> 8] |= (1 << (index & 0xff)); } /// @dev Updates the bit at `index` in `bitmap` to false. function unset(Bitmap storage bitmap, uint256 index) internal { bitmap.map[index >> 8] &= ~(1 << (index & 0xff)); } /// @dev Flips the bit at `index` in `bitmap`. /// Returns the boolean result of the flipped bit. function toggle(Bitmap storage bitmap, uint256 index) internal returns (bool newIsSet) { /// @solidity memory-safe-assembly assembly { mstore(0x20, bitmap.slot) mstore(0x00, shr(8, index)) let storageSlot := keccak256(0x00, 0x40) let shift := and(index, 0xff) let storageValue := xor(sload(storageSlot), shl(shift, 1)) // It makes sense to return the `newIsSet`, // as it allow us to skip an additional warm `sload`, // and it costs minimal gas (about 15), // which may be optimized away if the returned value is unused. newIsSet := and(1, shr(shift, storageValue)) sstore(storageSlot, storageValue) } } /// @dev Updates the bit at `index` in `bitmap` to `shouldSet`. function setTo(Bitmap storage bitmap, uint256 index, bool shouldSet) internal { /// @solidity memory-safe-assembly assembly { mstore(0x20, bitmap.slot) mstore(0x00, shr(8, index)) let storageSlot := keccak256(0x00, 0x40) let storageValue := sload(storageSlot) let shift := and(index, 0xff) sstore( storageSlot, // Unsets the bit at `shift` via `and`, then sets its new value via `or`. or(and(storageValue, not(shl(shift, 1))), shl(shift, iszero(iszero(shouldSet)))) ) } } /// @dev Consecutively sets `amount` of bits starting from the bit at `start`. function setBatch(Bitmap storage bitmap, uint256 start, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { let max := not(0) let shift := and(start, 0xff) mstore(0x20, bitmap.slot) mstore(0x00, shr(8, start)) if iszero(lt(add(shift, amount), 257)) { let storageSlot := keccak256(0x00, 0x40) sstore(storageSlot, or(sload(storageSlot), shl(shift, max))) let bucket := add(mload(0x00), 1) let bucketEnd := add(mload(0x00), shr(8, add(amount, shift))) amount := and(add(amount, shift), 0xff) shift := 0 for {} iszero(eq(bucket, bucketEnd)) { bucket := add(bucket, 1) } { mstore(0x00, bucket) sstore(keccak256(0x00, 0x40), max) } mstore(0x00, bucket) } let storageSlot := keccak256(0x00, 0x40) sstore(storageSlot, or(sload(storageSlot), shl(shift, shr(sub(256, amount), max)))) } } /// @dev Consecutively unsets `amount` of bits starting from the bit at `start`. function unsetBatch(Bitmap storage bitmap, uint256 start, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { let shift := and(start, 0xff) mstore(0x20, bitmap.slot) mstore(0x00, shr(8, start)) if iszero(lt(add(shift, amount), 257)) { let storageSlot := keccak256(0x00, 0x40) sstore(storageSlot, and(sload(storageSlot), not(shl(shift, not(0))))) let bucket := add(mload(0x00), 1) let bucketEnd := add(mload(0x00), shr(8, add(amount, shift))) amount := and(add(amount, shift), 0xff) shift := 0 for {} iszero(eq(bucket, bucketEnd)) { bucket := add(bucket, 1) } { mstore(0x00, bucket) sstore(keccak256(0x00, 0x40), 0) } mstore(0x00, bucket) } let storageSlot := keccak256(0x00, 0x40) sstore( storageSlot, and(sload(storageSlot), not(shl(shift, shr(sub(256, amount), not(0))))) ) } } /// @dev Returns number of set bits within a range by /// scanning `amount` of bits starting from the bit at `start`. function popCount(Bitmap storage bitmap, uint256 start, uint256 amount) internal view returns (uint256 count) { unchecked { uint256 bucket = start >> 8; uint256 shift = start & 0xff; if (!(amount + shift < 257)) { count = LibBit.popCount(bitmap.map[bucket] >> shift); uint256 bucketEnd = bucket + ((amount + shift) >> 8); amount = (amount + shift) & 0xff; shift = 0; for (++bucket; bucket != bucketEnd; ++bucket) { count += LibBit.popCount(bitmap.map[bucket]); } } count += LibBit.popCount((bitmap.map[bucket] >> shift) << (256 - amount)); } } /// @dev Returns the index of the most significant set bit before the bit at `before`. /// If no set bit is found, returns `NOT_FOUND`. function findLastSet(Bitmap storage bitmap, uint256 before) internal view returns (uint256 setBitIndex) { uint256 bucket; uint256 bucketBits; /// @solidity memory-safe-assembly assembly { setBitIndex := not(0) bucket := shr(8, before) mstore(0x00, bucket) mstore(0x20, bitmap.slot) let offset := and(0xff, not(before)) // `256 - (255 & before) - 1`. bucketBits := shr(offset, shl(offset, sload(keccak256(0x00, 0x40)))) if iszero(or(bucketBits, iszero(bucket))) { for {} 1 {} { bucket := add(bucket, setBitIndex) // `sub(bucket, 1)`. mstore(0x00, bucket) bucketBits := sload(keccak256(0x00, 0x40)) if or(bucketBits, iszero(bucket)) { break } } } } if (bucketBits != 0) { setBitIndex = (bucket << 8) | LibBit.fls(bucketBits); /// @solidity memory-safe-assembly assembly { setBitIndex := or(setBitIndex, sub(0, gt(setBitIndex, before))) } } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// /// @dev Note: /// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection. /// - For ERC20s, this implementation won't check that a token has code, /// responsibility is delegated to the caller. library SafeTransferLib { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The ETH transfer has failed. error ETHTransferFailed(); /// @dev The ERC20 `transferFrom` has failed. error TransferFromFailed(); /// @dev The ERC20 `transfer` has failed. error TransferFailed(); /// @dev The ERC20 `approve` has failed. error ApproveFailed(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes. uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300; /// @dev Suggested gas stipend for contract receiving ETH to perform a few /// storage reads and writes, but low enough to prevent griefing. uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ETH OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants. // // The regular variants: // - Forwards all remaining gas to the target. // - Reverts if the target reverts. // - Reverts if the current contract has insufficient balance. // // The force variants: // - Forwards with an optional gas stipend // (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases). // - If the target reverts, or if the gas stipend is exhausted, // creates a temporary contract to force send the ETH via `SELFDESTRUCT`. // Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758. // - Reverts if the current contract has insufficient balance. // // The try variants: // - Forwards with a mandatory gas stipend. // - Instead of reverting, returns whether the transfer succeeded. /// @dev Sends `amount` (in wei) ETH to `to`. function safeTransferETH(address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } } } /// @dev Sends all the ETH in the current contract to `to`. function safeTransferAllETH(address to) internal { /// @solidity memory-safe-assembly assembly { // Transfer all the ETH and check if it succeeded or not. if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } } } /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`. function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal { /// @solidity memory-safe-assembly assembly { if lt(selfbalance(), amount) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`. function forceSafeTransferAllETH(address to, uint256 gasStipend) internal { /// @solidity memory-safe-assembly assembly { if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`. function forceSafeTransferETH(address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { if lt(selfbalance(), amount) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`. function forceSafeTransferAllETH(address to) internal { /// @solidity memory-safe-assembly assembly { // forgefmt: disable-next-item if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`. function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal returns (bool success) { /// @solidity memory-safe-assembly assembly { success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00) } } /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`. function trySafeTransferAllETH(address to, uint256 gasStipend) internal returns (bool success) { /// @solidity memory-safe-assembly assembly { success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ERC20 OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. /// Reverts upon failure. /// /// The `from` account must have at least `amount` approved for /// the current contract to manage. function safeTransferFrom(address token, address from, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x60, amount) // Store the `amount` argument. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`. // Perform the transfer, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) ) ) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Sends all of ERC20 `token` from `from` to `to`. /// Reverts upon failure. /// /// The `from` account must have their entire balance approved for /// the current contract to manage. function safeTransferAllFrom(address token, address from, address to) internal returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`. // Read the balance, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20) ) ) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`. amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it. // Perform the transfer, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) ) ) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`. /// Reverts upon failure. function safeTransfer(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`. // Perform the transfer, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) ) ) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sends all of ERC20 `token` from the current contract to `to`. /// Reverts upon failure. function safeTransferAll(address token, address to) internal returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`. mstore(0x20, address()) // Store the address of the current contract. // Read the balance, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20) ) ) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } mstore(0x14, to) // Store the `to` argument. amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it. mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`. // Perform the transfer, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) ) ) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. /// Reverts upon failure. function safeApprove(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. // Perform the approval, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) ) ) { mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`. revert(0x1c, 0x04) } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. /// If the initial attempt to approve fails, attempts to reset the approved amount to zero, /// then retries the approval again (some tokens, e.g. USDT, requires this). /// Reverts upon failure. function safeApproveWithRetry(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. // Perform the approval, retrying upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) ) ) { mstore(0x34, 0) // Store 0 for the `amount`. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval. mstore(0x34, amount) // Store back the original `amount`. // Retry the approval, reverting upon failure. if iszero( and( or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) ) ) { mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`. revert(0x1c, 0x04) } } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Returns the amount of ERC20 `token` owned by `account`. /// Returns zero if the `token` does not exist. function balanceOf(address token, address account) internal view returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { mstore(0x14, account) // Store the `account` argument. mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`. amount := mul( mload(0x20), and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20) ) ) } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Simple single owner authorization mixin. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol) /// /// @dev Note: /// This implementation does NOT auto-initialize the owner to `msg.sender`. /// You MUST call the `_initializeOwner` in the constructor / initializer. /// /// While the ownable portion follows /// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility, /// the nomenclature for the 2-step ownership handover may be unique to this codebase. abstract contract Ownable { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The caller is not authorized to call the function. error Unauthorized(); /// @dev The `newOwner` cannot be the zero address. error NewOwnerIsZeroAddress(); /// @dev The `pendingOwner` does not have a valid handover request. error NoHandoverRequest(); /// @dev Cannot double-initialize. error AlreadyInitialized(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EVENTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The ownership is transferred from `oldOwner` to `newOwner`. /// This event is intentionally kept the same as OpenZeppelin's Ownable to be /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173), /// despite it not being as lightweight as a single argument event. event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); /// @dev An ownership handover to `pendingOwner` has been requested. event OwnershipHandoverRequested(address indexed pendingOwner); /// @dev The ownership handover to `pendingOwner` has been canceled. event OwnershipHandoverCanceled(address indexed pendingOwner); /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`. uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE = 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0; /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`. uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE = 0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d; /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`. uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE = 0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The owner slot is given by: /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`. /// It is intentionally chosen to be a high value /// to avoid collision with lower slots. /// The choice of manual storage layout is to enable compatibility /// with both regular and upgradeable contracts. bytes32 internal constant _OWNER_SLOT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927; /// The ownership handover slot of `newOwner` is given by: /// ``` /// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED)) /// let handoverSlot := keccak256(0x00, 0x20) /// ``` /// It stores the expiry timestamp of the two-step ownership handover. uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Override to return true to make `_initializeOwner` prevent double-initialization. function _guardInitializeOwner() internal pure virtual returns (bool guard) {} /// @dev Initializes the owner directly without authorization guard. /// This function must be called upon initialization, /// regardless of whether the contract is upgradeable or not. /// This is to enable generalization to both regular and upgradeable contracts, /// and to save gas in case the initial owner is not the caller. /// For performance reasons, this function will not check if there /// is an existing owner. function _initializeOwner(address newOwner) internal virtual { if (_guardInitializeOwner()) { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT if sload(ownerSlot) { mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`. revert(0x1c, 0x04) } // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Store the new value. sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) } } else { /// @solidity memory-safe-assembly assembly { // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Store the new value. sstore(_OWNER_SLOT, newOwner) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) } } } /// @dev Sets the owner directly without authorization guard. function _setOwner(address newOwner) internal virtual { if (_guardInitializeOwner()) { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) // Store the new value. sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) } } else { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) // Store the new value. sstore(ownerSlot, newOwner) } } } /// @dev Throws if the sender is not the owner. function _checkOwner() internal view virtual { /// @solidity memory-safe-assembly assembly { // If the caller is not the stored owner, revert. if iszero(eq(caller(), sload(_OWNER_SLOT))) { mstore(0x00, 0x82b42900) // `Unauthorized()`. revert(0x1c, 0x04) } } } /// @dev Returns how long a two-step ownership handover is valid for in seconds. /// Override to return a different value if needed. /// Made internal to conserve bytecode. Wrap it in a public function if needed. function _ownershipHandoverValidFor() internal view virtual returns (uint64) { return 48 * 3600; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PUBLIC UPDATE FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Allows the owner to transfer the ownership to `newOwner`. function transferOwnership(address newOwner) public payable virtual onlyOwner { /// @solidity memory-safe-assembly assembly { if iszero(shl(96, newOwner)) { mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`. revert(0x1c, 0x04) } } _setOwner(newOwner); } /// @dev Allows the owner to renounce their ownership. function renounceOwnership() public payable virtual onlyOwner { _setOwner(address(0)); } /// @dev Request a two-step ownership handover to the caller. /// The request will automatically expire in 48 hours (172800 seconds) by default. function requestOwnershipHandover() public payable virtual { unchecked { uint256 expires = block.timestamp + _ownershipHandoverValidFor(); /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to `expires`. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, caller()) sstore(keccak256(0x0c, 0x20), expires) // Emit the {OwnershipHandoverRequested} event. log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller()) } } } /// @dev Cancels the two-step ownership handover to the caller, if any. function cancelOwnershipHandover() public payable virtual { /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to 0. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, caller()) sstore(keccak256(0x0c, 0x20), 0) // Emit the {OwnershipHandoverCanceled} event. log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller()) } } /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`. /// Reverts if there is no existing ownership handover requested by `pendingOwner`. function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner { /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to 0. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, pendingOwner) let handoverSlot := keccak256(0x0c, 0x20) // If the handover does not exist, or has expired. if gt(timestamp(), sload(handoverSlot)) { mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`. revert(0x1c, 0x04) } // Set the handover slot to 0. sstore(handoverSlot, 0) } _setOwner(pendingOwner); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PUBLIC READ FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the owner of the contract. function owner() public view virtual returns (address result) { /// @solidity memory-safe-assembly assembly { result := sload(_OWNER_SLOT) } } /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`. function ownershipHandoverExpiresAt(address pendingOwner) public view virtual returns (uint256 result) { /// @solidity memory-safe-assembly assembly { // Compute the handover slot. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, pendingOwner) // Load the handover slot. result := sload(keccak256(0x0c, 0x20)) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* MODIFIERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Marks a function as only callable by the owner. modifier onlyOwner() virtual { _checkOwner(); _; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Library for bit twiddling and boolean operations. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBit.sol) /// @author Inspired by (https://graphics.stanford.edu/~seander/bithacks.html) library LibBit { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* BIT TWIDDLING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Find last set. /// Returns the index of the most significant bit of `x`, /// counting from the least significant bit position. /// If `x` is zero, returns 256. function fls(uint256 x) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { r := or(shl(8, iszero(x)), shl(7, lt(0xffffffffffffffffffffffffffffffff, x))) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffff, shr(r, x)))) r := or(r, shl(3, lt(0xff, shr(r, x)))) // forgefmt: disable-next-item r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)), 0x0706060506020504060203020504030106050205030304010505030400000000)) } } /// @dev Count leading zeros. /// Returns the number of zeros preceding the most significant one bit. /// If `x` is zero, returns 256. function clz(uint256 x) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffff, shr(r, x)))) r := or(r, shl(3, lt(0xff, shr(r, x)))) // forgefmt: disable-next-item r := add(xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)), 0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff)), iszero(x)) } } /// @dev Find first set. /// Returns the index of the least significant bit of `x`, /// counting from the least significant bit position. /// If `x` is zero, returns 256. /// Equivalent to `ctz` (count trailing zeros), which gives /// the number of zeros following the least significant one bit. function ffs(uint256 x) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { // Isolate the least significant bit. let b := and(x, add(not(x), 1)) r := or(shl(8, iszero(x)), shl(7, lt(0xffffffffffffffffffffffffffffffff, b))) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, b)))) r := or(r, shl(5, lt(0xffffffff, shr(r, b)))) // For the remaining 32 bits, use a De Bruijn lookup. // forgefmt: disable-next-item r := or(r, byte(and(div(0xd76453e0, shr(r, b)), 0x1f), 0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405)) } } /// @dev Returns the number of set bits in `x`. function popCount(uint256 x) internal pure returns (uint256 c) { /// @solidity memory-safe-assembly assembly { let max := not(0) let isMax := eq(x, max) x := sub(x, and(shr(1, x), div(max, 3))) x := add(and(x, div(max, 5)), and(shr(2, x), div(max, 5))) x := and(add(x, shr(4, x)), div(max, 17)) c := or(shl(8, isMax), shr(248, mul(x, div(max, 255)))) } } /// @dev Returns whether `x` is a power of 2. function isPo2(uint256 x) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { // Equivalent to `x && !(x & (x - 1))`. result := iszero(add(and(x, sub(x, 1)), iszero(x))) } } /// @dev Returns `x` reversed at the bit level. function reverseBits(uint256 x) internal pure returns (uint256 r) { uint256 m0 = 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f; uint256 m1 = m0 ^ (m0 << 2); uint256 m2 = m1 ^ (m1 << 1); r = reverseBytes(x); r = (m2 & (r >> 1)) | ((m2 & r) << 1); r = (m1 & (r >> 2)) | ((m1 & r) << 2); r = (m0 & (r >> 4)) | ((m0 & r) << 4); } /// @dev Returns `x` reversed at the byte level. function reverseBytes(uint256 x) internal pure returns (uint256 r) { unchecked { // Computing masks on-the-fly reduces bytecode size by about 200 bytes. uint256 m0 = 0x100000000000000000000000000000001 * (~toUint(x == 0) >> 192); uint256 m1 = m0 ^ (m0 << 32); uint256 m2 = m1 ^ (m1 << 16); uint256 m3 = m2 ^ (m2 << 8); r = (m3 & (x >> 8)) | ((m3 & x) << 8); r = (m2 & (r >> 16)) | ((m2 & r) << 16); r = (m1 & (r >> 32)) | ((m1 & r) << 32); r = (m0 & (r >> 64)) | ((m0 & r) << 64); r = (r >> 128) | (r << 128); } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* BOOLEAN OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // A Solidity bool on the stack or memory is represented as a 256-bit word. // Non-zero values are true, zero is false. // A clean bool is either 0 (false) or 1 (true) under the hood. // Usually, if not always, the bool result of a regular Solidity expression, // or the argument of a public/external function will be a clean bool. // You can usually use the raw variants for more performance. // If uncertain, test (best with exact compiler settings). // Or use the non-raw variants (compiler can sometimes optimize out the double `iszero`s). /// @dev Returns `x & y`. Inputs must be clean. function rawAnd(bool x, bool y) internal pure returns (bool z) { /// @solidity memory-safe-assembly assembly { z := and(x, y) } } /// @dev Returns `x & y`. function and(bool x, bool y) internal pure returns (bool z) { /// @solidity memory-safe-assembly assembly { z := and(iszero(iszero(x)), iszero(iszero(y))) } } /// @dev Returns `x | y`. Inputs must be clean. function rawOr(bool x, bool y) internal pure returns (bool z) { /// @solidity memory-safe-assembly assembly { z := or(x, y) } } /// @dev Returns `x | y`. function or(bool x, bool y) internal pure returns (bool z) { /// @solidity memory-safe-assembly assembly { z := or(iszero(iszero(x)), iszero(iszero(y))) } } /// @dev Returns 1 if `b` is true, else 0. Input must be clean. function rawToUint(bool b) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := b } } /// @dev Returns 1 if `b` is true, else 0. function toUint(bool b) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := iszero(iszero(b)) } } }
{ "remappings": [ "forge-std/=test/utils/forge-std/", "murky/=lib/murky/", "solady/=lib/solady/src/", "@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/", "@openzeppelin/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/", "permit2/=lib/permit2/src/", "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/", "ds-test/=lib/forge-std/lib/ds-test/src/", "erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/", "forge-gas-snapshot/=lib/permit2/lib/forge-gas-snapshot/src/", "forge-std/=lib/forge-std/src/", "murky/=lib/murky/", "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/", "openzeppelin-contracts/=lib/murky/lib/openzeppelin-contracts/", "solady/=lib/solady/src/", "solmate/=lib/permit2/lib/solmate/" ], "optimizer": { "enabled": true, "runs": 1000 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "cancun", "viaIR": false, "libraries": {} }
[{"inputs":[],"stateMutability":"payable","type":"constructor"},{"inputs":[],"name":"AlreadyLinked","type":"error"},{"inputs":[],"name":"CannotLink","type":"error"},{"inputs":[],"name":"NotLinked","type":"error"},{"inputs":[],"name":"Reentrancy","type":"error"},{"inputs":[],"name":"SenderNotBase","type":"error"},{"inputs":[],"name":"SenderNotDeployer","type":"error"},{"inputs":[],"name":"TransferToNonERC721ReceiverImplementer","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnauthorizedCallContext","type":"error"},{"inputs":[],"name":"UpgradeFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"isApproved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"},{"indexed":false,"internalType":"address","name":"bidToken","type":"address"}],"name":"Bid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"maker","type":"address"}],"name":"Bought","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"}],"name":"CancelBid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"}],"name":"CancelOffer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"idX","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"idY","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"exchangeFee","type":"uint256"}],"name":"Exchange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"minPrice","type":"uint256"},{"indexed":false,"internalType":"address","name":"offerToken","type":"address"}],"name":"Offer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bool","name":"lockStatus","type":"bool"}],"name":"UpdateLockState","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"trader","type":"address"}],"internalType":"struct BT404Mirror.NFTOrder[]","name":"orders","type":"tuple[]"}],"name":"acceptBid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"trader","type":"address"}],"internalType":"struct BT404Mirror.NFTOrder[]","name":"orders","type":"tuple[]"}],"name":"acceptOffer","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftOwner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseERC20","outputs":[{"internalType":"address","name":"base","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"trader","type":"address"}],"internalType":"struct BT404Mirror.NFTOrder[]","name":"orders","type":"tuple[]"}],"name":"bidForBuy","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"burn","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"cancelBid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"cancelOffer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"idX","type":"uint256"},{"internalType":"uint256","name":"idY","type":"uint256"}],"name":"exchange","outputs":[{"internalType":"uint256","name":"exchangeFee","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"nftOwner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"result","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"begin","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"lockedIds","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"bool","name":"lock","type":"bool"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"trader","type":"address"}],"internalType":"struct BT404Mirror.NFTOrder[]","name":"orders","type":"tuple[]"}],"name":"offerForSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"begin","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"ownedIds","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"ownerAt","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pullOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"result","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"bool","name":"lock","type":"bool"}],"name":"updateLockState","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]
[ Download: CSV Export ]
A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.