Feature Tip: Add private address tag to any address under My Name Tag !
ERC-721
Source Code
Overview
Max Total Supply
150 ABO
Holders
104
Transfers
-
0
Market
Volume (24H)
N/A
Min Price (24H)
N/A
Max Price (24H)
N/A
Other Info
Token Contract
Loading...
Loading
Loading...
Loading
Loading...
Loading
| # | Exchange | Pair | Price | 24H Volume | % Volume |
|---|
Contract Name:
Abo
Compiler Version
v0.8.28+commit.7893614a
Optimization Enabled:
Yes with 200 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
//////////////////////////////////////////////////////////////////
// ▓███████████████ ▒▒▒▒▒▒▒▒██ ░░░░░░░░░░ ▒▒▒▒▒▒▒▒▒▒███ ▓▓▓▓███ //
// ▓▓▓▓████████████ ▒▒▒▒▒█████ ░░░▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓░░░░░ //
// ▓▓▓▓████████████ ▒▒▒▒▒▒▒▒██ ░░░░░░▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ██████░ //
// ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░ ▒▒▒▒▒▒▒▒██ ░░░░░░▓▓▓▓ █▒▒▒▒▒▒▒▒▒▒▒▒ ███████ //
// ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░ ▒▒▒▒▒▒▒▒██ ░░░░░░▓▓▓▓ █▒▒▒▒▒▒▒▒▒▒▒▒ ███████ //
// █████████░░░░░░░ █████▒▒▒▒▒ ░░░░░░▓▓▓▓ ███▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓█ //
// ████████████░░░░ █████████▒ ░░░░▓▓▓▓▓▓ ███▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓█ //
// ████████░░░░░░░░ █████████▒ ▓▓▓▓▓▓░░░░ ▒████████████ ████▓▓▓ //
// █████░░░░░░░░░░░ █████████▒ ▓▓▓▓▓▓░░░░ ▒████████████ ████▓▓▓ //
// █████░░░░░░░░░░░ █████████▒ ▓░░░░░░░░░ ▒████████████ ████▓▓▓ //
// █████░░░░░░░░░░░ █████████▒ ▓▓▓▓▓▓▓▓░░ ▒████████████ ░░░░▓▓▓ //
// ░░░░░███████████ ▒▒▒▒▒▒▒▒██ ▓▓▓▓▓▓▓░░░ ▒████████████ ███▓▓▓▓ //
// ░███████████████ ▒▒▒▒▒▒▒▒██ ░░░░░▓▓▓▓▓ ▒▒▒▒█████████ ░░▓▓▓▓▓ //
// ▓▓▓▓▓▓██████████ ▒▒▒▒▒▒▒▒██ ▓▓▓▓▓▓▓▓░░ ▒▒▒██████████ ░░░░▓▓▓ //
// ░░░░░░░░░░░░████ ▒▒▒▒▒▒▒▒██ ▓░░░░░░░░░ █████████████ ██████▓ //
// █████░░░░░░░░░░░ █████████▒ ▓░░░░░░░░░ ▒████████████ ████▓▓▓ //
//////////////////////////////////////////////////////////////////
// ABO, 2025 ███████████████████████████ Leander Herzog & 0xfff //
//////////////////////////////////////////////////////////////////
import "./AboAdmin.sol";
import "./Render.sol";
import {AboState, AboStateLib} from "./AboState.sol";
import "./Sculpture.sol";
contract Abo is AboAdmin, Sculpture {
using AboStateLib for AboState;
uint8 public constant MAX_CONNECTIONS = 6;
uint256 public totalConnectionCount; // ↑ only
mapping(uint8 => AboState) public abos;
// ABO Events
event Connect(uint8 indexed from, uint8 indexed to);
event Disconnect(uint8 indexed from, uint8 indexed to);
event Ignore(uint8 indexed from, uint8 indexed blocked);
event Unignore(uint8 indexed from, uint8 indexed unblocked);
// [aside] Unlock
event Unlock(uint256 _tokenId);
// EIP-4906: Metadata Update
event MetadataUpdate(uint256 _tokenId);
constructor(address _owner) AboAdmin(_owner) {}
//////////////////////////////////////////////////////////
// Token
//////////////////////////////////////////////////////////
function name() public pure override returns (string memory) {
return "ABO by Leander Herzog and 0xfff";
}
function symbol() public pure override returns (string memory) {
return "ABO";
}
function tokenURI(uint256 id) public view override returns (string memory) {
return Render(render).tokenURI(id);
}
function isUnlocked(uint256 id) public view returns (bool) {
return abos[uint8(id)].isUnlocked;
}
function _beforeTokenTransfer(address from, address, uint256 id) internal view override {
if (from != address(0) && !isUnlocked(id)) {
revert("Token is locked. Connect to unlock.");
}
}
//////////////////////////////////////////////////////////
// Connections
//////////////////////////////////////////////////////////
function connect(uint8 fromId, uint8 toId) external {
require(msg.sender == ownerOf(fromId), "Not the owner");
require(_exists(toId), "Token does not exist");
require(toId != fromId, "Cannot connect to self");
AboState memory fromState = abos[fromId];
AboState memory toState = abos[toId];
require(!toState.isIgnoring(fromId), "Token is ignoring you");
// Remove reverse connection if it exists
if (toState.hasOutbound(fromId)) {
toState = toState.removeOutbound(fromId);
fromState = fromState.removeInbound(toId);
}
// Add outgoing connection and unlock
fromState = fromState.addOutbound(toId);
if (!fromState.isUnlocked) {
fromState = fromState.setUnlocked(true);
emit Unlock(fromId);
}
// Add incoming connection and unlock
toState = toState.addInbound(fromId);
if (!toState.isUnlocked) {
toState = toState.setUnlocked(true);
emit Unlock(toId);
}
// Save states back to storage
abos[fromId] = fromState;
abos[toId] = toState;
// Count the connection
totalConnectionCount++;
emit Connect(fromId, toId);
emit MetadataUpdate(fromId);
emit MetadataUpdate(toId);
}
function connect(uint8 fromId, uint8[] calldata ids) external {
require(msg.sender == ownerOf(fromId), "Not the owner");
AboState memory fromState = abos[fromId];
for (uint256 i = 0; i < ids.length; i++) {
uint8 to = ids[i];
require(_exists(to), "Token does not exist");
require(to != fromId, "Cannot connect to self");
AboState memory toState = abos[to];
require(!toState.isIgnoring(fromId), "Token is ignoring you");
// Remove reverse connection if it exists
if (toState.hasOutbound(fromId)) {
toState = toState.removeOutbound(fromId);
fromState = fromState.removeInbound(to);
}
// Add connection
fromState = fromState.addOutbound(to);
toState = toState.addInbound(fromId);
// Count the connection
totalConnectionCount++;
if (!toState.isUnlocked) {
toState = toState.setUnlocked(true);
emit Unlock(to);
}
// Save to state back to storage
abos[to] = toState;
emit Connect(fromId, to);
emit MetadataUpdate(to);
}
if (!fromState.isUnlocked) {
fromState = fromState.setUnlocked(true);
emit Unlock(fromId);
}
// Save from state back to storage
abos[fromId] = fromState;
emit MetadataUpdate(fromId);
}
function disconnect(uint8 fromId, uint8[] calldata ids) external {
require(msg.sender == ownerOf(fromId), "Not the owner");
AboState memory fromState = abos[fromId];
for (uint256 i = 0; i < ids.length; i++) {
require(_exists(ids[i]), "Token does not exist");
require(ids[i] != fromId, "Cannot disconnect self");
uint8 to = ids[i];
AboState memory toState = abos[to];
if (fromState.hasOutbound(to)) {
// Remove outgoing connection
fromState = fromState.removeOutbound(to);
toState = toState.removeInbound(fromId);
emit Disconnect(fromId, to);
} else if (toState.hasOutbound(fromId)) {
// Remove incoming connection
toState = toState.removeOutbound(fromId);
fromState = fromState.removeInbound(to);
emit Disconnect(to, fromId);
} else {
revert("Not connected");
}
// Save to state back to storage
abos[to] = toState;
emit MetadataUpdate(to);
}
// Save from state back to storage
abos[fromId] = fromState;
emit MetadataUpdate(fromId);
}
function disconnectAll(uint8 toId) external {
require(msg.sender == ownerOf(toId), "Not the owner");
AboState memory toState = abos[toId];
require(toState.totalCount() > 0, "Token has no connections");
// Remove all outgoing connections from other tokens' incoming lists
uint8[] memory outgoingIds = toState.getOutbound();
for (uint256 i = 0; i < outgoingIds.length; i++) {
uint8 targetId = outgoingIds[i];
AboState memory targetState = abos[targetId];
targetState = targetState.removeInbound(toId);
abos[targetId] = targetState;
emit Disconnect(toId, targetId);
emit MetadataUpdate(targetId);
}
// Remove all incoming connections from other tokens' outgoing lists
uint8[] memory incomingIds = toState.getInbound();
for (uint256 i = 0; i < incomingIds.length; i++) {
uint8 sourceId = incomingIds[i];
AboState memory sourceState = abos[sourceId];
sourceState = sourceState.removeOutbound(toId);
abos[sourceId] = sourceState;
emit Disconnect(sourceId, toId);
emit MetadataUpdate(sourceId);
}
// Clear all connections for this token but preserve unlock status
toState = toState.clearAll();
abos[toId] = toState;
emit MetadataUpdate(toId);
}
function updateConnections(uint8 fromId, uint8[] calldata ids) external {
require(msg.sender == ownerOf(fromId), "Not the owner");
AboState memory fromState = abos[fromId];
// Validate all target tokens
for (uint256 i = 0; i < ids.length; i++) {
require(_exists(ids[i]), "Token does not exist");
require(ids[i] != fromId, "Cannot connect to self");
}
// Track which tokens should remain connected using bitmap
uint256 shouldKeepBitmap;
for (uint256 i = 0; i < ids.length; i++) {
shouldKeepBitmap |= (1 << ids[i]);
}
// Remove outgoing connections that are not in the new set
uint8[] memory currentOutgoing = fromState.getOutbound();
for (uint256 i = 0; i < currentOutgoing.length; i++) {
uint8 targetId = currentOutgoing[i];
if ((shouldKeepBitmap & (1 << targetId)) == 0) {
AboState memory targetState = abos[targetId];
targetState = targetState.removeInbound(fromId);
fromState = fromState.removeOutbound(targetId);
abos[targetId] = targetState;
emit Disconnect(fromId, targetId);
}
}
// Remove incoming connections from tokens NOT in the new set
uint8[] memory currentIncoming = fromState.getInbound();
for (uint256 i = 0; i < currentIncoming.length; i++) {
uint8 sourceId = currentIncoming[i];
if ((shouldKeepBitmap & (1 << sourceId)) == 0) {
// Remove the outgoing connection from the source token
AboState memory sourceState = abos[sourceId];
if (sourceState.hasOutbound(fromId)) {
sourceState = sourceState.removeOutbound(fromId);
abos[sourceId] = sourceState;
emit Disconnect(sourceId, fromId);
emit MetadataUpdate(sourceId);
}
fromState = fromState.removeInbound(sourceId);
}
}
// Build new outgoing connections which are new
for (uint256 i = 0; i < ids.length; i++) {
uint8 toId = ids[i];
// Skip if already present
if (fromState.hasConnection(toId)) continue;
AboState memory toState = abos[toId];
require(!toState.isIgnoring(fromId), "Token is ignoring you");
fromState = fromState.addOutbound(toId);
// Add corresponding incoming connection to toId
toState = toState.addInbound(fromId);
// Count the connection
totalConnectionCount++;
// Unlock target if needed
if (!toState.isUnlocked) {
toState = toState.setUnlocked(true);
emit Unlock(toId);
}
abos[toId] = toState;
emit Connect(fromId, toId);
emit MetadataUpdate(toId);
}
// Unlock fromId if it was previously locked
if (!fromState.isUnlocked && ids.length > 0) {
fromState = fromState.setUnlocked(true);
emit Unlock(fromId);
}
abos[fromId] = fromState;
emit MetadataUpdate(fromId);
}
function ignore(uint8 fromId, uint8 blockId) external {
require(msg.sender == ownerOf(fromId), "Not the owner");
require(_exists(blockId), "Token does not exist");
require(blockId != fromId, "Cannot block self");
AboState memory fromState = abos[fromId];
abos[fromId] = fromState.addIgnored(blockId);
emit Ignore(fromId, blockId);
}
function unignore(uint8 fromId, uint8 blockId) external {
require(msg.sender == ownerOf(fromId), "Not the owner");
require(_exists(blockId), "Token does not exist");
require(blockId != fromId, "Cannot unblock self");
AboState memory fromState = abos[fromId];
abos[fromId] = fromState.removeIgnored(blockId);
emit Unignore(fromId, blockId);
}
//////////////////////////////////////////////////////////
// View
//////////////////////////////////////////////////////////
function hasOutbound(uint8 from, uint8 to) external view returns (bool) {
return abos[from].hasOutbound(to);
}
function hasInbound(uint8 from, uint8 to) external view returns (bool) {
return abos[from].hasInbound(to);
}
function isConnected(uint8 from, uint8 to) external view returns (bool) {
return abos[from].hasConnection(to);
}
function isIgnoring(uint8 from, uint8 to) external view returns (bool) {
return abos[from].isIgnoring(to);
}
function getConnections(uint8 from) external view returns (uint8[] memory) {
return abos[from].getConnections();
}
function getOutboundConnections(uint8 from) external view returns (uint8[] memory) {
return abos[from].getOutbound();
}
function getInboundConnections(uint8 from) external view returns (uint8[] memory) {
return abos[from].getInbound();
}
function getConnectionCount(uint8 from) public view returns (uint256) {
return abos[from].totalCount();
}
function getCurrentConnectionCount() public view returns (uint256) {
uint256 count = 0;
uint256 limit = MINT_SUPPLY + ARTIST_ALLOTMENT;
for (uint8 i = 1; i <= limit; i++) {
if (_exists(i)) {
count += abos[i].outboundCount();
}
}
return count;
}
/// @notice Returns token owner, id and connections (as AboState)
function tokenState(uint8 id) public view returns (address, AboState memory) {
return (ownerOf(id), abos[id]);
}
/// @notice Returns all token owners, ids and connection states
function tokens() external view returns (address[] memory, uint256[] memory, AboState[] memory, bool[] memory) {
address[] memory owners = new address[](totalSupply());
uint256[] memory ids = new uint256[](totalSupply());
AboState[] memory states = new AboState[](totalSupply());
bool[] memory unlocked = new bool[](totalSupply());
uint8 counter;
for (uint8 i = 1; i <= editionCount; i++) {
if (_exists(i)) {
owners[counter] = ownerOf(i);
ids[counter] = i;
states[counter] = abos[i];
unlocked[counter] = abos[i].isUnlocked;
counter++;
}
}
for (uint8 i = MINT_SUPPLY + 1; i <= MINT_SUPPLY + 1 + apCount; i++) {
if (_exists(i)) {
owners[counter] = ownerOf(i);
ids[counter] = i;
states[counter] = abos[i];
unlocked[counter] = abos[i].isUnlocked;
counter++;
}
}
return (owners, ids, states, unlocked);
}
//////////////////////////////////////////////////////////
// Sculpture
//////////////////////////////////////////////////////////
function title() external pure returns (string memory) {
return "ABO";
}
function authors() external pure returns (string[] memory) {
string[] memory authors_ = new string[](2);
authors_[0] = "Leander Herzog";
authors_[1] = "0xfff";
return authors_;
}
function addresses() external view returns (address[] memory) {
address[] memory addresses_ = new address[](1);
addresses_[0] = address(this);
return addresses_;
}
string[] private urls_;
function setUrls(string[] memory _urls) external onlyOwner {
urls_ = _urls;
}
function urls() external view returns (string[] memory) {
return urls_;
}
function text() external view returns (string memory) {
return string.concat(
"ABO is a social sculpture of 150 networked tokens. To date, ",
LibString.toString(totalConnectionCount),
" connections have been made."
);
}
}//SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
interface ENS {
// Logged when the owner of a node assigns a new owner to a subnode.
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);
// Logged when the owner of a node transfers ownership to a new account.
event Transfer(bytes32 indexed node, address owner);
// Logged when the resolver for a node changes.
event NewResolver(bytes32 indexed node, address resolver);
// Logged when the TTL of a node changes
event NewTTL(bytes32 indexed node, uint64 ttl);
// Logged when an operator is added or removed.
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
function setRecord(
bytes32 node,
address owner,
address resolver,
uint64 ttl
) external;
function setSubnodeRecord(
bytes32 node,
bytes32 label,
address owner,
address resolver,
uint64 ttl
) external;
function setSubnodeOwner(
bytes32 node,
bytes32 label,
address owner
) external returns (bytes32);
function setResolver(bytes32 node, address resolver) external;
function setOwner(bytes32 node, address owner) external;
function setTTL(bytes32 node, uint64 ttl) external;
function setApprovalForAll(address operator, bool approved) external;
function owner(bytes32 node) external view returns (address);
function resolver(bytes32 node) external view returns (address);
function ttl(bytes32 node) external view returns (uint64);
function recordExists(bytes32 node) external view returns (bool);
function isApprovedForAll(
address owner,
address operator
) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
interface INameResolver {
event NameChanged(bytes32 indexed node, string name);
/// Returns the name associated with an ENS node, for reverse records.
/// Defined in EIP181.
/// @param node The ENS node to query.
/// @return The associated name.
function name(bytes32 node) external view returns (string memory);
}pragma solidity >=0.8.4;
interface IReverseRegistrar {
function setDefaultResolver(address resolver) external;
function claim(address owner) external returns (bytes32);
function claimForAddr(
address addr,
address owner,
address resolver
) external returns (bytes32);
function claimWithResolver(
address owner,
address resolver
) external returns (bytes32);
function setName(string memory name) external returns (bytes32);
function setNameForAddr(
address addr,
address owner,
address resolver,
string memory name
) external returns (bytes32);
function node(address addr) external pure returns (bytes32);
}// 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 Simple ERC721 implementation with storage hitchhiking.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC721.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC721/ERC721.sol)
///
/// @dev Note:
/// - The ERC721 standard allows for self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
/// - For performance, methods are made payable where permitted by the ERC721 standard.
/// - The `safeTransfer` functions use the identity precompile (0x4)
/// to copy memory internally.
///
/// If you are overriding:
/// - NEVER violate the ERC721 invariant:
/// the balance of an owner MUST always be equal to their number of ownership slots.
/// The transfer functions do not have an underflow guard for user token balances.
/// - Make sure all variables written to storage are properly cleaned
/// (e.g. the bool value for `isApprovedForAll` MUST be either 1 or 0 under the hood).
/// - Check that the overridden function is actually used in the function you want to
/// change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC721 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev An account can hold up to 4294967295 tokens.
uint256 internal constant _MAX_ACCOUNT_BALANCE = 0xffffffff;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Only the token owner or an approved account can manage the token.
error NotOwnerNorApproved();
/// @dev The token does not exist.
error TokenDoesNotExist();
/// @dev The token already exists.
error TokenAlreadyExists();
/// @dev Cannot query the balance for the zero address.
error BalanceQueryForZeroAddress();
/// @dev Cannot mint or transfer to the zero address.
error TransferToZeroAddress();
/// @dev The token must be owned by `from`.
error TransferFromIncorrectOwner();
/// @dev The recipient's balance has overflowed.
error AccountBalanceOverflow();
/// @dev Cannot safely transfer to a contract that does not implement
/// the ERC721Receiver interface.
error TransferToNonERC721ReceiverImplementer();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when token `id` is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 indexed id);
/// @dev Emitted when `owner` enables `account` to manage the `id` token.
event Approval(address indexed owner, address indexed account, uint256 indexed id);
/// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens.
event ApprovalForAll(address indexed owner, address indexed operator, bool isApproved);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/// @dev `keccak256(bytes("ApprovalForAll(address,address,bool)"))`.
uint256 private constant _APPROVAL_FOR_ALL_EVENT_SIGNATURE =
0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership data slot of `id` is given by:
/// ```
/// mstore(0x00, id)
/// mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
/// let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
/// ```
/// Bits Layout:
/// - [0..159] `addr`
/// - [160..255] `extraData`
///
/// The approved address slot is given by: `add(1, ownershipSlot)`.
///
/// See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip
///
/// The balance slot of `owner` is given by:
/// ```
/// mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
/// mstore(0x00, owner)
/// let balanceSlot := keccak256(0x0c, 0x1c)
/// ```
/// Bits Layout:
/// - [0..31] `balance`
/// - [32..255] `aux`
///
/// The `operator` approval slot of `owner` is given by:
/// ```
/// mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
/// mstore(0x00, owner)
/// let operatorApprovalSlot := keccak256(0x0c, 0x30)
/// ```
uint256 private constant _ERC721_MASTER_SLOT_SEED = 0x7d8825530a5a2e7a << 192;
/// @dev Pre-shifted and pre-masked constant.
uint256 private constant _ERC721_MASTER_SLOT_SEED_MASKED = 0x0a5a2e7a00000000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC721 METADATA */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the token collection name.
function name() public view virtual returns (string memory);
/// @dev Returns the token collection symbol.
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);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC721 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function ownerOf(uint256 id) public view virtual returns (address result) {
result = _ownerOf(id);
/// @solidity memory-safe-assembly
assembly {
if iszero(result) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns the number of tokens owned by `owner`.
///
/// Requirements:
/// - `owner` must not be the zero address.
function balanceOf(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// Revert if the `owner` is the zero address.
if iszero(owner) {
mstore(0x00, 0x8f4eb604) // `BalanceQueryForZeroAddress()`.
revert(0x1c, 0x04)
}
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
result := and(sload(keccak256(0x0c, 0x1c)), _MAX_ACCOUNT_BALANCE)
}
}
/// @dev Returns the account approved to manage token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function getApproved(uint256 id) public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
if iszero(shl(96, sload(ownershipSlot))) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
result := sload(add(1, ownershipSlot))
}
}
/// @dev Sets `account` as the approved account to manage token `id`.
///
/// 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 account, uint256 id) public payable virtual {
_approve(msg.sender, account, id);
}
/// @dev Returns whether `operator` is approved to manage the tokens of `owner`.
function isApprovedForAll(address owner, address operator)
public
view
virtual
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, operator)
mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x30))
}
}
/// @dev Sets whether `operator` is approved to manage the tokens of the caller.
///
/// Emits an {ApprovalForAll} event.
function setApprovalForAll(address operator, bool isApproved) public virtual {
/// @solidity memory-safe-assembly
assembly {
// Convert to 0 or 1.
isApproved := iszero(iszero(isApproved))
// Update the `isApproved` for (`msg.sender`, `operator`).
mstore(0x1c, operator)
mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x30), isApproved)
// Emit the {ApprovalForAll} event.
mstore(0x00, isApproved)
// forgefmt: disable-next-item
log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), shr(96, shl(96, operator)))
}
}
/// @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 payable virtual {
_beforeTokenTransfer(from, to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
from := and(bitmaskAddress, from)
to := and(bitmaskAddress, to)
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, caller()))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
let owner := and(bitmaskAddress, ownershipPacked)
// Revert if the token does not exist, or if `from` is not the owner.
if iszero(mul(owner, eq(owner, from))) {
// `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
revert(0x1c, 0x04)
}
// Load, check, and update the token approval.
{
mstore(0x00, from)
let approvedAddress := sload(add(1, ownershipSlot))
// Revert if the caller is not the owner, nor approved.
if iszero(or(eq(caller(), from), eq(caller(), approvedAddress))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Update with the new owner.
sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
// Decrement the balance of `from`.
{
let fromBalanceSlot := keccak256(0x0c, 0x1c)
sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
}
// Increment the balance of `to`.
{
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x1c)
let toBalanceSlotPacked := add(sload(toBalanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(toBalanceSlot, toBalanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
_afterTokenTransfer(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
payable
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))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL QUERY FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns if token `id` exists.
function _exists(uint256 id) internal view virtual returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := iszero(iszero(shl(96, sload(add(id, add(id, keccak256(0x00, 0x20)))))))
}
}
/// @dev Returns the owner of token `id`.
/// Returns the zero address instead of reverting if the token does not exist.
function _ownerOf(uint256 id) internal view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := shr(96, shl(96, sload(add(id, add(id, keccak256(0x00, 0x20))))))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL DATA HITCHHIKING FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance, no events are emitted for the hitchhiking setters.
// Please emit your own events if required.
/// @dev Returns the auxiliary data for `owner`.
/// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
/// Auxiliary data can be set for any address, even if it does not have any tokens.
function _getAux(address owner) internal view virtual returns (uint224 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
result := shr(32, sload(keccak256(0x0c, 0x1c)))
}
}
/// @dev Set the auxiliary data for `owner` to `value`.
/// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
/// Auxiliary data can be set for any address, even if it does not have any tokens.
function _setAux(address owner, uint224 value) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
let balanceSlot := keccak256(0x0c, 0x1c)
let packed := sload(balanceSlot)
sstore(balanceSlot, xor(packed, shl(32, xor(value, shr(32, packed)))))
}
}
/// @dev Returns the extra data for token `id`.
/// Minting, transferring, burning a token will not change the extra data.
/// The extra data can be set on a non-existent token.
function _getExtraData(uint256 id) internal view virtual returns (uint96 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := shr(160, sload(add(id, add(id, keccak256(0x00, 0x20)))))
}
}
/// @dev Sets the extra data for token `id` to `value`.
/// Minting, transferring, burning a token will not change the extra data.
/// The extra data can be set on a non-existent token.
function _setExtraData(uint256 id, uint96 value) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let packed := sload(ownershipSlot)
sstore(ownershipSlot, xor(packed, shl(160, xor(value, shr(160, packed)))))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL MINT FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Mints token `id` to `to`.
///
/// Requirements:
///
/// - Token `id` must not exist.
/// - `to` cannot be the zero address.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 id) internal virtual {
_beforeTokenTransfer(address(0), to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
to := shr(96, shl(96, to))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
// Revert if the token already exists.
if shl(96, ownershipPacked) {
mstore(0x00, 0xc991cbb1) // `TokenAlreadyExists()`.
revert(0x1c, 0x04)
}
// Update with the owner.
sstore(ownershipSlot, or(ownershipPacked, to))
// Increment the balance of the owner.
{
mstore(0x00, to)
let balanceSlot := keccak256(0x0c, 0x1c)
let balanceSlotPacked := add(sload(balanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(balanceSlot, balanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}
/// @dev Mints token `id` to `to`, and updates the extra data for token `id` to `value`.
/// Does NOT check if token `id` already exists (assumes `id` is auto-incrementing).
///
/// Requirements:
///
/// - `to` cannot be the zero address.
///
/// Emits a {Transfer} event.
function _mintAndSetExtraDataUnchecked(address to, uint256 id, uint96 value) internal virtual {
_beforeTokenTransfer(address(0), to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
to := shr(96, shl(96, to))
// Update with the owner and extra data.
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
sstore(add(id, add(id, keccak256(0x00, 0x20))), or(shl(160, value), to))
// Increment the balance of the owner.
{
mstore(0x00, to)
let balanceSlot := keccak256(0x0c, 0x1c)
let balanceSlotPacked := add(sload(balanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(balanceSlot, balanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}
/// @dev Equivalent to `_safeMint(to, id, "")`.
function _safeMint(address to, uint256 id) internal virtual {
_safeMint(to, id, "");
}
/// @dev Mints token `id` to `to`.
///
/// Requirements:
///
/// - Token `id` must not exist.
/// - `to` cannot be the zero address.
/// - If `to` refers to a smart contract, it must implement
/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
///
/// Emits a {Transfer} event.
function _safeMint(address to, uint256 id, bytes memory data) internal virtual {
_mint(to, id);
if (_hasCode(to)) _checkOnERC721Received(address(0), to, id, data);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL BURN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `_burn(address(0), id)`.
function _burn(uint256 id) internal virtual {
_burn(address(0), id);
}
/// @dev Destroys token `id`, using `by`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - If `by` is not the zero address,
/// it must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _burn(address by, uint256 id) internal virtual {
address owner = ownerOf(id);
_beforeTokenTransfer(owner, address(0), id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
by := shr(96, shl(96, by))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
// Reload the owner in case it is changed in `_beforeTokenTransfer`.
owner := shr(96, shl(96, ownershipPacked))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// Load and check the token approval.
{
mstore(0x00, owner)
let approvedAddress := sload(add(1, ownershipSlot))
// If `by` is not the zero address, do the authorization check.
// Revert if the `by` is not the owner, nor approved.
if iszero(or(iszero(by), or(eq(by, owner), eq(by, approvedAddress)))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Clear the owner.
sstore(ownershipSlot, xor(ownershipPacked, owner))
// Decrement the balance of `owner`.
{
let balanceSlot := keccak256(0x0c, 0x1c)
sstore(balanceSlot, sub(sload(balanceSlot), 1))
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, owner, 0, id)
}
_afterTokenTransfer(owner, address(0), id);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL APPROVAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `account` is the owner of token `id`, or is approved to manage it.
///
/// Requirements:
/// - Token `id` must exist.
function _isApprovedOrOwner(address account, uint256 id)
internal
view
virtual
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
result := 1
// Clear the upper 96 bits.
account := shr(96, shl(96, account))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, account))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let owner := shr(96, shl(96, sload(ownershipSlot)))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// Check if `account` is the `owner`.
if iszero(eq(account, owner)) {
mstore(0x00, owner)
// Check if `account` is approved to manage the token.
if iszero(sload(keccak256(0x0c, 0x30))) {
result := eq(account, sload(add(1, ownershipSlot)))
}
}
}
}
/// @dev Returns the account approved to manage token `id`.
/// Returns the zero address instead of reverting if the token does not exist.
function _getApproved(uint256 id) internal view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := sload(add(1, add(id, add(id, keccak256(0x00, 0x20)))))
}
}
/// @dev Equivalent to `_approve(address(0), account, id)`.
function _approve(address account, uint256 id) internal virtual {
_approve(address(0), account, id);
}
/// @dev Sets `account` as the approved account to manage token `id`, using `by`.
///
/// Requirements:
/// - Token `id` must exist.
/// - If `by` is not the zero address, `by` must be the owner
/// or an approved operator for the token owner.
///
/// Emits a {Approval} event.
function _approve(address by, address account, uint256 id) internal virtual {
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
account := and(bitmaskAddress, account)
by := and(bitmaskAddress, by)
// Load the owner of the token.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let owner := and(bitmaskAddress, sload(ownershipSlot))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// If `by` is not the zero address, do the authorization check.
// Revert if `by` is not the owner, nor approved.
if iszero(or(iszero(by), eq(by, owner))) {
mstore(0x00, owner)
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Sets `account` as the approved account to manage `id`.
sstore(add(1, ownershipSlot), account)
// Emit the {Approval} event.
log4(codesize(), 0x00, _APPROVAL_EVENT_SIGNATURE, owner, account, id)
}
}
/// @dev Approve or remove the `operator` as an operator for `by`,
/// without authorization checks.
///
/// Emits an {ApprovalForAll} event.
function _setApprovalForAll(address by, address operator, bool isApproved) internal virtual {
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
by := shr(96, shl(96, by))
operator := shr(96, shl(96, operator))
// Convert to 0 or 1.
isApproved := iszero(iszero(isApproved))
// Update the `isApproved` for (`by`, `operator`).
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
mstore(0x00, by)
sstore(keccak256(0x0c, 0x30), isApproved)
// Emit the {ApprovalForAll} event.
mstore(0x00, isApproved)
log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, by, operator)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL TRANSFER FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `_transfer(address(0), from, to, id)`.
function _transfer(address from, address to, uint256 id) internal virtual {
_transfer(address(0), 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.
/// - If `by` is not the zero address,
/// it must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _transfer(address by, address from, address to, uint256 id) internal virtual {
_beforeTokenTransfer(from, to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
from := and(bitmaskAddress, from)
to := and(bitmaskAddress, to)
by := and(bitmaskAddress, by)
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
let owner := and(bitmaskAddress, ownershipPacked)
// Revert if the token does not exist, or if `from` is not the owner.
if iszero(mul(owner, eq(owner, from))) {
// `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
revert(0x1c, 0x04)
}
// Load, check, and update the token approval.
{
mstore(0x00, from)
let approvedAddress := sload(add(1, ownershipSlot))
// If `by` is not the zero address, do the authorization check.
// Revert if the `by` is not the owner, nor approved.
if iszero(or(iszero(by), or(eq(by, from), eq(by, approvedAddress)))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Update with the new owner.
sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
// Decrement the balance of `from`.
{
let fromBalanceSlot := keccak256(0x0c, 0x1c)
sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
}
// Increment the balance of `to`.
{
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x1c)
let toBalanceSlotPacked := add(sload(toBalanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(toBalanceSlot, toBalanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
_afterTokenTransfer(from, to, id);
}
/// @dev Equivalent to `_safeTransfer(from, to, id, "")`.
function _safeTransfer(address from, address to, uint256 id) internal virtual {
_safeTransfer(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 _safeTransfer(address from, address to, uint256 id, bytes memory data)
internal
virtual
{
_transfer(address(0), from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/// @dev Equivalent to `_safeTransfer(by, from, to, id, "")`.
function _safeTransfer(address by, address from, address to, uint256 id) internal virtual {
_safeTransfer(by, 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.
/// - If `by` is not the zero address,
/// it 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 _safeTransfer(address by, address from, address to, uint256 id, bytes memory data)
internal
virtual
{
_transfer(by, from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HOOKS FOR OVERRIDING */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Hook that is called before any token transfers, including minting and burning.
function _beforeTokenTransfer(address from, address to, uint256 id) internal virtual {}
/// @dev Hook that is called after any token transfers, including minting and burning.
function _afterTokenTransfer(address from, address to, uint256 id) internal virtual {}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns if `a` has bytecode of non-zero length.
function _hasCode(address a) private view returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := extcodesize(a) // Can handle dirty upper bits.
}
}
/// @dev Perform a call to invoke {IERC721Receiver-onERC721Received} on `to`.
/// Reverts if the target does not support the function correctly.
function _checkOnERC721Received(address from, address to, uint256 id, bytes memory data)
private
{
/// @solidity memory-safe-assembly
assembly {
// Prepare the calldata.
let m := mload(0x40)
let onERC721ReceivedSelector := 0x150b7a02
mstore(m, onERC721ReceivedSelector)
mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`.
mstore(add(m, 0x40), shr(96, shl(96, from)))
mstore(add(m, 0x60), id)
mstore(add(m, 0x80), 0x80)
let n := mload(data)
mstore(add(m, 0xa0), n)
if n { pop(staticcall(gas(), 4, add(data, 0x20), n, add(m, 0xc0), n)) }
// Revert if the call reverts.
if iszero(call(gas(), to, 0, add(m, 0x1c), add(n, 0xa4), m, 0x20)) {
if returndatasize() {
// Bubble up the revert if the call reverts.
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
}
// Load the returndata and compare it.
if iszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) {
mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`.
revert(0x1c, 0x04)
}
}
}
}// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice 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) let dataEnd := add(add(0x20, data), dataLength) let dataEndValue := mload(dataEnd) // Cache the value at the `dataEnd` slot. mstore(dataEnd, 0x00) // Zeroize the `dataEnd` slot to clear dirty bits. // 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(dataEnd, dataEndValue) // Restore the cached value at `dataEnd`. 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 Library for byte related operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBytes.sol)
library LibBytes {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRUCTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Goated bytes storage struct that totally MOGs, no cap, fr.
/// Uses less gas and bytecode than Solidity's native bytes storage. It's meta af.
/// Packs length with the first 31 bytes if <255 bytes, so it’s mad tight.
struct BytesStorage {
bytes32 _spacer;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The constant returned when the `search` is not found in the bytes.
uint256 internal constant NOT_FOUND = type(uint256).max;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BYTE STORAGE OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sets the value of the bytes storage `$` to `s`.
function set(BytesStorage storage $, bytes memory s) internal {
/// @solidity memory-safe-assembly
assembly {
let n := mload(s)
let packed := or(0xff, shl(8, n))
for { let i := 0 } 1 {} {
if iszero(gt(n, 0xfe)) {
i := 0x1f
packed := or(n, shl(8, mload(add(s, i))))
if iszero(gt(n, i)) { break }
}
let o := add(s, 0x20)
mstore(0x00, $.slot)
for { let p := keccak256(0x00, 0x20) } 1 {} {
sstore(add(p, shr(5, i)), mload(add(o, i)))
i := add(i, 0x20)
if iszero(lt(i, n)) { break }
}
break
}
sstore($.slot, packed)
}
}
/// @dev Sets the value of the bytes storage `$` to `s`.
function setCalldata(BytesStorage storage $, bytes calldata s) internal {
/// @solidity memory-safe-assembly
assembly {
let packed := or(0xff, shl(8, s.length))
for { let i := 0 } 1 {} {
if iszero(gt(s.length, 0xfe)) {
i := 0x1f
packed := or(s.length, shl(8, shr(8, calldataload(s.offset))))
if iszero(gt(s.length, i)) { break }
}
mstore(0x00, $.slot)
for { let p := keccak256(0x00, 0x20) } 1 {} {
sstore(add(p, shr(5, i)), calldataload(add(s.offset, i)))
i := add(i, 0x20)
if iszero(lt(i, s.length)) { break }
}
break
}
sstore($.slot, packed)
}
}
/// @dev Sets the value of the bytes storage `$` to the empty bytes.
function clear(BytesStorage storage $) internal {
delete $._spacer;
}
/// @dev Returns whether the value stored is `$` is the empty bytes "".
function isEmpty(BytesStorage storage $) internal view returns (bool) {
return uint256($._spacer) & 0xff == uint256(0);
}
/// @dev Returns the length of the value stored in `$`.
function length(BytesStorage storage $) internal view returns (uint256 result) {
result = uint256($._spacer);
/// @solidity memory-safe-assembly
assembly {
let n := and(0xff, result)
result := or(mul(shr(8, result), eq(0xff, n)), mul(n, iszero(eq(0xff, n))))
}
}
/// @dev Returns the value stored in `$`.
function get(BytesStorage storage $) internal view returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let o := add(result, 0x20)
let packed := sload($.slot)
let n := shr(8, packed)
for { let i := 0 } 1 {} {
if iszero(eq(or(packed, 0xff), packed)) {
mstore(o, packed)
n := and(0xff, packed)
i := 0x1f
if iszero(gt(n, i)) { break }
}
mstore(0x00, $.slot)
for { let p := keccak256(0x00, 0x20) } 1 {} {
mstore(add(o, i), sload(add(p, shr(5, i))))
i := add(i, 0x20)
if iszero(lt(i, n)) { break }
}
break
}
mstore(result, n) // Store the length of the memory.
mstore(add(o, n), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(o, n), 0x20)) // Allocate memory.
}
}
/// @dev Returns the uint8 at index `i`. If out-of-bounds, returns 0.
function uint8At(BytesStorage storage $, uint256 i) internal view returns (uint8 result) {
/// @solidity memory-safe-assembly
assembly {
for { let packed := sload($.slot) } 1 {} {
if iszero(eq(or(packed, 0xff), packed)) {
if iszero(gt(i, 0x1e)) {
result := byte(i, packed)
break
}
if iszero(gt(i, and(0xff, packed))) {
mstore(0x00, $.slot)
let j := sub(i, 0x1f)
result := byte(and(j, 0x1f), sload(add(keccak256(0x00, 0x20), shr(5, j))))
}
break
}
if iszero(gt(i, shr(8, packed))) {
mstore(0x00, $.slot)
result := byte(and(i, 0x1f), sload(add(keccak256(0x00, 0x20), shr(5, i))))
}
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BYTES OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `subject` all occurrences of `needle` replaced with `replacement`.
function replace(bytes memory subject, bytes memory needle, bytes memory replacement)
internal
pure
returns (bytes memory result)
{
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let needleLen := mload(needle)
let replacementLen := mload(replacement)
let d := sub(result, subject) // Memory difference.
let i := add(subject, 0x20) // Subject bytes pointer.
mstore(0x00, add(i, mload(subject))) // End of subject.
if iszero(gt(needleLen, mload(subject))) {
let subjectSearchEnd := add(sub(mload(0x00), needleLen), 1)
let h := 0 // The hash of `needle`.
if iszero(lt(needleLen, 0x20)) { h := keccak256(add(needle, 0x20), needleLen) }
let s := mload(add(needle, 0x20))
for { let m := shl(3, sub(0x20, and(needleLen, 0x1f))) } 1 {} {
let t := mload(i)
// Whether the first `needleLen % 32` bytes of `subject` and `needle` matches.
if iszero(shr(m, xor(t, s))) {
if h {
if iszero(eq(keccak256(i, needleLen), h)) {
mstore(add(i, d), t)
i := add(i, 1)
if iszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
// Copy the `replacement` one word at a time.
for { let j := 0 } 1 {} {
mstore(add(add(i, d), j), mload(add(add(replacement, 0x20), j)))
j := add(j, 0x20)
if iszero(lt(j, replacementLen)) { break }
}
d := sub(add(d, replacementLen), needleLen)
if needleLen {
i := add(i, needleLen)
if iszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
mstore(add(i, d), t)
i := add(i, 1)
if iszero(lt(i, subjectSearchEnd)) { break }
}
}
let end := mload(0x00)
let n := add(sub(d, add(result, 0x20)), end)
// Copy the rest of the bytes one word at a time.
for {} lt(i, end) { i := add(i, 0x20) } { mstore(add(i, d), mload(i)) }
let o := add(i, d)
mstore(o, 0) // Zeroize the slot after the bytes.
mstore(0x40, add(o, 0x20)) // Allocate memory.
mstore(result, n) // Store the length.
}
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from left to right, starting from `from`.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function indexOf(bytes memory subject, bytes memory needle, uint256 from)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
result := not(0) // Initialize to `NOT_FOUND`.
for { let subjectLen := mload(subject) } 1 {} {
if iszero(mload(needle)) {
result := from
if iszero(gt(from, subjectLen)) { break }
result := subjectLen
break
}
let needleLen := mload(needle)
let subjectStart := add(subject, 0x20)
subject := add(subjectStart, from)
let end := add(sub(add(subjectStart, subjectLen), needleLen), 1)
let m := shl(3, sub(0x20, and(needleLen, 0x1f)))
let s := mload(add(needle, 0x20))
if iszero(and(lt(subject, end), lt(from, subjectLen))) { break }
if iszero(lt(needleLen, 0x20)) {
for { let h := keccak256(add(needle, 0x20), needleLen) } 1 {} {
if iszero(shr(m, xor(mload(subject), s))) {
if eq(keccak256(subject, needleLen), 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 `needle` in `subject`,
/// needleing from left to right.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function indexOf(bytes memory subject, bytes memory needle) internal pure returns (uint256) {
return indexOf(subject, needle, 0);
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from right to left, starting from `from`.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function lastIndexOf(bytes memory subject, bytes memory needle, uint256 from)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
for {} 1 {} {
result := not(0) // Initialize to `NOT_FOUND`.
let needleLen := mload(needle)
if gt(needleLen, mload(subject)) { break }
let w := result
let fromMax := sub(mload(subject), needleLen)
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(needle, 0x20), needleLen) } 1 {} {
if eq(keccak256(subject, needleLen), 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 `needle` in `subject`,
/// needleing from right to left.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function lastIndexOf(bytes memory subject, bytes memory needle)
internal
pure
returns (uint256)
{
return lastIndexOf(subject, needle, type(uint256).max);
}
/// @dev Returns true if `needle` is found in `subject`, false otherwise.
function contains(bytes memory subject, bytes memory needle) internal pure returns (bool) {
return indexOf(subject, needle) != NOT_FOUND;
}
/// @dev Returns whether `subject` starts with `needle`.
function startsWith(bytes memory subject, bytes memory needle)
internal
pure
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(needle)
// Just using keccak256 directly is actually cheaper.
let t := eq(keccak256(add(subject, 0x20), n), keccak256(add(needle, 0x20), n))
result := lt(gt(n, mload(subject)), t)
}
}
/// @dev Returns whether `subject` ends with `needle`.
function endsWith(bytes memory subject, bytes memory needle)
internal
pure
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(needle)
let notInRange := gt(n, mload(subject))
// `subject + 0x20 + max(subject.length - needle.length, 0)`.
let t := add(add(subject, 0x20), mul(iszero(notInRange), sub(mload(subject), n)))
// Just using keccak256 directly is actually cheaper.
result := gt(eq(keccak256(t, n), keccak256(add(needle, 0x20), n)), notInRange)
}
}
/// @dev Returns `subject` repeated `times`.
function repeat(bytes memory subject, uint256 times)
internal
pure
returns (bytes memory result)
{
/// @solidity memory-safe-assembly
assembly {
let l := mload(subject) // Subject length.
if iszero(or(iszero(times), iszero(l))) {
result := mload(0x40)
subject := add(subject, 0x20)
let o := add(result, 0x20)
for {} 1 {} {
// Copy the `subject` one word at a time.
for { let j := 0 } 1 {} {
mstore(add(o, j), mload(add(subject, j)))
j := add(j, 0x20)
if iszero(lt(j, l)) { break }
}
o := add(o, l)
times := sub(times, 1)
if iszero(times) { break }
}
mstore(o, 0) // Zeroize the slot after the bytes.
mstore(0x40, add(o, 0x20)) // Allocate memory.
mstore(result, sub(o, add(result, 0x20))) // Store the length.
}
}
}
/// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
/// `start` and `end` are byte offsets.
function slice(bytes memory subject, uint256 start, uint256 end)
internal
pure
returns (bytes memory result)
{
/// @solidity memory-safe-assembly
assembly {
let l := mload(subject) // Subject length.
if iszero(gt(l, end)) { end := l }
if iszero(gt(l, start)) { start := l }
if lt(start, end) {
result := mload(0x40)
let n := sub(end, start)
let i := add(subject, start)
let w := not(0x1f)
// Copy the `subject` one word at a time, backwards.
for { let j := and(add(n, 0x1f), w) } 1 {} {
mstore(add(result, j), mload(add(i, j)))
j := add(j, w) // `sub(j, 0x20)`.
if iszero(j) { break }
}
let o := add(add(result, 0x20), n)
mstore(o, 0) // Zeroize the slot after the bytes.
mstore(0x40, add(o, 0x20)) // Allocate memory.
mstore(result, n) // Store the length.
}
}
}
/// @dev Returns a copy of `subject` sliced from `start` to the end of the bytes.
/// `start` is a byte offset.
function slice(bytes memory subject, uint256 start)
internal
pure
returns (bytes memory result)
{
result = slice(subject, start, type(uint256).max);
}
/// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
/// `start` and `end` are byte offsets. Faster than Solidity's native slicing.
function sliceCalldata(bytes calldata subject, uint256 start, uint256 end)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
end := xor(end, mul(xor(end, subject.length), lt(subject.length, end)))
start := xor(start, mul(xor(start, subject.length), lt(subject.length, start)))
result.offset := add(subject.offset, start)
result.length := mul(lt(start, end), sub(end, start))
}
}
/// @dev Returns a copy of `subject` sliced from `start` to the end of the bytes.
/// `start` is a byte offset. Faster than Solidity's native slicing.
function sliceCalldata(bytes calldata subject, uint256 start)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
start := xor(start, mul(xor(start, subject.length), lt(subject.length, start)))
result.offset := add(subject.offset, start)
result.length := mul(lt(start, subject.length), sub(subject.length, start))
}
}
/// @dev Reduces the size of `subject` to `n`.
/// If `n` is greater than the size of `subject`, this will be a no-op.
function truncate(bytes memory subject, uint256 n)
internal
pure
returns (bytes memory result)
{
/// @solidity memory-safe-assembly
assembly {
result := subject
mstore(mul(lt(n, mload(result)), result), n)
}
}
/// @dev Returns a copy of `subject`, with the length reduced to `n`.
/// If `n` is greater than the size of `subject`, this will be a no-op.
function truncatedCalldata(bytes calldata subject, uint256 n)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
result.offset := subject.offset
result.length := xor(n, mul(xor(n, subject.length), lt(subject.length, n)))
}
}
/// @dev Returns all the indices of `needle` in `subject`.
/// The indices are byte offsets.
function indicesOf(bytes memory subject, bytes memory needle)
internal
pure
returns (uint256[] memory result)
{
/// @solidity memory-safe-assembly
assembly {
let searchLen := mload(needle)
if iszero(gt(searchLen, mload(subject))) {
result := mload(0x40)
let i := add(subject, 0x20)
let o := add(result, 0x20)
let subjectSearchEnd := add(sub(add(i, mload(subject)), searchLen), 1)
let h := 0 // The hash of `needle`.
if iszero(lt(searchLen, 0x20)) { h := keccak256(add(needle, 0x20), searchLen) }
let s := mload(add(needle, 0x20))
for { let m := shl(3, sub(0x20, and(searchLen, 0x1f))) } 1 {} {
let t := mload(i)
// Whether the first `searchLen % 32` bytes of `subject` and `needle` matches.
if iszero(shr(m, xor(t, s))) {
if h {
if iszero(eq(keccak256(i, searchLen), h)) {
i := add(i, 1)
if iszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
mstore(o, sub(i, add(subject, 0x20))) // Append to `result`.
o := add(o, 0x20)
i := add(i, searchLen) // Advance `i` by `searchLen`.
if searchLen {
if iszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
i := add(i, 1)
if iszero(lt(i, subjectSearchEnd)) { break }
}
mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store the length of `result`.
// Allocate memory for result.
// We allocate one more word, so this array can be recycled for {split}.
mstore(0x40, add(o, 0x20))
}
}
}
/// @dev Returns an arrays of bytess based on the `delimiter` inside of the `subject` bytes.
function split(bytes memory subject, bytes memory delimiter)
internal
pure
returns (bytes[] 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))
for { let prevIndex := 0 } 1 {} {
let index := mload(indexPtr)
mstore(indexPtr, 0x60)
if iszero(eq(index, prevIndex)) {
let element := mload(0x40)
let l := sub(index, prevIndex)
mstore(element, l) // Store the length of the element.
// Copy the `subject` one word at a time, backwards.
for { let o := and(add(l, 0x1f), w) } 1 {} {
mstore(add(element, o), mload(add(add(subject, prevIndex), o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
mstore(add(add(element, 0x20), l), 0) // Zeroize the slot after the bytes.
// Allocate memory for the length and the bytes, rounded up to a multiple of 32.
mstore(0x40, add(element, and(add(l, 0x3f), w)))
mstore(indexPtr, element) // Store the `element` into the array.
}
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 bytes of `a` and `b`.
/// Cheaper than `bytes.concat()` and does not de-align the free memory pointer.
function concat(bytes memory a, bytes memory b) internal pure returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let w := not(0x1f)
let aLen := mload(a)
// Copy `a` one word at a time, backwards.
for { let o := and(add(aLen, 0x20), w) } 1 {} {
mstore(add(result, o), mload(add(a, o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
let bLen := mload(b)
let output := add(result, aLen)
// Copy `b` one word at a time, backwards.
for { let o := and(add(bLen, 0x20), w) } 1 {} {
mstore(add(output, o), mload(add(b, o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
let totalLen := add(aLen, bLen)
let last := add(add(result, 0x20), totalLen)
mstore(last, 0) // Zeroize the slot after the bytes.
mstore(result, totalLen) // Store the length.
mstore(0x40, add(last, 0x20)) // Allocate memory.
}
}
/// @dev Returns whether `a` equals `b`.
function eq(bytes memory a, bytes 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 bytes.
function eqs(bytes 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 Returns 0 if `a == b`, -1 if `a < b`, +1 if `a > b`.
/// If `a` == b[:a.length]`, and `a.length < b.length`, returns -1.
function cmp(bytes memory a, bytes memory b) internal pure returns (int256 result) {
/// @solidity memory-safe-assembly
assembly {
let aLen := mload(a)
let bLen := mload(b)
let n := and(xor(aLen, mul(xor(aLen, bLen), lt(bLen, aLen))), not(0x1f))
if n {
for { let i := 0x20 } 1 {} {
let x := mload(add(a, i))
let y := mload(add(b, i))
if iszero(or(xor(x, y), eq(i, n))) {
i := add(i, 0x20)
continue
}
result := sub(gt(x, y), lt(x, y))
break
}
}
// forgefmt: disable-next-item
if iszero(result) {
let l := 0x201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201
let x := and(mload(add(add(a, 0x20), n)), shl(shl(3, byte(sub(aLen, n), l)), not(0)))
let y := and(mload(add(add(b, 0x20), n)), shl(shl(3, byte(sub(bLen, n), l)), not(0)))
result := sub(gt(x, y), lt(x, y))
if iszero(result) { result := sub(gt(aLen, bLen), lt(aLen, bLen)) }
}
}
}
/// @dev Directly returns `a` without copying.
function directReturn(bytes memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
// Assumes that the bytes does not start from the scratch space.
let retStart := sub(a, 0x20)
let retUnpaddedSize := add(mload(a), 0x40)
// Right pad with zeroes. Just in case the bytes is produced
// by a method that doesn't zero right pad.
mstore(add(retStart, retUnpaddedSize), 0)
mstore(retStart, 0x20) // Store the return offset.
// End the transaction, returning the bytes.
return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize)))
}
}
/// @dev Directly returns `a` with minimal copying.
function directReturn(bytes[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let n := mload(a) // `a.length`.
let o := add(a, 0x20) // Start of elements in `a`.
let u := a // Highest memory slot.
let w := not(0x1f)
for { let i := 0 } iszero(eq(i, n)) { i := add(i, 1) } {
let c := add(o, shl(5, i)) // Location of pointer to `a[i]`.
let s := mload(c) // `a[i]`.
let l := mload(s) // `a[i].length`.
let r := and(l, 0x1f) // `a[i].length % 32`.
let z := add(0x20, and(l, w)) // Offset of last word in `a[i]` from `s`.
// If `s` comes before `o`, or `s` is not zero right padded.
if iszero(lt(lt(s, o), or(iszero(r), iszero(shl(shl(3, r), mload(add(s, z))))))) {
let m := mload(0x40)
mstore(m, l) // Copy `a[i].length`.
for {} 1 {} {
mstore(add(m, z), mload(add(s, z))) // Copy `a[i]`, backwards.
z := add(z, w) // `sub(z, 0x20)`.
if iszero(z) { break }
}
let e := add(add(m, 0x20), l)
mstore(e, 0) // Zeroize the slot after the copied bytes.
mstore(0x40, add(e, 0x20)) // Allocate memory.
s := m
}
mstore(c, sub(s, o)) // Convert to calldata offset.
let t := add(l, add(s, 0x20))
if iszero(lt(t, u)) { u := t }
}
let retStart := add(a, w) // Assumes `a` doesn't start from scratch space.
mstore(retStart, 0x20) // Store the return offset.
return(retStart, add(0x40, sub(u, retStart))) // End the transaction.
}
}
/// @dev Returns the word at `offset`, without any bounds checks.
function load(bytes memory a, uint256 offset) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(add(add(a, 0x20), offset))
}
}
/// @dev Returns the word at `offset`, without any bounds checks.
function loadCalldata(bytes calldata a, uint256 offset)
internal
pure
returns (bytes32 result)
{
/// @solidity memory-safe-assembly
assembly {
result := calldataload(add(a.offset, offset))
}
}
/// @dev Returns a slice representing a static struct in the calldata. Performs bounds checks.
function staticStructInCalldata(bytes calldata a, uint256 offset)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
let l := sub(a.length, 0x20)
result.offset := add(a.offset, offset)
result.length := sub(a.length, offset)
if or(shr(64, or(l, a.offset)), gt(offset, l)) { revert(l, 0x00) }
}
}
/// @dev Returns a slice representing a dynamic struct in the calldata. Performs bounds checks.
function dynamicStructInCalldata(bytes calldata a, uint256 offset)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
let l := sub(a.length, 0x20)
let s := calldataload(add(a.offset, offset)) // Relative offset of `result` from `a.offset`.
result.offset := add(a.offset, s)
result.length := sub(a.length, s)
if or(shr(64, or(s, or(l, a.offset))), gt(offset, l)) { revert(l, 0x00) }
}
}
/// @dev Returns bytes in calldata. Performs bounds checks.
function bytesInCalldata(bytes calldata a, uint256 offset)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
let l := sub(a.length, 0x20)
let s := calldataload(add(a.offset, offset)) // Relative offset of `result` from `a.offset`.
result.offset := add(add(a.offset, s), 0x20)
result.length := calldataload(add(a.offset, s))
// forgefmt: disable-next-item
if or(shr(64, or(result.length, or(s, or(l, a.offset)))),
or(gt(add(s, result.length), l), gt(offset, l))) { revert(l, 0x00) }
}
}
/// @dev Returns empty calldata bytes. For silencing the compiler.
function emptyCalldata() internal pure returns (bytes calldata result) {
/// @solidity memory-safe-assembly
assembly {
result.length := 0
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {LibBytes} from "./LibBytes.sol";
/// @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 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRUCTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Goated string storage struct that totally MOGs, no cap, fr.
/// Uses less gas and bytecode than Solidity's native string storage. It's meta af.
/// Packs length with the first 31 bytes if <255 bytes, so it’s mad tight.
struct StringStorage {
bytes32 _spacer;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 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();
/// @dev The input string must be a 7-bit ASCII.
error StringNot7BitASCII();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The constant returned when the `search` is not found in the string.
uint256 internal constant NOT_FOUND = type(uint256).max;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant ALPHANUMERIC_7_BIT_ASCII = 0x7fffffe07fffffe03ff000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant LETTERS_7_BIT_ASCII = 0x7fffffe07fffffe0000000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyz'.
uint128 internal constant LOWERCASE_7_BIT_ASCII = 0x7fffffe000000000000000000000000;
/// @dev Lookup for 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant UPPERCASE_7_BIT_ASCII = 0x7fffffe0000000000000000;
/// @dev Lookup for '0123456789'.
uint128 internal constant DIGITS_7_BIT_ASCII = 0x3ff000000000000;
/// @dev Lookup for '0123456789abcdefABCDEF'.
uint128 internal constant HEXDIGITS_7_BIT_ASCII = 0x7e0000007e03ff000000000000;
/// @dev Lookup for '01234567'.
uint128 internal constant OCTDIGITS_7_BIT_ASCII = 0xff000000000000;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'.
uint128 internal constant PRINTABLE_7_BIT_ASCII = 0x7fffffffffffffffffffffff00003e00;
/// @dev Lookup for '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.
uint128 internal constant PUNCTUATION_7_BIT_ASCII = 0x78000001f8000001fc00fffe00000000;
/// @dev Lookup for ' \t\n\r\x0b\x0c'.
uint128 internal constant WHITESPACE_7_BIT_ASCII = 0x100003e00;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRING STORAGE OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sets the value of the string storage `$` to `s`.
function set(StringStorage storage $, string memory s) internal {
LibBytes.set(bytesStorage($), bytes(s));
}
/// @dev Sets the value of the string storage `$` to `s`.
function setCalldata(StringStorage storage $, string calldata s) internal {
LibBytes.setCalldata(bytesStorage($), bytes(s));
}
/// @dev Sets the value of the string storage `$` to the empty string.
function clear(StringStorage storage $) internal {
delete $._spacer;
}
/// @dev Returns whether the value stored is `$` is the empty string "".
function isEmpty(StringStorage storage $) internal view returns (bool) {
return uint256($._spacer) & 0xff == uint256(0);
}
/// @dev Returns the length of the value stored in `$`.
function length(StringStorage storage $) internal view returns (uint256) {
return LibBytes.length(bytesStorage($));
}
/// @dev Returns the value stored in `$`.
function get(StringStorage storage $) internal view returns (string memory) {
return string(LibBytes.get(bytesStorage($)));
}
/// @dev Returns the uint8 at index `i`. If out-of-bounds, returns 0.
function uint8At(StringStorage storage $, uint256 i) internal view returns (uint8) {
return LibBytes.uint8At(bytesStorage($), i);
}
/// @dev Helper to cast `$` to a `BytesStorage`.
function bytesStorage(StringStorage storage $)
internal
pure
returns (LibBytes.BytesStorage storage casted)
{
/// @solidity memory-safe-assembly
assembly {
casted.slot := $.slot
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the base 10 decimal representation of `value`.
function toString(uint256 value) internal pure returns (string memory result) {
/// @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.
result := add(mload(0x40), 0x80)
mstore(0x40, add(result, 0x20)) // Allocate memory.
mstore(result, 0) // Zeroize the slot after the string.
let end := result // Cache the end of the memory to calculate the length later.
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 {} {
result := add(result, w) // `sub(result, 1)`.
// Store the character to the pointer.
// The ASCII index of the '0' character is 48.
mstore8(result, add(48, mod(temp, 10)))
temp := div(temp, 10) // Keep dividing `temp` until zero.
if iszero(temp) { break }
}
let n := sub(end, result)
result := sub(result, 0x20) // Move the pointer 32 bytes back to make room for the length.
mstore(result, n) // Store the length.
}
}
/// @dev Returns the base 10 decimal representation of `value`.
function toString(int256 value) internal pure returns (string memory result) {
if (value >= 0) return toString(uint256(value));
unchecked {
result = 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 n := mload(result) // Load the string length.
mstore(result, 0x2d) // Store the '-' character.
result := sub(result, 1) // Move back the string pointer by a byte.
mstore(result, add(n, 1)) // Update the string length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HEXADECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the hexadecimal representation of `value`,
/// left-padded to an input length of `byteCount` bytes.
/// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,
/// giving a total length of `byteCount * 2 + 2` bytes.
/// Reverts if `byteCount` is too small for the output to contain all the digits.
function toHexString(uint256 value, uint256 byteCount)
internal
pure
returns (string memory result)
{
result = toHexStringNoPrefix(value, byteCount);
/// @solidity memory-safe-assembly
assembly {
let n := add(mload(result), 2) // Compute the length.
mstore(result, 0x3078) // Store the "0x" prefix.
result := sub(result, 2) // Move the pointer.
mstore(result, n) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`,
/// left-padded to an input length of `byteCount` bytes.
/// The output is not prefixed with "0x" and is encoded using 2 hexadecimal digits per byte,
/// giving a total length of `byteCount * 2` bytes.
/// Reverts if `byteCount` is too small for the output to contain all the digits.
function toHexStringNoPrefix(uint256 value, uint256 byteCount)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
// We need 0x20 bytes for the trailing zeros padding, `byteCount * 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.
result := add(mload(0x40), and(add(shl(1, byteCount), 0x42), not(0x1f)))
mstore(0x40, add(result, 0x20)) // Allocate memory.
mstore(result, 0) // Zeroize the slot after the string.
let end := result // Cache the end to calculate the length later.
// Store "0123456789abcdef" in scratch space.
mstore(0x0f, 0x30313233343536373839616263646566)
let start := sub(result, add(byteCount, byteCount))
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 {} {
result := add(result, w) // `sub(result, 2)`.
mstore8(add(result, 1), mload(and(temp, 15)))
mstore8(result, mload(and(shr(4, temp), 15)))
temp := shr(8, temp)
if iszero(xor(result, start)) { break }
}
if temp {
mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`.
revert(0x1c, 0x04)
}
let n := sub(end, result)
result := sub(result, 0x20)
mstore(result, n) // Store the length.
}
}
/// @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 result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let n := add(mload(result), 2) // Compute the length.
mstore(result, 0x3078) // Store the "0x" prefix.
result := sub(result, 2) // Move the pointer.
mstore(result, n) // Store 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 result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let o := eq(byte(0, mload(add(result, 0x20))), 0x30) // Whether leading zero is present.
let n := add(mload(result), 2) // Compute the length.
mstore(add(result, o), 0x3078) // Store the "0x" prefix, accounting for leading zero.
result := sub(add(result, o), 2) // Move the pointer, accounting for leading zero.
mstore(result, sub(n, o)) // Store 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 result)
{
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let o := eq(byte(0, mload(add(result, 0x20))), 0x30) // Whether leading zero is present.
let n := mload(result) // Get the length.
result := add(result, o) // Move the pointer, accounting for leading zero.
mstore(result, sub(n, o)) // Store 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 result) {
/// @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.
result := add(mload(0x40), 0x80)
mstore(0x40, add(result, 0x20)) // Allocate memory.
mstore(result, 0) // Zeroize the slot after the string.
let end := result // Cache the end to calculate the length later.
mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
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 {} {
result := add(result, w) // `sub(result, 2)`.
mstore8(add(result, 1), mload(and(temp, 15)))
mstore8(result, mload(and(shr(4, temp), 15)))
temp := shr(8, temp)
if iszero(temp) { break }
}
let n := sub(end, result)
result := sub(result, 0x20)
mstore(result, n) // Store the length.
}
}
/// @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 result) {
result = toHexString(value);
/// @solidity memory-safe-assembly
assembly {
let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...`
let o := add(result, 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 result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let n := add(mload(result), 2) // Compute the length.
mstore(result, 0x3078) // Store the "0x" prefix.
result := sub(result, 2) // Move the pointer.
mstore(result, n) // Store 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 result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
// Allocate 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(result, 0x80))
mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
result := add(result, 2)
mstore(result, 40) // Store the length.
let o := add(result, 0x20)
mstore(add(o, 40), 0) // Zeroize the slot after the string.
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 result) {
result = toHexStringNoPrefix(raw);
/// @solidity memory-safe-assembly
assembly {
let n := add(mload(result), 2) // Compute the length.
mstore(result, 0x3078) // Store the "0x" prefix.
result := sub(result, 2) // Move the pointer.
mstore(result, n) // Store 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 result) {
/// @solidity memory-safe-assembly
assembly {
let n := mload(raw)
result := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix.
mstore(result, add(n, n)) // Store the length of the output.
mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
let o := add(result, 0x20)
let end := add(raw, n)
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 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 {
result := 1
let mask := shl(7, div(not(0), 255))
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)
}
}
}
/// @dev Returns if this string is a 7-bit ASCII string,
/// AND all characters are in the `allowed` lookup.
/// Note: If `s` is empty, returns true regardless of `allowed`.
function is7BitASCII(string memory s, uint128 allowed) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if mload(s) {
let allowed_ := shr(128, shl(128, allowed))
let o := add(s, 0x20)
for { let end := add(o, mload(s)) } 1 {} {
result := and(result, shr(byte(0, mload(o)), allowed_))
o := add(o, 1)
if iszero(and(result, lt(o, end))) { break }
}
}
}
}
/// @dev Converts the bytes in the 7-bit ASCII string `s` to
/// an allowed lookup for use in `is7BitASCII(s, allowed)`.
/// To save runtime gas, you can cache the result in an immutable variable.
function to7BitASCIIAllowedLookup(string memory s) internal pure returns (uint128 result) {
/// @solidity memory-safe-assembly
assembly {
if mload(s) {
let o := add(s, 0x20)
for { let end := add(o, mload(s)) } 1 {} {
result := or(result, shl(byte(0, mload(o)), 1))
o := add(o, 1)
if iszero(lt(o, end)) { break }
}
if shr(128, result) {
mstore(0x00, 0xc9807e0d) // `StringNot7BitASCII()`.
revert(0x1c, 0x04)
}
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 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 `needle` replaced with `replacement`.
function replace(string memory subject, string memory needle, string memory replacement)
internal
pure
returns (string memory)
{
return string(LibBytes.replace(bytes(subject), bytes(needle), bytes(replacement)));
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from left to right, starting from `from`.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function indexOf(string memory subject, string memory needle, uint256 from)
internal
pure
returns (uint256)
{
return LibBytes.indexOf(bytes(subject), bytes(needle), from);
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from left to right.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function indexOf(string memory subject, string memory needle) internal pure returns (uint256) {
return LibBytes.indexOf(bytes(subject), bytes(needle), 0);
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from right to left, starting from `from`.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function lastIndexOf(string memory subject, string memory needle, uint256 from)
internal
pure
returns (uint256)
{
return LibBytes.lastIndexOf(bytes(subject), bytes(needle), from);
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from right to left.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function lastIndexOf(string memory subject, string memory needle)
internal
pure
returns (uint256)
{
return LibBytes.lastIndexOf(bytes(subject), bytes(needle), type(uint256).max);
}
/// @dev Returns true if `needle` is found in `subject`, false otherwise.
function contains(string memory subject, string memory needle) internal pure returns (bool) {
return LibBytes.contains(bytes(subject), bytes(needle));
}
/// @dev Returns whether `subject` starts with `needle`.
function startsWith(string memory subject, string memory needle) internal pure returns (bool) {
return LibBytes.startsWith(bytes(subject), bytes(needle));
}
/// @dev Returns whether `subject` ends with `needle`.
function endsWith(string memory subject, string memory needle) internal pure returns (bool) {
return LibBytes.endsWith(bytes(subject), bytes(needle));
}
/// @dev Returns `subject` repeated `times`.
function repeat(string memory subject, uint256 times) internal pure returns (string memory) {
return string(LibBytes.repeat(bytes(subject), times));
}
/// @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)
{
return string(LibBytes.slice(bytes(subject), start, end));
}
/// @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) {
return string(LibBytes.slice(bytes(subject), start, type(uint256).max));
}
/// @dev Returns all the indices of `needle` in `subject`.
/// The indices are byte offsets.
function indicesOf(string memory subject, string memory needle)
internal
pure
returns (uint256[] memory)
{
return LibBytes.indicesOf(bytes(subject), bytes(needle));
}
/// @dev Returns an 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)
{
bytes[] memory a = LibBytes.split(bytes(subject), bytes(delimiter));
/// @solidity memory-safe-assembly
assembly {
result := a
}
}
/// @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) {
return string(LibBytes.concat(bytes(a), bytes(b)));
}
/// @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 n := mload(subject)
if n {
result := mload(0x40)
let o := add(result, 0x20)
let d := sub(subject, result)
let flags := shl(add(70, shl(5, toUpper)), 0x3ffffff)
for { let end := add(o, n) } 1 {} {
let b := byte(0, mload(add(d, o)))
mstore8(o, xor(and(shr(b, flags), 0x20), b))
o := add(o, 1)
if eq(o, end) { break }
}
mstore(result, n) // Store the length.
mstore(o, 0) // Zeroize the slot after the string.
mstore(0x40, add(o, 0x20)) // Allocate 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) // Store the length.
let o := add(result, 0x20)
mstore(o, s) // Store the bytes of the string.
mstore(add(o, n), 0) // Zeroize the slot after the string.
mstore(0x40, add(result, 0x40)) // Allocate memory.
}
}
/// @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 {
result := mload(0x40)
let end := add(s, mload(s))
let o := add(result, 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(o, c)
o := add(o, 1)
continue
}
let t := shr(248, mload(c))
mstore(o, mload(and(t, 0x1f)))
o := add(o, shr(5, t))
}
mstore(o, 0) // Zeroize the slot after the string.
mstore(result, sub(o, add(result, 0x20))) // Store the length.
mstore(0x40, add(o, 0x20)) // Allocate 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 {
result := mload(0x40)
let o := add(result, 0x20)
if addDoubleQuotes {
mstore8(o, 34)
o := add(1, o)
}
// 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 { let end := add(s, mload(s)) } 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(o, c)
o := add(o, 1)
continue
}
mstore8(o, 0x5c) // "\\".
mstore8(add(o, 1), c)
o := add(o, 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(o, mload(0x19)) // "\\u00XX".
o := add(o, 6)
continue
}
mstore8(o, 0x5c) // "\\".
mstore8(add(o, 1), mload(add(c, 8)))
o := add(o, 2)
}
if addDoubleQuotes {
mstore8(o, 34)
o := add(1, o)
}
mstore(o, 0) // Zeroize the slot after the string.
mstore(result, sub(o, add(result, 0x20))) // Store the length.
mstore(0x40, add(o, 0x20)) // Allocate 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 Encodes `s` so that it can be safely used in a URI,
/// just like `encodeURIComponent` in JavaScript.
/// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
/// See: https://datatracker.ietf.org/doc/html/rfc2396
/// See: https://datatracker.ietf.org/doc/html/rfc3986
function encodeURIComponent(string memory s) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
// Store "0123456789ABCDEF" in scratch space.
// Uppercased to be consistent with JavaScript's implementation.
mstore(0x0f, 0x30313233343536373839414243444546)
let o := add(result, 0x20)
for { let end := add(s, mload(s)) } iszero(eq(s, end)) {} {
s := add(s, 1)
let c := and(mload(s), 0xff)
// If not in `[0-9A-Z-a-z-_.!~*'()]`.
if iszero(and(1, shr(c, 0x47fffffe87fffffe03ff678200000000))) {
mstore8(o, 0x25) // '%'.
mstore8(add(o, 1), mload(and(shr(4, c), 15)))
mstore8(add(o, 2), mload(and(c, 15)))
o := add(o, 3)
continue
}
mstore8(o, c)
o := add(o, 1)
}
mstore(result, sub(o, add(result, 0x20))) // Store the length.
mstore(o, 0) // Zeroize the slot after the string.
mstore(0x40, add(o, 0x20)) // Allocate memory.
}
}
/// @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 Returns 0 if `a == b`, -1 if `a < b`, +1 if `a > b`.
/// If `a` == b[:a.length]`, and `a.length < b.length`, returns -1.
function cmp(string memory a, string memory b) internal pure returns (int256) {
return LibBytes.cmp(bytes(a), bytes(b));
}
/// @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 {
result := mload(0x40) // Grab the free memory pointer.
mstore(0x40, add(result, 0x40)) // Allocate 2 words (1 for the length, 1 for the bytes).
mstore(result, 0) // Zeroize the length slot.
mstore(add(result, 0x1f), packed) // Store the length and bytes.
mstore(add(add(result, 0x20), mload(result)), 0) // Right pad with zeroes.
}
}
/// @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 aLen := mload(a)
// We don't need to zero right pad the strings,
// since this is our own custom non-standard packing scheme.
result :=
mul(
or( // Load the length and the bytes of `a` and `b`.
shl(shl(3, sub(0x1f, aLen)), mload(add(a, aLen))), mload(sub(add(b, 0x1e), aLen))),
// `totalLen != 0 && totalLen < 31`. Abuses underflow.
// Assumes that the lengths are valid and within the block gas limit.
lt(sub(add(aLen, 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 {
resultA := mload(0x40) // Grab the free memory pointer.
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 {
/// @solidity memory-safe-assembly
assembly {
// Assumes that the string does not start from the scratch space.
let retStart := sub(a, 0x20)
let retUnpaddedSize := 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, retUnpaddedSize), 0)
mstore(retStart, 0x20) // Store the return offset.
// End the transaction, returning the string.
return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize)))
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol)
library MerkleProofLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MERKLE PROOF VERIFICATION OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf)
internal
pure
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
if mload(proof) {
// Initialize `offset` to the offset of `proof` elements in memory.
let offset := add(proof, 0x20)
// Left shift by 5 is equivalent to multiplying by 0x20.
let end := add(offset, shl(5, mload(proof)))
// Iterate over proof elements to compute root hash.
for {} 1 {} {
// Slot of `leaf` in scratch space.
// If the condition is true: 0x20, otherwise: 0x00.
let scratch := shl(5, gt(leaf, mload(offset)))
// Store elements to hash contiguously in scratch space.
// Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
mstore(scratch, leaf)
mstore(xor(scratch, 0x20), mload(offset))
// Reuse `leaf` to store the hash to reduce stack operations.
leaf := keccak256(0x00, 0x40)
offset := add(offset, 0x20)
if iszero(lt(offset, end)) { break }
}
}
isValid := eq(leaf, root)
}
}
/// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf)
internal
pure
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
if proof.length {
// Left shift by 5 is equivalent to multiplying by 0x20.
let end := add(proof.offset, shl(5, proof.length))
// Initialize `offset` to the offset of `proof` in the calldata.
let offset := proof.offset
// Iterate over proof elements to compute root hash.
for {} 1 {} {
// Slot of `leaf` in scratch space.
// If the condition is true: 0x20, otherwise: 0x00.
let scratch := shl(5, gt(leaf, calldataload(offset)))
// Store elements to hash contiguously in scratch space.
// Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
mstore(scratch, leaf)
mstore(xor(scratch, 0x20), calldataload(offset))
// Reuse `leaf` to store the hash to reduce stack operations.
leaf := keccak256(0x00, 0x40)
offset := add(offset, 0x20)
if iszero(lt(offset, end)) { break }
}
}
isValid := eq(leaf, root)
}
}
/// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
/// given `proof` and `flags`.
///
/// Note:
/// - Breaking the invariant `flags.length == (leaves.length - 1) + proof.length`
/// will always return false.
/// - The sum of the lengths of `proof` and `leaves` must never overflow.
/// - Any non-zero word in the `flags` array is treated as true.
/// - The memory offset of `proof` must be non-zero
/// (i.e. `proof` is not pointing to the scratch space).
function verifyMultiProof(
bytes32[] memory proof,
bytes32 root,
bytes32[] memory leaves,
bool[] memory flags
) internal pure returns (bool isValid) {
// Rebuilds the root by consuming and producing values on a queue.
// The queue starts with the `leaves` array, and goes into a `hashes` array.
// After the process, the last element on the queue is verified
// to be equal to the `root`.
//
// The `flags` array denotes whether the sibling
// should be popped from the queue (`flag == true`), or
// should be popped from the `proof` (`flag == false`).
/// @solidity memory-safe-assembly
assembly {
// Cache the lengths of the arrays.
let leavesLength := mload(leaves)
let proofLength := mload(proof)
let flagsLength := mload(flags)
// Advance the pointers of the arrays to point to the data.
leaves := add(0x20, leaves)
proof := add(0x20, proof)
flags := add(0x20, flags)
// If the number of flags is correct.
for {} eq(add(leavesLength, proofLength), add(flagsLength, 1)) {} {
// For the case where `proof.length + leaves.length == 1`.
if iszero(flagsLength) {
// `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
isValid := eq(mload(xor(leaves, mul(xor(proof, leaves), proofLength))), root)
break
}
// The required final proof offset if `flagsLength` is not zero, otherwise zero.
let proofEnd := add(proof, shl(5, proofLength))
// We can use the free memory space for the queue.
// We don't need to allocate, since the queue is temporary.
let hashesFront := mload(0x40)
// Copy the leaves into the hashes.
// Sometimes, a little memory expansion costs less than branching.
// Should cost less, even with a high free memory offset of 0x7d00.
leavesLength := shl(5, leavesLength)
for { let i := 0 } iszero(eq(i, leavesLength)) { i := add(i, 0x20) } {
mstore(add(hashesFront, i), mload(add(leaves, i)))
}
// Compute the back of the hashes.
let hashesBack := add(hashesFront, leavesLength)
// This is the end of the memory for the queue.
// We recycle `flagsLength` to save on stack variables (sometimes save gas).
flagsLength := add(hashesBack, shl(5, flagsLength))
for {} 1 {} {
// Pop from `hashes`.
let a := mload(hashesFront)
// Pop from `hashes`.
let b := mload(add(hashesFront, 0x20))
hashesFront := add(hashesFront, 0x40)
// If the flag is false, load the next proof,
// else, pops from the queue.
if iszero(mload(flags)) {
// Loads the next proof.
b := mload(proof)
proof := add(proof, 0x20)
// Unpop from `hashes`.
hashesFront := sub(hashesFront, 0x20)
}
// Advance to the next flag.
flags := add(flags, 0x20)
// Slot of `a` in scratch space.
// If the condition is true: 0x20, otherwise: 0x00.
let scratch := shl(5, gt(a, b))
// Hash the scratch space and push the result onto the queue.
mstore(scratch, a)
mstore(xor(scratch, 0x20), b)
mstore(hashesBack, keccak256(0x00, 0x40))
hashesBack := add(hashesBack, 0x20)
if iszero(lt(hashesBack, flagsLength)) { break }
}
isValid :=
and(
// Checks if the last value in the queue is same as the root.
eq(mload(sub(hashesBack, 0x20)), root),
// And whether all the proofs are used, if required.
eq(proofEnd, proof)
)
break
}
}
}
/// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
/// given `proof` and `flags`.
///
/// Note:
/// - Breaking the invariant `flags.length == (leaves.length - 1) + proof.length`
/// will always return false.
/// - Any non-zero word in the `flags` array is treated as true.
/// - The calldata offset of `proof` must be non-zero
/// (i.e. `proof` is from a regular Solidity function with a 4-byte selector).
function verifyMultiProofCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32[] calldata leaves,
bool[] calldata flags
) internal pure returns (bool isValid) {
// Rebuilds the root by consuming and producing values on a queue.
// The queue starts with the `leaves` array, and goes into a `hashes` array.
// After the process, the last element on the queue is verified
// to be equal to the `root`.
//
// The `flags` array denotes whether the sibling
// should be popped from the queue (`flag == true`), or
// should be popped from the `proof` (`flag == false`).
/// @solidity memory-safe-assembly
assembly {
// If the number of flags is correct.
for {} eq(add(leaves.length, proof.length), add(flags.length, 1)) {} {
// For the case where `proof.length + leaves.length == 1`.
if iszero(flags.length) {
// `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
// forgefmt: disable-next-item
isValid := eq(
calldataload(
xor(leaves.offset, mul(xor(proof.offset, leaves.offset), proof.length))
),
root
)
break
}
// The required final proof offset if `flagsLength` is not zero, otherwise zero.
let proofEnd := add(proof.offset, shl(5, proof.length))
// We can use the free memory space for the queue.
// We don't need to allocate, since the queue is temporary.
let hashesFront := mload(0x40)
// Copy the leaves into the hashes.
// Sometimes, a little memory expansion costs less than branching.
// Should cost less, even with a high free memory offset of 0x7d00.
calldatacopy(hashesFront, leaves.offset, shl(5, leaves.length))
// Compute the back of the hashes.
let hashesBack := add(hashesFront, shl(5, leaves.length))
// This is the end of the memory for the queue.
// We recycle `flagsLength` to save on stack variables (sometimes save gas).
flags.length := add(hashesBack, shl(5, flags.length))
// We don't need to make a copy of `proof.offset` or `flags.offset`,
// as they are pass-by-value (this trick may not always save gas).
for {} 1 {} {
// Pop from `hashes`.
let a := mload(hashesFront)
// Pop from `hashes`.
let b := mload(add(hashesFront, 0x20))
hashesFront := add(hashesFront, 0x40)
// If the flag is false, load the next proof,
// else, pops from the queue.
if iszero(calldataload(flags.offset)) {
// Loads the next proof.
b := calldataload(proof.offset)
proof.offset := add(proof.offset, 0x20)
// Unpop from `hashes`.
hashesFront := sub(hashesFront, 0x20)
}
// Advance to the next flag offset.
flags.offset := add(flags.offset, 0x20)
// Slot of `a` in scratch space.
// If the condition is true: 0x20, otherwise: 0x00.
let scratch := shl(5, gt(a, b))
// Hash the scratch space and push the result onto the queue.
mstore(scratch, a)
mstore(xor(scratch, 0x20), b)
mstore(hashesBack, keccak256(0x00, 0x40))
hashesBack := add(hashesBack, 0x20)
if iszero(lt(hashesBack, flags.length)) { break }
}
isValid :=
and(
// Checks if the last value in the queue is same as the root.
eq(mload(sub(hashesBack, 0x20)), root),
// And whether all the proofs are used, if required.
eq(proofEnd, proof.offset)
)
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMPTY CALLDATA HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an empty calldata bytes32 array.
function emptyProof() internal pure returns (bytes32[] calldata proof) {
/// @solidity memory-safe-assembly
assembly {
proof.length := 0
}
}
/// @dev Returns an empty calldata bytes32 array.
function emptyLeaves() internal pure returns (bytes32[] calldata leaves) {
/// @solidity memory-safe-assembly
assembly {
leaves.length := 0
}
}
/// @dev Returns an empty calldata bool array.
function emptyFlags() internal pure returns (bool[] calldata flags) {
/// @solidity memory-safe-assembly
assembly {
flags.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;
/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SSTORE2.sol)
/// @author Saw-mon-and-Natalie (https://github.com/Saw-mon-and-Natalie)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
/// @author Modified from SSTORE3 (https://github.com/Philogy/sstore3)
library SSTORE2 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The proxy initialization code.
uint256 private constant _CREATE3_PROXY_INITCODE = 0x67363d3d37363d34f03d5260086018f3;
/// @dev Hash of the `_CREATE3_PROXY_INITCODE`.
/// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.
bytes32 internal constant CREATE3_PROXY_INITCODE_HASH =
0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Unable to deploy the storage contract.
error DeploymentFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* WRITE LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Writes `data` into the bytecode of a storage contract and returns its address.
function write(bytes memory data) internal returns (address pointer) {
/// @solidity memory-safe-assembly
assembly {
let n := mload(data) // Let `l` be `n + 1`. +1 as we prefix a STOP opcode.
/**
* ---------------------------------------------------+
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------|
* 61 l | PUSH2 l | l | |
* 80 | DUP1 | l l | |
* 60 0xa | PUSH1 0xa | 0xa l l | |
* 3D | RETURNDATASIZE | 0 0xa l l | |
* 39 | CODECOPY | l | [0..l): code |
* 3D | RETURNDATASIZE | 0 l | [0..l): code |
* F3 | RETURN | | [0..l): code |
* 00 | STOP | | |
* ---------------------------------------------------+
* @dev Prefix the bytecode with a STOP opcode to ensure it cannot be called.
* Also PUSH2 is used since max contract size cap is 24,576 bytes which is less than 2 ** 16.
*/
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
// Deploy a new contract with the generated creation code.
pointer := create(0, add(data, 0x15), add(n, 0xb))
if iszero(pointer) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Writes `data` into the bytecode of a storage contract with `salt`
/// and returns its normal CREATE2 deterministic address.
function writeCounterfactual(bytes memory data, bytes32 salt)
internal
returns (address pointer)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(data)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
// Deploy a new contract with the generated creation code.
pointer := create2(0, add(data, 0x15), add(n, 0xb), salt)
if iszero(pointer) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Writes `data` into the bytecode of a storage contract and returns its address.
/// This uses the so-called "CREATE3" workflow,
/// which means that `pointer` is agnostic to `data, and only depends on `salt`.
function writeDeterministic(bytes memory data, bytes32 salt)
internal
returns (address pointer)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(data)
mstore(0x00, _CREATE3_PROXY_INITCODE) // Store the `_PROXY_INITCODE`.
let proxy := create2(0, 0x10, 0x10, salt)
if iszero(proxy) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, proxy) // Store the proxy's address.
// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
mstore(0x00, 0xd694)
mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
pointer := keccak256(0x1e, 0x17)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
if iszero(
mul( // The arguments of `mul` are evaluated last to first.
extcodesize(pointer),
call(gas(), proxy, 0, add(data, 0x15), add(n, 0xb), codesize(), 0x00)
)
) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ADDRESS CALCULATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the initialization code hash of the storage contract for `data`.
/// Used for mining vanity addresses with create2crunch.
function initCodeHash(bytes memory data) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
let n := mload(data)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xfffe))
mstore(data, add(0x61000180600a3d393df300, shl(0x40, n)))
hash := keccak256(add(data, 0x15), add(n, 0xb))
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Equivalent to `predictCounterfactualAddress(data, salt, address(this))`
function predictCounterfactualAddress(bytes memory data, bytes32 salt)
internal
view
returns (address pointer)
{
pointer = predictCounterfactualAddress(data, salt, address(this));
}
/// @dev Returns the CREATE2 address of the storage contract for `data`
/// deployed with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictCounterfactualAddress(bytes memory data, bytes32 salt, address deployer)
internal
pure
returns (address predicted)
{
bytes32 hash = initCodeHash(data);
/// @solidity memory-safe-assembly
assembly {
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, hash)
mstore(0x01, shl(96, deployer))
mstore(0x15, salt)
predicted := keccak256(0x00, 0x55)
// Restore the part of the free memory pointer that has been overwritten.
mstore(0x35, 0)
}
}
/// @dev Equivalent to `predictDeterministicAddress(salt, address(this))`.
function predictDeterministicAddress(bytes32 salt) internal view returns (address pointer) {
pointer = predictDeterministicAddress(salt, address(this));
}
/// @dev Returns the "CREATE3" deterministic address for `salt` with `deployer`.
function predictDeterministicAddress(bytes32 salt, address deployer)
internal
pure
returns (address pointer)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x00, deployer) // Store `deployer`.
mstore8(0x0b, 0xff) // Store the prefix.
mstore(0x20, salt) // Store the salt.
mstore(0x40, CREATE3_PROXY_INITCODE_HASH) // Store the bytecode hash.
mstore(0x14, keccak256(0x0b, 0x55)) // Store the proxy's address.
mstore(0x40, m) // Restore the free memory pointer.
// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
mstore(0x00, 0xd694)
mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
pointer := keccak256(0x1e, 0x17)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* READ LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `read(pointer, 0, 2 ** 256 - 1)`.
function read(address pointer) internal view returns (bytes memory data) {
/// @solidity memory-safe-assembly
assembly {
data := mload(0x40)
let n := and(0xffffffffff, sub(extcodesize(pointer), 0x01))
extcodecopy(pointer, add(data, 0x1f), 0x00, add(n, 0x21))
mstore(data, n) // Store the length.
mstore(0x40, add(n, add(data, 0x40))) // Allocate memory.
}
}
/// @dev Equivalent to `read(pointer, start, 2 ** 256 - 1)`.
function read(address pointer, uint256 start) internal view returns (bytes memory data) {
/// @solidity memory-safe-assembly
assembly {
data := mload(0x40)
let n := and(0xffffffffff, sub(extcodesize(pointer), 0x01))
let l := sub(n, and(0xffffff, mul(lt(start, n), start)))
extcodecopy(pointer, add(data, 0x1f), start, add(l, 0x21))
mstore(data, mul(sub(n, start), lt(start, n))) // Store the length.
mstore(0x40, add(data, add(0x40, mload(data)))) // Allocate memory.
}
}
/// @dev Returns a slice of the data on `pointer` from `start` to `end`.
/// `start` and `end` will be clamped to the range `[0, args.length]`.
/// The `pointer` MUST be deployed via the SSTORE2 write functions.
/// Otherwise, the behavior is undefined.
/// Out-of-gas reverts if `pointer` does not have any code.
function read(address pointer, uint256 start, uint256 end)
internal
view
returns (bytes memory data)
{
/// @solidity memory-safe-assembly
assembly {
data := mload(0x40)
if iszero(lt(end, 0xffff)) { end := 0xffff }
let d := mul(sub(end, start), lt(start, end))
extcodecopy(pointer, add(data, 0x1f), start, add(d, 0x01))
if iszero(and(0xff, mload(add(data, d)))) {
let n := sub(extcodesize(pointer), 0x01)
returndatacopy(returndatasize(), returndatasize(), shr(40, n))
d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
}
mstore(data, d) // Store the length.
mstore(add(add(data, 0x20), d), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(data, 0x40), d)) // Allocate memory.
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
//////////////////////////////////////////////////////////////////
// ▒▒▒▒▒▒▒▒██ ░░░░░███████████ ▒▒▒▒█████████ ███▓▓▓▓ ▓▓▓▓▓▓▓░░░ //
// ▒▒▒▒▒▒▒▒██ ░███████████████ ▒▒▒▒█████████ ░░▓▓▓▓▓ ░░░░░▓▓▓▓▓ //
// ▒▒▒▒▒▒▒▒██ ░███████████████ ▒▒▒▒█████████ ░░▓▓▓▓▓ ░░░░░▓▓▓▓▓ //
// ▒▒▒▒▒▒▒▒██ ▓▓▓▓▓▓██████████ ▒▒▒▒█████████ ░░░░▓▓▓ ▓▓▓▓▓▓▓▓░░ //
// ██▒▒▒▒▒▒▒▒ ░███████████████ ▒▒▒▒▒▒▒▒█████ ░░▓▓▓▓▓ ░░░░░▓▓▓▓▓ //
// ██▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓██████████ ▒▒▒▒█████████ ▓▓▓▓▓░░ ▓▓▓▓▓▓▓▓░░ //
// █████████▒ █████░░░░░░░░░░░ ▒████████████ ████▓▓▓ ▓░░░░░░░░░ //
// █████████▒ █████░░░░░░░░░░░ ▒████████████ ░░░░▓▓▓ ▓▓▓▓▓▓▓▓░░ //
// ▒▒▒▒▒█████ ▓▓▓▓████████████ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓░░░░░ ░░░▓▓▓▓▓▓▓ //
// ▒▒▒▒▒▒▒▒██ ▓▓▓▓████████████ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ██████░ ░░░░░░▓▓▓▓ //
// ▒▒▒▒▒▒▒▒██ ▓███████████████ ▒▒▒▒█████████ ▓▓▓▓███ ░░░░░░░░░░ //
// ▒▒▒▒▒▒▒▒██ ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░ █▒▒▒▒▒▒▒▒▒▒▒▒ ███████ ░░░░░░▓▓▓▓ //
// ▒▒▒▒▒▒▒▒██ ▓▓▓▓████████████ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ██████░ ░░░░░░▓▓▓▓ //
// █████████▒ █████░░░░░░░░░░░ ▒████████████ ████▓▓▓ ▓░░░░░░░░░ //
// █████████▒ █████████░░░░░░░ ███▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓█ ░░░░░░▓▓▓▓ //
// ▒▒▒▒▒▒▒▒██ ░░░░░░░░░░░░████ ▒▒▒▒█████████ ██████▓ ▓░░░░░░░░░ //
//////////////////////////////////////////////////////////////////
// ABO, 2025 ███████████████████████████ Leander Herzog & 0xfff //
//////////////////////////////////////////////////////////////////
import "solady/auth/Ownable.sol";
import "solady/utils/MerkleProofLib.sol";
import "solady/utils/ReentrancyGuard.sol";
import "solady/tokens/ERC721.sol";
error IncorrectPrice();
error MaxSupply();
error MaxAP();
error MintingPaused();
error MaxClaimed();
error NoDiscountForAddress();
error DontBeGreedy();
error InvalidProof();
error MaxAllowlistClaimed();
error PublicMintingNotYetOpen();
error AllowlistMintingNotYetOpen();
error TokenDoesNotExist();
abstract contract AboAdmin is ERC721, Ownable, ReentrancyGuard {
uint256 public constant PRICE = 0.15 ether;
uint8 public constant MINT_SUPPLY = 140;
uint8 public constant ARTIST_ALLOTMENT = 10;
uint8 public constant MAX_PER_TRANSACTION = 1;
uint256 public editionCount;
uint256 public apCount;
bool public mintingPaused = true;
mapping(address => uint256) public allowlistMinted;
bytes32 public allowlistRoot;
uint256 public allowlistFrom = 1751299200; // Monday, June 30: 6pm CET
uint256 public publicFrom = 1751385600; // Tuesday, July 1: 6pm CET
address public render;
constructor(address _owner) ERC721() {
_initializeOwner(_owner);
}
function totalSupply() public view returns (uint256) {
return editionCount + apCount;
}
//////////////////////////////////////////////////////////
// Mint
//////////////////////////////////////////////////////////
function mintAllowlist(bytes32[] calldata proof, uint256 max, uint256 amount)
external
payable
allowlistMintChecks(proof, max, amount)
nonReentrant
{
_mintToken(msg.sender, editionCount + 1, amount);
editionCount += amount;
}
function mint(uint256 amount) external payable publicMintCheck(amount) nonReentrant {
_mintToken(msg.sender, editionCount + 1, amount);
editionCount += amount;
}
function mintAp(address to, uint256 amount) external apMintCheck(to, amount) onlyOwner nonReentrant {
_mintToken(to, MINT_SUPPLY + 1 + apCount, amount);
apCount += amount;
}
function mintOwner(address to, uint256 amount) external ownerMintCheck(to, amount) onlyOwner nonReentrant {
_mintToken(to, editionCount + 1, amount);
editionCount += amount;
}
function _mintToken(address to, uint256 startId, uint256 amount) internal {
for (uint256 id = startId; id < startId + amount;) {
_mint(to, id);
unchecked {
++id;
}
}
}
//////////////////////////////////////////////////////////
// Checks
//////////////////////////////////////////////////////////
modifier publicMintCheck(uint256 amount) {
if (editionCount + amount > MINT_SUPPLY) revert MaxSupply();
if (mintingPaused) revert MintingPaused();
if (block.timestamp < publicFrom) revert PublicMintingNotYetOpen();
if (msg.value != amount * PRICE) revert IncorrectPrice();
if (amount > MAX_PER_TRANSACTION) revert DontBeGreedy();
_;
}
modifier allowlistMintChecks(bytes32[] calldata proof, uint256 max, uint256 amount) {
if (editionCount + amount > MINT_SUPPLY) revert MaxSupply();
if (mintingPaused) revert MintingPaused();
if (msg.value != amount * PRICE) revert IncorrectPrice();
if (amount > MAX_PER_TRANSACTION) revert DontBeGreedy();
if (block.timestamp < publicFrom) {
if (block.timestamp < allowlistFrom) revert AllowlistMintingNotYetOpen();
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, max))));
bool validProof = MerkleProofLib.verifyCalldata(proof, allowlistRoot, leaf);
if (!validProof) revert InvalidProof();
if (allowlistMinted[msg.sender] + amount > max) revert MaxAllowlistClaimed();
allowlistMinted[msg.sender] += amount;
}
_;
}
modifier apMintCheck(address to, uint256 amount) {
if (apCount + amount > ARTIST_ALLOTMENT) revert MaxAP();
_;
}
modifier ownerMintCheck(address to, uint256 amount) {
if (editionCount + amount > MINT_SUPPLY) revert MaxSupply();
_;
}
//////////////////////////////////////////////////////////
// Admin
//////////////////////////////////////////////////////////
/// @notice Pause/Unpause minting
function setPause(bool value) public onlyOwner {
mintingPaused = value;
}
/// @notice Set allowlist mint timestamp
function setAllowlistFrom(uint256 from) public onlyOwner {
allowlistFrom = from;
}
/// @notice Set public mint timestamp
function setPublicFrom(uint256 from) public onlyOwner {
publicFrom = from;
}
/// @notice Set merkle root for reserve claims
function setAllowlistRoot(bytes32 newRoot) public onlyOwner {
allowlistRoot = newRoot;
}
/// @notice Withdraws balance to address
function withdraw(address payable _to) public onlyOwner {
require(_to != address(0));
(bool success,) = _to.call{value: address(this).balance}("");
require(success);
}
/// @notice Set render contract address
function setRender(address _render) public onlyOwner {
render = _render;
}
//////////////////////////////////////////////////////////
// Utility
//////////////////////////////////////////////////////////
/// @notice Returns the token IDs owned by the address
function tokensOf(address owner) external view returns (uint256[] memory) {
uint256[] memory tokens = new uint256[](balanceOf(owner));
uint256 counter;
for (uint256 i = 1; i <= editionCount; i++) {
if (_exists(i) && ownerOf(i) == owner) {
tokens[counter] = i;
counter++;
}
}
for (uint256 i = MINT_SUPPLY + 1; i <= MINT_SUPPLY + 1 + apCount; i++) {
if (_exists(i) && ownerOf(i) == owner) {
tokens[counter] = i;
counter++;
}
}
return tokens;
}
/// @notice Returns all token owners and ids
function tokenOwners() external view returns (address[] memory, uint256[] memory) {
address[] memory owners = new address[](totalSupply());
uint256[] memory ids = new uint256[](totalSupply());
uint256 counter;
for (uint256 i = 1; i <= editionCount; i++) {
if (_exists(i)) {
owners[counter] = ownerOf(i);
ids[counter] = i;
counter++;
}
}
for (uint256 i = MINT_SUPPLY + 1; i <= MINT_SUPPLY + 1 + apCount; i++) {
if (_exists(i)) {
owners[counter] = ownerOf(i);
ids[counter] = i;
counter++;
}
}
return (owners, ids);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
//////////////////////////////////////////////////////////////////
// ▓▓▓▓▓▓░░░░ ▒████████████ █████░░░░░░░░░░░ █████████▒ ████▓▓▓ //
// ▓░░░░░░░░░ ▒▒▒▒▒████████ ░░░░░░░░░░░░████ ██▒▒▒▒▒▒▒▒ ██████▓ //
// ▓▓▓▓▓▓▓░░░ ▒▒▒▒▒▒███████ ░░░░░███████████ ██▒▒▒▒▒▒▒▒ ███▓▓▓▓ //
// ░░░░░▓▓▓▓▓ ▒▒▒▒▒▒▒▒█████ ░███████████████ ██▒▒▒▒▒▒▒▒ ░░▓▓▓▓▓ //
// ▓▓▓▓▓▓▓▓░░ ▒▒▒▒█████████ ▓▓▓▓▓▓██████████ ██▒▒▒▒▒▒▒▒ ▓▓▓▓▓░░ //
// ▓▓▓▓▓▓▓▓░░ ▒▒▒▒▒▒▒▒▒▒███ ░░░░░░░░░░░░████ ▒▒▒▒▒▒▒▒██ ░░░░▓▓▓ //
// ░░░░░░▓▓▓▓ ███▒▒▒▒▒▒▒▒▒▒ █████████░░░░░░░ █████████▒ ▓▓▓▓▓▓█ //
// ░░░░▓▓▓▓▓▓ ███▒▒▒▒▒▒▒▒▒▒ █████░░░░░░░░░░░ █████████▒ ▓▓▓▓▓▓█ //
// ▓▓▓▓▓▓▓▓░░ ▒████████████ █████░░░░░░░░░░░ █████████▒ ░░░░▓▓▓ //
// ▓▓▓▓▓▓▓░░░ ▒▒▒▒█████████ ░░░░░███████████ ██▒▒▒▒▒▒▒▒ ███▓▓▓▓ //
// ░░░░░░▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓████████████ ▒▒▒▒▒▒▒▒██ ██████░ //
// ░░░░░░▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓████████████ ▒▒▒▒▒▒▒▒██ ██████░ //
// ▓░░░░░░░░░ ▒▒▒██████████ █████░░░░░░░░░░░ █████████▒ ████▓▓▓ //
// ░░░░░░▓▓▓▓ █▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░ ▒▒▒▒▒▒▒▒██ ███████ //
// ░░░░░░▓▓▓▓ █▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░ ▒▒▒▒▒▒▒▒██ ███████ //
// ░░░▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓████████████ ▒▒▒▒▒█████ ▓▓░░░░░ //
//////////////////////////////////////////////////////////////////
// ABO, 2025 ███████████████████████████ Leander Herzog & 0xfff //
//////////////////////////////////////////////////////////////////
struct AboState {
uint8[6] inbound;
uint8[6] outbound;
uint8[6] ignored;
bool isUnlocked;
}
library AboStateLib {
uint8 constant MAX_CONNECTIONS = 6;
//////////////////////////////////////////////////////////
// Count Functions (memory)
//////////////////////////////////////////////////////////
function inboundCount(AboState memory state) internal pure returns (uint8 count) {
for (uint8 i = 0; i < 6; i++) {
if (state.inbound[i] != 0) count++;
}
}
function outboundCount(AboState memory state) internal pure returns (uint8 count) {
for (uint8 i = 0; i < 6; i++) {
if (state.outbound[i] != 0) count++;
}
}
function totalCount(AboState memory state) internal pure returns (uint8 count) {
count = inboundCount(state) + outboundCount(state);
}
function ignoredCount(AboState memory state) internal pure returns (uint8 count) {
for (uint8 i = 0; i < 6; i++) {
if (state.ignored[i] != 0) count++;
}
}
//////////////////////////////////////////////////////////
// Check Functions (memory)
//////////////////////////////////////////////////////////
function hasInbound(AboState memory state, uint8 tokenId) internal pure returns (bool) {
for (uint8 i = 0; i < 6; i++) {
if (state.inbound[i] == tokenId) return true;
}
return false;
}
function hasOutbound(AboState memory state, uint8 tokenId) internal pure returns (bool) {
for (uint8 i = 0; i < 6; i++) {
if (state.outbound[i] == tokenId) return true;
}
return false;
}
function hasConnection(AboState memory state, uint8 tokenId) internal pure returns (bool) {
return hasInbound(state, tokenId) || hasOutbound(state, tokenId);
}
function isIgnoring(AboState memory state, uint8 tokenId) internal pure returns (bool) {
for (uint8 i = 0; i < 6; i++) {
if (state.ignored[i] == tokenId) return true;
}
return false;
}
//////////////////////////////////////////////////////////
// Add Functions (memory) - returns modified struct
//////////////////////////////////////////////////////////
function addInbound(AboState memory state, uint8 tokenId) internal pure returns (AboState memory) {
require(tokenId != 0, "Invalid token ID");
require(!hasInbound(state, tokenId), "Already has inbound connection");
require(totalCount(state) < MAX_CONNECTIONS, "Max connections reached");
for (uint8 i = 0; i < 6; i++) {
if (state.inbound[i] == 0) {
state.inbound[i] = tokenId;
break;
}
}
return state;
}
function addOutbound(AboState memory state, uint8 tokenId) internal pure returns (AboState memory) {
require(tokenId != 0, "Invalid token ID");
require(!hasOutbound(state, tokenId), "Already has outbound connection");
require(totalCount(state) < MAX_CONNECTIONS, "Max connections reached");
for (uint8 i = 0; i < 6; i++) {
if (state.outbound[i] == 0) {
state.outbound[i] = tokenId;
break;
}
}
return state;
}
function addIgnored(AboState memory state, uint8 tokenId) internal pure returns (AboState memory) {
require(tokenId != 0, "Invalid token ID");
require(!isIgnoring(state, tokenId), "Already ignoring");
for (uint8 i = 0; i < 6; i++) {
if (state.ignored[i] == 0) {
state.ignored[i] = tokenId;
return state;
}
}
revert("Max ignored limit reached");
}
//////////////////////////////////////////////////////////
// Remove Functions (memory) - returns modified struct
//////////////////////////////////////////////////////////
function removeInbound(AboState memory state, uint8 tokenId) internal pure returns (AboState memory) {
for (uint8 i = 0; i < 6; i++) {
if (state.inbound[i] == tokenId) {
// Shift remaining elements to avoid gaps
for (uint8 j = i; j < 5; j++) {
state.inbound[j] = state.inbound[j + 1];
}
state.inbound[5] = 0;
break;
}
}
return state;
}
function removeOutbound(AboState memory state, uint8 tokenId) internal pure returns (AboState memory) {
for (uint8 i = 0; i < 6; i++) {
if (state.outbound[i] == tokenId) {
// Shift remaining elements to avoid gaps
for (uint8 j = i; j < 5; j++) {
state.outbound[j] = state.outbound[j + 1];
}
state.outbound[5] = 0;
break;
}
}
return state;
}
function removeIgnored(AboState memory state, uint8 tokenId) internal pure returns (AboState memory) {
for (uint8 i = 0; i < 6; i++) {
if (state.ignored[i] == tokenId) {
// Shift remaining elements to avoid gaps
for (uint8 j = i; j < 5; j++) {
state.ignored[j] = state.ignored[j + 1];
}
state.ignored[5] = 0;
break;
}
}
return state;
}
//////////////////////////////////////////////////////////
// Clear Functions (memory) - returns modified struct
//////////////////////////////////////////////////////////
function clearInbound(AboState memory state) internal pure returns (AboState memory) {
for (uint8 i = 0; i < 6; i++) {
state.inbound[i] = 0;
}
return state;
}
function clearOutbound(AboState memory state) internal pure returns (AboState memory) {
for (uint8 i = 0; i < 6; i++) {
state.outbound[i] = 0;
}
return state;
}
function clearAll(AboState memory state) internal pure returns (AboState memory) {
state = clearInbound(state);
state = clearOutbound(state);
// Note: We don't clear ignored connections
return state;
}
//////////////////////////////////////////////////////////
// Get Functions (return arrays)
//////////////////////////////////////////////////////////
function getInboundUnsorted(AboState memory state) internal pure returns (uint8[] memory) {
uint8 count = inboundCount(state);
uint8[] memory result = new uint8[](count);
uint8 index = 0;
for (uint8 i = 0; i < 6; i++) {
if (state.inbound[i] != 0) {
result[index++] = state.inbound[i];
}
}
return result;
}
function getInbound(AboState memory state) internal pure returns (uint8[] memory) {
uint8[] memory result = getInboundUnsorted(state);
_insertionSort(result);
return result;
}
function getOutboundUnsorted(AboState memory state) internal pure returns (uint8[] memory) {
uint8 count = outboundCount(state);
uint8[] memory result = new uint8[](count);
uint8 index = 0;
for (uint8 i = 0; i < 6; i++) {
if (state.outbound[i] != 0) {
result[index++] = state.outbound[i];
}
}
return result;
}
function getOutbound(AboState memory state) internal pure returns (uint8[] memory) {
uint8[] memory result = getOutboundUnsorted(state);
_insertionSort(result);
return result;
}
function getConnections(AboState memory state) internal pure returns (uint8[] memory) {
// No duplicates since we prevent bidirectional connections
uint8 totalConnections = inboundCount(state) + outboundCount(state);
uint8[] memory result = new uint8[](totalConnections);
uint8 index = 0;
// Add all inbound
for (uint8 i = 0; i < 6; i++) {
if (state.inbound[i] != 0) {
result[index++] = state.inbound[i];
}
}
// Add all outbound
for (uint8 i = 0; i < 6; i++) {
if (state.outbound[i] != 0) {
result[index++] = state.outbound[i];
}
}
_insertionSort(result);
return result;
}
//////////////////////////////////////////////////////////
// Utility Functions for working with memory structs
//////////////////////////////////////////////////////////
function setUnlocked(AboState memory state, bool unlocked) internal pure returns (AboState memory) {
state.isUnlocked = unlocked;
return state;
}
function _insertionSort(uint8[] memory arr) internal pure {
for (uint8 i = 1; i < arr.length; i++) {
uint8 key = arr[i];
uint8 j = i;
while (j > 0 && arr[j - 1] > key) {
arr[j] = arr[j - 1];
j--;
}
arr[j] = key;
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import "solady/utils/LibString.sol";
import "solady/utils/Base64.sol";
import "solady/utils/SSTORE2.sol";
library AboHTML {
struct TokenInfo {
uint256 id;
uint8[] connections;
string owner;
bool isUnlocked;
}
struct TokenData {
TokenInfo[] tokens;
}
struct Bundles {
address scriptPointer;
address cssPointer;
}
//////////////////////////////////////////////////////////
// HTML Generation
//////////////////////////////////////////////////////////
function tokenHTML(TokenData memory data, Bundles memory bundles) internal view returns (string memory) {
return string.concat("data:text/html;base64,", Base64.encode(bytes(_assembleHTML(data, bundles))));
}
function _assembleHTML(TokenData memory data, Bundles memory bundles) internal view returns (string memory) {
return string.concat(_htmlHead(bundles), _htmlBody(data, bundles));
}
//////////////////////////////////////////////////////////
// HTML Structure
//////////////////////////////////////////////////////////
function _htmlHead(Bundles memory bundles) internal view returns (string memory) {
return string.concat(
"<!doctype html>",
"<html lang=\"en\">",
"<head>",
"<title>ABO, 2025 by Leander Herzog & 0xfff</title>",
"<meta charset=\"utf-8\">",
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
"<meta name=\"description\" content=\"ABO\">",
"<meta name=\"theme-color\" content=\"#000000\">",
"<meta name=\"mobile-web-app-capable\" content=\"yes\">",
"<style>body{margin:0;overscroll-behavior:none;}</style>",
_loadCSS(bundles.cssPointer),
"</head>"
);
}
function _htmlBody(TokenData memory data, Bundles memory bundles) internal view returns (string memory) {
return string.concat(
"<body>",
"<main class=\"abo\" tabindex=\"-1\"></main>",
_generateTokenDataScript(data),
_loadScript(bundles.scriptPointer),
"</body>",
"</html>"
);
}
//////////////////////////////////////////////////////////
// Token Data Injection
//////////////////////////////////////////////////////////
function _generateTokenDataScript(TokenData memory data) internal view returns (string memory) {
string memory tokensArray = "[";
for (uint256 i = 0; i < data.tokens.length; i++) {
if (i > 0) tokensArray = string.concat(tokensArray, ",");
// Generate connections array for this token
string memory connectionsStr = "[";
for (uint256 j = 0; j < data.tokens[i].connections.length; j++) {
if (j > 0) connectionsStr = string.concat(connectionsStr, ",");
connectionsStr = string.concat(connectionsStr, LibString.toString(data.tokens[i].connections[j]));
}
connectionsStr = string.concat(connectionsStr, "]");
// Generate token object
tokensArray = string.concat(
tokensArray,
"{id:",
LibString.toString(data.tokens[i].id),
",connections:",
connectionsStr,
",owner:\"",
data.tokens[i].owner,
"\",isUnlocked:",
data.tokens[i].isUnlocked ? "true" : "false",
"}"
);
}
tokensArray = string.concat(tokensArray, "]");
return string.concat("<script>window.tokens=", tokensArray, ";</script>");
}
//////////////////////////////////////////////////////////
// Bundle Loading
//////////////////////////////////////////////////////////
function _loadScript(address _scriptPointer) internal view returns (string memory) {
if (_scriptPointer == address(0)) {
return "";
}
string memory js = string(SSTORE2.read(_scriptPointer));
return string.concat("<script>", js, "</script>");
}
function _loadCSS(address _cssPointer) internal view returns (string memory) {
if (_cssPointer == address(0)) {
return "";
}
string memory css = string(SSTORE2.read(_cssPointer));
return string.concat("<style>", css, "</style>");
}
//////////////////////////////////////////////////////////
// Bundle Management Helpers
//////////////////////////////////////////////////////////
function setScript(Bundles storage bundles, string memory script) internal {
bundles.scriptPointer = SSTORE2.write(bytes(script));
}
function setCSS(Bundles storage bundles, string memory styles) internal {
bundles.cssPointer = SSTORE2.write(bytes(styles));
}
function getScript(Bundles memory bundles) internal view returns (string memory) {
return bundles.scriptPointer == address(0) ? "" : string(SSTORE2.read(bundles.scriptPointer));
}
function getCSS(Bundles memory bundles) internal view returns (string memory) {
return bundles.cssPointer == address(0) ? "" : string(SSTORE2.read(bundles.cssPointer));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import "./RNG.sol";
import "./Colors.sol";
import "solady/utils/LibString.sol";
library AboSVG {
using RNG for RNG.State;
using LibString for uint256;
using Colors for Colors.ColorManager;
// Fixed point arithmetic scale (4 decimal)
uint256 constant SCALE = 10000;
// Weighted probability helper function - p() equiv
function weightedChoice(RNG.State memory r, uint256[2][] memory weights) internal pure returns (uint256) {
uint256 piter = 0;
uint256 totalWeight = 0;
// Calculate total weight
for (uint256 i = 0; i < weights.length; i++) {
totalWeight += weights[i][0];
}
// Generate random value in range
uint256 randomValue = (r.nextScaled(uint32(SCALE)) * totalWeight) / SCALE;
// Find selected item
piter = 0;
for (uint256 i = 0; i < weights.length; i++) {
piter += weights[i][0];
if (randomValue < piter) {
return weights[i][1];
}
}
// Fallback to last item
return weights[weights.length - 1][1];
}
// Generate heights array
function generateHeights(RNG.State memory r) internal pure returns (uint256[] memory) {
uint256 arraySize = (r.nextScaled(uint32(SCALE)) * 15) / SCALE + 1;
uint256[] memory heights = new uint256[](arraySize);
uint256[2][] memory heightWeights = new uint256[2][](7);
heightWeights[0] = [uint256(1), uint256(2)];
heightWeights[1] = [uint256(3), uint256(7)];
heightWeights[2] = [uint256(9), uint256(20)];
heightWeights[3] = [uint256(10), uint256(100)];
heightWeights[4] = [uint256(5), uint256(200)];
heightWeights[5] = [uint256(2), uint256(700)];
heightWeights[6] = [uint256(1), uint256(1000)];
for (uint256 i = 0; i < arraySize; i++) {
uint256 baseHeight = weightedChoice(r, heightWeights);
heights[i] = (r.nextScaled(uint32(SCALE)) * baseHeight) / SCALE + 2;
}
// Sort the heights
for (uint256 i = 0; i < arraySize; i++) {
for (uint256 j = i + 1; j < arraySize; j++) {
if (heights[i] > heights[j]) {
uint256 temp = heights[i];
heights[i] = heights[j];
heights[j] = temp;
}
}
}
return heights;
}
function getFFColor(uint256 index) internal pure returns (uint256 packedColor) {
if (index == 0) return Colors.packColor(10000, 1700, 0);
if (index == 1) return Colors.packColor(10000, 10000, 6000);
if (index == 2) return Colors.packColor(1000, 0, 0);
if (index == 3) return Colors.packColor(10000, 8200, 0);
if (index == 4) return Colors.packColor(1100, 4700, 0);
if (index == 5) return Colors.packColor(2200, 5200, 10000);
if (index == 6) return Colors.packColor(0, 1200, 1200);
if (index == 7) return Colors.packColor(10000, 4800, 5700);
if (index == 8) return Colors.packColor(2900, 0, 5600);
if (index == 9) return Colors.packColor(10000, 10000, 0);
if (index == 10) return Colors.packColor(10000, 0, 4700);
if (index == 11) return Colors.packColor(1200, 0, 2000);
if (index == 12) return Colors.packColor(10000, 10000, 10000);
if (index == 13) return Colors.packColor(6700, 6700, 6700);
if (index == 14) return Colors.packColor(2500, 2500, 2500);
if (index == 15) return Colors.packColor(0, 0, 10000);
return 0; // Default
}
struct Stack {
uint256 id;
uint256 ccount;
uint256[10] colors; // Up to 10 packed colors (increased from 6)
uint256 numColors; // Actual number of colors (2-10)
uint256 max;
uint256 style;
uint256 special;
uint256[] heights; // Pre-generated height array
Item[] items;
bool direction;
}
struct Item {
bool visible;
uint256 currentHeight;
uint256[2] colors; // 2 packed colors (bg, fg)
uint256 animProgress; // 0 to SCALE
}
function generateSVG(
uint256 tokenId,
uint256[] memory connectedIds,
uint256[] memory connectionCounts,
uint256 timestamp,
uint256 size
) internal pure returns (string memory) {
// Sort connected IDs and their corresponding connection counts
// connectionCounts[0] = main token count, connectionCounts[1..n] = connected token counts
uint256[] memory sortedConnectedIds = new uint256[](connectedIds.length);
uint256[] memory sortedConnectedCounts = new uint256[](connectedIds.length);
// Copy arrays for sorting (skip first connectionCount as it belongs to main token)
for (uint256 i = 0; i < connectedIds.length; i++) {
sortedConnectedIds[i] = connectedIds[i];
sortedConnectedCounts[i] = connectionCounts[i + 1]; // Skip main token's count at index 0
}
// Bubble sort both arrays together, sorting by id
for (uint256 i = 0; i < sortedConnectedIds.length; i++) {
for (uint256 j = i + 1; j < sortedConnectedIds.length; j++) {
if (sortedConnectedIds[i] > sortedConnectedIds[j]) {
// Swap IDs
uint256 tempId = sortedConnectedIds[i];
sortedConnectedIds[i] = sortedConnectedIds[j];
sortedConnectedIds[j] = tempId;
// Swap corresponding connection counts
uint256 tempCount = sortedConnectedCounts[i];
sortedConnectedCounts[i] = sortedConnectedCounts[j];
sortedConnectedCounts[j] = tempCount;
}
}
}
// Create ids array with token first, then sorted connected ids
uint256[] memory ids = new uint256[](sortedConnectedIds.length + 1);
ids[0] = tokenId;
for (uint256 i = 0; i < sortedConnectedIds.length; i++) {
ids[i + 1] = sortedConnectedIds[i];
}
// Create connection counts array with token's count first, then sorted counts
uint256[] memory sortedCounts = new uint256[](connectionCounts.length);
sortedCounts[0] = connectionCounts[0]; // Token's own connection count (first element in original array)
for (uint256 i = 0; i < sortedConnectedCounts.length; i++) {
sortedCounts[i + 1] = sortedConnectedCounts[i];
}
// Generate stacks with packed colors
(Stack[] memory stacks, uint256 totalCcount) = generateStacks(ids, sortedCounts, timestamp);
return renderSVG(stacks, totalCcount, size);
}
function generateStacks(uint256[] memory ids, uint256[] memory connectionCounts, uint256 timestamp)
internal
pure
returns (Stack[] memory stacks, uint256 totalCcount)
{
stacks = new Stack[](ids.length);
// Current time in milliseconds
uint256 currentTime = timestamp * 1000;
uint256 minuteStart = (timestamp / 60) * 60; // Start of minute in seconds
for (uint256 i = 0; i < ids.length; i++) {
uint256 id = ids[i];
RNG.State memory r = RNG.init(id+3737);
// Use actual connection count, no RNG call
uint256 ccount = connectionCounts[i];
totalCcount += ccount;
// Generate colors using weighted probability like frontend
uint256[10] memory packedColors;
uint256 numColors;
uint256 style;
uint256 max;
uint256 special;
uint256[] memory heights;
{
// Create a shuffled copy of the color palette indices
uint8[] memory indices = new uint8[](16);
for (uint8 j = 0; j < 16; j++) {
indices[j] = j;
}
r.shuffle(indices); // First RNG usage
uint256[2][] memory colorWeights = new uint256[2][](6);
colorWeights[0] = [uint256(10), uint256(3)];
colorWeights[1] = [uint256(20), uint256(4)];
colorWeights[2] = [uint256(10), uint256(5)];
colorWeights[3] = [uint256(5), uint256(6)];
colorWeights[4] = [uint256(2), uint256(7)];
colorWeights[5] = [uint256(1), uint256(10)];
numColors = weightedChoice(r, colorWeights);
if (numColors < 2) numColors = 2; // Safety check
if (numColors > 10) numColors = 10; // Max limit
// Pick first numColors from shuffled palette
for (uint256 j = 0; j < numColors; j++) {
uint256 colorIndex = indices[j];
packedColors[j] = getFFColor(colorIndex);
}
style = r.nextScaled(uint32(SCALE)) * 2 / SCALE;
max = (r.nextScaled(uint32(SCALE)) * 30) / SCALE + 7; // 30 = 37-7
special = 0;
if (r.nextScaled(uint32(SCALE)) >= 5000) {
// >= 0.5
special = (r.nextScaled(uint32(SCALE)) * 500) / SCALE;
}
uint256 speed = ((r.nextScaled(uint32(SCALE)) * 80) / SCALE + 20) * 10; // ri(20,100) * 10
uint256[2][] memory stepWeights = new uint256[2][](6);
stepWeights[0] = [uint256(1), uint256(200)];
stepWeights[1] = [uint256(5), uint256(300)];
stepWeights[2] = [uint256(9), uint256(500)];
stepWeights[3] = [uint256(7), uint256(1_000)];
stepWeights[4] = [uint256(5), uint256(5_000)];
stepWeights[5] = [uint256(1), uint256(14_000)];
uint256 step = weightedChoice(r, stepWeights);
heights = generateHeights(r);
// Generate items
Item[] memory items = generateItems(
id, currentTime, minuteStart, r, step, speed, special, packedColors, numColors, heights, i > 0
);
stacks[i] = Stack({
id: id,
ccount: ccount,
colors: packedColors,
numColors: numColors,
max: max,
style: style,
special: special,
heights: heights,
items: items,
direction: i > 0
});
}
}
}
function generateItems(
uint256 id,
uint256 currentTime,
uint256 minuteStart,
RNG.State memory, /* parentR - unused */
uint256 step,
uint256 speed,
uint256 special,
uint256[10] memory packedColors,
uint256 numColors,
uint256[] memory heights,
bool direction
) internal pure returns (Item[] memory) {
// Store duration for each minute offset: [previous, current, next]
uint256[3] memory minuteDurations;
// Pre-generate durations for all minutes
for (int256 minuteOffset = -1; minuteOffset <= 1; minuteOffset++) {
uint256 targetMinute = uint256(int256(minuteStart) + minuteOffset * 60);
RNG.State memory rTimestamp = RNG.init(id * 1000000 + targetMinute);
// Generate duration for this minute - matching stack.js:121-127
uint256[2][] memory durationWeights = new uint256[2][](5);
durationWeights[0] = [uint256(3), uint256(1000)];
durationWeights[1] = [uint256(10), uint256(4000)];
durationWeights[2] = [uint256(10), uint256(10000)];
durationWeights[3] = [uint256(7), uint256(20000)];
durationWeights[4] = [uint256(1), uint256(30000)];
uint256 duration = weightedChoice(rTimestamp, durationWeights);
// Store duration for this minute offset (convert to array index)
minuteDurations[uint256(int256(minuteOffset) + 1)] = duration;
}
// Process timestamps on-the-fly without storing them
uint256 maxItems = 200;
Item[] memory items = new Item[](maxItems);
uint256 itemCount = 0;
// Generate and process timestamps for previous, current, and next minute
for (int256 minuteOffset = -1; minuteOffset <= 1; minuteOffset++) {
uint256 targetMinute = uint256(int256(minuteStart) + minuteOffset * 60);
RNG.State memory rTimestamp = RNG.init(id * 1000000 + targetMinute);
// Skip duration generation (already done above) but consume RNG call to maintain sequence
uint256[2][] memory durationWeights = new uint256[2][](5);
durationWeights[0] = [uint256(3), uint256(1000)];
durationWeights[1] = [uint256(10), uint256(4000)];
durationWeights[2] = [uint256(10), uint256(10000)];
durationWeights[3] = [uint256(7), uint256(20000)];
durationWeights[4] = [uint256(1), uint256(30000)];
weightedChoice(rTimestamp, durationWeights); // Consume RNG call
// Generate timestamps for this minute and process immediately
uint256 pointer = targetMinute * 1000; // Convert to milliseconds
uint256 limit = pointer + 60000; // 60 seconds in milliseconds
while (pointer < limit && itemCount < maxItems) {
// JS: pointer += step * r() - step is already in milliseconds
pointer += (step * rTimestamp.nextScaled(uint32(SCALE))) / SCALE;
if (pointer < limit) {
uint256 timestamp = pointer;
// Re-seed RNG for each item - matching Item constructor (frontend uses millisecond timestamp)
RNG.State memory rItem = RNG.init(timestamp);
// Item color selection - matching item.js:10-13
uint256 len = numColors;
if (len < 2) len = 2;
// JS: i1 = f(r() * c.length)
uint256 index1 = (rItem.nextScaled(uint32(SCALE)) * len) / SCALE;
if (index1 >= len) index1 = len - 1;
// JS: i2 = (i1 + f(r() * (c.length - 2) + 1)) % c.length
uint256 index2;
if (len <= 2) {
index2 = (index1 + 1) % len;
} else {
uint256 offset = (rItem.nextScaled(uint32(SCALE)) * (len - 2)) / SCALE + 1;
index2 = (index1 + offset) % len;
}
uint256[2] memory itemColors;
if (direction) {
itemColors[0] = packedColors[index2];
itemColors[1] = packedColors[index1];
} else {
itemColors[0] = packedColors[index1];
itemColors[1] = packedColors[index2];
}
// Height selection - matching item.js:14
uint256 height;
if (heights.length > 0) {
uint256 heightIndex = (rItem.nextScaled(uint32(SCALE)) * heights.length) / SCALE;
if (heightIndex >= heights.length) heightIndex = heights.length - 1;
height = heights[heightIndex];
} else {
height = 10; // Fallback
}
// Get the stored duration for this item's minute
uint256 itemMinuteStart = (timestamp / 1000) / 60 * 60; // Get minute start for this timestamp
uint256 minuteOffsetIndex =
itemMinuteStart > minuteStart ? 2 : (itemMinuteStart < minuteStart ? 0 : 1); // Map to array index
uint256 itemMinuteDuration = minuteDurations[minuteOffsetIndex];
// Item duration - matching item.js:15: f(this.root.duration * r() + 500)
// Add Math.floor equivalent by using integer division
uint256 itemDuration = (itemMinuteDuration * rItem.nextScaled(uint32(SCALE))) / SCALE + 500;
// Keyframe generation - consume RNG calls to match frontend
// JS: steps = p(r, [...])
uint256[2][] memory stepWeights = new uint256[2][](4);
stepWeights[0] = [uint256(7), uint256(2)];
stepWeights[1] = [uint256(20), uint256(3)];
stepWeights[2] = [uint256(10), uint256(7)];
stepWeights[3] = [uint256(2), uint256(15)];
uint256 steps = weightedChoice(rItem, stepWeights);
uint256 q = rItem.nextScaled(uint32(SCALE));
for (uint256 k = 0; k < steps; k++) {
if (rItem.nextScaled(uint32(SCALE)) > 3000) {
q = rItem.nextScaled(uint32(SCALE));
}
}
for (uint256 k = 0; k <= steps; k++) {
// + 1
if (rItem.nextScaled(uint32(SCALE)) > 9000) {
rItem.nextScaled(uint32(SCALE)); // Consume RNG call
}
}
// Calculate animation timing
uint256 startTime = timestamp;
uint256 entryDuration = speed;
uint256 colorDuration = itemDuration;
uint256 exitDuration = speed;
uint256 endTime = startTime + entryDuration + colorDuration + exitDuration;
// Check if item should be visible at current time
if (currentTime >= startTime && currentTime <= endTime) {
uint256 elapsed = currentTime - startTime;
uint256 animProgress = 0;
uint256 currentHeight;
if (elapsed < entryDuration) {
// Entry phase
currentHeight = (height * elapsed) / entryDuration;
animProgress = 0;
} else if (elapsed < entryDuration + colorDuration) {
// Color animation phase
currentHeight = height;
uint256 colorElapsed = elapsed - entryDuration;
animProgress = (colorElapsed * SCALE) / colorDuration;
} else {
// Exit phase
uint256 exitElapsed = elapsed - entryDuration - colorDuration;
currentHeight = (height * (exitDuration - exitElapsed)) / exitDuration;
animProgress = SCALE;
}
// Only mark as visible if currentHeight > 0 to avoid invisible/zero-height items
if (currentHeight > 0) {
items[itemCount] = Item({
visible: true,
currentHeight: currentHeight,
colors: itemColors,
animProgress: animProgress
});
itemCount++;
}
}
}
}
}
// Resize array to actual count
Item[] memory finalItems = new Item[](itemCount);
for (uint256 i = 0; i < itemCount; i++) {
finalItems[i] = items[i];
}
return finalItems;
}
function renderSVG(Stack[] memory stacks, uint256 totalCcount, uint256 size)
internal
pure
returns (string memory)
{
// Collect all unique colors - estimate max needed
// Max stacks * max colors per stack * max items per stack
// Realistic: ~10 stacks * 6 colors + items = ~100 unique colors max
uint256 maxColors = 100;
uint256[] memory uniqueColors = new uint256[](maxColors);
uint8[] memory colorIds = new uint8[](maxColors);
uint8 colorCount = 0;
// Single pass color collection with linear search
for (uint256 i = 0; i < stacks.length; i++) {
// Stack colors - only check up to numColors
for (uint256 j = 0; j < stacks[i].numColors; j++) {
uint256 color = stacks[i].colors[j];
bool found = false;
// Linear search
for (uint8 k = 0; k < colorCount; k++) {
if (uniqueColors[k] == color) {
found = true;
break;
}
}
if (!found && colorCount < maxColors) {
uniqueColors[colorCount] = color;
colorIds[colorCount] = colorCount;
colorCount++;
}
}
// Item colors
for (uint256 k = 0; k < stacks[i].items.length; k++) {
if (stacks[i].items[k].visible) {
for (uint256 m = 0; m < 2; m++) {
uint256 color = stacks[i].items[k].colors[m];
bool found = false;
for (uint8 n = 0; n < colorCount; n++) {
if (uniqueColors[n] == color) {
found = true;
break;
}
}
if (!found && colorCount < maxColors) {
uniqueColors[colorCount] = color;
colorIds[colorCount] = colorCount;
colorCount++;
}
}
}
}
}
// Build SVG
string memory svg = string.concat(
'<svg width="',
size.toString(),
'" height="',
size.toString(),
'" viewBox="0 0 ',
size.toString(),
" ",
size.toString(),
'" xmlns="http://www.w3.org/2000/svg"><style>'
);
// Generate CSS for all colors in one pass
for (uint8 i = 0; i < colorCount; i++) {
svg = string.concat(svg, ".c", uint256(i).toString(), "{fill:", Colors.colorToCSS(uniqueColors[i]), "}");
}
// Add background and start rendering
svg = string.concat(
svg, '</style><rect width="', size.toString(), '" height="', size.toString(), '" fill="#111"/>'
);
// Render stacks
uint256 xOffset = 0;
for (uint256 i = 0; i < stacks.length; i++) {
uint256 stackWidth;
if (i == stacks.length - 1) {
// Last stack gets remaining space to avoid rounding gaps
stackWidth = size - xOffset;
} else if (totalCcount == 0) {
// If no connections, divide space equally among stacks
stackWidth = size / stacks.length;
} else {
stackWidth = (stacks[i].ccount * size) / totalCcount;
}
// Find color ID for stack background
uint8 bgColorId = 0;
for (uint8 j = 0; j < colorCount; j++) {
if (uniqueColors[j] == stacks[i].colors[0]) {
bgColorId = j;
break;
}
}
// Stack group and background
svg = string.concat(
svg,
'<g transform="translate(',
xOffset.toString(),
',0)">',
'<rect width="',
stackWidth.toString(),
'" height="',
size.toString(),
'" class="c',
uint256(bgColorId).toString(),
'"/>'
);
// Render items
svg = string.concat(svg, renderItems(stacks[i], stackWidth, uniqueColors, colorCount));
svg = string.concat(svg, "</g>");
xOffset += stackWidth;
}
return string.concat(svg, "</svg>");
}
function renderItems(Stack memory stack, uint256 stackWidth, uint256[] memory uniqueColors, uint8 colorCount)
internal
pure
returns (string memory result)
{
uint256 currentY = 0;
for (uint256 j = 0; j < stack.items.length; j++) {
if (!stack.items[j].visible) continue;
uint256 barHeight = stack.items[j].currentHeight;
// Find color IDs
uint8 bgColorId = 0;
uint8 fgColorId = 0;
for (uint8 k = 0; k < colorCount; k++) {
if (uniqueColors[k] == stack.items[j].colors[0]) bgColorId = k;
if (uniqueColors[k] == stack.items[j].colors[1]) fgColorId = k;
}
// Background rect
result = string.concat(
result,
'<rect y="',
currentY.toString(),
'" width="',
stackWidth.toString(),
'" height="',
barHeight.toString(),
'" class="c',
uint256(bgColorId).toString(),
'"/>'
);
// Foreground rect with progress
uint256 fgWidth = (stackWidth * stack.items[j].animProgress) / SCALE;
if (fgWidth > 0) {
result = string.concat(
result,
'<rect y="',
currentY.toString(),
'" width="',
fgWidth.toString(),
'" height="',
barHeight.toString(),
'" class="c',
uint256(fgColorId).toString(),
'"/>'
);
}
currentY += barHeight;
if (currentY > 1000) break;
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import "solady/utils/LibString.sol";
library Colors {
using LibString for uint256;
// Pack RGB values into single uint256: (r << 32) | (g << 16) | b
// Each component is 0-10000 (4 decimal places, needs 14 bits, using 16-bit spacing)
function packColor(uint256 r, uint256 g, uint256 b) internal pure returns (uint256 packed) {
return (r << 32) | (g << 16) | b;
}
function unpackColor(uint256 packed) internal pure returns (uint256 r, uint256 g, uint256 b) {
r = (packed >> 32) & 0xFFFF;
g = (packed >> 16) & 0xFFFF;
b = packed & 0xFFFF;
}
// Pre-compute decimal strings for common values
function getDecimalString(uint256 value) internal pure returns (string memory) {
// Clamp to valid range
if (value > 10000) value = 10000;
// Use efficient lookup for common values
if (value == 0) return "0.0000";
if (value == 10000) return "1.0000";
if (value == 5000) return "0.5000";
if (value == 2500) return "0.2500";
if (value == 7500) return "0.7500";
// For other values, calculate efficiently
uint256 intPart = value / 10000;
uint256 fracPart = value % 10000;
string memory result = intPart.toString();
result = string.concat(result, ".");
// Add leading zeros and fractional part - always 4 digits to match JS toFixed(4)
if (fracPart < 1000) result = string.concat(result, "0");
if (fracPart < 100) result = string.concat(result, "0");
if (fracPart < 10) result = string.concat(result, "0");
result = string.concat(result, fracPart.toString());
return result;
}
// Convert packed color to CSS Display P3 string
function colorToCSS(uint256 packedColor) internal pure returns (string memory) {
(uint256 r, uint256 g, uint256 b) = unpackColor(packedColor);
return string.concat(
"color(display-p3 ", getDecimalString(r), " ", getDecimalString(g), " ", getDecimalString(b), ")"
);
}
struct ColorManager {
mapping(uint256 => uint8) colorToId; // packed color -> class ID
uint256[] idToColor; // class ID -> packed color
uint8 nextId; // next available ID
}
function getColorId(ColorManager storage manager, uint256 packedColor) internal returns (uint8 colorId) {
colorId = manager.colorToId[packedColor];
if (colorId == 0 && (manager.idToColor.length == 0 || manager.idToColor[0] != packedColor)) {
// New color, assign ID
colorId = manager.nextId++;
manager.colorToId[packedColor] = colorId;
// Expand array if needed
if (colorId >= manager.idToColor.length) {
manager.idToColor.push(packedColor);
} else {
manager.idToColor[colorId] = packedColor;
}
}
return colorId;
}
function generateCSS(ColorManager storage manager) internal view returns (string memory css) {
css = "<style>";
for (uint8 i = 0; i < manager.nextId; i++) {
css = string.concat(css, ".c", uint256(i).toString(), "{fill:", colorToCSS(manager.idToColor[i]), "}");
}
css = string.concat(css, "</style>");
}
function getClassName(uint8 colorId) internal pure returns (string memory) {
return string.concat("c", uint256(colorId).toString());
}
}// SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.0;
import "solady/utils/Base64.sol";
import "solady/utils/LibString.sol";
library Metadata {
string constant JSON_BASE64_HEADER = "data:application/json;base64,";
string constant SVG_XML_BASE64_HEADER = "data:image/svg+xml;base64,";
function encodeMetadata(
uint256 _tokenId,
string memory _name,
string memory _description,
string memory _attributes,
string memory _svg,
string memory _animationUrl
) internal pure returns (string memory) {
string memory metadata = string.concat(
"{",
keyValue("tokenId", LibString.toString(_tokenId)),
",",
keyValue("name", _name),
",",
keyValue("description", _description),
",",
keyValueNoQuotes("attributes", _attributes),
",",
keyValue("image", _encodeSVG(_svg)),
",",
keyValue("animation_url", _animationUrl),
"}"
);
return _encodeJSON(metadata);
}
/// @notice base64 encode json
/// @param _json, stringified json
/// @return string, bytes64 encoded json with prefix
function _encodeJSON(string memory _json) internal pure returns (string memory) {
return string.concat(JSON_BASE64_HEADER, Base64.encode(bytes(_json)));
}
/// @notice base64 encode svg
/// @param _svg, stringified json
/// @return string, bytes64 encoded svg with prefix
function _encodeSVG(string memory _svg) internal pure returns (string memory) {
return string.concat(SVG_XML_BASE64_HEADER, Base64.encode(bytes(_svg)));
}
function keyValue(string memory _key, string memory _value) internal pure returns (string memory) {
return string.concat('"', _key, '":"', _value, '"');
}
function keyValueNoQuotes(string memory _key, string memory _value) internal pure returns (string memory) {
return string.concat('"', _key, '":', _value);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
/**
* @title RNG
* @notice Simple deterministic RNG that matches JavaScript implementation exactly
* @dev Uses Linear Congruential Generator (LCG) with parameters from Numerical Recipes
*/
library RNG {
struct State {
uint32 seed;
}
/**
* @notice Hash function to spread any input across full 32-bit space
* @dev Uses bit-mixing without large multiplications to avoid overflow issues
* @param input The input value to hash
* @return hash The hashed 32-bit value
*/
function hashSeed(uint256 input) internal pure returns (uint32 hash) {
// Simple but effective hash - just multiply by a large prime and ensure it's not zero
hash = uint32(input);
unchecked {
hash = hash * 0x9E3779B9; // Golden ratio based multiplier
}
if (hash == 0) hash = 1;
return hash;
}
/**
* @notice Initialize RNG with seed
* @param seed Initial seed value
* @return state The initialized RNG state
*/
function init(uint256 seed) internal pure returns (State memory state) {
// Hash the seed to ensure good distribution even for consecutive inputs
state.seed = hashSeed(seed);
if (state.seed == 0) {
state.seed = 1;
}
}
/**
* @notice Generate next random number and update seed
* @param state The RNG state to update
* @return value The generated 32-bit unsigned integer
*/
function next(State memory state) internal pure returns (uint32 value) {
// LCG formula: (a * seed + c) mod 2^32
// Using parameters from Numerical Recipes
uint32 a = 1664525;
uint32 c = 1013904223;
unchecked {
state.seed = a * state.seed + c;
}
return state.seed;
}
/**
* @notice Generate a random number between 0 and max (exclusive)
* @param state The RNG state
* @param max The upper bound (exclusive)
* @return value Random number in range [0, max)
*/
function nextMax(State memory state, uint32 max) internal pure returns (uint32 value) {
return next(state) % max;
}
/**
* @notice Generate a random number between min and max (inclusive)
* @param state The RNG state
* @param min The lower bound (inclusive)
* @param max The upper bound (inclusive)
* @return value Random number in range [min, max]
*/
function nextInt(State memory state, uint32 min, uint32 max) internal pure returns (uint32 value) {
uint32 range = max - min + 1;
return min + (next(state) % range);
}
/**
* @notice Shuffle an array of uint8s in place
* @param state The RNG state
* @param array The array to shuffle
*/
function shuffle(State memory state, uint8[] memory array) internal pure {
for (uint256 i = array.length - 1; i > 0; i--) {
uint256 j = next(state) % (i + 1);
uint8 temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
/**
* @notice Generate a "float" between 0 and SCALE
* @param state The RNG state
* @param scale The scale factor (e.g., 10000 for 4 decimal places)
* @return value Scaled integer representing a decimal
*/
function nextScaled(State memory state, uint32 scale) internal pure returns (uint32 value) {
// Equivalent to nextFloat() * scale in JS
uint256 raw = next(state);
return uint32((raw * scale) / 0x100000000);
}
}// SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.0;
import "solady/utils/LibString.sol";
library SVG {
//////////////////////////////////////////////////////////
// Element
//////////////////////////////////////////////////////////
function element(string memory _type, string memory _attributes) internal pure returns (string memory) {
return string.concat("<", _type, " ", _attributes, "/>");
}
function element(string memory _type, string memory _attributes, string memory _children)
internal
pure
returns (string memory)
{
return string.concat("<", _type, " ", _attributes, ">", _children, "</", _type, ">");
}
function element(string memory _type, string memory _attributes, string memory _child1, string memory _child2)
internal
pure
returns (string memory)
{
return element(_type, _attributes, string.concat(_child1, _child2));
}
function element(
string memory _type,
string memory _attributes,
string memory _child1,
string memory _child2,
string memory _child3
) internal pure returns (string memory) {
return element(_type, _attributes, string.concat(_child1, _child2, _child3));
}
function element(
string memory _type,
string memory _attributes,
string memory _child1,
string memory _child2,
string memory _child3,
string memory _child4
) internal pure returns (string memory) {
return element(_type, _attributes, string.concat(_child1, _child2, _child3, _child4));
}
function element(
string memory _type,
string memory _attributes,
string memory _child1,
string memory _child2,
string memory _child3,
string memory _child4,
string memory _child5
) internal pure returns (string memory) {
return element(_type, _attributes, string.concat(_child1, _child2, _child3, _child4, _child5));
}
function element(
string memory _type,
string memory _attributes,
string memory _child1,
string memory _child2,
string memory _child3,
string memory _child4,
string memory _child5,
string memory _child6
) internal pure returns (string memory) {
return element(_type, _attributes, string.concat(_child1, _child2, _child3, _child4, _child5, _child6));
}
function element(
string memory _type,
string memory _attributes,
string memory _child1,
string memory _child2,
string memory _child3,
string memory _child4,
string memory _child5,
string memory _child6,
string memory _child7
) internal pure returns (string memory) {
return element(_type, _attributes, string.concat(_child1, _child2, _child3, _child4, _child5, _child6, _child7));
}
function element(
string memory _type,
string memory _attributes,
string memory _child1,
string memory _child2,
string memory _child3,
string memory _child4,
string memory _child5,
string memory _child6,
string memory _child7,
string memory _child8
) internal pure returns (string memory) {
return element(
_type, _attributes, string.concat(_child1, _child2, _child3, _child4, _child5, _child6, _child7, _child8)
);
}
//////////////////////////////////////////////////////////
// Attributes
//////////////////////////////////////////////////////////
function svgAttributes(uint256 height) internal pure returns (string memory) {
return string.concat(
'xmlns="http://www.w3.org/2000/svg" ' 'xmlns:xlink="http://www.w3.org/1999/xlink" ' 'width="100%" '
'height="100%" ' 'viewBox="0 0 1000 ',
LibString.toString(height),
'" ',
'preserveAspectRatio="xMidYMid meet" ',
'fill="none" '
);
}
function textAttributes(string[2] memory _coords, string memory _attributes)
internal
pure
returns (string memory)
{
return string.concat("x=", _quote(_coords[0]), "y=", _quote(_coords[1]), " ", _attributes, " ");
}
function rectAttributes(string memory _width, string memory _height, string memory _fill, string memory _attributes)
internal
pure
returns (string memory)
{
return string.concat(
"width=", _quote(_width), "height=", _quote(_height), "fill=", _quote(_fill), " ", _attributes, " "
);
}
function _quote(string memory value) internal pure returns (string memory) {
return string.concat('"', value, '" ');
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
//////////////////////////////////////////////////////////////////
// ▒▒▒▒█████████ ███▓▓▓▓ ░░░░░███████████ ▒▒▒▒▒▒▒▒██ ▓▓▓▓▓▓▓░░░ //
// ▒▒▒▒█████████ ░░▓▓▓▓▓ ░███████████████ ▒▒▒▒▒▒▒▒██ ░░░░░▓▓▓▓▓ //
// ▒▒▒▒█████████ ░░░░▓▓▓ ▓▓▓▓▓▓██████████ ▒▒▒▒▒▒▒▒██ ▓▓▓▓▓▓▓▓░░ //
// ▒▒▒▒█████████ ██████▓ ░░░░░░░░░░░░████ ▒▒▒▒▒▒▒▒██ ▓░░░░░░░░░ //
// ▒████████████ ████▓▓▓ █████░░░░░░░░░░░ █████████▒ ▓░░░░░░░░░ //
// █▒▒▒▒▒▒▒▒▒▒▒▒ ███████ ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░ ▒▒▒▒▒▒▒▒██ ░░░░░░▓▓▓▓ //
// ▒████████████ ░░░░▓▓▓ █████░░░░░░░░░░░ █████████▒ ▓▓▓▓▓▓▓▓░░ //
// █▒▒▒▒▒▒▒▒▒▒▒▒ ███████ ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░ ▒▒▒▒▒▒▒▒██ ░░░░░░▓▓▓▓ //
// ▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓░░░░░ ▓▓▓▓████████████ ▒▒▒▒▒█████ ░░░▓▓▓▓▓▓▓ //
// ▒▒▒▒▒▒▒▒▒▒▒▒▒ ██████░ ▓▓▓▓████████████ ▒▒▒▒▒▒▒▒██ ░░░░░░▓▓▓▓ //
// ▒▒▒▒█████████ ▓▓▓▓███ ▓███████████████ ▒▒▒▒▒▒▒▒██ ░░░░░░░░░░ //
// ▒▒▒▒█████████ ░░░░▓▓▓ ▓▓▓▓▓▓██████████ ▒▒▒▒▒▒▒▒██ ▓▓▓▓▓▓▓▓░░ //
// █▒▒▒▒▒▒▒▒▒▒▒▒ ███████ ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░ ▒▒▒▒▒▒▒▒██ ░░░░░░▓▓▓▓ //
// ▒████████████ ████▓▓▓ █████░░░░░░░░░░░ █████████▒ ▓░░░░░░░░░ //
// █▒▒▒▒▒▒▒▒▒▒▒▒ ███████ ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░ ▒▒▒▒▒▒▒▒██ ░░░░░░▓▓▓▓ //
// ███▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓█ █████████░░░░░░░ █████████▒ ░░░░░░▓▓▓▓ //
//////////////////////////////////////////////////////////////////
// ABO, 2025 ███████████████████████████ Leander Herzog & 0xfff //
//////////////////////////////////////////////////////////////////
import "solady/utils/LibString.sol";
import "solady/auth/Ownable.sol";
import "solady/utils/LibString.sol";
import {ENS} from "ens-contracts/registry/ENS.sol";
import {IReverseRegistrar} from "ens-contracts/reverseRegistrar/IReverseRegistrar.sol";
import {INameResolver} from "ens-contracts/resolvers/profiles/INameResolver.sol";
import "./libraries/SVG.sol";
import "./libraries/Metadata.sol";
import "./libraries/AboSVG.sol";
import "./libraries/AboHTML.sol";
import "./Abo.sol";
contract Render is Ownable {
address public immutable abo;
address public immutable ensRegistry;
address public immutable reverseRegistrar;
AboHTML.Bundles public bundles;
constructor(address _owner, address _abo, address _ensRegistry, address _reverseRegistrar) {
abo = _abo;
ensRegistry = _ensRegistry;
reverseRegistrar = _reverseRegistrar;
_initializeOwner(_owner);
}
//////////////////////////////////////////////////////////
// Token URI
//////////////////////////////////////////////////////////
function tokenURI(uint256 tokenId) external view returns (string memory) {
return Metadata.encodeMetadata({
_tokenId: tokenId,
_name: _name(tokenId),
_description: _description(tokenId),
_attributes: _attributes(tokenId),
_svg: _svg(tokenId),
_animationUrl: tokenHTML(tokenId)
});
}
function _name(uint256 _tokenId) internal view returns (string memory) {
uint8[] memory connected = Abo(abo).getConnections(uint8(_tokenId));
string memory tokenName = string.concat(unicode"ABO #", LibString.toString(_tokenId));
if (connected.length > 0) {
for (uint256 i = 0; i < connected.length; i++) {
tokenName = string.concat(tokenName, ", #", LibString.toString(uint256(connected[i])));
}
}
return tokenName;
}
function _description(uint256 tokenId) internal view returns (string memory) {
uint8[] memory connected = Abo(abo).getConnections(uint8(tokenId));
if (connected.length == 0) {
return string.concat("ABO #", LibString.toString(tokenId), " is not connected to any other ABOs.");
}
string memory description = string.concat("ABO #", LibString.toString(tokenId), " is connected to ");
for (uint256 i = 0; i < connected.length; i++) {
uint8 connectedId = connected[i];
(address connectedOwner,) = Abo(abo).tokenState(connectedId);
string memory ownerStr = resolveAddress(connectedOwner);
if (i > 0) {
if (i == connected.length - 1) {
description = string.concat(description, " and ");
} else {
description = string.concat(description, ", ");
}
}
description = string.concat(
description, "ABO #", LibString.toString(uint256(connectedId)), " (owned by ", ownerStr, ")"
);
}
description = string.concat(description, ".");
return description;
}
//////////////////////////////////////////////////////////
// Attributes
//////////////////////////////////////////////////////////
function attributes(uint256 tokenId) external view returns (string memory) {
return _attributes(tokenId);
}
function _attributes(uint256 tokenId) internal view returns (string memory) {
uint8[] memory connected = Abo(abo).getConnections(uint8(tokenId));
uint256 connectionCount = connected.length;
string memory result = "[";
result = string.concat(
result,
_attributeNoQuotes("Connections", LibString.toString(connectionCount)),
",",
_attribute("Status", Abo(abo).isUnlocked(tokenId) ? "Unlocked" : "Locked")
);
if (tokenId > 140) {
result = string.concat(result, ",", _attribute("Artist Proof"));
}
return string.concat(result, "]");
}
function _attribute(string memory value) internal pure returns (string memory) {
return string.concat("{", Metadata.keyValue("value", value), "}");
}
function _attribute(string memory traitType, string memory value) internal pure returns (string memory) {
return
string.concat("{", Metadata.keyValue("trait_type", traitType), ",", Metadata.keyValue("value", value), "}");
}
function _attributeNoQuotes(string memory value) internal pure returns (string memory) {
return string.concat("{", Metadata.keyValueNoQuotes("value", value), "}");
}
function _attributeNoQuotes(string memory traitType, string memory value) internal pure returns (string memory) {
return string.concat(
"{", Metadata.keyValue("trait_type", traitType), ",", Metadata.keyValueNoQuotes("value", value), "}"
);
}
//////////////////////////////////////////////////////////
// SVG
//////////////////////////////////////////////////////////
function renderSVG(uint256 tokenId) external view returns (string memory) {
return _svg(tokenId);
}
function renderSVGBase64(uint256 tokenId) external view returns (string memory) {
return Metadata._encodeSVG(_svg(tokenId));
}
function _svg(uint256 tokenId) internal view returns (string memory) {
// Get connected tokens from the Abo contract
uint8[] memory connected8 = Abo(abo).getConnections(uint8(tokenId));
// Convert uint8[] to uint256[] and get connection counts
uint256[] memory connectedIds = new uint256[](connected8.length);
uint256[] memory connectionCounts = new uint256[](connected8.length + 1);
// First element is the main token's connection count
connectionCounts[0] = connected8.length;
for (uint256 i = 0; i < connected8.length; i++) {
connectedIds[i] = uint256(connected8[i]);
// Get connection count for each connected token
connectionCounts[i + 1] = Abo(abo).getConnections(connected8[i]).length;
}
// Generate SVG using current block timestamp for animation
return AboSVG.generateSVG(
tokenId,
connectedIds,
connectionCounts,
block.timestamp,
500 // 500x500
);
}
//////////////////////////////////////////////////////////
// HTML Rendering
//////////////////////////////////////////////////////////
function tokenHTML(uint256 tokenId) public view returns (string memory) {
uint8[] memory connectedIds = Abo(abo).getConnections(uint8(tokenId));
// Create array with main token + connected tokens
AboHTML.TokenInfo[] memory tokens = new AboHTML.TokenInfo[](connectedIds.length + 1);
// Main token
(address mainOwner,) = Abo(abo).tokenState(uint8(tokenId));
string memory mainOwnerStr = resolveAddress(mainOwner);
tokens[0] = AboHTML.TokenInfo({
id: tokenId,
connections: connectedIds,
owner: mainOwnerStr,
isUnlocked: Abo(abo).isUnlocked(tokenId)
});
// Connected tokens
for (uint256 i = 0; i < connectedIds.length; i++) {
uint8 connectedId = connectedIds[i];
(address connectedOwner,) = Abo(abo).tokenState(connectedId);
string memory connectedOwnerStr = resolveAddress(connectedOwner);
uint8[] memory connectedConnections = Abo(abo).getConnections(connectedId);
tokens[i + 1] = AboHTML.TokenInfo({
id: uint256(connectedId),
connections: connectedConnections,
owner: connectedOwnerStr,
isUnlocked: Abo(abo).isUnlocked(connectedId)
});
}
AboHTML.TokenData memory data = AboHTML.TokenData({tokens: tokens});
return AboHTML.tokenHTML(data, bundles);
}
//////////////////////////////////////////////////////////
// Bundle Management
//////////////////////////////////////////////////////////
function setScript(string memory script) external onlyOwner {
AboHTML.setScript(bundles, script);
}
function setCSS(string memory styles) external onlyOwner {
AboHTML.setCSS(bundles, styles);
}
function getScript() external view returns (string memory) {
return AboHTML.getScript(bundles);
}
function getCSS() external view returns (string memory) {
return AboHTML.getCSS(bundles);
}
//////////////////////////////////////////////////////////
// ENS Resolver
// Author: @yigitduman
// Source: https://github.com/ygtdmn/drakeflipping/blob/main/src/DrakeflippingRenderer.sol
//////////////////////////////////////////////////////////
/**
* @notice Retrieves the ENS name or checksummed address for a given address.
* @param addr The address to retrieve the ENS name or checksummed address for.
* @return The ENS name if available, otherwise the checksummed address.
*/
function resolveAddress(address addr) internal view returns (string memory) {
if (ensRegistry != address(0) && reverseRegistrar != address(0)) {
ENS ens = ENS(ensRegistry);
IReverseRegistrar reverseRegistrarInstance = IReverseRegistrar(reverseRegistrar);
bytes32 node = reverseRegistrarInstance.node(addr);
address resolverAddress = ens.resolver(node);
if (resolverAddress != address(0)) {
// If the resolver is not the zero address, try to get the name from the resolver
try INameResolver(resolverAddress).name(node) returns (string memory name) {
// If a name is found and it's not empty, return it
if (bytes(name).length > 0) {
return name;
}
} catch {}
}
}
// If no valid name is found or registry is not set, return the address as a string
return LibString.toHexStringChecksummed(addr);
}
function _isContract(address _addr) internal view returns (bool) {
uint32 size;
assembly {
size := extcodesize(_addr)
}
return size > 0;
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
////////////////////////////////////////////////////////////////////////
// //
// //
// //
// //
// //
// //
// //
// //
// ⚘ //
// sculpture //
// //
// //
// //
// //
// //
// //
// //
// //
////////////////////////////////////////////////////////////////////////
interface Sculpture {
function title() external view returns (string memory);
function authors() external view returns (string[] memory);
function addresses() external view returns (address[] memory);
function urls() external view returns (string[] memory);
function text() external view returns (string memory);
}{
"optimizer": {
"enabled": true,
"runs": 200
},
"viaIR": true,
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
},
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccountBalanceOverflow","type":"error"},{"inputs":[],"name":"AllowlistMintingNotYetOpen","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"BalanceQueryForZeroAddress","type":"error"},{"inputs":[],"name":"DontBeGreedy","type":"error"},{"inputs":[],"name":"IncorrectPrice","type":"error"},{"inputs":[],"name":"InvalidProof","type":"error"},{"inputs":[],"name":"MaxAP","type":"error"},{"inputs":[],"name":"MaxAllowlistClaimed","type":"error"},{"inputs":[],"name":"MaxSupply","type":"error"},{"inputs":[],"name":"MintingPaused","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"NotOwnerNorApproved","type":"error"},{"inputs":[],"name":"PublicMintingNotYetOpen","type":"error"},{"inputs":[],"name":"Reentrancy","type":"error"},{"inputs":[],"name":"TokenAlreadyExists","type":"error"},{"inputs":[],"name":"TokenDoesNotExist","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToNonERC721ReceiverImplementer","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"isApproved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint8","name":"from","type":"uint8"},{"indexed":true,"internalType":"uint8","name":"to","type":"uint8"}],"name":"Connect","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint8","name":"from","type":"uint8"},{"indexed":true,"internalType":"uint8","name":"to","type":"uint8"}],"name":"Disconnect","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint8","name":"from","type":"uint8"},{"indexed":true,"internalType":"uint8","name":"blocked","type":"uint8"}],"name":"Ignore","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"MetadataUpdate","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":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint8","name":"from","type":"uint8"},{"indexed":true,"internalType":"uint8","name":"unblocked","type":"uint8"}],"name":"Unignore","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"Unlock","type":"event"},{"inputs":[],"name":"ARTIST_ALLOTMENT","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_CONNECTIONS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PER_TRANSACTION","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINT_SUPPLY","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"","type":"uint8"}],"name":"abos","outputs":[{"internalType":"bool","name":"isUnlocked","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"addresses","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allowlistFrom","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"allowlistMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allowlistRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"apCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"authors","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","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":[{"internalType":"uint8","name":"fromId","type":"uint8"},{"internalType":"uint8","name":"toId","type":"uint8"}],"name":"connect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"fromId","type":"uint8"},{"internalType":"uint8[]","name":"ids","type":"uint8[]"}],"name":"connect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"fromId","type":"uint8"},{"internalType":"uint8[]","name":"ids","type":"uint8[]"}],"name":"disconnect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"toId","type":"uint8"}],"name":"disconnectAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"editionCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"from","type":"uint8"}],"name":"getConnectionCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"from","type":"uint8"}],"name":"getConnections","outputs":[{"internalType":"uint8[]","name":"","type":"uint8[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentConnectionCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"from","type":"uint8"}],"name":"getInboundConnections","outputs":[{"internalType":"uint8[]","name":"","type":"uint8[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"from","type":"uint8"}],"name":"getOutboundConnections","outputs":[{"internalType":"uint8[]","name":"","type":"uint8[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"from","type":"uint8"},{"internalType":"uint8","name":"to","type":"uint8"}],"name":"hasInbound","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"from","type":"uint8"},{"internalType":"uint8","name":"to","type":"uint8"}],"name":"hasOutbound","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"fromId","type":"uint8"},{"internalType":"uint8","name":"blockId","type":"uint8"}],"name":"ignore","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"result","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"from","type":"uint8"},{"internalType":"uint8","name":"to","type":"uint8"}],"name":"isConnected","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"from","type":"uint8"},{"internalType":"uint8","name":"to","type":"uint8"}],"name":"isIgnoring","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"isUnlocked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"uint256","name":"max","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mintAllowlist","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mintAp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mintOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"mintingPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"publicFrom","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"render","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"from","type":"uint256"}],"name":"setAllowlistFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"newRoot","type":"bytes32"}],"name":"setAllowlistRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"isApproved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"value","type":"bool"}],"name":"setPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"from","type":"uint256"}],"name":"setPublicFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_render","type":"address"}],"name":"setRender","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string[]","name":"_urls","type":"string[]"}],"name":"setUrls","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"result","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"text","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"title","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"tokenOwners","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"id","type":"uint8"}],"name":"tokenState","outputs":[{"internalType":"address","name":"","type":"address"},{"components":[{"internalType":"uint8[6]","name":"inbound","type":"uint8[6]"},{"internalType":"uint8[6]","name":"outbound","type":"uint8[6]"},{"internalType":"uint8[6]","name":"ignored","type":"uint8[6]"},{"internalType":"bool","name":"isUnlocked","type":"bool"}],"internalType":"struct AboState","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"components":[{"internalType":"uint8[6]","name":"inbound","type":"uint8[6]"},{"internalType":"uint8[6]","name":"outbound","type":"uint8[6]"},{"internalType":"uint8[6]","name":"ignored","type":"uint8[6]"},{"internalType":"bool","name":"isUnlocked","type":"bool"}],"internalType":"struct AboState[]","name":"","type":"tuple[]"},{"internalType":"bool[]","name":"","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"tokensOf","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalConnectionCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint8","name":"fromId","type":"uint8"},{"internalType":"uint8","name":"blockId","type":"uint8"}],"name":"unignore","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"fromId","type":"uint8"},{"internalType":"uint8[]","name":"ids","type":"uint8[]"}],"name":"updateConnections","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"urls","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"_to","type":"address"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
60803460a757601f61503138819003918201601f19168301916001600160401b0383118484101760ac5780849260209460405283398101031260a757516001600160a01b0381169081900360a757600160ff196002541617600255636862b480600555636864060060065580638b78c6d8195560007f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a3604051614f6e90816100c38239f35b600080fd5b634e487b7160e01b600052604160045260246000fdfe608080604052600436101561001357600080fd5b60003560e01c90816301ffc9a714613c825750806306fdde0314613c23578063081812fc14613bd1578063095ea7b314613b1757806311e6c0401461375b57806313e4f9511461362157806318160ddd146135ff5780631a041204146135995780631f1bd6921461348c57806322d9a7b614612d2557806323b872dd14612d1357806324899b7614612cd75780632569296214612c8b57806328411ae114612bca5780632c73dc4a14612ac55780632e69100d146129b95780633defb8191461299b578063408cbf941461291457806342842e0e146128da578063490b196b146128bc5780634a79d50c14611b6a5780634bf440261461289e57806351cff8d91461282b5780635317af21146125cc57806354d1f13d1461258457806358b1fad4146121d85780635a3f2672146120ad5780636352211e1461207d5780636589a7af1461203a5780636cc895a914611d725780636faa349714611d2f57806370a0823114611d04578063715018a614611cb857806372abc8b714611c855780637becf1ea14611c67578063825769fb14611bfc5780638d859f3e14611bd95780638da5cb5b14611bac5780638e0acd1214611b8b5780639524bb4414611b6f57806395d89b4114611b6a578063976da93814611b175780639c9c666914611af95780639caa07c0146119465780639d63848a146116265780639dfbcde81461160a578063a01cc77114611589578063a0712d6814611487578063a22cb46514611412578063a26e2a5b146113f6578063a5038c74146113d5578063b88d4fde1461134e578063bcad51e214610e23578063bd1da6d414610de7578063be3723dd14610d3d578063bedb86fb14610d04578063c87b56dd14610c0d578063ca3152e414610a3b578063cb14eb8714610a1f578063d607497a146109f6578063da0321cd146109a7578063e1a283d614610984578063e244fff014610966578063e55fc93b14610584578063e6b0561314610563578063e6f4f3c614610516578063e81ed044146104dc578063e985e9c514610497578063f04e283e14610447578063f0c136cb14610404578063f2fde38b146103c6578063f530e68b1461037a5763fee81cf41461034257600080fd5b346103755760203660031901126103755761035b613d14565b63389a75e1600c52600052602080600c2054604051908152f35b600080fd5b346103755760203660031901126103755760ff610395613d40565b1660005260096020526103c26103b66103b160406000206140f4565b614745565b60405191829182613ee2565b0390f35b6020366003190112610375576103da613d14565b6103e2614a8c565b8060601b156103f6576103f490614c42565b005b637448fbae6000526004601cfd5b346103755760203660031901126103755761041d613d14565b610425614a8c565b600780546001600160a01b0319166001600160a01b0392909216919091179055005b60203660031901126103755761045b613d14565b610463614a8c565b63389a75e1600c52806000526020600c2090815442116104895760006103f49255614c42565b636f5e88186000526004601cfd5b34610375576040366003190112610375576104b0613d14565b6104b8613d2a565b601c52670a5a2e7a0000000060085260005260206030600c20546040519015158152f35b34610375576020366003190112610375576001600160a01b036104fd613d14565b1660005260036020526020604060002054604051908152f35b34610375576040366003190112610375576020610559610534613d40565b60ff61053e613d50565b91166000526009835261055460406000206140f4565b6145e2565b6040519015158152f35b346103755760203660031901126103755761057c614a8c565b600435600655005b346103755761059236613d90565b60ff8316929091906105b66001600160a01b036105ae86614517565b163314614069565b8360005260096020526105cc60406000206140f4565b9260005b81811061072f57858560608101805115610702575b50908060005260096020526040600020825160009060005b600681106106d45750508155602083015160009060005b600681106106a6575050600182015560408301519260009360005b6006811061067857600080516020614ef983398151915260208661066f8760036060898d60028501550151151591019060ff801983541691151516179055565b604051908152a1005b9094602061069d6001928460ff8a5116919060ff809160031b9316831b921b19161790565b9601910161062f565b909160206106cb6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610614565b909160206106f96001928460ff875116919060ff809160031b9316831b921b19161790565b930191016105fd565b60019061070d614273565b505290600080516020614ed98339815191526020604051838152a190826105e5565b61074261073d828487614140565b614166565b60ff811690610758610753836145ad565b614174565b610764888314156142ca565b81600052600960205261077a60406000206140f4565b9661078e610788878a614830565b1561430f565b61079886896145e2565b61093f575b6107ab6107b19287926148f3565b976149cc565b6107bc600854614200565b60085560608101805115610914575b508160005260096020526040600020908051600090815b600681106108e657505082556020810151600090815b600681106108b857505060018301556040810151600090815b6006811061088a575050926108568360036060602095600199986002600080516020614ef98339815191529901550151151591019060ff801983541691151516179055565b60405190808b7f67a48d6d1051d802ffaf80bddeb34dc4d41cedaa1e6dac418e7390cc2b68eefd600080a38152a1016105d0565b909160206108af6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610811565b909160206108dd6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016107f8565b9091602061090b6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016107e2565b60019061091f614273565b5052600080516020614ed98339815191526020604051848152a1886107cb565b966107ab8261095c6107b1946109568a809661462b565b9b6146bf565b925092505061079d565b34610375576000366003190112610375576020600154604051908152f35b3461037557600036600319011261037557602060ff600254166040519015158152f35b34610375576000366003190112610375576103c260408051906109ca8183613fdf565b60018252601f198101366020840137306109e38361420f565b5251918291602083526020830190613dcd565b34610375576000366003190112610375576007546040516001600160a01b039091168152602090f35b3461037557600036600319011261037557602060405160018152f35b3461037557604036600319011261037557610a54613d40565b60ff610a5e613d50565b9116610a746001600160a01b036105ae83614517565b60ff821691610a85610753846145ad565b818314610bd457610aab90826000526009602052610aa660406000206140f4565b614cd2565b816000526009602052604060002090805160009060005b60068110610ba65750508255602081015160009060005b60068110610b785750506001830155604081015160009060005b60068110610b4a575050606083926003926002610b239601550151151591019060ff801983541691151516179055565b7f74c7a4a7b27d4f0db8545d9c8c688b37ee8059db8b47251f05f2c0ad6f2f0830600080a3005b90916020610b6f6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610af3565b90916020610b9d6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610ad9565b90916020610bcb6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610ac2565b60405162461bcd60e51b815260206004820152601160248201527021b0b73737ba10313637b1b59039b2b63360791b6044820152606490fd5b346103755760203660031901126103755760075460405163c87b56dd60e01b8152600480359082015290600090829060249082906001600160a01b03165afa908115610cf857600091610c71575b604051602080825281906103c290820185613cef565b3d8083833e610c808183613fdf565b810190602081830312610cf0578051906001600160401b038211610cf4570181601f82011215610cf057805192610cb684614017565b92610cc46040519485613fdf565b84845260208584010111610ced57506103c292610ce79160208085019101613ccc565b90610c5b565b80fd5b8280fd5b8380fd5b6040513d6000823e3d90fd5b346103755760203660031901126103755760043580151580910361037557610d2a614a8c565b60ff801960025416911617600255600080f35b3461037557604036600319011261037557610d56613d14565b60243560015491600a610d698385614266565b11610dd657610d76614a8c565b3068929eee149b4bd212685414610dc85781610daf91610da9610db7953068929eee149b4bd212685560ff608d16614266565b90614aa9565b600154614266565b6001553868929eee149b4bd2126855005b63ab143c066000526004601cfd5b63c98cb7ab60e01b60005260046000fd5b346103755760203660031901126103755760ff610e02613d40565b166000526009602052602060ff600360406000200154166040519015158152f35b3461037557602036600319011261037557610e3c613d40565b60ff8116610e546001600160a01b036105ae83614517565b806000526009602052610e6a60406000206140f4565b9060ff610e88610e7984614e6f565b610e8285614c80565b90614252565b161561130957610e9782614745565b60005b81518110156110065760ff610eaf828461422c565b5116806000526009602052610ed086610ecb60406000206140f4565b6146bf565b8160005260096020526040600020908051600090815b60068110610fd857505082556020810151600090815b60068110610faa57505060018301556040810151600090815b60068110610f7c57505092610f5a8360036060602095600199986002600080516020614ef98339815191529901550151151591019060ff801983541691151516179055565b604051908088600080516020614f19833981519152600080a38152a101610e9a565b90916020610fa16001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610f15565b90916020610fcf6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610efc565b90916020610ffd6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610ee6565b5050611011826147ca565b9260005b84518110156111815760ff61102a828761422c565b511680600052600960205261104b8361104660406000206140f4565b61462b565b8160005260096020526040600020908051600090815b6006811061115357505082556020810151600090815b6006811061112557505060018301556040810151600090815b600681106110f7575050926110d58360036060602095600199986002600080516020614ef98339815191529901550151151591019060ff801983541691151516179055565b604051908781600080516020614f19833981519152600080a38152a101611015565b9091602061111c6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101611090565b9091602061114a6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101611077565b909160206111786001928460ff875116919060ff809160031b9316831b921b19161790565b93019101611061565b505061118b614273565b50611194614273565b5060005b60ff811660068110156111bf5760ff9160006111b760019387516145d1565b520116611198565b50506111c9614273565b506020820160005b60ff811660068110156111f85760ff9160006111f060019386516145d1565b5201166111d1565b5050816000526009602052604060002090835160009060005b600681106112db57505082555160009060005b600681106112ad575050600182015560408301519260009360005b6006811061127f57600080516020614ef983398151915260208661066f8760036060898d60028501550151151591019060ff801983541691151516179055565b909460206112a46001928460ff8a5116919060ff809160031b9316831b921b19161790565b9601910161123f565b909160206112d26001928460ff875116919060ff809160031b9316831b921b19161790565b93019101611224565b909160206113006001928460ff875116919060ff809160031b9316831b921b19161790565b93019101611211565b60405162461bcd60e51b815260206004820152601860248201527f546f6b656e20686173206e6f20636f6e6e656374696f6e7300000000000000006044820152606490fd5b608036600319011261037557611362613d14565b61136a613d2a565b90604435606435926001600160401b03841161037557366023850112156103755783600401356001600160401b038111610375573660248287010111610375576113b5838386614353565b813b6113bd57005b6103f4946113cf916024369201614032565b92614b78565b34610375576020366003190112610375576113ee614a8c565b600435600555005b3461037557600036600319011261037557602060405160068152f35b346103755760403660031901126103755761142b613d14565b6024358015158091036103755781601c52670a5a2e7a0000000060085233600052806030600c205560005260018060a01b0316337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160206000a3005b602036600319011261037557600435608c6114a482600054614266565b116115785760ff6002541661156757600654421061155657670214e8348c4f00008102818104670214e8348c4f0000148215171561154057340361152f576001811161151e573068929eee149b4bd212685414610dc857611510903068929eee149b4bd212685561457a565b3868929eee149b4bd2126855005b6306b6632960e41b60005260046000fd5b6399b5cb1d60e01b60005260046000fd5b634e487b7160e01b600052601160045260246000fd5b6358755ad360e11b60005260046000fd5b6375ab03ab60e11b60005260046000fd5b632cdb04a160e21b60005260046000fd5b34610375576000366003190112610375576000609660015b60ff81168281116115ff576115b5816145ad565b6115c9575b506115c49061459c565b6115a1565b6115c491936115f891600052600960205260ff6115f16115ec60406000206140f4565b614c80565b1690614266565b92906115ba565b602084604051908152f35b34610375576000366003190112610375576020604051608c8152f35b346103755760003660031901126103755760005460015461164f61164a8284614266565b6141ce565b61165c61164a8385614266565b926116678382614266565b9161167183614000565b9261167f6040519485613fdf565b80845261168e601f1991614000565b0160005b81811061192f5750506116a58483614266565b906116c86116b283614000565b926116c06040519485613fdf565b808452614000565b602083019690601f190136883760009360015b60ff8116828111611799576116ef816145ad565b611703575b506116fe9061459c565b6116db565b6116fe91968161171561179293614517565b9060ff831691611725838a61422c565b6001600160a01b0390911690528061173d838961422c565b5280600052600960205261175460406000206140f4565b61175e838d61422c565b52611769828c61422c565b50600052600960205261178860ff60036040600020015416918961422c565b901515905261459c565b95906116f4565b5050509095608d9560ff608d16965b886117b3838a614266565b60ff8316908111611877576117c7816145ad565b6117e3575b5050906117db6117b39261459c565b9091506117a8565b6117b39392976117db92826117fa61186c94614517565b61180860ff8516809461422c565b6001600160a01b03909116905280611820838b61422c565b528060005260096020528b6118428361183c60406000206140f4565b9261422c565b5261184d828d61422c565b50600052600960205261178860ff60036040600020015416918a61422c565b9691928a91506117cc565b50611897908489886118a589604051968796608088526080880190613dcd565b908682036020880152613e0a565b84810360408601526020808451928381520193019060005b818110611906575050506020908483036060860152519182815201919060005b8181106118eb575050500390f35b825115158452859450602093840193909201916001016118dd565b91949550919260206102608261191f6001948951613e69565b01950191019186959493926118bd565b60209061193a614273565b82828801015201611692565b6060366003190112610375576004356001600160401b03811161037557611971903690600401613d60565b906024359060443592608c61198885600054614266565b116115785760ff6002541661156757670214e8348c4f00008402848104670214e8348c4f0000148515171561154057340361152f576001841161151e5760065442106119f8575b5050503068929eee149b4bd212685414610dc857611510903068929eee149b4bd212685561457a565b6005544210611ae857604051602081019033825284604082015260408152611a21606082613fdf565b5190206040516020810191825260208152611a3d604082613fdf565b51902091816004549392611ab5575b505003611aa457336000526003602052611a6b82604060002054614266565b11611a93573360005260036020526040600020611a89828254614266565b90558180806119cf565b632c7e8a1760e11b60005260046000fd5b6309bde33960e01b60005260046000fd5b60051b810190915b602083359182811160051b908152185260206040600020920191818310611abd579150508480611a4c565b632d5d416160e01b60005260046000fd5b34610375576000366003190112610375576020600454604051908152f35b346103755760203660031901126103755760ff611b32613d40565b166000526009602052602060ff611b61611b4f60406000206140f4565b610e82611b5b82614e6f565b91614c80565b16604051908152f35b613f7f565b34610375576000366003190112610375576020604051600a8152f35b3461037557602036600319011261037557611ba4614a8c565b600480359055005b3461037557600036600319011261037557638b78c6d819546040516001600160a01b039091168152602090f35b34610375576000366003190112610375576020604051670214e8348c4f00008152f35b34610375576040366003190112610375576020611c17613d40565b60ff611c21613d50565b911660005260098252611c3760406000206140f4565b90611c428183614c0d565b918215611c56575b50506040519015158152f35b611c6092506145e2565b8280611c4a565b34610375576000366003190112610375576020600854604051908152f35b3461037557602036600319011261037557602061055960043560ff16600052600960205260ff6003604060002001541690565b600036600319011261037557611ccc614a8c565b6000638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a36000638b78c6d81955005b34610375576020366003190112610375576020611d27611d22613d14565b614545565b604051908152f35b34610375576040366003190112610375576020610559611d4d613d40565b60ff611d57613d50565b911660005260098352611d6d60406000206140f4565b614c0d565b34610375576020366003190112610375576004356001600160401b038111610375573660238201121561037557806004013590611dae82614000565b90611dbc6040519283613fdf565b82825260208201906024829460051b820101903682116103755760248101925b828410611ffa578585611ded614a8c565b5190680100000000000000008211611f4f57600a5482600a55808310611f65575b5090600a600052600080516020614eb9833981519152916000905b828210611e3257005b80518051906001600160401b038211611f4f57611e4f86546144dd565b601f8111611f12575b50602090601f8311600114611ea5579282600194936020938695600092611e9a575b5050600019600383901b1c191690841b1787555b01940191019092611e29565b015190508980611e7a565b90601f1983169187600052816000209260005b818110611efa5750936020936001969387969383889510611ee1575b505050811b018755611e8e565b015160001960f88460031b161c19169055898080611ed4565b92936020600181928786015181550195019301611eb8565b611f3f90876000526020600020601f850160051c81019160208610611f45575b601f0160051c01906141b7565b86611e58565b9091508190611f32565b634e487b7160e01b600052604160045260246000fd5b600a600052600080516020614eb98339815191520182600080516020614eb9833981519152015b818110611f995750611e0e565b80611fa6600192546144dd565b80611fb3575b5001611f8c565b601f81118314611fc95750600081555b85611fac565b611fe7908260005283601f6020600020920160051c820191016141b7565b8060005260006020812081835555611fc3565b83356001600160401b038111610375578201366043820112156103755760209161202f83923690604460248201359101614032565b815201930192611ddc565b34610375576040366003190112610375576020610559612058613d40565b60ff612062613d50565b91166000526009835261207860406000206140f4565b614830565b3461037557602036600319011261037557602061209b600435614517565b6040516001600160a01b039091168152f35b34610375576020366003190112610375576120c6613d14565b6120d261164a82614545565b6000906001600054905b8181111561217e575050600154608d9283915b6120f98186614266565b831161216857612108836145ad565b8061214b575b612128575b6121206120f99293614200565b9291506120ef565b612120612143838561213d6120f9968961422c565b52614200565b925050612113565b5061215583614517565b6001600160a01b0387811691161461210e565b604051602080825281906103c290820187613e0a565b612187816145ad565b806121bb575b6121a0575b61219b90614200565b6120dc565b926121b3818561213d61219b948761422c565b939050612192565b506121c581614517565b6001600160a01b0386811691161461218d565b34610375576040366003190112610375576121f1613d40565b6121f9613d50565b60ff8216906122126001600160a01b036105ae84614517565b60ff811692612223610753856145ad565b61222f838514156142ca565b82600052600960205261224560406000206140f4565b9184600052600960205261225c60406000206140f4565b9261226a6107888486614830565b61227483856145e2565b612563575b90612283916148f3565b9060608201805115612532575b5061229b91926149cc565b60608101805115612505575b5090826000526009602052604060002090805160009060005b600681106124d75750508255602081015160009060005b600681106124a95750506001830155604081015160009060005b6006811061247b5750506060839260039260026123219601550151151591019060ff801983541691151516179055565b826000526009602052604060002090805160009060005b6006811061244d5750508255602081015160009060005b6006811061241f5750506001830155604081015160009060005b600681106123f157600080516020614ef983398151915260208882828a6123ac8b600360608d8d60028501550151151591019060ff801983541691151516179055565b6123b7600854614200565b6008556040519084817f67a48d6d1051d802ffaf80bddeb34dc4d41cedaa1e6dac418e7390cc2b68eefd600080a38152a1604051908152a1005b909160206124166001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612369565b909160206124446001928460ff875116919060ff809160031b9316831b921b19161790565b9301910161234f565b909160206124726001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612338565b909160206124a06001928460ff875116919060ff809160031b9316831b921b19161790565b930191016122f1565b909160206124ce6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016122d7565b909160206124fc6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016122c0565b600190612510614273565b505290600080516020614ed98339815191526020604051868152a190846122a7565b91600161229b93612541614273565b505292600080516020614ed98339815191526020604051878152a19291612290565b929061257c81612576856122839561462b565b956146bf565b909150612279565b60003660031901126103755763389a75e1600c523360005260006020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92600080a2005b34610375576040366003190112610375576125e5613d40565b60ff806125f0613d50565b9216916126076001600160a01b036105ae85614517565b1690612615610753836145ad565b8082146127f05780600052600960205261263260406000206140f4565b9061263b614273565b506040820160005b8460ff821660068110156127e65761265e60ff9185516145d1565b5116146126705760010160ff16612643565b93909192935b60ff811660058110156126b55760ff916001916126ad846126a388518261269c87614240565b16906145d1565b51169187516145d1565b520116612676565b505092600060a0835101525b826000526009602052604060002091815160009060005b600681106127b85750508355602082015160009060005b6006811061278a57505060018401555160009060005b6006811061275c5750506060839260039260026127359601550151151591019060ff801983541691151516179055565b7f01144783e79beda3fbe88d466bfbfa047f503d6120ea2724049ead213fd85d64600080a3005b909160206127816001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612705565b909160206127af6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016126ef565b909160206127dd6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016126d8565b50505090916126c1565b60405162461bcd60e51b815260206004820152601360248201527221b0b73737ba103ab7313637b1b59039b2b63360691b6044820152606490fd5b34610375576020366003190112610375576004356001600160a01b0381169081900361037557612859614a8c565b80156103755760008080809347905af13d15612899573d61287981614017565b906128876040519283613fdf565b8152600060203d92013e5b1561037557005b612892565b34610375576000366003190112610375576020600054604051908152f35b34610375576000366003190112610375576020600554604051908152f35b6128e336613ea8565b6128f08183859495614353565b823b6128f857005b6103f4926040519261290b602085613fdf565b60008452614b78565b346103755760403660031901126103755761292d613d14565b602435600054608c61293f8383614266565b116115785761294c614a8c565b3068929eee149b4bd212685414610dc8573068929eee149b4bd21268556001810180911161154057816129829161298a94614aa9565b600054614266565b6000553868929eee149b4bd2126855005b34610375576000366003190112610375576020600654604051908152f35b346103755760203660031901126103755760ff6129d4613d40565b1660005260096020526129ea60406000206140f4565b612a0160ff6129fb610e7984614e6f565b166141ce565b906000805b60ff81166006811015612a5e5760ff918183612a2560019488516145d1565b5116612a34575b500116612a06565b612a40849187516145d1565b5116612a5784612a4f8761459c565b96168861422c565b5286612a2c565b5050602060009201915b60ff81166006811015612ab85760ff918183612a8760019488516145d1565b5116612a96575b500116612a68565b612aa2849187516145d1565b5116612ab184612a4f8761459c565b5286612a8e565b6103c2856103b681614dc0565b3461037557600036600319011261037557600a54612ae281614000565b612aef6040519182613fdf565b8181526020810191600a600052600080516020614eb9833981519152926000905b828210612b2557604051806103c28682613f1f565b60405160008654612b35816144dd565b8084529060018116908115612ba75750600114612b6f575b5060019282612b6185946020940382613fdf565b815201950191019093612b10565b6000888152602081209092505b818310612b9157505081016020016001612b4d565b6001816020925483868801015201920191612b7c565b60ff191660208581019190915291151560051b8401909101915060019050612b4d565b34610375576000366003190112610375576040516060612bea8183613fdf565b60028252601f1981019060005b828110612c7b576103c28460408051612c108282613fdf565b600e81526d4c65616e646572204865727a6f6760901b6020820152612c348361420f565b52612c3e8261420f565b508051612c4b8282613fdf565b6005815264183c33333360d91b6020820152612c668361421c565b52612c708261421c565b505191829182613f1f565b8082602080938701015201612bf7565b60003660031901126103755763389a75e1600c52336000526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d600080a2005b346103755760203660031901126103755760ff612cf2613d40565b1660005260096020526103c26103b6612d0e60406000206140f4565b6147ca565b6103f4612d1f36613ea8565b91614353565b3461037557612d3336613d90565b9060ff8316612d4c6001600160a01b036105ae83614517565b806000526009602052612d6260406000206140f4565b60005b84811061344b575060009260005b85811061342c5750612d8482614745565b9060005b8251811015612ef65760ff612d9d828561422c565b51166001811b871615612db4575b50600101612d88565b806000526009602052612dd881612dd28b610ecb60406000206140f4565b9661462b565b948160005260096020526040600020908051600090815b60068110612ec857505082556020810151600090815b60068110612e9a57505060018301556040810151600090815b60068110612e6c5750509160036060612e5293600197969560028501550151151591019060ff801983541691151516179055565b86600080516020614f19833981519152600080a390612dab565b90916020612e916001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612e1e565b90916020612ebf6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612e05565b90916020612eed6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612def565b509392949050612f05826147ca565b9060005b82518110156130b95760ff612f1e828561422c565b51166001811b861615612f35575b50600101612f09565b909381600052600960205288612f4e60406000206140f4565b612f5882826145e2565b612f72575b5050600191612f6b916146bf565b9390612f2c565b90612f7c9161462b565b8260005260096020526040600020908051600090815b6006811061308b57505082556020810151600090815b6006811061305d57505060018301556040810151600090815b6006811061302f5750508260036060612f6b9694612ff894600260019a9801550151151591019060ff801983541691151516179055565b600080516020614ef983398151915260206040518c85600080516020614f19833981519152600080a3848152a1918a919350612f5d565b909160206130546001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612fc1565b909160206130826001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612fa8565b909160206130b06001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612f92565b50925050918460005b83811061322c5750505060608201908151159081613222575b506131f5575b50908060005260096020526040600020825160009060005b600681106131c75750508155602083015160009060005b60068110613199575050600182015560408301519260009360005b6006811061316b57600080516020614ef983398151915260208661066f8760036060898d60028501550151151591019060ff801983541691151516179055565b909460206131906001928460ff8a5116919060ff809160031b9316831b921b19161790565b9601910161312b565b909160206131be6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101613110565b909160206131ec6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016130f9565b600190613200614273565b505290600080516020614ed98339815191526020604051838152a190826130e1565b90501515846130db565b61323a61073d828686614140565b6132448187614c0d565b801561341c575b61341357613284836107ab60ff84169384600052600960205261327160406000206140f4565b9961327f610788858d614830565b6148f3565b61328f600854614200565b600855606081018051156133e8575b508160005260096020526040600020908051600090815b600681106133ba57505082556020810151600090815b6006811061338c57505060018301556040810151600090815b6006811061335e575050926133298360036060602095600199986002600080516020614ef98339815191529901550151151591019060ff801983541691151516179055565b60405190808b7f67a48d6d1051d802ffaf80bddeb34dc4d41cedaa1e6dac418e7390cc2b68eefd600080a38152a15b016130c2565b909160206133836001928460ff875116919060ff809160031b9316831b921b19161790565b930191016132e4565b909160206133b16001928460ff875116919060ff809160031b9316831b921b19161790565b930191016132cb565b909160206133df6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016132b5565b6001906133f3614273565b5052600080516020614ed98339815191526020604051848152a18861329e565b50600190613358565b5061342781876145e2565b61324b565b936001908160ff61344161073d898b88614140565b161b179401612d73565b8061346a61075360ff61346461073d6001968b8b614140565b166145ad565b6134868460ff61347e61073d858b8b614140565b1614156142ca565b01612d65565b3461037557600036600319011261037557600854604051600a608082019260a0830160405260008452925b60001901926030828206018453049182156134d457600a906134b7565b613585601c605c6103c29461354e94608081601f19810193030182526040519586927f41424f206973206120736f6369616c207363756c7074757265206f662031353060208501527f206e6574776f726b656420746f6b656e732e20546f20646174652c2000000000604085015251809285850190613ccc565b81017f20636f6e6e656374696f6e732068617665206265656e206d6164652e00000000838201520301600319810184520182613fdf565b604051918291602083526020830190613cef565b346103755760203660031901126103755761028060ff6135b7613d40565b6135bf614273565b50166135ca81614517565b9060005260096020526135fd6135e360406000206140f4565b6040516001600160a01b0390931683526020830190613e69565bf35b34610375576000366003190112610375576020611d2760005460015490614266565b346103755760003660031901126103755760005460015461364561164a8284614266565b61365261164a8385614266565b9260009060015b818111156137045750608d93849291505b6136748186614266565b83116136da57613683836145ad565b61369d575b6136956136749293614200565b92915061366a565b6136956136d2613674936136b086614517565b6136ba828961422c565b6001600160a01b0390911690528561213d828b61422c565b925050613688565b6136f6846103c288604051938493604085526040850190613dcd565b908382036020850152613e0a565b61370d816145ad565b613720575b61371b90614200565b613659565b9161375361371b9161373185614517565b61373b828861422c565b6001600160a01b0390911690528461213d828a61422c565b929050613712565b346103755761376936613d90565b909160ff811691906137856001600160a01b036105ae85614517565b82600052600960205261379b60406000206140f4565b9360005b8381106138ba5785858060005260096020526040600020825160009060005b6006811061388c5750508155602083015160009060005b6006811061385e575050600182015560408301519260009360005b6006811061383057600080516020614ef983398151915260208661066f8760036060898d60028501550151151591019060ff801983541691151516179055565b909460206138556001928460ff8a5116919060ff809160031b9316831b921b19161790565b960191016137f0565b909160206138836001928460ff875116919060ff809160031b9316831b921b19161790565b930191016137d5565b909160206138b16001928460ff875116919060ff809160031b9316831b921b19161790565b930191016137be565b6138d061075360ff61346461073d858988614140565b8460ff6138e161073d848887614140565b1614613ad9576138f561073d828685614140565b60ff81169081600052600960205261391060406000206140f4565b9761391b82826145e2565b15613a6b5761392e61393492879261462b565b986146bf565b8187600080516020614f19833981519152600080a35b8160005260096020526040600020908051600090815b60068110613a3d57505082556020810151600090815b60068110613a0f57505060018301556040810151600090815b600681106139e1575050926139d48360036060602095600199986002600080516020614ef98339815191529901550151151591019060ff801983541691151516179055565b604051908152a10161379f565b90916020613a066001928460ff875116919060ff809160031b9316831b921b19161790565b9301910161398f565b90916020613a346001928460ff875116919060ff809160031b9316831b921b19161790565b93019101613976565b90916020613a626001928460ff875116919060ff809160031b9316831b921b19161790565b93019101613960565b9790613a7786836145e2565b15613aa45761392e86613a899361462b565b968682600080516020614f19833981519152600080a361394a565b60405162461bcd60e51b815260206004820152600d60248201526c139bdd0818dbdb9b9958dd1959609a1b6044820152606490fd5b60405162461bcd60e51b815260206004820152601660248201527521b0b73737ba103234b9b1b7b73732b1ba1039b2b63360511b6044820152606490fd5b604036600319011261037557613b2b613d14565b6024356000818152673ec412a9852d173d60c11b3317601c526020902081018101805491926001600160a01b039081169216908115613bc357829082331433151715613b9d575b600101557f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600080a4005b9050816000526030600c205415613bb5578290613b72565b634b6e7f186000526004601cfd5b63ceea21b66000526004601cfd5b34610375576020366003190112610375576004356000818152673ec412a9852d173d60c11b601c5260209020810101805460601b15613bc357600101546040516001600160a01b039091168152602090f35b34610375576000366003190112610375576103c26040805190613c468183613fdf565b601f82527f41424f206279204c65616e646572204865727a6f6720616e6420307866666600602083015251918291602083526020830190613cef565b3461037557602036600319011261037557600435906001600160e01b0319821682036103755760209160e01c635b5e139f8114906301ffc9a76380ac58cd82149114171715158152f35b60005b838110613cdf5750506000910152565b8181015183820152602001613ccf565b90602091613d0881518092818552858086019101613ccc565b601f01601f1916010190565b600435906001600160a01b038216820361037557565b602435906001600160a01b038216820361037557565b6004359060ff8216820361037557565b6024359060ff8216820361037557565b9181601f84011215610375578235916001600160401b038311610375576020808501948460051b01011161037557565b9060406003198301126103755760043560ff811681036103755791602435906001600160401b03821161037557613dc991600401613d60565b9091565b906020808351928381520192019060005b818110613deb5750505090565b82516001600160a01b0316845260209384019390920191600101613dde565b906020808351928381520192019060005b818110613e285750505090565b8251845260209384019390920191600101613e1b565b906000905b60068210613e5057505050565b60208060019260ff865116815201930191019091613e43565b606061024091613e7a848251613e3e565b613e8c602082015160c0860190613e3e565b613e9f6040820151610180860190613e3e565b01511515910152565b6060906003190112610375576004356001600160a01b038116810361037557906024356001600160a01b0381168103610375579060443590565b602060408183019282815284518094520192019060005b818110613f065750505090565b825160ff16845260209384019390920191600101613ef9565b602081016020825282518091526040820191602060408360051b8301019401926000915b838310613f5257505050505090565b9091929394602080613f70600193603f198682030187528951613cef565b97019301930191939290613f43565b34610375576000366003190112610375576103c26040805190613fa28183613fdf565b600382526241424f60e81b602083015251918291602083526020830190613cef565b608081019081106001600160401b03821117611f4f57604052565b90601f801991011681019081106001600160401b03821117611f4f57604052565b6001600160401b038111611f4f5760051b60200190565b6001600160401b038111611f4f57601f01601f191660200190565b92919261403e82614017565b9161404c6040519384613fdf565b829481845281830111610375578281602093846000960137010152565b1561407057565b60405162461bcd60e51b815260206004820152600d60248201526c2737ba103a34329037bbb732b960991b6044820152606490fd5b9060ff60405192548181168452818160081c166020850152818160101c166040850152818160181c166060850152818160201c16608085015260281c1660a08301526140f260c083613fdf565b565b9060405161410181613fc4565b606060ff60038395614112816140a5565b8552614120600182016140a5565b6020860152614131600282016140a5565b60408601520154161515910152565b91908110156141505760051b0190565b634e487b7160e01b600052603260045260246000fd5b3560ff811681036103755790565b1561417b57565b60405162461bcd60e51b8152602060048201526014602482015273151bdad95b88191bd95cc81b9bdd08195e1a5cdd60621b6044820152606490fd5b8181106141c2575050565b600081556001016141b7565b906141d882614000565b6141e56040519182613fdf565b82815280926141f6601f1991614000565b0190602036910137565b60001981146115405760010190565b8051156141505760200190565b8051600110156141505760400190565b80518210156141505760209160051b010190565b60ff60019116019060ff821161154057565b9060ff8091169116019060ff821161154057565b9190820180921161154057565b6040519061428082613fc4565b600060608360c06040516142948282613fdf565b8136823782526040516142a78282613fdf565b813682376020830152604051906142be8183613fdf565b36823760408201520152565b156142d157565b60405162461bcd60e51b815260206004820152601660248201527521b0b73737ba1031b7b73732b1ba103a379039b2b63360511b6044820152606490fd5b1561431657565b60405162461bcd60e51b8152602060048201526015602482015274546f6b656e2069732069676e6f72696e6720796f7560581b6044820152606490fd5b6001600160a01b0381161515806144b8575b614467576000838152673ec412a9852d173d60c11b3317601c52602090208301830180546001600160a01b0393841693928316928116808414810215614452575082600052816001018054803314853314171561443a575b614430575b50838318189055601c600c20600019815401905581600052601c600c2060018154019063ffffffff821684021561441b57557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef600080a4565b67ea553b3401336cea841560021b526004601cfd5b60009055386143c2565b6030600c20546143bd57634b6e7f186000526004601cfd5b67ceea21b6a1148100901560021b526004601cfd5b60405162461bcd60e51b815260206004820152602360248201527f546f6b656e206973206c6f636b65642e20436f6e6e65637420746f20756e6c6f60448201526231b59760e91b6064820152608490fd5b506144d78360ff16600052600960205260ff6003604060002001541690565b15614365565b90600182811c9216801561450d575b60208310146144f757565b634e487b7160e01b600052602260045260246000fd5b91607f16916144ec565b6000818152673ec412a9852d173d60c11b601c5260209020810101546001600160a01b0316908115613bc357565b801561456c57673ec412a9852d173d60c11b601c5260005263ffffffff601c600c20541690565b638f4eb6046000526004601cfd5b600054906001820180921161154057612982816145979333614aa9565b600055565b60ff1660ff81146115405760010190565b6000818152673ec412a9852d173d60c11b601c52602090208101015460601b151590565b9060068110156141505760051b0190565b919060005b60ff811660068110156146225761460460ff9160208701516145d1565b511660ff83161461461a5760010160ff166145e7565b506001925050565b50600093505050565b9091614635614273565b5060005b60ff811660068110156146b85760ff614657602086019283516145d1565b511660ff86161461466e575060010160ff16614639565b919293505b60ff811660058110156146ab5760ff916001916146a38461469987518261269c87614240565b51169186516145d1565b520116614673565b505051600060a090910152565b5050915090565b9190916146ca614273565b5060005b60ff8116600681101561473e576146e860ff9184516145d1565b511660ff8516146146fe5760010160ff166146ce565b909192505b60ff811660058110156147315760ff916001916147298461469987518261269c87614240565b520116614703565b5050600060a08251015290565b5090925050565b9061475460ff6129fb84614c80565b90600091602060009401935b60ff811660068110156147b85760ff91818361477f6001948a516145d1565b511661478e575b500116614760565b61479a849189516145d1565b51166147b1846147a98961459c565b98168661422c565b5238614786565b5050915091506147c781614dc0565b90565b906147d960ff6129fb84614e6f565b9060009160005b60ff811660068110156147b85760ff9181836147ff6001948a516145d1565b511661480e575b5001166147e0565b61481a849189516145d1565b5116614829846147a98961459c565b5238614806565b919060005b60ff811660068110156146225761485260ff9160408701516145d1565b511660ff83161461461a5760010160ff16614835565b1561486f57565b60405162461bcd60e51b815260206004820152601060248201526f125b9d985b1a59081d1bdad95b88125160821b6044820152606490fd5b156148ae57565b60405162461bcd60e51b815260206004820152601760248201527f4d617820636f6e6e656374696f6e7320726561636865640000000000000000006044820152606490fd5b9190916148fe614273565b5061491860ff841693614912851515614868565b826145e2565b6149875761493f600660ff61493861492f85614e6f565b610e8286614c80565b16106148a7565b60005b60ff8116600681101561473e576020830160ff6149608383516145d1565b51161561497457505060010160ff16614942565b614983925094929394516145d1565b5290565b60405162461bcd60e51b815260206004820152601f60248201527f416c726561647920686173206f7574626f756e6420636f6e6e656374696f6e006044820152606490fd5b9190916149d7614273565b506149f160ff8416936149eb851515614868565b82614c0d565b614a4757614a08600660ff61493861492f85614e6f565b60005b60ff8116600681101561473e5760ff614a258285516145d1565b511615614a38575060010160ff16614a0b565b614983915082939492516145d1565b60405162461bcd60e51b815260206004820152601e60248201527f416c72656164792068617320696e626f756e6420636f6e6e656374696f6e00006044820152606490fd5b638b78c6d819543303614a9b57565b6382b429006000526004601cfd5b91929092835b614ab98286614266565b811015614b71576000818152673ec412a9852d173d60c11b601c52602090208101810180546001600160a01b0386169190606081901b614b63578217905580600052601c600c2060018154019063ffffffff8216830215614b4e5755614ab99291600191819060007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8180a401909150614aaf565b67ea553b3401336cea831560021b526004601cfd5b63c991cbb16000526004601cfd5b5050915050565b9060a46020939460405195869463150b7a028652338787015260018060a01b03166040860152606085015260808085015280518091818060a0880152614bf9575b505001906000601c8401915af115614bea575b5163757a42ff60e11b01614bdc57565b63d1a57ed66000526004601cfd5b3d15614bcc573d6000823e3d90fd5b818760c08801920160045afa508038614bb9565b919060005b60ff8116600681101561462257614c2c60ff9186516145d1565b511660ff83161461461a5760010160ff16614c12565b60018060a01b031680638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3638b78c6d81955565b60009160005b60ff81166006811015614ccc57614ca360ff9160208601516145d1565b5116614cb5575b60010160ff16614c86565b926001614cc360ff9261459c565b94915050614caa565b50509050565b919091614cdd614273565b50614cf760ff841693614cf1851515614868565b82614830565b614d755760005b60ff81166006811015614d30576040830160ff614d1c8383516145d1565b51161561497457505060010160ff16614cfe565b60405162461bcd60e51b815260206004820152601960248201527f4d61782069676e6f726564206c696d69742072656163686564000000000000006044820152606490fd5b60405162461bcd60e51b815260206004820152601060248201526f416c72656164792069676e6f72696e6760801b6044820152606490fd5b60ff6000199116019060ff821161154057565b906001915b805160ff841690811015614ccc57614ddf60ff918361422c565b511691835b60ff811690811590811580614e4f575b15614e2957614e0f60ff614e088193614dad565b168661422c565b5116614e1b838661422c565b526115405760001901614de4565b614e4894959250614e42915060ff90969396168561422c565b5261459c565b9190614dc5565b508560ff614e6781614e6085614dad565b168861422c565b511611614df4565b60009160005b60ff81166006811015614ccc57614e8f60ff9185516145d1565b5116614ea1575b60010160ff16614e75565b926001614eaf60ff9261459c565b94915050614e9656fec65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8832a253ad4e9e88f705006a24d9957b8aa1de307a0f9d0a6ad5fd0b0ac810505f8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce73ebdd074970314b7ecf93f5dc8b82ec796fcc71c749d2d2e7f2ff99e92af9934a2646970667358221220ab0d6ff1ee95bbd7cf900d4aa89ddd1bf1cbd565171b4b179a1830003119308c64736f6c634300081c0033000000000000000000000000fff9a1c9fcae8f2791c91a24b9f0ff6d9c467337
Deployed Bytecode
0x608080604052600436101561001357600080fd5b60003560e01c90816301ffc9a714613c825750806306fdde0314613c23578063081812fc14613bd1578063095ea7b314613b1757806311e6c0401461375b57806313e4f9511461362157806318160ddd146135ff5780631a041204146135995780631f1bd6921461348c57806322d9a7b614612d2557806323b872dd14612d1357806324899b7614612cd75780632569296214612c8b57806328411ae114612bca5780632c73dc4a14612ac55780632e69100d146129b95780633defb8191461299b578063408cbf941461291457806342842e0e146128da578063490b196b146128bc5780634a79d50c14611b6a5780634bf440261461289e57806351cff8d91461282b5780635317af21146125cc57806354d1f13d1461258457806358b1fad4146121d85780635a3f2672146120ad5780636352211e1461207d5780636589a7af1461203a5780636cc895a914611d725780636faa349714611d2f57806370a0823114611d04578063715018a614611cb857806372abc8b714611c855780637becf1ea14611c67578063825769fb14611bfc5780638d859f3e14611bd95780638da5cb5b14611bac5780638e0acd1214611b8b5780639524bb4414611b6f57806395d89b4114611b6a578063976da93814611b175780639c9c666914611af95780639caa07c0146119465780639d63848a146116265780639dfbcde81461160a578063a01cc77114611589578063a0712d6814611487578063a22cb46514611412578063a26e2a5b146113f6578063a5038c74146113d5578063b88d4fde1461134e578063bcad51e214610e23578063bd1da6d414610de7578063be3723dd14610d3d578063bedb86fb14610d04578063c87b56dd14610c0d578063ca3152e414610a3b578063cb14eb8714610a1f578063d607497a146109f6578063da0321cd146109a7578063e1a283d614610984578063e244fff014610966578063e55fc93b14610584578063e6b0561314610563578063e6f4f3c614610516578063e81ed044146104dc578063e985e9c514610497578063f04e283e14610447578063f0c136cb14610404578063f2fde38b146103c6578063f530e68b1461037a5763fee81cf41461034257600080fd5b346103755760203660031901126103755761035b613d14565b63389a75e1600c52600052602080600c2054604051908152f35b600080fd5b346103755760203660031901126103755760ff610395613d40565b1660005260096020526103c26103b66103b160406000206140f4565b614745565b60405191829182613ee2565b0390f35b6020366003190112610375576103da613d14565b6103e2614a8c565b8060601b156103f6576103f490614c42565b005b637448fbae6000526004601cfd5b346103755760203660031901126103755761041d613d14565b610425614a8c565b600780546001600160a01b0319166001600160a01b0392909216919091179055005b60203660031901126103755761045b613d14565b610463614a8c565b63389a75e1600c52806000526020600c2090815442116104895760006103f49255614c42565b636f5e88186000526004601cfd5b34610375576040366003190112610375576104b0613d14565b6104b8613d2a565b601c52670a5a2e7a0000000060085260005260206030600c20546040519015158152f35b34610375576020366003190112610375576001600160a01b036104fd613d14565b1660005260036020526020604060002054604051908152f35b34610375576040366003190112610375576020610559610534613d40565b60ff61053e613d50565b91166000526009835261055460406000206140f4565b6145e2565b6040519015158152f35b346103755760203660031901126103755761057c614a8c565b600435600655005b346103755761059236613d90565b60ff8316929091906105b66001600160a01b036105ae86614517565b163314614069565b8360005260096020526105cc60406000206140f4565b9260005b81811061072f57858560608101805115610702575b50908060005260096020526040600020825160009060005b600681106106d45750508155602083015160009060005b600681106106a6575050600182015560408301519260009360005b6006811061067857600080516020614ef983398151915260208661066f8760036060898d60028501550151151591019060ff801983541691151516179055565b604051908152a1005b9094602061069d6001928460ff8a5116919060ff809160031b9316831b921b19161790565b9601910161062f565b909160206106cb6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610614565b909160206106f96001928460ff875116919060ff809160031b9316831b921b19161790565b930191016105fd565b60019061070d614273565b505290600080516020614ed98339815191526020604051838152a190826105e5565b61074261073d828487614140565b614166565b60ff811690610758610753836145ad565b614174565b610764888314156142ca565b81600052600960205261077a60406000206140f4565b9661078e610788878a614830565b1561430f565b61079886896145e2565b61093f575b6107ab6107b19287926148f3565b976149cc565b6107bc600854614200565b60085560608101805115610914575b508160005260096020526040600020908051600090815b600681106108e657505082556020810151600090815b600681106108b857505060018301556040810151600090815b6006811061088a575050926108568360036060602095600199986002600080516020614ef98339815191529901550151151591019060ff801983541691151516179055565b60405190808b7f67a48d6d1051d802ffaf80bddeb34dc4d41cedaa1e6dac418e7390cc2b68eefd600080a38152a1016105d0565b909160206108af6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610811565b909160206108dd6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016107f8565b9091602061090b6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016107e2565b60019061091f614273565b5052600080516020614ed98339815191526020604051848152a1886107cb565b966107ab8261095c6107b1946109568a809661462b565b9b6146bf565b925092505061079d565b34610375576000366003190112610375576020600154604051908152f35b3461037557600036600319011261037557602060ff600254166040519015158152f35b34610375576000366003190112610375576103c260408051906109ca8183613fdf565b60018252601f198101366020840137306109e38361420f565b5251918291602083526020830190613dcd565b34610375576000366003190112610375576007546040516001600160a01b039091168152602090f35b3461037557600036600319011261037557602060405160018152f35b3461037557604036600319011261037557610a54613d40565b60ff610a5e613d50565b9116610a746001600160a01b036105ae83614517565b60ff821691610a85610753846145ad565b818314610bd457610aab90826000526009602052610aa660406000206140f4565b614cd2565b816000526009602052604060002090805160009060005b60068110610ba65750508255602081015160009060005b60068110610b785750506001830155604081015160009060005b60068110610b4a575050606083926003926002610b239601550151151591019060ff801983541691151516179055565b7f74c7a4a7b27d4f0db8545d9c8c688b37ee8059db8b47251f05f2c0ad6f2f0830600080a3005b90916020610b6f6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610af3565b90916020610b9d6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610ad9565b90916020610bcb6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610ac2565b60405162461bcd60e51b815260206004820152601160248201527021b0b73737ba10313637b1b59039b2b63360791b6044820152606490fd5b346103755760203660031901126103755760075460405163c87b56dd60e01b8152600480359082015290600090829060249082906001600160a01b03165afa908115610cf857600091610c71575b604051602080825281906103c290820185613cef565b3d8083833e610c808183613fdf565b810190602081830312610cf0578051906001600160401b038211610cf4570181601f82011215610cf057805192610cb684614017565b92610cc46040519485613fdf565b84845260208584010111610ced57506103c292610ce79160208085019101613ccc565b90610c5b565b80fd5b8280fd5b8380fd5b6040513d6000823e3d90fd5b346103755760203660031901126103755760043580151580910361037557610d2a614a8c565b60ff801960025416911617600255600080f35b3461037557604036600319011261037557610d56613d14565b60243560015491600a610d698385614266565b11610dd657610d76614a8c565b3068929eee149b4bd212685414610dc85781610daf91610da9610db7953068929eee149b4bd212685560ff608d16614266565b90614aa9565b600154614266565b6001553868929eee149b4bd2126855005b63ab143c066000526004601cfd5b63c98cb7ab60e01b60005260046000fd5b346103755760203660031901126103755760ff610e02613d40565b166000526009602052602060ff600360406000200154166040519015158152f35b3461037557602036600319011261037557610e3c613d40565b60ff8116610e546001600160a01b036105ae83614517565b806000526009602052610e6a60406000206140f4565b9060ff610e88610e7984614e6f565b610e8285614c80565b90614252565b161561130957610e9782614745565b60005b81518110156110065760ff610eaf828461422c565b5116806000526009602052610ed086610ecb60406000206140f4565b6146bf565b8160005260096020526040600020908051600090815b60068110610fd857505082556020810151600090815b60068110610faa57505060018301556040810151600090815b60068110610f7c57505092610f5a8360036060602095600199986002600080516020614ef98339815191529901550151151591019060ff801983541691151516179055565b604051908088600080516020614f19833981519152600080a38152a101610e9a565b90916020610fa16001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610f15565b90916020610fcf6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610efc565b90916020610ffd6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101610ee6565b5050611011826147ca565b9260005b84518110156111815760ff61102a828761422c565b511680600052600960205261104b8361104660406000206140f4565b61462b565b8160005260096020526040600020908051600090815b6006811061115357505082556020810151600090815b6006811061112557505060018301556040810151600090815b600681106110f7575050926110d58360036060602095600199986002600080516020614ef98339815191529901550151151591019060ff801983541691151516179055565b604051908781600080516020614f19833981519152600080a38152a101611015565b9091602061111c6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101611090565b9091602061114a6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101611077565b909160206111786001928460ff875116919060ff809160031b9316831b921b19161790565b93019101611061565b505061118b614273565b50611194614273565b5060005b60ff811660068110156111bf5760ff9160006111b760019387516145d1565b520116611198565b50506111c9614273565b506020820160005b60ff811660068110156111f85760ff9160006111f060019386516145d1565b5201166111d1565b5050816000526009602052604060002090835160009060005b600681106112db57505082555160009060005b600681106112ad575050600182015560408301519260009360005b6006811061127f57600080516020614ef983398151915260208661066f8760036060898d60028501550151151591019060ff801983541691151516179055565b909460206112a46001928460ff8a5116919060ff809160031b9316831b921b19161790565b9601910161123f565b909160206112d26001928460ff875116919060ff809160031b9316831b921b19161790565b93019101611224565b909160206113006001928460ff875116919060ff809160031b9316831b921b19161790565b93019101611211565b60405162461bcd60e51b815260206004820152601860248201527f546f6b656e20686173206e6f20636f6e6e656374696f6e7300000000000000006044820152606490fd5b608036600319011261037557611362613d14565b61136a613d2a565b90604435606435926001600160401b03841161037557366023850112156103755783600401356001600160401b038111610375573660248287010111610375576113b5838386614353565b813b6113bd57005b6103f4946113cf916024369201614032565b92614b78565b34610375576020366003190112610375576113ee614a8c565b600435600555005b3461037557600036600319011261037557602060405160068152f35b346103755760403660031901126103755761142b613d14565b6024358015158091036103755781601c52670a5a2e7a0000000060085233600052806030600c205560005260018060a01b0316337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160206000a3005b602036600319011261037557600435608c6114a482600054614266565b116115785760ff6002541661156757600654421061155657670214e8348c4f00008102818104670214e8348c4f0000148215171561154057340361152f576001811161151e573068929eee149b4bd212685414610dc857611510903068929eee149b4bd212685561457a565b3868929eee149b4bd2126855005b6306b6632960e41b60005260046000fd5b6399b5cb1d60e01b60005260046000fd5b634e487b7160e01b600052601160045260246000fd5b6358755ad360e11b60005260046000fd5b6375ab03ab60e11b60005260046000fd5b632cdb04a160e21b60005260046000fd5b34610375576000366003190112610375576000609660015b60ff81168281116115ff576115b5816145ad565b6115c9575b506115c49061459c565b6115a1565b6115c491936115f891600052600960205260ff6115f16115ec60406000206140f4565b614c80565b1690614266565b92906115ba565b602084604051908152f35b34610375576000366003190112610375576020604051608c8152f35b346103755760003660031901126103755760005460015461164f61164a8284614266565b6141ce565b61165c61164a8385614266565b926116678382614266565b9161167183614000565b9261167f6040519485613fdf565b80845261168e601f1991614000565b0160005b81811061192f5750506116a58483614266565b906116c86116b283614000565b926116c06040519485613fdf565b808452614000565b602083019690601f190136883760009360015b60ff8116828111611799576116ef816145ad565b611703575b506116fe9061459c565b6116db565b6116fe91968161171561179293614517565b9060ff831691611725838a61422c565b6001600160a01b0390911690528061173d838961422c565b5280600052600960205261175460406000206140f4565b61175e838d61422c565b52611769828c61422c565b50600052600960205261178860ff60036040600020015416918961422c565b901515905261459c565b95906116f4565b5050509095608d9560ff608d16965b886117b3838a614266565b60ff8316908111611877576117c7816145ad565b6117e3575b5050906117db6117b39261459c565b9091506117a8565b6117b39392976117db92826117fa61186c94614517565b61180860ff8516809461422c565b6001600160a01b03909116905280611820838b61422c565b528060005260096020528b6118428361183c60406000206140f4565b9261422c565b5261184d828d61422c565b50600052600960205261178860ff60036040600020015416918a61422c565b9691928a91506117cc565b50611897908489886118a589604051968796608088526080880190613dcd565b908682036020880152613e0a565b84810360408601526020808451928381520193019060005b818110611906575050506020908483036060860152519182815201919060005b8181106118eb575050500390f35b825115158452859450602093840193909201916001016118dd565b91949550919260206102608261191f6001948951613e69565b01950191019186959493926118bd565b60209061193a614273565b82828801015201611692565b6060366003190112610375576004356001600160401b03811161037557611971903690600401613d60565b906024359060443592608c61198885600054614266565b116115785760ff6002541661156757670214e8348c4f00008402848104670214e8348c4f0000148515171561154057340361152f576001841161151e5760065442106119f8575b5050503068929eee149b4bd212685414610dc857611510903068929eee149b4bd212685561457a565b6005544210611ae857604051602081019033825284604082015260408152611a21606082613fdf565b5190206040516020810191825260208152611a3d604082613fdf565b51902091816004549392611ab5575b505003611aa457336000526003602052611a6b82604060002054614266565b11611a93573360005260036020526040600020611a89828254614266565b90558180806119cf565b632c7e8a1760e11b60005260046000fd5b6309bde33960e01b60005260046000fd5b60051b810190915b602083359182811160051b908152185260206040600020920191818310611abd579150508480611a4c565b632d5d416160e01b60005260046000fd5b34610375576000366003190112610375576020600454604051908152f35b346103755760203660031901126103755760ff611b32613d40565b166000526009602052602060ff611b61611b4f60406000206140f4565b610e82611b5b82614e6f565b91614c80565b16604051908152f35b613f7f565b34610375576000366003190112610375576020604051600a8152f35b3461037557602036600319011261037557611ba4614a8c565b600480359055005b3461037557600036600319011261037557638b78c6d819546040516001600160a01b039091168152602090f35b34610375576000366003190112610375576020604051670214e8348c4f00008152f35b34610375576040366003190112610375576020611c17613d40565b60ff611c21613d50565b911660005260098252611c3760406000206140f4565b90611c428183614c0d565b918215611c56575b50506040519015158152f35b611c6092506145e2565b8280611c4a565b34610375576000366003190112610375576020600854604051908152f35b3461037557602036600319011261037557602061055960043560ff16600052600960205260ff6003604060002001541690565b600036600319011261037557611ccc614a8c565b6000638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a36000638b78c6d81955005b34610375576020366003190112610375576020611d27611d22613d14565b614545565b604051908152f35b34610375576040366003190112610375576020610559611d4d613d40565b60ff611d57613d50565b911660005260098352611d6d60406000206140f4565b614c0d565b34610375576020366003190112610375576004356001600160401b038111610375573660238201121561037557806004013590611dae82614000565b90611dbc6040519283613fdf565b82825260208201906024829460051b820101903682116103755760248101925b828410611ffa578585611ded614a8c565b5190680100000000000000008211611f4f57600a5482600a55808310611f65575b5090600a600052600080516020614eb9833981519152916000905b828210611e3257005b80518051906001600160401b038211611f4f57611e4f86546144dd565b601f8111611f12575b50602090601f8311600114611ea5579282600194936020938695600092611e9a575b5050600019600383901b1c191690841b1787555b01940191019092611e29565b015190508980611e7a565b90601f1983169187600052816000209260005b818110611efa5750936020936001969387969383889510611ee1575b505050811b018755611e8e565b015160001960f88460031b161c19169055898080611ed4565b92936020600181928786015181550195019301611eb8565b611f3f90876000526020600020601f850160051c81019160208610611f45575b601f0160051c01906141b7565b86611e58565b9091508190611f32565b634e487b7160e01b600052604160045260246000fd5b600a600052600080516020614eb98339815191520182600080516020614eb9833981519152015b818110611f995750611e0e565b80611fa6600192546144dd565b80611fb3575b5001611f8c565b601f81118314611fc95750600081555b85611fac565b611fe7908260005283601f6020600020920160051c820191016141b7565b8060005260006020812081835555611fc3565b83356001600160401b038111610375578201366043820112156103755760209161202f83923690604460248201359101614032565b815201930192611ddc565b34610375576040366003190112610375576020610559612058613d40565b60ff612062613d50565b91166000526009835261207860406000206140f4565b614830565b3461037557602036600319011261037557602061209b600435614517565b6040516001600160a01b039091168152f35b34610375576020366003190112610375576120c6613d14565b6120d261164a82614545565b6000906001600054905b8181111561217e575050600154608d9283915b6120f98186614266565b831161216857612108836145ad565b8061214b575b612128575b6121206120f99293614200565b9291506120ef565b612120612143838561213d6120f9968961422c565b52614200565b925050612113565b5061215583614517565b6001600160a01b0387811691161461210e565b604051602080825281906103c290820187613e0a565b612187816145ad565b806121bb575b6121a0575b61219b90614200565b6120dc565b926121b3818561213d61219b948761422c565b939050612192565b506121c581614517565b6001600160a01b0386811691161461218d565b34610375576040366003190112610375576121f1613d40565b6121f9613d50565b60ff8216906122126001600160a01b036105ae84614517565b60ff811692612223610753856145ad565b61222f838514156142ca565b82600052600960205261224560406000206140f4565b9184600052600960205261225c60406000206140f4565b9261226a6107888486614830565b61227483856145e2565b612563575b90612283916148f3565b9060608201805115612532575b5061229b91926149cc565b60608101805115612505575b5090826000526009602052604060002090805160009060005b600681106124d75750508255602081015160009060005b600681106124a95750506001830155604081015160009060005b6006811061247b5750506060839260039260026123219601550151151591019060ff801983541691151516179055565b826000526009602052604060002090805160009060005b6006811061244d5750508255602081015160009060005b6006811061241f5750506001830155604081015160009060005b600681106123f157600080516020614ef983398151915260208882828a6123ac8b600360608d8d60028501550151151591019060ff801983541691151516179055565b6123b7600854614200565b6008556040519084817f67a48d6d1051d802ffaf80bddeb34dc4d41cedaa1e6dac418e7390cc2b68eefd600080a38152a1604051908152a1005b909160206124166001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612369565b909160206124446001928460ff875116919060ff809160031b9316831b921b19161790565b9301910161234f565b909160206124726001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612338565b909160206124a06001928460ff875116919060ff809160031b9316831b921b19161790565b930191016122f1565b909160206124ce6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016122d7565b909160206124fc6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016122c0565b600190612510614273565b505290600080516020614ed98339815191526020604051868152a190846122a7565b91600161229b93612541614273565b505292600080516020614ed98339815191526020604051878152a19291612290565b929061257c81612576856122839561462b565b956146bf565b909150612279565b60003660031901126103755763389a75e1600c523360005260006020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92600080a2005b34610375576040366003190112610375576125e5613d40565b60ff806125f0613d50565b9216916126076001600160a01b036105ae85614517565b1690612615610753836145ad565b8082146127f05780600052600960205261263260406000206140f4565b9061263b614273565b506040820160005b8460ff821660068110156127e65761265e60ff9185516145d1565b5116146126705760010160ff16612643565b93909192935b60ff811660058110156126b55760ff916001916126ad846126a388518261269c87614240565b16906145d1565b51169187516145d1565b520116612676565b505092600060a0835101525b826000526009602052604060002091815160009060005b600681106127b85750508355602082015160009060005b6006811061278a57505060018401555160009060005b6006811061275c5750506060839260039260026127359601550151151591019060ff801983541691151516179055565b7f01144783e79beda3fbe88d466bfbfa047f503d6120ea2724049ead213fd85d64600080a3005b909160206127816001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612705565b909160206127af6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016126ef565b909160206127dd6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016126d8565b50505090916126c1565b60405162461bcd60e51b815260206004820152601360248201527221b0b73737ba103ab7313637b1b59039b2b63360691b6044820152606490fd5b34610375576020366003190112610375576004356001600160a01b0381169081900361037557612859614a8c565b80156103755760008080809347905af13d15612899573d61287981614017565b906128876040519283613fdf565b8152600060203d92013e5b1561037557005b612892565b34610375576000366003190112610375576020600054604051908152f35b34610375576000366003190112610375576020600554604051908152f35b6128e336613ea8565b6128f08183859495614353565b823b6128f857005b6103f4926040519261290b602085613fdf565b60008452614b78565b346103755760403660031901126103755761292d613d14565b602435600054608c61293f8383614266565b116115785761294c614a8c565b3068929eee149b4bd212685414610dc8573068929eee149b4bd21268556001810180911161154057816129829161298a94614aa9565b600054614266565b6000553868929eee149b4bd2126855005b34610375576000366003190112610375576020600654604051908152f35b346103755760203660031901126103755760ff6129d4613d40565b1660005260096020526129ea60406000206140f4565b612a0160ff6129fb610e7984614e6f565b166141ce565b906000805b60ff81166006811015612a5e5760ff918183612a2560019488516145d1565b5116612a34575b500116612a06565b612a40849187516145d1565b5116612a5784612a4f8761459c565b96168861422c565b5286612a2c565b5050602060009201915b60ff81166006811015612ab85760ff918183612a8760019488516145d1565b5116612a96575b500116612a68565b612aa2849187516145d1565b5116612ab184612a4f8761459c565b5286612a8e565b6103c2856103b681614dc0565b3461037557600036600319011261037557600a54612ae281614000565b612aef6040519182613fdf565b8181526020810191600a600052600080516020614eb9833981519152926000905b828210612b2557604051806103c28682613f1f565b60405160008654612b35816144dd565b8084529060018116908115612ba75750600114612b6f575b5060019282612b6185946020940382613fdf565b815201950191019093612b10565b6000888152602081209092505b818310612b9157505081016020016001612b4d565b6001816020925483868801015201920191612b7c565b60ff191660208581019190915291151560051b8401909101915060019050612b4d565b34610375576000366003190112610375576040516060612bea8183613fdf565b60028252601f1981019060005b828110612c7b576103c28460408051612c108282613fdf565b600e81526d4c65616e646572204865727a6f6760901b6020820152612c348361420f565b52612c3e8261420f565b508051612c4b8282613fdf565b6005815264183c33333360d91b6020820152612c668361421c565b52612c708261421c565b505191829182613f1f565b8082602080938701015201612bf7565b60003660031901126103755763389a75e1600c52336000526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d600080a2005b346103755760203660031901126103755760ff612cf2613d40565b1660005260096020526103c26103b6612d0e60406000206140f4565b6147ca565b6103f4612d1f36613ea8565b91614353565b3461037557612d3336613d90565b9060ff8316612d4c6001600160a01b036105ae83614517565b806000526009602052612d6260406000206140f4565b60005b84811061344b575060009260005b85811061342c5750612d8482614745565b9060005b8251811015612ef65760ff612d9d828561422c565b51166001811b871615612db4575b50600101612d88565b806000526009602052612dd881612dd28b610ecb60406000206140f4565b9661462b565b948160005260096020526040600020908051600090815b60068110612ec857505082556020810151600090815b60068110612e9a57505060018301556040810151600090815b60068110612e6c5750509160036060612e5293600197969560028501550151151591019060ff801983541691151516179055565b86600080516020614f19833981519152600080a390612dab565b90916020612e916001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612e1e565b90916020612ebf6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612e05565b90916020612eed6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612def565b509392949050612f05826147ca565b9060005b82518110156130b95760ff612f1e828561422c565b51166001811b861615612f35575b50600101612f09565b909381600052600960205288612f4e60406000206140f4565b612f5882826145e2565b612f72575b5050600191612f6b916146bf565b9390612f2c565b90612f7c9161462b565b8260005260096020526040600020908051600090815b6006811061308b57505082556020810151600090815b6006811061305d57505060018301556040810151600090815b6006811061302f5750508260036060612f6b9694612ff894600260019a9801550151151591019060ff801983541691151516179055565b600080516020614ef983398151915260206040518c85600080516020614f19833981519152600080a3848152a1918a919350612f5d565b909160206130546001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612fc1565b909160206130826001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612fa8565b909160206130b06001928460ff875116919060ff809160031b9316831b921b19161790565b93019101612f92565b50925050918460005b83811061322c5750505060608201908151159081613222575b506131f5575b50908060005260096020526040600020825160009060005b600681106131c75750508155602083015160009060005b60068110613199575050600182015560408301519260009360005b6006811061316b57600080516020614ef983398151915260208661066f8760036060898d60028501550151151591019060ff801983541691151516179055565b909460206131906001928460ff8a5116919060ff809160031b9316831b921b19161790565b9601910161312b565b909160206131be6001928460ff875116919060ff809160031b9316831b921b19161790565b93019101613110565b909160206131ec6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016130f9565b600190613200614273565b505290600080516020614ed98339815191526020604051838152a190826130e1565b90501515846130db565b61323a61073d828686614140565b6132448187614c0d565b801561341c575b61341357613284836107ab60ff84169384600052600960205261327160406000206140f4565b9961327f610788858d614830565b6148f3565b61328f600854614200565b600855606081018051156133e8575b508160005260096020526040600020908051600090815b600681106133ba57505082556020810151600090815b6006811061338c57505060018301556040810151600090815b6006811061335e575050926133298360036060602095600199986002600080516020614ef98339815191529901550151151591019060ff801983541691151516179055565b60405190808b7f67a48d6d1051d802ffaf80bddeb34dc4d41cedaa1e6dac418e7390cc2b68eefd600080a38152a15b016130c2565b909160206133836001928460ff875116919060ff809160031b9316831b921b19161790565b930191016132e4565b909160206133b16001928460ff875116919060ff809160031b9316831b921b19161790565b930191016132cb565b909160206133df6001928460ff875116919060ff809160031b9316831b921b19161790565b930191016132b5565b6001906133f3614273565b5052600080516020614ed98339815191526020604051848152a18861329e565b50600190613358565b5061342781876145e2565b61324b565b936001908160ff61344161073d898b88614140565b161b179401612d73565b8061346a61075360ff61346461073d6001968b8b614140565b166145ad565b6134868460ff61347e61073d858b8b614140565b1614156142ca565b01612d65565b3461037557600036600319011261037557600854604051600a608082019260a0830160405260008452925b60001901926030828206018453049182156134d457600a906134b7565b613585601c605c6103c29461354e94608081601f19810193030182526040519586927f41424f206973206120736f6369616c207363756c7074757265206f662031353060208501527f206e6574776f726b656420746f6b656e732e20546f20646174652c2000000000604085015251809285850190613ccc565b81017f20636f6e6e656374696f6e732068617665206265656e206d6164652e00000000838201520301600319810184520182613fdf565b604051918291602083526020830190613cef565b346103755760203660031901126103755761028060ff6135b7613d40565b6135bf614273565b50166135ca81614517565b9060005260096020526135fd6135e360406000206140f4565b6040516001600160a01b0390931683526020830190613e69565bf35b34610375576000366003190112610375576020611d2760005460015490614266565b346103755760003660031901126103755760005460015461364561164a8284614266565b61365261164a8385614266565b9260009060015b818111156137045750608d93849291505b6136748186614266565b83116136da57613683836145ad565b61369d575b6136956136749293614200565b92915061366a565b6136956136d2613674936136b086614517565b6136ba828961422c565b6001600160a01b0390911690528561213d828b61422c565b925050613688565b6136f6846103c288604051938493604085526040850190613dcd565b908382036020850152613e0a565b61370d816145ad565b613720575b61371b90614200565b613659565b9161375361371b9161373185614517565b61373b828861422c565b6001600160a01b0390911690528461213d828a61422c565b929050613712565b346103755761376936613d90565b909160ff811691906137856001600160a01b036105ae85614517565b82600052600960205261379b60406000206140f4565b9360005b8381106138ba5785858060005260096020526040600020825160009060005b6006811061388c5750508155602083015160009060005b6006811061385e575050600182015560408301519260009360005b6006811061383057600080516020614ef983398151915260208661066f8760036060898d60028501550151151591019060ff801983541691151516179055565b909460206138556001928460ff8a5116919060ff809160031b9316831b921b19161790565b960191016137f0565b909160206138836001928460ff875116919060ff809160031b9316831b921b19161790565b930191016137d5565b909160206138b16001928460ff875116919060ff809160031b9316831b921b19161790565b930191016137be565b6138d061075360ff61346461073d858988614140565b8460ff6138e161073d848887614140565b1614613ad9576138f561073d828685614140565b60ff81169081600052600960205261391060406000206140f4565b9761391b82826145e2565b15613a6b5761392e61393492879261462b565b986146bf565b8187600080516020614f19833981519152600080a35b8160005260096020526040600020908051600090815b60068110613a3d57505082556020810151600090815b60068110613a0f57505060018301556040810151600090815b600681106139e1575050926139d48360036060602095600199986002600080516020614ef98339815191529901550151151591019060ff801983541691151516179055565b604051908152a10161379f565b90916020613a066001928460ff875116919060ff809160031b9316831b921b19161790565b9301910161398f565b90916020613a346001928460ff875116919060ff809160031b9316831b921b19161790565b93019101613976565b90916020613a626001928460ff875116919060ff809160031b9316831b921b19161790565b93019101613960565b9790613a7786836145e2565b15613aa45761392e86613a899361462b565b968682600080516020614f19833981519152600080a361394a565b60405162461bcd60e51b815260206004820152600d60248201526c139bdd0818dbdb9b9958dd1959609a1b6044820152606490fd5b60405162461bcd60e51b815260206004820152601660248201527521b0b73737ba103234b9b1b7b73732b1ba1039b2b63360511b6044820152606490fd5b604036600319011261037557613b2b613d14565b6024356000818152673ec412a9852d173d60c11b3317601c526020902081018101805491926001600160a01b039081169216908115613bc357829082331433151715613b9d575b600101557f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600080a4005b9050816000526030600c205415613bb5578290613b72565b634b6e7f186000526004601cfd5b63ceea21b66000526004601cfd5b34610375576020366003190112610375576004356000818152673ec412a9852d173d60c11b601c5260209020810101805460601b15613bc357600101546040516001600160a01b039091168152602090f35b34610375576000366003190112610375576103c26040805190613c468183613fdf565b601f82527f41424f206279204c65616e646572204865727a6f6720616e6420307866666600602083015251918291602083526020830190613cef565b3461037557602036600319011261037557600435906001600160e01b0319821682036103755760209160e01c635b5e139f8114906301ffc9a76380ac58cd82149114171715158152f35b60005b838110613cdf5750506000910152565b8181015183820152602001613ccf565b90602091613d0881518092818552858086019101613ccc565b601f01601f1916010190565b600435906001600160a01b038216820361037557565b602435906001600160a01b038216820361037557565b6004359060ff8216820361037557565b6024359060ff8216820361037557565b9181601f84011215610375578235916001600160401b038311610375576020808501948460051b01011161037557565b9060406003198301126103755760043560ff811681036103755791602435906001600160401b03821161037557613dc991600401613d60565b9091565b906020808351928381520192019060005b818110613deb5750505090565b82516001600160a01b0316845260209384019390920191600101613dde565b906020808351928381520192019060005b818110613e285750505090565b8251845260209384019390920191600101613e1b565b906000905b60068210613e5057505050565b60208060019260ff865116815201930191019091613e43565b606061024091613e7a848251613e3e565b613e8c602082015160c0860190613e3e565b613e9f6040820151610180860190613e3e565b01511515910152565b6060906003190112610375576004356001600160a01b038116810361037557906024356001600160a01b0381168103610375579060443590565b602060408183019282815284518094520192019060005b818110613f065750505090565b825160ff16845260209384019390920191600101613ef9565b602081016020825282518091526040820191602060408360051b8301019401926000915b838310613f5257505050505090565b9091929394602080613f70600193603f198682030187528951613cef565b97019301930191939290613f43565b34610375576000366003190112610375576103c26040805190613fa28183613fdf565b600382526241424f60e81b602083015251918291602083526020830190613cef565b608081019081106001600160401b03821117611f4f57604052565b90601f801991011681019081106001600160401b03821117611f4f57604052565b6001600160401b038111611f4f5760051b60200190565b6001600160401b038111611f4f57601f01601f191660200190565b92919261403e82614017565b9161404c6040519384613fdf565b829481845281830111610375578281602093846000960137010152565b1561407057565b60405162461bcd60e51b815260206004820152600d60248201526c2737ba103a34329037bbb732b960991b6044820152606490fd5b9060ff60405192548181168452818160081c166020850152818160101c166040850152818160181c166060850152818160201c16608085015260281c1660a08301526140f260c083613fdf565b565b9060405161410181613fc4565b606060ff60038395614112816140a5565b8552614120600182016140a5565b6020860152614131600282016140a5565b60408601520154161515910152565b91908110156141505760051b0190565b634e487b7160e01b600052603260045260246000fd5b3560ff811681036103755790565b1561417b57565b60405162461bcd60e51b8152602060048201526014602482015273151bdad95b88191bd95cc81b9bdd08195e1a5cdd60621b6044820152606490fd5b8181106141c2575050565b600081556001016141b7565b906141d882614000565b6141e56040519182613fdf565b82815280926141f6601f1991614000565b0190602036910137565b60001981146115405760010190565b8051156141505760200190565b8051600110156141505760400190565b80518210156141505760209160051b010190565b60ff60019116019060ff821161154057565b9060ff8091169116019060ff821161154057565b9190820180921161154057565b6040519061428082613fc4565b600060608360c06040516142948282613fdf565b8136823782526040516142a78282613fdf565b813682376020830152604051906142be8183613fdf565b36823760408201520152565b156142d157565b60405162461bcd60e51b815260206004820152601660248201527521b0b73737ba1031b7b73732b1ba103a379039b2b63360511b6044820152606490fd5b1561431657565b60405162461bcd60e51b8152602060048201526015602482015274546f6b656e2069732069676e6f72696e6720796f7560581b6044820152606490fd5b6001600160a01b0381161515806144b8575b614467576000838152673ec412a9852d173d60c11b3317601c52602090208301830180546001600160a01b0393841693928316928116808414810215614452575082600052816001018054803314853314171561443a575b614430575b50838318189055601c600c20600019815401905581600052601c600c2060018154019063ffffffff821684021561441b57557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef600080a4565b67ea553b3401336cea841560021b526004601cfd5b60009055386143c2565b6030600c20546143bd57634b6e7f186000526004601cfd5b67ceea21b6a1148100901560021b526004601cfd5b60405162461bcd60e51b815260206004820152602360248201527f546f6b656e206973206c6f636b65642e20436f6e6e65637420746f20756e6c6f60448201526231b59760e91b6064820152608490fd5b506144d78360ff16600052600960205260ff6003604060002001541690565b15614365565b90600182811c9216801561450d575b60208310146144f757565b634e487b7160e01b600052602260045260246000fd5b91607f16916144ec565b6000818152673ec412a9852d173d60c11b601c5260209020810101546001600160a01b0316908115613bc357565b801561456c57673ec412a9852d173d60c11b601c5260005263ffffffff601c600c20541690565b638f4eb6046000526004601cfd5b600054906001820180921161154057612982816145979333614aa9565b600055565b60ff1660ff81146115405760010190565b6000818152673ec412a9852d173d60c11b601c52602090208101015460601b151590565b9060068110156141505760051b0190565b919060005b60ff811660068110156146225761460460ff9160208701516145d1565b511660ff83161461461a5760010160ff166145e7565b506001925050565b50600093505050565b9091614635614273565b5060005b60ff811660068110156146b85760ff614657602086019283516145d1565b511660ff86161461466e575060010160ff16614639565b919293505b60ff811660058110156146ab5760ff916001916146a38461469987518261269c87614240565b51169186516145d1565b520116614673565b505051600060a090910152565b5050915090565b9190916146ca614273565b5060005b60ff8116600681101561473e576146e860ff9184516145d1565b511660ff8516146146fe5760010160ff166146ce565b909192505b60ff811660058110156147315760ff916001916147298461469987518261269c87614240565b520116614703565b5050600060a08251015290565b5090925050565b9061475460ff6129fb84614c80565b90600091602060009401935b60ff811660068110156147b85760ff91818361477f6001948a516145d1565b511661478e575b500116614760565b61479a849189516145d1565b51166147b1846147a98961459c565b98168661422c565b5238614786565b5050915091506147c781614dc0565b90565b906147d960ff6129fb84614e6f565b9060009160005b60ff811660068110156147b85760ff9181836147ff6001948a516145d1565b511661480e575b5001166147e0565b61481a849189516145d1565b5116614829846147a98961459c565b5238614806565b919060005b60ff811660068110156146225761485260ff9160408701516145d1565b511660ff83161461461a5760010160ff16614835565b1561486f57565b60405162461bcd60e51b815260206004820152601060248201526f125b9d985b1a59081d1bdad95b88125160821b6044820152606490fd5b156148ae57565b60405162461bcd60e51b815260206004820152601760248201527f4d617820636f6e6e656374696f6e7320726561636865640000000000000000006044820152606490fd5b9190916148fe614273565b5061491860ff841693614912851515614868565b826145e2565b6149875761493f600660ff61493861492f85614e6f565b610e8286614c80565b16106148a7565b60005b60ff8116600681101561473e576020830160ff6149608383516145d1565b51161561497457505060010160ff16614942565b614983925094929394516145d1565b5290565b60405162461bcd60e51b815260206004820152601f60248201527f416c726561647920686173206f7574626f756e6420636f6e6e656374696f6e006044820152606490fd5b9190916149d7614273565b506149f160ff8416936149eb851515614868565b82614c0d565b614a4757614a08600660ff61493861492f85614e6f565b60005b60ff8116600681101561473e5760ff614a258285516145d1565b511615614a38575060010160ff16614a0b565b614983915082939492516145d1565b60405162461bcd60e51b815260206004820152601e60248201527f416c72656164792068617320696e626f756e6420636f6e6e656374696f6e00006044820152606490fd5b638b78c6d819543303614a9b57565b6382b429006000526004601cfd5b91929092835b614ab98286614266565b811015614b71576000818152673ec412a9852d173d60c11b601c52602090208101810180546001600160a01b0386169190606081901b614b63578217905580600052601c600c2060018154019063ffffffff8216830215614b4e5755614ab99291600191819060007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8180a401909150614aaf565b67ea553b3401336cea831560021b526004601cfd5b63c991cbb16000526004601cfd5b5050915050565b9060a46020939460405195869463150b7a028652338787015260018060a01b03166040860152606085015260808085015280518091818060a0880152614bf9575b505001906000601c8401915af115614bea575b5163757a42ff60e11b01614bdc57565b63d1a57ed66000526004601cfd5b3d15614bcc573d6000823e3d90fd5b818760c08801920160045afa508038614bb9565b919060005b60ff8116600681101561462257614c2c60ff9186516145d1565b511660ff83161461461a5760010160ff16614c12565b60018060a01b031680638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3638b78c6d81955565b60009160005b60ff81166006811015614ccc57614ca360ff9160208601516145d1565b5116614cb5575b60010160ff16614c86565b926001614cc360ff9261459c565b94915050614caa565b50509050565b919091614cdd614273565b50614cf760ff841693614cf1851515614868565b82614830565b614d755760005b60ff81166006811015614d30576040830160ff614d1c8383516145d1565b51161561497457505060010160ff16614cfe565b60405162461bcd60e51b815260206004820152601960248201527f4d61782069676e6f726564206c696d69742072656163686564000000000000006044820152606490fd5b60405162461bcd60e51b815260206004820152601060248201526f416c72656164792069676e6f72696e6760801b6044820152606490fd5b60ff6000199116019060ff821161154057565b906001915b805160ff841690811015614ccc57614ddf60ff918361422c565b511691835b60ff811690811590811580614e4f575b15614e2957614e0f60ff614e088193614dad565b168661422c565b5116614e1b838661422c565b526115405760001901614de4565b614e4894959250614e42915060ff90969396168561422c565b5261459c565b9190614dc5565b508560ff614e6781614e6085614dad565b168861422c565b511611614df4565b60009160005b60ff81166006811015614ccc57614e8f60ff9185516145d1565b5116614ea1575b60010160ff16614e75565b926001614eaf60ff9261459c565b94915050614e9656fec65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8832a253ad4e9e88f705006a24d9957b8aa1de307a0f9d0a6ad5fd0b0ac810505f8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce73ebdd074970314b7ecf93f5dc8b82ec796fcc71c749d2d2e7f2ff99e92af9934a2646970667358221220ab0d6ff1ee95bbd7cf900d4aa89ddd1bf1cbd565171b4b179a1830003119308c64736f6c634300081c0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000fff9a1c9fcae8f2791c91a24b9f0ff6d9c467337
-----Decoded View---------------
Arg [0] : _owner (address): 0xfFf9A1c9fcAe8f2791c91a24B9F0ff6D9c467337
-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 000000000000000000000000fff9a1c9fcae8f2791c91a24b9f0ff6d9c467337
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.