// SPDX-License-Identifier: MITpragmasolidity ^0.8.28;/// @title BT404Mirror/// @notice BT404Mirror provides an interface for interacting with the/// NFT tokens in a BT404 implementation.////// @author FlooringLab/// @author Modified from DN404(https://github.com/Vectorized/dn404/src/DN404Mirror.sol)////// @dev Note:/// - The ERC721 data is stored in the base BT404 contract.contractBT404Mirror{
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*//* EVENTS *//*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*//// @dev Emitted when token `id` is transferred from `from` to `to`.eventTransfer(addressindexedfrom, addressindexed to, uint256indexed id);
/// @dev Emitted when `owner` enables `account` to manage the `id` token.eventApproval(addressindexed owner, addressindexed account, uint256indexed id);
/// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens.eventApprovalForAll(addressindexed owner, addressindexed 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.eventOwnershipTransferred(addressindexed oldOwner, addressindexed newOwner);
/// @dev Emitted when `owner` lock or unlock token `id`.eventUpdateLockState(addressindexed owner, uint256indexed id, bool lockStatus);
/// @dev Emitted when token `idX` and `idY` exchanged.eventExchange(uint256indexed idX, uint256indexed idY, uint256 exchangeFee);
/// @dev Emitted when token `id` offered for sale.eventOffer(uint256indexed id, addressindexed to, uint256 minPrice, address offerToken);
/// @dev Emitted when token `id` offered for sale.eventCancelOffer(uint256indexed id, addressindexed owner);
/// @dev Emitted when token `id` offered for sale.eventBid(uint256indexed id, addressindexedfrom, uint256 price, address bidToken);
/// @dev Emitted when token `id` offered for sale.eventCancelBid(uint256indexed id, addressindexedfrom);
/// @dev Emitted when token `id` bought.eventBought(uint256indexed id,
addressindexedfrom,
addressindexed to,
uint256 price,
address token,
address maker
);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.uint256internalconstant _TRANSFER_EVENT_SIGNATURE =0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.uint256internalconstant _APPROVAL_EVENT_SIGNATURE =0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/// @dev `keccak256(bytes("ApprovalForAll(address,address,bool)"))`.uint256internalconstant _APPROVAL_FOR_ALL_EVENT_SIGNATURE =0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
/// @dev `keccak256(bytes("UpdateLockState(address,uint256,bool)"))`.uint256internalconstant _UPDATE_LOCK_STATE_EVENT_SIGNATURE =0xcc3a1bd7e528af8582cd3578d82ae22e309de7c3663c9d0fa5b5ce79c1a346ac;
/// @dev `keccak256(bytes("Exchange(uint256,uint256,uint256)"))`uint256internalconstant _EXCHANGE_EVENT_SIGNATURE =0xbc43d7c0945f5a13a7bfa8ca7309e55f903f01d66c38c6d1353fe7ff9335d776;
/// @dev `keccak256(bytes("Offer(uint256,address,uint256,address)"))`uint256privateconstant _OFFER_EVENT_SIGNATURE =0xc56f8610599b5a39311e36563ef3386394748f787ef5efc116d960d77def8050;
/// @dev `keccak256(bytes("CancelOffer(uint256,address)"))`uint256privateconstant _CANCEL_OFFER_EVENT_SIGNATURE =0xc4caef7e3533865382e608c341581a5e2a1b0d1ac37b0aaf58023ccd4eedfd8e;
/// @dev `keccak256(bytes("Bid(uint256,address,uint256,address)"))`uint256privateconstant _BID_EVENT_SIGNATURE =0xec85e6e86fabc4c703529b570fb5eb567dad69ddbf7901bc0fd28b38b93de7f3;
/// @dev `keccak256(bytes("CancelBid(uint256,address)"))`uint256privateconstant _CANCEL_BID_EVENT_SIGNATURE =0x874afcdd5e90b2329b3c1601e613dcdc6abb6deb62ce61339a8337b48c053e51;
/// @dev `keccak256(bytes("Bought(uint256,address,address,uint256,address,address)"))`uint256privateconstant _BOUGHT_EVENT_SIGNATURE =0xd9882bc1ac8e78c918b907fa0ff79cc9d866091c5eb450ebed79e9d147541d5b;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*//* CUSTOM ERRORS *//*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*//// @dev Thrown when a call for an NFT function did not originate/// from the base BT404 contract.errorSenderNotBase();
/// @dev Thrown when a call for an NFT function did not originate from the deployer.errorSenderNotDeployer();
/// @dev Thrown when transferring an NFT to a contract address that/// does not implement ERC721Receiver.errorTransferToNonERC721ReceiverImplementer();
/// @dev Thrown when linking to the BT404 base contract and the/// BT404 supportsInterface check fails or the call reverts.errorCannotLink();
/// @dev Thrown when a linkMirrorContract call is received and the/// NFT mirror contract has already been linked to a BT404 base contract.errorAlreadyLinked();
/// @dev Thrown when retrieving the base BT404 address when a link has not/// been established.errorNotLinked();
/// @dev The caller is not authorized to call the function.errorUnauthorized();
/// @dev Unauthorized reentrant call.errorReentrancy();
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*//* STORAGE *//*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*//// @dev Struct contain the NFT mirror contract storage.structBT404NFTStorage {
address baseERC20;
bool locked;
address deployer;
address owner;
}
/// @dev Returns a storage pointer for BT404NFTStorage.function_getBT404NFTStorage() internalpurevirtualreturns (BT404NFTStorage storage $) {
/// @solidity memory-safe-assemblyassembly {
// `uint72(bytes9(keccak256("DN404_MIRROR_STORAGE")))`.
$.slot:=0x3602298b8c10b01230// Truncate to 9 bytes to reduce bytecode size.
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*//* REENTRANCY GUARD *//*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*//// @dev Guards a function from reentrancy.modifiernonReentrant() virtual{
BT404NFTStorage storage $ = _getBT404NFTStorage();
if ($.locked) revert Reentrancy();
$.locked =true;
_;
$.locked =false;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*//* CONSTRUCTOR *//*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/constructor(address deployer) {
// For non-proxies, we will store the deployer so that only the deployer can// link the base contract.
_getBT404NFTStorage().deployer = deployer;
}
function_initializeBT404Mirror(address deployer) internal{
// For non-proxies, we will store the deployer so that only the deployer can// link the base contract.
_getBT404NFTStorage().deployer = deployer;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*//* ERC721 OPERATIONS *//*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*//// @dev Returns the token collection name from the base BT404 contract.functionname() publicviewvirtualreturns (stringmemory result) {
return _readString(0x06fdde03, 0); // `name()`.
}
/// @dev Returns the token collection symbol from the base BT404 contract.functionsymbol() publicviewvirtualreturns (stringmemory result) {
return _readString(0x95d89b41, 0); // `symbol()`.
}
/// @dev Returns the Uniform Resource Identifier (URI) for token `id` from/// the base BT404 contract.functiontokenURI(uint256 id) publicviewvirtualreturns (stringmemory result) {
return _readString(0xc87b56dd, id); // `tokenURI()`.
}
/// @dev Returns the total NFT supply from the base BT404 contract.functiontotalSupply() publicviewvirtualreturns (uint256 result) {
return _readWord(0xe2c79281, 0, 0); // `totalNFTSupply()`.
}
/// @dev Returns the number of NFT tokens owned by `nftOwner` from the base BT404 contract.////// Requirements:/// - `nftOwner` must not be the zero address.functionbalanceOf(address nftOwner) publicviewvirtualreturns (uint256 result) {
return _readWord(0xf5b100ea, uint160(nftOwner), 0); // `balanceOfNFT(address)`.
}
/// @dev Returns the owner of token `id` from the base BT404 contract.////// Requirements:/// - Token `id` must exist.functionownerOf(uint256 id) publicviewvirtualreturns (address result) {
returnaddress(uint160(_readWord(0x6352211e, id, 0))); // `ownerOf(uint256)`.
}
/// @dev Returns the owner of token `id` from the base BT404 contract./// Returns `address(0)` instead of reverting if the token does not exist.functionownerAt(uint256 id) publicviewvirtualreturns (address result) {
returnaddress(uint160(_readWord(0x24359879, id, 0))); // `ownerAt(uint256)`.
}
/// @dev Sets `spender` as the approved account to manage token `id` in/// the base BT404 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.functionapprove(address spender, uint256 id) publicvirtual{
address base = baseERC20();
/// @solidity memory-safe-assemblyassembly {
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())
ifiszero(
and(
gt(returndatasize(), 0x1f),
call(gas(), base, callvalue(), 0x1c, 0x64, 0x00, 0x20)
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
mstore(0x40, m) // Restore the free memory pointer.mstore(0x60, 0) // Restore the zero pointer.// Emit the {Approval} event.log4(codesize(), 0x00, _APPROVAL_EVENT_SIGNATURE, shr(96, mload(0x0c)), spender, id)
}
}
/// @dev Returns the account approved to manage token `id` from/// the base BT404 contract.////// Requirements:/// - Token `id` must exist.functiongetApproved(uint256 id) publicviewvirtualreturns (address) {
returnaddress(uint160(_readWord(0x081812fc, id, 0))); // `getApproved(uint256)`.
}
/// @dev Sets whether `operator` is approved to manage the tokens of the caller in/// the base BT404 contract.////// Emits an {ApprovalForAll} event.functionsetApprovalForAll(address operator, bool approved) publicvirtual{
address base = baseERC20();
/// @solidity memory-safe-assemblyassembly {
operator :=shr(96, shl(96, operator))
let m :=mload(0x40)
mstore(0x00, 0x813500fc) // `setApprovalForAll(address,bool,address)`.mstore(0x20, operator)
mstore(0x40, iszero(iszero(approved)))
mstore(0x60, caller())
ifiszero(
and(eq(mload(0x00), 1), call(gas(), base, callvalue(), 0x1c, 0x64, 0x00, 0x20))
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
// Emit the {ApprovalForAll} event.// The `approved` value is already at 0x40.log3(0x40, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), operator)
mstore(0x40, m) // Restore the free memory pointer.mstore(0x60, 0) // Restore the zero pointer.
}
}
/// @dev Returns whether `operator` is approved to manage the tokens of `nftOwner` from/// the base BT404 contract.functionisApprovedForAll(address nftOwner, address operator)
publicviewvirtualreturns (bool result)
{
// `isApprovedForAll(address,address)`.return _readWord(0xe985e9c5, uint160(nftOwner), uint160(operator)) !=0;
}
/// @dev Returns the owned token ids of `account` from the base BT404 contract.functionownedIds(address account, uint256 begin, uint256 end)
publicviewvirtualreturns (uint256[] memory)
{
return _ownedIds(account, begin, end, false);
}
/// @dev Returns the locked token ids of `account` from the base BT404 contract.functionlockedIds(address account, uint256 begin, uint256 end)
publicviewvirtualreturns (uint256[] memory)
{
return _ownedIds(account, begin, end, true);
}
/// @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.functiontransferFrom(addressfrom, address to, uint256 id) publicvirtual{
address base = baseERC20();
/// @solidity memory-safe-assemblyassembly {
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())
ifiszero(
and(eq(mload(m), 1), call(gas(), base, callvalue(), add(m, 0x1c), 0x84, m, 0x20))
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
// Emit the `from` unlock event.mstore(m, 0x00)
log3(m, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, from, id)
// Emit the {Transfer} event.log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
// Emit the `to` lock eventmstore(m, 0x01)
log3(m, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, to, id)
}
}
/// @dev Equivalent to `safeTransferFrom(from, to, id, "")`.functionsafeTransferFrom(addressfrom, address to, uint256 id) publicvirtual{
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.////// Requirements:////// - Token `id` must exist./// - `from` must be the owner of the token./// - `to` cannot be the zero address./// - The caller must be the owner of the token, or be approved to manage the token./// - If `to` refers to a smart contract, it must implement/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.////// Emits a {Transfer} event.functionsafeTransferFrom(addressfrom, address to, uint256 id, bytescalldata data)
publicvirtual{
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
functionupdateLockState(uint256[] memory ids, bool lock) publicvirtual{
address base = baseERC20();
(bool success, bytesmemory result) = base.call(
abi.encodeWithSignature(
"setNFTLockState(uint256,uint256[])",
uint256(uint160(msg.sender)) <<96| (lock ? 1 : 0),
ids
)
);
// @solidity memory-safe-assemblyassembly {
ifiszero(and(eq(mload(add(result, 0x20)), 1), success)) {
revert(add(result, 0x20), mload(result))
}
let idLen :=mload(ids)
mstore(0x00, lock)
for {
let s :=add(ids, 0x20)
let end :=add(s, shl(5, idLen))
} iszero(eq(s, end)) { s :=add(s, 0x20) } {
log3(0x00, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, caller(), mload(s))
}
}
}
functionexchange(uint256 idX, uint256 idY) publicvirtualreturns (uint256 exchangeFee) {
address base = baseERC20();
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40)
mstore(0x00, 0x2c5966af) // `exchangeNFT(uint256,uint256,address)`.mstore(0x20, idX)
mstore(0x40, idY)
mstore(0x60, caller())
ifiszero(
and(
gt(returndatasize(), 0x5F),
call(gas(), base, callvalue(), 0x1c, 0x64, 0x00, 0x60)
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
// store return valuelet x :=mload(0x00)
let y :=mload(0x20)
exchangeFee :=mload(0x40)
// Emit the {Transfer} event.log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, x, y, idX)
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, y, caller(), idY)
// Emit the {Exchange} event.log3(0x40, 0x20, _EXCHANGE_EVENT_SIGNATURE, idX, idY)
// Emit the `caller` lock event.mstore(0x40, 0x01)
log3(0x40, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, caller(), idY)
mstore(0x40, m) // Restore the free memory pointer.mstore(0x60, 0) // Restore the zero pointer.
}
}
structNFTOrder {
uint256 id;
uint256 price;
address token;
address trader;
}
functionofferForSale(NFTOrder[] memory orders) publicvirtualnonReentrant{
_callBaseRetWord(
abi.encodeWithSignature(
"offerForSale(address,(uint256,uint256,address,address)[])", msg.sender, orders
)
);
/// @solidity memory-safe-assemblyassembly {
for {
let s :=add(orders, add(0x20, shl(5, mload(orders))))
let end :=add(s, mul(0x80, mload(orders)))
} iszero(eq(s, end)) { s :=add(s, 0x80) } {
log3(add(s, 0x20), 0x40, _OFFER_EVENT_SIGNATURE, mload(s), mload(add(s, 0x60)))
}
}
}
functionacceptOffer(NFTOrder[] memory orders) publicpayablevirtualnonReentrant{
_callBaseRetWord(
abi.encodeWithSignature(
"acceptOffer(address,(uint256,uint256,address,address)[])", msg.sender, orders
)
);
/// @solidity memory-safe-assemblyassembly {
for {
let s :=add(orders, add(0x20, shl(5, mload(orders))))
let end :=add(s, mul(0x80, mload(orders)))
} iszero(eq(s, end)) { s :=add(s, 0x80) } {
let from :=mload(add(s, 0x60))
mstore(0x00, 0x01)
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, caller(), mload(s))
log3(0x00, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, caller(), mload(s))
log4(add(s, 0x20), 0x60, _BOUGHT_EVENT_SIGNATURE, mload(s), from, caller())
}
}
}
functioncancelOffer(uint256[] memory ids) publicvirtualnonReentrant{
_callBaseRetWord(abi.encodeWithSignature("cancelOffer(address,uint256[])", msg.sender, ids));
/// @solidity memory-safe-assemblyassembly {
for {
let s :=add(ids, 0x20)
let end :=add(s, shl(5, mload(ids)))
} iszero(eq(s, end)) { s :=add(s, 0x20) } {
log3(codesize(), 0x00, _CANCEL_OFFER_EVENT_SIGNATURE, mload(s), caller())
}
}
}
functionbidForBuy(NFTOrder[] memory orders) publicpayablevirtualnonReentrant{
_callBaseRetWord(
abi.encodeWithSignature(
"bidForBuy(address,(uint256,uint256,address,address)[])", msg.sender, orders
)
);
/// @solidity memory-safe-assemblyassembly {
for {
let s :=add(orders, add(0x20, shl(5, mload(orders))))
let end :=add(s, mul(0x80, mload(orders)))
} iszero(eq(s, end)) { s :=add(s, 0x80) } {
log3(add(s, 0x20), 0x40, _BID_EVENT_SIGNATURE, mload(s), caller())
}
}
}
functionacceptBid(NFTOrder[] memory orders) publicvirtualnonReentrant{
_callBaseRetWord(
abi.encodeWithSignature(
"acceptBid(address,(uint256,uint256,address,address)[])", msg.sender, orders
)
);
/// @solidity memory-safe-assemblyassembly {
for {
let s :=add(orders, add(0x20, shl(5, mload(orders))))
let end :=add(s, mul(0x80, mload(orders)))
} iszero(eq(s, end)) { s :=add(s, 0x80) } {
let to :=mload(add(s, 0x60))
mstore(0x00, 0x01)
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, caller(), to, mload(s))
log3(0x00, 0x20, _UPDATE_LOCK_STATE_EVENT_SIGNATURE, to, mload(s))
log4(add(s, 0x20), 0x60, _BOUGHT_EVENT_SIGNATURE, mload(s), caller(), to)
}
}
}
functioncancelBid(uint256[] memory ids) publicvirtualnonReentrant{
_callBaseRetWord(abi.encodeWithSignature("cancelBid(address,uint256[])", msg.sender, ids));
/// @solidity memory-safe-assemblyassembly {
for {
let s :=add(ids, 0x20)
let end :=add(s, shl(5, mload(ids)))
} iszero(eq(s, end)) { s :=add(s, 0x20) } {
log3(codesize(), 0x00, _CANCEL_BID_EVENT_SIGNATURE, mload(s), caller())
}
}
}
/// @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.functionsupportsInterface(bytes4 interfaceId) publicviewvirtualreturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
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.functionowner() publicviewvirtualreturns (address) {
return _getBT404NFTStorage().owner;
}
/// @dev Permissionless function to pull the owner from the base BT404 contract/// if it implements ownable, for marketplace signaling purposes.functionpullOwner() publicvirtual{
address newOwner;
address base = baseERC20();
/// @solidity memory-safe-assemblyassembly {
mstore(0x00, 0x8da5cb5b) // `owner()`.ifand(gt(returndatasize(), 0x1f), staticcall(gas(), base, 0x1c, 0x04, 0x00, 0x20)) {
newOwner :=shr(96, mload(0x0c))
}
}
BT404NFTStorage storage $ = _getBT404NFTStorage();
address oldOwner = $.owner;
if (oldOwner != newOwner) {
$.owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*//* MIRROR OPERATIONS *//*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*//// @dev Returns the address of the base BT404 contract.functionbaseERC20() publicviewvirtualreturns (address base) {
base = _getBT404NFTStorage().baseERC20;
if (base ==address(0)) revert NotLinked();
}
/// @dev Fallback modifier to execute calls from the base BT404 contract.modifierbt404NFTFallback() virtual{
BT404NFTStorage storage $ = _getBT404NFTStorage();
uint256 fnSelector = _calldataload(0x00) >>224;
// `logTransfer(uint256[])`.if (fnSelector ==0x263c69d6) {
if (msg.sender!= $.baseERC20) revert SenderNotBase();
assembly ("memory-safe") {
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) revert SenderNotBase();
assembly ("memory-safe") {
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) {
revert SenderNotDeployer();
}
}
if ($.baseERC20 !=address(0)) revert AlreadyLinked();
$.baseERC20 =msg.sender;
assembly ("memory-safe") {
mstore(0x00, 0x01)
return(0x00, 0x20)
}
}
_;
}
/// @dev Fallback function for calls from base BT404 contract.fallback() externalpayablevirtualbt404NFTFallback{}
receive() externalpayablevirtual{}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*//* PRIVATE HELPERS *//*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*//// @dev Helper to read owned ids of a account from the base BT404 contractfunction_ownedIds(address account, uint256 begin, uint256 end, bool locked)
privateviewreturns (uint256[] memory result)
{
address base = baseERC20();
/// @solidity memory-safe-assemblyassembly {
result :=mload(0x40)
mstore(0x00, 0xf9b4b328) // `ownedIds(uint256,uint256,uint256)`.mstore(0x20, or(shl(96, account), iszero(iszero(locked))))
mstore(0x40, begin)
mstore(0x60, end)
ifiszero(staticcall(gas(), base, 0x1c, 0x64, 0x00, 0x00)) {
returndatacopy(result, 0x00, returndatasize())
revert(result, returndatasize())
}
returndatacopy(0x00, 0x00, 0x20) // Copy the offset of the array in returndata.returndatacopy(result, mload(0x00), 0x20) // Copy the length of the array.returndatacopy(add(result, 0x20), add(mload(0x00), 0x20), shl(5, mload(result))) // Copy the array elements.mstore(0x40, add(add(result, 0x20), shl(5, mload(result)))) // Allocate memory.mstore(0x60, 0) // Restore the zero pointer.
}
}
/// @dev Helper to read a string from the base BT404 contract.function_readString(uint256 fnSelector, uint256 arg0)
privateviewreturns (stringmemory result)
{
address base = baseERC20();
/// @solidity memory-safe-assemblyassembly {
result :=mload(0x40)
mstore(0x00, fnSelector)
mstore(0x20, arg0)
ifiszero(staticcall(gas(), base, 0x1c, 0x24, 0x00, 0x00)) {
returndatacopy(result, 0x00, returndatasize())
revert(result, returndatasize())
}
returndatacopy(0x00, 0x00, 0x20) // Copy the offset of the string in returndata.returndatacopy(result, mload(0x00), 0x20) // Copy the length of the string.returndatacopy(add(result, 0x20), add(mload(0x00), 0x20), mload(result)) // Copy the string.mstore(0x40, add(add(result, 0x20), mload(result))) // Allocate memory.
}
}
/// @dev Helper to read a word from the base BT404 contract.function_readWord(uint256 fnSelector, uint256 arg0, uint256 arg1)
privateviewreturns (uint256 result)
{
address base = baseERC20();
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40)
mstore(0x00, fnSelector)
mstore(0x20, arg0)
mstore(0x40, arg1)
ifiszero(
and(gt(returndatasize(), 0x1f), staticcall(gas(), base, 0x1c, 0x44, 0x00, 0x20))
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
mstore(0x40, m) // Restore the free memory pointer.
result :=mload(0x00)
}
}
/// @dev Helper to call a function and return a word value.function_callBaseRetWord(bytesmemory _calldata) privatereturns (uint256 result) {
address base = baseERC20();
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40)
ifiszero(
and(
gt(returndatasize(), 0x1f),
call(
gas(), base, callvalue(), add(_calldata, 0x20), mload(_calldata), 0x00, 0x20
)
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
mstore(0x40, m) // Restore the free memory pointer.mstore(0x60, 0) // Restore the zero pointer.
result :=mload(0x00)
}
}
/// @dev Returns the calldata value at `offset`.function_calldataload(uint256 offset) internalpurereturns (uint256 value) {
/// @solidity memory-safe-assemblyassembly {
value :=calldataload(offset)
}
}
/// @dev Returns if `a` has bytecode of non-zero length.function_hasCode(address a) privateviewreturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
result :=extcodesize(a) // Can handle dirty upper bits.
}
}
/// @dev Perform a call to invoke {IERC721Receiver-onERC721Received} on `to`./// Reverts if the target does not support the function correctly.function_checkOnERC721Received(addressfrom, address to, uint256 id, bytesmemory data)
private{
/// @solidity memory-safe-assemblyassembly {
// Prepare the calldata.let m :=mload(0x40)
let onERC721ReceivedSelector :=0x150b7a02mstore(m, onERC721ReceivedSelector)
mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`.mstore(add(m, 0x40), shr(96, shl(96, from)))
mstore(add(m, 0x60), id)
mstore(add(m, 0x80), 0x80)
let n :=mload(data)
mstore(add(m, 0xa0), n)
if n { pop(staticcall(gas(), 4, add(data, 0x20), n, add(m, 0xc0), n)) }
// Revert if the call reverts.ifiszero(call(gas(), to, 0, add(m, 0x1c), add(n, 0xa4), m, 0x20)) {
ifreturndatasize() {
// Bubble up the revert if the call reverts.returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
}
// Load the returndata and compare it.ifiszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) {
mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`.revert(0x1c, 0x04)
}
}
}
}
Contract Source Code
File 2 of 6: Base64.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @notice Library to encode strings in Base64./// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base64.sol)/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Base64.sol)/// @author Modified from (https://github.com/Brechtpd/base64/blob/main/base64.sol) by Brecht Devos - <brecht@loopring.org>.libraryBase64{
/// @dev Encodes `data` using the base64 encoding described in RFC 4648./// See: https://datatracker.ietf.org/doc/html/rfc4648/// @param fileSafe Whether to replace '+' with '-' and '/' with '_'./// @param noPadding Whether to strip away the padding.functionencode(bytesmemory data, bool fileSafe, bool noPadding)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
let dataLength :=mload(data)
if dataLength {
// Multiply by 4/3 rounded up.// The `shl(2, ...)` is equivalent to multiplying by 4.let encodedLength :=shl(2, div(add(dataLength, 2), 3))
// Set `result` to point to the start of the free memory.
result :=mload(0x40)
// Store the table into the scratch space.// Offsetted by -1 byte so that the `mload` will load the character.// We will rewrite the free memory pointer at `0x40` later with// the allocated size.// The magic constant 0x0670 will turn "-_" into "+/".mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef")
mstore(0x3f, xor("ghijklmnopqrstuvwxyz0123456789-_", mul(iszero(fileSafe), 0x0670)))
// Skip the first slot, which stores the length.let ptr :=add(result, 0x20)
let end :=add(ptr, encodedLength)
let dataEnd :=add(add(0x20, data), dataLength)
let dataEndValue :=mload(dataEnd) // Cache the value at the `dataEnd` slot.mstore(dataEnd, 0x00) // Zeroize the `dataEnd` slot to clear dirty bits.// Run over the input, 3 bytes at a time.for {} 1 {} {
data :=add(data, 3) // Advance 3 bytes.let input :=mload(data)
// Write 4 bytes. Optimized for fewer stack operations.mstore8(0, mload(and(shr(18, input), 0x3F)))
mstore8(1, mload(and(shr(12, input), 0x3F)))
mstore8(2, mload(and(shr(6, input), 0x3F)))
mstore8(3, mload(and(input, 0x3F)))
mstore(ptr, mload(0x00))
ptr :=add(ptr, 4) // Advance 4 bytes.ifiszero(lt(ptr, end)) { break }
}
mstore(dataEnd, dataEndValue) // Restore the cached value at `dataEnd`.mstore(0x40, add(end, 0x20)) // Allocate the memory.// Equivalent to `o = [0, 2, 1][dataLength % 3]`.let o :=div(2, mod(dataLength, 3))
// Offset `ptr` and pad with '='. We can simply write over the end.mstore(sub(ptr, o), shl(240, 0x3d3d))
// Set `o` to zero if there is padding.
o :=mul(iszero(iszero(noPadding)), o)
mstore(sub(ptr, o), 0) // Zeroize the slot after the string.mstore(result, sub(encodedLength, o)) // Store the length.
}
}
}
/// @dev Encodes `data` using the base64 encoding described in RFC 4648./// Equivalent to `encode(data, false, false)`.functionencode(bytesmemory data) internalpurereturns (stringmemory result) {
result = encode(data, false, false);
}
/// @dev Encodes `data` using the base64 encoding described in RFC 4648./// Equivalent to `encode(data, fileSafe, false)`.functionencode(bytesmemory data, bool fileSafe)
internalpurereturns (stringmemory result)
{
result = encode(data, fileSafe, false);
}
/// @dev Decodes base64 encoded `data`.////// Supports:/// - RFC 4648 (both standard and file-safe mode)./// - RFC 3501 (63: ',').////// Does not support:/// - Line breaks.////// Note: For performance reasons,/// this function will NOT revert on invalid `data` inputs./// Outputs for invalid inputs will simply be undefined behaviour./// It is the user's responsibility to ensure that the `data`/// is a valid base64 encoded string.functiondecode(stringmemory data) internalpurereturns (bytesmemory result) {
/// @solidity memory-safe-assemblyassembly {
let dataLength :=mload(data)
if dataLength {
let decodedLength :=mul(shr(2, dataLength), 3)
for {} 1 {} {
// If padded.ifiszero(and(dataLength, 3)) {
let t :=xor(mload(add(data, dataLength)), 0x3d3d)
// forgefmt: disable-next-item
decodedLength :=sub(
decodedLength,
add(iszero(byte(30, t)), iszero(byte(31, t)))
)
break
}
// If non-padded.
decodedLength :=add(decodedLength, sub(and(dataLength, 3), 1))
break
}
result :=mload(0x40)
// Write the length of the bytes.mstore(result, decodedLength)
// Skip the first slot, which stores the length.let ptr :=add(result, 0x20)
let end :=add(ptr, decodedLength)
// Load the table into the scratch space.// Constants are optimized for smaller bytecode with zero gas overhead.// `m` also doubles as the mask of the upper 6 bits.let m :=0xfc000000fc00686c7074787c8084888c9094989ca0a4a8acb0b4b8bcc0c4c8ccmstore(0x5b, m)
mstore(0x3b, 0x04080c1014181c2024282c3034383c4044484c5054585c6064)
mstore(0x1a, 0xf8fcf800fcd0d4d8dce0e4e8ecf0f4)
for {} 1 {} {
// Read 4 bytes.
data :=add(data, 4)
let input :=mload(data)
// Write 3 bytes.// forgefmt: disable-next-itemmstore(ptr, or(
and(m, mload(byte(28, input))),
shr(6, or(
and(m, mload(byte(29, input))),
shr(6, or(
and(m, mload(byte(30, input))),
shr(6, mload(byte(31, input)))
))
))
))
ptr :=add(ptr, 3)
ifiszero(lt(ptr, end)) { break }
}
mstore(0x40, add(end, 0x20)) // Allocate the memory.mstore(end, 0) // Zeroize the slot after the bytes.mstore(0x60, 0) // Restore the zero slot.
}
}
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @notice Library for converting numbers into strings and other string operations./// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol)/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)////// @dev Note:/// For performance and bytecode compactness, most of the string operations are restricted to/// byte strings (7-bit ASCII), except where otherwise specified./// Usage of byte string operations on charsets with runes spanning two or more bytes/// can lead to undefined behavior.libraryLibString{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CUSTOM ERRORS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The length of the output is too small to contain all the hex digits.errorHexLengthInsufficient();
/// @dev The length of the string is more than 32 bytes.errorTooBigForSmallString();
/// @dev The input string must be a 7-bit ASCII.errorStringNot7BitASCII();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CONSTANTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The constant returned when the `search` is not found in the string.uint256internalconstant NOT_FOUND =type(uint256).max;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.uint128internalconstant ALPHANUMERIC_7_BIT_ASCII =0x7fffffe07fffffe03ff000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.uint128internalconstant LETTERS_7_BIT_ASCII =0x7fffffe07fffffe0000000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyz'.uint128internalconstant LOWERCASE_7_BIT_ASCII =0x7fffffe000000000000000000000000;
/// @dev Lookup for 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.uint128internalconstant UPPERCASE_7_BIT_ASCII =0x7fffffe0000000000000000;
/// @dev Lookup for '0123456789'.uint128internalconstant DIGITS_7_BIT_ASCII =0x3ff000000000000;
/// @dev Lookup for '0123456789abcdefABCDEF'.uint128internalconstant HEXDIGITS_7_BIT_ASCII =0x7e0000007e03ff000000000000;
/// @dev Lookup for '01234567'.uint128internalconstant OCTDIGITS_7_BIT_ASCII =0xff000000000000;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'.uint128internalconstant PRINTABLE_7_BIT_ASCII =0x7fffffffffffffffffffffff00003e00;
/// @dev Lookup for '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.uint128internalconstant PUNCTUATION_7_BIT_ASCII =0x78000001f8000001fc00fffe00000000;
/// @dev Lookup for ' \t\n\r\x0b\x0c'.uint128internalconstant WHITESPACE_7_BIT_ASCII =0x100003e00;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* DECIMAL OPERATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns the base 10 decimal representation of `value`.functiontoString(uint256 value) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
// 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.ifiszero(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`.functiontoString(int256 value) internalpurereturns (stringmemory result) {
if (value >=0) return toString(uint256(value));
unchecked {
result = toString(~uint256(value) +1);
}
/// @solidity memory-safe-assemblyassembly {
// 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 `length` bytes./// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,/// giving a total length of `length * 2 + 2` bytes./// Reverts if `length` is too small for the output to contain all the digits.functiontoHexString(uint256 value, uint256 length)
internalpurereturns (stringmemory result)
{
result = toHexStringNoPrefix(value, length);
/// @solidity memory-safe-assemblyassembly {
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 `length` bytes./// The output is not prefixed with "0x" and is encoded using 2 hexadecimal digits per byte,/// giving a total length of `length * 2` bytes./// Reverts if `length` is too small for the output to contain all the digits.functiontoHexStringNoPrefix(uint256 value, uint256 length)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
// We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes// for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length.// We add 0x20 to the total and round down to a multiple of 0x20.// (0x20 + 0x20 + 0x02 + 0x20) = 0x62.
result :=add(mload(0x40), and(add(shl(1, length), 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(length, length))
let w :=not(1) // Tsk.let temp := value
// We write the string from rightmost digit to leftmost digit.// The following is essentially a do-while loop that also handles the zero case.for {} 1 {} {
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)
ifiszero(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.functiontoHexString(uint256 value) internalpurereturns (stringmemory result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
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"`.functiontoMinimalHexString(uint256 value) internalpurereturns (stringmemory result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
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"`.functiontoMinimalHexStringNoPrefix(uint256 value)
internalpurereturns (stringmemory result)
{
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
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.functiontoHexStringNoPrefix(uint256 value) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
// 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)
ifiszero(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-55functiontoHexStringChecksummed(address value) internalpurereturns (stringmemory result) {
result = toHexString(value);
/// @solidity memory-safe-assemblyassembly {
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)
ifeq(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.functiontoHexString(address value) internalpurereturns (stringmemory result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
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.functiontoHexStringNoPrefix(address value) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
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)
ifeq(i, 20) { break }
}
}
}
/// @dev Returns the hex encoded string from the raw bytes./// The output is encoded using 2 hexadecimal digits per byte.functiontoHexString(bytesmemory raw) internalpurereturns (stringmemory result) {
result = toHexStringNoPrefix(raw);
/// @solidity memory-safe-assemblyassembly {
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.functiontoHexStringNoPrefix(bytesmemory raw) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
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.functionruneCount(stringmemory s) internalpurereturns (uint256 result) {
/// @solidity memory-safe-assemblyassembly {
ifmload(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)))))
ifiszero(lt(o, end)) { break }
}
}
}
}
/// @dev Returns if this string is a 7-bit ASCII string./// (i.e. all characters codes are in [0..127])functionis7BitASCII(stringmemory s) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
result :=1let 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 {} {
ifand(mask, mload(o)) {
result :=0break
}
o :=add(o, 0x20)
ifiszero(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`.functionis7BitASCII(stringmemory s, uint128 allowed) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
result :=1ifmload(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)
ifiszero(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.functionto7BitASCIIAllowedLookup(stringmemory s) internalpurereturns (uint128 result) {
/// @solidity memory-safe-assemblyassembly {
ifmload(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)
ifiszero(lt(o, end)) { break }
}
ifshr(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`.functionreplace(stringmemory subject, stringmemory needle, stringmemory replacement)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
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.let end :=add(i, mload(subject))
ifiszero(gt(needleLen, mload(subject))) {
let subjectSearchEnd :=add(sub(end, needleLen), 1)
let h :=0// The hash of `needle`.ifiszero(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.ifiszero(shr(m, xor(t, s))) {
if h {
ifiszero(eq(keccak256(i, needleLen), h)) {
mstore(add(i, d), t)
i :=add(i, 1)
ifiszero(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)
ifiszero(lt(j, replacementLen)) { break }
}
d :=sub(add(d, replacementLen), needleLen)
if needleLen {
i :=add(i, needleLen)
ifiszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
mstore(add(i, d), t)
i :=add(i, 1)
ifiszero(lt(i, subjectSearchEnd)) { break }
}
}
let n :=add(sub(d, add(result, 0x20)), end)
// Copy the rest of the string 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 string.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.functionindexOf(stringmemory subject, stringmemory needle, uint256from)
internalpurereturns (uint256 result)
{
/// @solidity memory-safe-assemblyassembly {
result :=not(0) // Initialize to `NOT_FOUND`.for { let subjectLen :=mload(subject) } 1 {} {
ifiszero(mload(needle)) {
result := from
ifiszero(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))
ifiszero(and(lt(subject, end), lt(from, subjectLen))) { break }
ifiszero(lt(needleLen, 0x20)) {
for { let h :=keccak256(add(needle, 0x20), needleLen) } 1 {} {
ifiszero(shr(m, xor(mload(subject), s))) {
ifeq(keccak256(subject, needleLen), h) {
result :=sub(subject, subjectStart)
break
}
}
subject :=add(subject, 1)
ifiszero(lt(subject, end)) { break }
}
break
}
for {} 1 {} {
ifiszero(shr(m, xor(mload(subject), s))) {
result :=sub(subject, subjectStart)
break
}
subject :=add(subject, 1)
ifiszero(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.functionindexOf(stringmemory subject, stringmemory needle)
internalpurereturns (uint256 result)
{
result = 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.functionlastIndexOf(stringmemory subject, stringmemory needle, uint256from)
internalpurereturns (uint256 result)
{
/// @solidity memory-safe-assemblyassembly {
for {} 1 {} {
result :=not(0) // Initialize to `NOT_FOUND`.let needleLen :=mload(needle)
ifgt(needleLen, mload(subject)) { break }
let w := result
let fromMax :=sub(mload(subject), needleLen)
ifiszero(gt(fromMax, from)) { from := fromMax }
let end :=add(add(subject, 0x20), w)
subject :=add(add(subject, 0x20), from)
ifiszero(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 {} {
ifeq(keccak256(subject, needleLen), h) {
result :=sub(subject, add(end, 1))
break
}
subject :=add(subject, w) // `sub(subject, 1)`.ifiszero(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.functionlastIndexOf(stringmemory subject, stringmemory needle)
internalpurereturns (uint256 result)
{
result = lastIndexOf(subject, needle, type(uint256).max);
}
/// @dev Returns true if `needle` is found in `subject`, false otherwise.functioncontains(stringmemory subject, stringmemory needle) internalpurereturns (bool) {
return indexOf(subject, needle) != NOT_FOUND;
}
/// @dev Returns whether `subject` starts with `needle`.functionstartsWith(stringmemory subject, stringmemory needle)
internalpurereturns (bool result)
{
/// @solidity memory-safe-assemblyassembly {
let needleLen :=mload(needle)
// Just using keccak256 directly is actually cheaper.// forgefmt: disable-next-item
result :=and(
iszero(gt(needleLen, mload(subject))),
eq(
keccak256(add(subject, 0x20), needleLen),
keccak256(add(needle, 0x20), needleLen)
)
)
}
}
/// @dev Returns whether `subject` ends with `needle`.functionendsWith(stringmemory subject, stringmemory needle)
internalpurereturns (bool result)
{
/// @solidity memory-safe-assemblyassembly {
let needleLen :=mload(needle)
// Whether `needle` is not longer than `subject`.let inRange :=iszero(gt(needleLen, mload(subject)))
// Just using keccak256 directly is actually cheaper.// forgefmt: disable-next-item
result :=and(
eq(
keccak256(
// `subject + 0x20 + max(subjectLen - needleLen, 0)`.add(add(subject, 0x20), mul(inRange, sub(mload(subject), needleLen))),
needleLen
),
keccak256(add(needle, 0x20), needleLen)
),
inRange
)
}
}
/// @dev Returns `subject` repeated `times`.functionrepeat(stringmemory subject, uint256 times)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
let subjectLen :=mload(subject)
ifiszero(or(iszero(times), iszero(subjectLen))) {
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)
ifiszero(lt(j, subjectLen)) { break }
}
o :=add(o, subjectLen)
times :=sub(times, 1)
ifiszero(times) { break }
}
mstore(o, 0) // Zeroize the slot after the string.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.functionslice(stringmemory subject, uint256 start, uint256 end)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
let subjectLen :=mload(subject)
ifiszero(gt(subjectLen, end)) { end := subjectLen }
ifiszero(gt(subjectLen, start)) { start := subjectLen }
iflt(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)`.ifiszero(j) { break }
}
let o :=add(add(result, 0x20), n)
mstore(o, 0) // Zeroize the slot after the string.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 string./// `start` is a byte offset.functionslice(stringmemory subject, uint256 start)
internalpurereturns (stringmemory result)
{
result = slice(subject, start, type(uint256).max);
}
/// @dev Returns all the indices of `needle` in `subject`./// The indices are byte offsets.functionindicesOf(stringmemory subject, stringmemory needle)
internalpurereturns (uint256[] memory result)
{
/// @solidity memory-safe-assemblyassembly {
let searchLen :=mload(needle)
ifiszero(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`.ifiszero(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.ifiszero(shr(m, xor(t, s))) {
if h {
ifiszero(eq(keccak256(i, searchLen), h)) {
i :=add(i, 1)
ifiszero(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 {
ifiszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
i :=add(i, 1)
ifiszero(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 strings based on the `delimiter` inside of the `subject` string.functionsplit(stringmemory subject, stringmemory delimiter)
internalpurereturns (string[] memory result)
{
uint256[] memory indices = indicesOf(subject, delimiter);
/// @solidity memory-safe-assemblyassembly {
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)
ifiszero(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)`.ifiszero(o) { break }
}
mstore(add(add(element, 0x20), l), 0) // Zeroize the slot after the string.// 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)
ifiszero(lt(indexPtr, indicesEnd)) { break }
}
result := indices
ifiszero(mload(delimiter)) {
result :=add(indices, 0x20)
mstore(result, sub(mload(indices), 2))
}
}
}
/// @dev Returns a concatenated string of `a` and `b`./// Cheaper than `string.concat()` and does not de-align the free memory pointer.functionconcat(stringmemory a, stringmemory b)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
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)`.ifiszero(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)`.ifiszero(o) { break }
}
let totalLen :=add(aLen, bLen)
let last :=add(add(result, 0x20), totalLen)
mstore(last, 0) // Zeroize the slot after the string.mstore(result, totalLen) // Store the length.mstore(0x40, add(last, 0x20)) // Allocate memory.
}
}
/// @dev Returns a copy of the string in either lowercase or UPPERCASE./// WARNING! This function is only compatible with 7-bit ASCII strings.functiontoCase(stringmemory subject, bool toUpper)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
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)
ifeq(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.functionfromSmallString(bytes32 s) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
result :=mload(0x40)
let n :=0for {} 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.functionnormalizeSmallString(bytes32 s) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
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.functiontoSmallString(stringmemory s) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
result :=mload(s)
ifiszero(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.functionlower(stringmemory subject) internalpurereturns (stringmemory result) {
result = toCase(subject, false);
}
/// @dev Returns an UPPERCASED copy of the string./// WARNING! This function is only compatible with 7-bit ASCII strings.functionupper(stringmemory subject) internalpurereturns (stringmemory result) {
result = toCase(subject, true);
}
/// @dev Escapes the string to be used within HTML tags.functionescapeHTML(stringmemory s) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
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 `["\"","'","&","<",">"]`.ifiszero(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.functionescapeJSON(stringmemory s, bool addDoubleQuotes)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
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)
ifiszero(lt(c, 0x20)) {
ifiszero(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
}
ifiszero(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.functionescapeJSON(stringmemory s) internalpurereturns (stringmemory 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/rfc3986functionencodeURIComponent(stringmemory s) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
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-_.!~*'()]`.ifiszero(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`.functioneq(stringmemory a, stringmemory b) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
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.functioneqs(stringmemory a, bytes32 b) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
// These should be evaluated on compile time, as far as possible.let m :=not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`.let x :=not(or(m, or(b, add(m, and(b, m)))))
let r :=shl(7, iszero(iszero(shr(128, x))))
r :=or(r, shl(6, iszero(iszero(shr(64, shr(r, x))))))
r :=or(r, shl(5, lt(0xffffffff, shr(r, x))))
r :=or(r, shl(4, lt(0xffff, shr(r, x))))
r :=or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
result :=gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))),
xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20)))))
}
}
/// @dev Packs a single string with its length into a single word./// Returns `bytes32(0)` if the length is zero or greater than 31.functionpackOne(stringmemory a) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
// 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.functionunpackOne(bytes32 packed) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
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.functionpackTwo(stringmemory a, stringmemory b) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
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.functionunpackTwo(bytes32 packed)
internalpurereturns (stringmemory resultA, stringmemory resultB)
{
/// @solidity memory-safe-assemblyassembly {
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.functiondirectReturn(stringmemory a) internalpure{
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)))
}
}
}