// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @title DN404
/// @notice DN404 is a hybrid ERC20 and ERC721 implementation that mints
/// and burns NFTs based on an account's ERC20 token balance.
///
/// @author vectorized.eth (@optimizoor)
/// @author Quit (@0xQuit)
/// @author Michael Amadi (@AmadiMichaels)
/// @author cygaar (@0xCygaar)
/// @author Thomas (@0xjustadev)
/// @author Harrison (@PopPunkOnChain)
///
/// @dev Note:
/// - The ERC721 data is stored in this base DN404 contract, however a
/// DN404Mirror contract ***MUST*** be deployed and linked during
/// initialization.
/// - For ERC20 transfers, the most recently acquired NFT will be burned / transferred out first.
/// - A unit worth of ERC20 tokens equates to a deed to one NFT token.
/// The skip NFT status determines if this deed is automatically exercised.
/// An account can configure their skip NFT status.
/// * If `getSkipNFT(owner) == true`, ERC20 mints / transfers to `owner`
/// will NOT trigger NFT mints / transfers to `owner` (i.e. deeds are left unexercised).
/// * If `getSkipNFT(owner) == false`, ERC20 mints / transfers to `owner`
/// will trigger NFT mints / transfers to `owner`, until the NFT balance of `owner`
/// is equal to its ERC20 balance divided by the unit (rounded down).
/// - Invariant: `mirror.balanceOf(owner) <= base.balanceOf(owner) / _unit()`.
/// - The gas costs for automatic minting / transferring / burning of NFTs is O(n).
/// This can exceed the block gas limit.
/// Applications and users may need to break up large transfers into a few transactions.
/// - This implementation does not support "safe" transfers for automatic NFT transfers.
/// - The ERC20 token allowances and ERC721 token / operator approvals are separate.
/// - For MEV safety, users should NOT have concurrently open orders for the ERC20 and ERC721.
abstract contract DN404 {
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* ENUMS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Denotes the default skip NFT status.
/// Override `_skipNFTDefault` to return a preferred value (defaults to `HasCode`).
enum SkipNFTDefault {
// `On` if the ERC20 owner has code, else `Off`.
HasCode,
// Skip NFT (ERC20 transfers will NOT trigger NFT minting / burning / transferring).
On,
// NOT skip NFT (ERC20 transfers will trigger NFT minting / burning / transferring).
Off
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* EVENTS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 amount);
/// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
event Approval(address indexed owner, address indexed spender, uint256 amount);
/// @dev Emitted when `owner` sets their skipNFT flag to `status`.
event SkipNFTSet(address indexed owner, bool status);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/// @dev `keccak256(bytes("SkipNFTSet(address,bool)"))`.
uint256 private constant _SKIP_NFT_SET_EVENT_SIGNATURE =
0xb5a1de456fff688115a4f75380060c23c8532d14ff85f687cc871456d6420393;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CUSTOM ERRORS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Thrown when attempting to double-initialize the contract.
error DNAlreadyInitialized();
/// @dev The function can only be called after the contract has been initialized.
error DNNotInitialized();
/// @dev Thrown when attempting to transfer or burn more tokens than sender's balance.
error InsufficientBalance();
/// @dev Thrown when a spender attempts to transfer tokens with an insufficient allowance.
error InsufficientAllowance();
/// @dev Thrown when minting an amount of tokens that would overflow the max tokens.
error TotalSupplyOverflow();
/// @dev The unit must be greater than zero and less than `2**96`.
error InvalidUnit();
/// @dev Thrown when the caller for a fallback NFT function is not the mirror contract.
error SenderNotMirror();
/// @dev Thrown when attempting to transfer tokens to the zero address.
error TransferToZeroAddress();
/// @dev Thrown when the mirror address provided for initialization is the zero address.
error MirrorAddressIsZero();
/// @dev Thrown when the link call to the mirror contract reverts.
error LinkMirrorContractFailed();
/// @dev Thrown when setting an NFT token approval
/// and the caller is not the owner or an approved operator.
error ApprovalCallerNotOwnerNorApproved();
/// @dev Thrown when transferring an NFT
/// and the caller is not the owner or an approved operator.
error TransferCallerNotOwnerNorApproved();
/// @dev Thrown when transferring an NFT and the from address is not the current owner.
error TransferFromIncorrectOwner();
/// @dev Thrown when checking the owner or approved address for a non-existent NFT.
error TokenDoesNotExist();
/// @dev The function selector is not recognized.
error FnSelectorNotRecognized();
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CONSTANTS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev The flag to denote that the skip NFT flag is initialized.
uint8 internal constant _ADDRESS_DATA_SKIP_NFT_INITIALIZED_FLAG = 1 << 0;
/// @dev The flag to denote that the address should skip NFTs.
uint8 internal constant _ADDRESS_DATA_SKIP_NFT_FLAG = 1 << 1;
/// @dev The flag to denote that the address has overridden the default Permit2 allowance.
uint8 internal constant _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG = 1 << 2;
/// @dev The canonical Permit2 address.
/// For signature-based allowance granting for single transaction ERC20 `transferFrom`.
/// Enabled by default. In Permit2 we trust.
/// To disable, override `_givePermit2DefaultInfiniteAllowance()`.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/// @dev The ZKsync Permit2 deployment.
/// If deploying on ZKsync or Abstract, override `_isPermit2(address)` to check against this too.
/// [Etherscan](https://era.zksync.network/address/0x0000000000225e31D15943971F47aD3022F714Fa)
address internal constant _ZKSYNC_PERMIT_2 = 0x0000000000225e31D15943971F47aD3022F714Fa;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* STORAGE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Struct containing an address's token data and settings.
struct AddressData {
// Auxiliary data.
uint88 aux;
// Flags for `initialized` and `skipNFT`.
uint8 flags;
// The alias for the address. Zero means absence of an alias.
uint32 addressAlias;
// The number of NFT tokens.
uint32 ownedLength;
// The token balance in wei.
uint96 balance;
}
/// @dev A uint32 map in storage.
struct Uint32Map {
uint256 spacer;
}
/// @dev A bitmap in storage.
struct Bitmap {
uint256 spacer;
}
/// @dev A struct to wrap a uint256 in storage.
struct Uint256Ref {
uint256 value;
}
/// @dev A mapping of an address pair to a Uint256Ref.
struct AddressPairToUint256RefMap {
uint256 spacer;
}
/// @dev Struct containing the base token contract storage.
struct DN404Storage {
// Current number of address aliases assigned.
uint32 numAliases;
// Next NFT ID to assign for a mint.
uint32 nextTokenId;
// The head of the burned pool.
uint32 burnedPoolHead;
// The tail of the burned pool.
uint32 burnedPoolTail;
// Total number of NFTs in existence.
uint32 totalNFTSupply;
// Total supply of tokens.
uint96 totalSupply;
// Address of the NFT mirror contract.
address mirrorERC721;
// Mapping of a user alias number to their address.
mapping(uint32 => address) aliasToAddress;
// Mapping of user operator approvals for NFTs.
AddressPairToUint256RefMap operatorApprovals;
// Mapping of NFT approvals to approved operators.
mapping(uint256 => address) nftApprovals;
// Bitmap of whether an non-zero NFT approval may exist.
Bitmap mayHaveNFTApproval;
// Bitmap of whether a NFT ID exists. Ignored if `_useExistsLookup()` returns false.
Bitmap exists;
// Mapping of user allowances for ERC20 spenders.
AddressPairToUint256RefMap allowance;
// Mapping of NFT IDs owned by an address.
mapping(address => Uint32Map) owned;
// The pool of burned NFT IDs.
Uint32Map burnedPool;
// Even indices: owner aliases. Odd indices: owned indices.
Uint32Map oo;
// Mapping of user account AddressData.
mapping(address => AddressData) addressData;
}
/// @dev Returns a storage pointer for DN404Storage.
function _getDN404Storage() internal pure virtual returns (DN404Storage storage $) {
/// @solidity memory-safe-assembly
assembly {
// `uint72(bytes9(keccak256("DN404_STORAGE")))`.
$.slot := 0xa20d6e21d0e5255308 // Truncate to 9 bytes to reduce bytecode size.
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INITIALIZER */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Initializes the DN404 contract with an
/// `initialTokenSupply`, `initialTokenOwner` and `mirror` NFT contract address.
///
/// Note: The `initialSupplyOwner` will have their skip NFT status set to true.
function _initializeDN404(
uint256 initialTokenSupply,
address initialSupplyOwner,
address mirror
) internal virtual {
DN404Storage storage $ = _getDN404Storage();
unchecked {
if (_unit() - 1 >= 2 ** 96 - 1) _rv(uint32(InvalidUnit.selector));
}
if ($.mirrorERC721 != address(0)) _rv(uint32(DNAlreadyInitialized.selector));
if (mirror == address(0)) _rv(uint32(MirrorAddressIsZero.selector));
/// @solidity memory-safe-assembly
assembly {
// Make the call to link the mirror contract.
mstore(0x00, 0x0f4599e5) // `linkMirrorContract(address)`.
mstore(0x20, caller())
if iszero(and(eq(mload(0x00), 1), call(gas(), mirror, 0, 0x1c, 0x24, 0x00, 0x20))) {
mstore(0x00, 0xd125259c) // `LinkMirrorContractFailed()`.
revert(0x1c, 0x04)
}
// Query `owner()` on this contract, and if it is non-zero, call `pullOwner()` on the mirror.
// This allows for any Ownable (e.g. OpenZeppelin, Solady).
mstore(0x00, 0x8da5cb5b6cef16e6) // `owner()` and `pullOwner()`.
if and(
lt(iszero(shl(96, mload(0x20))), gt(returndatasize(), 0x1f)),
staticcall(gas(), address(), 0x18, 0x04, 0x20, 0x20)
) { if iszero(call(gas(), mirror, 0, 0x1c, 0x04, 0x00, 0x00)) { revert(0x00, 0x00) } }
}
$.nextTokenId = uint32(_toUint(_useOneIndexed()));
$.mirrorERC721 = mirror;
if (initialTokenSupply != 0) {
if (initialSupplyOwner == address(0)) _rv(uint32(TransferToZeroAddress.selector));
if (_totalSupplyOverflows(initialTokenSupply)) {
_rv(uint32(TotalSupplyOverflow.selector));
}
$.totalSupply = uint96(initialTokenSupply);
AddressData storage initialOwnerAddressData = $.addressData[initialSupplyOwner];
initialOwnerAddressData.balance = uint96(initialTokenSupply);
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, initialTokenSupply)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, initialSupplyOwner)))
}
_setSkipNFT(initialSupplyOwner, true);
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* BASE UNIT FUNCTION TO OVERRIDE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Amount of token balance that is equal to one NFT.
///
/// Note: The return value MUST be kept constant after `_initializeDN404` is called.
function _unit() internal view virtual returns (uint256) {
return 10 ** 18;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* METADATA FUNCTIONS TO OVERRIDE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the name of the token.
function name() public view virtual returns (string memory);
/// @dev Returns the symbol of the token.
function symbol() public view virtual returns (string memory);
/// @dev Returns the Uniform Resource Identifier (URI) for token `id`.
function _tokenURI(uint256 id) internal view virtual returns (string memory);
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CONFIGURABLES */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the default mode for the skip NFT status.
function _skipNFTDefault() internal view virtual returns (SkipNFTDefault) {
return SkipNFTDefault.HasCode;
}
/// @dev Returns whether the tokens IDs are from `[1..n]` instead of `[0..n-1]`.
function _useOneIndexed() internal pure virtual returns (bool) {
return true;
}
/// @dev Returns if direct NFT transfers should be used during ERC20 transfers
/// whenever possible, instead of burning and re-minting.
function _useDirectTransfersIfPossible() internal view virtual returns (bool) {
return true;
}
/// @dev Returns if burns should be added to the burn pool.
/// This returns false by default, which means the NFT IDs are re-minted in a cycle.
function _addToBurnedPool(uint256 totalNFTSupplyAfterBurn, uint256 totalSupplyAfterBurn)
internal
view
virtual
returns (bool)
{
// Silence unused variable compiler warning.
totalSupplyAfterBurn = totalNFTSupplyAfterBurn;
return false;
}
/// @dev Returns whether to use the exists bitmap for more efficient
/// scanning of an empty token ID slot.
/// Recommended for collections that do not use the burn pool,
/// and are expected to have nearly all possible NFTs materialized.
///
/// Note: The returned value must be constant after initialization.
function _useExistsLookup() internal view virtual returns (bool) {
return true;
}
/// @dev Hook that is called after a batch of NFT transfers.
/// The lengths of `from`, `to`, and `ids` are guaranteed to be the same.
function _afterNFTTransfers(address[] memory from, address[] memory to, uint256[] memory ids)
internal
virtual
{}
/// @dev Override this function to return true if `_afterNFTTransfers` is used.
/// This is to help the compiler avoid producing dead bytecode.
function _useAfterNFTTransfers() internal virtual returns (bool) {}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* ERC20 OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the decimals places of the token. Defaults to 18.
/// Does not affect DN404's internal calculations.
/// Will only affect the frontend UI on most protocols.
function decimals() public view virtual returns (uint8) {
return 18;
}
/// @dev Returns the amount of tokens in existence.
function totalSupply() public view virtual returns (uint256) {
return uint256(_getDN404Storage().totalSupply);
}
/// @dev Returns the amount of tokens owned by `owner`.
function balanceOf(address owner) public view virtual returns (uint256) {
return _getDN404Storage().addressData[owner].balance;
}
/// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
function allowance(address owner, address spender) public view returns (uint256) {
if (_givePermit2DefaultInfiniteAllowance() && _isPermit2(spender)) {
uint8 flags = _getDN404Storage().addressData[owner].flags;
if ((flags & _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG) == uint256(0)) {
return type(uint256).max;
}
}
return _ref(_getDN404Storage().allowance, owner, spender).value;
}
/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
///
/// Emits a {Approval} event.
function approve(address spender, uint256 amount) public virtual returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
/// @dev Transfer `amount` tokens from the caller to `to`.
///
/// Will burn sender NFTs if balance after transfer is less than
/// the amount required to support the current NFT balance.
///
/// Will mint NFTs to `to` if the recipient's new balance supports
/// additional NFTs ***AND*** the `to` address's skipNFT flag is
/// set to false.
///
/// Requirements:
/// - `from` must at least have `amount`.
///
/// Emits a {Transfer} event.
function transfer(address to, uint256 amount) public virtual returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}
/// @dev Transfers `amount` tokens from `from` to `to`.
///
/// Note: Does not update the allowance if it is the maximum uint256 value.
///
/// Will burn sender NFTs if balance after transfer is less than
/// the amount required to support the current NFT balance.
///
/// Will mint NFTs to `to` if the recipient's new balance supports
/// additional NFTs ***AND*** the `to` address's skipNFT flag is
/// set to false.
///
/// Requirements:
/// - `from` must at least have `amount`.
/// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
Uint256Ref storage a = _ref(_getDN404Storage().allowance, from, msg.sender);
uint256 allowed = _givePermit2DefaultInfiniteAllowance() && _isPermit2(msg.sender)
&& (_getDN404Storage().addressData[from].flags & _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG)
== uint256(0) ? type(uint256).max : a.value;
if (allowed != type(uint256).max) {
if (amount > allowed) _rv(uint32(InsufficientAllowance.selector));
unchecked {
a.value = allowed - amount;
}
}
_transfer(from, to, amount);
return true;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* PERMIT2 */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Whether Permit2 has infinite allowances by default for all owners.
/// For signature-based allowance granting for single transaction ERC20 `transferFrom`.
/// To disable, override this function to return false.
///
/// Note: The returned value SHOULD be kept constant after tokens have been minted.
/// If the returned value changes from false to true and vice-versa,
/// it can override the user customized allowances for Permit2 to infinity.
function _givePermit2DefaultInfiniteAllowance() internal view virtual returns (bool) {
return true;
}
/// @dev Returns checks if `sender` is the canonical Permit2 address.
/// If on ZKsync, override this function to check against `_ZKSYNC_PERMIT_2` as well.
function _isPermit2(address sender) internal view virtual returns (bool) {
return sender == _PERMIT2;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL MINT FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Mints `amount` tokens to `to`, increasing the total supply.
///
/// Will mint NFTs to `to` if the recipient's new balance supports
/// additional NFTs ***AND*** the `to` address's skipNFT flag is set to false.
///
/// Note:
/// - May mint more NFTs than `amount / _unit()`.
/// The number of NFTs minted is what is needed to make `to`'s NFT balance whole.
/// - Token IDs wraps back to `_toUint(_useOneIndexed())` upon exceeding the upper limit.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 amount) internal virtual {
if (to == address(0)) _rv(uint32(TransferToZeroAddress.selector));
DN404Storage storage $ = _getDN404Storage();
if ($.mirrorERC721 == address(0)) _rv(uint32(DNNotInitialized.selector));
AddressData storage toAddressData = $.addressData[to];
_DNMintTemps memory t;
unchecked {
{
uint256 toBalance = uint256(toAddressData.balance) + amount;
toAddressData.balance = uint96(toBalance);
t.toEnd = toBalance / _unit();
}
uint256 idLimit;
{
uint256 newTotalSupply = uint256($.totalSupply) + amount;
$.totalSupply = uint96(newTotalSupply);
uint256 overflows = _toUint(_totalSupplyOverflows(newTotalSupply));
if (overflows | _toUint(newTotalSupply < amount) != 0) {
_rv(uint32(TotalSupplyOverflow.selector));
}
idLimit = newTotalSupply / _unit();
}
while (!getSkipNFT(to)) {
Uint32Map storage toOwned = $.owned[to];
Uint32Map storage oo = $.oo;
uint256 toIndex = toAddressData.ownedLength;
if ((t.numNFTMints = _zeroFloorSub(t.toEnd, toIndex)) == uint256(0)) break;
t.packedLogs = _packedLogsMalloc(t.numNFTMints);
_packedLogsSet(t.packedLogs, to, 0);
$.totalNFTSupply += uint32(t.numNFTMints);
toAddressData.ownedLength = uint32(t.toEnd);
t.toAlias = _registerAndResolveAlias(toAddressData, to);
uint32 burnedPoolHead = $.burnedPoolHead;
t.burnedPoolTail = $.burnedPoolTail;
t.nextTokenId = _wrapNFTId($.nextTokenId, idLimit);
// Mint loop.
do {
uint256 id;
if (burnedPoolHead != t.burnedPoolTail) {
id = _get($.burnedPool, burnedPoolHead++);
} else {
id = t.nextTokenId;
while (_get(oo, _ownershipIndex(id)) != 0) {
id = _useExistsLookup()
? _wrapNFTId(_findFirstUnset($.exists, id + 1, idLimit), idLimit)
: _wrapNFTId(id + 1, idLimit);
}
t.nextTokenId = _wrapNFTId(id + 1, idLimit);
}
if (_useExistsLookup()) _set($.exists, id, true);
_set(toOwned, toIndex, uint32(id));
_setOwnerAliasAndOwnedIndex(oo, id, t.toAlias, uint32(toIndex++));
_packedLogsAppend(t.packedLogs, id);
} while (toIndex != t.toEnd);
$.nextTokenId = uint32(t.nextTokenId);
$.burnedPoolHead = burnedPoolHead;
_packedLogsSend(t.packedLogs, $);
break;
}
}
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, to)))
}
if (_useAfterNFTTransfers()) {
_afterNFTTransfers(
_zeroAddresses(t.numNFTMints),
_filled(t.numNFTMints, to),
_packedLogsIds(t.packedLogs)
);
}
}
/// @dev Mints `amount` tokens to `to`, increasing the total supply.
/// This variant mints NFT tokens starting from ID
/// `preTotalSupply / _unit() + _toUint(_useOneIndexed())`.
/// The `nextTokenId` will not be changed.
/// If any NFTs are minted, the burned pool will be invalidated (emptied).
///
/// Will mint NFTs to `to` if the recipient's new balance supports
/// additional NFTs ***AND*** the `to` address's skipNFT flag is set to false.
///
/// Note:
/// - May mint more NFTs than `amount / _unit()`.
/// The number of NFTs minted is what is needed to make `to`'s NFT balance whole.
/// - Token IDs wraps back to `_toUint(_useOneIndexed())` upon exceeding the upper limit.
///
/// Emits a {Transfer} event.
function _mintNext(address to, uint256 amount) internal virtual {
if (to == address(0)) _rv(uint32(TransferToZeroAddress.selector));
DN404Storage storage $ = _getDN404Storage();
if ($.mirrorERC721 == address(0)) _rv(uint32(DNNotInitialized.selector));
AddressData storage toAddressData = $.addressData[to];
_DNMintTemps memory t;
unchecked {
{
uint256 toBalance = uint256(toAddressData.balance) + amount;
toAddressData.balance = uint96(toBalance);
t.toEnd = toBalance / _unit();
}
uint256 id;
uint256 idLimit;
{
uint256 preTotalSupply = uint256($.totalSupply);
uint256 newTotalSupply = uint256(preTotalSupply) + amount;
$.totalSupply = uint96(newTotalSupply);
uint256 overflows = _toUint(_totalSupplyOverflows(newTotalSupply));
if (overflows | _toUint(newTotalSupply < amount) != 0) {
_rv(uint32(TotalSupplyOverflow.selector));
}
idLimit = newTotalSupply / _unit();
id = _wrapNFTId(preTotalSupply / _unit() + _toUint(_useOneIndexed()), idLimit);
}
while (!getSkipNFT(to)) {
Uint32Map storage toOwned = $.owned[to];
Uint32Map storage oo = $.oo;
uint256 toIndex = toAddressData.ownedLength;
if ((t.numNFTMints = _zeroFloorSub(t.toEnd, toIndex)) == uint256(0)) break;
t.packedLogs = _packedLogsMalloc(t.numNFTMints);
// Invalidate (empty) the burned pool.
$.burnedPoolHead = 0;
$.burnedPoolTail = 0;
_packedLogsSet(t.packedLogs, to, 0);
$.totalNFTSupply += uint32(t.numNFTMints);
toAddressData.ownedLength = uint32(t.toEnd);
t.toAlias = _registerAndResolveAlias(toAddressData, to);
// Mint loop.
do {
while (_get(oo, _ownershipIndex(id)) != 0) {
id = _useExistsLookup()
? _wrapNFTId(_findFirstUnset($.exists, id + 1, idLimit), idLimit)
: _wrapNFTId(id + 1, idLimit);
}
if (_useExistsLookup()) _set($.exists, id, true);
_set(toOwned, toIndex, uint32(id));
_setOwnerAliasAndOwnedIndex(oo, id, t.toAlias, uint32(toIndex++));
_packedLogsAppend(t.packedLogs, id);
id = _wrapNFTId(id + 1, idLimit);
} while (toIndex != t.toEnd);
_packedLogsSend(t.packedLogs, $);
break;
}
}
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, to)))
}
if (_useAfterNFTTransfers()) {
_afterNFTTransfers(
_zeroAddresses(t.numNFTMints),
_filled(t.numNFTMints, to),
_packedLogsIds(t.packedLogs)
);
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL BURN FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Burns `amount` tokens from `from`, reducing the total supply.
///
/// Will burn sender NFTs if balance after transfer is less than
/// the amount required to support the current NFT balance.
///
/// Emits a {Transfer} event.
function _burn(address from, uint256 amount) internal virtual {
DN404Storage storage $ = _getDN404Storage();
if ($.mirrorERC721 == address(0)) _rv(uint32(DNNotInitialized.selector));
AddressData storage fromAddressData = $.addressData[from];
_DNBurnTemps memory t;
unchecked {
t.fromBalance = fromAddressData.balance;
if (amount > t.fromBalance) _rv(uint32(InsufficientBalance.selector));
fromAddressData.balance = uint96(t.fromBalance -= amount);
t.totalSupply = uint256($.totalSupply) - amount;
$.totalSupply = uint96(t.totalSupply);
Uint32Map storage fromOwned = $.owned[from];
uint256 fromIndex = fromAddressData.ownedLength;
t.numNFTBurns = _zeroFloorSub(fromIndex, t.fromBalance / _unit());
if (t.numNFTBurns != 0) {
t.packedLogs = _packedLogsMalloc(t.numNFTBurns);
_packedLogsSet(t.packedLogs, from, 1);
bool addToBurnedPool;
{
uint256 totalNFTSupply = uint256($.totalNFTSupply) - t.numNFTBurns;
$.totalNFTSupply = uint32(totalNFTSupply);
addToBurnedPool = _addToBurnedPool(totalNFTSupply, t.totalSupply);
}
Uint32Map storage oo = $.oo;
uint256 fromEnd = fromIndex - t.numNFTBurns;
fromAddressData.ownedLength = uint32(fromEnd);
uint32 burnedPoolTail = $.burnedPoolTail;
// Burn loop.
do {
uint256 id = _get(fromOwned, --fromIndex);
_setOwnerAliasAndOwnedIndex(oo, id, 0, 0);
_packedLogsAppend(t.packedLogs, id);
if (_useExistsLookup()) _set($.exists, id, false);
if (addToBurnedPool) _set($.burnedPool, burnedPoolTail++, uint32(id));
if (_get($.mayHaveNFTApproval, id)) {
_set($.mayHaveNFTApproval, id, false);
delete $.nftApprovals[id];
}
} while (fromIndex != fromEnd);
if (addToBurnedPool) $.burnedPoolTail = burnedPoolTail;
_packedLogsSend(t.packedLogs, $);
}
}
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0)
}
if (_useAfterNFTTransfers()) {
_afterNFTTransfers(
_filled(t.numNFTBurns, from),
_zeroAddresses(t.numNFTBurns),
_packedLogsIds(t.packedLogs)
);
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL TRANSFER FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Moves `amount` of tokens from `from` to `to`.
///
/// Will burn sender NFTs if balance after transfer is less than
/// the amount required to support the current NFT balance.
///
/// Will mint NFTs to `to` if the recipient's new balance supports
/// additional NFTs ***AND*** the `to` address's skipNFT flag is
/// set to false.
///
/// Emits a {Transfer} event.
function _transfer(address from, address to, uint256 amount) internal virtual {
if (to == address(0)) _rv(uint32(TransferToZeroAddress.selector));
DN404Storage storage $ = _getDN404Storage();
AddressData storage fromAddressData = $.addressData[from];
AddressData storage toAddressData = $.addressData[to];
if ($.mirrorERC721 == address(0)) _rv(uint32(DNNotInitialized.selector));
_DNTransferTemps memory t;
t.fromOwnedLength = fromAddressData.ownedLength;
t.toOwnedLength = toAddressData.ownedLength;
unchecked {
{
uint256 fromBalance = fromAddressData.balance;
if (amount > fromBalance) _rv(uint32(InsufficientBalance.selector));
fromAddressData.balance = uint96(fromBalance -= amount);
uint256 toBalance = uint256(toAddressData.balance) + amount;
toAddressData.balance = uint96(toBalance);
t.numNFTBurns = _zeroFloorSub(t.fromOwnedLength, fromBalance / _unit());
if (!getSkipNFT(to)) {
if (from == to) t.toOwnedLength = t.fromOwnedLength - t.numNFTBurns;
t.numNFTMints = _zeroFloorSub(toBalance / _unit(), t.toOwnedLength);
}
}
while (_useDirectTransfersIfPossible()) {
uint256 n = _min(t.fromOwnedLength, _min(t.numNFTBurns, t.numNFTMints));
if (n == uint256(0)) break;
t.numNFTBurns -= n;
t.numNFTMints -= n;
if (from == to) {
t.toOwnedLength += n;
break;
}
t.directLogs = _directLogsMalloc(n, from, to);
Uint32Map storage fromOwned = $.owned[from];
Uint32Map storage toOwned = $.owned[to];
t.toAlias = _registerAndResolveAlias(toAddressData, to);
uint256 toIndex = t.toOwnedLength;
n = toIndex + n;
// Direct transfer loop.
do {
uint256 id = _get(fromOwned, --t.fromOwnedLength);
_set(toOwned, toIndex, uint32(id));
_setOwnerAliasAndOwnedIndex($.oo, id, t.toAlias, uint32(toIndex));
_directLogsAppend(t.directLogs, id);
if (_get($.mayHaveNFTApproval, id)) {
_set($.mayHaveNFTApproval, id, false);
delete $.nftApprovals[id];
}
} while (++toIndex != n);
toAddressData.ownedLength = uint32(t.toOwnedLength = toIndex);
fromAddressData.ownedLength = uint32(t.fromOwnedLength);
break;
}
t.totalNFTSupply = uint256($.totalNFTSupply) + t.numNFTMints - t.numNFTBurns;
$.totalNFTSupply = uint32(t.totalNFTSupply);
Uint32Map storage oo = $.oo;
t.packedLogs = _packedLogsMalloc(t.numNFTBurns + t.numNFTMints);
t.burnedPoolTail = $.burnedPoolTail;
if (t.numNFTBurns != 0) {
_packedLogsSet(t.packedLogs, from, 1);
bool addToBurnedPool = _addToBurnedPool(t.totalNFTSupply, $.totalSupply);
Uint32Map storage fromOwned = $.owned[from];
uint256 fromIndex = t.fromOwnedLength;
fromAddressData.ownedLength = uint32(t.fromEnd = fromIndex - t.numNFTBurns);
uint32 burnedPoolTail = t.burnedPoolTail;
// Burn loop.
do {
uint256 id = _get(fromOwned, --fromIndex);
_setOwnerAliasAndOwnedIndex(oo, id, 0, 0);
_packedLogsAppend(t.packedLogs, id);
if (_useExistsLookup()) _set($.exists, id, false);
if (addToBurnedPool) _set($.burnedPool, burnedPoolTail++, uint32(id));
if (_get($.mayHaveNFTApproval, id)) {
_set($.mayHaveNFTApproval, id, false);
delete $.nftApprovals[id];
}
} while (fromIndex != t.fromEnd);
if (addToBurnedPool) $.burnedPoolTail = (t.burnedPoolTail = burnedPoolTail);
}
if (t.numNFTMints != 0) {
_packedLogsSet(t.packedLogs, to, 0);
Uint32Map storage toOwned = $.owned[to];
t.toAlias = _registerAndResolveAlias(toAddressData, to);
uint256 idLimit = $.totalSupply / _unit();
t.nextTokenId = _wrapNFTId($.nextTokenId, idLimit);
uint256 toIndex = t.toOwnedLength;
toAddressData.ownedLength = uint32(t.toEnd = toIndex + t.numNFTMints);
uint32 burnedPoolHead = $.burnedPoolHead;
// Mint loop.
do {
uint256 id;
if (burnedPoolHead != t.burnedPoolTail) {
id = _get($.burnedPool, burnedPoolHead++);
} else {
id = t.nextTokenId;
while (_get(oo, _ownershipIndex(id)) != 0) {
id = _useExistsLookup()
? _wrapNFTId(_findFirstUnset($.exists, id + 1, idLimit), idLimit)
: _wrapNFTId(id + 1, idLimit);
}
t.nextTokenId = _wrapNFTId(id + 1, idLimit);
}
if (_useExistsLookup()) _set($.exists, id, true);
_set(toOwned, toIndex, uint32(id));
_setOwnerAliasAndOwnedIndex(oo, id, t.toAlias, uint32(toIndex++));
_packedLogsAppend(t.packedLogs, id);
} while (toIndex != t.toEnd);
$.burnedPoolHead = burnedPoolHead;
$.nextTokenId = uint32(t.nextTokenId);
}
if (t.directLogs != bytes32(0)) _directLogsSend(t.directLogs, $);
if (t.packedLogs != bytes32(0)) _packedLogsSend(t.packedLogs, $);
}
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, amount)
// forgefmt: disable-next-item
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), shr(96, shl(96, to)))
}
if (_useAfterNFTTransfers()) {
uint256[] memory ids = _directLogsIds(t.directLogs);
unchecked {
_afterNFTTransfers(
_concat(
_filled(ids.length + t.numNFTBurns, from), _zeroAddresses(t.numNFTMints)
),
_concat(
_concat(_filled(ids.length, to), _zeroAddresses(t.numNFTBurns)),
_filled(t.numNFTMints, to)
),
_concat(ids, _packedLogsIds(t.packedLogs))
);
}
}
}
/// @dev Transfers token `id` from `from` to `to`.
/// Also emits an ERC721 {Transfer} event on the `mirrorERC721`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - `msgSender` must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _initiateTransferFromNFT(address from, address to, uint256 id, address msgSender)
internal
virtual
{
// Emit ERC721 {Transfer} event.
// We do this before the `_transferFromNFT`, as `_transferFromNFT` may use
// the `_afterNFTTransfers` hook, which may trigger more transfers.
// This helps keeps the sequence of emitted events consistent.
// Since `mirrorERC721` is a trusted contract, we can do this.
bytes32 directLogs = _directLogsMalloc(1, from, to);
_directLogsAppend(directLogs, id);
_directLogsSend(directLogs, _getDN404Storage());
_transferFromNFT(from, to, id, msgSender);
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// This function will be called when a ERC721 transfer is made on the mirror contract.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - `msgSender` must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _transferFromNFT(address from, address to, uint256 id, address msgSender)
internal
virtual
{
if (to == address(0)) _rv(uint32(TransferToZeroAddress.selector));
DN404Storage storage $ = _getDN404Storage();
if ($.mirrorERC721 == address(0)) _rv(uint32(DNNotInitialized.selector));
Uint32Map storage oo = $.oo;
if (from != $.aliasToAddress[_get(oo, _ownershipIndex(_restrictNFTId(id)))]) {
_rv(uint32(TransferFromIncorrectOwner.selector));
}
if (msgSender != from) {
if (!_isApprovedForAll(from, msgSender)) {
if (_getApproved(id) != msgSender) {
_rv(uint32(TransferCallerNotOwnerNorApproved.selector));
}
}
}
AddressData storage fromAddressData = $.addressData[from];
AddressData storage toAddressData = $.addressData[to];
uint256 unit = _unit();
mapping(address => Uint32Map) storage owned = $.owned;
unchecked {
uint256 fromBalance = fromAddressData.balance;
if (unit > fromBalance) _rv(uint32(InsufficientBalance.selector));
fromAddressData.balance = uint96(fromBalance - unit);
toAddressData.balance += uint96(unit);
}
if (_get($.mayHaveNFTApproval, id)) {
_set($.mayHaveNFTApproval, id, false);
delete $.nftApprovals[id];
}
unchecked {
Uint32Map storage fromOwned = owned[from];
uint32 updatedId = _get(fromOwned, --fromAddressData.ownedLength);
uint32 i = _get(oo, _ownedIndex(id));
_set(fromOwned, i, updatedId);
_set(oo, _ownedIndex(updatedId), i);
}
unchecked {
uint32 n = toAddressData.ownedLength++;
_set(owned[to], n, uint32(id));
_setOwnerAliasAndOwnedIndex(oo, id, _registerAndResolveAlias(toAddressData, to), n);
}
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, unit)
// forgefmt: disable-next-item
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), shr(96, shl(96, to)))
}
if (_useAfterNFTTransfers()) {
_afterNFTTransfers(_filled(1, from), _filled(1, to), _filled(1, id));
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL APPROVE FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`.
///
/// Emits a {Approval} event.
function _approve(address owner, address spender, uint256 amount) internal virtual {
if (_givePermit2DefaultInfiniteAllowance() && _isPermit2(spender)) {
_getDN404Storage().addressData[owner].flags |= _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG;
}
_ref(_getDN404Storage().allowance, owner, spender).value = amount;
/// @solidity memory-safe-assembly
assembly {
// Emit the {Approval} event.
mstore(0x00, amount)
// forgefmt: disable-next-item
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, shl(96, owner)), shr(96, shl(96, spender)))
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* DATA HITCHHIKING FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the auxiliary data for `owner`.
/// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
/// Auxiliary data can be set for any address, even if it does not have any tokens.
function _getAux(address owner) internal view virtual returns (uint88) {
return _getDN404Storage().addressData[owner].aux;
}
/// @dev Set the auxiliary data for `owner` to `value`.
/// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
/// Auxiliary data can be set for any address, even if it does not have any tokens.
function _setAux(address owner, uint88 value) internal virtual {
_getDN404Storage().addressData[owner].aux = value;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* SKIP NFT FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns true if minting and transferring ERC20s to `owner` will skip minting NFTs.
/// Returns false otherwise.
function getSkipNFT(address owner) public view virtual returns (bool result) {
uint8 flags = _getDN404Storage().addressData[owner].flags;
result = flags & _ADDRESS_DATA_SKIP_NFT_FLAG != 0;
if (flags & _ADDRESS_DATA_SKIP_NFT_INITIALIZED_FLAG == uint256(0)) {
if (_skipNFTDefault() == SkipNFTDefault.HasCode) {
/// @solidity memory-safe-assembly
assembly {
result := iszero(iszero(extcodesize(owner)))
}
}
if (_skipNFTDefault() == SkipNFTDefault.On) result = true;
if (_skipNFTDefault() == SkipNFTDefault.Off) result = false;
}
}
/// @dev Sets the caller's skipNFT flag to `skipNFT`. Returns true.
///
/// Emits a {SkipNFTSet} event.
function setSkipNFT(bool skipNFT) public virtual returns (bool) {
_setSkipNFT(msg.sender, skipNFT);
return true;
}
/// @dev Internal function to set account `owner` skipNFT flag to `state`
///
/// Initializes account `owner` AddressData if it is not currently initialized.
///
/// Emits a {SkipNFTSet} event.
function _setSkipNFT(address owner, bool state) internal virtual {
AddressData storage d = _getDN404Storage().addressData[owner];
uint8 flags = d.flags;
/// @solidity memory-safe-assembly
assembly {
let s := xor(iszero(and(flags, _ADDRESS_DATA_SKIP_NFT_FLAG)), iszero(state))
flags := xor(mul(_ADDRESS_DATA_SKIP_NFT_FLAG, s), flags)
flags := or(_ADDRESS_DATA_SKIP_NFT_INITIALIZED_FLAG, flags)
mstore(0x00, iszero(iszero(state)))
log2(0x00, 0x20, _SKIP_NFT_SET_EVENT_SIGNATURE, shr(96, shl(96, owner)))
}
d.flags = flags;
}
/// @dev Returns the `addressAlias` of account `to`.
///
/// Assigns and registers the next alias if `to` alias was not previously registered.
function _registerAndResolveAlias(AddressData storage toAddressData, address to)
internal
virtual
returns (uint32 addressAlias)
{
DN404Storage storage $ = _getDN404Storage();
addressAlias = toAddressData.addressAlias;
if (addressAlias == uint256(0)) {
unchecked {
addressAlias = ++$.numAliases;
}
toAddressData.addressAlias = addressAlias;
$.aliasToAddress[addressAlias] = to;
if (addressAlias == uint256(0)) revert(); // Overflow.
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* MIRROR OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the address of the mirror NFT contract.
function mirrorERC721() public view virtual returns (address) {
return _getDN404Storage().mirrorERC721;
}
/// @dev Returns the total NFT supply.
function _totalNFTSupply() internal view virtual returns (uint256) {
return _getDN404Storage().totalNFTSupply;
}
/// @dev Returns `owner` NFT balance.
function _balanceOfNFT(address owner) internal view virtual returns (uint256) {
return _getDN404Storage().addressData[owner].ownedLength;
}
/// @dev Returns the owner of token `id`.
/// Returns the zero address instead of reverting if the token does not exist.
function _ownerAt(uint256 id) internal view virtual returns (address) {
DN404Storage storage $ = _getDN404Storage();
return $.aliasToAddress[_get($.oo, _ownershipIndex(_restrictNFTId(id)))];
}
/// @dev Returns the owner of token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function _ownerOf(uint256 id) internal view virtual returns (address) {
if (!_exists(id)) _rv(uint32(TokenDoesNotExist.selector));
return _ownerAt(id);
}
/// @dev Returns whether `operator` is approved to manage the NFT tokens of `owner`.
function _isApprovedForAll(address owner, address operator)
internal
view
virtual
returns (bool)
{
return _ref(_getDN404Storage().operatorApprovals, owner, operator).value != 0;
}
/// @dev Returns if token `id` exists.
function _exists(uint256 id) internal view virtual returns (bool) {
return _ownerAt(id) != address(0);
}
/// @dev Returns the account approved to manage token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function _getApproved(uint256 id) internal view virtual returns (address) {
if (!_exists(id)) _rv(uint32(TokenDoesNotExist.selector));
return _getDN404Storage().nftApprovals[id];
}
/// @dev Sets `spender` as the approved account to manage token `id`, using `msgSender`.
///
/// Requirements:
/// - `msgSender` must be the owner or an approved operator for the token owner.
function _approveNFT(address spender, uint256 id, address msgSender)
internal
virtual
returns (address owner)
{
DN404Storage storage $ = _getDN404Storage();
owner = $.aliasToAddress[_get($.oo, _ownershipIndex(_restrictNFTId(id)))];
if (msgSender != owner) {
if (!_isApprovedForAll(owner, msgSender)) {
_rv(uint32(ApprovalCallerNotOwnerNorApproved.selector));
}
}
$.nftApprovals[id] = spender;
_set($.mayHaveNFTApproval, id, spender != address(0));
}
/// @dev Approve or remove the `operator` as an operator for `msgSender`,
/// without authorization checks.
function _setApprovalForAll(address operator, bool approved, address msgSender)
internal
virtual
{
// For efficiency, we won't check if `operator` isn't `address(0)` (practically a no-op).
_ref(_getDN404Storage().operatorApprovals, msgSender, operator).value = _toUint(approved);
}
/// @dev Returns the NFT IDs of `owner` in the range `[begin..end)` (exclusive of `end`).
/// `begin` and `end` are indices in the owner's token ID array, not the entire token range.
/// Optimized for smaller bytecode size, as this function is intended for off-chain calling.
function _ownedIds(address owner, uint256 begin, uint256 end)
internal
view
virtual
returns (uint256[] memory ids)
{
DN404Storage storage $ = _getDN404Storage();
Uint32Map storage owned = $.owned[owner];
end = _min($.addressData[owner].ownedLength, end);
/// @solidity memory-safe-assembly
assembly {
ids := mload(0x40)
let i := begin
for {} lt(i, end) { i := add(i, 1) } {
let s := add(shl(96, owned.slot), shr(3, i)) // Storage slot.
let id := and(0xffffffff, shr(shl(5, and(i, 7)), sload(s)))
mstore(add(add(ids, 0x20), shl(5, sub(i, begin))), id) // Append to.
}
mstore(ids, sub(i, begin)) // Store the length.
mstore(0x40, add(add(ids, 0x20), shl(5, sub(i, begin)))) // Allocate memory.
}
}
/// @dev Fallback modifier to dispatch calls from the mirror NFT contract
/// to internal functions in this contract.
modifier dn404Fallback() virtual {
DN404Storage storage $ = _getDN404Storage();
uint256 fnSelector = _calldataload(0x00) >> 224;
// `transferFromNFT(address,address,uint256,address)`.
if (fnSelector == 0xe5eb36c8) {
if (msg.sender != $.mirrorERC721) _rv(uint32(SenderNotMirror.selector));
_transferFromNFT(
address(uint160(_calldataload(0x04))), // `from`.
address(uint160(_calldataload(0x24))), // `to`.
_calldataload(0x44), // `id`.
address(uint160(_calldataload(0x64))) // `msgSender`.
);
_return(1);
}
// `setApprovalForAllNFT(address,bool,address)`.
if (fnSelector == 0xf6916ddd) {
if (msg.sender != $.mirrorERC721) _rv(uint32(SenderNotMirror.selector));
_setApprovalForAll(
address(uint160(_calldataload(0x04))), // `spender`.
_calldataload(0x24) != 0, // `status`.
address(uint160(_calldataload(0x44))) // `msgSender`.
);
_return(1);
}
// `isApprovedForAllNFT(address,address)`.
if (fnSelector == 0x62fb246d) {
bool result = _isApprovedForAll(
address(uint160(_calldataload(0x04))), // `owner`.
address(uint160(_calldataload(0x24))) // `operator`.
);
_return(_toUint(result));
}
// `ownerOfNFT(uint256)`.
if (fnSelector == 0x2d8a746e) {
_return(uint160(_ownerOf(_calldataload(0x04))));
}
// `ownerAtNFT(uint256)`.
if (fnSelector == 0xc016aa52) {
_return(uint160(_ownerAt(_calldataload(0x04))));
}
// `approveNFT(address,uint256,address)`.
if (fnSelector == 0xd10b6e0c) {
if (msg.sender != $.mirrorERC721) _rv(uint32(SenderNotMirror.selector));
address owner = _approveNFT(
address(uint160(_calldataload(0x04))), // `spender`.
_calldataload(0x24), // `id`.
address(uint160(_calldataload(0x44))) // `msgSender`.
);
_return(uint160(owner));
}
// `getApprovedNFT(uint256)`.
if (fnSelector == 0x27ef5495) {
_return(uint160(_getApproved(_calldataload(0x04))));
}
// `balanceOfNFT(address)`.
if (fnSelector == 0xf5b100ea) {
_return(_balanceOfNFT(address(uint160(_calldataload(0x04)))));
}
// `totalNFTSupply()`.
if (fnSelector == 0xe2c79281) {
_return(_totalNFTSupply());
}
// `tokenURINFT(uint256)`.
if (fnSelector == 0xcb30b460) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x40, add(mload(0x40), 0x20))
}
string memory uri = _tokenURI(_calldataload(0x04));
/// @solidity memory-safe-assembly
assembly {
// Memory safe, as we've advanced the free memory pointer by a word.
let o := sub(uri, 0x20) // Start of the returndata.
let z := add(mload(uri), 0x40) // Unpadded length of returndata.
mstore(add(o, z), 0) // Zeroize the word after the end of the string.
mstore(o, 0x20) // Store the offset of `uri`.
return(o, and(not(0x1f), add(0x1f, z)))
}
}
// `implementsDN404()`.
if (fnSelector == 0xb7a94eb8) {
_return(1);
}
_;
}
/// @dev Fallback function for calls from mirror NFT contract.
/// Override this if you need to implement your custom
/// fallback with utilities like Solady's `LibZip.cdFallback()`.
/// And always remember to always wrap the fallback with `dn404Fallback`.
fallback() external payable virtual dn404Fallback {
_rv(uint32(FnSelectorNotRecognized.selector)); // Not mandatory. Just for quality of life.
}
/// @dev This is to silence the compiler warning.
/// Override and remove the revert if you want your contract to receive ETH via receive.
receive() external payable virtual {
if (msg.value != 0) revert();
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL / PRIVATE HELPERS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns `(i - _toUint(_useOneIndexed())) << 1`.
function _ownershipIndex(uint256 i) internal pure returns (uint256) {
unchecked {
return (i - _toUint(_useOneIndexed())) << 1;
}
}
/// @dev Returns `((i - _toUint(_useOneIndexed())) << 1) + 1`.
function _ownedIndex(uint256 i) internal pure returns (uint256) {
unchecked {
return ((i - _toUint(_useOneIndexed())) << 1) + 1;
}
}
/// @dev Returns the uint32 value at `index` in `map`.
function _get(Uint32Map storage map, uint256 index) internal view returns (uint32 result) {
/// @solidity memory-safe-assembly
assembly {
let s := add(shl(96, map.slot), shr(3, index)) // Storage slot.
result := and(0xffffffff, shr(shl(5, and(index, 7)), sload(s)))
}
}
/// @dev Updates the uint32 value at `index` in `map`.
function _set(Uint32Map storage map, uint256 index, uint32 value) internal {
/// @solidity memory-safe-assembly
assembly {
let s := add(shl(96, map.slot), shr(3, index)) // Storage slot.
let o := shl(5, and(index, 7)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
sstore(s, xor(v, shl(o, and(0xffffffff, xor(value, shr(o, v))))))
}
}
/// @dev Sets the owner alias and the owned index together.
function _setOwnerAliasAndOwnedIndex(
Uint32Map storage map,
uint256 id,
uint32 ownership,
uint32 ownedIndex
) internal {
uint256 t = _toUint(_useOneIndexed());
/// @solidity memory-safe-assembly
assembly {
let i := sub(id, t) // Index of the uint64 combined value.
let s := add(shl(96, map.slot), shr(2, i)) // Storage slot.
let v := sload(s) // Storage slot value.
let o := shl(6, and(i, 3)) // Storage slot offset (bits).
let combined := or(shl(32, ownedIndex), and(0xffffffff, ownership))
sstore(s, xor(v, shl(o, and(0xffffffffffffffff, xor(shr(o, v), combined)))))
}
}
/// @dev Returns the boolean value of the bit at `index` in `bitmap`.
function _get(Bitmap storage bitmap, uint256 index) internal view returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
let s := add(shl(96, bitmap.slot), shr(8, index)) // Storage slot.
result := and(1, shr(and(0xff, index), sload(s)))
}
}
/// @dev Updates the bit at `index` in `bitmap` to `value`.
function _set(Bitmap storage bitmap, uint256 index, bool value) internal {
/// @solidity memory-safe-assembly
assembly {
let s := add(shl(96, bitmap.slot), shr(8, index)) // Storage slot.
let o := and(0xff, index) // Storage slot offset (bits).
sstore(s, or(and(sload(s), not(shl(o, 1))), shl(o, iszero(iszero(value)))))
}
}
/// @dev Returns the index of the least significant unset bit in `[begin..upTo]`.
/// If no unset bit is found, returns `type(uint256).max`.
function _findFirstUnset(Bitmap storage bitmap, uint256 begin, uint256 upTo)
internal
view
returns (uint256 unsetBitIndex)
{
/// @solidity memory-safe-assembly
assembly {
unsetBitIndex := not(0) // Initialize to `type(uint256).max`.
let s := shl(96, bitmap.slot) // Storage offset of the bitmap.
let bucket := add(s, shr(8, begin))
let negBits := shl(and(0xff, begin), shr(and(0xff, begin), not(sload(bucket))))
if iszero(negBits) {
let lastBucket := add(s, shr(8, upTo))
for {} 1 {} {
bucket := add(bucket, 1)
negBits := not(sload(bucket))
if or(negBits, gt(bucket, lastBucket)) { break }
}
if gt(bucket, lastBucket) {
negBits := shr(and(0xff, not(upTo)), shl(and(0xff, not(upTo)), negBits))
}
}
if negBits {
// Find-first-set routine.
// From: https://github.com/vectorized/solady/blob/main/src/utils/LibBit.sol
let b := and(negBits, add(not(negBits), 1)) // Isolate the least significant bit.
// For the upper 3 bits of the result, use a De Bruijn-like lookup.
// Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/
// forgefmt: disable-next-item
let r := shl(5, shr(252, shl(shl(2, shr(250, mul(b,
0x2aaaaaaaba69a69a6db6db6db2cb2cb2ce739ce73def7bdeffffffff))),
0x1412563212c14164235266736f7425221143267a45243675267677)))
// For the lower 5 bits of the result, use a De Bruijn lookup.
// forgefmt: disable-next-item
r := or(r, byte(and(div(0xd76453e0, shr(r, b)), 0x1f),
0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405))
r := or(shl(8, sub(bucket, s)), r)
unsetBitIndex := or(r, sub(0, or(gt(r, upTo), lt(r, begin))))
}
}
}
/// @dev Returns a storage reference to the value at (`a0`, `a1`) in `map`.
function _ref(AddressPairToUint256RefMap storage map, address a0, address a1)
internal
pure
returns (Uint256Ref storage ref)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x28, a1)
mstore(0x14, a0)
mstore(0x00, map.slot)
ref.slot := keccak256(0x00, 0x48)
// Clear the part of the free memory pointer that was overwritten.
mstore(0x28, 0x00)
}
}
/// @dev Wraps the NFT ID.
function _wrapNFTId(uint256 id, uint256 idLimit) internal pure returns (uint256 result) {
result = _toUint(_useOneIndexed());
/// @solidity memory-safe-assembly
assembly {
result :=
or(
mul(or(mul(iszero(gt(id, idLimit)), id), gt(id, idLimit)), result),
mul(mul(lt(id, idLimit), id), iszero(result))
)
}
}
/// @dev Returns `id > type(uint32).max ? 0 : id`.
function _restrictNFTId(uint256 id) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := mul(id, lt(id, 0x100000000))
}
}
/// @dev Returns whether `amount` is an invalid `totalSupply`.
function _totalSupplyOverflows(uint256 amount) internal view returns (bool result) {
uint256 unit = _unit();
/// @solidity memory-safe-assembly
assembly {
result := iszero(iszero(or(shr(96, amount), lt(0xfffffffe, div(amount, unit)))))
}
}
/// @dev Returns `max(0, x - y)`.
function _zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Returns `x < y ? x : y`.
function _min(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
/// @dev Returns `b ? 1 : 0`.
function _toUint(bool b) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := iszero(iszero(b))
}
}
/// @dev Initiates memory allocation for direct logs with `n` log items.
function _directLogsMalloc(uint256 n, address from, address to)
private
pure
returns (bytes32 p)
{
/// @solidity memory-safe-assembly
assembly {
// `p`'s layout:
// uint256 offset;
// uint256[] logs;
p := mload(0x40)
let m := add(p, 0x40)
mstore(m, 0x144027d3) // `logDirectTransfer(address,address,uint256[])`.
mstore(add(m, 0x20), shr(96, shl(96, from)))
mstore(add(m, 0x40), shr(96, shl(96, to)))
mstore(add(m, 0x60), 0x60) // Offset of `logs` in the calldata to send.
// Skip 4 words: `fnSelector`, `from`, `to`, `calldataLogsOffset`.
let logs := add(0x80, m)
mstore(logs, n) // Store the length.
let offset := add(0x20, logs) // Skip the word for `p.logs.length`.
mstore(0x40, add(offset, shl(5, n))) // Allocate memory.
mstore(add(0x20, p), logs) // Set `p.logs`.
mstore(p, offset) // Set `p.offset`.
}
}
/// @dev Adds a direct log item to `p` with token `id`.
function _directLogsAppend(bytes32 p, uint256 id) private pure {
/// @solidity memory-safe-assembly
assembly {
let offset := mload(p)
mstore(offset, id)
mstore(p, add(offset, 0x20))
}
}
/// @dev Calls the `mirror` NFT contract to emit {Transfer} events for packed logs `p`.
function _directLogsSend(bytes32 p, DN404Storage storage $) private {
address mirror = $.mirrorERC721;
/// @solidity memory-safe-assembly
assembly {
let logs := mload(add(p, 0x20))
let n := add(0x84, shl(5, mload(logs))) // Length of calldata to send.
let o := sub(logs, 0x80) // Start of calldata to send.
if iszero(and(eq(mload(o), 1), call(gas(), mirror, 0, add(o, 0x1c), n, o, 0x20))) {
revert(o, 0x00)
}
}
}
/// @dev Returns the token IDs of the direct logs.
function _directLogsIds(bytes32 p) private pure returns (uint256[] memory ids) {
/// @solidity memory-safe-assembly
assembly {
if p { ids := mload(add(p, 0x20)) }
}
}
/// @dev Initiates memory allocation for packed logs with `n` log items.
function _packedLogsMalloc(uint256 n) private pure returns (bytes32 p) {
/// @solidity memory-safe-assembly
assembly {
// `p`'s layout:
// uint256 offset;
// uint256 addressAndBit;
// uint256[] logs;
p := mload(0x40)
let logs := add(p, 0xa0)
mstore(logs, n) // Store the length.
let offset := add(0x20, logs) // Skip the word for `p.logs.length`.
mstore(0x40, add(offset, shl(5, n))) // Allocate memory.
mstore(add(0x40, p), logs) // Set `p.logs`.
mstore(p, offset) // Set `p.offset`.
}
}
/// @dev Set the current address and the burn bit.
function _packedLogsSet(bytes32 p, address a, uint256 burnBit) private pure {
/// @solidity memory-safe-assembly
assembly {
mstore(add(p, 0x20), or(shl(96, a), burnBit)) // Set `p.addressAndBit`.
}
}
/// @dev Adds a packed log item to `p` with token `id`.
function _packedLogsAppend(bytes32 p, uint256 id) private pure {
/// @solidity memory-safe-assembly
assembly {
let offset := mload(p)
mstore(offset, or(mload(add(p, 0x20)), shl(8, id))) // `p.addressAndBit | (id << 8)`.
mstore(p, add(offset, 0x20))
}
}
/// @dev Calls the `mirror` NFT contract to emit {Transfer} events for packed logs `p`.
function _packedLogsSend(bytes32 p, DN404Storage storage $) private {
address mirror = $.mirrorERC721;
/// @solidity memory-safe-assembly
assembly {
let logs := mload(add(p, 0x40))
let o := sub(logs, 0x40) // Start of calldata to send.
mstore(o, 0x263c69d6) // `logTransfer(uint256[])`.
mstore(add(o, 0x20), 0x20) // Offset of `logs` in the calldata to send.
let n := add(0x44, shl(5, mload(logs))) // Length of calldata to send.
if iszero(and(eq(mload(o), 1), call(gas(), mirror, 0, add(o, 0x1c), n, o, 0x20))) {
revert(o, 0x00)
}
}
}
/// @dev Returns the token IDs of the packed logs (destructively).
function _packedLogsIds(bytes32 p) private pure returns (uint256[] memory ids) {
/// @solidity memory-safe-assembly
assembly {
if p {
ids := mload(add(p, 0x40))
let o := add(ids, 0x20)
let end := add(o, shl(5, mload(ids)))
for {} iszero(eq(o, end)) { o := add(o, 0x20) } {
mstore(o, shr(168, shl(160, mload(o))))
}
}
}
}
/// @dev Returns an array of zero addresses.
function _zeroAddresses(uint256 n) private pure returns (address[] memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
mstore(0x40, add(add(result, 0x20), shl(5, n)))
mstore(result, n)
calldatacopy(add(result, 0x20), calldatasize(), shl(5, n))
}
}
/// @dev Returns an array each set to `value`.
function _filled(uint256 n, uint256 value) private pure returns (uint256[] memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let o := add(result, 0x20)
let end := add(o, shl(5, n))
mstore(0x40, end)
mstore(result, n)
for {} iszero(eq(o, end)) { o := add(o, 0x20) } { mstore(o, value) }
}
}
/// @dev Returns an array each set to `value`.
function _filled(uint256 n, address value) private pure returns (address[] memory result) {
result = _toAddresses(_filled(n, uint160(value)));
}
/// @dev Concatenates the arrays.
function _concat(uint256[] memory a, uint256[] memory b)
private
pure
returns (uint256[] memory result)
{
uint256 aN = a.length;
uint256 bN = b.length;
if (aN == uint256(0)) return b;
if (bN == uint256(0)) return a;
/// @solidity memory-safe-assembly
assembly {
let n := add(aN, bN)
if n {
result := mload(0x40)
mstore(result, n)
function copy(dst_, src_, n_) -> _end {
_end := add(dst_, shl(5, n_))
if n_ {
for { let d_ := sub(src_, dst_) } 1 {} {
mstore(dst_, mload(add(dst_, d_)))
dst_ := add(dst_, 0x20)
if eq(dst_, _end) { break }
}
}
}
mstore(0x40, copy(copy(add(result, 0x20), add(a, 0x20), aN), add(b, 0x20), bN))
}
}
}
/// @dev Concatenates the arrays.
function _concat(address[] memory a, address[] memory b)
private
pure
returns (address[] memory result)
{
result = _toAddresses(_concat(_toUints(a), _toUints(b)));
}
/// @dev Reinterpret cast to an uint array.
function _toUints(address[] memory a) private pure returns (uint256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Reinterpret cast to an address array.
function _toAddresses(uint256[] memory a) private pure returns (address[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Struct of temporary variables for transfers.
struct _DNTransferTemps {
uint256 numNFTBurns;
uint256 numNFTMints;
uint256 fromOwnedLength;
uint256 toOwnedLength;
uint256 totalNFTSupply;
uint256 fromEnd;
uint256 toEnd;
uint32 toAlias;
uint256 nextTokenId;
uint32 burnedPoolTail;
bytes32 directLogs;
bytes32 packedLogs;
}
/// @dev Struct of temporary variables for mints.
struct _DNMintTemps {
uint256 nextTokenId;
uint32 burnedPoolTail;
uint256 toEnd;
uint32 toAlias;
uint256 numNFTMints;
bytes32 packedLogs;
}
/// @dev Struct of temporary variables for burns.
struct _DNBurnTemps {
uint256 fromBalance;
uint256 totalSupply;
uint256 numNFTBurns;
bytes32 packedLogs;
}
/// @dev Returns the calldata value at `offset`.
function _calldataload(uint256 offset) private pure returns (uint256 value) {
/// @solidity memory-safe-assembly
assembly {
value := calldataload(offset)
}
}
/// @dev Executes a return opcode to return `x` and end the current call frame.
function _return(uint256 x) private pure {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, x)
return(0x00, 0x20)
}
}
/// @dev More bytecode-efficient way to revert.
function _rv(uint32 s) private pure {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, s)
revert(0x1c, 0x04)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @title DN404Mirror
/// @notice DN404Mirror provides an interface for interacting with the
/// NFT tokens in a DN404 implementation.
///
/// @author vectorized.eth (@optimizoor)
/// @author Quit (@0xQuit)
/// @author Michael Amadi (@AmadiMichaels)
/// @author cygaar (@0xCygaar)
/// @author Thomas (@0xjustadev)
/// @author Harrison (@PopPunkOnChain)
///
/// @dev Note:
/// - The ERC721 data is stored in the base DN404 contract.
contract DN404Mirror {
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* EVENTS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Emitted when token `id` is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 indexed id);
/// @dev Emitted when `owner` enables `account` to manage the `id` token.
event Approval(address indexed owner, address indexed account, uint256 indexed id);
/// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens.
event ApprovalForAll(address indexed owner, address indexed operator, bool isApproved);
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This is for marketplace signaling purposes. This contract has a `pullOwner()`
/// function that will sync the owner from the base contract.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/// @dev `keccak256(bytes("ApprovalForAll(address,address,bool)"))`.
uint256 private constant _APPROVAL_FOR_ALL_EVENT_SIGNATURE =
0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CUSTOM ERRORS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Thrown when a call for an NFT function did not originate
/// from the base DN404 contract.
error SenderNotBase();
/// @dev Thrown when a call for an NFT function did not originate from the deployer.
error SenderNotDeployer();
/// @dev Thrown when transferring an NFT to a contract address that
/// does not implement ERC721Receiver.
error TransferToNonERC721ReceiverImplementer();
/// @dev Thrown when a linkMirrorContract call is received and the
/// NFT mirror contract has already been linked to a DN404 base contract.
error AlreadyLinked();
/// @dev Thrown when retrieving the base DN404 address when a link has not
/// been established.
error NotLinked();
/// @dev The function selector is not recognized.
error FnSelectorNotRecognized();
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* STORAGE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Struct contain the NFT mirror contract storage.
struct DN404NFTStorage {
// Address of the ERC20 base contract.
address baseERC20;
// The deployer, if provided. If non-zero, the initialization of the
// ERC20 <-> ERC721 link can only be done by the deployer via the ERC20 base contract.
address deployer;
// The owner of the ERC20 base contract. For marketplace signaling.
address owner;
}
/// @dev Returns a storage pointer for DN404NFTStorage.
function _getDN404NFTStorage() internal pure virtual returns (DN404NFTStorage storage $) {
/// @solidity memory-safe-assembly
assembly {
// `uint72(bytes9(keccak256("DN404_MIRROR_STORAGE")))`.
$.slot := 0x3602298b8c10b01230 // Truncate to 9 bytes to reduce bytecode size.
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CONSTRUCTOR */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
constructor(address deployer) {
// For non-proxies, we will store the deployer so that only the deployer can
// link the base contract.
_getDN404NFTStorage().deployer = deployer;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* ERC721 OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the token collection name from the base DN404 contract.
function name() public view virtual returns (string memory) {
return _readString(0x06fdde03, 0); // `name()`.
}
/// @dev Returns the token collection symbol from the base DN404 contract.
function symbol() public view virtual returns (string memory) {
return _readString(0x95d89b41, 0); // `symbol()`.
}
/// @dev Returns the Uniform Resource Identifier (URI) for token `id` from
/// the base DN404 contract.
function tokenURI(uint256 id) public view virtual returns (string memory) {
ownerOf(id); // `ownerOf` reverts if the token does not exist.
// We'll leave if optional for `_tokenURI` to revert for non-existent token
// on the ERC20 side, since this is only recommended by the ERC721 standard.
return _readString(0xcb30b460, id); // `tokenURINFT(uint256)`.
}
/// @dev Returns the total NFT supply from the base DN404 contract.
function totalSupply() public view virtual returns (uint256) {
return _readWord(0xe2c79281, 0, 0); // `totalNFTSupply()`.
}
/// @dev Returns the number of NFT tokens owned by `nftOwner` from the base DN404 contract.
///
/// Requirements:
/// - `nftOwner` must not be the zero address.
function balanceOf(address nftOwner) public view virtual returns (uint256) {
return _readWord(0xf5b100ea, uint160(nftOwner), 0); // `balanceOfNFT(address)`.
}
/// @dev Returns the owner of token `id` from the base DN404 contract.
///
/// Requirements:
/// - Token `id` must exist.
function ownerOf(uint256 id) public view virtual returns (address) {
return address(uint160(_readWord(0x2d8a746e, id, 0))); // `ownerOfNFT(uint256)`.
}
/// @dev Returns the owner of token `id` from the base DN404 contract.
/// Returns `address(0)` instead of reverting if the token does not exist.
function ownerAt(uint256 id) public view virtual returns (address) {
return address(uint160(_readWord(0xc016aa52, id, 0))); // `ownerAtNFT(uint256)`.
}
/// @dev Sets `spender` as the approved account to manage token `id` in
/// the base DN404 contract.
///
/// Requirements:
/// - Token `id` must exist.
/// - The caller must be the owner of the token,
/// or an approved operator for the token owner.
///
/// Emits an {Approval} event.
function approve(address spender, uint256 id) public payable virtual {
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
spender := shr(96, shl(96, spender))
let m := mload(0x40)
mstore(0x00, 0xd10b6e0c) // `approveNFT(address,uint256,address)`.
mstore(0x20, spender)
mstore(0x40, id)
mstore(0x60, caller())
if iszero(
and( // Arguments of `and` are evaluated last to first.
gt(returndatasize(), 0x1f), // The call must return at least 32 bytes.
call(gas(), base, callvalue(), 0x1c, 0x64, 0x00, 0x20)
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
// Emit the {Approval} event.
log4(codesize(), 0x00, _APPROVAL_EVENT_SIGNATURE, shr(96, mload(0x0c)), spender, id)
}
}
/// @dev Returns the account approved to manage token `id` from
/// the base DN404 contract.
///
/// Requirements:
/// - Token `id` must exist.
function getApproved(uint256 id) public view virtual returns (address) {
return address(uint160(_readWord(0x27ef5495, id, 0))); // `getApprovedNFT(uint256)`.
}
/// @dev Sets whether `operator` is approved to manage the tokens of the caller in
/// the base DN404 contract.
///
/// Emits an {ApprovalForAll} event.
function setApprovalForAll(address operator, bool approved) public virtual {
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
operator := shr(96, shl(96, operator))
let m := mload(0x40)
mstore(0x00, 0xf6916ddd) // `setApprovalForAllNFT(address,bool,address)`.
mstore(0x20, operator)
mstore(0x40, iszero(iszero(approved)))
mstore(0x60, caller())
if iszero(
and( // Arguments of `and` are evaluated last to first.
eq(mload(0x00), 1), // The call must return 1.
call(gas(), base, callvalue(), 0x1c, 0x64, 0x00, 0x20)
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
// Emit the {ApprovalForAll} event.
// The `approved` value is already at 0x40.
log3(0x40, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), operator)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}
/// @dev Returns whether `operator` is approved to manage the tokens of `nftOwner` from
/// the base DN404 contract.
function isApprovedForAll(address nftOwner, address operator)
public
view
virtual
returns (bool)
{
// `isApprovedForAllNFT(address,address)`.
return _readWord(0x62fb246d, uint160(nftOwner), uint160(operator)) != 0;
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - The caller must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 id) public payable virtual {
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
from := shr(96, shl(96, from))
to := shr(96, shl(96, to))
let m := mload(0x40)
mstore(m, 0xe5eb36c8) // `transferFromNFT(address,address,uint256,address)`.
mstore(add(m, 0x20), from)
mstore(add(m, 0x40), to)
mstore(add(m, 0x60), id)
mstore(add(m, 0x80), caller())
if iszero(
and( // Arguments of `and` are evaluated last to first.
eq(mload(m), 1), // The call must return 1.
call(gas(), base, callvalue(), add(m, 0x1c), 0x84, m, 0x20)
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
}
/// @dev Equivalent to `safeTransferFrom(from, to, id, "")`.
function safeTransferFrom(address from, address to, uint256 id) public payable virtual {
bytes calldata emptyData;
/// @solidity memory-safe-assembly
assembly {
emptyData.length := 0
}
safeTransferFrom(from, to, id, emptyData);
}
/// @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);
/// @solidity memory-safe-assembly
assembly {
if extcodesize(to) {
// 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)
mstore(add(m, 0xa0), data.length)
calldatacopy(add(m, 0xc0), data.offset, data.length)
// Revert if the call reverts.
if iszero(call(gas(), to, 0, add(m, 0x1c), add(data.length, 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)
}
}
}
}
/// @dev Returns true if this contract implements the interface defined by `interfaceId`.
/// See: https://eips.ethereum.org/EIPS/eip-165
/// This function call must use less than 30000 gas.
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
let s := shr(224, interfaceId)
// ERC165: 0x01ffc9a7, ERC721: 0x80ac58cd, ERC721Metadata: 0x5b5e139f.
result := or(or(eq(s, 0x01ffc9a7), eq(s, 0x80ac58cd)), eq(s, 0x5b5e139f))
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* OWNER SYNCING OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the `owner` of the contract, for marketplace signaling purposes.
function owner() public view virtual returns (address) {
return _getDN404NFTStorage().owner;
}
/// @dev Permissionless function to pull the owner from the base DN404 contract
/// if it implements ownable, for marketplace signaling purposes.
function pullOwner() public virtual returns (bool) {
address newOwner;
address base = baseERC20();
uint32 baseOwnerFunctionSelector = uint32(_baseOwnerFunctionSelector());
DN404NFTStorage storage $ = _getDN404NFTStorage();
address oldOwner = $.owner;
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, baseOwnerFunctionSelector)
let success := staticcall(gas(), base, 0x1c, 0x04, 0x00, 0x20)
newOwner := mul(shr(96, mload(0x0c)), and(gt(returndatasize(), 0x1f), success))
if iszero(success) { if shl(96, oldOwner) { revert(0x00, 0x00) } }
}
if (oldOwner != newOwner) {
$.owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
return true;
}
/// @dev Override to allow for a different function selector on `baseERC20`.
function _baseOwnerFunctionSelector() internal view virtual returns (bytes4) {
return 0x8da5cb5b; // `owner()`.
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* MIRROR OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the address of the base DN404 contract.
function baseERC20() public view virtual returns (address base) {
base = _getDN404NFTStorage().baseERC20;
if (base == address(0)) _rv(uint32(NotLinked.selector));
}
/// @dev Fallback modifier to execute calls from the base DN404 contract.
modifier dn404NFTFallback() virtual {
DN404NFTStorage storage $ = _getDN404NFTStorage();
uint256 fnSelector = _calldataload(0x00) >> 224;
// `logTransfer(uint256[])`.
if (fnSelector == 0x263c69d6) {
if (msg.sender != $.baseERC20) _rv(uint32(SenderNotBase.selector));
/// @solidity memory-safe-assembly
assembly {
let o := add(0x24, calldataload(0x04)) // Packed logs offset.
let end := add(o, shl(5, calldataload(sub(o, 0x20))))
for {} iszero(eq(o, end)) { o := add(0x20, o) } {
let d := calldataload(o) // Entry in the packed logs.
let a := shr(96, d) // The address.
let b := and(1, d) // Whether it is a burn.
log4(
codesize(),
0x00,
_TRANSFER_EVENT_SIGNATURE,
mul(a, b), // `from`.
mul(a, iszero(b)), // `to`.
shr(168, shl(160, d)) // `id`.
)
}
mstore(0x00, 0x01)
return(0x00, 0x20)
}
}
// `logDirectTransfer(address,address,uint256[])`.
if (fnSelector == 0x144027d3) {
if (msg.sender != $.baseERC20) _rv(uint32(SenderNotBase.selector));
/// @solidity memory-safe-assembly
assembly {
let from := calldataload(0x04)
let to := calldataload(0x24)
let o := add(0x24, calldataload(0x44)) // Direct logs offset.
let end := add(o, shl(5, calldataload(sub(o, 0x20))))
for {} iszero(eq(o, end)) { o := add(0x20, o) } {
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, calldataload(o))
}
mstore(0x00, 0x01)
return(0x00, 0x20)
}
}
// `linkMirrorContract(address)`.
if (fnSelector == 0x0f4599e5) {
if ($.deployer != address(0)) {
if (address(uint160(_calldataload(0x04))) != $.deployer) {
_rv(uint32(SenderNotDeployer.selector));
}
}
if ($.baseERC20 != address(0)) _rv(uint32(AlreadyLinked.selector));
$.baseERC20 = msg.sender;
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x01)
return(0x00, 0x20)
}
}
_;
}
/// @dev Fallback function for calls from base DN404 contract.
/// Override this if you need to implement your custom
/// fallback with utilities like Solady's `LibZip.cdFallback()`.
/// And always remember to always wrap the fallback with `dn404NFTFallback`.
fallback() external payable virtual dn404NFTFallback {
_rv(uint32(FnSelectorNotRecognized.selector)); // Not mandatory. Just for quality of life.
}
/// @dev This is to silence the compiler warning.
/// Override and remove the revert if you want your contract to receive ETH via receive.
receive() external payable virtual {
if (msg.value != 0) revert();
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* PRIVATE HELPERS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Helper to read a string from the base DN404 contract.
function _readString(uint256 fnSelector, uint256 arg0)
private
view
returns (string memory result)
{
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
mstore(0x00, fnSelector)
mstore(0x20, arg0)
if iszero(staticcall(gas(), base, 0x1c, 0x24, 0x00, 0x00)) {
returndatacopy(result, 0x00, returndatasize())
revert(result, returndatasize())
}
returndatacopy(0x00, 0x00, 0x20) // Copy the offset of the string in returndata.
returndatacopy(result, mload(0x00), 0x20) // Copy the length of the string.
returndatacopy(add(result, 0x20), add(mload(0x00), 0x20), mload(result)) // Copy the string.
let end := add(add(result, 0x20), mload(result))
mstore(end, 0) // Zeroize the word after the string.
mstore(0x40, add(end, 0x20)) // Allocate memory.
}
}
/// @dev Helper to read a word from the base DN404 contract.
function _readWord(uint256 fnSelector, uint256 arg0, uint256 arg1)
private
view
returns (uint256 result)
{
address base = baseERC20();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(0x00, fnSelector)
mstore(0x20, arg0)
mstore(0x40, arg1)
if iszero(
and( // Arguments of `and` are evaluated last to first.
gt(returndatasize(), 0x1f), // The call must return at least 32 bytes.
staticcall(gas(), base, 0x1c, 0x44, 0x00, 0x20)
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
mstore(0x40, m) // Restore the free memory pointer.
result := mload(0x00)
}
}
/// @dev Returns the calldata value at `offset`.
function _calldataload(uint256 offset) private pure returns (uint256 value) {
/// @solidity memory-safe-assembly
assembly {
value := calldataload(offset)
}
}
/// @dev More bytecode-efficient way to revert.
function _rv(uint32 s) private pure {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, s)
revert(0x1c, 0x04)
}
}
}
/*#+-++#+----+##+++++......-.....++++##+++###++++---++..............................................................-...--------.---..
####+++++++++#####+++--+++###+-+-#++++#####+++#+++###+--+++###############+-++++-++#####+++-+++#######+++######++++++++##########++++-
####+++--------####+--+--------+.-----#####------+-.----+++--------------.+-++--++#-----.+++-++------##++.----.++++-+##.--------###-+-
###++------------##++--.----.++#.-----++++++----.+-------+#--------------.#-+-----#------++--+#.------+##.-----#++-+#.------------#++.
###+------#+#-----##+#-.----.+--.-----+#+++-----.#+++++++++---+------+----+-------#------++++++.-------.#-----.#+++#.------#+------++.
##+#-----#+++#--+.##++#.----.#--.-----++++++----.#-----+--++++#+-----++++---------+------+--+-+.---------+-----#++++------#+##.....++.
##+.-----++++#########++-----++#+-----++#++-----.#-------------+-----++-----------+-----------+.--------------.#+-++------#++#######+.
##+#-----++++#-----###++-----+++#-----++++++----.#+++++--------------+------------+-----------+.----#----------#+-++------###+-----++.
###+-------#------+##++#------+------#++++++-----------.--------------------------------.------.----+#---------#+-+#-------..-----.+-.
+##++#-----------+++#+#+#-----------##++++++--------------------------------------------.------------+++------.+-+-+#+-----------+#++.
####+++++-----#+++++++++#+##-----+#++#++++++--------------------+++++------+++-----+++++----------------#.-----+----++##.-----.+##++-.
-#+--++++++#++++++++++++++#++++++++++#++++++#-+-++-----------.--------..-.-----..-----------------+------+++++++-------++#####+++----.
-#+#####++++####+##+##+#+++++++++++++#++++++#---------------------------------------------------------------------+---------------++-.
-#+###+##########+##+##+####+++++++++#++++++#-------------------------.....------------------------------------------------+++++-----.
##+++##+##+##############++#####+++++#++++++#------------------------...#+...-----------------.--------------------------------------.
-+###+#++####++++++++++++++++++####+++++++++#.----.---------------....#####...------------------------------------------+------------.
###+##++#+++++++++++++#####+++++++###+++++++#.--------...............#######..-------------------------------------------------------.
+####-#######+++++++++++++##++#####++##+++++#.--------.#####...##############..------------------------------------------------------.
##++######+#++++++++++#+++++#+++###+++++#+++#.--------..######################..----------.....--.....-------------------------------.
+#+#######+##+++###++++++++++--+++##+++++#++#.--------...#####################-.---------........-...........-......-----------------.
+####++########++--+#############--+++++++###..--------...#####################.--------..#####-.#####...###.-##.##-.--#+-.-..-------.
#############++-###+###############+-+++++++#-..--------..#####################..-------..#####.######...##....###-#....+####+-------.
#####++++###+######-##################++++++##-..-------..#####################..-------..-.#.##.#.-##.-#.###+#+.##..#########.------.
############+++-##++###################-++++++#+..------..##########--#########..--------.........................#########...-------.
#####+###+++++++--+----##################+++++###.-------..####################..-------------------------....##########...----------.
#####+##+-++++-###+####---################-++++##--------..#####################...-------------------.....#########....-------------.
#######+####+-####-+###++++-###############-##++##--------..####################+#...--------------.....#########....----------------.
###+##---#########-+####----+###############-#+#+#---------...################+++##-....-------.....+########+....-------------------.
++++++#-####################-++##############++++#---------....-############+..+-#####...........#########-....----------------------.
####+########################-+##############++#++#-........#########-+##+...--+##########+...#########........--.--.----------------.
####++-######++-##############++##############+##++###+++-###########-#++..---+#####################.........-------#---+++++########+
###++++#+----++##############-.#+#############-+#++##+#-++###############+-.-++###################+.+##############++++++++++++++++++-
###++#++#######+++##############+#############+++++##+#################---++++#########+--############################++++++++++++++++
###++.#+---####----..#######+.#---+##..#+..###--+++-----################++-.+#######---++##########+++++++++++++++++++++++++++++++++++
##++++---+++-##-#----#######.##.#-###..##.-.##.-+#--+++.################+.++#####--++++#############++++++++++++++++++++++++++++++++++
##+++++#---.+#+#+-#-.#######-#.---.##..##.-..#.-+--#++##################++###+--++++#################+++++++++++++++++++++++++++++++++
##+#++#-+#-+##++.-#-#########-----###..##.##.---+---###################++#+--++#######################++++++++++++++++++++++++++++++++
+#+#+--.-----++#+#--###..###+.#-#--##..##.-##---++-.....-#########+++++++++###########################++++++++++++++++++++++++++++++++
-#++++#++####++++#+#############+++#######+###++#################-++++++-+############################++++++++++++++++++++++++++++++++
##+++#++###+++++++++++#+++-+####+++#######+#####################+++++++################################+++++++++++++++++++++++++++++++
##+++#+++++++++++++++++++#++####+++#######+############+-######--+++##+#################################++++++++++++++++++++++++++++++
###++#+++++##++++.+++++++##-+###+-#######++############-##############-##################################+++++++++++++++++++++++++++++
###++#+#++++++++#+#+##++#+-###+-#########+#############-#################################################+++++++++++++++++++++++++++++
###+++-+++++++++++++#++++++############################-##################################################++++++++++++++++++++++++++++
####+++++++++++++++-++++#+++#-+-############+++#########+++#####+##########################################+++++++++++++++++++++++++++
####++++#.+#+++++#++#+-++####+++#######++###++++--++++++++++-+--############################################++++++++++++++++++++++++++
##+##+#++++#++##.++++####-++++########+#####+++++++++++++++++++##############################################+++++++++++++++++++++++++
##++##+++#++#++++++++++++++++########+#####++##+++++++++++++-+###############################################+++++++++++++++++++++++++
#######+++++++##++++++++++++########+#####++##+-++++++++++###################################################++++++##+++++++++++++++++
########+++++++##++++++++++#######++##++#++++###++++++++++##################################################++++++#-++++++++++++++++++
##########+++++++++++++++#######+++++-+#++++++#+++++++++++#################################################+++++++#.#+++++++++++++++++
####+##-+##++++++#+++++######+++#++++#++++++++++++++++++++#############+###################################++++#+##-###+++++++++++++++
#############+++-##++####++++++++++##++++++#++++++++++++++################################################+++++--------###++++++++++++
################+--++++++++++++++##++++++##+++++++++++++++############+##################################++-############-#+#++++++++++
#####################++++++#####++++++++#++++++++++++++++##############################################++-###-.....-...-##--#+++++++++
##############+#############++++++++++##+++++++++++++++++#############################################++###...#---##----..##+#++++++++
######################+++++++###++++##+++++++++++++++++++############################################+-+#.#.-...--.----....-#-++++++++
#########################+###########+++++++####++++++++#############################################++##.-+-##-.---....###.##+#++++++
############+#####.+#####+#+..##....#++#+##+.#.###++++++######.#######.......#######################+++#..-.-.-##...-##-.....#++#+++++
+#############..........##++..+.#....+++.........#++++#+.##.##.#..####.##.##..######################+-##-##----..###-..---+#.#+-#+++++
+#############+###.+########..#+...##++##.#+.#.###+++++-###.##..######.##.##.######.################++##....----....-------..#-#++++++
+#################.##.+##+....+.-#..#+++..##...#++++++##..#.#.....####........-######################++#+-##--------------..+#+#++++++
+++#####++++###..++.-.++++-#.###..##+++##......+##++++#.-##.#..#-.###########.####+.#################++##-....-----------+#-##++++++++
+##########+#...+#+++...+++#.##..#+..++..##+.###.#+++++.##.##.#....#######....########################+++##..##.--+---#...##++++++++++
+#+++#++++++#+##+++++##+++#+++##++++#++###++#++###++++##################################################++###-...--....-####++++++++++
+####+#+#+#+#+#++++++++++#+++####++#++++++++++++++++++####################################################+++############+++++++++++++
+##+#######+##+++++###++++++++++##+++++++++++++++++++-#######################################################+++++++++++++++++++++++++
+######+++++#+++++++++++++++####+++++++++++++++++++++-#########################################################+++++++##++++++++++++++
+###+######+#++++++++++++++++####++++++++++++++++++++###########################################################+++++++++++#+++++++##+
+#######+##+#++##+++++++++++++--++#+++++++++++++++++-###########################################################+++++++++++#++++++++++
+#+++###++#+###+++++++++++++####++++++++++++++++++###############################################################+++++++++++++++++++++
++##+########++++++++++++###++++#++++++++++++######+####-+++#####################################################+++++++++++++++++++++
-++++++------+++-+---+##+---+++-+++++++++++++####---##-++++-+#####################################################++++++++++++++++++*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { DN404 } from "dn404/src/DN404.sol";
import { DN404Mirror } from "dn404/src/DN404Mirror.sol";
import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { LibString } from "solady/utils/LibString.sol";
import { MerkleProofLib } from "solady/utils/MerkleProofLib.sol";
import { IUniswapV3SwapCallback } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol";
/// @title EXEC404 - A DN404 token with advanced liquidity and NFT features
/// @notice This contract extends DN404 with the following key features:
/// @dev Implements bonding curve, liquidity provision, and NFT mechanics
///
/// Key Features:
/// - 12-day presale with daily sequential merkle tree whitelisting
/// - Automatic liquidity deployment after presale period
/// - Buy/sell taxes that convert to CULT-ETH liquidity
/// - NFT minting disabled by default (skipNFT = true)
/// - Balance-based NFT minting system
/// - Designated NFT holder can collect liquidity position fees
/// - Portion of deployed liquidity allocated to designated NFT holder
/// - On-chain message system for buys/sells
/// - Gas optimized within bytecode limits
/// - NFT ID tracking per user
///
/// Made by Arthur T. McDonald (twitter: x.com/miladystation)
/// Website: ms2.fun
///
/// For the Milady Cult Coin community
///
// ┌───────────────────────────────────────────────────────────────┐
// │ │
// │ CULT EXEC DUAL NATURE SMART CONTRACT │
// │ │
// └───────────────────────────────────────────────────────────────┘
contract EXEC404 is DN404, IUniswapV3SwapCallback {
// ┌─────────────────────────┐
// │ Types │
// └─────────────────────────┘
struct BondingMessage {
address sender; // 20 bytes
uint128 packedData; // 16 bytes (contains timestamp:32 | amount:95 | isBuy:1)
string message; // variable length
}
// ┌─────────────────────────┐
// │ Externals │
// └─────────────────────────┘
address public constant CULT = 0x0000000000c5dc95539589fbD24BE07c6C14eCa4;
address private constant router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
address private constant positionManager = 0xC36442b4a4522E871399CD717aBDD847Ab11FE88;
address private constant factory3 = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
address private constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address private constant OPERATOR_NFT = 0xB24BaB1732D34cAD0A7C7035C3539aEC553bF3a0;
// ┌─────────────────────────┐
// │ Constants │
// └─────────────────────────┘
uint256 public constant MAX_SUPPLY = 4_440_000_000 ether; // 4.44B tokens
uint256 public constant maxSupply = 4440; // nft maxSupply for uri hider
uint256 public constant startTokenId = 1;
uint256 private constant INITIAL_PRICE = 0.025 ether; // Base price per 10M tokens
uint256 private constant LIQUIDITY_RESERVE = MAX_SUPPLY * 10 / 100; // 10% reserve for liquidity
// ┌─────────────┐
// │ Tax Stuff │
// └─────────────┘
uint256 private constant TAX_RATE = 400; // 4% tax
uint256 private constant MIN_SELL_THRESHOLD = 12000 ether; // 100 tokens minimum for sell
uint256 private constant MIN_CULT_THRESHOLD = 8000 ether; // 500 tokens minimum for CULT ops
uint256 private constant MIN_LP_THRESHOLD = 4000 ether; // 1000 tokens minimum for LP ops
uint24 private constant POOL_FEE = 10000; // 1% fee tier
int24 private constant TICK_SPACING = 60;
// ┌─────────────────────────┐
// │ Immutables │
// └─────────────────────────┘
address private immutable factory;
uint256 public immutable LAUNCH_TIME;
// ┌─────────────────────────┐
// │ Variables │
// └─────────────────────────┘
// address private cultLiquidityPair;
address public liquidityPair;
bytes32[] private tierRoots;
uint256 public totalBondingSupply;
uint256 private reserve;
address public cultPool;
uint256 public cultV3Position;
bool private swapping;
mapping(uint256 => uint256) private blockSwaps;
mapping(address => bool) public freeMint;
uint256 public freeSupply = 1000 * 1000000 ether; //1000 free mints
string public uri;
string public unrevealedUri;
bool public revealed = false;
mapping(uint256 => BondingMessage) public bondingMessages;
uint256 public totalMessages;
// ┌─────────────────────────┐
// │ Events │
// └─────────────────────────┘
event WhitelistInitialized(bytes32[] roots);
event V3FeesCollected(address indexed collector, uint256 amount0, uint256 amount1);
event BondingSale(address indexed participant, uint256 amount, uint256 cost, bool isBuy);
event LiquidityDeployed(uint256 amountToken, uint256 amountETH, uint256 liquidity);
// ┌─────────────────────────┐
// │ Modifiers │
// └─────────────────────────┘
modifier whitelistGated(bytes32[] calldata proof) {
require(liquidityPair == address(0), "Presale ended");
uint256 currentTier = getCurrentTier();
if (currentTier < tierRoots.length - 1) {
require(isWhitelisted(proof, msg.sender), "Non-white");
}
_;
}
// ┌───────────────────────────────────────────────┐
// │ CONSTRUCTOR │
// └───────────────────────────────────────────────┘
constructor(
bytes32[] memory _tierRoots
) {
require(_tierRoots.length == 12, "Bad roots");
tierRoots = _tierRoots;
LAUNCH_TIME = block.timestamp;
// Store router address in memory before assembly block
address routerAddr = address(router);
address factoryAddr;
assembly {
// Get factory address using factory() selector: 0xc45a0155
mstore(0x00, 0xc45a015500000000000000000000000000000000000000000000000000000000)
let success := staticcall(
gas(),
routerAddr, // use local variable instead of immutable
0x00, // input offset
0x04, // input size (just selector)
0x00, // output offset
0x20 // output size (32 bytes)
)
if iszero(success) {
revert(0, 0)
}
factoryAddr := mload(0x00) // store in temporary variable
}
factory = factoryAddr; // assign to immutable after assembly block
// Set CULT V3 pool directly
(,bytes memory d) = factory3.staticcall(abi.encodeWithSelector(0x1698ee82, CULT, weth, POOL_FEE));
cultPool = abi.decode(d, (address));
require(cultPool != address(0), "no CULT");
emit WhitelistInitialized(_tierRoots);
address mirror = address(new DN404Mirror(msg.sender));
_initializeDN404(MAX_SUPPLY, address(this), mirror);
}
// ┌───────────────────────────────────────────────┐
// │ External Functions │
// └───────────────────────────────────────────────┘
/// @notice Allows users to buy tokens through the bonding curve mechanism
/// @dev Only whitelisted addresses can call this function
/// @param amount The amount of tokens to buy
/// @param maxCost The maximum ETH cost the user is willing to pay
/// @param mintNFT If true, will mint NFTs for the tokens. If false, keeps as ERC20
/// @param proof The merkle proof to verify the caller is whitelisted
/// @param message Optional message to store with the purchase
function buyBonding(
uint256 amount,
uint256 maxCost,
bool mintNFT,
bytes32[] calldata proof,
string calldata message
) external payable whitelistGated(proof) {
// Verify:
// 1. Amount requested + current supply doesn't exceed max bonding supply (MAX_SUPPLY - LIQUIDITY_RESERVE)
// 2. User's specified maxCost is >= actual cost calculated from bonding curve
// 3. User sent enough ETH to cover the actual cost
require(totalBondingSupply + amount <= MAX_SUPPLY - LIQUIDITY_RESERVE, "Exceeds bonding");
uint256 totalCost = calculateCost(amount);
require(maxCost >= totalCost, "MaxCost exceeded");
require(msg.value >= totalCost, "Low ETH value");
// Only flip skipNFT if it's currently true and user wants to mint
bool originalSkipNFT = mintNFT ? getSkipNFT(msg.sender) : false;
if (originalSkipNFT) {
_setSkipNFT(msg.sender, false);
}
// Handle free mints if applicable
if(freeSupply > 1000000 ether && !freeMint[msg.sender]) {
totalBondingSupply += amount;
amount += 1000000 ether;
freeSupply -= 1000000 ether;
freeMint[msg.sender] = true;
} else {
totalBondingSupply += amount;
}
// Transfer tokens from contract to user
_transfer(address(this), msg.sender, amount);
reserve += totalCost;
// Store message if provided
if (bytes(message).length > 0) {
uint64 scaledAmount = uint64(amount / 1e18);
require(scaledAmount <= type(uint64).max, "Too size for msg");
bondingMessages[totalMessages++] = BondingMessage({
sender: msg.sender,
packedData: packData(
uint32(block.timestamp),
scaledAmount,
true // isBuy
),
message: message
});
}
// Only flip back if we changed it
if (originalSkipNFT) {
_setSkipNFT(msg.sender, true);
}
// Return any excess ETH to user
if (msg.value > totalCost) {
SafeTransferLib.safeTransferETH(msg.sender, msg.value - totalCost);
}
emit BondingSale(msg.sender, amount, totalCost, true);
}
/// @notice Allows users to sell tokens back to the bonding curve
/// @dev Requires whitelisting and prevents selling of free minted tokens
/// @param amount The amount of tokens to sell back
/// @param minRefund The minimum ETH refund amount expected
/// @param proof Merkle proof for whitelist verification
/// @param message Optional message to store with the sale
/// @custom:tax 4% total tax (1% to operator, 3% to protocol)
function sellBonding(
uint256 amount,
uint256 minRefund,
bytes32[] calldata proof,
string calldata message
) external whitelistGated(proof) {
// Requirements for selling tokens back to bonding curve:
// 1. User must have sufficient balance to sell the requested amount
// 2. Users who received free tokens (freeMint) cannot sell below their initial 1M token allocation
// 3. The calculated refund must meet the user's minimum expected refund amount
// 4. The contract must have sufficient ETH reserves to process the refund
uint256 balance = balanceOf(msg.sender);
require(balance >= amount, "smolbalance");
if(freeMint[msg.sender] && (balance - amount < 1000000 ether)) {
revert("Cannot sell your freebie back into bonding");
}
// Calculate refund and validate
uint256 refund = calculateRefund(amount);
require(refund >= minRefund && reserve >= refund, "Invalid refund");
// Transfer tokens first
_transfer(msg.sender, address(this), amount);
totalBondingSupply -= amount;
reserve -= refund;
// Store message if provided
if (bytes(message).length > 0) {
require(amount / 1 ether <= type(uint64).max, "Too size for msg");
bondingMessages[totalMessages++] = BondingMessage({
sender: msg.sender,
packedData: packData(
uint32(block.timestamp),
uint64(amount / 1 ether),
false // isBuy
),
message: message
});
}
// Calculate and distribute refund with tax (4% total tax)
// 1% to operator (25% of tax), 3% to protocol (75% of tax)
uint256 userRefund = (refund * 9600) / 10000; // 96% to user
SafeTransferLib.safeTransferETH(
_erc721OwnerOf(OPERATOR_NFT, 598),
(refund * 100) / 10000 // 1% to operator
);
SafeTransferLib.safeTransferETH(msg.sender, userRefund);
emit BondingSale(msg.sender, amount, refund, false);
}
/// @notice Allows a user to mint NFTs based on their token balance by temporarily storing excess tokens
/// @dev DN404 mints NFTs when tokens are transferred and the recipient has skipNFT set to false.
/// This function:
/// 1. Calculates how many NFTs the user's balance can support
/// 2. Temporarily stores excess tokens in the contract that shouldn't be minted
/// 3. Sets skipNFT to false and triggers a self-transfer to mint the desired NFTs
/// 4. Returns the stored tokens back to the user
/// @param amount The number of NFTs to mint
function balanceMint(uint256 amount) external {
DN404Storage storage $ = _getDN404Storage();
AddressData storage addressData = $.addressData[msg.sender];
// Check if they have enough token balance to support the NFTs
uint256 balance = addressData.balance;
uint256 currentOwnedLength = addressData.ownedLength;
uint256 maxMintPossible = balance / _unit() - currentOwnedLength;
require(amount <= maxMintPossible, "NFTs over balance");
// Calculate amounts
uint256 amountToMint = amount * _unit();
// Keep enough tokens to support existing NFTs plus new ones we want to mint
uint256 amountToHold = balance - (currentOwnedLength + amount) * _unit();
// First transfer the portion we don't want to mint to the contract
_transfer(msg.sender, address(this), amountToHold);
// Set skipNFT false for minting
bool originalSkipNFT = getSkipNFT(msg.sender);
_setSkipNFT(msg.sender, false);
// Self-transfer to trigger mint
_transfer(msg.sender, msg.sender, amountToMint);
// Reset skipNFT
_setSkipNFT(msg.sender, originalSkipNFT);
// Return held tokens
_transfer(address(this), msg.sender, amountToHold);
// Verify final state
require(addressData.balance == balance);
require(addressData.ownedLength == currentOwnedLength + amount);
}
/// @notice Deploys initial liquidity to Uniswap V2 and V3 pools after whitelist period ends
/// @dev This function:
/// - Can only be called once after 12 hours from launch time
/// - Creates a V2 pair with WETH and deploys liquidity using remaining token supply
/// - Sets aside 0.01 ETH to initialize CULT pool
/// - Stores the V2 pair address for future use
/// - Attempts to initialize CULT pool with remaining ETH
/// @return amountToken The amount of tokens added to V2 liquidity
/// @return amountETH The amount of ETH added to V2 liquidity
/// @return liquidity The amount of V2 LP tokens received
function deployLiquidity() external returns (uint256 amountToken, uint256 amountETH, uint256 liquidity) {
// For liquidity deployment to succeed:
// 1. Must be at least 12 hours after launch time (LAUNCH_TIME)
// 2. Liquidity pair must not already be deployed (liquidityPair must be address(0))
// 3. Contract must have more than 0.01 ETH balance to deploy
// 4. Must have remaining tokens available to deploy, calculated as:
// MAX_SUPPLY - (totalBondingSupply + (1000000000 ether - freeSupply))
// where 1000000000 ether represents total supply and freeSupply is available supply
require(block.timestamp >= LAUNCH_TIME + 12 hours, "Too early for liq");
require(liquidityPair == address(0), "Liq already deployed!");
uint256 ethBalance = address(this).balance;
require(ethBalance > 0.015 ether, "No ETH to deploy");
uint256 remainingSupply = MAX_SUPPLY - (totalBondingSupply + (1000000000 ether - freeSupply));
require(remainingSupply > 0, "No tokens to deploy");
// Create and store the pair address
(,bytes memory d) = factory.call(abi.encodeWithSelector(0xc9c65396, address(this), weth));
liquidityPair = abi.decode(d, (address));
// Set aside small amount for CULT pool initialization
uint256 ethForCult = 0.005 ether;
uint256 ethForOperations = 0.01 ether;
uint256 ethForV2 = ethBalance - ethForCult - ethForOperations;
// Deploy V2 liquidity first
_approve(address(this), address(router), remainingSupply);
// Assembly block that replaces a low-level call to the V2 router interface function:
// addLiquidityETH(address token, uint256 amountTokenDesired, uint256 amountTokenMin, uint256 amountETHMin, address to, uint256 deadline)
// Returns (uint256 amountToken, uint256 amountETH, uint256 liquidity)
assembly {
// Get free memory pointer for our calldata
let ptr := mload(0x40)
// Store the function selector for addLiquidityETH(address,uint256,uint256,uint256,address,uint256)
mstore(ptr, 0xf305d71900000000000000000000000000000000000000000000000000000000)
// Pack the 6 parameters into calldata:
mstore(add(ptr, 0x04), address()) // param1: token - address of this contract
mstore(add(ptr, 0x24), remainingSupply) // param2: amountTokenDesired - remaining tokens for liquidity
mstore(add(ptr, 0x44), 0) // param3: amountTokenMin - accept any amount (0)
mstore(add(ptr, 0x64), 0) // param4: amountETHMin - accept any amount (0)
mstore(add(ptr, 0x84), address()) // param5: to - send LP tokens to this contract
mstore(add(ptr, 0xa4), timestamp()) // param6: deadline - current block timestamp
// Call the router contract's addLiquidityETH function
let success := call(
gas(), // Forward all gas
router, // Address of router contract
ethForV2, // Send ethForV2 amount of ETH with call
ptr, // Pointer to start of input (calldata)
0xc4, // Input size = 4 (selector) + 6 * 32 (params) = 196 bytes
ptr, // Store output at same memory location
0x60 // Output size = 3 * 32 = 96 bytes (three uint256 return values)
)
// If call failed, copy error data and revert
if iszero(success) {
returndatacopy(0, 0, returndatasize())
revert(0, 0)
}
// Store the three return values in their respective variables:
amountToken := mload(ptr) // amount of tokens actually sent
amountETH := mload(add(ptr, 0x20)) // amount of ETH actually sent
liquidity := mload(add(ptr, 0x40)) // amount of LP tokens received
}
// Try to initialize CULT pool with remaining ETH
if (!_initializeCultPoolLogic()) {
// If initialization fails, check if we already have a position
// If we do, that's fine - continue. If not, then revert
require(cultV3Position != 0);
}
return (amountToken, amountETH, liquidity);
emit LiquidityDeployed(amountToken, amountETH, liquidity);
}
// ┌─────────────────────────┐
// │ Operator Only │
// └─────────────────────────┘
/// @notice Collects fees from the V3 liquidity position
/// @dev Only callable by the owner of OPERATOR_TOKEN_ID
/// @param amount0Max The maximum amount of token0 to collect
/// @param amount1Max The maximum amount of token1 to collect
/// @return amount0 The amount of token0 collected
/// @return amount1 The amount of token1 collected
function collectV3Fees(uint128 amount0Max, uint128 amount1Max) external payable returns (uint256 amount0, uint256 amount1) {
// Check if caller owns the operator token
require(_erc721OwnerOf(OPERATOR_NFT, 598) == msg.sender, "Not oper");
// Require at least one amount to be non-zero (matching V3 requirement)
require(amount0Max > 0 || amount1Max > 0, "Amount0Max and amount1Max both 0");
// Assembly block that replaces a low-level call to the INonfungiblePositionManager interface function:
// collect(INonfungiblePositionManager.CollectParams memory params) external payable returns (uint256 amount0, uint256 amount1)
uint256 _cultPosition = cultV3Position;
assembly {
// collect function selector: 0xfc6f7865
let ptr := mload(0x40)
mstore(ptr, 0xfc6f786500000000000000000000000000000000000000000000000000000000)
// Pack parameters
mstore(add(ptr, 0x04), _cultPosition) // tokenId
mstore(add(ptr, 0x24), caller()) // recipient
mstore(add(ptr, 0x44), amount0Max) // amount0Max
mstore(add(ptr, 0x64), amount1Max) // amount1Max
// Make the call
let success := call(
gas(),
positionManager, // target
callvalue(), // forward any ETH value
ptr, // input
0x84, // input size (4 + 4 * 32)
ptr, // output
0x40 // output size (2 * 32 for two uint256 returns)
)
if iszero(success) {
returndatacopy(0, 0, returndatasize())
revert(0, 0)
}
// Load return values
amount0 := mload(ptr)
amount1 := mload(add(ptr, 0x20))
}
emit V3FeesCollected(msg.sender, amount0, amount1);
}
/// @notice Updates the token URI configuration for revealed and unrevealed states
/// @dev Only callable by the owner of OPERATOR_NFT token ID 1
/// @param _uri The new base URI for revealed tokens
/// @param _unrevealedUri The new URI for unrevealed tokens
/// @param _revealed Whether tokens should be revealed or not
function configure(string memory _uri, string memory _unrevealedUri, bool _revealed) public {
require(_erc721OwnerOf(OPERATOR_NFT, 598) == msg.sender, "not oper");
uri = _uri;
unrevealedUri = _unrevealedUri;
revealed = _revealed;
}
/// @notice Failsafe function to initialize the CULT liquidity pool if automatic initialization fails
/// @dev This function can be called manually to retry pool initialization if the automatic process fails
/// @dev The function requires ETH to be sent with the call to provide initial liquidity
/// @dev Will revert if pool is already initialized or if initialization fails
function initializeCultPool() external payable {
require(_initializeCultPoolLogic(), "Pool init failed");
}
/// @dev Override receive to accept ETH transfers
receive() external payable override {
// Accept ETH transfers silently
}
/// @notice Callback function for Uniswap V3 swaps that allows buying CULT tokens
/// @dev This function is called by the Uniswap V3 pool during a swap operation
/// @dev It verifies the caller is the authorized CULT pool and handles token transfers
/// @dev For positive amount0Delta, transfers token0 to pool. For positive amount1Delta, transfers token1 to pool
/// @param amount0Delta The change in token0 balance that needs to be paid for
/// @param amount1Delta The change in token1 balance that needs to be paid for
/// data param not used
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata /* data */
) external override {
assembly {
// First, verify the caller is our expected CULT pool
// This is a security check to ensure only our authorized pool can trigger this callback
let sender := caller()
if iszero(eq(sender, sload(cultPool.slot))) {
// If not authorized, revert with "Unauthed pool" message
mstore(0x00, 0x20) // String offset
mstore(0x20, 0x0e) // String length (14 bytes)
mstore(0x40, "Unauthed pool")
revert(0x00, 0x4e) // Revert with error message
}
// Get token0 address from the pool using token0() function
// Function selector for token0(): 0x0dfe1681
mstore(0x00, 0x0dfe168100000000000000000000000000000000000000000000000000000000)
if iszero(staticcall(gas(), sender, 0x00, 0x04, 0x00, 0x20)) {
revert(0, 0)
}
let token0 := mload(0x00)
// Get token1 address from the pool using token1() function
// Function selector for token1(): 0xd21220a7
mstore(0x00, 0xd21220a700000000000000000000000000000000000000000000000000000000)
if iszero(staticcall(gas(), sender, 0x00, 0x04, 0x00, 0x20)) {
revert(0, 0)
}
let token1 := mload(0x00)
// Prepare ERC20 transfer call data
// Function selector for transfer(address,uint256): 0xa9059cbb
mstore(0x00, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(0x04, sender) // First parameter: recipient (pool address)
// If amount0Delta is positive, we need to send token0 to the pool
if sgt(amount0Delta, 0) {
mstore(0x24, amount0Delta) // Second parameter: amount to transfer
// Execute transfer of token0
pop(call(gas(), token0, 0, 0x00, 0x44, 0x00, 0x00))
}
// If amount1Delta is positive, we need to send token1 to the pool
if sgt(amount1Delta, 0) {
mstore(0x24, amount1Delta) // Second parameter: amount to transfer
// Execute transfer of token1
pop(call(gas(), token1, 0, 0x00, 0x44, 0x00, 0x00))
}
}
}
/// @notice Handles ERC721 token transfers
/// @dev This function is called when an ERC721 token is transferred to this contract
/// @dev It returns the selector for the onERC721Received function
/// @dev necessary to receive v3 liquidity position NFTs
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external pure returns (bytes4) {
return 0x150b7a02; // IERC721Receiver.onERC721Received.selector
}
/// @notice Gets the details of a specific bonding message
/// @dev Unpacks the message data from the packed storage format
/// @param messageId The ID of the message to retrieve
/// @return sender The address that created the message
/// @return timestamp The timestamp when the message was created
/// @return amount The token amount involved in the transaction
/// @return isBuy Whether the message was from a buy (true) or sell (false)
/// @return message The text content of the message
function getMessageDetails(uint256 messageId) external view returns (
address sender,
uint32 timestamp,
uint96 amount,
bool isBuy,
string memory message
) {
require(messageId < totalMessages, "Msg doesnt exist");
BondingMessage memory bondingMsg = bondingMessages[messageId]; // Changed variable name to avoid shadowing
(timestamp, amount, isBuy) = unpackData(bondingMsg.packedData);
return (bondingMsg.sender, timestamp, amount, isBuy, bondingMsg.message);
}
/// @notice Retrieves a batch of bonding messages between specified indices
/// @dev Efficiently fetches multiple messages in a single call to reduce gas costs
/// @param start The starting index of messages to retrieve (inclusive)
/// @param end The ending index of messages to retrieve (inclusive)
/// @return senders Array of addresses that created the messages
/// @return timestamps Array of timestamps when messages were created
/// @return amounts Array of token amounts involved in each transaction
/// @return isBuys Array of booleans indicating if each message was from a buy (true) or sell (false)
/// @return messages Array of message text contents
function getMessagesBatch(uint256 start, uint256 end) external view returns (
address[] memory senders,
uint32[] memory timestamps,
uint96[] memory amounts,
bool[] memory isBuys,
string[] memory messages
) {
require(end >= start, "Invalid range");
require(end < totalMessages, "End out of bounds");
uint256 size = end - start + 1;
senders = new address[](size);
timestamps = new uint32[](size);
amounts = new uint96[](size);
isBuys = new bool[](size);
messages = new string[](size);
for (uint256 i = 0; i < size; i++) {
BondingMessage memory bondingMsg = bondingMessages[start + i]; // Changed variable name to avoid shadowing
senders[i] = bondingMsg.sender;
(timestamps[i], amounts[i], isBuys[i]) = unpackData(bondingMsg.packedData);
messages[i] = bondingMsg.message;
}
}
// ┌───────────────────────────────────────────────┐
// │ PUBLIC FUNCTIONS │
// └───────────────────────────────────────────────┘
/// @notice The transfer and transferFrom functions implement the Wankel Tax system
/// @dev When tokens are transferred to/from the liquidity pool, a tax is applied
/// The tax is collected by the contract and later converted to liquidity for $CULT
/// This creates a positive feedback loop where trading activity strengthens the liquidity
/// The tax rate is set via TAX_RATE constant (in basis points)
/// Tax is not applied to:
/// - Transfers before liquidity pair is set
/// - Transfers involving the contract itself
/// - Regular P2P transfers between users
function transfer(address to, uint256 amount) public virtual override returns (bool) {
// First calculate the tax amount
uint256 taxAmount = amount - _beforeTransfer(msg.sender, to, amount);
_processTaxes(msg.sender, to);
// Finally perform the transfers
if (taxAmount > 0) {
_transfer(msg.sender, address(this), taxAmount);
}
_transfer(msg.sender, to, amount - taxAmount);
return true;
}
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
// Calculate tax and perform transfers
uint256 taxAmount = amount - _beforeTransfer(from, to, amount);
_processTaxes(from, to);
if (taxAmount > 0) {
_transfer(from, address(this), taxAmount);
}
return super.transferFrom(from, to, amount - taxAmount);
}
// ┌───────────────────────────────────────────────┐
// │ INTERNAL FUNCTIONS │
// └───────────────────────────────────────────────┘
/// @notice Internal function to handle tax logic for transfers
/// @dev Checks if a transfer involves the liquidity pool and applies the tax rate if so
/// @param from The sender address
/// @param to The recipient address
/// @param amount The transfer amount
/// @return The final transfer amount after any applicable tax deduction
function _beforeTransfer(address from, address to, uint256 amount) internal view returns (uint256) {
// Don't tax if liquidity pair isn't set yet (initial deployment)
address liq = liquidityPair;
if (liq == address(0)) {
// Liquidity not deployed
// Check if sender has a free mint and ensure they maintain minimum balance
if (freeMint[from] && from != to) {
require(balanceOf(from) - amount >= 1000000 ether, "Cannot transfer free $EXEC until presale ends");
}
return amount;
}
// Don't tax if contract is involved in the transfer
if (from == address(this) || to == address(this)) return amount;
// Apply tax on liquidity pair interactions
if (to == liq || from == liq) {
// uint256 taxAmount = (amount * TAX_RATE) / 10000;
// return amount - taxAmount;
assembly {
// amount - ((amount * TAX_RATE) / 10000)
let tax := div(mul(amount, TAX_RATE), 10000)
amount := sub(amount, tax)
}
}
return amount;
}
// ┌─────────────────────────┐
// │ DN404 Overrides │
// └─────────────────────────┘
/// @notice Returns the number of tokens that correspond to one NFT
/// @dev Each NFT represents 1,000,000 $EXEC tokens (1M tokens = 1 NFT)
/// @return The number of tokens per NFT (1,000,000 * 10^18)
function _unit() internal pure override returns (uint256) {
return 1000000 * 10 ** 18;
}
/// @notice Returns the token URI for a given token ID
/// @dev If the token does not exist or is not revealed, returns the unrevealed URI
/// @param tokenId The ID of the token to get the URI for
/// @return The token URI as a string
function _tokenURI(uint256 tokenId) internal view override returns (string memory) {
// return "https://example.com/token/1";
if (!_exists(tokenId) || !revealed) {
return unrevealedUri;
}
return bytes(uri).length != 0 ? string(abi.encodePacked(uri, LibString.concat(_toString(tokenId),".json"))) : "test";
}
/// @dev Override to set skip NFT default to On (true)
/// @return SkipNFTDefault.On
function _skipNFTDefault() internal pure override returns (SkipNFTDefault) {
return SkipNFTDefault.On;
}
// ┌─────────────────────────┐
// │ Wankel Tax System ♺ │
// └─────────────────────────┘
/**
* @notice The Wankel Tax System, inspired by the rotary engine's tri-chamber design
* @dev A three-phase tax recycling system that:
* 1. Sells accumulated $EXEC tokens (compression)
* 2. Buys $CULT with ETH proceeds (combustion)
* 3. Adds liquidity to UniswapV3 position (exhaust)
*
* Like the Wankel engine's continuous rotary motion, this system
* maintains constant pressure under the V2 liquidity pool by
* churning volume through its three distinct phases. Each phase
* triggers automatically based on accumulated token thresholds.
*/
/**
* @notice Core tax processing function that drives the Wankel Tax System
* @dev Executes the three-phase tax recycling system on token sells:
* 1. Selling EXEC tokens when accumulated (compression phase)
* 2. Buying CULT with ETH proceeds (combustion phase)
* 3. Adding liquidity to UniswapV3 (exhaust phase)
*
* This function acts as the central coordinator, checking balances and
* conditions before selecting and executing the appropriate tax operation.
* It maintains system pressure by limiting operations to 3 per block and
* prevents recursive calls via the swapping flag.
*
* @param from Address tokens are being transferred from
* @param to Address tokens are being transferred to
*/
function _processTaxes(address from, address to) internal {
// Skip if pair isn't initialized yet (for initial liquidity)
address liq = liquidityPair;
if (liq == address(0)) return;
if (from == address(this) || to == address(this)) return;
// Only process on sells (when transferring TO the pair)
uint256 blockSwap = blockSwaps[block.number];
bool isSell = to == liq;
if (isSell && !swapping && blockSwap < 3) {
uint256 execBalance = balanceOf(address(this));
if (execBalance >= MIN_SELL_THRESHOLD) {
swapping = true;
uint256 ethBalance = address(this).balance;
uint256 cultBalance = _erc20BalanceOf(CULT,address(this));
(uint8 operation, uint256 amount) = _selectOperation(ethBalance,cultBalance,execBalance);
if (operation == 0) {
_sellExecTax(execBalance);
} else if (operation == 1) {
_buyCultWithExactEth(amount);
} else {
_increaseCultLiquidity(cultBalance, ethBalance);
}
swapping = false;
blockSwaps[block.number] = blockSwap + 1;
}
}
}
/**
* @notice Determines the next tax operation based on current token balances
* @dev Part of the Wankel Tax System that selects between selling EXEC, buying CULT, or adding liquidity
* @param ethBalance The current ETH balance of the contract
* @param cultBalance The current CULT token balance of the contract
* @param execBalance The current EXEC token balance of the contract
* @return operation The selected operation (0 = sell EXEC, 1 = buy CULT, 2 = add liquidity)
* @return amount The amount of tokens to use in the operation
*/
function _selectOperation(uint256 ethBalance, uint256 cultBalance, uint256 execBalance) internal view returns (uint8 operation, uint256 amount) {
// Priority 1: If we have EXEC and low ETH, sell EXEC
if (execBalance >= MIN_SELL_THRESHOLD && ethBalance < 0.01 ether) {
return (0, execBalance);
}
// Priority 2: If we have ETH but low CULT, buy CULT with ALL available ETH
if (ethBalance >= 0.01 ether && cultBalance < MIN_CULT_THRESHOLD) {
// Use ALL available ETH (minus gas buffer)
uint256 ethToUse = ethBalance - 0.005 ether; // Leave 0.005 ETH for gas
return (1, ethToUse);
}
// Priority 3: If we have both ETH and CULT, add ALL liquidity
if (ethBalance >= 0.01 ether && cultBalance >= MIN_LP_THRESHOLD) {
// Get optimal ratio for our ETH
(uint256 optimalCult,) = _getOptimalCultRatio(ethBalance);
// Use the maximum amount possible while maintaining ratio
uint256 cultToUse = cultBalance > optimalCult ? optimalCult : cultBalance;
return (2, cultToUse);
}
//fallback return
return (0, 0);
}
function _sellExecTax(uint256 tokenBalance) internal {
if (tokenBalance < MIN_SELL_THRESHOLD) return;
_approve(address(this), address(router), tokenBalance);
//Assembly veresion of v2 UniswapRouter interface function
//swapExactTokensForETHSupportingFeeOnTransferTokens(uint256,uint256,address[],address,uint256)
assembly {
let ptr := mload(0x40)
// Store function selector for swapExactTokensForETHSupportingFeeOnTransferTokens
mstore(ptr, 0x791ac94700000000000000000000000000000000000000000000000000000000)
// Store amountIn parameter (tokenBalance)
mstore(add(ptr, 0x04), tokenBalance)
// Store amountOutMin parameter (0 for no minimum)
mstore(add(ptr, 0x24), 0)
// Store path array offset (points to 0xa0 where array data begins)
mstore(add(ptr, 0x44), 0xa0)
// Store recipient address (this contract)
mstore(add(ptr, 0x64), address())
// Store deadline (block.timestamp)
mstore(add(ptr, 0x84), timestamp())
// Store path array length (2 elements)
mstore(add(ptr, 0xa4), 2)
// Store first path address (this contract)
mstore(add(ptr, 0xc4), address())
// Store second path address (WETH)
mstore(add(ptr, 0xe4), weth)
// Make external call to router contract
let success := call(
gas(),
router,//mainnet
0, // No ETH sent
ptr, // Input data pointer
0x104, // Input data size (260 bytes)
0, // Output data pointer
0 // Output data size
)
// Revert on failure and forward error message
if iszero(success) {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}
}
/// @notice Swaps ETH for CULT tokens using Uniswap V3 pool
/// @dev Deposits ETH as WETH, approves pool, and executes swap using assembly for gas optimization (and bytecode size reduction)
/// @param ethAmount The amount of ETH to swap for CULT
/// @return cultBought The amount of CULT tokens received from the swap
function _buyCultWithExactEth(uint256 ethAmount) internal returns (uint256 cultBought) {
_wethDeposit(ethAmount);
address pool = cultPool;
_erc20Approve(weth, pool, ethAmount);
bool zeroForOne = weth < CULT; // true if WETH is token0
//bytes memory data = ""; // No callback needed
//Assembly version of Uniswap V3 Pool swap function
//Interface: swap(address recipient, bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes calldata data)
uint256 result;
assembly {
// Get free memory pointer
let ptr := mload(0x40)
// Store function selector for swap()
mstore(ptr, 0x128acb0800000000000000000000000000000000000000000000000000000000)
// Store recipient address (this contract)
mstore(add(ptr, 0x04), address())
// Store zeroForOne bool (true if WETH is token0)
// Double negation ensures proper boolean value
mstore(add(ptr, 0x24), iszero(iszero(zeroForOne)))
// Store amountSpecified (ethAmount as int256)
mstore(add(ptr, 0x44), ethAmount)
// Store sqrtPriceLimitX96 based on direction
// If zeroForOne: use min price + 1 (4295128740)
// If oneForZero: use max price - 1 (1461446703485210103287273052203988822378723970341)
switch zeroForOne
case 1 {
mstore(add(ptr, 0x64), 4295128740)
}
default {
mstore(add(ptr, 0x64), 1461446703485210103287273052203988822378723970341)
}
// Store empty bytes data
// First word is offset (0xa0)
mstore(add(ptr, 0x84), 0xa0)
// Second word is length (0)
mstore(add(ptr, 0xa4), 0)
// Make external call to pool contract
let callSuccess := call(
gas(), // Forward all gas
pool, // Target contract (Uniswap V3 pool)
0, // No ETH sent
ptr, // Input data pointer
0xc4, // Input size (196 bytes)
ptr, // Output data pointer
0x40 // Output size (64 bytes for two int256)
)
// On successful swap
if callSuccess {
// Load returned amounts
let amount0 := mload(ptr) // First returned int256
let amount1 := mload(add(ptr, 0x20)) // Second returned int256
// Calculate result based on swap direction
// We negate the relevant amount since V3 returns negative for tokens received
switch zeroForOne
case 1 {
result := sub(0, amount1) // Use amount1 if WETH is token0
}
default {
result := sub(0, amount0) // Use amount0 if WETH is token1
}
}
// On failed swap
if iszero(callSuccess) {
// Withdraw WETH by calling withdraw(uint256)
mstore(0x00, 0x2e1a7d4d) // withdraw() selector
mstore(0x04, ethAmount) // amount to withdraw
pop(call(gas(), weth, 0, 0x00, 0x24, 0x00, 0x00))
result := 0
}
}
cultBought = result;
}
/// @notice Calculates the optimal amount of CULT tokens needed for a given ETH amount based on current pool price
/// @dev Uses the Uniswap V3 pool's current sqrt price to determine the ideal ratio for liquidity provision
/// @dev The calculation is: optimalCult = ethAmount * price / 1e18, where price = (sqrtPriceX96^2 * 1e18) >> 192
/// @param ethAmount The amount of ETH to calculate the ratio for
/// @return optimalCult The optimal amount of CULT tokens needed
/// @return price The current price of CULT in terms of ETH (scaled by 1e18)
function _getOptimalCultRatio(uint256 ethAmount) internal view returns (uint256 optimalCult, uint256 price) {
(uint160 sqrtPriceX96,) = _staticcallSlot0Values(cultPool);
assembly {
// Calculate price = (sqrtPriceX96 * sqrtPriceX96 * 1e18) >> 192
let priceX96 := mul(sqrtPriceX96, sqrtPriceX96)
price := shr(192, mul(priceX96, exp(10, 18)))
// Calculate optimalCult = (ethAmount * price) / 1e18
optimalCult := div(mul(ethAmount, price), exp(10, 18))
}
}
function _increaseCultLiquidity(uint256 cultBalance, uint256 ethBalance) internal returns (bool success) {
if (cultBalance == 0 || ethBalance < 0.01 ether) {
return false;
}
if (cultV3Position == 0) {
return false;
}
// Use everything except gas buffer
uint256 ethAmount = ethBalance - 0.01 ether;
if (ethAmount < 0.01 ether) {
return false;
}
_wethDeposit(ethAmount);
_erc20Approve(CULT, address(positionManager), cultBalance);
_erc20Approve(weth, address(positionManager), ethAmount);
// Store immutable values in memory before assembly block
address posAddr = address(positionManager);
assembly {
// Prepare calldata for increaseLiquidity
let ptr := mload(0x40)
// Interface: function increaseLiquidity(IncreaseLiquidityParams calldata params) external payable returns (uint128 liquidity, uint256 amount0, uint256 amount1)
// Where IncreaseLiquidityParams is:
// struct IncreaseLiquidityParams {
// uint256 tokenId; // The ID of the NFT position to increase liquidity for
// uint256 amount0Desired; // The desired amount of token0 to be spent
// uint256 amount1Desired; // The desired amount of token1 to be spent
// uint256 amount0Min; // The minimum amount of token0 to spend
// uint256 amount1Min; // The minimum amount of token1 to spend
// uint256 deadline; // The time by which the transaction must be included
// }
// Function selector for increaseLiquidity
mstore(ptr, 0x219f5d1700000000000000000000000000000000000000000000000000000000)
// Store tokenId parameter
mstore(add(ptr, 0x04), sload(cultV3Position.slot))
// Store amount0Desired and amount1Desired based on token ordering
mstore(add(ptr, 0x24), cultBalance) // amount0Desired = cultBalance
mstore(add(ptr, 0x44), ethAmount) // amount1Desired = ethAmount
// Store amount0Min and amount1Min as 0 (no minimum amounts required)
mstore(add(ptr, 0x64), 0) // amount0Min = 0
mstore(add(ptr, 0x84), 0) // amount1Min = 0
// Store deadline as current block timestamp
mstore(add(ptr, 0xa4), timestamp())
// Make the call to increaseLiquidity
let callSuccess := call(
gas(),
posAddr, // position manager address
0, // no ETH sent
ptr, // calldata pointer
0xc4, // calldata size (4 + 6 * 32)
0, // output offset (ignore return values)
0 // output size (ignore return values)
)
// On success, handle any unused WETH
if callSuccess {
// Interface: function balanceOf(address account) external view returns (uint256)
let wethPtr := mload(0x40)
mstore(wethPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000)
mstore(add(wethPtr, 0x04), address())
// Check remaining WETH balance
pop(staticcall(
gas(),
weth,
wethPtr,
0x24, // input size (4 + 32)
0, // output offset
0x20 // output size (32 bytes)
))
let unusedWeth := mload(0)
if gt(unusedWeth, 0) {
// Interface: function withdraw(uint256 wad) external
mstore(wethPtr, 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000)
mstore(add(wethPtr, 0x04), unusedWeth)
pop(call(gas(), weth, 0, wethPtr, 0x24, 0, 0))
}
success := 1
}
// On failure, withdraw all WETH
// Interface: function withdraw(uint256 wad) external
let wethPtr := mload(0x40)
mstore(wethPtr, 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000)
mstore(add(wethPtr, 0x04), ethAmount)
pop(call(gas(), weth, 0, wethPtr, 0x24, 0, 0))
success := 0
}
return success;
}
// ┌─────────────────────────┐
// │ Low Level Calls │
// └─────────────────────────┘
// ┌───────────┐
// │ erc20 │
// └───────────┘
/**
* @notice Low level call to ERC20 approve function
* @dev Uses assembly to make a direct call to the token's approve function
* Reverts if the call fails, bubbling up any error message
* @param token The address of the ERC20 token contract
* @param spender The address being approved to spend tokens
* @param amount The amount of tokens being approved
*/
function _erc20Approve(address token, address spender, uint256 amount) internal {
assembly {
let ptr := mload(0x40) // get free memory pointer
// keccak256("approve(address,uint256)") = 0x095ea7b3...
mstore(ptr, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x04), and(spender, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(ptr, 0x24), amount)
let success := call(
gas(), // gas
token, // to
0, // value
ptr, // input offset
0x44, // input size (4 + 32 + 32)
0, // output offset
0 // output size
)
// If call fails, bubble up the revert
if iszero(success) {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}
}
/**
* @notice Low level call to ERC20 balanceOf function
* @dev Uses assembly to make a direct staticcall to the token's balanceOf function
* This optimizes bytecode size by avoiding ABI encoding/decoding
* Reverts if the call fails
* @param token The address of the ERC20 token contract to query
* @param account The address to check the balance of
* @return result The token balance of the account
*/
function _erc20BalanceOf(address token, address account) internal view returns (uint256 result) {
assembly {
// Store the function selector and argument in memory
mstore(0x00, 0x70a0823100000000000000000000000000000000000000000000000000000000)
mstore(0x04, and(account, 0xffffffffffffffffffffffffffffffffffffffff))
// Perform the staticcall
let success := staticcall(
gas(), // gas
token, // to
0x00, // input offset
0x24, // input size (4 + 32)
0x00, // output offset
0x20 // output size (32 bytes)
)
// Check if the call was successful
if iszero(success) {
revert(0, 0)
}
// Return value is already in memory at 0x00, load it to the named return variable
result := mload(0x00)
}
}
/**
* @notice Low level call to ERC20 transfer function
* @dev Uses assembly to make a direct call to the token's transfer function
* Reverts if the call fails, bubbling up any error message
* @param token The address of the ERC20 token contract
* @param to The address to transfer the tokens to
* @param amount The amount of tokens to transfer
*/
function _erc20Transfer(address token, address to, uint256 amount) internal {
assembly {
let ptr := mload(0x40)
// Function selector for transfer(address,uint256)
mstore(ptr, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x04), and(to, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(ptr, 0x24), amount)
let success := call(
gas(),
token,
0,
ptr,
0x44, // input length = 4 + 32 + 32
ptr, // Store output in the same location
0x20 // Expect 32 bytes (bool) return
)
// Check both call success and returned boolean
if iszero(success) {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
// Load the returned boolean
let returnValue := mload(ptr)
if iszero(returnValue) {
revert(0, 0)
}
}
}
// ┌───────────┐
// │ erc721 │
// └───────────┘
/**
* @notice Low level call to ERC721 ownerOf function
* @dev Uses assembly to make a direct staticcall to the token's ownerOf function
* This optimizes bytecode size by avoiding ABI encoding/decoding
* Reverts if the call fails
* @param collection The address of the ERC721 token contract
* @param tokenId The ID of the token to check ownership of
* @return owner The address of the token owner
*/
function _erc721OwnerOf(address collection, uint256 tokenId) internal view returns (address owner) {
assembly {
// Store the function selector and argument in memory
// keccak256("ownerOf(uint256)") = 0x6352211e
mstore(0x00, 0x6352211e00000000000000000000000000000000000000000000000000000000)
mstore(0x04, tokenId)
// Perform the staticcall
let success := staticcall(
gas(), // gas
collection, // to
0x00, // input offset
0x24, // input size (4 + 32)
0x00, // output offset
0x20 // output size (32 bytes)
)
// Check if the call was successful
if iszero(success) {
revert(0, 0)
}
// Load the owner address from memory
// Note: We mask the upper bits to ensure it's a valid address
owner := and(mload(0x00), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
// ┌───────────┐
// │ weth │
// └───────────┘
/**
* @notice Low level call to WETH deposit function
* @dev Uses assembly to make a direct call to the WETH contract's deposit function
* Reverts if the call fails, bubbling up any error message
* @param amount The amount of ETH to wrap in WETH
* return not used
*/
function _wethDeposit(uint256 amount) internal {
assembly {
// deposit() selector = 0xd0e30db0
mstore(0x00, 0xd0e30db000000000000000000000000000000000000000000000000000000000)
let success := call(
gas(), // gas
weth, // to
amount, // value (ETH to wrap)
0x00, // input offset
0x04, // input size (just selector)
0x00, // output offset
0x00 // output size
)
if iszero(success) {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}
}
/**
* @notice Low level call to WETH withdraw function
* @dev Uses assembly to make a direct call to the WETH contract's withdraw function
* Reverts if the call fails, bubbling up any error message
* @param amount The amount of WETH to withdraw
* return not used
*/
function _wethWithdraw(uint256 amount) internal {
assembly {
// withdraw(uint256) selector = 0x2e1a7d4d
mstore(0x00, 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000)
mstore(0x04, amount)
let success := call(
gas(), // gas
weth, // to
0, // value
0x00, // input offset
0x24, // input size (4 + 32)
0x00, // output offset
0x00 // output size
)
if iszero(success) {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}
}
// ┌───────────┐
// │ V3 Pool │
// └───────────┘
/// @notice Reads sqrtPriceX96 and tick from a Uniswap V3 pool using a static call
/// @param pool The address of the V3 pool
/// @return sqrtPriceX96 The current price as a Q64.96
/// @return tick The current tick function _staticcallSlot0Values(ad
function _staticcallSlot0Values(address pool) internal view returns (uint160 sqrtPriceX96, int24 tick) {
// slot0() function selector: 3850c7bd
assembly {
// Prepare calldata for staticcall (4 bytes for function selector)
mstore(0x0, 0x3850c7bd00000000000000000000000000000000000000000000000000000000)
// Perform staticcall
// First 32 bytes: sqrtPriceX96 (uint160)
// Next 32 bytes: tick (int24)
let success := staticcall(gas(), pool, 0x0, 0x4, 0x0, 0x40)
// Revert if call failed
if iszero(success) {
revert(0, 0)
}
// Load results
sqrtPriceX96 := mload(0x0) // First 32 bytes contain sqrtPriceX96
tick := mload(0x20) // Next 32 bytes contain tick
}
}
/// @notice Reads the tick spacing from a Uniswap V3 pool using a static call
/// @dev Makes a low-level staticcall to get the tick spacing value which determines valid tick intervals
/// @dev Uses the tickSpacing() function selector (0xd0c93a7c) to make the call
/// @param pool The address of the V3 pool to query
/// @return spacing The tick spacing value for the pool (e.g. 60 for 0.6% fee tier)
function _staticcallTickSpacing(address pool) internal view returns (int24 spacing) {
assembly {
// Store the function selector for tickSpacing()
mstore(0, 0xd0c93a7c00000000000000000000000000000000000000000000000000000000)
// Make the call
let success := staticcall(gas(), pool, 0, 4, 0, 32)
if iszero(success) { revert(0, 0) }
// Load the result
spacing := mload(0)
}
}
// ┌─────────────────────────┐
// │ Strings │
// └─────────────────────────┘
/**
* @dev Converts a uint256 to its ASCII string decimal representation.
*/
function _toString(uint256 value) internal view virtual returns (string memory str) {
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. Total: 5 * 0x20 = 0xa0.
let m := add(mload(0x40), 0xa0)
// Update the free memory pointer to allocate.
mstore(0x40, m)
// Assign the `str` to the end.
str := sub(m, 0x20)
// Zeroize the slot after the string.
mstore(str, 0)
// Cache the end of the memory to calculate the length later.
let end := str
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
// prettier-ignore
for { let temp := value } 1 {} {
str := sub(str, 1)
// Write the character to the pointer.
// The ASCII index of the '0' character is 48.
mstore8(str, add(48, mod(temp, 10)))
// Keep dividing `temp` until zero.
temp := div(temp, 10)
// prettier-ignore
if iszero(temp) { break }
}
let length := sub(end, str)
// Move the pointer 32 bytes leftwards to make room for the length.
str := sub(str, 0x20)
// Store the length.
mstore(str, length)
}
}
// ┌─────────────────────────┐
// │ Messages │
// └─────────────────────────┘
/// @notice Packs timestamp, amount and buy flag into a single uint128 value
/// @dev Packs data in the following format:
/// - timestamp: highest 32 bits
/// - amount: middle 95 bits (in ether, can store up to ~3.94e28 ether)
/// - isBuy flag: lowest bit
/// @param timestamp The timestamp to pack (32 bits)
/// @param amount The amount to pack (95 bits)
/// @param isBuy The buy flag to pack (1 bit)
/// @return packed The packed uint128 containing all data
function packData(uint32 timestamp, uint96 amount, bool isBuy) internal pure returns (uint128) {
return uint128(
(uint128(timestamp) << 96) | // timestamp in highest 32 bits
(uint128(amount) << 1) | // amount in middle 95 bits
(isBuy ? 1 : 0) // isBuy flag in lowest bit
);
}
/// @notice Unpacks a uint128 value into timestamp, amount and buy flag components
/// @dev Unpacks data from the following format:
/// - timestamp: highest 32 bits
/// - amount: middle 95 bits
/// - isBuy flag: lowest bit
/// @param packed The packed uint128 to unpack
/// @return timestamp The unpacked timestamp (32 bits)
/// @return amount The unpacked amount (95 bits)
/// @return isBuy The unpacked buy flag (1 bit)
// function unpackData(uint128 packed) internal pure returns (uint32 timestamp, uint96 amount, bool isBuy) {
// timestamp = uint32(packed >> 96);
// amount = uint96(packed >> 1);
// isBuy = packed & 1 == 1;
// }
function unpackData(uint128 packed) internal pure returns (uint32 timestamp, uint96 amount, bool isBuy) {
timestamp = uint32(packed >> 96);
uint256 amountMask = (uint256(1) << 95) - 1;
amount = uint96((packed >> 1) & amountMask);
isBuy = (packed & 1) == 1;
}
// ┌─────────────────────────┐
// │ Bonding Curve Math │
// └─────────────────────────┘
/// **************************************************
/// @dev The bonding curve formula is: P(s) = 12e-9 * S^3 + 4e-9 * S^2 + 4e-9 + 0.025 ether
/// where:
/// - S is the normalized supply (current supply / 10M tokens)
/// - The polynomial terms create an accelerating price curve
/// - Base price starts at 0.025 ETH per 10M tokens
/// **************************************************
/// @notice Calculates the integral of the bonding curve price function
/// @dev Uses numerical integration to find the area under the price curve
/// @param lowerBound The lower bound of the supply range to integrate
/// @param upperBound The upper bound of the supply range to integrate
/// @return integral The calculated integral value in ETH
function calculateIntegral(uint256 lowerBound, uint256 upperBound) internal pure returns (uint256) {
require(upperBound >= lowerBound, "Invalid bounds");
return _calculateIntegralFromZero(upperBound) - _calculateIntegralFromZero(lowerBound);
}
/// @notice Calculates the integral of the bonding curve price function from zero to a given supply
/// @dev Uses numerical integration to find the area under the price curve
/// @param supply The upper bound of the supply range to integrate
/// @return integral The calculated integral value in ETH
function _calculateIntegralFromZero(uint256 supply) internal pure returns (uint256) {
// Scale down to hundreds since price curve is per 10M tokens
uint256 scaledSupplyWad = supply / 1e7;
// Base price integral dewadded by 1e18
uint256 basePart = INITIAL_PRICE * scaledSupplyWad / 1e18;
// Calculate integral terms with scaled numbers
uint256 quarticTerm = FixedPointMathLib.mulWad(
//12 / 4
3 gwei,
FixedPointMathLib.mulWad(
FixedPointMathLib.mulWad(
FixedPointMathLib.mulWad(scaledSupplyWad, scaledSupplyWad),
scaledSupplyWad
),
scaledSupplyWad
)
);
uint256 cubicTerm = FixedPointMathLib.mulWad(
1333333333, //4/3 * 1gwei
FixedPointMathLib.mulWad(
FixedPointMathLib.mulWad(scaledSupplyWad, scaledSupplyWad),
scaledSupplyWad
)
);
uint256 quadraticTerm = FixedPointMathLib.mulWad(
2 gwei,
FixedPointMathLib.mulWad(scaledSupplyWad, scaledSupplyWad)
);
// Scale the result back up by 1e8
return basePart + quarticTerm + cubicTerm + quadraticTerm;
}
// ┌───────────────────────────────────────────────┐
// │ Private Functions │
// └───────────────────────────────────────────────┘
/// @notice Initializes the CULT pool logic
/// @dev Initializes the CULT pool by buying CULT with half of the ETH
/// @dev Wraps the other half for the position
/// @dev Approves tokens for the position manager
/// @dev Calculates the tick ranges for the position
/// @dev Creates a new position in the V3 pool
function _initializeCultPoolLogic() private returns (bool success) {
if (cultV3Position != 0 || cultPool == address(0)) {
return false;
}
// Get current tick and calculate proper range
int24 tickSpacing = _staticcallTickSpacing(cultPool);
(,int24 currentTick) = _staticcallSlot0Values(cultPool);
// Calculate ticks ±16 spacing units from current tick
int24 tickRange = tickSpacing * 16;
// Buy CULT with half the ETH
uint256 cultBought = _buyCultWithExactEth(0.0025 ether);
// Wrap the other half for the position
//IWETH(weth).deposit{value: 0.005 ether}();
_wethDeposit(0.0025 ether);
// Approve tokens for position manager
_erc20Approve(CULT, address(positionManager), cultBought);
_erc20Approve(weth, address(positionManager), 0.0025 ether);
// Calculate tick ranges
int24 tickLower = ((currentTick - tickRange) / tickSpacing) * tickSpacing;
int24 tickUpper = ((currentTick + tickRange) / tickSpacing) * tickSpacing;
address _CULT = CULT;
address _posMan = address(positionManager);
bool isToken0 = CULT < weth;
assembly {
// Setup call to positionManager.mint()
let ptr := mload(0x40)
// Function selector for mint(MintParams)
mstore(ptr, 0x8831645600000000000000000000000000000000000000000000000000000000)
// Pack parameters based on token order
// MintParams struct:
// token0: address
// token1: address
// fee: uint24
// tickLower: int24
// tickUpper: int24
// amount0Desired: uint256
// amount1Desired: uint256
// amount0Min: uint256
// amount1Min: uint256
// recipient: address
// deadline: uint256
switch isToken0
case 1 {
// If CULT is token0
mstore(add(ptr, 0x04), _CULT) // token0 = CULT
mstore(add(ptr, 0x24), weth) // token1 = WETH
mstore(add(ptr, 0xa4), cultBought) // amount0Desired = cultBought
mstore(add(ptr, 0xc4), 5000000000000000) // amount1Desired = 0.005 ETH
}
default {
// If WETH is token0
mstore(add(ptr, 0x04), weth) // token0 = WETH
mstore(add(ptr, 0x24), _CULT) // token1 = CULT
mstore(add(ptr, 0xa4), 5000000000000000) // amount0Desired = 0.005 ETH
mstore(add(ptr, 0xc4), cultBought) // amount1Desired = cultBought
}
// Common parameters
mstore(add(ptr, 0x44), POOL_FEE) // fee
mstore(add(ptr, 0x64), tickLower) // tickLower
mstore(add(ptr, 0x84), tickUpper) // tickUpper
mstore(add(ptr, 0xe4), 0) // amount0Min
mstore(add(ptr, 0x104), 0) // amount1Min
mstore(add(ptr, 0x124), address()) // recipient = this contract
mstore(add(ptr, 0x144), timestamp()) // deadline = current block timestamp
// Make external call to position manager
// Input size is 0x164 (356 bytes) - full MintParams struct
// Output size is 0xA0 (160 bytes) - returns (tokenId, liquidity, amount0, amount1)
success := call(gas(), _posMan, 0, ptr, 0x164, ptr, 0xA0)
if success {
// Store returned tokenId as cultV3Position
sstore(cultV3Position.slot, mload(ptr))
// Handle unused WETH refund if mint succeeded
if iszero(lt(returndatasize(), 0xA0)) {
// Get actual amount1 used from return data
let amount1 := mload(add(ptr, 0x80))
let unusedWeth := sub(5000000000000000, amount1)
// Safety check on unusedWeth amount
if gt(unusedWeth, 0) {
if gt(unusedWeth, 5000000000000000) {
unusedWeth := 0
}
// Call WETH.withdraw() with unused amount
mstore(0x00, 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000)
mstore(0x04, unusedWeth)
pop(call(gas(), weth, 0, 0x00, 0x24, 0x00, 0x00))
}
}
}
// If mint failed, withdraw all WETH
if iszero(success) {
mstore(0x00, 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000)
mstore(0x04, 5000000000000000)
pop(call(gas(), weth, 0, 0x00, 0x24, 0x00, 0x00))
}
}
}
// ┌───────────────────────────────────────────────┐
// │ VIEW AND PURE │
// └───────────────────────────────────────────────┘
function name() public pure override returns (string memory) {
return "CULT EXECUTIVES";
}
function symbol() public pure override returns (string memory) {
return "EXEC";
}
/// @notice Calculates the cost of a given amount of EXEC tokens
/// @dev Uses the bonding curve formula to calculate the cost
/// @param amount The amount of EXEC tokens to calculate cost for
/// @return cost The calculated cost in ETH
function calculateCost(uint256 amount) public view returns (uint256) {
return calculateIntegral(totalBondingSupply, totalBondingSupply + amount);
}
/// @notice Calculates the refund amount for a given amount of EXEC tokens
/// @dev Uses the bonding curve formula to calculate the refund
/// @param amount The amount of EXEC tokens to calculate refund for
/// @return refund The calculated refund amount in ETH
function calculateRefund(uint256 amount) public view returns (uint256) {
return calculateIntegral(totalBondingSupply - amount, totalBondingSupply);
}
/// @notice Returns the token IDs owned by a specific address
/// @dev Retrieves all token IDs owned by the address
/// @param owner The address to check for owned tokens
/// @return tokenIds The array of token IDs owned by the address
function getOwnerTokens(address owner) public view returns (uint256[] memory) {
uint256 ownerBalanceLength = _balanceOfNFT(owner);
return _ownedIds(owner, 0, ownerBalanceLength);
}
/// @notice Returns the token URI for a given token ID
/// @dev Retrieves the token URI for the specified token ID
/// @param tokenId The ID of the token to get the URI for
/// @return uri The token URI for the specified token ID
function tokenURI(uint256 tokenId) public view returns (string memory) {
return _tokenURI(tokenId);
}
/// @notice Verifies if an address is whitelisted using a Merkle proof
/// @dev Uses the Merkle proof to verify if the address is whitelisted
/// @param proof The Merkle proof for the address
/// @param account The address to verify
/// @return isWhitelisted True if the address is whitelisted, false otherwise
function isWhitelisted(bytes32[] calldata proof, address account) public view returns (bool) {
return MerkleProofLib.verify(
proof,
currentRoot(),
// keccak256(abi.encodePacked(account))
keccak256(abi.encodePacked(bytes20(account)))
);
}
/// @notice Returns the current tier based on the timestamp
/// @dev Calculates the current tier based on the timestamp
/// @return currentTier The current tier
function getCurrentTier() public view returns (uint256) {
if (block.timestamp < LAUNCH_TIME) return 0;
uint256 hoursSinceLaunch = (block.timestamp - LAUNCH_TIME) / 1 hours;
return hoursSinceLaunch >= tierRoots.length ? tierRoots.length - 1 : hoursSinceLaunch;
}
/// @notice Returns the current tier's Merkle root
/// @dev Retrieves the Merkle root for the current tier
/// @return root The Merkle root for the current tier
function currentRoot() public view returns (bytes32) {
return tierRoots[getCurrentTier()];
}
/// @notice Calculates the amount of ETH required to purchase a given amount of EXEC tokens
/// @dev Uses the bonding curve formula to calculate the cost
/// @param execAmount The amount of EXEC tokens to calculate cost for
/// @return ethAmount The calculated cost in ETH
function getEthForExec(uint256 execAmount) public view returns (uint256 ethAmount) {
require(execAmount <= totalBondingSupply, "Exceeds bonding");
return calculateRefund(execAmount);
}
/// @notice Calculates the amount of EXEC tokens that can be purchased for a given amount of ETH
/// @dev Uses binary search to find the amount of EXEC that costs closest to the given ETH amount
/// @param ethAmount The amount of ETH to calculate the amount of EXEC for
/// @return execAmount The calculated amount of EXEC tokens
function getExecForEth(uint256 ethAmount) public view returns (uint256 execAmount) {
// If price is too low, return max possible
uint256 remainingSupply = MAX_SUPPLY - LIQUIDITY_RESERVE - totalBondingSupply;
if (calculateCost(remainingSupply) <= ethAmount) {
return remainingSupply;
}
// Binary search for the amount of EXEC that costs closest to ethAmount
uint256 low = 0;
uint256 high = remainingSupply;
while (low < high) {
uint256 mid = (low + high + 1) / 2;
uint256 cost = calculateCost(mid);
if (cost <= ethAmount) {
low = mid;
} else {
high = mid - 1;
}
}
return low;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error ExpOverflow();
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error FactorialOverflow();
/// @dev The operation failed, due to an overflow.
error RPowOverflow();
/// @dev The mantissa is too big to fit.
error MantissaOverflow();
/// @dev The operation failed, due to an multiplication overflow.
error MulWadFailed();
/// @dev The operation failed, due to an multiplication overflow.
error SMulWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error DivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error SDivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error MulDivFailed();
/// @dev The division failed, as the denominator is zero.
error DivFailed();
/// @dev The full precision multiply-divide operation failed, either due
/// to the result being larger than 256 bits, or a division by a zero.
error FullMulDivFailed();
/// @dev The output is undefined, as the input is less-than-or-equal to zero.
error LnWadUndefined();
/// @dev The input outside the acceptable domain.
error OutOfDomain();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The scalar of ETH and most ERC20s.
uint256 internal constant WAD = 1e18;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIMPLIFIED FIXED POINT OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if gt(x, div(not(0), y)) {
if y {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
}
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function sMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require((x == 0 || z / x == y) && !(x == -1 && y == type(int256).min))`.
if iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) {
mstore(0x00, 0xedcd4dd4) // `SMulWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(z, WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawMulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawSMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up.
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if iszero(eq(div(z, y), x)) {
if y {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
}
z := add(iszero(iszero(mod(z, WAD))), div(z, WAD))
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up, but without overflow checks.
function rawMulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`.
if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function sDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, WAD)
// Equivalent to `require(y != 0 && ((x * WAD) / WAD == x))`.
if iszero(mul(y, eq(sdiv(z, WAD), x))) {
mstore(0x00, 0x5c43740d) // `SDivWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(z, y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawDivWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawSDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up.
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`.
if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up, but without overflow and divide by zero checks.
function rawDivWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `x` to the power of `y`.
/// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`.
/// Note: This function is an approximation.
function powWad(int256 x, int256 y) internal pure returns (int256) {
// Using `ln(x)` means `x` must be greater than 0.
return expWad((lnWad(x) * y) / int256(WAD));
}
/// @dev Returns `exp(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
/// Note: This function is an approximation. Monotonically increasing.
function expWad(int256 x) internal pure returns (int256 r) {
unchecked {
// When the result is less than 0.5 we return zero.
// This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`.
if (x <= -41446531673892822313) return r;
/// @solidity memory-safe-assembly
assembly {
// When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as
// an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`.
if iszero(slt(x, 135305999368893231589)) {
mstore(0x00, 0xa37bfec9) // `ExpOverflow()`.
revert(0x1c, 0x04)
}
}
// `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96`
// for more intermediate precision and a binary basis. This base conversion
// is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
x = (x << 78) / 5 ** 18;
// Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
// of two such that exp(x) = exp(x') * 2**k, where k is an integer.
// Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
x = x - k * 54916777467707473351141471128;
// `k` is in the range `[-61, 195]`.
// Evaluate using a (6, 7)-term rational approximation.
// `p` is made monic, we'll multiply by a scale factor later.
int256 y = x + 1346386616545796478920950773328;
y = ((y * x) >> 96) + 57155421227552351082224309758442;
int256 p = y + x - 94201549194550492254356042504812;
p = ((p * y) >> 96) + 28719021644029726153956944680412240;
p = p * x + (4385272521454847904659076985693276 << 96);
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
int256 q = x - 2855989394907223263936484059900;
q = ((q * x) >> 96) + 50020603652535783019961831881945;
q = ((q * x) >> 96) - 533845033583426703283633433725380;
q = ((q * x) >> 96) + 3604857256930695427073651918091429;
q = ((q * x) >> 96) - 14423608567350463180887372962807573;
q = ((q * x) >> 96) + 26449188498355588339934803723976023;
/// @solidity memory-safe-assembly
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial won't have zeros in the domain as all its roots are complex.
// No scaling is necessary because p is already `2**96` too large.
r := sdiv(p, q)
}
// r should be in the range `(0.09, 0.25) * 2**96`.
// We now need to multiply r by:
// - The scale factor `s ≈ 6.031367120`.
// - The `2**k` factor from the range reduction.
// - The `1e18 / 2**96` factor for base conversion.
// We do this all at once, with an intermediate result in `2**213`
// basis, so the final right shift is always by a positive amount.
r = int256(
(uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)
);
}
}
/// @dev Returns `ln(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
/// Note: This function is an approximation. Monotonically increasing.
function lnWad(int256 x) internal pure returns (int256 r) {
/// @solidity memory-safe-assembly
assembly {
// We want to convert `x` from `10**18` fixed point to `2**96` fixed point.
// We do this by multiplying by `2**96 / 10**18`. But since
// `ln(x * C) = ln(x) + ln(C)`, we can simply do nothing here
// and add `ln(2**96 / 10**18)` at the end.
// Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 ^ log2(x)`.
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// We place the check here for more optimal stack operations.
if iszero(sgt(x, 0)) {
mstore(0x00, 0x1615e638) // `LnWadUndefined()`.
revert(0x1c, 0x04)
}
// forgefmt: disable-next-item
r := xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff))
// Reduce range of x to (1, 2) * 2**96
// ln(2^k * x) = k * ln(2) + ln(x)
x := shr(159, shl(r, x))
// Evaluate using a (8, 8)-term rational approximation.
// `p` is made monic, we will multiply by a scale factor later.
// forgefmt: disable-next-item
let p := sub( // This heavily nested expression is to avoid stack-too-deep for via-ir.
sar(96, mul(add(43456485725739037958740375743393,
sar(96, mul(add(24828157081833163892658089445524,
sar(96, mul(add(3273285459638523848632254066296,
x), x))), x))), x)), 11111509109440967052023855526967)
p := sub(sar(96, mul(p, x)), 45023709667254063763336534515857)
p := sub(sar(96, mul(p, x)), 14706773417378608786704636184526)
p := sub(mul(p, x), shl(96, 795164235651350426258249787498))
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
// `q` is monic by convention.
let q := add(5573035233440673466300451813936, x)
q := add(71694874799317883764090561454958, sar(96, mul(x, q)))
q := add(283447036172924575727196451306956, sar(96, mul(x, q)))
q := add(401686690394027663651624208769553, sar(96, mul(x, q)))
q := add(204048457590392012362485061816622, sar(96, mul(x, q)))
q := add(31853899698501571402653359427138, sar(96, mul(x, q)))
q := add(909429971244387300277376558375, sar(96, mul(x, q)))
// `p / q` is in the range `(0, 0.125) * 2**96`.
// Finalization, we need to:
// - Multiply by the scale factor `s = 5.549…`.
// - Add `ln(2**96 / 10**18)`.
// - Add `k * ln(2)`.
// - Multiply by `10**18 / 2**96 = 5**18 >> 78`.
// The q polynomial is known not to have zeros in the domain.
// No scaling required because p is already `2**96` too large.
p := sdiv(p, q)
// Multiply by the scaling factor: `s * 5**18 * 2**96`, base is now `5**18 * 2**192`.
p := mul(1677202110996718588342820967067443963516166, p)
// Add `ln(2) * k * 5**18 * 2**192`.
// forgefmt: disable-next-item
p := add(mul(16597577552685614221487285958193947469193820559219878177908093499208371, sub(159, r)), p)
// Add `ln(2**96 / 10**18) * 5**18 * 2**192`.
p := add(600920179829731861736702779321621459595472258049074101567377883020018308, p)
// Base conversion: mul `2**18 / 2**192`.
r := sar(174, p)
}
}
/// @dev Returns `W_0(x)`, denominated in `WAD`.
/// See: https://en.wikipedia.org/wiki/Lambert_W_function
/// a.k.a. Product log function. This is an approximation of the principal branch.
/// Note: This function is an approximation. Monotonically increasing.
function lambertW0Wad(int256 x) internal pure returns (int256 w) {
// forgefmt: disable-next-item
unchecked {
if ((w = x) <= -367879441171442322) revert OutOfDomain(); // `x` less than `-1/e`.
(int256 wad, int256 p) = (int256(WAD), x);
uint256 c; // Whether we need to avoid catastrophic cancellation.
uint256 i = 4; // Number of iterations.
if (w <= 0x1ffffffffffff) {
if (-0x4000000000000 <= w) {
i = 1; // Inputs near zero only take one step to converge.
} else if (w <= -0x3ffffffffffffff) {
i = 32; // Inputs near `-1/e` take very long to converge.
}
} else if (uint256(w >> 63) == uint256(0)) {
/// @solidity memory-safe-assembly
assembly {
// Inline log2 for more performance, since the range is small.
let v := shr(49, w)
let l := shl(3, lt(0xff, v))
l := add(or(l, byte(and(0x1f, shr(shr(l, v), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000)), 49)
w := sdiv(shl(l, 7), byte(sub(l, 31), 0x0303030303030303040506080c13))
c := gt(l, 60)
i := add(2, add(gt(l, 53), c))
}
} else {
int256 ll = lnWad(w = lnWad(w));
/// @solidity memory-safe-assembly
assembly {
// `w = ln(x) - ln(ln(x)) + b * ln(ln(x)) / ln(x)`.
w := add(sdiv(mul(ll, 1023715080943847266), w), sub(w, ll))
i := add(3, iszero(shr(68, x)))
c := iszero(shr(143, x))
}
if (c == uint256(0)) {
do { // If `x` is big, use Newton's so that intermediate values won't overflow.
int256 e = expWad(w);
/// @solidity memory-safe-assembly
assembly {
let t := mul(w, div(e, wad))
w := sub(w, sdiv(sub(t, x), div(add(e, t), wad)))
}
if (p <= w) break;
p = w;
} while (--i != uint256(0));
/// @solidity memory-safe-assembly
assembly {
w := sub(w, sgt(w, 2))
}
return w;
}
}
do { // Otherwise, use Halley's for faster convergence.
int256 e = expWad(w);
/// @solidity memory-safe-assembly
assembly {
let t := add(w, wad)
let s := sub(mul(w, e), mul(x, wad))
w := sub(w, sdiv(mul(s, wad), sub(mul(e, t), sdiv(mul(add(t, wad), s), add(t, t)))))
}
if (p <= w) break;
p = w;
} while (--i != c);
/// @solidity memory-safe-assembly
assembly {
w := sub(w, sgt(w, 2))
}
// For certain ranges of `x`, we'll use the quadratic-rate recursive formula of
// R. Iacono and J.P. Boyd for the last iteration, to avoid catastrophic cancellation.
if (c == uint256(0)) return w;
int256 t = w | 1;
/// @solidity memory-safe-assembly
assembly {
x := sdiv(mul(x, wad), t)
}
x = (t * (wad + lnWad(x)));
/// @solidity memory-safe-assembly
assembly {
w := sdiv(x, add(wad, t))
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* GENERAL NUMBER UTILITIES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `a * b == x * y`, with full precision.
function fullMulEq(uint256 a, uint256 b, uint256 x, uint256 y)
internal
pure
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
result := and(eq(mul(a, b), mul(x, y)), eq(mulmod(x, y, not(0)), mulmod(a, b, not(0))))
}
}
/// @dev Calculates `floor(x * y / d)` with full precision.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Remco Bloemen under MIT license: https://2π.com/21/muldiv
function fullMulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// 512-bit multiply `[p1 p0] = x * y`.
// Compute the product mod `2**256` and mod `2**256 - 1`
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that `product = p1 * 2**256 + p0`.
// Temporarily use `z` as `p0` to save gas.
z := mul(x, y) // Lower 256 bits of `x * y`.
for {} 1 {} {
// If overflows.
if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) {
let mm := mulmod(x, y, not(0))
let p1 := sub(mm, add(z, lt(mm, z))) // Upper 256 bits of `x * y`.
/*------------------- 512 by 256 division --------------------*/
// Make division exact by subtracting the remainder from `[p1 p0]`.
let r := mulmod(x, y, d) // Compute remainder using mulmod.
let t := and(d, sub(0, d)) // The least significant bit of `d`. `t >= 1`.
// Make sure `z` is less than `2**256`. Also prevents `d == 0`.
// Placing the check here seems to give more optimal stack operations.
if iszero(gt(d, p1)) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
d := div(d, t) // Divide `d` by `t`, which is a power of two.
// Invert `d mod 2**256`
// Now that `d` is an odd number, it has an inverse
// modulo `2**256` such that `d * inv = 1 mod 2**256`.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, `d * inv = 1 mod 2**4`.
let inv := xor(2, mul(3, d))
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**8
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**16
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**32
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**64
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**128
z :=
mul(
// Divide [p1 p0] by the factors of two.
// Shift in bits from `p1` into `p0`. For this we need
// to flip `t` such that it is `2**256 / t`.
or(mul(sub(p1, gt(r, z)), add(div(sub(0, t), t), 1)), div(sub(z, r), t)),
mul(sub(2, mul(d, inv)), inv) // inverse mod 2**256
)
break
}
z := div(z, d)
break
}
}
}
/// @dev Calculates `floor(x * y / d)` with full precision.
/// Behavior is undefined if `d` is zero or the final result cannot fit in 256 bits.
/// Performs the full 512 bit calculation regardless.
function fullMulDivUnchecked(uint256 x, uint256 y, uint256 d)
internal
pure
returns (uint256 z)
{
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
let mm := mulmod(x, y, not(0))
let p1 := sub(mm, add(z, lt(mm, z)))
let t := and(d, sub(0, d))
let r := mulmod(x, y, d)
d := div(d, t)
let inv := xor(2, mul(3, d))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
z :=
mul(
or(mul(sub(p1, gt(r, z)), add(div(sub(0, t), t), 1)), div(sub(z, r), t)),
mul(sub(2, mul(d, inv)), inv)
)
}
}
/// @dev Calculates `floor(x * y / d)` with full precision, rounded up.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Uniswap-v3-core under MIT license:
/// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol
function fullMulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
z = fullMulDiv(x, y, d);
/// @solidity memory-safe-assembly
assembly {
if mulmod(x, y, d) {
z := add(z, 1)
if iszero(z) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Calculates `floor(x * y / 2 ** n)` with full precision.
/// Throws if result overflows a uint256.
/// Credit to Philogy under MIT license:
/// https://github.com/SorellaLabs/angstrom/blob/main/contracts/src/libraries/X128MathLib.sol
function fullMulDivN(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Temporarily use `z` as `p0` to save gas.
z := mul(x, y) // Lower 256 bits of `x * y`. We'll call this `z`.
for {} 1 {} {
if iszero(or(iszero(x), eq(div(z, x), y))) {
let k := and(n, 0xff) // `n`, cleaned.
let mm := mulmod(x, y, not(0))
let p1 := sub(mm, add(z, lt(mm, z))) // Upper 256 bits of `x * y`.
// | p1 | z |
// Before: | p1_0 ¦ p1_1 | z_0 ¦ z_1 |
// Final: | 0 ¦ p1_0 | p1_1 ¦ z_0 |
// Check that final `z` doesn't overflow by checking that p1_0 = 0.
if iszero(shr(k, p1)) {
z := add(shl(sub(256, k), p1), shr(k, z))
break
}
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
z := shr(and(n, 0xff), z)
break
}
}
}
/// @dev Returns `floor(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`.
if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := div(z, d)
}
}
/// @dev Returns `ceil(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`.
if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(z, d))), div(z, d))
}
}
/// @dev Returns `x`, the modular multiplicative inverse of `a`, such that `(a * x) % n == 1`.
function invMod(uint256 a, uint256 n) internal pure returns (uint256 x) {
/// @solidity memory-safe-assembly
assembly {
let g := n
let r := mod(a, n)
for { let y := 1 } 1 {} {
let q := div(g, r)
let t := g
g := r
r := sub(t, mul(r, q))
let u := x
x := y
y := sub(u, mul(y, q))
if iszero(r) { break }
}
x := mul(eq(g, 1), add(x, mul(slt(x, 0), n)))
}
}
/// @dev Returns `ceil(x / d)`.
/// Reverts if `d` is zero.
function divUp(uint256 x, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
if iszero(d) {
mstore(0x00, 0x65244e4e) // `DivFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(x, d))), div(x, d))
}
}
/// @dev Returns `max(0, x - y)`. Alias for `saturatingSub`.
function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Returns `max(0, x - y)`.
function saturatingSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Returns `min(2 ** 256 - 1, x + y)`.
function saturatingAdd(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := or(sub(0, lt(add(x, y), x)), add(x, y))
}
}
/// @dev Returns `min(2 ** 256 - 1, x * y)`.
function saturatingMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := or(sub(or(iszero(x), eq(div(mul(x, y), x), y)), 1), mul(x, y))
}
}
/// @dev Returns `condition ? x : y`, without branching.
function ternary(bool condition, uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), iszero(condition)))
}
}
/// @dev Returns `condition ? x : y`, without branching.
function ternary(bool condition, bytes32 x, bytes32 y) internal pure returns (bytes32 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), iszero(condition)))
}
}
/// @dev Returns `condition ? x : y`, without branching.
function ternary(bool condition, address x, address y) internal pure returns (address z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), iszero(condition)))
}
}
/// @dev Exponentiate `x` to `y` by squaring, denominated in base `b`.
/// Reverts if the computation overflows.
function rpow(uint256 x, uint256 y, uint256 b) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(b, iszero(y)) // `0 ** 0 = 1`. Otherwise, `0 ** n = 0`.
if x {
z := xor(b, mul(xor(b, x), and(y, 1))) // `z = isEven(y) ? scale : x`
let half := shr(1, b) // Divide `b` by 2.
// Divide `y` by 2 every iteration.
for { y := shr(1, y) } y { y := shr(1, y) } {
let xx := mul(x, x) // Store x squared.
let xxRound := add(xx, half) // Round to the nearest number.
// Revert if `xx + half` overflowed, or if `x ** 2` overflows.
if or(lt(xxRound, xx), shr(128, x)) {
mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
revert(0x1c, 0x04)
}
x := div(xxRound, b) // Set `x` to scaled `xxRound`.
// If `y` is odd:
if and(y, 1) {
let zx := mul(z, x) // Compute `z * x`.
let zxRound := add(zx, half) // Round to the nearest number.
// If `z * x` overflowed or `zx + half` overflowed:
if or(xor(div(zx, x), z), lt(zxRound, zx)) {
// Revert if `x` is non-zero.
if x {
mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
revert(0x1c, 0x04)
}
}
z := div(zxRound, b) // Return properly scaled `zxRound`.
}
}
}
}
}
/// @dev Returns the square root of `x`, rounded down.
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// `floor(sqrt(2**15)) = 181`. `sqrt(2**15) - 181 = 2.84`.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// Let `y = x / 2**r`. We check `y >= 2**(k + 8)`
// but shift right by `k` bits to ensure that if `x >= 256`, then `y >= 256`.
let r := shl(7, lt(0xffffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffffff, shr(r, x))))
z := shl(shr(1, r), z)
// Goal was to get `z*z*y` within a small factor of `x`. More iterations could
// get y in a tighter range. Currently, we will have y in `[256, 256*(2**16))`.
// We ensured `y >= 256` so that the relative difference between `y` and `y+1` is small.
// That's not possible if `x < 256` but we can just verify those cases exhaustively.
// Now, `z*z*y <= x < z*z*(y+1)`, and `y <= 2**(16+8)`, and either `y >= 256`, or `x < 256`.
// Correctness can be checked exhaustively for `x < 256`, so we assume `y >= 256`.
// Then `z*sqrt(y)` is within `sqrt(257)/sqrt(256)` of `sqrt(x)`, or about 20bps.
// For `s` in the range `[1/256, 256]`, the estimate `f(s) = (181/1024) * (s+1)`
// is in the range `(1/2.84 * sqrt(s), 2.84 * sqrt(s))`,
// with largest error when `s = 1` and when `s = 256` or `1/256`.
// Since `y` is in `[256, 256*(2**16))`, let `a = y/65536`, so that `a` is in `[1/256, 256)`.
// Then we can estimate `sqrt(y)` using
// `sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2**18`.
// There is no overflow risk here since `y < 2**136` after the first branch above.
z := shr(18, mul(z, add(shr(r, x), 65536))) // A `mul()` is saved from starting `z` at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If `x+1` is a perfect square, the Babylonian method cycles between
// `floor(sqrt(x))` and `ceil(sqrt(x))`. This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
z := sub(z, lt(div(x, z), z))
}
}
/// @dev Returns the cube root of `x`, rounded down.
/// Credit to bout3fiddy and pcaversaccio under AGPLv3 license:
/// https://github.com/pcaversaccio/snekmate/blob/main/src/utils/Math.vy
/// Formally verified by xuwinnie:
/// https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf
function cbrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// Makeshift lookup table to nudge the approximate log2 result.
z := div(shl(div(r, 3), shl(lt(0xf, shr(r, x)), 0xf)), xor(7, mod(r, 3)))
// Newton-Raphson's.
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
// Round down.
z := sub(z, lt(div(x, mul(z, z)), z))
}
}
/// @dev Returns the square root of `x`, denominated in `WAD`, rounded down.
function sqrtWad(uint256 x) internal pure returns (uint256 z) {
unchecked {
if (x <= type(uint256).max / 10 ** 18) return sqrt(x * 10 ** 18);
z = (1 + sqrt(x)) * 10 ** 9;
z = (fullMulDivUnchecked(x, 10 ** 18, z) + z) >> 1;
}
/// @solidity memory-safe-assembly
assembly {
z := sub(z, gt(999999999999999999, sub(mulmod(z, z, x), 1))) // Round down.
}
}
/// @dev Returns the cube root of `x`, denominated in `WAD`, rounded down.
/// Formally verified by xuwinnie:
/// https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf
function cbrtWad(uint256 x) internal pure returns (uint256 z) {
unchecked {
if (x <= type(uint256).max / 10 ** 36) return cbrt(x * 10 ** 36);
z = (1 + cbrt(x)) * 10 ** 12;
z = (fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3;
}
/// @solidity memory-safe-assembly
assembly {
let p := x
for {} 1 {} {
if iszero(shr(229, p)) {
if iszero(shr(199, p)) {
p := mul(p, 100000000000000000) // 10 ** 17.
break
}
p := mul(p, 100000000) // 10 ** 8.
break
}
if iszero(shr(249, p)) { p := mul(p, 100) }
break
}
let t := mulmod(mul(z, z), z, p)
z := sub(z, gt(lt(t, shr(1, p)), iszero(t))) // Round down.
}
}
/// @dev Returns the factorial of `x`.
function factorial(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := 1
if iszero(lt(x, 58)) {
mstore(0x00, 0xaba0f2a2) // `FactorialOverflow()`.
revert(0x1c, 0x04)
}
for {} x { x := sub(x, 1) } { z := mul(z, x) }
}
}
/// @dev Returns the log2 of `x`.
/// Equivalent to computing the index of the most significant bit (MSB) of `x`.
/// Returns 0 if `x` is zero.
function log2(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000))
}
}
/// @dev Returns the log2 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log2Up(uint256 x) internal pure returns (uint256 r) {
r = log2(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(shl(r, 1), x))
}
}
/// @dev Returns the log10 of `x`.
/// Returns 0 if `x` is zero.
function log10(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(x, 100000000000000000000000000000000000000)) {
x := div(x, 100000000000000000000000000000000000000)
r := 38
}
if iszero(lt(x, 100000000000000000000)) {
x := div(x, 100000000000000000000)
r := add(r, 20)
}
if iszero(lt(x, 10000000000)) {
x := div(x, 10000000000)
r := add(r, 10)
}
if iszero(lt(x, 100000)) {
x := div(x, 100000)
r := add(r, 5)
}
r := add(r, add(gt(x, 9), add(gt(x, 99), add(gt(x, 999), gt(x, 9999)))))
}
}
/// @dev Returns the log10 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log10Up(uint256 x) internal pure returns (uint256 r) {
r = log10(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(exp(10, r), x))
}
}
/// @dev Returns the log256 of `x`.
/// Returns 0 if `x` is zero.
function log256(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(shr(3, r), lt(0xff, shr(r, x)))
}
}
/// @dev Returns the log256 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log256Up(uint256 x) internal pure returns (uint256 r) {
r = log256(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(shl(shl(3, r), 1), x))
}
}
/// @dev Returns the scientific notation format `mantissa * 10 ** exponent` of `x`.
/// Useful for compressing prices (e.g. using 25 bit mantissa and 7 bit exponent).
function sci(uint256 x) internal pure returns (uint256 mantissa, uint256 exponent) {
/// @solidity memory-safe-assembly
assembly {
mantissa := x
if mantissa {
if iszero(mod(mantissa, 1000000000000000000000000000000000)) {
mantissa := div(mantissa, 1000000000000000000000000000000000)
exponent := 33
}
if iszero(mod(mantissa, 10000000000000000000)) {
mantissa := div(mantissa, 10000000000000000000)
exponent := add(exponent, 19)
}
if iszero(mod(mantissa, 1000000000000)) {
mantissa := div(mantissa, 1000000000000)
exponent := add(exponent, 12)
}
if iszero(mod(mantissa, 1000000)) {
mantissa := div(mantissa, 1000000)
exponent := add(exponent, 6)
}
if iszero(mod(mantissa, 10000)) {
mantissa := div(mantissa, 10000)
exponent := add(exponent, 4)
}
if iszero(mod(mantissa, 100)) {
mantissa := div(mantissa, 100)
exponent := add(exponent, 2)
}
if iszero(mod(mantissa, 10)) {
mantissa := div(mantissa, 10)
exponent := add(exponent, 1)
}
}
}
}
/// @dev Convenience function for packing `x` into a smaller number using `sci`.
/// The `mantissa` will be in bits [7..255] (the upper 249 bits).
/// The `exponent` will be in bits [0..6] (the lower 7 bits).
/// Use `SafeCastLib` to safely ensure that the `packed` number is small
/// enough to fit in the desired unsigned integer type:
/// ```
/// uint32 packed = SafeCastLib.toUint32(FixedPointMathLib.packSci(777 ether));
/// ```
function packSci(uint256 x) internal pure returns (uint256 packed) {
(x, packed) = sci(x); // Reuse for `mantissa` and `exponent`.
/// @solidity memory-safe-assembly
assembly {
if shr(249, x) {
mstore(0x00, 0xce30380c) // `MantissaOverflow()`.
revert(0x1c, 0x04)
}
packed := or(shl(7, x), packed)
}
}
/// @dev Convenience function for unpacking a packed number from `packSci`.
function unpackSci(uint256 packed) internal pure returns (uint256 unpacked) {
unchecked {
unpacked = (packed >> 7) * 10 ** (packed & 0x7f);
}
}
/// @dev Returns the average of `x` and `y`. Rounds towards zero.
function avg(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = (x & y) + ((x ^ y) >> 1);
}
}
/// @dev Returns the average of `x` and `y`. Rounds towards negative infinity.
function avg(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = (x >> 1) + (y >> 1) + (x & y & 1);
}
}
/// @dev Returns the absolute value of `x`.
function abs(int256 x) internal pure returns (uint256 z) {
unchecked {
z = (uint256(x) + uint256(x >> 255)) ^ uint256(x >> 255);
}
}
/// @dev Returns the absolute distance between `x` and `y`.
function dist(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(xor(sub(0, gt(x, y)), sub(y, x)), gt(x, y))
}
}
/// @dev Returns the absolute distance between `x` and `y`.
function dist(int256 x, int256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(xor(sub(0, sgt(x, y)), sub(y, x)), sgt(x, y))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), slt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), gt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), sgt(y, x)))
}
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(uint256 x, uint256 minValue, uint256 maxValue)
internal
pure
returns (uint256 z)
{
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, minValue), gt(minValue, x)))
z := xor(z, mul(xor(z, maxValue), lt(maxValue, z)))
}
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(int256 x, int256 minValue, int256 maxValue) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, minValue), sgt(minValue, x)))
z := xor(z, mul(xor(z, maxValue), slt(maxValue, z)))
}
}
/// @dev Returns greatest common divisor of `x` and `y`.
function gcd(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
for { z := x } y {} {
let t := y
y := mod(z, y)
z := t
}
}
}
/// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`,
/// with `t` clamped between `begin` and `end` (inclusive).
/// Agnostic to the order of (`a`, `b`) and (`end`, `begin`).
/// If `begins == end`, returns `t <= begin ? a : b`.
function lerp(uint256 a, uint256 b, uint256 t, uint256 begin, uint256 end)
internal
pure
returns (uint256)
{
if (begin > end) (t, begin, end) = (~t, ~begin, ~end);
if (t <= begin) return a;
if (t >= end) return b;
unchecked {
if (b >= a) return a + fullMulDiv(b - a, t - begin, end - begin);
return a - fullMulDiv(a - b, t - begin, end - begin);
}
}
/// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`.
/// with `t` clamped between `begin` and `end` (inclusive).
/// Agnostic to the order of (`a`, `b`) and (`end`, `begin`).
/// If `begins == end`, returns `t <= begin ? a : b`.
function lerp(int256 a, int256 b, int256 t, int256 begin, int256 end)
internal
pure
returns (int256)
{
if (begin > end) (t, begin, end) = (~t, ~begin, ~end);
if (t <= begin) return a;
if (t >= end) return b;
// forgefmt: disable-next-item
unchecked {
if (b >= a) return int256(uint256(a) + fullMulDiv(uint256(b - a),
uint256(t - begin), uint256(end - begin)));
return int256(uint256(a) - fullMulDiv(uint256(a - b),
uint256(t - begin), uint256(end - begin)));
}
}
/// @dev Returns if `x` is an even number. Some people may need this.
function isEven(uint256 x) internal pure returns (bool) {
return x & uint256(1) == uint256(0);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RAW NUMBER OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `x + y`, without checking for overflow.
function rawAdd(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x + y;
}
}
/// @dev Returns `x + y`, without checking for overflow.
function rawAdd(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x + y;
}
}
/// @dev Returns `x - y`, without checking for underflow.
function rawSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x - y;
}
}
/// @dev Returns `x - y`, without checking for underflow.
function rawSub(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x - y;
}
}
/// @dev Returns `x * y`, without checking for overflow.
function rawMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x * y;
}
}
/// @dev Returns `x * y`, without checking for overflow.
function rawMul(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x * y;
}
}
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function rawDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(x, y)
}
}
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function rawSDiv(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function rawMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mod(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function rawSMod(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := smod(x, y)
}
}
/// @dev Returns `(x + y) % d`, return 0 if `d` if zero.
function rawAddMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := addmod(x, y, d)
}
}
/// @dev Returns `(x * y) % d`, return 0 if `d` if zero.
function rawMulMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mulmod(x, y, d)
}
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}
// 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.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 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 a 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.
/// To load an address, you can use `address(bytes20(load(a, offset)))`.
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.
/// To load an address, you can use `address(bytes20(loadCalldata(a, offset)))`.
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 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 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 a arrays of strings based on the `delimiter` inside of the `subject` string.
function split(string memory subject, string memory delimiter)
internal
pure
returns (string[] memory result)
{
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 Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/// @dev The ERC20 `totalSupply` query has failed.
error TotalSupplyQueryFailed();
/// @dev The Permit2 operation has failed.
error Permit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.
error Permit2AmountOverflow();
/// @dev The Permit2 approve operation has failed.
error Permit2ApproveFailed();
/// @dev The Permit2 lockdown operation has failed.
error Permit2LockdownFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/// @dev The unique EIP-712 domain domain separator for the DAI token contract.
bytes32 internal constant DAI_DOMAIN_SEPARATOR =
0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.
address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function trySafeTransferFrom(address token, address from, address to, uint256 amount)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
// Check the `extcodesize` again just in case the token selfdestructs lol.
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul( // The arguments of `mul` are evaluated from right to left.
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Returns the total supply of the `token`.
/// Reverts if the token does not exist or does not implement `totalSupply()`.
function totalSupply(address token) internal view returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x18160ddd) // `totalSupply()`.
if iszero(
and(gt(returndatasize(), 0x1f), staticcall(gas(), token, 0x1c, 0x04, 0x00, 0x20))
) {
mstore(0x00, 0x54cd9435) // `TotalSupplyQueryFailed()`.
revert(0x1c, 0x04)
}
result := mload(0x00)
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// If the initial attempt fails, try to use Permit2 to transfer the token.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
if (!trySafeTransferFrom(token, from, to, amount)) {
permit2TransferFrom(token, from, to, amount);
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
/// Reverts upon failure.
function permit2TransferFrom(address token, address from, address to, uint256 amount)
internal
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(add(m, 0x74), shr(96, shl(96, token)))
mstore(add(m, 0x54), amount)
mstore(add(m, 0x34), to)
mstore(add(m, 0x20), shl(96, from))
// `transferFrom(address,address,uint160,address)`.
mstore(m, 0x36c78516000000000000000000000000)
let p := PERMIT2
let exists := eq(chainid(), 1)
if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
if iszero(
and(
call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00),
lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists.
)
) {
mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
}
}
}
/// @dev Permit a user to spend a given amount of
/// another user's tokens via native EIP-2612 permit if possible, falling
/// back to Permit2 if native permit fails or is not implemented on the token.
function permit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
for {} shl(96, xor(token, WETH9)) {} {
mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
// Gas stipend to limit gas burn for tokens that don't refund gas when
// an non-existing function is called. 5K should be enough for a SLOAD.
staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
)
) { break }
// After here, we can be sure that token is a contract.
let m := mload(0x40)
mstore(add(m, 0x34), spender)
mstore(add(m, 0x20), shl(96, owner))
mstore(add(m, 0x74), deadline)
if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
mstore(0x14, owner)
mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
mstore(
add(m, 0x94),
lt(iszero(amount), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
)
mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
// `nonces` is already at `add(m, 0x54)`.
// `amount != 0` is already stored at `add(m, 0x94)`.
mstore(add(m, 0xb4), and(0xff, v))
mstore(add(m, 0xd4), r)
mstore(add(m, 0xf4), s)
success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
break
}
mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
mstore(add(m, 0x54), amount)
mstore(add(m, 0x94), and(0xff, v))
mstore(add(m, 0xb4), r)
mstore(add(m, 0xd4), s)
success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
break
}
}
if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
}
/// @dev Simple permit on the Permit2 contract.
function simplePermit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x927da105) // `allowance(address,address,address)`.
{
let addressMask := shr(96, not(0))
mstore(add(m, 0x20), and(addressMask, owner))
mstore(add(m, 0x40), and(addressMask, token))
mstore(add(m, 0x60), and(addressMask, spender))
mstore(add(m, 0xc0), and(addressMask, spender))
}
let p := mul(PERMIT2, iszero(shr(160, amount)))
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
)
) {
mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(p))), 0x04)
}
mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
// `owner` is already `add(m, 0x20)`.
// `token` is already at `add(m, 0x40)`.
mstore(add(m, 0x60), amount)
mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
// `nonce` is already at `add(m, 0xa0)`.
// `spender` is already at `add(m, 0xc0)`.
mstore(add(m, 0xe0), deadline)
mstore(add(m, 0x100), 0x100) // `signature` offset.
mstore(add(m, 0x120), 0x41) // `signature` length.
mstore(add(m, 0x140), r)
mstore(add(m, 0x160), s)
mstore(add(m, 0x180), shl(248, v))
if iszero( // Revert if token does not have code, or if the call fails.
mul(extcodesize(token), call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00))) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Approves `spender` to spend `amount` of `token` for `address(this)`.
function permit2Approve(address token, address spender, uint160 amount, uint48 expiration)
internal
{
/// @solidity memory-safe-assembly
assembly {
let addressMask := shr(96, not(0))
let m := mload(0x40)
mstore(m, 0x87517c45) // `approve(address,address,uint160,uint48)`.
mstore(add(m, 0x20), and(addressMask, token))
mstore(add(m, 0x40), and(addressMask, spender))
mstore(add(m, 0x60), and(addressMask, amount))
mstore(add(m, 0x80), and(0xffffffffffff, expiration))
if iszero(call(gas(), PERMIT2, 0, add(m, 0x1c), 0xa0, codesize(), 0x00)) {
mstore(0x00, 0x324f14ae) // `Permit2ApproveFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Revokes an approval for `token` and `spender` for `address(this)`.
function permit2Lockdown(address token, address spender) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0xcc53287f) // `Permit2.lockdown`.
mstore(add(m, 0x20), 0x20) // Offset of the `approvals`.
mstore(add(m, 0x40), 1) // `approvals.length`.
mstore(add(m, 0x60), shr(96, shl(96, token)))
mstore(add(m, 0x80), shr(96, shl(96, spender)))
if iszero(call(gas(), PERMIT2, 0, add(m, 0x1c), 0xa0, codesize(), 0x00)) {
mstore(0x00, 0x96b3de23) // `Permit2LockdownFailed()`.
revert(0x1c, 0x04)
}
}
}
}
{
"compilationTarget": {
"src/EXEC404.sol": "EXEC404"
},
"evmVersion": "cancun",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1000
},
"remappings": [
":@ensdomains/=lib/v4-core/node_modules/@ensdomains/",
":@openzeppelin/=lib/v4-core/lib/openzeppelin-contracts/",
":@uniswap/v2-core/=lib/uniswap-v2-core/",
":@uniswap/v2-periphery/=lib/uniswap-v2-periphery/",
":@uniswap/v3-core/=lib/v3-core/",
":@uniswap/v3-periphery/=lib/v3-periphery/",
":@uniswap/v4-core/=lib/v4-core/",
":dn404/=lib/dn404/",
":ds-test/=lib/v4-core/lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-gas-snapshot/=lib/v4-core/lib/forge-gas-snapshot/src/",
":forge-std/=lib/v4-core/lib/forge-std/src/",
":forge-std/=test/utils/forge-std/",
":halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
":hardhat/=lib/v4-core/node_modules/hardhat/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":permit2/=lib/v4-periphery/lib/permit2/",
":solady/=lib/solady/src/",
":solmate/=lib/v4-core/lib/solmate/",
":uniswap-v2-core/=lib/uniswap-v2-core/contracts/",
":uniswap-v2-periphery/=lib/uniswap-v2-periphery/contracts/",
":v3-core/=lib/v3-core/",
":v3-periphery/=lib/v3-periphery/contracts/",
":v4-core/=lib/v4-core/src/",
":v4-periphery/=lib/v4-periphery/"
]
}
[{"inputs":[{"internalType":"bytes32[]","name":"_tierRoots","type":"bytes32[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"DNAlreadyInitialized","type":"error"},{"inputs":[],"name":"DNNotInitialized","type":"error"},{"inputs":[],"name":"FnSelectorNotRecognized","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InvalidUnit","type":"error"},{"inputs":[],"name":"LinkMirrorContractFailed","type":"error"},{"inputs":[],"name":"MirrorAddressIsZero","type":"error"},{"inputs":[],"name":"SenderNotMirror","type":"error"},{"inputs":[],"name":"TokenDoesNotExist","type":"error"},{"inputs":[],"name":"TotalSupplyOverflow","type":"error"},{"inputs":[],"name":"TransferCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"participant","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"cost","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isBuy","type":"bool"}],"name":"BondingSale","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amountToken","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidity","type":"uint256"}],"name":"LiquidityDeployed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"bool","name":"status","type":"bool"}],"name":"SkipNFTSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collector","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"V3FeesCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32[]","name":"roots","type":"bytes32[]"}],"name":"WhitelistInitialized","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"CULT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LAUNCH_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SUPPLY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"balanceMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"bondingMessages","outputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint128","name":"packedData","type":"uint128"},{"internalType":"string","name":"message","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"maxCost","type":"uint256"},{"internalType":"bool","name":"mintNFT","type":"bool"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"string","name":"message","type":"string"}],"name":"buyBonding","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"calculateCost","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"calculateRefund","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"amount0Max","type":"uint128"},{"internalType":"uint128","name":"amount1Max","type":"uint128"}],"name":"collectV3Fees","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"_uri","type":"string"},{"internalType":"string","name":"_unrevealedUri","type":"string"},{"internalType":"bool","name":"_revealed","type":"bool"}],"name":"configure","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cultPool","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cultV3Position","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deployLiquidity","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"freeMint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"freeSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentTier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"execAmount","type":"uint256"}],"name":"getEthForExec","outputs":[{"internalType":"uint256","name":"ethAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"ethAmount","type":"uint256"}],"name":"getExecForEth","outputs":[{"internalType":"uint256","name":"execAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"messageId","type":"uint256"}],"name":"getMessageDetails","outputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint32","name":"timestamp","type":"uint32"},{"internalType":"uint96","name":"amount","type":"uint96"},{"internalType":"bool","name":"isBuy","type":"bool"},{"internalType":"string","name":"message","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"getMessagesBatch","outputs":[{"internalType":"address[]","name":"senders","type":"address[]"},{"internalType":"uint32[]","name":"timestamps","type":"uint32[]"},{"internalType":"uint96[]","name":"amounts","type":"uint96[]"},{"internalType":"bool[]","name":"isBuys","type":"bool[]"},{"internalType":"string[]","name":"messages","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"getOwnerTokens","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"getSkipNFT","outputs":[{"internalType":"bool","name":"result","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initializeCultPool","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"address","name":"account","type":"address"}],"name":"isWhitelisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liquidityPair","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mirrorERC721","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"revealed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minRefund","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"string","name":"message","type":"string"}],"name":"sellBonding","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"skipNFT","type":"bool"}],"name":"setSkipNFT","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalBondingSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalMessages","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":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unrevealedUri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"uri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]