Created
July 22, 2024 13:32
-
-
Save barchef/10a94449a6674f38a31e4b8d3208a7c9 to your computer and use it in GitHub Desktop.
BUIDL404.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.20; | |
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; | |
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | |
import {IERC721Receiver} from "@openzeppelin/contracts/interfaces/IERC721Receiver.sol"; | |
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; | |
import {IERC404} from "./interfaces/IERC404.sol"; | |
import {DoubleEndedQueue} from "./lib/DoubleEndedQueue.sol"; | |
import {ERC721Events} from "./lib/ERC721Events.sol"; | |
import {ERC20Events} from "./lib/ERC20Events.sol"; | |
interface IUniswapV2Factory { | |
function createPair( | |
address tokenA, | |
address tokenB | |
) external returns (address pair); | |
} | |
interface IUniswapV2Router01 { | |
function factory() external pure returns (address); | |
function WETH() external pure returns (address); | |
} | |
contract BUIDL404 is IERC404, Ownable { | |
using DoubleEndedQueue for DoubleEndedQueue.Uint256Deque; | |
/// @dev The queue of ERC-721 tokens stored in the contract. | |
DoubleEndedQueue.Uint256Deque private _storedERC721Ids; | |
/// @dev are early pros on | |
bool public earlyPro = true; | |
uint256 public earlyPro1; | |
uint256 public earlyPro2; | |
/// @dev Active indicator | |
bool public active = false; | |
address public corePair; | |
/// @dev uniswap v2 router | |
address public router = address(0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24); | |
/// @dev block number of opened trading | |
uint256 launchedAt; | |
/// @dev Token name | |
string public name; | |
/// @dev Token symbol | |
string public symbol; | |
/// @dev Decimals for ERC-20 representation | |
uint8 public immutable decimals; | |
/// @dev Units for ERC-20 representation | |
uint256 public immutable units; | |
/// @dev Total supply in ERC-20 representation | |
uint256 public totalSupply; | |
/// @dev Current mint counter which also represents the highest | |
/// minted id, monotonically increasing to ensure accurate ownership | |
uint256 public minted; | |
mapping(address => bool) public isErc20Valid; | |
/// @dev store addresses that a automatic market maker pairs. Any transfer *to* these addresses | |
/// could be subject to a maximum transfer amount | |
mapping(address => bool) public ammPairs; | |
/// @dev Balance of user in ERC-20 representation | |
mapping(address => uint256) public balanceOf; | |
/// @dev Allowance of user in ERC-20 representation | |
mapping(address => mapping(address => uint256)) public allowance; | |
/// @dev Approval in ERC-721 representaion | |
mapping(uint256 => address) public getApproved; | |
/// @dev Approval for all in ERC-721 representation | |
mapping(address => mapping(address => bool)) public isApprovedForAll; | |
/// @dev Packed representation of ownerOf and owned indices | |
mapping(uint256 => uint256) internal _ownedData; | |
/// @dev Array of owned ids in ERC-721 representation | |
mapping(address => uint256[]) internal _owned; | |
/// @dev Addresses that are exempt from ERC-721 transfer, typically for gas savings (pairs, routers, etc) | |
mapping(address => bool) internal _erc721TransferExempt; | |
/// @dev Address bitmask for packed ownership data | |
uint256 private constant _BITMASK_ADDRESS = (1 << 160) - 1; | |
/// @dev Owned index bitmask for packed ownership data | |
uint256 private constant _BITMASK_OWNED_INDEX = ((1 << 96) - 1) << 160; | |
/// @dev Constant for token id encoding | |
uint256 public constant ID_ENCODING_PREFIX = 1 << 255; | |
/// @dev Notify new pair support | |
event SetAMMPair(address indexed pair, bool indexed value); | |
constructor( | |
string memory name_, | |
string memory symbol_, | |
uint8 decimals_ | |
) Ownable(msg.sender) { | |
name = name_; | |
symbol = symbol_; | |
if (decimals_ < 18) { | |
revert DecimalsTooLow(); | |
} | |
decimals = decimals_; | |
units = 21500 * (10 ** uint256(decimals)); // 21500 ERC-20 tokens == 1 ERC-721 token | |
uint256 _totalSupply = 215000000 * 1e18; | |
earlyPro1 = _totalSupply / 1000; // 0.1% | |
earlyPro2 = _totalSupply / 1000; // 0.1% | |
setErc20Valid(owner(), true); | |
setErc20Valid(address(this), true); | |
setErc20Valid(address(0xdead), true); | |
_setERC721TransferExempt(owner(), true); | |
_mintERC20(owner(), _totalSupply); | |
} | |
function createPair() public onlyOwner { | |
IUniswapV2Router01 _v2Router = IUniswapV2Router01(router); | |
corePair = IUniswapV2Factory(_v2Router.factory()).createPair( | |
address(this), | |
_v2Router.WETH() | |
); | |
setErc20Valid(address(router), true); | |
setErc20Valid(address(corePair), true); | |
_setERC721TransferExempt(address(router), true); | |
setAMMPair(address(corePair), true); | |
} | |
function setAMMPair( | |
address pair, | |
bool value | |
) public onlyOwner { | |
require(pair != address(0), "Invalid pair"); | |
ammPairs[pair] = value; | |
_setERC721TransferExempt(address(pair), true); | |
emit SetAMMPair(pair, value); | |
} | |
function removePro() public onlyOwner { | |
earlyPro = false; | |
} | |
function setActive() public onlyOwner { | |
active = true; | |
launchedAt = block.number; | |
} | |
function setErc20Valid(address to_, bool state_) public onlyOwner { | |
isErc20Valid[to_] = state_; | |
} | |
function setPros(uint256 _earlyPro2, uint256 _earlyPro1) public onlyOwner { | |
earlyPro1 = _earlyPro1; | |
earlyPro2 = _earlyPro2; | |
} | |
/// @notice Function to find owner of a given ERC-721 token | |
function ownerOf(uint256 id_) public view returns (address erc721Owner) { | |
erc721Owner = _getOwnerOf(id_); | |
if (!_isValidTokenId(id_)) { | |
revert InvalidTokenId(); | |
} | |
if (erc721Owner == address(0)) { | |
revert NotFound(); | |
} | |
} | |
function owned(address owner_) public view returns (uint256[] memory) { | |
return _owned[owner_]; | |
} | |
function erc721BalanceOf(address owner_) public view returns (uint256) { | |
return _owned[owner_].length; | |
} | |
function erc20BalanceOf(address owner_) public view returns (uint256) { | |
return balanceOf[owner_]; | |
} | |
function erc20TotalSupply() public view returns (uint256) { | |
return totalSupply; | |
} | |
function erc721TotalSupply() public view returns (uint256) { | |
return minted; | |
} | |
function getERC721QueueLength() public view returns (uint256) { | |
return _storedERC721Ids.length(); | |
} | |
function getERC721TokensInQueue( | |
uint256 start_, | |
uint256 count_ | |
) public view returns (uint256[] memory) { | |
uint256[] memory tokensInQueue = new uint256[](count_); | |
for (uint256 i = start_; i < start_ + count_; ) { | |
tokensInQueue[i - start_] = _storedERC721Ids.at(i); | |
unchecked { | |
++i; | |
} | |
} | |
return tokensInQueue; | |
} | |
/// @notice Load erc721 by id | |
function tokenURI(uint256 id_) public pure returns (string memory) { | |
return string.concat("https://starter.xyz/buidl404/", Strings.toString(id_)); | |
} | |
/// @notice Function for token approvals | |
/// @dev This function assumes the operator is attempting to approve | |
/// an ERC-721 if valueOrId_ is a possibly valid ERC-721 token id. | |
/// Unlike setApprovalForAll, spender_ must be allowed to be 0x0 so | |
/// that approval can be revoked. | |
function approve(address spender_, uint256 valueOrId_) public returns (bool) { | |
if (_isValidTokenId(valueOrId_)) { | |
erc721Approve(spender_, valueOrId_); | |
} else { | |
return erc20Approve(spender_, valueOrId_); | |
} | |
return true; | |
} | |
function erc721Approve(address spender_, uint256 id_) public { | |
// Intention is to approve as ERC-721 token (id). | |
address erc721Owner = _getOwnerOf(id_); | |
if ( | |
msg.sender != erc721Owner && !isApprovedForAll[erc721Owner][msg.sender] | |
) { | |
revert Unauthorized(); | |
} | |
getApproved[id_] = spender_; | |
emit ERC721Events.Approval(erc721Owner, spender_, id_); | |
} | |
/// @dev Providing type(uint256).max for approval value results in an | |
/// unlimited approval that is not deducted from on transfers. | |
function erc20Approve( | |
address spender_, | |
uint256 value_ | |
) public returns (bool) { | |
// Prevent granting 0x0 an ERC-20 allowance. | |
if (spender_ == address(0)) { | |
revert InvalidSpender(); | |
} | |
allowance[msg.sender][spender_] = value_; | |
emit ERC20Events.Approval(msg.sender, spender_, value_); | |
return true; | |
} | |
/// @notice Function for ERC-721 approvals | |
function setApprovalForAll(address operator_, bool approved_) public { | |
// Prevent approvals to 0x0. | |
if (operator_ == address(0)) { | |
revert InvalidOperator(); | |
} | |
isApprovedForAll[msg.sender][operator_] = approved_; | |
emit ERC721Events.ApprovalForAll(msg.sender, operator_, approved_); | |
} | |
/// @notice Function for mixed transfers from an operator that may be different than 'from'. | |
/// @dev This function assumes the operator is attempting to transfer an ERC-721 | |
/// if valueOrId is a possible valid token id. | |
function transferFrom( | |
address from_, | |
address to_, | |
uint256 valueOrId_ | |
) public returns (bool) { | |
if (_isValidTokenId(valueOrId_)) { | |
erc721TransferFrom(from_, to_, valueOrId_); | |
} else { | |
// Intention is to transfer as ERC-20 token (value). | |
return erc20TransferFrom(from_, to_, valueOrId_); | |
} | |
return true; | |
} | |
/// @notice Function for ERC-721 transfers from. | |
/// @dev This function is recommended for ERC721 transfers. | |
function erc721TransferFrom(address from_, address to_, uint256 id_) public { | |
// Prevent minting tokens from 0x0. | |
if (from_ == address(0)) { | |
revert InvalidSender(); | |
} | |
// Prevent burning tokens to 0x0. | |
if (to_ == address(0)) { | |
revert InvalidRecipient(); | |
} | |
if (from_ != _getOwnerOf(id_)) { | |
revert Unauthorized(); | |
} | |
// Check that the operator is either the sender or approved for the transfer. | |
if ( | |
msg.sender != from_ && | |
!isApprovedForAll[from_][msg.sender] && | |
msg.sender != getApproved[id_] | |
) { | |
revert Unauthorized(); | |
} | |
// We only need to check ERC-721 transfer exempt status for the recipient | |
// since the sender being ERC-721 transfer exempt means they have already | |
// had their ERC-721s stripped away during the rebalancing process. | |
if (erc721TransferExempt(to_)) { | |
revert RecipientIsERC721TransferExempt(); | |
} | |
// Transfer 1 * units ERC-20 and 1 ERC-721 token. | |
// ERC-721 transfer exemptions handled above. Can't make it to this point if either is transfer exempt. | |
_transferERC20(from_, to_, units); | |
_transferERC721(from_, to_, id_); | |
} | |
/// @notice Function for ERC-20 transfers from. | |
/// @dev This function is recommended for ERC20 transfers | |
function erc20TransferFrom( | |
address from_, | |
address to_, | |
uint256 value_ | |
) public returns (bool) { | |
// Prevent minting tokens from 0x0. | |
if (from_ == address(0)) { | |
revert InvalidSender(); | |
} | |
// Prevent burning tokens to 0x0. | |
if (to_ == address(0)) { | |
revert InvalidRecipient(); | |
} | |
uint256 allowed = allowance[from_][msg.sender]; | |
// Check that the operator has sufficient allowance. | |
if (allowed != type(uint256).max) { | |
if (allowance[from_][msg.sender] < value_) { | |
revert InsufficientAllowance(); | |
} | |
allowance[from_][msg.sender] = allowed - value_; | |
} | |
// Transferring ERC-20s directly requires the _transferERC20WithERC721 function. | |
// Handles ERC-721 exemptions internally. | |
return _transferERC20WithERC721(from_, to_, value_); | |
} | |
/// @notice Function for ERC-20 transfers. | |
/// @dev This function assumes the operator is attempting to transfer as ERC-20 | |
/// given this function is only supported on the ERC-20 interface. | |
/// Treats even large amounts that are valid ERC-721 ids as ERC-20s. | |
function transfer(address to_, uint256 value_) public returns (bool) { | |
// Prevent burning tokens to 0x0. | |
if (to_ == address(0)) { | |
revert InvalidRecipient(); | |
} | |
// Transferring ERC-20s directly requires the _transferERC20WithERC721 function. | |
// Handles ERC-721 exemptions internally. | |
return _transferERC20WithERC721(msg.sender, to_, value_); | |
} | |
/// @notice Function for ERC-721 transfers with contract support. | |
/// This function only supports moving valid ERC-721 ids, as it does not exist on the ERC-20 | |
/// spec and will revert otherwise. | |
function safeTransferFrom(address from_, address to_, uint256 id_) public { | |
safeTransferFrom(from_, to_, id_, ""); | |
} | |
/// @notice Function for ERC-721 transfers with contract support and callback data. | |
/// This function only supports moving valid ERC-721 ids, as it does not exist on the | |
/// ERC-20 spec and will revert otherwise. | |
function safeTransferFrom( | |
address from_, | |
address to_, | |
uint256 id_, | |
bytes memory data_ | |
) public { | |
if (!_isValidTokenId(id_)) { | |
revert InvalidTokenId(); | |
} | |
transferFrom(from_, to_, id_); | |
if ( | |
to_.code.length != 0 && | |
IERC721Receiver(to_).onERC721Received(msg.sender, from_, id_, data_) != | |
IERC721Receiver.onERC721Received.selector | |
) { | |
revert UnsafeRecipient(); | |
} | |
} | |
function supportsInterface(bytes4 interfaceId) public pure returns (bool) { | |
return | |
interfaceId == type(IERC404).interfaceId || | |
interfaceId == type(IERC165).interfaceId; | |
} | |
/// @notice Function for self-exemption by owner | |
function setNonErc721Recepient(address to_, bool state_) public onlyOwner { | |
_setERC721TransferExempt(to_, state_); | |
} | |
/// @notice Function to check if address is transfer exempt | |
function erc721TransferExempt(address target_) public view returns (bool) { | |
return target_ == address(0) || _erc721TransferExempt[target_]; | |
} | |
/// @notice For a token token id to be considered valid, it just needs | |
/// to fall within the range of possible token ids, it does not | |
/// necessarily have to be minted yet. | |
function _isValidTokenId(uint256 id_) internal pure returns (bool) { | |
return id_ > ID_ENCODING_PREFIX && id_ != type(uint256).max; | |
} | |
/// @notice This is the lowest level ERC-20 transfer function, which | |
/// should be used for both normal ERC-20 transfers as well as minting. | |
/// Note that this function allows transfers to and from 0x0. | |
function _transferERC20(address from_, address to_, uint256 value_) private { | |
if (earlyPro) { | |
if ( | |
from_ != owner() && | |
from_ != address(0) && | |
to_ != owner() && | |
to_ != address(0) && | |
to_ != address(0xdead) | |
) { | |
if(!active) { | |
require(isErc20Valid[from_] || isErc20Valid[to_], "1"); | |
} | |
//accepting | |
if (ammPairs[from_] && !isErc20Valid[to_]) { | |
require(value_ <= earlyPro1, "2"); | |
require(value_ + balanceOf[to_] <= earlyPro2, "3"); | |
} | |
//offering | |
else if (ammPairs[to_] && !isErc20Valid[from_]) { | |
require(value_ <= earlyPro1, "4"); | |
} else if (!isErc20Valid[to_]) { | |
require(value_ + balanceOf[to_] <= earlyPro2, "3"); | |
} | |
} | |
} | |
// Minting is a special case for which we should not check the balance of | |
// the sender, and we should increase the total supply. | |
if (from_ == address(0)) { | |
totalSupply += value_; | |
} else { | |
// Deduct value from sender's balance. | |
balanceOf[from_] -= value_; | |
} | |
// Update the recipient's balance. | |
// Can be unchecked because on mint, adding to totalSupply is checked, and on transfer balance deduction is checked. | |
unchecked { | |
balanceOf[to_] += value_; | |
} | |
emit ERC20Events.Transfer(from_, to_, value_); | |
} | |
/// @notice Consolidated record keeping function for transferring ERC-721s. | |
/// @dev Assign the token to the new owner, and remove from the old owner. | |
/// Note that this function allows transfers to and from 0x0. | |
/// Does not handle ERC-721 exemptions. | |
function _transferERC721(address from_, address to_, uint256 id_) private { | |
// If this is not a mint, handle record keeping for transfer from previous owner. | |
if (from_ != address(0)) { | |
// On transfer of an NFT, any previous approval is reset. | |
delete getApproved[id_]; | |
uint256 updatedId = _owned[from_][_owned[from_].length - 1]; | |
if (updatedId != id_) { | |
uint256 updatedIndex = _getOwnedIndex(id_); | |
// update _owned for sender | |
_owned[from_][updatedIndex] = updatedId; | |
// update index for the moved id | |
_setOwnedIndex(updatedId, updatedIndex); | |
} | |
// pop | |
_owned[from_].pop(); | |
} | |
// Check if this is a burn. | |
if (to_ != address(0)) { | |
// If not a burn, update the owner of the token to the new owner. | |
// Update owner of the token to the new owner. | |
_setOwnerOf(id_, to_); | |
// Push token onto the new owner's stack. | |
_owned[to_].push(id_); | |
// Update index for new owner's stack. | |
_setOwnedIndex(id_, _owned[to_].length - 1); | |
} else { | |
// If this is a burn, reset the owner of the token to 0x0 by deleting the token from _ownedData. | |
delete _ownedData[id_]; | |
} | |
emit ERC721Events.Transfer(from_, to_, id_); | |
} | |
/// @notice private function for ERC-20 transfers. Also handles any ERC-721 transfers that may be required. | |
// Handles ERC-721 exemptions. | |
function _transferERC20WithERC721( | |
address from_, | |
address to_, | |
uint256 value_ | |
) private returns (bool) { | |
uint256 erc20BalanceOfSenderBefore = erc20BalanceOf(from_); | |
uint256 erc20BalanceOfReceiverBefore = erc20BalanceOf(to_); | |
_transferERC20(from_, to_, value_); | |
// Preload for gas savings on branches | |
bool isFromERC721TransferExempt = erc721TransferExempt(from_); | |
bool isToERC721TransferExempt = erc721TransferExempt(to_); | |
// Skip _withdrawAndStoreERC721 and/or _retrieveOrMintERC721 for ERC-721 transfer exempt addresses | |
// 1) to save gas | |
// 2) because ERC-721 transfer exempt addresses won't always have/need ERC-721s corresponding to their ERC20s. | |
if (isFromERC721TransferExempt && isToERC721TransferExempt) { | |
// Case 1) Both sender and recipient are ERC-721 transfer exempt. No ERC-721s need to be transferred. | |
// NOOP. | |
} else if (isFromERC721TransferExempt) { | |
// Case 2) The sender is ERC-721 transfer exempt, but the recipient is not. Contract should not attempt | |
// to transfer ERC-721s from the sender, but the recipient should receive ERC-721s | |
// from the bank/minted for any whole number increase in their balance. | |
// Only cares about whole number increments. | |
uint256 tokensToRetrieveOrMint = (balanceOf[to_] / units) - | |
(erc20BalanceOfReceiverBefore / units); | |
for (uint256 i = 0; i < tokensToRetrieveOrMint; ) { | |
_retrieveOrMintERC721(to_); | |
unchecked { | |
++i; | |
} | |
} | |
} else if (isToERC721TransferExempt) { | |
// Case 3) The sender is not ERC-721 transfer exempt, but the recipient is. Contract should attempt | |
// to withdraw and store ERC-721s from the sender, but the recipient should not | |
// receive ERC-721s from the bank/minted. | |
// Only cares about whole number increments. | |
uint256 tokensToWithdrawAndStore = (erc20BalanceOfSenderBefore / units) - | |
(balanceOf[from_] / units); | |
for (uint256 i = 0; i < tokensToWithdrawAndStore; ) { | |
_withdrawAndStoreERC721(from_); | |
unchecked { | |
++i; | |
} | |
} | |
} else { | |
// Case 4) Neither the sender nor the recipient are ERC-721 transfer exempt. | |
// Strategy: | |
// 1. First deal with the whole tokens. These are easy and will just be transferred. | |
// 2. Look at the fractional part of the value: | |
// a) If it causes the sender to lose a whole token that was represented by an NFT due to a | |
// fractional part being transferred, withdraw and store an additional NFT from the sender. | |
// b) If it causes the receiver to gain a whole new token that should be represented by an NFT | |
// due to receiving a fractional part that completes a whole token, retrieve or mint an NFT to the recevier. | |
// Whole tokens worth of ERC-20s get transferred as ERC-721s without any burning/minting. | |
uint256 nftsToTransfer = value_ / units; | |
for (uint256 i = 0; i < nftsToTransfer; ) { | |
// Pop from sender's ERC-721 stack and transfer them (LIFO) | |
uint256 indexOfLastToken = _owned[from_].length - 1; | |
uint256 tokenId = _owned[from_][indexOfLastToken]; | |
_transferERC721(from_, to_, tokenId); | |
unchecked { | |
++i; | |
} | |
} | |
// If the transfer changes either the sender or the recipient's holdings from a fractional to a non-fractional | |
// amount (or vice versa), adjust ERC-721s. | |
// First check if the send causes the sender to lose a whole token that was represented by an ERC-721 | |
// due to a fractional part being transferred. | |
// | |
// Process: | |
// Take the difference between the whole number of tokens before and after the transfer for the sender. | |
// If that difference is greater than the number of ERC-721s transferred (whole units), then there was | |
// an additional ERC-721 lost due to the fractional portion of the transfer. | |
// If this is a self-send and the before and after balances are equal (not always the case but often), | |
// then no ERC-721s will be lost here. | |
if ( | |
erc20BalanceOfSenderBefore / units - erc20BalanceOf(from_) / units > | |
nftsToTransfer | |
) { | |
_withdrawAndStoreERC721(from_); | |
} | |
// Then, check if the transfer causes the receiver to gain a whole new token which requires gaining | |
// an additional ERC-721. | |
// | |
// Process: | |
// Take the difference between the whole number of tokens before and after the transfer for the recipient. | |
// If that difference is greater than the number of ERC-721s transferred (whole units), then there was | |
// an additional ERC-721 gained due to the fractional portion of the transfer. | |
// Again, for self-sends where the before and after balances are equal, no ERC-721s will be gained here. | |
if ( | |
erc20BalanceOf(to_) / units - erc20BalanceOfReceiverBefore / units > | |
nftsToTransfer | |
) { | |
_retrieveOrMintERC721(to_); | |
} | |
} | |
return true; | |
} | |
/// @notice Internal function for ERC20 minting | |
/// @dev This function will allow minting of new ERC20s. | |
/// If mintCorrespondingERC721s_ is true, and the recipient is not ERC-721 exempt, it will | |
/// also mint the corresponding ERC721s. | |
/// Handles ERC-721 exemptions. | |
function _mintERC20(address to_, uint256 value_) private { | |
/// You cannot mint to the zero address (you can't mint and immediately burn in the same transfer). | |
if (to_ == address(0)) { | |
revert InvalidRecipient(); | |
} | |
if (totalSupply + value_ > ID_ENCODING_PREFIX) { | |
revert MintLimitReached(); | |
} | |
_transferERC20WithERC721(address(0), to_, value_); | |
} | |
/// @notice Internal function for ERC-721 minting and retrieval from the bank. | |
/// @dev This function will allow minting of new ERC-721s up to the total fractional supply. It will | |
/// first try to pull from the bank, and if the bank is empty, it will mint a new token. | |
/// Does not handle ERC-721 exemptions. | |
function _retrieveOrMintERC721(address to_) private { | |
if (to_ == address(0)) { | |
revert InvalidRecipient(); | |
} | |
uint256 id; | |
if (!_storedERC721Ids.empty()) { | |
// If there are any tokens in the bank, use those first. | |
// Pop off the end of the queue (FIFO). | |
id = _storedERC721Ids.popBack(); | |
} else { | |
// Otherwise, mint a new token, should not be able to go over the total fractional supply. | |
++minted; | |
// Reserve max uint256 for approvals | |
if (minted == type(uint256).max) { | |
revert MintLimitReached(); | |
} | |
id = ID_ENCODING_PREFIX + minted; | |
} | |
address erc721Owner = _getOwnerOf(id); | |
// The token should not already belong to anyone besides 0x0 or this contract. | |
// If it does, something is wrong, as this should never happen. | |
if (erc721Owner != address(0)) { | |
revert AlreadyExists(); | |
} | |
// Transfer the token to the recipient, either transferring from the contract's bank or minting. | |
// Does not handle ERC-721 exemptions. | |
_transferERC721(erc721Owner, to_, id); | |
} | |
/// @notice private function for ERC-721 deposits to bank (this contract). | |
/// @dev This function will allow depositing of ERC-721s to the bank, which can be retrieved by future minters. | |
// Does not handle ERC-721 exemptions. | |
function _withdrawAndStoreERC721(address from_) private { | |
if (from_ == address(0)) { | |
revert InvalidSender(); | |
} | |
// Retrieve the latest token added to the owner's stack (LIFO). | |
uint256 id = _owned[from_][_owned[from_].length - 1]; | |
// Transfer to 0x0. | |
// Does not handle ERC-721 exemptions. | |
_transferERC721(from_, address(0), id); | |
// Record the token in the contract's bank queue. | |
_storedERC721Ids.pushFront(id); | |
} | |
/// @notice Initialization function to set pairs / etc, saving gas by avoiding mint / burn on unnecessary targets | |
function _setERC721TransferExempt(address target_, bool state_) private { | |
if (target_ == address(0)) { | |
revert InvalidExemption(); | |
} | |
// Adjust the ERC721 balances of the target to respect exemption rules. | |
// Despite this logic, it is still recommended practice to exempt prior to the target | |
// having an active balance. | |
if (state_) { | |
_clearERC721Balance(target_); | |
} else { | |
_reinstateERC721Balance(target_); | |
} | |
_erc721TransferExempt[target_] = state_; | |
} | |
/// @notice Function to reinstate balance on exemption removal | |
function _reinstateERC721Balance(address target_) private { | |
uint256 expectedERC721Balance = erc20BalanceOf(target_) / units; | |
uint256 actualERC721Balance = erc721BalanceOf(target_); | |
for (uint256 i = 0; i < expectedERC721Balance - actualERC721Balance; ) { | |
// Transfer ERC721 balance in from pool | |
_retrieveOrMintERC721(target_); | |
unchecked { | |
++i; | |
} | |
} | |
} | |
/// @notice Function to clear balance on exemption inclusion | |
function _clearERC721Balance(address target_) private { | |
uint256 erc721Balance = erc721BalanceOf(target_); | |
for (uint256 i = 0; i < erc721Balance; ) { | |
// Transfer out ERC721 balance | |
_withdrawAndStoreERC721(target_); | |
unchecked { | |
++i; | |
} | |
} | |
} | |
function _getOwnerOf(uint256 id_) internal view returns (address ownerOf_) { | |
uint256 data = _ownedData[id_]; | |
assembly { | |
ownerOf_ := and(data, _BITMASK_ADDRESS) | |
} | |
} | |
function _setOwnerOf(uint256 id_, address owner_) private { | |
uint256 data = _ownedData[id_]; | |
assembly { | |
data := add( | |
and(data, _BITMASK_OWNED_INDEX), | |
and(owner_, _BITMASK_ADDRESS) | |
) | |
} | |
_ownedData[id_] = data; | |
} | |
function _getOwnedIndex( | |
uint256 id_ | |
) internal view returns (uint256 ownedIndex_) { | |
uint256 data = _ownedData[id_]; | |
assembly { | |
ownedIndex_ := shr(160, data) | |
} | |
} | |
function _setOwnedIndex(uint256 id_, uint256 index_) private { | |
uint256 data = _ownedData[id_]; | |
if (index_ > _BITMASK_OWNED_INDEX >> 160) { | |
revert OwnedIndexOverflow(); | |
} | |
assembly { | |
data := add( | |
and(data, _BITMASK_ADDRESS), | |
and(shl(160, index_), _BITMASK_OWNED_INDEX) | |
) | |
} | |
_ownedData[id_] = data; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment