ERC-20
Overview
Max Total Supply
30,198,804.199673522626425524 DNNER
Holders
247
Market
Onchain Market Cap
$0.00
Circulating Supply Market Cap
-
Other Info
Token Contract (WITH 18 Decimals)
Balance
62.213405680435303021 DNNERValue
$0.00Loading...
Loading
Loading...
Loading
Loading...
Loading
# | Exchange | Pair | Price | 24H Volume | % Volume |
---|
Contract Source Code Verified (Exact Match)
Contract Name:
Dnner
Compiler Version
v0.8.24+commit.e11b9ed9
Optimization Enabled:
Yes with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// @dev Dnner Note: // - Dnner(DN404ND) does not possess the fragmentability, nor does // it have the feature of NFT transactions without the // need for a third party, characteristic of ERC404/DN404. // // DN404 can easily create efficient hybrid tokens and // define the ratio between NFT and tokens. // We aim to leverage the foundation of DN404 to enable // NFT to drive the increase in token value. // // Another way of putting it is to avoid turning NFTs // into purely tokenized assets. // // This is an experimental project not conforming to // the 404 standard, DYOR and read more from our website. // Twitter: https://twitter.com/DN404ND // Website: https://dnner.shop // // SPX-License-Identifier: MIT pragma solidity ^0.8.4; import "../DN404ND.sol"; import "../DN404Mirror.sol"; import "solady/utils/ReentrancyGuard.sol"; import {ECDSA} from "solady/utils/ECDSA.sol"; import {Base64} from "solady/utils/Base64.sol"; import {Ownable} from "solady/auth/Ownable.sol"; import {LibString} from "solady/utils/LibString.sol"; import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol"; contract Dnner is DN404ND, Ownable, ReentrancyGuard { string private _name; string private _symbol; string private _baseURI; uint256 private constant BURN_REWARD_RATIO = 40000; uint256 private constant BURNT_ALLOC_RATIO = 20000; uint256 public initialNFTValue = _NFTUnit(); uint256 public maxNFTSupply = 10000; uint256 public maxMint = 9500; uint256 public price = 0.005 ether; uint256 public maxBuy = 60; bool public openSetSkipNFT = false; bool public openNextStage = false; bool public openUnlock = false; bool public openBuy = true; address signer; address team; /* ------------------ */ /* Custom Event */ /* ------------------ */ event Burn(uint256 amount, uint256 reward, uint256 fee); event Buy(uint256 amount); /* ------------------ */ /* Custom Error */ /* ------------------ */ error InvalidSender(); error InvalidSignature(); error ReachedMaxBuy(); error InvalidOperation(); modifier onlyEOA() { if (msg.sender != tx.origin) { revert InvalidSender(); } _; } modifier onlyHolder(address from) { if (msg.sender != from) { revert InvalidSender(); } _; } /* ------------------ */ /* Constructor */ /* ------------------ */ constructor() { _name = "Dnner"; _symbol = "DNNER"; _initializeOwner(msg.sender); _setMaxNFTSupply(maxNFTSupply); _setSkipNFT(address(this), true); setTeamWallet(msg.sender); setSigner(0x59d5Fbf6297F58D0E6e29edDa4457e860Ac3935a); address mirror = address(new DN404Mirror(msg.sender)); _initializeDN404( uint96(maxNFTSupply * 19 * _unit()), msg.sender, mirror ); } /* ------------------ */ /* Buy */ /* ------------------ */ function buyDnner(uint256 amount, bytes calldata signature) public payable onlyEOA { uint256 payAmount = amount - 1; uint256 mintAmount = amount > 1 ? amount > 10 ? 10 : amount : 1; if (openBuy == false) { revert InvalidOperation(); } if (addressAlias(msg.sender) > 0) { revert InvalidOperation(); } if (amount > maxBuy) { revert ReachedMaxBuy(); } if (amount > 1 && (price * payAmount) > msg.value) { revert InsufficientBalance(); } if (_totalNFTSupply() + amount > maxMint) { revert TotalSupplyOverflow(); } if (!verify(signature)) { revert InvalidSignature(); } _mint(msg.sender, mintAmount * _NFTUnit()); emit Buy(amount); } function verify(bytes calldata s) private view returns (bool) { bytes32 hash = ECDSA.toEthSignedMessageHash( keccak256(abi.encodePacked(msg.sender)) ); address a = ECDSA.recover(hash, s); return signer == a; } /* ------------------ */ /* Wrap */ /* ------------------ */ // Wrap tokens to NFT, will add `initialNFTValue` token to reward pool. function wrap(address from, uint256 amount) public onlyHolder(from) onlyEOA { uint256 wrapCost = amount * initialNFTValue; if (openNextStage == false) revert InvalidOperation(); if (_totalNFTSupply() + amount > _getMaxNFTSupply()) revert TotalSupplyOverflow(); if (balanceOf(msg.sender) < wrapCost) revert InsufficientBalance(); _mint(msg.sender, amount * _NFTUnit()); _transfer(from, address(this), wrapCost); } /* ------------------ */ /* Burn */ /* ------------------ */ // Burn amount of NFT, the fee which will back to reward pool. function eat(address from, uint256 amount) public nonReentrant onlyHolder(from) onlyEOA { uint256 fees = initialNFTValue * amount; if (openNextStage == false) revert InvalidOperation(); if (_balanceOfNFT(msg.sender) < amount) revert InsufficientBalance(); if (balanceOf(msg.sender) < fees) revert InsufficientBalance(); uint256 totalReward = 0; uint256 lastSupply = _totalNFTSupply(); for (uint256 i = 0; i < amount; i++) { totalReward += burnReward(); unchecked { lastSupply--; } initialNFTValue += ((initialNFTValue * BURN_REWARD_RATIO * BURNT_ALLOC_RATIO) / lastSupply) / 10**8; } _burn(msg.sender, amount * _NFTUnit(), true); _transfer(address(this), msg.sender, totalReward - fees); emit Burn(amount, totalReward, fees); } function burnReward() public view returns (uint256) { return (initialNFTValue * BURN_REWARD_RATIO) / 10000; } /* ------------------ */ /* Unlock */ /* ------------------ */ function unlock(address from) public nonReentrant onlyHolder(from) onlyEOA { if (openUnlock == false) revert InvalidOperation(); _unlock(from); } function lockedBalance(address from) public view returns (uint256) { return _getDN404Storage().addressData[from].lockedBalance; } /* ------------------ */ /* DEV */ /* ------------------ */ function reserve(uint256 amount) public { if (msg.sender != team) { revert InvalidSender(); } if (_totalNFTSupply() + amount > _getMaxNFTSupply()) { revert TotalSupplyOverflow(); } _mint(msg.sender, amount * _NFTUnit()); } function setBaseURI(string calldata baseURI_) public onlyOwner { _baseURI = baseURI_; } function setPrices(uint120 price_) public onlyOwner { price = price_; } function setMaxBuy(uint256 maxBuy_) public onlyOwner { maxBuy = maxBuy_; } function setMaxNFTSupply(uint256 value) public onlyOwner { _setMaxNFTSupply(value); } function setTeamWallet(address a) public onlyOwner { team = a; } function setSigner(address a) public onlyOwner { signer = a; } function toggleOpenSetSkipNFT() public onlyOwner { openSetSkipNFT = !openSetSkipNFT; } function toggleOpenUnlock() public onlyOwner { openUnlock = !openUnlock; } function toggleOpenNextStage() public onlyOwner { openNextStage = !openNextStage; } function toggleOpenBuy() public onlyOwner { openBuy = !openBuy; } function withdraw() public onlyOwner nonReentrant { SafeTransferLib.safeTransferAllETH(team); } function name() public view override returns (string memory) { return _name; } function symbol() public view override returns (string memory) { return _symbol; } function _unit() internal view override returns (uint256) { return initialNFTValue; } function tokenURI(uint256 tokenId) public view override returns (string memory result) { if (bytes(_baseURI).length != 0) { result = string( abi.encodePacked(_baseURI, LibString.toString(tokenId)) ); } } function getTokensOfOwner(address a) public view returns (uint256[] memory) { return _tokensOfWithChecks(a); } function getNFTSupply() public view returns (uint256) { return _totalNFTSupply(); } function lastNFTUnit(uint256 id) public view returns (uint256) { return _get(_getDN404Storage().NFTUnit, id); } function addressAlias(address a) public view returns (uint32) { return _getDN404Storage().addressData[a].addressAlias; } /* ---------------------- */ /* Override Operation */ /* ---------------------- */ // setSkipNFT lokced before all NFT minted. function setSkipNFT(bool skip) public override returns (bool) { if (openSetSkipNFT == false) revert InvalidOperation(); _setSkipNFT(msg.sender, skip); return true; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// /// @dev Note: /// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection. /// - For ERC20s, this implementation won't check that a token has code, /// responsibility is delegated to the caller. library SafeTransferLib { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The ETH transfer has failed. error ETHTransferFailed(); /// @dev The ERC20 `transferFrom` has failed. error TransferFromFailed(); /// @dev The ERC20 `transfer` has failed. error TransferFailed(); /// @dev The ERC20 `approve` has failed. error ApproveFailed(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes. uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300; /// @dev Suggested gas stipend for contract receiving ETH to perform a few /// storage reads and writes, but low enough to prevent griefing. uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ETH OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants. // // The regular variants: // - Forwards all remaining gas to the target. // - Reverts if the target reverts. // - Reverts if the current contract has insufficient balance. // // The force variants: // - Forwards with an optional gas stipend // (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases). // - If the target reverts, or if the gas stipend is exhausted, // creates a temporary contract to force send the ETH via `SELFDESTRUCT`. // Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758. // - Reverts if the current contract has insufficient balance. // // The try variants: // - Forwards with a mandatory gas stipend. // - Instead of reverting, returns whether the transfer succeeded. /// @dev Sends `amount` (in wei) ETH to `to`. function safeTransferETH(address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } } } /// @dev Sends all the ETH in the current contract to `to`. function safeTransferAllETH(address to) internal { /// @solidity memory-safe-assembly assembly { // Transfer all the ETH and check if it succeeded or not. if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } } } /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`. function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal { /// @solidity memory-safe-assembly assembly { if lt(selfbalance(), amount) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`. function forceSafeTransferAllETH(address to, uint256 gasStipend) internal { /// @solidity memory-safe-assembly assembly { if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`. function forceSafeTransferETH(address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { if lt(selfbalance(), amount) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`. function forceSafeTransferAllETH(address to) internal { /// @solidity memory-safe-assembly assembly { // forgefmt: disable-next-item if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`. function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal returns (bool success) { /// @solidity memory-safe-assembly assembly { success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00) } } /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`. function trySafeTransferAllETH(address to, uint256 gasStipend) internal returns (bool success) { /// @solidity memory-safe-assembly assembly { success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ERC20 OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. /// Reverts upon failure. /// /// The `from` account must have at least `amount` approved for /// the current contract to manage. function safeTransferFrom(address token, address from, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x60, amount) // Store the `amount` argument. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`. // Perform the transfer, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) ) ) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Sends all of ERC20 `token` from `from` to `to`. /// Reverts upon failure. /// /// The `from` account must have their entire balance approved for /// the current contract to manage. function safeTransferAllFrom(address token, address from, address to) internal returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`. // Read the balance, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20) ) ) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`. amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it. // Perform the transfer, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) ) ) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`. /// Reverts upon failure. function safeTransfer(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`. // Perform the transfer, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) ) ) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sends all of ERC20 `token` from the current contract to `to`. /// Reverts upon failure. function safeTransferAll(address token, address to) internal returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`. mstore(0x20, address()) // Store the address of the current contract. // Read the balance, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20) ) ) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } mstore(0x14, to) // Store the `to` argument. amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it. mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`. // Perform the transfer, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) ) ) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. /// Reverts upon failure. function safeApprove(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. // Perform the approval, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) ) ) { mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`. revert(0x1c, 0x04) } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. /// If the initial attempt to approve fails, attempts to reset the approved amount to zero, /// then retries the approval again (some tokens, e.g. USDT, requires this). /// Reverts upon failure. function safeApproveWithRetry(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. // Perform the approval, retrying upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) ) ) { mstore(0x34, 0) // Store 0 for the `amount`. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval. mstore(0x34, amount) // Store back the original `amount`. // Retry the approval, reverting upon failure. if iszero( and( or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) ) ) { mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`. revert(0x1c, 0x04) } } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Returns the amount of ERC20 `token` owned by `account`. /// Returns zero if the `token` does not exist. function balanceOf(address token, address account) internal view returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { mstore(0x14, account) // Store the `account` argument. mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`. amount := mul( mload(0x20), and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20) ) ) } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice 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. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HEXADECIMAL OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the hexadecimal representation of `value`, /// left-padded to an input length of `length` bytes. /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, /// giving a total length of `length * 2 + 2` bytes. /// Reverts if `length` is too small for the output to contain all the digits. function toHexString(uint256 value, uint256 length) internal pure returns (string memory str) { str = toHexStringNoPrefix(value, length); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hexadecimal representation of `value`, /// left-padded to an input length of `length` bytes. /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, /// giving a total length of `length * 2` bytes. /// Reverts if `length` is too small for the output to contain all the digits. function toHexStringNoPrefix(uint256 value, uint256 length) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { // We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes // for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length. // We add 0x20 to the total and round down to a multiple of 0x20. // (0x20 + 0x20 + 0x02 + 0x20) = 0x62. str := add(mload(0x40), and(add(shl(1, length), 0x42), not(0x1f))) // Allocate the memory. mstore(0x40, add(str, 0x20)) // Zeroize the slot after the string. mstore(str, 0) // Cache the end to calculate the length later. let end := str // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) let start := sub(str, add(length, length)) let w := not(1) // Tsk. let temp := value // 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 {} 1 {} { str := add(str, w) // `sub(str, 2)`. mstore8(add(str, 1), mload(and(temp, 15))) mstore8(str, mload(and(shr(4, temp), 15))) temp := shr(8, temp) if iszero(xor(str, start)) { break } } if temp { mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`. revert(0x1c, 0x04) } // Compute the string's length. let strLength := sub(end, str) // Move the pointer and write the length. str := sub(str, 0x20) mstore(str, strLength) } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. /// As address are 20 bytes long, the output will left-padded to have /// a length of `20 * 2 + 2` bytes. function toHexString(uint256 value) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x". /// The output excludes leading "0" from the `toHexString` output. /// `0x00: "0x0", 0x01: "0x1", 0x12: "0x12", 0x123: "0x123"`. function toMinimalHexString(uint256 value) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present. let strLength := add(mload(str), 2) // Compute the length. mstore(add(str, o), 0x3078) // Write the "0x" prefix, accounting for leading zero. str := sub(add(str, o), 2) // Move the pointer, accounting for leading zero. mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero. } } /// @dev Returns the hexadecimal representation of `value`. /// The output excludes leading "0" from the `toHexStringNoPrefix` output. /// `0x00: "0", 0x01: "1", 0x12: "12", 0x123: "123"`. function toMinimalHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present. let strLength := mload(str) // Get the length. str := add(str, o) // Move the pointer, accounting for leading zero. mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero. } } /// @dev Returns the hexadecimal representation of `value`. /// The output is encoded using 2 hexadecimal digits per byte. /// As address are 20 bytes long, the output will left-padded to have /// a length of `20 * 2` bytes. function toHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, // 0x02 bytes for the prefix, and 0x40 bytes for the digits. // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0. str := add(mload(0x40), 0x80) // Allocate the memory. mstore(0x40, add(str, 0x20)) // Zeroize the slot after the string. mstore(str, 0) // Cache the end to calculate the length later. let end := str // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) let w := not(1) // 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, 2)`. mstore8(add(str, 1), mload(and(temp, 15))) mstore8(str, mload(and(shr(4, temp), 15))) temp := shr(8, temp) if iszero(temp) { break } } // Compute the string's length. let strLength := sub(end, str) // Move the pointer and write the length. str := sub(str, 0x20) mstore(str, strLength) } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte, /// and the alphabets are capitalized conditionally according to /// https://eips.ethereum.org/EIPS/eip-55 function toHexStringChecksummed(address value) internal pure returns (string memory str) { str = toHexString(value); /// @solidity memory-safe-assembly assembly { let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...` let o := add(str, 0x22) let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... ` let t := shl(240, 136) // `0b10001000 << 240` for { let i := 0 } 1 {} { mstore(add(i, i), mul(t, byte(i, hashed))) i := add(i, 1) if eq(i, 20) { break } } mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask))))) o := add(o, 0x20) mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask))))) } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. function toHexString(address value) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hexadecimal representation of `value`. /// The output is encoded using 2 hexadecimal digits per byte. function toHexStringNoPrefix(address value) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { str := mload(0x40) // Allocate the memory. // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, // 0x02 bytes for the prefix, and 0x28 bytes for the digits. // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80. mstore(0x40, add(str, 0x80)) // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) str := add(str, 2) mstore(str, 40) let o := add(str, 0x20) mstore(add(o, 40), 0) value := shl(96, value) // 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 i := 0 } 1 {} { let p := add(o, add(i, i)) let temp := byte(i, value) mstore8(add(p, 1), mload(and(temp, 15))) mstore8(p, mload(shr(4, temp))) i := add(i, 1) if eq(i, 20) { break } } } } /// @dev Returns the hex encoded string from the raw bytes. /// The output is encoded using 2 hexadecimal digits per byte. function toHexString(bytes memory raw) internal pure returns (string memory str) { str = toHexStringNoPrefix(raw); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hex encoded string from the raw bytes. /// The output is encoded using 2 hexadecimal digits per byte. function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { let length := mload(raw) str := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix. mstore(str, add(length, length)) // Store the length of the output. // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) let o := add(str, 0x20) let end := add(raw, length) for {} iszero(eq(raw, end)) {} { raw := add(raw, 1) mstore8(add(o, 1), mload(and(mload(raw), 15))) mstore8(o, mload(and(shr(4, mload(raw)), 15))) o := add(o, 2) } mstore(o, 0) // Zeroize the slot after the string. mstore(0x40, add(o, 0x20)) // Allocate the memory. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* RUNE STRING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the number of UTF characters in the string. function runeCount(string memory s) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { if mload(s) { mstore(0x00, div(not(0), 255)) mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506) let o := add(s, 0x20) let end := add(o, mload(s)) for { result := 1 } 1 { result := add(result, 1) } { o := add(o, byte(0, mload(shr(250, mload(o))))) if iszero(lt(o, end)) { break } } } } } /// @dev Returns if this string is a 7-bit ASCII string. /// (i.e. all characters codes are in [0..127]) function is7BitASCII(string memory s) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { let mask := shl(7, div(not(0), 255)) result := 1 let n := mload(s) if n { let o := add(s, 0x20) let end := add(o, n) let last := mload(end) mstore(end, 0) for {} 1 {} { if and(mask, mload(o)) { result := 0 break } o := add(o, 0x20) if iszero(lt(o, end)) { break } } mstore(end, last) } } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* BYTE STRING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // For performance and bytecode compactness, byte string operations are restricted // to 7-bit ASCII strings. All offsets are byte offsets, not UTF character offsets. // Usage of byte string operations on charsets with runes spanning two or more bytes // can lead to undefined behavior. /// @dev Returns `subject` all occurrences of `search` replaced with `replacement`. function replace(string memory subject, string memory search, string memory replacement) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) let searchLength := mload(search) let replacementLength := mload(replacement) subject := add(subject, 0x20) search := add(search, 0x20) replacement := add(replacement, 0x20) result := add(mload(0x40), 0x20) let subjectEnd := add(subject, subjectLength) if iszero(gt(searchLength, subjectLength)) { let subjectSearchEnd := add(sub(subjectEnd, searchLength), 1) let h := 0 if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) } let m := shl(3, sub(0x20, and(searchLength, 0x1f))) let s := mload(search) for {} 1 {} { let t := mload(subject) // Whether the first `searchLength % 32` bytes of // `subject` and `search` matches. if iszero(shr(m, xor(t, s))) { if h { if iszero(eq(keccak256(subject, searchLength), h)) { mstore(result, t) result := add(result, 1) subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } continue } } // Copy the `replacement` one word at a time. for { let o := 0 } 1 {} { mstore(add(result, o), mload(add(replacement, o))) o := add(o, 0x20) if iszero(lt(o, replacementLength)) { break } } result := add(result, replacementLength) subject := add(subject, searchLength) if searchLength { if iszero(lt(subject, subjectSearchEnd)) { break } continue } } mstore(result, t) result := add(result, 1) subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } } } let resultRemainder := result result := add(mload(0x40), 0x20) let k := add(sub(resultRemainder, result), sub(subjectEnd, subject)) // Copy the rest of the string one word at a time. for {} lt(subject, subjectEnd) {} { mstore(resultRemainder, mload(subject)) resultRemainder := add(resultRemainder, 0x20) subject := add(subject, 0x20) } result := sub(result, 0x20) let last := add(add(result, 0x20), k) // Zeroize the slot after the string. mstore(last, 0) mstore(0x40, add(last, 0x20)) // Allocate the memory. mstore(result, k) // Store the length. } } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from left to right, starting from `from`. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function indexOf(string memory subject, string memory search, uint256 from) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { for { let subjectLength := mload(subject) } 1 {} { if iszero(mload(search)) { if iszero(gt(from, subjectLength)) { result := from break } result := subjectLength break } let searchLength := mload(search) let subjectStart := add(subject, 0x20) result := not(0) // Initialize to `NOT_FOUND`. subject := add(subjectStart, from) let end := add(sub(add(subjectStart, subjectLength), searchLength), 1) let m := shl(3, sub(0x20, and(searchLength, 0x1f))) let s := mload(add(search, 0x20)) if iszero(and(lt(subject, end), lt(from, subjectLength))) { break } if iszero(lt(searchLength, 0x20)) { for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { if iszero(shr(m, xor(mload(subject), s))) { if eq(keccak256(subject, searchLength), h) { result := sub(subject, subjectStart) break } } subject := add(subject, 1) if iszero(lt(subject, end)) { break } } break } for {} 1 {} { if iszero(shr(m, xor(mload(subject), s))) { result := sub(subject, subjectStart) break } subject := add(subject, 1) if iszero(lt(subject, end)) { break } } break } } } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from left to right. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function indexOf(string memory subject, string memory search) internal pure returns (uint256 result) { result = indexOf(subject, search, 0); } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from right to left, starting from `from`. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function lastIndexOf(string memory subject, string memory search, uint256 from) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { for {} 1 {} { result := not(0) // Initialize to `NOT_FOUND`. let searchLength := mload(search) if gt(searchLength, mload(subject)) { break } let w := result let fromMax := sub(mload(subject), searchLength) if iszero(gt(fromMax, from)) { from := fromMax } let end := add(add(subject, 0x20), w) subject := add(add(subject, 0x20), from) if iszero(gt(subject, end)) { break } // As this function is not too often used, // we shall simply use keccak256 for smaller bytecode size. for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { if eq(keccak256(subject, searchLength), h) { result := sub(subject, add(end, 1)) break } subject := add(subject, w) // `sub(subject, 1)`. if iszero(gt(subject, end)) { break } } break } } } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from right to left. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function lastIndexOf(string memory subject, string memory search) internal pure returns (uint256 result) { result = lastIndexOf(subject, search, uint256(int256(-1))); } /// @dev Returns true if `search` is found in `subject`, false otherwise. function contains(string memory subject, string memory search) internal pure returns (bool) { return indexOf(subject, search) != NOT_FOUND; } /// @dev Returns whether `subject` starts with `search`. function startsWith(string memory subject, string memory search) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { let searchLength := mload(search) // Just using keccak256 directly is actually cheaper. // forgefmt: disable-next-item result := and( iszero(gt(searchLength, mload(subject))), eq( keccak256(add(subject, 0x20), searchLength), keccak256(add(search, 0x20), searchLength) ) ) } } /// @dev Returns whether `subject` ends with `search`. function endsWith(string memory subject, string memory search) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { let searchLength := mload(search) let subjectLength := mload(subject) // Whether `search` is not longer than `subject`. let withinRange := iszero(gt(searchLength, subjectLength)) // Just using keccak256 directly is actually cheaper. // forgefmt: disable-next-item result := and( withinRange, eq( keccak256( // `subject + 0x20 + max(subjectLength - searchLength, 0)`. add(add(subject, 0x20), mul(withinRange, sub(subjectLength, searchLength))), searchLength ), keccak256(add(search, 0x20), searchLength) ) ) } } /// @dev Returns `subject` repeated `times`. function repeat(string memory subject, uint256 times) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) if iszero(or(iszero(times), iszero(subjectLength))) { subject := add(subject, 0x20) result := mload(0x40) let output := add(result, 0x20) for {} 1 {} { // Copy the `subject` one word at a time. for { let o := 0 } 1 {} { mstore(add(output, o), mload(add(subject, o))) o := add(o, 0x20) if iszero(lt(o, subjectLength)) { break } } output := add(output, subjectLength) times := sub(times, 1) if iszero(times) { break } } mstore(output, 0) // Zeroize the slot after the string. let resultLength := sub(output, add(result, 0x20)) mstore(result, resultLength) // Store the length. // Allocate the memory. mstore(0x40, add(result, add(resultLength, 0x20))) } } } /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). /// `start` and `end` are byte offsets. function slice(string memory subject, uint256 start, uint256 end) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) if iszero(gt(subjectLength, end)) { end := subjectLength } if iszero(gt(subjectLength, start)) { start := subjectLength } if lt(start, end) { result := mload(0x40) let resultLength := sub(end, start) mstore(result, resultLength) subject := add(subject, start) let w := not(0x1f) // Copy the `subject` one word at a time, backwards. for { let o := and(add(resultLength, 0x1f), w) } 1 {} { mstore(add(result, o), mload(add(subject, o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } // Zeroize the slot after the string. mstore(add(add(result, 0x20), resultLength), 0) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, add(result, and(add(resultLength, 0x3f), w))) } } } /// @dev Returns a copy of `subject` sliced from `start` to the end of the string. /// `start` is a byte offset. function slice(string memory subject, uint256 start) internal pure returns (string memory result) { result = slice(subject, start, uint256(int256(-1))); } /// @dev Returns all the indices of `search` in `subject`. /// The indices are byte offsets. function indicesOf(string memory subject, string memory search) internal pure returns (uint256[] memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) let searchLength := mload(search) if iszero(gt(searchLength, subjectLength)) { subject := add(subject, 0x20) search := add(search, 0x20) result := add(mload(0x40), 0x20) let subjectStart := subject let subjectSearchEnd := add(sub(add(subject, subjectLength), searchLength), 1) let h := 0 if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) } let m := shl(3, sub(0x20, and(searchLength, 0x1f))) let s := mload(search) for {} 1 {} { let t := mload(subject) // Whether the first `searchLength % 32` bytes of // `subject` and `search` matches. if iszero(shr(m, xor(t, s))) { if h { if iszero(eq(keccak256(subject, searchLength), h)) { subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } continue } } // Append to `result`. mstore(result, sub(subject, subjectStart)) result := add(result, 0x20) // Advance `subject` by `searchLength`. subject := add(subject, searchLength) if searchLength { if iszero(lt(subject, subjectSearchEnd)) { break } continue } } subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } } let resultEnd := result // Assign `result` to the free memory pointer. result := mload(0x40) // Store the length of `result`. mstore(result, shr(5, sub(resultEnd, add(result, 0x20)))) // Allocate memory for result. // We allocate one more word, so this array can be recycled for {split}. mstore(0x40, add(resultEnd, 0x20)) } } } /// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string. function split(string memory subject, string memory delimiter) internal pure returns (string[] memory result) { uint256[] memory indices = indicesOf(subject, delimiter); /// @solidity memory-safe-assembly assembly { let w := not(0x1f) let indexPtr := add(indices, 0x20) let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1))) mstore(add(indicesEnd, w), mload(subject)) mstore(indices, add(mload(indices), 1)) let prevIndex := 0 for {} 1 {} { let index := mload(indexPtr) mstore(indexPtr, 0x60) if iszero(eq(index, prevIndex)) { let element := mload(0x40) let elementLength := sub(index, prevIndex) mstore(element, elementLength) // Copy the `subject` one word at a time, backwards. for { let o := and(add(elementLength, 0x1f), w) } 1 {} { mstore(add(element, o), mload(add(add(subject, prevIndex), o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } // Zeroize the slot after the string. mstore(add(add(element, 0x20), elementLength), 0) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, add(element, and(add(elementLength, 0x3f), w))) // Store the `element` into the array. mstore(indexPtr, element) } prevIndex := add(index, mload(delimiter)) indexPtr := add(indexPtr, 0x20) if iszero(lt(indexPtr, indicesEnd)) { break } } result := indices if iszero(mload(delimiter)) { result := add(indices, 0x20) mstore(result, sub(mload(indices), 2)) } } } /// @dev Returns a concatenated string of `a` and `b`. /// Cheaper than `string.concat()` and does not de-align the free memory pointer. function concat(string memory a, string memory b) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let w := not(0x1f) result := mload(0x40) let aLength := mload(a) // Copy `a` one word at a time, backwards. for { let o := and(add(aLength, 0x20), w) } 1 {} { mstore(add(result, o), mload(add(a, o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } let bLength := mload(b) let output := add(result, aLength) // Copy `b` one word at a time, backwards. for { let o := and(add(bLength, 0x20), w) } 1 {} { mstore(add(output, o), mload(add(b, o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } let totalLength := add(aLength, bLength) let last := add(add(result, 0x20), totalLength) // Zeroize the slot after the string. mstore(last, 0) // Stores the length. mstore(result, totalLength) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, and(add(last, 0x1f), w)) } } /// @dev Returns a copy of the string in either lowercase or UPPERCASE. /// WARNING! This function is only compatible with 7-bit ASCII strings. function toCase(string memory subject, bool toUpper) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let length := mload(subject) if length { result := add(mload(0x40), 0x20) subject := add(subject, 1) let flags := shl(add(70, shl(5, toUpper)), 0x3ffffff) let w := not(0) for { let o := length } 1 {} { o := add(o, w) let b := and(0xff, mload(add(subject, o))) mstore8(add(result, o), xor(b, and(shr(b, flags), 0x20))) if iszero(o) { break } } result := mload(0x40) mstore(result, length) // Store the length. let last := add(add(result, 0x20), length) mstore(last, 0) // Zeroize the slot after the string. mstore(0x40, add(last, 0x20)) // Allocate the memory. } } } /// @dev Returns a string from a small bytes32 string. /// `s` must be null-terminated, or behavior will be undefined. function fromSmallString(bytes32 s) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) let n := 0 for {} byte(n, s) { n := add(n, 1) } {} // Scan for '\0'. mstore(result, n) let o := add(result, 0x20) mstore(o, s) mstore(add(o, n), 0) mstore(0x40, add(result, 0x40)) } } /// @dev Returns the small string, with all bytes after the first null byte zeroized. function normalizeSmallString(bytes32 s) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { for {} byte(result, s) { result := add(result, 1) } {} // Scan for '\0'. mstore(0x00, s) mstore(result, 0x00) result := mload(0x00) } } /// @dev Returns the string as a normalized null-terminated small string. function toSmallString(string memory s) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { result := mload(s) if iszero(lt(result, 33)) { mstore(0x00, 0xec92f9a3) // `TooBigForSmallString()`. revert(0x1c, 0x04) } result := shl(shl(3, sub(32, result)), mload(add(s, result))) } } /// @dev Returns a lowercased copy of the string. /// WARNING! This function is only compatible with 7-bit ASCII strings. function lower(string memory subject) internal pure returns (string memory result) { result = toCase(subject, false); } /// @dev Returns an UPPERCASED copy of the string. /// WARNING! This function is only compatible with 7-bit ASCII strings. function upper(string memory subject) internal pure returns (string memory result) { result = toCase(subject, true); } /// @dev Escapes the string to be used within HTML tags. function escapeHTML(string memory s) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let end := add(s, mload(s)) result := add(mload(0x40), 0x20) // Store the bytes of the packed offsets and strides into the scratch space. // `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6. mstore(0x1f, 0x900094) mstore(0x08, 0xc0000000a6ab) // Store ""&'<>" into the scratch space. mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b)) for {} iszero(eq(s, end)) {} { s := add(s, 1) let c := and(mload(s), 0xff) // Not in `["\"","'","&","<",">"]`. if iszero(and(shl(c, 1), 0x500000c400000000)) { mstore8(result, c) result := add(result, 1) continue } let t := shr(248, mload(c)) mstore(result, mload(and(t, 0x1f))) result := add(result, shr(5, t)) } let last := result mstore(last, 0) // Zeroize the slot after the string. result := mload(0x40) mstore(result, sub(last, add(result, 0x20))) // Store the length. mstore(0x40, add(last, 0x20)) // Allocate the memory. } } /// @dev Escapes the string to be used within double-quotes in a JSON. /// If `addDoubleQuotes` is true, the result will be enclosed in double-quotes. function escapeJSON(string memory s, bool addDoubleQuotes) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let end := add(s, mload(s)) result := add(mload(0x40), 0x20) if addDoubleQuotes { mstore8(result, 34) result := add(1, result) } // Store "\\u0000" in scratch space. // Store "0123456789abcdef" in scratch space. // Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`. // into the scratch space. mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672) // Bitmask for detecting `["\"","\\"]`. let e := or(shl(0x22, 1), shl(0x5c, 1)) for {} iszero(eq(s, end)) {} { s := add(s, 1) let c := and(mload(s), 0xff) if iszero(lt(c, 0x20)) { if iszero(and(shl(c, 1), e)) { // Not in `["\"","\\"]`. mstore8(result, c) result := add(result, 1) continue } mstore8(result, 0x5c) // "\\". mstore8(add(result, 1), c) result := add(result, 2) continue } if iszero(and(shl(c, 1), 0x3700)) { // Not in `["\b","\t","\n","\f","\d"]`. mstore8(0x1d, mload(shr(4, c))) // Hex value. mstore8(0x1e, mload(and(c, 15))) // Hex value. mstore(result, mload(0x19)) // "\\u00XX". result := add(result, 6) continue } mstore8(result, 0x5c) // "\\". mstore8(add(result, 1), mload(add(c, 8))) result := add(result, 2) } if addDoubleQuotes { mstore8(result, 34) result := add(1, result) } let last := result mstore(last, 0) // Zeroize the slot after the string. result := mload(0x40) mstore(result, sub(last, add(result, 0x20))) // Store the length. mstore(0x40, add(last, 0x20)) // Allocate the memory. } } /// @dev Escapes the string to be used within double-quotes in a JSON. function escapeJSON(string memory s) internal pure returns (string memory result) { result = escapeJSON(s, false); } /// @dev Returns whether `a` equals `b`. function eq(string memory a, string memory b) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b))) } } /// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small string. function eqs(string memory a, bytes32 b) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { // These should be evaluated on compile time, as far as possible. let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`. let x := not(or(m, or(b, add(m, and(b, m))))) let r := shl(7, iszero(iszero(shr(128, x)))) r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x)))))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffff, shr(r, x)))) r := or(r, shl(3, lt(0xff, shr(r, x)))) // forgefmt: disable-next-item result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))), xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20))))) } } /// @dev Packs a single string with its length into a single word. /// Returns `bytes32(0)` if the length is zero or greater than 31. function packOne(string memory a) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { // We don't need to zero right pad the string, // since this is our own custom non-standard packing scheme. result := mul( // Load the length and the bytes. mload(add(a, 0x1f)), // `length != 0 && length < 32`. Abuses underflow. // Assumes that the length is valid and within the block gas limit. lt(sub(mload(a), 1), 0x1f) ) } } /// @dev Unpacks a string packed using {packOne}. /// Returns the empty string if `packed` is `bytes32(0)`. /// If `packed` is not an output of {packOne}, the output behavior is undefined. function unpackOne(bytes32 packed) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { // Grab the free memory pointer. result := mload(0x40) // Allocate 2 words (1 for the length, 1 for the bytes). mstore(0x40, add(result, 0x40)) // Zeroize the length slot. mstore(result, 0) // Store the length and bytes. mstore(add(result, 0x1f), packed) // Right pad with zeroes. mstore(add(add(result, 0x20), mload(result)), 0) } } /// @dev Packs two strings with their lengths into a single word. /// Returns `bytes32(0)` if combined length is zero or greater than 30. function packTwo(string memory a, string memory b) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { let aLength := mload(a) // We don't need to zero right pad the strings, // since this is our own custom non-standard packing scheme. result := mul( // Load the length and the bytes of `a` and `b`. or( shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))), mload(sub(add(b, 0x1e), aLength)) ), // `totalLength != 0 && totalLength < 31`. Abuses underflow. // Assumes that the lengths are valid and within the block gas limit. lt(sub(add(aLength, mload(b)), 1), 0x1e) ) } } /// @dev Unpacks strings packed using {packTwo}. /// Returns the empty strings if `packed` is `bytes32(0)`. /// If `packed` is not an output of {packTwo}, the output behavior is undefined. function unpackTwo(bytes32 packed) internal pure returns (string memory resultA, string memory resultB) { /// @solidity memory-safe-assembly assembly { // Grab the free memory pointer. resultA := mload(0x40) resultB := add(resultA, 0x40) // Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words. mstore(0x40, add(resultB, 0x40)) // Zeroize the length slots. mstore(resultA, 0) mstore(resultB, 0) // Store the lengths and bytes. mstore(add(resultA, 0x1f), packed) mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA)))) // Right pad with zeroes. mstore(add(add(resultA, 0x20), mload(resultA)), 0) mstore(add(add(resultB, 0x20), mload(resultB)), 0) } } /// @dev Directly returns `a` without copying. function directReturn(string memory a) internal pure { assembly { // Assumes that the string does not start from the scratch space. let retStart := sub(a, 0x20) let retSize := add(mload(a), 0x40) // Right pad with zeroes. Just in case the string is produced // by a method that doesn't zero right pad. mstore(add(retStart, retSize), 0) // Store the return offset. mstore(retStart, 0x20) // End the transaction, returning the string. return(retStart, retSize) } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Simple single owner authorization mixin. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol) /// /// @dev Note: /// This implementation does NOT auto-initialize the owner to `msg.sender`. /// You MUST call the `_initializeOwner` in the constructor / initializer. /// /// While the ownable portion follows /// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility, /// the nomenclature for the 2-step ownership handover may be unique to this codebase. abstract contract Ownable { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The caller is not authorized to call the function. error Unauthorized(); /// @dev The `newOwner` cannot be the zero address. error NewOwnerIsZeroAddress(); /// @dev The `pendingOwner` does not have a valid handover request. error NoHandoverRequest(); /// @dev Cannot double-initialize. error AlreadyInitialized(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EVENTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The ownership is transferred from `oldOwner` to `newOwner`. /// This event is intentionally kept the same as OpenZeppelin's Ownable to be /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173), /// despite it not being as lightweight as a single argument event. event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); /// @dev An ownership handover to `pendingOwner` has been requested. event OwnershipHandoverRequested(address indexed pendingOwner); /// @dev The ownership handover to `pendingOwner` has been canceled. event OwnershipHandoverCanceled(address indexed pendingOwner); /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`. uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE = 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0; /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`. uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE = 0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d; /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`. uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE = 0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The owner slot is given by: /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`. /// It is intentionally chosen to be a high value /// to avoid collision with lower slots. /// The choice of manual storage layout is to enable compatibility /// with both regular and upgradeable contracts. bytes32 internal constant _OWNER_SLOT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927; /// The ownership handover slot of `newOwner` is given by: /// ``` /// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED)) /// let handoverSlot := keccak256(0x00, 0x20) /// ``` /// It stores the expiry timestamp of the two-step ownership handover. uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Override to return true to make `_initializeOwner` prevent double-initialization. function _guardInitializeOwner() internal pure virtual returns (bool guard) {} /// @dev Initializes the owner directly without authorization guard. /// This function must be called upon initialization, /// regardless of whether the contract is upgradeable or not. /// This is to enable generalization to both regular and upgradeable contracts, /// and to save gas in case the initial owner is not the caller. /// For performance reasons, this function will not check if there /// is an existing owner. function _initializeOwner(address newOwner) internal virtual { if (_guardInitializeOwner()) { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT if sload(ownerSlot) { mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`. revert(0x1c, 0x04) } // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Store the new value. sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) } } else { /// @solidity memory-safe-assembly assembly { // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Store the new value. sstore(_OWNER_SLOT, newOwner) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) } } } /// @dev Sets the owner directly without authorization guard. function _setOwner(address newOwner) internal virtual { if (_guardInitializeOwner()) { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) // Store the new value. sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) } } else { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) // Store the new value. sstore(ownerSlot, newOwner) } } } /// @dev Throws if the sender is not the owner. function _checkOwner() internal view virtual { /// @solidity memory-safe-assembly assembly { // If the caller is not the stored owner, revert. if iszero(eq(caller(), sload(_OWNER_SLOT))) { mstore(0x00, 0x82b42900) // `Unauthorized()`. revert(0x1c, 0x04) } } } /// @dev Returns how long a two-step ownership handover is valid for in seconds. /// Override to return a different value if needed. /// Made internal to conserve bytecode. Wrap it in a public function if needed. function _ownershipHandoverValidFor() internal view virtual returns (uint64) { return 48 * 3600; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PUBLIC UPDATE FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Allows the owner to transfer the ownership to `newOwner`. function transferOwnership(address newOwner) public payable virtual onlyOwner { /// @solidity memory-safe-assembly assembly { if iszero(shl(96, newOwner)) { mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`. revert(0x1c, 0x04) } } _setOwner(newOwner); } /// @dev Allows the owner to renounce their ownership. function renounceOwnership() public payable virtual onlyOwner { _setOwner(address(0)); } /// @dev Request a two-step ownership handover to the caller. /// The request will automatically expire in 48 hours (172800 seconds) by default. function requestOwnershipHandover() public payable virtual { unchecked { uint256 expires = block.timestamp + _ownershipHandoverValidFor(); /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to `expires`. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, caller()) sstore(keccak256(0x0c, 0x20), expires) // Emit the {OwnershipHandoverRequested} event. log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller()) } } } /// @dev Cancels the two-step ownership handover to the caller, if any. function cancelOwnershipHandover() public payable virtual { /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to 0. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, caller()) sstore(keccak256(0x0c, 0x20), 0) // Emit the {OwnershipHandoverCanceled} event. log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller()) } } /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`. /// Reverts if there is no existing ownership handover requested by `pendingOwner`. function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner { /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to 0. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, pendingOwner) let handoverSlot := keccak256(0x0c, 0x20) // If the handover does not exist, or has expired. if gt(timestamp(), sload(handoverSlot)) { mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`. revert(0x1c, 0x04) } // Set the handover slot to 0. sstore(handoverSlot, 0) } _setOwner(pendingOwner); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PUBLIC READ FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the owner of the contract. function owner() public view virtual returns (address result) { /// @solidity memory-safe-assembly assembly { result := sload(_OWNER_SLOT) } } /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`. function ownershipHandoverExpiresAt(address pendingOwner) public view virtual returns (uint256 result) { /// @solidity memory-safe-assembly assembly { // Compute the handover slot. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, pendingOwner) // Load the handover slot. result := sload(keccak256(0x0c, 0x20)) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* MODIFIERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Marks a function as only callable by the owner. modifier onlyOwner() virtual { _checkOwner(); _; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Library to encode strings in Base64. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base64.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Base64.sol) /// @author Modified from (https://github.com/Brechtpd/base64/blob/main/base64.sol) by Brecht Devos - <[email protected]>. library Base64 { /// @dev Encodes `data` using the base64 encoding described in RFC 4648. /// See: https://datatracker.ietf.org/doc/html/rfc4648 /// @param fileSafe Whether to replace '+' with '-' and '/' with '_'. /// @param noPadding Whether to strip away the padding. function encode(bytes memory data, bool fileSafe, bool noPadding) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let dataLength := mload(data) if dataLength { // Multiply by 4/3 rounded up. // The `shl(2, ...)` is equivalent to multiplying by 4. let encodedLength := shl(2, div(add(dataLength, 2), 3)) // Set `result` to point to the start of the free memory. result := mload(0x40) // Store the table into the scratch space. // Offsetted by -1 byte so that the `mload` will load the character. // We will rewrite the free memory pointer at `0x40` later with // the allocated size. // The magic constant 0x0670 will turn "-_" into "+/". mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef") mstore(0x3f, xor("ghijklmnopqrstuvwxyz0123456789-_", mul(iszero(fileSafe), 0x0670))) // Skip the first slot, which stores the length. let ptr := add(result, 0x20) let end := add(ptr, encodedLength) // Run over the input, 3 bytes at a time. for {} 1 {} { data := add(data, 3) // Advance 3 bytes. let input := mload(data) // Write 4 bytes. Optimized for fewer stack operations. mstore8(0, mload(and(shr(18, input), 0x3F))) mstore8(1, mload(and(shr(12, input), 0x3F))) mstore8(2, mload(and(shr(6, input), 0x3F))) mstore8(3, mload(and(input, 0x3F))) mstore(ptr, mload(0x00)) ptr := add(ptr, 4) // Advance 4 bytes. if iszero(lt(ptr, end)) { break } } mstore(0x40, add(end, 0x20)) // Allocate the memory. // Equivalent to `o = [0, 2, 1][dataLength % 3]`. let o := div(2, mod(dataLength, 3)) // Offset `ptr` and pad with '='. We can simply write over the end. mstore(sub(ptr, o), shl(240, 0x3d3d)) // Set `o` to zero if there is padding. o := mul(iszero(iszero(noPadding)), o) mstore(sub(ptr, o), 0) // Zeroize the slot after the string. mstore(result, sub(encodedLength, o)) // Store the length. } } } /// @dev Encodes `data` using the base64 encoding described in RFC 4648. /// Equivalent to `encode(data, false, false)`. function encode(bytes memory data) internal pure returns (string memory result) { result = encode(data, false, false); } /// @dev Encodes `data` using the base64 encoding described in RFC 4648. /// Equivalent to `encode(data, fileSafe, false)`. function encode(bytes memory data, bool fileSafe) internal pure returns (string memory result) { result = encode(data, fileSafe, false); } /// @dev Decodes base64 encoded `data`. /// /// Supports: /// - RFC 4648 (both standard and file-safe mode). /// - RFC 3501 (63: ','). /// /// Does not support: /// - Line breaks. /// /// Note: For performance reasons, /// this function will NOT revert on invalid `data` inputs. /// Outputs for invalid inputs will simply be undefined behaviour. /// It is the user's responsibility to ensure that the `data` /// is a valid base64 encoded string. function decode(string memory data) internal pure returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { let dataLength := mload(data) if dataLength { let decodedLength := mul(shr(2, dataLength), 3) for {} 1 {} { // If padded. if iszero(and(dataLength, 3)) { let t := xor(mload(add(data, dataLength)), 0x3d3d) // forgefmt: disable-next-item decodedLength := sub( decodedLength, add(iszero(byte(30, t)), iszero(byte(31, t))) ) break } // If non-padded. decodedLength := add(decodedLength, sub(and(dataLength, 3), 1)) break } result := mload(0x40) // Write the length of the bytes. mstore(result, decodedLength) // Skip the first slot, which stores the length. let ptr := add(result, 0x20) let end := add(ptr, decodedLength) // Load the table into the scratch space. // Constants are optimized for smaller bytecode with zero gas overhead. // `m` also doubles as the mask of the upper 6 bits. let m := 0xfc000000fc00686c7074787c8084888c9094989ca0a4a8acb0b4b8bcc0c4c8cc mstore(0x5b, m) mstore(0x3b, 0x04080c1014181c2024282c3034383c4044484c5054585c6064) mstore(0x1a, 0xf8fcf800fcd0d4d8dce0e4e8ecf0f4) for {} 1 {} { // Read 4 bytes. data := add(data, 4) let input := mload(data) // Write 3 bytes. // forgefmt: disable-next-item mstore(ptr, or( and(m, mload(byte(28, input))), shr(6, or( and(m, mload(byte(29, input))), shr(6, or( and(m, mload(byte(30, input))), shr(6, mload(byte(31, input))) )) )) )) ptr := add(ptr, 3) if iszero(lt(ptr, end)) { break } } mstore(0x40, add(end, 0x20)) // Allocate the memory. mstore(end, 0) // Zeroize the slot after the bytes. mstore(0x60, 0) // Restore the zero slot. } } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Gas optimized ECDSA wrapper. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol) /// /// @dev Note: /// - The recovery functions use the ecrecover precompile (0x1). /// - As of Solady version 0.0.68, the `recover` variants will revert upon recovery failure. /// This is for more safety by default. /// Use the `tryRecover` variants if you need to get the zero address back /// upon recovery failure instead. /// - As of Solady version 0.0.134, all `bytes signature` variants accept both /// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures. /// See: https://eips.ethereum.org/EIPS/eip-2098 /// This is for calldata efficiency on smart accounts prevalent on L2s. /// /// WARNING! Do NOT use signatures as unique identifiers: /// - Use a nonce in the digest to prevent replay attacks on the same contract. /// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts. /// EIP-712 also enables readable signing of typed data for better user safety. /// This implementation does NOT check if a signature is non-malleable. library ECDSA { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The signature is invalid. error InvalidSignature(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* RECOVERY OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. function recover(bytes32 hash, bytes memory signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { result := 1 let m := mload(0x40) // Cache the free memory pointer. for {} 1 {} { mstore(0x00, hash) mstore(0x40, mload(add(signature, 0x20))) // `r`. if eq(mload(signature), 64) { let vs := mload(add(signature, 0x40)) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x60, shr(1, shl(1, vs))) // `s`. break } if eq(mload(signature), 65) { mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. mstore(0x60, mload(add(signature, 0x40))) // `s`. break } result := 0 break } result := mload( staticcall( gas(), // Amount of gas left for the transaction. result, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. function recoverCalldata(bytes32 hash, bytes calldata signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { result := 1 let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) for {} 1 {} { if eq(signature.length, 64) { let vs := calldataload(add(signature.offset, 0x20)) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, calldataload(signature.offset)) // `r`. mstore(0x60, shr(1, shl(1, vs))) // `s`. break } if eq(signature.length, 65) { mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. break } result := 0 break } result := mload( staticcall( gas(), // Amount of gas left for the transaction. result, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the EIP-2098 short form signature defined by `r` and `vs`. function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, r) mstore(0x60, shr(1, shl(1, vs))) // `s`. result := mload( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the signature defined by `v`, `r`, `s`. function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, and(v, 0xff)) mstore(0x40, r) mstore(0x60, s) result := mload( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* TRY-RECOVER OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // WARNING! // These functions will NOT revert upon recovery failure. // Instead, they will return the zero address upon recovery failure. // It is critical that the returned address is NEVER compared against // a zero address (e.g. an uninitialized address variable). /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. function tryRecover(bytes32 hash, bytes memory signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { result := 1 let m := mload(0x40) // Cache the free memory pointer. for {} 1 {} { mstore(0x00, hash) mstore(0x40, mload(add(signature, 0x20))) // `r`. if eq(mload(signature), 64) { let vs := mload(add(signature, 0x40)) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x60, shr(1, shl(1, vs))) // `s`. break } if eq(mload(signature), 65) { mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. mstore(0x60, mload(add(signature, 0x40))) // `s`. break } result := 0 break } pop( staticcall( gas(), // Amount of gas left for the transaction. result, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. function tryRecoverCalldata(bytes32 hash, bytes calldata signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { result := 1 let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) for {} 1 {} { if eq(signature.length, 64) { let vs := calldataload(add(signature.offset, 0x20)) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, calldataload(signature.offset)) // `r`. mstore(0x60, shr(1, shl(1, vs))) // `s`. break } if eq(signature.length, 65) { mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. break } result := 0 break } pop( staticcall( gas(), // Amount of gas left for the transaction. result, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the EIP-2098 short form signature defined by `r` and `vs`. function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, r) mstore(0x60, shr(1, shl(1, vs))) // `s`. pop( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the signature defined by `v`, `r`, `s`. function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, and(v, 0xff)) mstore(0x40, r) mstore(0x60, s) pop( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HASHING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns an Ethereum Signed Message, created from a `hash`. /// This produces a hash corresponding to the one signed with the /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) /// JSON-RPC method as part of EIP-191. function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { mstore(0x20, hash) // Store into scratch space for keccak256. mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. } } /// @dev Returns an Ethereum Signed Message, created from `s`. /// This produces a hash corresponding to the one signed with the /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) /// JSON-RPC method as part of EIP-191. /// Note: Supports lengths of `s` up to 999999 bytes. function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { let sLength := mload(s) let o := 0x20 mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. mstore(0x00, 0x00) // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. for { let temp := sLength } 1 {} { o := sub(o, 1) mstore8(o, add(48, mod(temp, 10))) temp := div(temp, 10) if iszero(temp) { break } } let n := sub(0x3a, o) // Header length: `26 + 32 - o`. // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) mstore(s, sLength) // Restore the length. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EMPTY CALLDATA HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns an empty calldata bytes. function emptySignature() internal pure returns (bytes calldata signature) { /// @solidity memory-safe-assembly assembly { signature.length := 0 } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Reentrancy guard mixin. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ReentrancyGuard.sol) abstract contract ReentrancyGuard { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Unauthorized reentrant call. error Reentrancy(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Equivalent to: `uint72(bytes9(keccak256("_REENTRANCY_GUARD_SLOT")))`. /// 9 bytes is large enough to avoid collisions with lower slots, /// but not too large to result in excessive bytecode bloat. uint256 private constant _REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* REENTRANCY GUARD */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Guards a function from reentrancy. modifier nonReentrant() virtual { /// @solidity memory-safe-assembly assembly { if eq(sload(_REENTRANCY_GUARD_SLOT), address()) { mstore(0x00, 0xab143c06) // `Reentrancy()`. revert(0x1c, 0x04) } sstore(_REENTRANCY_GUARD_SLOT, address()) } _; /// @solidity memory-safe-assembly assembly { sstore(_REENTRANCY_GUARD_SLOT, codesize()) } } /// @dev Guards a view function from read-only reentrancy. modifier nonReadReentrant() virtual { /// @solidity memory-safe-assembly assembly { if eq(sload(_REENTRANCY_GUARD_SLOT), address()) { mstore(0x00, 0xab143c06) // `Reentrancy()`. revert(0x1c, 0x04) } } _; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @dev Dnner Note: /// - Dnner(DN404ND) does not possess the fragmentability, nor does /// it have the feature of NFT transactions without the /// need for a third party, characteristic of ERC404/DN404. /// /// DN404 can easily create efficient hybrid tokens and /// define the ratio between NFT and tokens. /// We aim to leverage the foundation of DN404 to enable /// NFT to drive the increase in token value. /// /// Another way of putting it is to avoid turning NFTs /// into purely tokenized assets. /// /// This is an experimental project not conforming to /// the 404 standard, DYOR and read more from our website. /// /// Twitter: https://twitter.com/DN404ND /// Website: https://dnner.shop /// @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 The ownership is transferred from `oldOwner` to `newOwner`. /// This is for marketplace signaling purposes. This contract has a `pullOwner()` /// function that will sync the owner from the base contract. event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); /// @dev `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; address owner; } /// @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) { return _readString(0x06fdde03, 0); // `name()`. } /// @dev Returns the token collection symbol from the base DN404 contract. function symbol() public view virtual returns (string memory result) { return _readString(0x95d89b41, 0); // `symbol()`. } /// @dev Returns the Uniform Resource Identifier (URI) for token `id` from /// the base DN404 contract. function tokenURI(uint256 id) public view virtual returns (string memory result) { return _readString(0xc87b56dd, id); // `tokenURI()`. } /// @dev Returns the total NFT supply from the base DN404 contract. function totalSupply() public view virtual returns (uint256 result) { return _readWord(0xe2c79281, 0, 0); // `totalNFTSupply()`. } /// @dev Returns the number of NFT tokens owned by `nftOwner` from the base DN404 contract. /// /// Requirements: /// - `nftOwner` must not be the zero address. function balanceOf(address nftOwner) public view virtual returns (uint256 result) { return _readWord(0xf5b100ea, uint160(nftOwner), 0); // `balanceOfNFT(address)`. } /// @dev Returns the owner of token `id` from the base DN404 contract. /// /// Requirements: /// - Token `id` must exist. function ownerOf(uint256 id) public view virtual returns (address result) { return address(uint160(_readWord(0x6352211e, id, 0))); // `ownerOf(uint256)`. } /// @dev Returns the owner of token `id` from the base DN404 contract. /// Returns `address(0)` instead of reverting if the token does not exist. function ownerAt(uint256 id) public view virtual returns (address result) { return address(uint160(_readWord(0x24359879, id, 0))); // `ownerAt(uint256)`. } /// @dev Sets `spender` as the approved account to manage token `id` in /// the base 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) { return address(uint160(_readWord(0x081812fc, id, 0))); // `getApproved(uint256)`. } /// @dev Sets whether `operator` is approved to manage the tokens of the caller in /// the base 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. // The `approved` value is already at 0x40. log3(0x40, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), operator) mstore(0x40, m) // Restore the free memory pointer. mstore(0x60, 0) // Restore the zero pointer. } } /// @dev Returns whether `operator` is approved to manage the tokens of `nftOwner` from /// the base DN404 contract. function isApprovedForAll(address nftOwner, address operator) public view virtual returns (bool result) { // `isApprovedForAll(address,address)`. return _readWord(0xe985e9c5, uint160(nftOwner), uint160(operator)) != 0; } /// @dev 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)) } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* OWNER SYNCING OPERATIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns the `owner` of the contract, for marketplace signaling purposes. function owner() public view virtual returns (address) { return _getDN404NFTStorage().owner; } /// @dev Permissionless function to pull the owner from the base DN404 contract /// if it implements ownable, for marketplace signaling purposes. function pullOwner() public virtual { address newOwner; address base = baseERC20(); /// @solidity memory-safe-assembly assembly { mstore(0x00, 0x8da5cb5b) // `owner()`. if and(gt(returndatasize(), 0x1f), staticcall(gas(), base, 0x1c, 0x04, 0x00, 0x20)) { newOwner := shr(96, mload(0x0c)) } } DN404NFTStorage storage $ = _getDN404NFTStorage(); address oldOwner = $.owner; if (oldOwner != newOwner) { $.owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* 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), // `from`. mul(a, iszero(b)), // `to`. shr(168, shl(160, d)) // `id`. ) } 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 Helper to read a string from the base DN404 contract. function _readString(uint256 fnSelector, uint256 arg0) private view returns (string memory result) { address base = baseERC20(); /// @solidity memory-safe-assembly assembly { result := mload(0x40) mstore(0x00, fnSelector) mstore(0x20, arg0) if iszero(staticcall(gas(), base, 0x1c, 0x24, 0x00, 0x00)) { returndatacopy(result, 0x00, returndatasize()) revert(result, returndatasize()) } returndatacopy(0x00, 0x00, 0x20) // Copy the offset of the string in returndata. returndatacopy(result, mload(0x00), 0x20) // Copy the length of the string. returndatacopy(add(result, 0x20), add(mload(0x00), 0x20), mload(result)) // Copy the string. mstore(0x40, add(add(result, 0x20), mload(result))) // Allocate memory. } } /// @dev Helper to read a word from the base DN404 contract. function _readWord(uint256 fnSelector, uint256 arg0, uint256 arg1) private view returns (uint256 result) { address base = baseERC20(); /// @solidity memory-safe-assembly assembly { let m := mload(0x40) mstore(0x00, fnSelector) mstore(0x20, arg0) mstore(0x40, arg1) if iszero( and(gt(returndatasize(), 0x1f), staticcall(gas(), base, 0x1c, 0x44, 0x00, 0x20)) ) { returndatacopy(m, 0x00, returndatasize()) revert(m, returndatasize()) } mstore(0x40, m) // Restore the free memory pointer. result := mload(0x00) } } /// @dev Returns the calldata value at `offset`. function _calldataload(uint256 offset) private pure returns (uint256 value) { /// @solidity memory-safe-assembly assembly { value := calldataload(offset) } } /// @dev Returns if `a` has bytecode of non-zero length. function _hasCode(address a) private view returns (bool result) { /// @solidity memory-safe-assembly assembly { result := extcodesize(a) // Can handle dirty upper bits. } } /// @dev Perform a call to invoke {IERC721Receiver-onERC721Received} on `to`. /// Reverts if the target does not support the function correctly. function _checkOnERC721Received(address from, address to, uint256 id, bytes memory data) private { /// @solidity memory-safe-assembly assembly { // Prepare the calldata. let m := mload(0x40) let onERC721ReceivedSelector := 0x150b7a02 mstore(m, onERC721ReceivedSelector) mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`. mstore(add(m, 0x40), shr(96, shl(96, from))) mstore(add(m, 0x60), id) mstore(add(m, 0x80), 0x80) let n := mload(data) mstore(add(m, 0xa0), n) if n { pop(staticcall(gas(), 4, add(data, 0x20), n, add(m, 0xc0), n)) } // Revert if the call reverts. if iszero(call(gas(), to, 0, add(m, 0x1c), add(n, 0xa4), m, 0x20)) { if returndatasize() { // Bubble up the revert if the call reverts. returndatacopy(m, 0x00, returndatasize()) revert(m, returndatasize()) } } // Load the returndata and compare it. if iszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) { mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`. revert(0x1c, 0x04) } } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @dev Dnner Note: /// - Dnner(DN404ND) does not possess the fragmentability, nor does /// it have the feature of NFT transactions without the /// need for a third party, characteristic of ERC404/DN404. /// /// DN404 can easily create efficient hybrid tokens and /// define the ratio between NFT and tokens. /// We aim to leverage the foundation of DN404 to enable /// NFT to drive the increase in token value. /// /// Another way of putting it is to avoid turning NFTs /// into purely tokenized assets. /// /// This is an experimental project not conforming to /// the 404 standard, DYOR and read more from our website. /// /// Twitter: https://twitter.com/DN404ND /// Website: https://dnner.shop /// @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 DN404ND { /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* EVENTS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. event Transfer(address indexed from, address indexed to, uint256 amount); /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. event Approval( address indexed owner, address indexed spender, uint256 amount ); /// @dev Emitted when `target` sets their skipNFT flag to `status`. event SkipNFTSet(address indexed target, bool status); /// @dev `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("SkipNFTSet(address,bool)"))`. uint256 private constant _SKIP_NFT_SET_EVENT_SIGNATURE = 0xb5a1de456fff688115a4f75380060c23c8532d14ff85f687cc871456d6420393; /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* CUSTOM ERRORS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Thrown when attempting to double-initialize the contract. error DNAlreadyInitialized(); /// @dev Thrown when attempting to transfer or burn more tokens than sender's balance. error InsufficientBalance(); /// @dev Thrown when a spender attempts to transfer tokens with an insufficient allowance. error InsufficientAllowance(); /// @dev Thrown when minting an amount of tokens that would overflow the max tokens. error TotalSupplyOverflow(); /// @dev The unit cannot be zero. error UnitIsZero(); /// @dev Thrown when the caller for a fallback NFT function is not the mirror contract. error SenderNotMirror(); /// @dev Thrown when attempting to transfer tokens to the zero address. error TransferToZeroAddress(); /// @dev Thrown when the mirror address provided for initialization is the zero address. error MirrorAddressIsZero(); /// @dev Thrown when the link call to the mirror contract reverts. error LinkMirrorContractFailed(); /// @dev Thrown when setting an NFT token approval /// and the caller is not the owner or an approved operator. error ApprovalCallerNotOwnerNorApproved(); /// @dev Thrown when transferring an NFT /// and the caller is not the owner or an approved operator. error TransferCallerNotOwnerNorApproved(); /// @dev Thrown when transferring an NFT and the from address is not the current owner. error TransferFromIncorrectOwner(); /// @dev Thrown when checking the owner or approved address for a non-existent NFT. error TokenDoesNotExist(); /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* CONSTANTS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev The flag to denote that the address data is initialized. uint8 internal constant _ADDRESS_DATA_INITIALIZED_FLAG = 1 << 0; /// @dev The flag to denote that the address should skip NFTs. uint8 internal constant _ADDRESS_DATA_SKIP_NFT_FLAG = 1 << 1; /// @dev The flag to denote that the address has overridden the default Permit2 allowance. uint8 internal constant _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG = 1 << 2; /// @dev The canonical Permit2 address. /// For signature-based allowance granting for single transaction ERC20 `transferFrom`. /// To enable, override `_givePermit2DefaultInfiniteAllowance()`. /// [Github](https://github.com/Uniswap/permit2) /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* STORAGE */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Struct containing an address's token data and settings. struct AddressData { // 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; // // Lock the token balance from received(NFT) or mint(NFT) to prevent // from being mistakenly transferred, which could lead to // NFT transaction errors. You can use unlock to release them, // which will also destroy a corresponding amount of NFTs. // // These tokens will be deducted when the NFT is transferred // and increased when the NFT is received. uint96 lockedBalance; } /// @dev A uint32 map in storage. struct Uint32Map { uint256 spacer; } /// @dev A uint32 map in storage. struct Uint256Map { uint256 spacer; } /// @dev A bitmap in storage. struct Bitmap { uint256 spacer; } /// @dev A struct to wrap a uint256 in storage. struct Uint256Ref { uint256 value; } /// @dev A mapping of an address pair to a Uint256Ref. struct AddressPairToUint256RefMap { uint256 spacer; } /// @dev Struct containing the base token contract storage. struct DN404Storage { // Current number of address aliases assigned. uint32 numAliases; // Next NFT ID to assign for a mint. uint32 nextTokenId; // Total number of NFT IDs in the burned pool. uint32 burnedPoolSize; // Total supply of minted NFTs. uint32 totalNFTSupply; // Max NFT supply. uint256 maxNFTSupply; // 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. AddressPairToUint256RefMap operatorApprovals; // Mapping of NFT approvals to approved operators. mapping(uint256 => address) nftApprovals; // Bitmap of whether an non-zero NFT approval may exist. Bitmap mayHaveNFTApproval; // Mapping of user allowances for ERC20 spenders. AddressPairToUint256RefMap allowance; // Mapping of NFT IDs owned by an address. mapping(address => Uint32Map) owned; // The pool of burned NFT IDs. Uint32Map burnedPool; // Even indices: owner aliases. Odd indices: owned indices. Uint32Map oo; // Mapping of user account AddressData. mapping(address => AddressData) addressData; // Mapping of nftID to check last value(unit). Uint256Map NFTUnit; } /// @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(); /// @solidity memory-safe-assembly assembly { // Make the call to link the mirror contract. mstore(0x00, 0x0f4599e5) // `linkMirrorContract(address)`. mstore(0x20, caller()) if iszero( and( eq(mload(0x00), 1), call(gas(), mirror, 0, 0x1c, 0x24, 0x00, 0x20) ) ) { mstore(0x00, 0xd125259c) // `LinkMirrorContractFailed()`. revert(0x1c, 0x04) } } $.nextTokenId = 1; $.mirrorERC721 = mirror; if (_unit() == 0) revert UnitIsZero(); if (initialTokenSupply != 0) { if (initialSupplyOwner == address(0)) revert TransferToZeroAddress(); if (_totalSupplyOverflows(initialTokenSupply)) revert TotalSupplyOverflow(); $.totalSupply = uint96(initialTokenSupply); AddressData storage initialOwnerAddressData = _addressData( initialSupplyOwner ); initialOwnerAddressData.balance = uint96(initialTokenSupply); /// @solidity memory-safe-assembly assembly { // Emit the {Transfer} event. mstore(0x00, initialTokenSupply) log3( 0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, initialSupplyOwner)) ) } } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* BASE UNIT FUNCTION TO OVERRIDE */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Amount of token balance that is equal to one NFT. function _unit() internal view virtual returns (uint256) { return 10**18; } function _NFTUnit() internal view virtual returns (uint256) { return 10**18; } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* METADATA FUNCTIONS TO OVERRIDE */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns the name of the token. function name() public view virtual returns (string memory); /// @dev Returns the symbol of the token. function symbol() public view virtual returns (string memory); /// @dev Returns the Uniform Resource Identifier (URI) for token `id`. function tokenURI(uint256 id) public view virtual returns (string memory); /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* ERC20 OPERATIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns the decimals places of the token. Always 18. function decimals() public pure returns (uint8) { return 18; } /// @dev Returns the amount of tokens in existence. function totalSupply() public view virtual returns (uint256) { return uint256(_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) { if (_givePermit2DefaultInfiniteAllowance() && spender == _PERMIT2) { uint8 flags = _getDN404Storage().addressData[owner].flags; if (flags & _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG == 0) return type(uint256).max; } return _ref(_getDN404Storage().allowance, owner, spender).value; } /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. /// /// Emits a {Approval} event. function approve(address spender, uint256 amount) public virtual returns (bool) { _approve(msg.sender, spender, amount); return true; } /// @dev Transfer `amount` tokens from the caller to `to`. /// /// Will burn sender NFTs if balance after transfer is less than /// the amount required to support the current NFT balance. /// /// Will mint NFTs to `to` if the recipient's new balance supports /// additional NFTs ***AND*** the `to` address's skipNFT flag is /// set to false. /// /// Requirements: /// - `from` must at least have `amount`. /// /// Emits a {Transfer} event. function transfer(address to, uint256 amount) public virtual returns (bool) { _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) { Uint256Ref storage a = _ref( _getDN404Storage().allowance, from, msg.sender ); uint256 allowed = _givePermit2DefaultInfiniteAllowance() && msg.sender == _PERMIT2 && (_getDN404Storage().addressData[from].flags & _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG) == 0 ? type(uint256).max : a.value; if (allowed != type(uint256).max) { if (amount > allowed) revert InsufficientAllowance(); unchecked { a.value = allowed - amount; } } _transfer(from, to, amount); return true; } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* PERMIT2 */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Whether Permit2 has infinite allowances by default for all owners. /// For signature-based allowance granting for single transaction ERC20 `transferFrom`. /// To enable, override this function to return true. function _givePermit2DefaultInfiniteAllowance() internal view virtual returns (bool) { return false; } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* 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 maxId; { uint256 totalSupply_ = uint256($.totalSupply) + amount; $.totalSupply = uint96(totalSupply_); maxId = $.totalNFTSupply + amount; } uint256 toEnd; { uint256 toBalance = uint256(toAddressData.lockedBalance) + amount; toAddressData.lockedBalance = uint96(toBalance); toEnd = amount / _NFTUnit() + toAddressData.ownedLength; } if (toAddressData.flags & _ADDRESS_DATA_SKIP_NFT_FLAG == 0) { Uint32Map storage toOwned = $.owned[to]; Uint32Map storage oo = $.oo; uint256 toIndex = toAddressData.ownedLength; _PackedLogs memory packedLogs = _packedLogsMalloc( _zeroFloorSub(toEnd, toIndex) ); if (packedLogs.logs.length != 0) { _packedLogsSet(packedLogs, to, 0); uint256 burnedPoolSize = $.burnedPoolSize; uint256 nextTokenId = $.nextTokenId; uint32 toAlias = _registerAndResolveAlias( toAddressData, to ); $.totalNFTSupply += uint32(packedLogs.logs.length); toAddressData.ownedLength = uint32(toEnd); // Mint loop. do { uint256 id; if (burnedPoolSize != 0) { id = _get($.burnedPool, --burnedPoolSize); _set($.NFTUnit, id, _NFTUnit()); } else { id = nextTokenId; while (_get(oo, _ownershipIndex(id)) != 0) { id = _wrapNFTId(id + 1, maxId); } nextTokenId = _wrapNFTId(id + 1, maxId); } _set(toOwned, toIndex, uint32(id)); _setOwnerAliasAndOwnedIndex( oo, id, toAlias, uint32(toIndex++) ); _packedLogsAppend(packedLogs, id); } while (toIndex != toEnd); $.nextTokenId = uint32(nextTokenId); $.burnedPoolSize = uint32(burnedPoolSize); _packedLogsSend(packedLogs, $.mirrorERC721); } } } /// @solidity memory-safe-assembly assembly { // Emit the {Transfer} event. mstore(0x00, amount) log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, to))) } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* 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, bool burnNFT ) internal virtual { DN404Storage storage $ = _getDN404Storage(); AddressData storage fromAddressData = _addressData(from); unchecked { if (burnNFT) { Uint32Map storage fromOwned = $.owned[from]; uint256 fromIndex = fromAddressData.ownedLength; uint256 numNFTBurns = amount / _NFTUnit(); if (numNFTBurns != 0) { _PackedLogs memory packedLogs = _packedLogsMalloc( numNFTBurns ); _packedLogsSet(packedLogs, from, 1); uint256 totalNFTSupply = uint256($.totalNFTSupply) - numNFTBurns; $.totalNFTSupply = uint32(totalNFTSupply); Uint32Map storage oo = $.oo; uint256 fromEnd = fromIndex - numNFTBurns; fromAddressData.ownedLength = uint32(fromEnd); uint256 burnedPoolSize = $.burnedPoolSize; uint256 lockedBalanceToRemove = 0; // Burn loop. do { uint256 id = _get(fromOwned, --fromIndex); uint256 lastNFTUnit = _get($.NFTUnit, id); lockedBalanceToRemove += lastNFTUnit > _NFTUnit() ? lastNFTUnit : _NFTUnit(); _setOwnerAliasAndOwnedIndex(oo, id, 0, 0); _packedLogsAppend(packedLogs, id); _set($.burnedPool, burnedPoolSize++, uint32(id)); } while (fromIndex != fromEnd); fromAddressData.lockedBalance -= uint96( lockedBalanceToRemove ); $.burnedPoolSize = uint32(burnedPoolSize); _packedLogsSend(packedLogs, $.mirrorERC721); } } else { uint256 fromBalance = fromAddressData.balance; if (amount > fromBalance) revert InsufficientBalance(); fromAddressData.balance = uint96(fromBalance -= amount); } uint256 totalSupply_ = uint256($.totalSupply) - amount; $.totalSupply = uint96(totalSupply_); } /// @solidity memory-safe-assembly assembly { // Emit the {Transfer} event. mstore(0x00, amount) log3( 0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0 ) } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* 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.totalSupply = $.totalSupply; if (amount > (t.fromBalance = fromAddressData.balance)) revert InsufficientBalance(); unchecked { fromAddressData.balance = uint96(t.fromBalance -= amount); toAddressData.balance = uint96( t.toBalance = uint256(toAddressData.balance) + amount ); // @dev Dnner note: // - The minting and burning methods have been removed here; // users transfer the corresponding tokens simultaneously when transferring NFTs. } /// @solidity memory-safe-assembly assembly { // Emit the {Transfer} event. mstore(0x00, amount) // forgefmt: disable-next-item log3( 0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), shr(96, shl(96, to)) ) } } /// @dev Returns if burns should be added to the burn pool. function _addToBurnedPool( uint256 totalNFTSupplyAfterBurn, uint256 totalSupplyAfterBurn ) internal view virtual returns (bool) { // Add to burned pool if the load factor > 50%, and collection is not small. uint256 thres = (totalSupplyAfterBurn / _unit()) >> 1; return _toUint(totalNFTSupplyAfterBurn > thres) & _toUint(thres > 128) != 0; } /// @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(); Uint32Map storage oo = $.oo; if (from != $.aliasToAddress[_get(oo, _ownershipIndex(id))]) { revert TransferFromIncorrectOwner(); } if (msgSender != from) { if (_ref($.operatorApprovals, from, msgSender).value == 0) { if (msgSender != $.nftApprovals[id]) { revert TransferCallerNotOwnerNorApproved(); } } } AddressData storage fromAddressData = _addressData(from); AddressData storage toAddressData = _addressData(to); uint256 unit = _unit(); unchecked { { uint256 fromBalance = fromAddressData.lockedBalance; // Get last transfer unit. uint256 lastNFTUnit = _get($.NFTUnit, id); uint256 nftValue = lastNFTUnit > _NFTUnit() ? lastNFTUnit : _NFTUnit(); if (nftValue > fromBalance) revert InsufficientBalance(); fromAddressData.lockedBalance = uint96(fromBalance - nftValue); // Save the last unit for next transfer. _set($.NFTUnit, id, unit); toAddressData.lockedBalance += uint96(unit); } mapping(address => Uint32Map) storage owned = $.owned; Uint32Map storage fromOwned = owned[from]; if (_get($.mayHaveNFTApproval, id)) { _set($.mayHaveNFTApproval, id, false); delete $.nftApprovals[id]; } { uint32 updatedId = _get( fromOwned, --fromAddressData.ownedLength ); uint32 i = _get(oo, _ownedIndex(id)); _set(fromOwned, i, updatedId); _set(oo, _ownedIndex(updatedId), i); } uint32 n = toAddressData.ownedLength++; _set(owned[to], n, uint32(id)); _setOwnerAliasAndOwnedIndex( oo, id, _registerAndResolveAlias(toAddressData, to), n ); } /// @solidity memory-safe-assembly assembly { // Emit the {Transfer} event. mstore(0x00, unit) // forgefmt: disable-next-item log3( 0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), shr(96, shl(96, to)) ) } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* INTERNAL APPROVE FUNCTIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. /// /// Emits a {Approval} event. function _approve( address owner, address spender, uint256 amount ) internal virtual { if (_givePermit2DefaultInfiniteAllowance() && spender == _PERMIT2) { _getDN404Storage() .addressData[owner] .flags |= _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG; } _ref(_getDN404Storage().allowance, owner, spender).value = amount; /// @solidity memory-safe-assembly assembly { // Emit the {Approval} event. mstore(0x00, amount) // forgefmt: disable-next-item log3( 0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, shl(96, owner)), shr(96, shl(96, spender)) ) } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* INTERNAL CUSTOM FUNCTIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev /// - Unlock the locked balance from receiving or minting NFTs, /// while simultaneously destroying a corresponding amount of NFTs. function _unlock(address from) internal virtual { DN404Storage storage $ = _getDN404Storage(); AddressData storage fromAddressData = $.addressData[from]; uint96 lockedBalance = fromAddressData.lockedBalance; _burn(from, lockedBalance, true); fromAddressData.balance += lockedBalance; fromAddressData.lockedBalance = 0; /// @solidity memory-safe-assembly assembly { // Emit the {Transfer} event. mstore(0x00, lockedBalance) log3( 0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, from)) ) } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* SKIP NFT FUNCTIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns true if minting and transferring ERC20s to `owner` will skip minting NFTs. /// Returns false otherwise. function getSkipNFT(address owner) public view virtual returns (bool) { AddressData storage d = _getDN404Storage().addressData[owner]; if (d.flags & _ADDRESS_DATA_INITIALIZED_FLAG == 0) return _hasCode(owner); return d.flags & _ADDRESS_DATA_SKIP_NFT_FLAG != 0; } /// @dev Sets the caller's skipNFT flag to `skipNFT`. Returns true. /// /// Emits a {SkipNFTSet} event. function setSkipNFT(bool skipNFT) public virtual returns (bool) { _setSkipNFT(msg.sender, skipNFT); return true; } /// @dev Internal function to set account `owner` skipNFT flag to `state` /// /// Initializes account `owner` AddressData if it is not currently initialized. /// /// Emits a {SkipNFTSet} event. function _setSkipNFT(address owner, bool state) internal virtual { AddressData storage d = _addressData(owner); if ((d.flags & _ADDRESS_DATA_SKIP_NFT_FLAG != 0) != state) { d.flags ^= _ADDRESS_DATA_SKIP_NFT_FLAG; } /// @solidity memory-safe-assembly assembly { mstore(0x00, iszero(iszero(state))) log2( 0x00, 0x20, _SKIP_NFT_SET_EVENT_SIGNATURE, shr(96, shl(96, owner)) ) } } /// @dev Returns a storage data pointer for account `owner` AddressData /// /// Initializes account `owner` AddressData if it is not currently initialized. function _addressData(address owner) internal virtual returns (AddressData storage d) { d = _getDN404Storage().addressData[owner]; unchecked { if (d.flags & _ADDRESS_DATA_INITIALIZED_FLAG == 0) { uint256 skipNFT = (_toUint(_hasCode(owner)) * _ADDRESS_DATA_SKIP_NFT_FLAG); d.flags = uint8(skipNFT | _ADDRESS_DATA_INITIALIZED_FLAG); } } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* CUSTOM FUNCTIONS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ function _tokensOfWithChecks(address owner) internal view returns (uint256[] memory result) { DN404Storage storage $ = _getDN404Storage(); uint256 n = $.addressData[owner].ownedLength; result = new uint256[](n); for (uint256 i; i < n; ++i) { uint256 id = _get($.owned[owner], i); result[i] = id; // Check invariants. require(_ownerAt(id) == owner); require(_get($.oo, _ownedIndex(id)) == i); } } function _setMaxNFTSupply(uint256 num) internal virtual { DN404Storage storage $ = _getDN404Storage(); $.maxNFTSupply = num; } function _getMaxNFTSupply() internal view virtual returns (uint256) { return _getDN404Storage().maxNFTSupply; } /// @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) { unchecked { addressAlias = ++$.numAliases; } toAddressData.addressAlias = addressAlias; $.aliasToAddress[addressAlias] = to; if (addressAlias == 0) revert(); // Overflow. } } /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* 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().nftApprovals[id]; } /// @dev Sets `spender` as the approved account to manage token `id`, using `msgSender`. /// /// Requirements: /// - `msgSender` must be the owner or an approved operator for the token owner. function _approveNFT( address spender, uint256 id, address msgSender ) internal virtual returns (address owner) { DN404Storage storage $ = _getDN404Storage(); owner = $.aliasToAddress[_get($.oo, _ownershipIndex(id))]; if (msgSender != owner) { if (_ref($.operatorApprovals, owner, msgSender).value == 0) { revert ApprovalCallerNotOwnerNorApproved(); } } $.nftApprovals[id] = spender; _set($.mayHaveNFTApproval, id, spender != address(0)); } /// @dev Approve or remove the `operator` as an operator for `msgSender`, /// without authorization checks. function _setApprovalForAll( address operator, bool approved, address msgSender ) internal virtual { _ref(_getDN404Storage().operatorApprovals, msgSender, operator) .value = _toUint(approved); } /// @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; // `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); } // `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(_ref($.operatorApprovals, owner, operator).value); } // `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))); } // `ownerAt(uint256)`. if (fnSelector == 0x24359879) { if (msg.sender != $.mirrorERC721) revert SenderNotMirror(); if (msg.data.length < 0x24) revert(); uint256 id = _calldataload(0x04); _return(uint160(_ownerAt(id))); } // `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 {} /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ /* INTERNAL / PRIVATE HELPERS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ /// @dev Returns `(i - 1) << 1`. function _ownershipIndex(uint256 i) internal pure returns (uint256) { unchecked { return (i - 1) << 1; // Minus 1 as token IDs start from 1. } } /// @dev Returns `((i - 1) << 1) + 1`. function _ownedIndex(uint256 i) internal pure returns (uint256) { unchecked { return ((i - 1) << 1) + 1; // Minus 1 as token IDs start from 1. } } /// @dev Returns the uint32 value at `index` in `map`. function _get(Uint32Map storage map, uint256 index) internal view returns (uint32 result) { /// @solidity memory-safe-assembly assembly { let s := add(shl(96, map.slot), shr(3, index)) // Storage slot. result := and(0xffffffff, shr(shl(5, and(index, 7)), sload(s))) } } /// @dev Updates the uint32 value at `index` in `map`. function _set( Uint32Map storage map, uint256 index, uint32 value ) internal { /// @solidity memory-safe-assembly assembly { let s := add(shl(96, map.slot), shr(3, index)) // 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 Returns the uint256 value at `index` in `map`. function _get(Uint256Map storage map, uint256 index) internal view returns (uint256 result) { /// @solidity memory-safe-assembly assembly { let s := add(map.slot, index) // Storage slot calculation. result := sload(s) } } /// @dev Updates the uint256 value at `index` in `map`. function _set( Uint256Map storage map, uint256 index, uint256 value ) internal { /// @solidity memory-safe-assembly assembly { let s := add(map.slot, index) // Storage slot calculation. sstore(s, value) } } /// @dev Sets the owner alias and the owned index together. function _setOwnerAliasAndOwnedIndex( Uint32Map storage map, uint256 id, uint32 ownership, uint32 ownedIndex ) internal { /// @solidity memory-safe-assembly assembly { let i := sub(id, 1) // Index of the uint64 combined value. let s := add(shl(96, map.slot), shr(2, i)) // Storage slot. let o := shl(6, and(i, 3)) // Storage slot offset (bits). let v := sload(s) // Storage slot value. let m := 0xffffffffffffffff // Value mask. let combined := or(shl(32, ownedIndex), and(0xffffffff, ownership)) sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), combined))))) } } /// @dev Returns the boolean value of the bit at `index` in `bitmap`. function _get(Bitmap storage bitmap, uint256 index) internal view returns (bool result) { /// @solidity memory-safe-assembly assembly { let s := add(shl(96, bitmap.slot), shr(8, index)) // Storage slot. result := and(1, shr(and(0xff, index), sload(s))) } } /// @dev Updates the bit at `index` in `bitmap` to `value`. function _set( Bitmap storage bitmap, uint256 index, bool value ) internal { /// @solidity memory-safe-assembly assembly { let s := add(shl(96, bitmap.slot), shr(8, index)) // Storage slot. let o := and(0xff, index) // Storage slot offset (bits). sstore( s, or(and(sload(s), not(shl(o, 1))), shl(o, iszero(iszero(value)))) ) } } /// @dev Returns the index of the least significant unset bit in `[begin, end)`. /// If no set bit is found, returns `type(uint256).max`. function _findFirstUnset( Bitmap storage bitmap, uint256 begin, uint256 end ) internal view returns (uint256 unsetBitIndex) { /// @solidity memory-safe-assembly assembly { unsetBitIndex := not(0) // Initialize to `type(uint256).max`. let s := shl(96, bitmap.slot) // Storage offset of the bitmap. let bucket := add(s, shr(8, begin)) let lastBucket := add(s, shr(8, end)) let negBits := shl( and(0xff, begin), shr(and(0xff, begin), not(sload(bucket))) ) if iszero(negBits) { for { } 1 { } { bucket := add(bucket, 1) negBits := not(sload(bucket)) if or(negBits, gt(bucket, lastBucket)) { break } } if gt(bucket, lastBucket) { negBits := shr( and(0xff, not(end)), shl(and(0xff, not(end)), negBits) ) } } if negBits { // Find-first-set routine. let b := and(negBits, add(not(negBits), 1)) // Isolate the least significant bit. let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, b)) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, b)))) r := or(r, shl(5, lt(0xffffffff, shr(r, b)))) // For the remaining 32 bits, use a De Bruijn lookup. // forgefmt: disable-next-item r := or( r, byte( and(div(0xd76453e0, shr(r, b)), 0x1f), 0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405 ) ) r := or(shl(8, sub(bucket, s)), r) unsetBitIndex := or( r, sub(0, or(iszero(lt(r, end)), lt(r, begin))) ) } } } /// @dev Returns a storage reference to the value at (`a0`, `a1`) in `map`. function _ref( AddressPairToUint256RefMap storage map, address a0, address a1 ) internal pure returns (Uint256Ref storage ref) { /// @solidity memory-safe-assembly assembly { mstore(0x28, a1) mstore(0x14, a0) mstore(0x00, map.slot) ref.slot := keccak256(0x00, 0x48) // Clear the part of the free memory pointer that was overwritten. mstore(0x28, 0x00) } } /// @dev Wraps the NFT ID. function _wrapNFTId(uint256 id, uint256 maxId) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { result := or(mul(iszero(gt(id, maxId)), id), gt(id, maxId)) } } /// @dev Returns whether `amount` is a valid `totalSupply`. function _totalSupplyOverflows(uint256 amount) internal view returns (bool) { unchecked { return _toUint(amount > type(uint96).max) | _toUint(amount / _unit() > type(uint32).max - 1) != 0; } } /// @dev Returns `max(0, x - y)`. function _zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mul(gt(x, y), sub(x, y)) } } /// @dev Returns `b ? 1 : 0`. function _toUint(bool b) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { result := iszero(iszero(b)) } } /// @dev Struct containing packed log data for {Transfer} events to be /// emitted by the mirror NFT contract. struct _PackedLogs { uint256 offset; uint256 addressAndBit; uint256[] logs; } /// @dev Initiates memory allocation for packed logs with `n` log items. function _packedLogsMalloc(uint256 n) private pure returns (_PackedLogs memory p) { /// @solidity memory-safe-assembly assembly { // Note that `p` implicitly allocates and advances the free memory pointer by // 3 words, which we can safely mutate in `_packedLogsSend`. let logs := mload(0x40) mstore(logs, n) // Store the length. let offset := add(0x20, logs) // Skip the word for `p.logs.length`. mstore(0x40, add(offset, shl(5, n))) // Allocate memory. mstore(add(0x40, p), logs) // Set `p.logs`. mstore(p, offset) // Set `p.offset`. } } /// @dev Set the current address and the burn bit. function _packedLogsSet( _PackedLogs memory p, address a, uint256 burnBit ) private pure { /// @solidity memory-safe-assembly assembly { mstore(add(p, 0x20), or(shl(96, a), burnBit)) // Set `p.addressAndBit`. } } /// @dev Adds a packed log item to `p` with token `id`. function _packedLogsAppend(_PackedLogs memory p, uint256 id) private pure { /// @solidity memory-safe-assembly assembly { let offset := mload(p) mstore(offset, or(mload(add(p, 0x20)), shl(8, id))) // `p.addressAndBit | (id << 8)`. mstore(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(add(p, 0x40)) let o := sub(logs, 0x40) // Start of calldata to send. mstore(o, 0x263c69d6) // `logTransfer(uint256[])`. mstore(add(o, 0x20), 0x20) // Offset of `logs` in the calldata to send. let n := add(0x44, shl(5, mload(logs))) // Length of calldata to send. if iszero( and( eq(mload(o), 1), call(gas(), mirror, 0, add(o, 0x1c), n, o, 0x20) ) ) { revert(o, 0x00) } } } /// @dev Struct of temporary variables for transfers. struct _TransferTemps { uint256 numNFTBurns; uint256 numNFTMints; uint256 fromBalance; uint256 toBalance; uint256 fromOwnedLength; uint256 toOwnedLength; uint256 totalSupply; uint256 totalNFTSupply; } /// @dev Struct of temporary variables for mints. struct _DNMintTemps { uint256 toEnd; uint32 toAlias; } /// @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) } } }
{ "optimizer": { "enabled": true, "runs": 200 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"DNAlreadyInitialized","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InvalidOperation","type":"error"},{"inputs":[],"name":"InvalidSender","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"LinkMirrorContractFailed","type":"error"},{"inputs":[],"name":"MirrorAddressIsZero","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"ReachedMaxBuy","type":"error"},{"inputs":[],"name":"Reentrancy","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"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnitIsZero","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":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Buy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"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":"a","type":"address"}],"name":"addressAlias","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"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":"burnReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"buyDnner","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"eat","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getNFTSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"getSkipNFT","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"getTokensOfOwner","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialNFTValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"lastNFTUnit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"}],"name":"lockedBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxBuy","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxNFTSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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":"view","type":"function"},{"inputs":[],"name":"openBuy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"openNextStage","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"openSetSkipNFT","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"openUnlock","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"reserve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseURI_","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"maxBuy_","type":"uint256"}],"name":"setMaxBuy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"setMaxNFTSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint120","name":"price_","type":"uint120"}],"name":"setPrices","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"setSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"skip","type":"bool"}],"name":"setSkipNFT","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"setTeamWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"toggleOpenBuy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"toggleOpenNextStage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"toggleOpenSetSkipNFT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"toggleOpenUnlock","outputs":[],"stateMutability":"nonpayable","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":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"}],"name":"unlock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"wrap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
Contract Creation Code
6080604052670de0b6b3a764000060035561271060045561251c6005556611c37937e08000600655603c6007556008805463ffffffff191663010000001790553480156200004b575f80fd5b5060408051808201909152600581526422373732b960d91b60208201525f90620000769082620005a8565b5060408051808201909152600581526422272722a960d91b6020820152600190620000a29082620005a8565b50620000ae3362000172565b60045468a20d6e21d0e525530955620000c9306001620001ad565b620000d43362000217565b620000f37359d5fbf6297f58d0e6e29edda4457e860ac3935a62000243565b5f336040516200010390620004fc565b6001600160a01b039091168152602001604051809103905ff0801580156200012d573d5f803e3d5ffd5b5090506200016b6200013e60035490565b6004546200014e90601362000674565b6200015a919062000674565b6001600160601b031633836200027a565b50620006b2565b6001600160a01b0316638b78c6d819819055805f7f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a350565b5f620001b98362000457565b8054909150600216151582151514620001e157805460ff8082166002181660ff199091161781555b8115155f528260601b60601c7fb5a1de456fff688115a4f75380060c23c8532d14ff85f687cc871456d642039360205fa2505050565b62000221620004a2565b600980546001600160a01b0319166001600160a01b0392909216919091179055565b6200024d620004a2565b600880546001600160a01b0390921664010000000002600160201b600160c01b0319909216919091179055565b68a20d6e21d0e52553088054640100000000900463ffffffff1615620002b357604051633ab534b960e21b815260040160405180910390fd5b6001600160a01b038216620002db576040516339a84a7b60e01b815260040160405180910390fd5b630f4599e55f523360205260205f6024601c5f865af160015f511416620003095763d125259c5f526004601cfd5b805464010000000063ffffffff60201b199091161781556002810180546001600160601b03166c010000000000000000000000006001600160a01b038516021790556003545f036200036e57604051630af2b2b160e01b815260040160405180910390fd5b831562000451576001600160a01b0383166200039d57604051633a954ecd60e21b815260040160405180910390fd5b620003a884620004bf565b15620003c75760405163e5cfe95760e01b815260040160405180910390fd5b6002810180546001600160601b0319166001600160601b0386161790555f620003f08462000457565b8054600160481b600160a81b03191669010000000000000000006001600160601b038816021781555f8681529091506001600160a01b038516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602082a3505b50505050565b6001600160a01b0381165f90815268a20d6e21d0e52553136020526040812080549091600190911690036200049d57805460ff191660ff6002843b151502600117161781555b919050565b638b78c6d819543314620004bd576382b429005f526004601cfd5b565b5f620004e963fffffffe620004d360035490565b8481620004e457620004e46200069e565b041190565b6001600160601b03831117151592915050565b610c9180620037e483390190565b634e487b7160e01b5f52604160045260245ffd5b600181811c908216806200053357607f821691505b6020821081036200055257634e487b7160e01b5f52602260045260245ffd5b50919050565b601f821115620005a357805f5260205f20601f840160051c810160208510156200057f5750805b601f840160051c820191505b81811015620005a0575f81556001016200058b565b50505b505050565b81516001600160401b03811115620005c457620005c46200050a565b620005dc81620005d584546200051e565b8462000558565b602080601f83116001811462000612575f8415620005fa5750858301515b5f19600386901b1c1916600185901b1785556200066c565b5f85815260208120601f198616915b82811015620006425788860151825594840194600190910190840162000621565b50858210156200066057878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b80820281158282048414176200069857634e487b7160e01b5f52601160045260245ffd5b92915050565b634e487b7160e01b5f52601260045260245ffd5b61312480620006c05f395ff3fe6080604052600436106102e7575f3560e01c80637501f7411161018f578063c87b56dd116100db578063f04e283e11610094578063f2fde38b1161006e578063f2fde38b14610cc6578063f53bc83514610cd9578063f7438c5e14610cf8578063fee81cf414610d0c576102ee565b8063f04e283e14610c6b578063f157574314610c7e578063f16f4eb414610ca7576102ee565b8063c87b56dd14610b9d578063cfefe4a914610bbc578063d4a6793014610bdb578063d7a18e9414610bfb578063dd62ed3e14610c10578063e159c14314610c4d576102ee565b8063a035b1fe11610148578063a9059cbb11610122578063a9059cbb14610aef578063bb95a21814610b0e578063be5a246314610b22578063bf376c7a14610b7e576102ee565b8063a035b1fe14610aa7578063a1e063e714610abc578063a21b694614610ad0576102ee565b80637501f741146109eb578063819b25ba14610a005780638da5cb5b14610a1f5780638df4164514610a3757806395d89b4114610a4b5780639ae697bf14610a5f576102ee565b80632a6a935d1161024e57806354d1f13d116102075780636c19e783116101e15780636c19e7831461099057806370a08231146109af57806370db69d6146109ce578063715018a6146109e3576102ee565b806354d1f13d1461093d57806355f804b3146109455780635de6dc5514610964576102ee565b80632a6a935d1461087d5780632f6c493c1461089c578063313ce567146108bb5780633ccfd60b146108d65780634cf294ab146108ea5780634ef41efc146108fd576102ee565b806318160ddd116102a057806318160ddd146107de57806323b872dd1461080357806324c979a3146108225780632569296214610837578063269822461461083f578063274e430b1461085e576102ee565b806302a794541461071057806302b9a97e1461073757806306fdde0314610760578063077a24c814610781578063095ea7b3146107a05780631525ff7d146107bf576102ee565b366102ee57005b68a20d6e21d0e52553085f3560e01c63e5eb36c8819003610371576002820154600160601b90046001600160a01b0316331461033d5760405163ce5a776b60e01b815260040160405180910390fd5b608436101561034a575f80fd5b60043560243560443560643561036284848484610d3d565b61036c60016110cb565b505050505b8063813500fc036103f8576002820154600160601b90046001600160a01b031633146103b05760405163ce5a776b60e01b815260040160405180910390fd5b60643610156103bd575f80fd5b6004356028818152604435601481905268a20d6e21d0e525530c5f9081526048812092526024351515918290556103f460016110cb565b5050505b8063e985e9c503610473576002820154600160601b90046001600160a01b031633146104375760405163ce5a776b60e01b815260040160405180910390fd5b6044361015610444575f80fd5b60243560288181526004803560148190529085015f9081526048812092529054909190610470906110cb565b50505b80636352211e036104de576002820154600160601b90046001600160a01b031633146104b25760405163ce5a776b60e01b815260040160405180910390fd5b60243610156104bf575f80fd5b6004356104dc6104ce826110d3565b6001600160a01b03166110cb565b505b8063243598790361053b576002820154600160601b90046001600160a01b0316331461051d5760405163ce5a776b60e01b815260040160405180910390fd5b602436101561052a575f80fd5b6004356105396104ce82611109565b505b8063d10b6e0c036105a2576002820154600160601b90046001600160a01b0316331461057a5760405163ce5a776b60e01b815260040160405180910390fd5b6064361015610587575f80fd5b60043560243560443561059e6104ce84848461115e565b5050505b8063081812fc036105ff576002820154600160601b90046001600160a01b031633146105e15760405163ce5a776b60e01b815260040160405180910390fd5b60243610156105ee575f80fd5b6004356105fd6104ce8261124b565b505b8063f5b100ea0361068c576002820154600160601b90046001600160a01b0316331461063e5760405163ce5a776b60e01b815260040160405180910390fd5b602436101561064b575f80fd5b60043561068a610685826001600160a01b03165f90815268a20d6e21d0e5255313602052604090205463ffffffff600160281b9091041690565b6110cb565b505b8063e2c79281036106f9576002820154600160601b90046001600160a01b031633146106cb5760405163ce5a776b60e01b815260040160405180910390fd5b60043610156106d8575f80fd5b68a20d6e21d0e5255308546106f990600160601b900463ffffffff166110cb565b8063b7a94eb80361070e5761070e60016110cb565b005b34801561071b575f80fd5b50610724611295565b6040519081526020015b60405180910390f35b348015610742575f80fd5b506008546107509060ff1681565b604051901515815260200161072e565b34801561076b575f80fd5b506107746112b8565b60405161072e9190612be4565b34801561078c575f80fd5b5061070e61079b366004612c16565b611347565b3480156107ab575f80fd5b506107506107ba366004612c52565b61135d565b3480156107ca575f80fd5b5061070e6107d9366004612c7a565b611372565b3480156107e9575f80fd5b5068a20d6e21d0e525530a546001600160601b0316610724565b34801561080e575f80fd5b5061075061081d366004612c93565b61139c565b34801561082d575f80fd5b5061072460045481565b61070e611408565b34801561084a575f80fd5b506008546107509062010000900460ff1681565b348015610869575f80fd5b50610750610878366004612c7a565b611455565b348015610888575f80fd5b50610750610897366004612ccc565b611495565b3480156108a7575f80fd5b5061070e6108b6366004612c7a565b6114d0565b3480156108c6575f80fd5b506040516012815260200161072e565b3480156108e1575f80fd5b5061070e61158a565b61070e6108f8366004612d30565b6115df565b348015610908575f80fd5b5068a20d6e21d0e525530a54600160601b90046001600160a01b03165b6040516001600160a01b03909116815260200161072e565b61070e6117c0565b348015610950575f80fd5b5061070e61095f366004612d78565b6117f9565b34801561096f575f80fd5b5061098361097e366004612c7a565b611813565b60405161072e9190612db7565b34801561099b575f80fd5b5061070e6109aa366004612c7a565b61181e565b3480156109ba575f80fd5b506107246109c9366004612c7a565b611854565b3480156109d9575f80fd5b5061072460075481565b61070e611886565b3480156109f6575f80fd5b5061072460055481565b348015610a0b575f80fd5b5061070e610a1a366004612dfa565b611899565b348015610a2a575f80fd5b50638b78c6d81954610925565b348015610a42575f80fd5b5061070e611927565b348015610a56575f80fd5b50610774611943565b348015610a6a575f80fd5b50610724610a79366004612c7a565b6001600160a01b03165f90815268a20d6e21d0e525531360205260409020600101546001600160601b031690565b348015610ab2575f80fd5b5061072460065481565b348015610ac7575f80fd5b5061070e611952565b348015610adb575f80fd5b5061070e610aea366004612dfa565b611979565b348015610afa575f80fd5b50610750610b09366004612c52565b611992565b348015610b19575f80fd5b5061070e61199e565b348015610b2d575f80fd5b50610b69610b3c366004612c7a565b6001600160a01b03165f90815268a20d6e21d0e52553136020526040902054610100900463ffffffff1690565b60405163ffffffff909116815260200161072e565b348015610b89575f80fd5b5061070e610b98366004612c52565b6119c3565b348015610ba8575f80fd5b50610774610bb7366004612dfa565b611ae9565b348015610bc7575f80fd5b50610724610bd6366004612dfa565b611b32565b348015610be6575f80fd5b50600854610750906301000000900460ff1681565b348015610c06575f80fd5b5061072460035481565b348015610c1b575f80fd5b50610724610c2a366004612e11565b602890815260149190915268a20d6e21d0e525530f5f9081526048812091525490565b348015610c58575f80fd5b5060085461075090610100900460ff1681565b61070e610c79366004612c7a565b611b46565b348015610c89575f80fd5b5068a20d6e21d0e525530854600160601b900463ffffffff16610724565b348015610cb2575f80fd5b5061070e610cc1366004612c52565b611b80565b61070e610cd4366004612c7a565b611dc5565b348015610ce4575f80fd5b5061070e610cf3366004612dfa565b611deb565b348015610d03575f80fd5b5061070e611df8565b348015610d17575f80fd5b50610724610d26366004612c7a565b63389a75e1600c9081525f91909152602090205490565b68a20d6e21d0e52553086001600160a01b038416610d6e57604051633a954ecd60e21b815260040160405180910390fd5b600a8101600382015f610da6835f19880160011b5b5f8160031c8360601b0180546007841660051b1c63ffffffff1691505092915050565b63ffffffff16815260208101919091526040015f20546001600160a01b03878116911614610de65760405162a1148160e81b815260040160405180910390fd5b856001600160a01b0316836001600160a01b031614610e595760288381526014879052600483015f908152604881209152545f03610e59575f8481526005830160205260409020546001600160a01b03848116911614610e5957604051632ce44b5f60e11b815260040160405180910390fd5b5f610e6387611e21565b90505f610e6f87611e21565b90505f610e7b60035490565b60018401549091506001600160601b03165f610e9a600c88018a015490565b90505f670de0b6b3a76400008211610eba57670de0b6b3a7640000610ebc565b815b905082811115610edf57604051631e9acf1760e31b815260040160405180910390fd5b6001860180546001600160601b0319168285036001600160601b0316179055898801600c018490555050506001820180546001600160601b038082168401166001600160601b03199091161790556001600160a01b0389165f9081526008860160208190526040909120610f6a600688018a60609190911b600882901c0154600160ff9092161c1690565b15610fa7576006870160601b60088a901c018054600160ff8c161b191690555f898152600588016020526040902080546001600160a01b03191690555b845463ffffffff60281b198116600160281b9182900463ffffffff9081165f1901808216909302919091178755606083901b631fffffff600384901c1601545f9260e060059190911b161c1690505f611009885f198d01600190811b01610d83565b905061101c838263ffffffff1684611e6f565b611036885f1963ffffffff851601600190811b0183611e6f565b5050835463ffffffff60281b198116600160281b9182900463ffffffff908116600181019091169092021785556001600160a01b038b165f90815260208490526040902061108590828c611e6f565b61109a878b611094888f611e9c565b84611f26565b505050805f528760601b60601c8960601b60601c5f805160206130cf83398151915260205fa3505050505050505050565b805f5260205ff35b5f6110dd82611f6b565b6110fa5760405163677510db60e11b815260040160405180910390fd5b61110382611109565b92915050565b5f68a20d6e21d0e525530868a20d6e21d0e525530b8261113868a20d6e21d0e52553125f19870160011b610d83565b63ffffffff16815260208101919091526040015f20546001600160a01b03169392505050565b5f68a20d6e21d0e525530868a20d6e21d0e525530b8261118d68a20d6e21d0e52553125f19880160011b610d83565b63ffffffff16815260208101919091526040015f20546001600160a01b039081169250831682146111f05760288381526014839052600482015f908152604881209152545f036111f0576040516367d9dca160e11b815260040160405180910390fd5b5f848152600582016020526040902080546001600160a01b0319166001600160a01b0387169081179091556006820160601b600886901c018054600160ff881690811b1991909116921515901b919091179055509392505050565b5f61125582611f6b565b6112725760405163677510db60e11b815260040160405180910390fd5b505f90815268a20d6e21d0e525530d60205260409020546001600160a01b031690565b5f612710619c406003546112a99190612e56565b6112b39190612e81565b905090565b60605f80546112c690612ea0565b80601f01602080910402602001604051908101604052809291908181526020018280546112f290612ea0565b801561133d5780601f106113145761010080835404028352916020019161133d565b820191905f5260205f20905b81548152906001019060200180831161132057829003601f168201915b5050505050905090565b61134f611f87565b6001600160781b0316600655565b5f611369338484611fa1565b50600192915050565b61137a611f87565b600980546001600160a01b0319166001600160a01b0392909216919091179055565b336028908152601484905268a20d6e21d0e525530f5f908152604881209181905281549091905f1981146113f157808411156113eb576040516313be252b60e01b815260040160405180910390fd5b83810382555b6113fc868686612001565b50600195945050505050565b5f6202a30067ffffffffffffffff164201905063389a75e1600c52335f52806020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d5f80a250565b6001600160a01b0381165f90815268a20d6e21d0e5255313602052604081208054600116820361148957823b5b9392505050565b54600216151592915050565b6008545f9060ff16151581036114be57604051631cc6a69960e11b815260040160405180910390fd5b6114c83383612170565b506001919050565b3068929eee149b4bd2126854036114ee5763ab143c065f526004601cfd5b3068929eee149b4bd212685580336001600160a01b0382161461152457604051636edaef2f60e11b815260040160405180910390fd5b33321461154457604051636edaef2f60e11b815260040160405180910390fd5b60085462010000900460ff1615155f0361157157604051631cc6a69960e11b815260040160405180910390fd5b61157a826121d7565b503868929eee149b4bd212685550565b611592611f87565b3068929eee149b4bd2126854036115b05763ab143c065f526004601cfd5b3068929eee149b4bd21268556009546115d1906001600160a01b03166122a1565b3868929eee149b4bd2126855565b3332146115ff57604051636edaef2f60e11b815260040160405180910390fd5b5f61160b600185612ed8565b90505f6001851161161d57600161162e565b600a851161162b578461162e565b600a5b6008549091506301000000900460ff1615155f0361165f57604051631cc6a69960e11b815260040160405180910390fd5b335f90815268a20d6e21d0e52553136020526040902054610100900463ffffffff161561169f57604051631cc6a69960e11b815260040160405180910390fd5b6007548511156116c257604051632257306f60e11b815260040160405180910390fd5b6001851180156116de575034826006546116dc9190612e56565b115b156116fc57604051631e9acf1760e31b815260040160405180910390fd5b60055468a20d6e21d0e5255308548690600160601b900463ffffffff166117239190612eeb565b11156117425760405163e5cfe95760e01b815260040160405180910390fd5b61174c84846122ba565b61176957604051638baa579f60e01b815260040160405180910390fd5b61178633670de0b6b3a76400005b6117819084612e56565b612380565b6040518581527f3e32821836f4caf5b64b2c8c6b460049a9797526960d31502f7575b8da39d5ae9060200160405180910390a15050505050565b63389a75e1600c52335f525f6020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c925f80a2565b611801611f87565b600261180e828483612f5d565b505050565b60606111038261266f565b611826611f87565b600880546001600160a01b0390921664010000000002640100000000600160c01b0319909216919091179055565b6001600160a01b03165f90815268a20d6e21d0e52553136020526040902054600160481b90046001600160601b031690565b61188e611f87565b6118975f6127b1565b565b6009546001600160a01b031633146118c457604051636edaef2f60e11b815260040160405180910390fd5b68a20d6e21d0e52553095468a20d6e21d0e5255308548290600160601b900463ffffffff166118f39190612eeb565b11156119125760405163e5cfe95760e01b815260040160405180910390fd5b61192433670de0b6b3a7640000611777565b50565b61192f611f87565b6008805460ff19811660ff90911615179055565b6060600180546112c690612ea0565b61195a611f87565b6008805462ff0000198116620100009182900460ff1615909102179055565b611981611f87565b6119248168a20d6e21d0e525530955565b5f611369338484612001565b6119a6611f87565b6008805461ff001981166101009182900460ff1615909102179055565b81336001600160a01b038216146119ed57604051636edaef2f60e11b815260040160405180910390fd5b333214611a0d57604051636edaef2f60e11b815260040160405180910390fd5b5f60035483611a1c9190612e56565b600854909150610100900460ff1615155f03611a4b57604051631cc6a69960e11b815260040160405180910390fd5b68a20d6e21d0e52553095468a20d6e21d0e5255308548490600160601b900463ffffffff16611a7a9190612eeb565b1115611a995760405163e5cfe95760e01b815260040160405180910390fd5b80611aa333611854565b1015611ac257604051631e9acf1760e31b815260040160405180910390fd5b611ad833611781670de0b6b3a764000086612e56565b611ae3843083612001565b50505050565b606060028054611af890612ea0565b159050611b2d576002611b0a836127ee565b604051602001611b1b929190613017565b60405160208183030381529060405290505b919050565b68a20d6e21d0e52553148101545f90611103565b611b4e611f87565b63389a75e1600c52805f526020600c208054421115611b7457636f5e88185f526004601cfd5b5f9055611924816127b1565b3068929eee149b4bd212685403611b9e5763ab143c065f526004601cfd5b3068929eee149b4bd212685581336001600160a01b03821614611bd457604051636edaef2f60e11b815260040160405180910390fd5b333214611bf457604051636edaef2f60e11b815260040160405180910390fd5b5f82600354611c039190612e56565b600854909150610100900460ff1615155f03611c3257604051631cc6a69960e11b815260040160405180910390fd5b335f90815268a20d6e21d0e525531360205260409020548390600160281b900463ffffffff161015611c7757604051631e9acf1760e31b815260040160405180910390fd5b80611c8133611854565b1015611ca057604051631e9acf1760e31b815260040160405180910390fd5b5f80611cc168a20d6e21d0e52553085463ffffffff600160601b9091041690565b90505f5b85811015611d3e57611cd5611295565b611cdf9084612eeb565b92508180600190039250506305f5e10082614e20619c40600354611d039190612e56565b611d0d9190612e56565b611d179190612e81565b611d219190612e81565b60035f828254611d319190612eeb565b9091555050600101611cc5565b50611d5c33611d55670de0b6b3a764000088612e56565b6001612830565b611d703033611d6b8686612ed8565b612001565b60408051868152602081018490529081018490527f5af801e983cfd842de8b0797a2d6077db3ac2dea16571e0c7f5080291ca1b9349060600160405180910390a1505050503868929eee149b4bd21268555050565b611dcd611f87565b8060601b611de257637448fbae5f526004601cfd5b611924816127b1565b611df3611f87565b600755565b611e00611f87565b6008805463ff00000019811663010000009182900460ff1615909102179055565b6001600160a01b0381165f90815268a20d6e21d0e5255313602052604081208054909160019091169003611b2d575f6002833b1515835460ff191660ff919092026001171617825550919050565b8160031c8360601b016007831660051b815463ffffffff8482841c188116831b8218845550505050505050565b8154610100900463ffffffff1668a20d6e21d0e52553085f829003611f1f57805463ffffffff198116600163ffffffff928316019182169081178355855464ffffffff00191661010082021786555f818152600384016020526040812080546001600160a01b0319166001600160a01b0388161790559193509003611f1f575f80fd5b5092915050565b600183038060021c8560601b016003821660061b9150805467ffffffffffffffff8563ffffffff168560201b178083861c188216851b83188455505050505050505050565b5f80611f7683611109565b6001600160a01b0316141592915050565b638b78c6d819543314611897576382b429005f526004601cfd5b6028828152601484905268a20d6e21d0e525530f5f9081526048812091528190555f8181526001600160a01b0380841691908516907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92590602090a3505050565b6001600160a01b03821661202857604051633a954ecd60e21b815260040160405180910390fd5b68a20d6e21d0e52553085f61203c85611e21565b90505f61204885611e21565b905061208a6040518061010001604052805f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81525090565b825463ffffffff600160281b808304821660808501528454041660a083015260028501546001600160601b0390811660c0840152600160481b90910416604082018190528511156120ee57604051631e9acf1760e31b815260040160405180910390fd5b6040810180518690039081905283546001600160601b03918216600160481b9081026bffffffffffffffffffffffff60481b19928316178655845481810484168901606086018190529093160291161782555f8581526001600160a01b0380881691908916905f805160206130cf83398151915290602090a350505050505050565b5f61217a83611e21565b80549091506002161515821515146121a157805460ff8082166002181660ff199091161781555b8115155f528260601b60601c7fb5a1de456fff688115a4f75380060c23c8532d14ff85f687cc871456d642039360205fa2505050565b6001600160a01b0381165f90815268a20d6e21d0e52553136020526040902060018082015468a20d6e21d0e525530892916001600160601b03909116906122219085908390612830565b815481908390600990612245908490600160481b90046001600160601b031661309a565b82546001600160601b039182166101009390930a9283029190920219909116179055506001820180546001600160601b03191690555f8181526001600160a01b038516905f805160206130cf833981519152602082a350505050565b5f385f3847855af16119245763b12d13eb5f526004601cfd5b6040516001600160601b03193360601b1660208201525f90819061231b90603401604051602081830303815290604052805190602001206020527b19457468657265756d205369676e6564204d6573736167653a0a33325f52603c60042090565b90505f61235d8286868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250612ab892505050565b60085464010000000090046001600160a01b039081169116149250505092915050565b6001600160a01b0382166123a757604051633a954ecd60e21b815260040160405180910390fd5b68a20d6e21d0e52553085f6123bb84611e21565b6002830180546001600160601b03808216870181166001600160601b03199283161790925584546001840180548085168901948516931692909217909155825492935063ffffffff600160601b90910481168601925f9291600160281b9091041661242b670de0b6b3a764000090565b878161243957612439612e6d565b86549190049190910192506002165f039050612646576001600160a01b0386165f908152600885016020526040812084549091600a870191600160281b900463ffffffff169061248e82860383871102612b43565b90508060400151515f146126415760608a901b6020820152875463ffffffff600160401b82048116916401000000009004165f6124cb8a8e611e9c565b6040850151518c5463ffffffff600160601b8083048216909301811690920263ffffffff60601b19909116178d558b54908a16600160281b0263ffffffff60281b19909116178b5590505b5f8315612566575f1993909301600381901c60098d0160601b0154909390600585901b60e0161c63ffffffff1663ffffffff169050612561600c8d0182670de0b6b3a7640000910155565b6125a5565b50815b612579875f19830160011b610d83565b63ffffffff161561259557600101898111801590910217612569565b600181018a811180159091021792505b6125b0888783611e6f565b6125c287828489806001019a50611f26565b8451602080870151600884901b17825201855250878503612516578a5463ffffffff848116600160401b0263ffffffff60401b1991851664010000000002919091166bffffffffffffffff000000001990921691909117178b5560028b015461263d9085906001600160a01b03600160601b90910416612b88565b5050505b505050505b50505f8381526001600160a01b038516905f805160206130cf833981519152602082a350505050565b6001600160a01b0381165f90815268a20d6e21d0e5255313602052604090205460609068a20d6e21d0e525530890600160281b900463ffffffff168067ffffffffffffffff8111156126c3576126c3612efe565b6040519080825280602002602001820160405280156126ec578160200160208202803683370190505b5092505f5b818110156127a9576001600160a01b0385165f908152600884016020526040812060601b600383901c015463ffffffff60e0600585901b169190911c1663ffffffff16905080858381518110612749576127496130ba565b602002602001018181525050856001600160a01b031661276882611109565b6001600160a01b03161461277a575f80fd5b81612791600a86015f198401600190811b01610d83565b63ffffffff16146127a0575f80fd5b506001016126f1565b505050919050565b638b78c6d81980546001600160a01b039092169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a355565b60606080604051019050602081016040525f8152805f19835b928101926030600a8206018453600a900480612807575050819003601f19909101908152919050565b68a20d6e21d0e52553085f61284485611e21565b90508215612a0c576001600160a01b0385165f908152600883016020526040812082549091600160281b90910463ffffffff1690670de0b6b3a7640000878161288f5761288f612e6d565b0490508015612a04575f6128a282612b43565b60608a901b60011760208201529050855463ffffffff600160601b808304821685900380831690910263ffffffff60601b199093169290921788558654848603808316600160281b0263ffffffff60281b199092169190911788558854600a8a0192600160401b909104165f5b606089901b5f1998909801600381901c989098015463ffffffff60e060058b901b169190911c168b8101600c0154670de0b6b3a7640000811161295a57670de0b6b3a764000061295c565b805b8301925061296c86835f80611f26565b87516020808a0151600885901b1782520188526129938d6009018580600101965084611e6f565b505082880361290f5760018a0180546001600160601b031981166001600160601b039182168490039091161790558a5463ffffffff60401b1916600160401b63ffffffff841602178b5560028b01546129fd908790600160601b90046001600160a01b0316612b88565b5050505050505b505050612a6d565b8054600160481b90046001600160601b031680851115612a3f57604051631e9acf1760e31b815260040160405180910390fd5b81546bffffffffffffffffffffffff60481b1916600160481b918690036001600160601b0316919091021781555b6002820180546001600160601b03808216879003166001600160601b03199091161790555f8481526001600160a01b0386165f805160206130cf833981519152602083a35050505050565b604051600190835f5260208301516040526040835103612af357604083015160ff81901c601b016020526001600160ff1b0316606052612b17565b6041835103612b135760608301515f1a6020526040830151606052612b17565b5f91505b6020600160805f855afa5191503d612b3657638baa579f5f526004601cfd5b5f60605260405292915050565b612b6560405180606001604052805f81526020015f8152602001606081525090565b604051828152806020018360051b81016040528183604001528083525050919050565b60408201516040810363263c69d68152602080820152815160051b604401915060208183601c84015f875af1600182511416611ae3575f81fd5b5f5b83811015612bdc578181015183820152602001612bc4565b50505f910152565b602081525f8251806020840152612c02816040850160208701612bc2565b601f01601f19169190910160400192915050565b5f60208284031215612c26575f80fd5b81356001600160781b0381168114611482575f80fd5b80356001600160a01b0381168114611b2d575f80fd5b5f8060408385031215612c63575f80fd5b612c6c83612c3c565b946020939093013593505050565b5f60208284031215612c8a575f80fd5b61148282612c3c565b5f805f60608486031215612ca5575f80fd5b612cae84612c3c565b9250612cbc60208501612c3c565b9150604084013590509250925092565b5f60208284031215612cdc575f80fd5b81358015158114611482575f80fd5b5f8083601f840112612cfb575f80fd5b50813567ffffffffffffffff811115612d12575f80fd5b602083019150836020828501011115612d29575f80fd5b9250929050565b5f805f60408486031215612d42575f80fd5b83359250602084013567ffffffffffffffff811115612d5f575f80fd5b612d6b86828701612ceb565b9497909650939450505050565b5f8060208385031215612d89575f80fd5b823567ffffffffffffffff811115612d9f575f80fd5b612dab85828601612ceb565b90969095509350505050565b602080825282518282018190525f9190848201906040850190845b81811015612dee57835183529284019291840191600101612dd2565b50909695505050505050565b5f60208284031215612e0a575f80fd5b5035919050565b5f8060408385031215612e22575f80fd5b612e2b83612c3c565b9150612e3960208401612c3c565b90509250929050565b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141761110357611103612e42565b634e487b7160e01b5f52601260045260245ffd5b5f82612e9b57634e487b7160e01b5f52601260045260245ffd5b500490565b600181811c90821680612eb457607f821691505b602082108103612ed257634e487b7160e01b5f52602260045260245ffd5b50919050565b8181038181111561110357611103612e42565b8082018082111561110357611103612e42565b634e487b7160e01b5f52604160045260245ffd5b601f82111561180e57805f5260205f20601f840160051c81016020851015612f375750805b601f840160051c820191505b81811015612f56575f8155600101612f43565b5050505050565b67ffffffffffffffff831115612f7557612f75612efe565b612f8983612f838354612ea0565b83612f12565b5f601f841160018114612fba575f8515612fa35750838201355b5f19600387901b1c1916600186901b178355612f56565b5f83815260208120601f198716915b82811015612fe95786850135825560209485019460019092019101612fc9565b5086821015613005575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b5f80845461302481612ea0565b6001828116801561303c57600181146130515761307d565b60ff198416875282151583028701945061307d565b885f526020805f205f5b858110156130745781548a82015290840190820161305b565b50505082870194505b505050508351613091818360208801612bc2565b01949350505050565b6001600160601b03818116838216019080821115611f1f57611f1f612e42565b634e487b7160e01b5f52603260045260245ffdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212205a27c1c83c35616a2b070aee75982c2824c85697d130a4694a2db1b468b3d68464736f6c63430008180033608060405234801561000f575f80fd5b50604051610c91380380610c9183398101604081905261002e9161005b565b683602298b8c10b0123180546001600160a01b0319166001600160a01b0392909216919091179055610088565b5f6020828403121561006b575f80fd5b81516001600160a01b0381168114610081575f80fd5b9392505050565b610bfc806100955f395ff3fe60806040526004361061010c575f3560e01c80636cef16e61161009457806397e5311c1161006357806397e5311c1461043a578063a22cb4651461044e578063b88d4fde1461046d578063c87b56dd1461048c578063e985e9c5146104ab57610113565b80636cef16e6146103ce57806370a08231146103e25780638da5cb5b1461040157806395d89b411461042657610113565b806318160ddd116100db57806318160ddd1461033c57806323b872dd1461035e578063243598791461037d57806342842e0e1461039c5780636352211e146103af57610113565b806301ffc9a71461027457806306fdde03146102c5578063081812fc146102e6578063095ea7b31461031d57610113565b3661011357005b683602298b8c10b012305f3560e01c63263c69d68190036101d85781546001600160a01b0316331461015857604051631b1e598960e11b815260040160405180910390fd5b602036103d5f3e6004356024018036103d5f3e602081033560051b81018036103d5f3e5b8082146101cd5781358060601c816001168260a01b60a81c811583028284027fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f38a450505081602001915061017c565b505060015f5260205ff35b80630f4599e5036102725760018201546001600160a01b03161561022e5760018201546001600160a01b03166004356001600160a01b03161461022e576040516362cf623d60e11b815260040160405180910390fd5b81546001600160a01b03161561025757604051635fb2b52360e11b815260040160405180910390fd5b81546001600160a01b0319163317825560015f908152602090f35b005b34801561027f575f80fd5b506102b061028e3660046109a3565b6301ffc9a760e09190911c9081146380ac58cd821417635b5e139f9091141790565b60405190151581526020015b60405180910390f35b3480156102d0575f80fd5b506102d96104ca565b6040516102bc91906109d1565b3480156102f1575f80fd5b50610305610300366004610a1d565b6104df565b6040516001600160a01b0390911681526020016102bc565b348015610328575f80fd5b50610272610337366004610a4f565b6104f5565b348015610347575f80fd5b50610350610575565b6040519081526020016102bc565b348015610369575f80fd5b50610272610378366004610a77565b610585565b348015610388575f80fd5b50610305610397366004610a1d565b610611565b6102726103aa366004610a77565b610621565b3480156103ba575f80fd5b506103056103c9366004610a1d565b610652565b3480156103d9575f80fd5b50610272610662565b3480156103ed575f80fd5b506103506103fc366004610ab0565b610711565b34801561040c575f80fd5b50683602298b8c10b01232546001600160a01b0316610305565b348015610431575f80fd5b506102d961072a565b348015610445575f80fd5b5061030561073a565b348015610459575f80fd5b50610272610468366004610ac9565b61076f565b348015610478575f80fd5b50610272610487366004610b02565b6107ec565b348015610497575f80fd5b506102d96104a6366004610a1d565b610846565b3480156104b6575f80fd5b506102b06104c5366004610b95565b610856565b60606104da6306fdde035f610881565b905090565b5f6104ef63081812fc835f6108d7565b92915050565b5f6104fe61073a565b90508260601b60601c925060405163d10b6e0c5f5283602052826040523360605260205f6064601c34865af1601f3d111661053b573d5f823e3d81fd5b80604052505f6060528183600c5160601c7f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9255f38a4505050565b5f6104da63e2c792815f806108d7565b5f61058e61073a565b90508360601b60601c93508260601b60601c925060405163e5eb36c881528460208201528360408201528260608201523360808201526020816084601c840134865af16001825114166105e3573d5f823e3d81fd5b508183857fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f38a450505050565b5f6104ef6324359879835f6108d7565b61062c838383610585565b813b1561064d5761064d83838360405180602001604052805f81525061091a565b505050565b5f6104ef636352211e835f6108d7565b5f8061066c61073a565b9050638da5cb5b5f5260205f6004601c845afa601f3d11161561069257600c5160601c91505b683602298b8c10b0123254683602298b8c10b01230906001600160a01b03908116908416811461070b576002820180546001600160a01b0319166001600160a01b0386811691821790925560405190918316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35b50505050565b5f6104ef63f5b100ea836001600160a01b03165f6108d7565b60606104da6395d89b415f610881565b683602298b8c10b01230546001600160a01b03168061076c57604051632d9523d760e11b815260040160405180910390fd5b90565b5f61077861073a565b90508260601b60601c925060405163813500fc5f52836020528215156040523360605260205f6064601c34865af160015f5114166107b8573d5f823e3d81fd5b83337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160206040a360405250505f60605250565b6107f7858585610585565b833b1561083f5761083f85858585858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061091a92505050565b5050505050565b60606104ef63c87b56dd83610881565b5f61087863e985e9c5846001600160a01b0316846001600160a01b03166108d7565b15159392505050565b60605f61088c61073a565b90506040519150835f52826020525f806024601c845afa6108af573d5f833e3d82fd5b60205f803e60205f51833e815160205f5101602084013e815160208301016040525092915050565b5f806108e161073a565b9050604051855f52846020528360405260205f6044601c855afa601f3d111661090c573d5f823e3d81fd5b60405250505f519392505050565b60405163150b7a028082523360208301528560601b60601c604083015283606083015260808083015282518060a08401528015610961578060c08401826020870160045afa505b60208360a48301601c86015f8a5af1610982573d15610982573d5f843e3d83fd5b508060e01b82511461099b5763d1a57ed65f526004601cfd5b505050505050565b5f602082840312156109b3575f80fd5b81356001600160e01b0319811681146109ca575f80fd5b9392505050565b5f602080835283518060208501525f5b818110156109fd578581018301518582016040015282016109e1565b505f604082860101526040601f19601f8301168501019250505092915050565b5f60208284031215610a2d575f80fd5b5035919050565b80356001600160a01b0381168114610a4a575f80fd5b919050565b5f8060408385031215610a60575f80fd5b610a6983610a34565b946020939093013593505050565b5f805f60608486031215610a89575f80fd5b610a9284610a34565b9250610aa060208501610a34565b9150604084013590509250925092565b5f60208284031215610ac0575f80fd5b6109ca82610a34565b5f8060408385031215610ada575f80fd5b610ae383610a34565b915060208301358015158114610af7575f80fd5b809150509250929050565b5f805f805f60808688031215610b16575f80fd5b610b1f86610a34565b9450610b2d60208701610a34565b935060408601359250606086013567ffffffffffffffff80821115610b50575f80fd5b818801915088601f830112610b63575f80fd5b813581811115610b71575f80fd5b896020828501011115610b82575f80fd5b9699959850939650602001949392505050565b5f8060408385031215610ba6575f80fd5b610baf83610a34565b9150610bbd60208401610a34565b9050925092905056fea2646970667358221220a00c9ca403ebd1202ded30498c6c9e2cfb6850c296c54362eb77022a37eba39364736f6c63430008180033
Deployed Bytecode
0x6080604052600436106102e7575f3560e01c80637501f7411161018f578063c87b56dd116100db578063f04e283e11610094578063f2fde38b1161006e578063f2fde38b14610cc6578063f53bc83514610cd9578063f7438c5e14610cf8578063fee81cf414610d0c576102ee565b8063f04e283e14610c6b578063f157574314610c7e578063f16f4eb414610ca7576102ee565b8063c87b56dd14610b9d578063cfefe4a914610bbc578063d4a6793014610bdb578063d7a18e9414610bfb578063dd62ed3e14610c10578063e159c14314610c4d576102ee565b8063a035b1fe11610148578063a9059cbb11610122578063a9059cbb14610aef578063bb95a21814610b0e578063be5a246314610b22578063bf376c7a14610b7e576102ee565b8063a035b1fe14610aa7578063a1e063e714610abc578063a21b694614610ad0576102ee565b80637501f741146109eb578063819b25ba14610a005780638da5cb5b14610a1f5780638df4164514610a3757806395d89b4114610a4b5780639ae697bf14610a5f576102ee565b80632a6a935d1161024e57806354d1f13d116102075780636c19e783116101e15780636c19e7831461099057806370a08231146109af57806370db69d6146109ce578063715018a6146109e3576102ee565b806354d1f13d1461093d57806355f804b3146109455780635de6dc5514610964576102ee565b80632a6a935d1461087d5780632f6c493c1461089c578063313ce567146108bb5780633ccfd60b146108d65780634cf294ab146108ea5780634ef41efc146108fd576102ee565b806318160ddd116102a057806318160ddd146107de57806323b872dd1461080357806324c979a3146108225780632569296214610837578063269822461461083f578063274e430b1461085e576102ee565b806302a794541461071057806302b9a97e1461073757806306fdde0314610760578063077a24c814610781578063095ea7b3146107a05780631525ff7d146107bf576102ee565b366102ee57005b68a20d6e21d0e52553085f3560e01c63e5eb36c8819003610371576002820154600160601b90046001600160a01b0316331461033d5760405163ce5a776b60e01b815260040160405180910390fd5b608436101561034a575f80fd5b60043560243560443560643561036284848484610d3d565b61036c60016110cb565b505050505b8063813500fc036103f8576002820154600160601b90046001600160a01b031633146103b05760405163ce5a776b60e01b815260040160405180910390fd5b60643610156103bd575f80fd5b6004356028818152604435601481905268a20d6e21d0e525530c5f9081526048812092526024351515918290556103f460016110cb565b5050505b8063e985e9c503610473576002820154600160601b90046001600160a01b031633146104375760405163ce5a776b60e01b815260040160405180910390fd5b6044361015610444575f80fd5b60243560288181526004803560148190529085015f9081526048812092529054909190610470906110cb565b50505b80636352211e036104de576002820154600160601b90046001600160a01b031633146104b25760405163ce5a776b60e01b815260040160405180910390fd5b60243610156104bf575f80fd5b6004356104dc6104ce826110d3565b6001600160a01b03166110cb565b505b8063243598790361053b576002820154600160601b90046001600160a01b0316331461051d5760405163ce5a776b60e01b815260040160405180910390fd5b602436101561052a575f80fd5b6004356105396104ce82611109565b505b8063d10b6e0c036105a2576002820154600160601b90046001600160a01b0316331461057a5760405163ce5a776b60e01b815260040160405180910390fd5b6064361015610587575f80fd5b60043560243560443561059e6104ce84848461115e565b5050505b8063081812fc036105ff576002820154600160601b90046001600160a01b031633146105e15760405163ce5a776b60e01b815260040160405180910390fd5b60243610156105ee575f80fd5b6004356105fd6104ce8261124b565b505b8063f5b100ea0361068c576002820154600160601b90046001600160a01b0316331461063e5760405163ce5a776b60e01b815260040160405180910390fd5b602436101561064b575f80fd5b60043561068a610685826001600160a01b03165f90815268a20d6e21d0e5255313602052604090205463ffffffff600160281b9091041690565b6110cb565b505b8063e2c79281036106f9576002820154600160601b90046001600160a01b031633146106cb5760405163ce5a776b60e01b815260040160405180910390fd5b60043610156106d8575f80fd5b68a20d6e21d0e5255308546106f990600160601b900463ffffffff166110cb565b8063b7a94eb80361070e5761070e60016110cb565b005b34801561071b575f80fd5b50610724611295565b6040519081526020015b60405180910390f35b348015610742575f80fd5b506008546107509060ff1681565b604051901515815260200161072e565b34801561076b575f80fd5b506107746112b8565b60405161072e9190612be4565b34801561078c575f80fd5b5061070e61079b366004612c16565b611347565b3480156107ab575f80fd5b506107506107ba366004612c52565b61135d565b3480156107ca575f80fd5b5061070e6107d9366004612c7a565b611372565b3480156107e9575f80fd5b5068a20d6e21d0e525530a546001600160601b0316610724565b34801561080e575f80fd5b5061075061081d366004612c93565b61139c565b34801561082d575f80fd5b5061072460045481565b61070e611408565b34801561084a575f80fd5b506008546107509062010000900460ff1681565b348015610869575f80fd5b50610750610878366004612c7a565b611455565b348015610888575f80fd5b50610750610897366004612ccc565b611495565b3480156108a7575f80fd5b5061070e6108b6366004612c7a565b6114d0565b3480156108c6575f80fd5b506040516012815260200161072e565b3480156108e1575f80fd5b5061070e61158a565b61070e6108f8366004612d30565b6115df565b348015610908575f80fd5b5068a20d6e21d0e525530a54600160601b90046001600160a01b03165b6040516001600160a01b03909116815260200161072e565b61070e6117c0565b348015610950575f80fd5b5061070e61095f366004612d78565b6117f9565b34801561096f575f80fd5b5061098361097e366004612c7a565b611813565b60405161072e9190612db7565b34801561099b575f80fd5b5061070e6109aa366004612c7a565b61181e565b3480156109ba575f80fd5b506107246109c9366004612c7a565b611854565b3480156109d9575f80fd5b5061072460075481565b61070e611886565b3480156109f6575f80fd5b5061072460055481565b348015610a0b575f80fd5b5061070e610a1a366004612dfa565b611899565b348015610a2a575f80fd5b50638b78c6d81954610925565b348015610a42575f80fd5b5061070e611927565b348015610a56575f80fd5b50610774611943565b348015610a6a575f80fd5b50610724610a79366004612c7a565b6001600160a01b03165f90815268a20d6e21d0e525531360205260409020600101546001600160601b031690565b348015610ab2575f80fd5b5061072460065481565b348015610ac7575f80fd5b5061070e611952565b348015610adb575f80fd5b5061070e610aea366004612dfa565b611979565b348015610afa575f80fd5b50610750610b09366004612c52565b611992565b348015610b19575f80fd5b5061070e61199e565b348015610b2d575f80fd5b50610b69610b3c366004612c7a565b6001600160a01b03165f90815268a20d6e21d0e52553136020526040902054610100900463ffffffff1690565b60405163ffffffff909116815260200161072e565b348015610b89575f80fd5b5061070e610b98366004612c52565b6119c3565b348015610ba8575f80fd5b50610774610bb7366004612dfa565b611ae9565b348015610bc7575f80fd5b50610724610bd6366004612dfa565b611b32565b348015610be6575f80fd5b50600854610750906301000000900460ff1681565b348015610c06575f80fd5b5061072460035481565b348015610c1b575f80fd5b50610724610c2a366004612e11565b602890815260149190915268a20d6e21d0e525530f5f9081526048812091525490565b348015610c58575f80fd5b5060085461075090610100900460ff1681565b61070e610c79366004612c7a565b611b46565b348015610c89575f80fd5b5068a20d6e21d0e525530854600160601b900463ffffffff16610724565b348015610cb2575f80fd5b5061070e610cc1366004612c52565b611b80565b61070e610cd4366004612c7a565b611dc5565b348015610ce4575f80fd5b5061070e610cf3366004612dfa565b611deb565b348015610d03575f80fd5b5061070e611df8565b348015610d17575f80fd5b50610724610d26366004612c7a565b63389a75e1600c9081525f91909152602090205490565b68a20d6e21d0e52553086001600160a01b038416610d6e57604051633a954ecd60e21b815260040160405180910390fd5b600a8101600382015f610da6835f19880160011b5b5f8160031c8360601b0180546007841660051b1c63ffffffff1691505092915050565b63ffffffff16815260208101919091526040015f20546001600160a01b03878116911614610de65760405162a1148160e81b815260040160405180910390fd5b856001600160a01b0316836001600160a01b031614610e595760288381526014879052600483015f908152604881209152545f03610e59575f8481526005830160205260409020546001600160a01b03848116911614610e5957604051632ce44b5f60e11b815260040160405180910390fd5b5f610e6387611e21565b90505f610e6f87611e21565b90505f610e7b60035490565b60018401549091506001600160601b03165f610e9a600c88018a015490565b90505f670de0b6b3a76400008211610eba57670de0b6b3a7640000610ebc565b815b905082811115610edf57604051631e9acf1760e31b815260040160405180910390fd5b6001860180546001600160601b0319168285036001600160601b0316179055898801600c018490555050506001820180546001600160601b038082168401166001600160601b03199091161790556001600160a01b0389165f9081526008860160208190526040909120610f6a600688018a60609190911b600882901c0154600160ff9092161c1690565b15610fa7576006870160601b60088a901c018054600160ff8c161b191690555f898152600588016020526040902080546001600160a01b03191690555b845463ffffffff60281b198116600160281b9182900463ffffffff9081165f1901808216909302919091178755606083901b631fffffff600384901c1601545f9260e060059190911b161c1690505f611009885f198d01600190811b01610d83565b905061101c838263ffffffff1684611e6f565b611036885f1963ffffffff851601600190811b0183611e6f565b5050835463ffffffff60281b198116600160281b9182900463ffffffff908116600181019091169092021785556001600160a01b038b165f90815260208490526040902061108590828c611e6f565b61109a878b611094888f611e9c565b84611f26565b505050805f528760601b60601c8960601b60601c5f805160206130cf83398151915260205fa3505050505050505050565b805f5260205ff35b5f6110dd82611f6b565b6110fa5760405163677510db60e11b815260040160405180910390fd5b61110382611109565b92915050565b5f68a20d6e21d0e525530868a20d6e21d0e525530b8261113868a20d6e21d0e52553125f19870160011b610d83565b63ffffffff16815260208101919091526040015f20546001600160a01b03169392505050565b5f68a20d6e21d0e525530868a20d6e21d0e525530b8261118d68a20d6e21d0e52553125f19880160011b610d83565b63ffffffff16815260208101919091526040015f20546001600160a01b039081169250831682146111f05760288381526014839052600482015f908152604881209152545f036111f0576040516367d9dca160e11b815260040160405180910390fd5b5f848152600582016020526040902080546001600160a01b0319166001600160a01b0387169081179091556006820160601b600886901c018054600160ff881690811b1991909116921515901b919091179055509392505050565b5f61125582611f6b565b6112725760405163677510db60e11b815260040160405180910390fd5b505f90815268a20d6e21d0e525530d60205260409020546001600160a01b031690565b5f612710619c406003546112a99190612e56565b6112b39190612e81565b905090565b60605f80546112c690612ea0565b80601f01602080910402602001604051908101604052809291908181526020018280546112f290612ea0565b801561133d5780601f106113145761010080835404028352916020019161133d565b820191905f5260205f20905b81548152906001019060200180831161132057829003601f168201915b5050505050905090565b61134f611f87565b6001600160781b0316600655565b5f611369338484611fa1565b50600192915050565b61137a611f87565b600980546001600160a01b0319166001600160a01b0392909216919091179055565b336028908152601484905268a20d6e21d0e525530f5f908152604881209181905281549091905f1981146113f157808411156113eb576040516313be252b60e01b815260040160405180910390fd5b83810382555b6113fc868686612001565b50600195945050505050565b5f6202a30067ffffffffffffffff164201905063389a75e1600c52335f52806020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d5f80a250565b6001600160a01b0381165f90815268a20d6e21d0e5255313602052604081208054600116820361148957823b5b9392505050565b54600216151592915050565b6008545f9060ff16151581036114be57604051631cc6a69960e11b815260040160405180910390fd5b6114c83383612170565b506001919050565b3068929eee149b4bd2126854036114ee5763ab143c065f526004601cfd5b3068929eee149b4bd212685580336001600160a01b0382161461152457604051636edaef2f60e11b815260040160405180910390fd5b33321461154457604051636edaef2f60e11b815260040160405180910390fd5b60085462010000900460ff1615155f0361157157604051631cc6a69960e11b815260040160405180910390fd5b61157a826121d7565b503868929eee149b4bd212685550565b611592611f87565b3068929eee149b4bd2126854036115b05763ab143c065f526004601cfd5b3068929eee149b4bd21268556009546115d1906001600160a01b03166122a1565b3868929eee149b4bd2126855565b3332146115ff57604051636edaef2f60e11b815260040160405180910390fd5b5f61160b600185612ed8565b90505f6001851161161d57600161162e565b600a851161162b578461162e565b600a5b6008549091506301000000900460ff1615155f0361165f57604051631cc6a69960e11b815260040160405180910390fd5b335f90815268a20d6e21d0e52553136020526040902054610100900463ffffffff161561169f57604051631cc6a69960e11b815260040160405180910390fd5b6007548511156116c257604051632257306f60e11b815260040160405180910390fd5b6001851180156116de575034826006546116dc9190612e56565b115b156116fc57604051631e9acf1760e31b815260040160405180910390fd5b60055468a20d6e21d0e5255308548690600160601b900463ffffffff166117239190612eeb565b11156117425760405163e5cfe95760e01b815260040160405180910390fd5b61174c84846122ba565b61176957604051638baa579f60e01b815260040160405180910390fd5b61178633670de0b6b3a76400005b6117819084612e56565b612380565b6040518581527f3e32821836f4caf5b64b2c8c6b460049a9797526960d31502f7575b8da39d5ae9060200160405180910390a15050505050565b63389a75e1600c52335f525f6020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c925f80a2565b611801611f87565b600261180e828483612f5d565b505050565b60606111038261266f565b611826611f87565b600880546001600160a01b0390921664010000000002640100000000600160c01b0319909216919091179055565b6001600160a01b03165f90815268a20d6e21d0e52553136020526040902054600160481b90046001600160601b031690565b61188e611f87565b6118975f6127b1565b565b6009546001600160a01b031633146118c457604051636edaef2f60e11b815260040160405180910390fd5b68a20d6e21d0e52553095468a20d6e21d0e5255308548290600160601b900463ffffffff166118f39190612eeb565b11156119125760405163e5cfe95760e01b815260040160405180910390fd5b61192433670de0b6b3a7640000611777565b50565b61192f611f87565b6008805460ff19811660ff90911615179055565b6060600180546112c690612ea0565b61195a611f87565b6008805462ff0000198116620100009182900460ff1615909102179055565b611981611f87565b6119248168a20d6e21d0e525530955565b5f611369338484612001565b6119a6611f87565b6008805461ff001981166101009182900460ff1615909102179055565b81336001600160a01b038216146119ed57604051636edaef2f60e11b815260040160405180910390fd5b333214611a0d57604051636edaef2f60e11b815260040160405180910390fd5b5f60035483611a1c9190612e56565b600854909150610100900460ff1615155f03611a4b57604051631cc6a69960e11b815260040160405180910390fd5b68a20d6e21d0e52553095468a20d6e21d0e5255308548490600160601b900463ffffffff16611a7a9190612eeb565b1115611a995760405163e5cfe95760e01b815260040160405180910390fd5b80611aa333611854565b1015611ac257604051631e9acf1760e31b815260040160405180910390fd5b611ad833611781670de0b6b3a764000086612e56565b611ae3843083612001565b50505050565b606060028054611af890612ea0565b159050611b2d576002611b0a836127ee565b604051602001611b1b929190613017565b60405160208183030381529060405290505b919050565b68a20d6e21d0e52553148101545f90611103565b611b4e611f87565b63389a75e1600c52805f526020600c208054421115611b7457636f5e88185f526004601cfd5b5f9055611924816127b1565b3068929eee149b4bd212685403611b9e5763ab143c065f526004601cfd5b3068929eee149b4bd212685581336001600160a01b03821614611bd457604051636edaef2f60e11b815260040160405180910390fd5b333214611bf457604051636edaef2f60e11b815260040160405180910390fd5b5f82600354611c039190612e56565b600854909150610100900460ff1615155f03611c3257604051631cc6a69960e11b815260040160405180910390fd5b335f90815268a20d6e21d0e525531360205260409020548390600160281b900463ffffffff161015611c7757604051631e9acf1760e31b815260040160405180910390fd5b80611c8133611854565b1015611ca057604051631e9acf1760e31b815260040160405180910390fd5b5f80611cc168a20d6e21d0e52553085463ffffffff600160601b9091041690565b90505f5b85811015611d3e57611cd5611295565b611cdf9084612eeb565b92508180600190039250506305f5e10082614e20619c40600354611d039190612e56565b611d0d9190612e56565b611d179190612e81565b611d219190612e81565b60035f828254611d319190612eeb565b9091555050600101611cc5565b50611d5c33611d55670de0b6b3a764000088612e56565b6001612830565b611d703033611d6b8686612ed8565b612001565b60408051868152602081018490529081018490527f5af801e983cfd842de8b0797a2d6077db3ac2dea16571e0c7f5080291ca1b9349060600160405180910390a1505050503868929eee149b4bd21268555050565b611dcd611f87565b8060601b611de257637448fbae5f526004601cfd5b611924816127b1565b611df3611f87565b600755565b611e00611f87565b6008805463ff00000019811663010000009182900460ff1615909102179055565b6001600160a01b0381165f90815268a20d6e21d0e5255313602052604081208054909160019091169003611b2d575f6002833b1515835460ff191660ff919092026001171617825550919050565b8160031c8360601b016007831660051b815463ffffffff8482841c188116831b8218845550505050505050565b8154610100900463ffffffff1668a20d6e21d0e52553085f829003611f1f57805463ffffffff198116600163ffffffff928316019182169081178355855464ffffffff00191661010082021786555f818152600384016020526040812080546001600160a01b0319166001600160a01b0388161790559193509003611f1f575f80fd5b5092915050565b600183038060021c8560601b016003821660061b9150805467ffffffffffffffff8563ffffffff168560201b178083861c188216851b83188455505050505050505050565b5f80611f7683611109565b6001600160a01b0316141592915050565b638b78c6d819543314611897576382b429005f526004601cfd5b6028828152601484905268a20d6e21d0e525530f5f9081526048812091528190555f8181526001600160a01b0380841691908516907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92590602090a3505050565b6001600160a01b03821661202857604051633a954ecd60e21b815260040160405180910390fd5b68a20d6e21d0e52553085f61203c85611e21565b90505f61204885611e21565b905061208a6040518061010001604052805f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81525090565b825463ffffffff600160281b808304821660808501528454041660a083015260028501546001600160601b0390811660c0840152600160481b90910416604082018190528511156120ee57604051631e9acf1760e31b815260040160405180910390fd5b6040810180518690039081905283546001600160601b03918216600160481b9081026bffffffffffffffffffffffff60481b19928316178655845481810484168901606086018190529093160291161782555f8581526001600160a01b0380881691908916905f805160206130cf83398151915290602090a350505050505050565b5f61217a83611e21565b80549091506002161515821515146121a157805460ff8082166002181660ff199091161781555b8115155f528260601b60601c7fb5a1de456fff688115a4f75380060c23c8532d14ff85f687cc871456d642039360205fa2505050565b6001600160a01b0381165f90815268a20d6e21d0e52553136020526040902060018082015468a20d6e21d0e525530892916001600160601b03909116906122219085908390612830565b815481908390600990612245908490600160481b90046001600160601b031661309a565b82546001600160601b039182166101009390930a9283029190920219909116179055506001820180546001600160601b03191690555f8181526001600160a01b038516905f805160206130cf833981519152602082a350505050565b5f385f3847855af16119245763b12d13eb5f526004601cfd5b6040516001600160601b03193360601b1660208201525f90819061231b90603401604051602081830303815290604052805190602001206020527b19457468657265756d205369676e6564204d6573736167653a0a33325f52603c60042090565b90505f61235d8286868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250612ab892505050565b60085464010000000090046001600160a01b039081169116149250505092915050565b6001600160a01b0382166123a757604051633a954ecd60e21b815260040160405180910390fd5b68a20d6e21d0e52553085f6123bb84611e21565b6002830180546001600160601b03808216870181166001600160601b03199283161790925584546001840180548085168901948516931692909217909155825492935063ffffffff600160601b90910481168601925f9291600160281b9091041661242b670de0b6b3a764000090565b878161243957612439612e6d565b86549190049190910192506002165f039050612646576001600160a01b0386165f908152600885016020526040812084549091600a870191600160281b900463ffffffff169061248e82860383871102612b43565b90508060400151515f146126415760608a901b6020820152875463ffffffff600160401b82048116916401000000009004165f6124cb8a8e611e9c565b6040850151518c5463ffffffff600160601b8083048216909301811690920263ffffffff60601b19909116178d558b54908a16600160281b0263ffffffff60281b19909116178b5590505b5f8315612566575f1993909301600381901c60098d0160601b0154909390600585901b60e0161c63ffffffff1663ffffffff169050612561600c8d0182670de0b6b3a7640000910155565b6125a5565b50815b612579875f19830160011b610d83565b63ffffffff161561259557600101898111801590910217612569565b600181018a811180159091021792505b6125b0888783611e6f565b6125c287828489806001019a50611f26565b8451602080870151600884901b17825201855250878503612516578a5463ffffffff848116600160401b0263ffffffff60401b1991851664010000000002919091166bffffffffffffffff000000001990921691909117178b5560028b015461263d9085906001600160a01b03600160601b90910416612b88565b5050505b505050505b50505f8381526001600160a01b038516905f805160206130cf833981519152602082a350505050565b6001600160a01b0381165f90815268a20d6e21d0e5255313602052604090205460609068a20d6e21d0e525530890600160281b900463ffffffff168067ffffffffffffffff8111156126c3576126c3612efe565b6040519080825280602002602001820160405280156126ec578160200160208202803683370190505b5092505f5b818110156127a9576001600160a01b0385165f908152600884016020526040812060601b600383901c015463ffffffff60e0600585901b169190911c1663ffffffff16905080858381518110612749576127496130ba565b602002602001018181525050856001600160a01b031661276882611109565b6001600160a01b03161461277a575f80fd5b81612791600a86015f198401600190811b01610d83565b63ffffffff16146127a0575f80fd5b506001016126f1565b505050919050565b638b78c6d81980546001600160a01b039092169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a355565b60606080604051019050602081016040525f8152805f19835b928101926030600a8206018453600a900480612807575050819003601f19909101908152919050565b68a20d6e21d0e52553085f61284485611e21565b90508215612a0c576001600160a01b0385165f908152600883016020526040812082549091600160281b90910463ffffffff1690670de0b6b3a7640000878161288f5761288f612e6d565b0490508015612a04575f6128a282612b43565b60608a901b60011760208201529050855463ffffffff600160601b808304821685900380831690910263ffffffff60601b199093169290921788558654848603808316600160281b0263ffffffff60281b199092169190911788558854600a8a0192600160401b909104165f5b606089901b5f1998909801600381901c989098015463ffffffff60e060058b901b169190911c168b8101600c0154670de0b6b3a7640000811161295a57670de0b6b3a764000061295c565b805b8301925061296c86835f80611f26565b87516020808a0151600885901b1782520188526129938d6009018580600101965084611e6f565b505082880361290f5760018a0180546001600160601b031981166001600160601b039182168490039091161790558a5463ffffffff60401b1916600160401b63ffffffff841602178b5560028b01546129fd908790600160601b90046001600160a01b0316612b88565b5050505050505b505050612a6d565b8054600160481b90046001600160601b031680851115612a3f57604051631e9acf1760e31b815260040160405180910390fd5b81546bffffffffffffffffffffffff60481b1916600160481b918690036001600160601b0316919091021781555b6002820180546001600160601b03808216879003166001600160601b03199091161790555f8481526001600160a01b0386165f805160206130cf833981519152602083a35050505050565b604051600190835f5260208301516040526040835103612af357604083015160ff81901c601b016020526001600160ff1b0316606052612b17565b6041835103612b135760608301515f1a6020526040830151606052612b17565b5f91505b6020600160805f855afa5191503d612b3657638baa579f5f526004601cfd5b5f60605260405292915050565b612b6560405180606001604052805f81526020015f8152602001606081525090565b604051828152806020018360051b81016040528183604001528083525050919050565b60408201516040810363263c69d68152602080820152815160051b604401915060208183601c84015f875af1600182511416611ae3575f81fd5b5f5b83811015612bdc578181015183820152602001612bc4565b50505f910152565b602081525f8251806020840152612c02816040850160208701612bc2565b601f01601f19169190910160400192915050565b5f60208284031215612c26575f80fd5b81356001600160781b0381168114611482575f80fd5b80356001600160a01b0381168114611b2d575f80fd5b5f8060408385031215612c63575f80fd5b612c6c83612c3c565b946020939093013593505050565b5f60208284031215612c8a575f80fd5b61148282612c3c565b5f805f60608486031215612ca5575f80fd5b612cae84612c3c565b9250612cbc60208501612c3c565b9150604084013590509250925092565b5f60208284031215612cdc575f80fd5b81358015158114611482575f80fd5b5f8083601f840112612cfb575f80fd5b50813567ffffffffffffffff811115612d12575f80fd5b602083019150836020828501011115612d29575f80fd5b9250929050565b5f805f60408486031215612d42575f80fd5b83359250602084013567ffffffffffffffff811115612d5f575f80fd5b612d6b86828701612ceb565b9497909650939450505050565b5f8060208385031215612d89575f80fd5b823567ffffffffffffffff811115612d9f575f80fd5b612dab85828601612ceb565b90969095509350505050565b602080825282518282018190525f9190848201906040850190845b81811015612dee57835183529284019291840191600101612dd2565b50909695505050505050565b5f60208284031215612e0a575f80fd5b5035919050565b5f8060408385031215612e22575f80fd5b612e2b83612c3c565b9150612e3960208401612c3c565b90509250929050565b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141761110357611103612e42565b634e487b7160e01b5f52601260045260245ffd5b5f82612e9b57634e487b7160e01b5f52601260045260245ffd5b500490565b600181811c90821680612eb457607f821691505b602082108103612ed257634e487b7160e01b5f52602260045260245ffd5b50919050565b8181038181111561110357611103612e42565b8082018082111561110357611103612e42565b634e487b7160e01b5f52604160045260245ffd5b601f82111561180e57805f5260205f20601f840160051c81016020851015612f375750805b601f840160051c820191505b81811015612f56575f8155600101612f43565b5050505050565b67ffffffffffffffff831115612f7557612f75612efe565b612f8983612f838354612ea0565b83612f12565b5f601f841160018114612fba575f8515612fa35750838201355b5f19600387901b1c1916600186901b178355612f56565b5f83815260208120601f198716915b82811015612fe95786850135825560209485019460019092019101612fc9565b5086821015613005575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b5f80845461302481612ea0565b6001828116801561303c57600181146130515761307d565b60ff198416875282151583028701945061307d565b885f526020805f205f5b858110156130745781548a82015290840190820161305b565b50505082870194505b505050508351613091818360208801612bc2565b01949350505050565b6001600160601b03818116838216019080821115611f1f57611f1f612e42565b634e487b7160e01b5f52603260045260245ffdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212205a27c1c83c35616a2b070aee75982c2824c85697d130a4694a2db1b468b3d68464736f6c63430008180033
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.