Overview
ETH Balance
0 ETH
Eth Value
$0.00| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||
|---|---|---|---|---|---|---|---|
| 0x60793d81 | 19035904 | 803 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Minimal Proxy Contract for 0x00000000000060d035a8002956b5fb02e3968eec
Contract Name:
Stash
Compiler Version
v0.8.23+commit.f704f362
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {IAuction} from "./interfaces/IAuction.sol";
import {IWETH} from "./interfaces/IWETH.sol";
import {OrderType} from "./helpers/Enum.sol";
import {Order, PunkBid} from "./helpers/Struct.sol";
import {MerkleProofLib} from "lib/solady/src/utils/MerkleProofLib.sol";
import {SignatureCheckerLib} from "lib/solady/src/utils/SignatureCheckerLib.sol";
import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";
import {IERC721} from "forge-std/interfaces/IERC721.sol";
import {IERC1155} from "forge-std/interfaces/IERC1155.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {ILegacyWrappedPunks} from "./interfaces/ILegacyWrappedPunks.sol";
import {ICryptoPunks721} from "./interfaces/ICryptoPunks721.sol";
import {ICryptoPunks} from "./interfaces/ICryptoPunks.sol";
import {IStashFactory} from "./interfaces/IStashFactory.sol";
import {IPunkTransferHelper} from "./interfaces/IPunkTransferHelper.sol";
/**
* @title Stash
* @author Yuga Labs
* @custom:security-contact security@yugalabs.io
* @notice A multipurpose user deployed contract.
*/
contract Stash {
// --------------------- STASH EVENTS ---------------------
/// @dev Emitted when an order is placed.
event OrderPlaced(Order order);
/// @dev Emitted when an order is updated.
event OrderUpdated(Order originalOrder, Order updatedOrder);
/// @dev Emitted when an order is removed, either because it was filled or the auction was finalized.
event OrderRemoved(Order order);
/// @dev Emitted when a punk bid is canceled.
event PunkBidCanceled(uint256 indexed bidNonce);
/// @dev Emitted when the Stash's global nonce is incremented, cancelling all Punk bids.
event AllPunkBidsCanceled();
/// @dev Emitted when a punk bid is accepted.
event PunkBidAccepted(uint256 indexed price, uint256 indexed punkIndex);
// --------------------- CUSTOM ERRORS ---------------------
/// @dev The punk bid has expired.
error BidExpired();
/// @dev The bid either has zero units or does not include the CryptoPunks address.
error InvalidBid();
/// @dev The bid has been used or canceled.
error BidCanceled();
/// @dev The caller is not authorized to perform this action.
error Unauthorized();
/// @dev The order does not exist. It may have been filled, canceled, or never existed.
error OrderNotFound();
/// @dev The merkle proof provided does not match the CryptoPunk Bid.
error InvalidProof();
/// @dev The vault already has 10 orders for the given payment token.
error TooManyOrders();
/// @dev The order type is not supported by the Stash.
error UnknownOrderType();
/// @dev The Punk is either not listed for sale, or there was an error paying the caller.
error FailedToBuyPunk();
/// @dev This Stash does not have an active bid on the auction that is attempting to process the order.
error NoBidForAuction();
/// @dev The provided signature does not match the provided CryptoPunk bid.
error InvalidSignature();
/// @dev The caller is not a valid auction contract, as determined by the StashFactory.
error CallerNotAuction();
/// @dev Payment to the Stash owner failed.
error FailedToWithdraw();
/// @dev The Stash has already been initialized.
error AlreadyInitialized();
/// @dev The order is being altered in a way that is not allowed by the order type.
error InvalidOrderAlteration();
/// @dev An order or withdrawal is being requested for an amount that exceeds the available balance.
error RequestExceedsAvailableBalance();
/// @dev An auction is attempting to pull more funds than the Stash owner has approved from the Stash.
error CannotTransferMoreThanBidAmount();
// --------------------- MODIFIERS ---------------------
modifier onlyOwner() {
if (msg.sender != owner) revert CallerNotAuction();
_;
}
// ----------------- CONSTANTS & IMMUTABLES -----------------
uint256 private constant _VERSION = 1;
bytes32 private constant _COLLECTION_BID_ROOT = bytes32(0);
bytes32 private constant _ORDER_TYPEHASH =
keccak256("Order(uint16 numberOfUnits,uint80 pricePerUnit,address auction)");
bytes32 private constant _PUNK_BID_TYPEHASH = keccak256(
"PunkBid(Order order,uint256 accountNonce,uint256 bidNonce,uint256 expiration,bytes32 root)Order(uint16 numberOfUnits,uint80 pricePerUnit,address auction)"
);
ICryptoPunks private immutable _CRYPTOPUNKS;
ILegacyWrappedPunks private immutable _LEGACY_WRAPPED_PUNKS;
ICryptoPunks721 private immutable _CRYPTOPUNKS_721;
IWETH private immutable _WETH;
IStashFactory private immutable _STASH_FACTORY;
IPunkTransferHelper private immutable _PUNK_TRANSFER_HELPER;
// -------------------- CONSTRUCTOR --------------------
constructor(
address stashFactory,
address weth,
address punks,
address legacyWrappedPunks,
address cryptoPunksWrapped,
address punkTransferHelper
) {
_STASH_FACTORY = IStashFactory(stashFactory);
_WETH = IWETH(weth);
_CRYPTOPUNKS = ICryptoPunks(punks);
_LEGACY_WRAPPED_PUNKS = ILegacyWrappedPunks(legacyWrappedPunks);
_CRYPTOPUNKS_721 = ICryptoPunks721(cryptoPunksWrapped);
_PUNK_TRANSFER_HELPER = IPunkTransferHelper(punkTransferHelper);
_initialized = true;
}
// --------------------- STORAGE ---------------------
/// @dev Whether or not the contract has been initialized.
bool private _initialized;
/// @notice The permanent and immutable owner of the stash. Set once at initialization.
address public owner;
/// @notice The current nonce of the stash owner's account. Used for punk bidding, can be incremented to cancel all open bids.
uint56 public punkAccountNonce;
/// @notice A mapping of punk bid nonces to the number of times they can be used.
mapping(uint256 punkBidNonce => uint256 usesRemaining) public punkBidNonceUsesRemaining;
/// @notice A mapping of punk bid nonces to whether or not they have been used.
mapping(uint256 punkBidNonce => bool isUsed) public usedPunkBidNonces;
/// @notice Returns an array of all current orders for a given payment token.
mapping(address paymentToken => Order[] orders) public paymentTokenToOrders;
// --------------------- EXTERNAL ---------------------
// allow receiving ETH.
receive() external payable {}
/**
* @notice Initializes the contract. This is called only once upon deployment by the StashFactory.
* @param _owner The permanent and immutable owner of the stash.
*/
function initialize(address _owner) external {
if (_initialized) revert AlreadyInitialized();
owner = _owner;
_initialized = true;
}
/**
* @notice Places an order for an auction. If one exists, it will be replaced or incremented depending on the order type.
* @param pricePerUnit The price per unit of the order.
* @param numberOfUnits The number of units included in the order.
* @dev The stash owner must initiate this transaction by calling the corresponding bid function on a valid auction contract.
*/
function placeOrder(uint80 pricePerUnit, uint16 numberOfUnits) external payable {
// Prevent unwanted bids by enforcing that the user initiated the transaction and that the caller is a registered auction.
if (tx.origin != owner) revert Unauthorized();
if (!_STASH_FACTORY.isAuction(msg.sender)) revert CallerNotAuction();
(address paymentToken, OrderType orderType) = IAuction(msg.sender).bidConfig();
uint256 paymentTokenBalance = _balanceOfToken(paymentToken);
(uint256 lockedAmount, uint256 finalizedIndexes) = _totalLockedAndStaleBids(paymentToken);
uint256 _availableLiquidity;
unchecked {
// Locked amount cannot exceed paymentTokenBalance.
_availableLiquidity = paymentTokenBalance - lockedAmount;
}
_cleanStaleBids(paymentToken, finalizedIndexes);
Order memory newOrder = Order(numberOfUnits, pricePerUnit, msg.sender);
Order[] storage _orders = paymentTokenToOrders[paymentToken];
for (uint256 i = 0; i < _orders.length; ++i) {
Order storage _order = _orders[i];
if (_order.auction == msg.sender) {
// cache the existing order to emit an event later.
Order memory existingOrder = _order;
// This will check that the stash has funds to cover the order, and modify the existing order in place.
_replaceOrIncrementExistingOrders(_order, numberOfUnits, pricePerUnit, _availableLiquidity, orderType);
emit OrderUpdated(existingOrder, _order);
return;
}
}
if (_bidDeltaExceedsLiquidity(0, uint256(numberOfUnits) * pricePerUnit, _availableLiquidity)) {
revert RequestExceedsAvailableBalance();
}
_orders.push(newOrder);
/**
* Limit the number of orders to 10 to prevent gas issues. Realistically, there will only ever be one order
* per payment token at a time. This is just a safety measure.
*/
if (_orders.length > 10) revert TooManyOrders();
emit OrderPlaced(newOrder);
}
/**
* @notice Processes an order for a given auction, transferring payment to the auction contract.
* @param costPerUnit The cost per unit of the order.
* @param numberOfUnits The number of units to process.
* @dev This function is called by the auction contract, which should handle minting corresponding units
* as part of the transaction. The order's numberOfUnits will be lowered by numberOfUnits, and the order will be
* removed if numberOfUnits is equal to the order's numberOfUnits.
*/
function processOrder(uint80 costPerUnit, uint16 numberOfUnits) external {
if (!_STASH_FACTORY.isAuction(msg.sender)) revert CallerNotAuction();
(address paymentToken,) = IAuction(msg.sender).bidConfig();
Order[] storage _orders = paymentTokenToOrders[paymentToken];
for (uint256 i = 0; i < _orders.length;) {
Order storage _order = _orders[i];
if (_order.auction == msg.sender) {
if (costPerUnit > _order.pricePerUnit || numberOfUnits > _order.numberOfUnits) {
revert CannotTransferMoreThanBidAmount();
}
if (numberOfUnits == _order.numberOfUnits) {
_removeBid(paymentToken, i);
} else {
// cache the existing order to emit an event later.
Order memory _originalOrder = _order;
unchecked {
_order.numberOfUnits -= numberOfUnits;
}
emit OrderUpdated(_originalOrder, _order);
}
_transferTokens(paymentToken, uint256(numberOfUnits) * costPerUnit);
return;
} else {
unchecked {
++i;
}
}
}
revert NoBidForAuction();
}
/**
* @notice Allows selling a punk to the stash. A valid signature from the stash owner is required for successful execution.
* @param bid The bid that was signed off-chain.
* @param punkIndex The id of the punk to sell. Must be included in the bid's merkle tree.
* @param signature The signed bid.
* @param proof The merkle proof for the punkIndex.
* @dev This function will revert if the bid is invalid, expired, or canceled. It will also revert if the bid.
* does not contain the punkIndex in its merkle tree. If the bid is valid, the punk will be transferred to the stash.
* owner and the bid's numberOfUnits will be decremented. If numberOfUnits is 1, the bid will be marked as used.
*/
function processPunkBid(PunkBid calldata bid, uint256 punkIndex, bytes memory signature, bytes32[] calldata proof)
external
{
uint256 availableETH = availableLiquidity(address(0));
Order calldata order = bid.order;
uint256 bidPrice = order.pricePerUnit;
if (order.numberOfUnits == 0) revert InvalidBid();
if (order.auction != address(_CRYPTOPUNKS)) revert InvalidBid();
if (punkAccountNonce != bid.accountNonce) revert BidCanceled();
if (usedPunkBidNonces[bid.bidNonce]) revert BidCanceled();
if (bid.expiration > 0 && block.timestamp > bid.expiration) revert BidExpired();
if (!_isValidSignature(bid, signature)) revert InvalidSignature();
if (bid.root != _COLLECTION_BID_ROOT) {
if (!MerkleProofLib.verifyCalldata(proof, bid.root, keccak256(abi.encode(punkIndex)))) {
revert InvalidProof();
}
}
// if balance is too low, we try to use owner's approved weth to supplement.
if (bidPrice > availableETH) {
_swapWETH(bidPrice - availableETH);
}
uint256 remainingUnits = punkBidNonceUsesRemaining[bid.bidNonce];
// we have already checked if the nonce is marked as used, so if remainingUnits is 0, this is the first use.
if (remainingUnits == 0) {
if (order.numberOfUnits == 1) {
usedPunkBidNonces[bid.bidNonce] = true;
} else {
unchecked {
punkBidNonceUsesRemaining[bid.bidNonce] = order.numberOfUnits - 1;
}
}
// If remainingUnits is greater than 1, decrement it.
} else if (remainingUnits > 1) {
unchecked {
--punkBidNonceUsesRemaining[bid.bidNonce];
}
// remainingUnits is 1 - this is the last use, so mark the nonce as used.
} else {
delete punkBidNonceUsesRemaining[bid.bidNonce];
usedPunkBidNonces[bid.bidNonce] = true;
}
(bool isPunkForSaleInLegacyMarketplace,,, uint256 minValue,) = _CRYPTOPUNKS.punksOfferedForSale(punkIndex);
uint256 amountDueToCaller;
// if the punk is listed for sale, we can infer that it is unwrapped.
if (isPunkForSaleInLegacyMarketplace) {
if (minValue > bidPrice) {
revert CannotTransferMoreThanBidAmount();
} else if (minValue < bidPrice) {
/**
* If the punk is listed under the bid, incentivize MEV fulfillment by paying the difference to the caller.
* We cache the value to be used after finalization to avoid reentrancy issues.
*/
unchecked {
amountDueToCaller = bidPrice - minValue;
}
}
_CRYPTOPUNKS.buyPunk{value: minValue}(punkIndex);
// If it is not listed, we can still fulfill the bid if the punk is wrapped in the legacy or 721 wrapper.
} else {
/**
* If the punk is wrapped in the legacy wrapper or CryptoPunks721, we need to unwrap it. Owners of Legacy Wrapped
* Punks or CryptoPunks721 have to approve their punks to the PunkTransferHelper. To prevent the Stash owner from
* maliciously replacing the bid with a lower price after the wrapped punk has already been approved, while unwrapped
* punk bids can be fulfilled by anybody, processing bids using punks that are wrapped will verify that msg.sender is
* the owner of the punk.
*/
address _unwrappedCryptoPunkOwner = _CRYPTOPUNKS.punkIndexToAddress(punkIndex);
bytes32 _punkIndexAndOwnerAddressPacked;
assembly {
// Shift _punkIndex left by 160 bits to make room for the _assetContract
let shiftedPunkIndex := shl(160, punkIndex)
// Combine shifted _punkIndex with _assetContract
_punkIndexAndOwnerAddressPacked := or(shiftedPunkIndex, caller())
}
if (_unwrappedCryptoPunkOwner == address(_LEGACY_WRAPPED_PUNKS)) {
// Transfer punk to stash for unwrapping. This implicitly checks that caller is owner of wrapped punk to prevent malicious bid replacement:
_PUNK_TRANSFER_HELPER.transferLegacyWrappedPunkToStash(_punkIndexAndOwnerAddressPacked);
// Unwrap punk:
_LEGACY_WRAPPED_PUNKS.burn(punkIndex);
} else if (_unwrappedCryptoPunkOwner == address(_CRYPTOPUNKS_721)) {
// Transfer punk to stash for unwrapping. This implicitly checks that caller is owner of wrapped punk to prevent malicious bid replacement:
_PUNK_TRANSFER_HELPER.transfer721PunkToStash(_punkIndexAndOwnerAddressPacked);
// Unwrap punk:
_CRYPTOPUNKS_721.unwrapPunk(punkIndex);
} else {
revert FailedToBuyPunk();
}
// set bidPrice as amount to pay caller. Caller will always be the stash owner in this case:
amountDueToCaller = bidPrice;
}
_CRYPTOPUNKS.transferPunk(owner, punkIndex);
if (amountDueToCaller > 0) {
(bool callerPaid,) = payable(msg.sender).call{value: amountDueToCaller}("");
if (!callerPaid) revert FailedToBuyPunk();
}
emit PunkBidAccepted(bidPrice, punkIndex);
}
/**
* @notice Cancels a bid.
* @param bidNonce The nonce of the bid to cancel.
*/
function cancelPunkBid(uint256 bidNonce) external onlyOwner {
usedPunkBidNonces[bidNonce] = true;
emit PunkBidCanceled(bidNonce);
}
/**
* @notice increments the global account nonce, canceling all existing offchain bids.
* @dev a very motivated stash owner could overflow their nonce, but there would be no benefit to doing so.
*/
function cancelAllPunkBids() external onlyOwner {
unchecked {
++punkAccountNonce;
}
emit AllPunkBidsCanceled();
}
// --------------------- WITHDRAWALS ---------------------
/**
* @notice Used by the CryptoPunks721 contract to wrap punks. Punks must be deposited to the Stash for wrapping.
* @param punkIndex The index of the punk to wrap.
*/
function wrapPunk(uint256 punkIndex) external {
if (msg.sender != address(_CRYPTOPUNKS_721)) revert Unauthorized();
_CRYPTOPUNKS.transferPunk(address(_CRYPTOPUNKS_721), punkIndex);
}
/**
* @notice withdraws funds from the stash.
* @param tokenAddress The address of the token to withdraw. Zero address for ETH.
* @param amount The amount to withdraw in wei.
* @dev This function allows withdrawal of funds that are not committed to an active bid. It will also
* clean up any stale bids that have been finalized or expired.
*/
function withdraw(address tokenAddress, uint256 amount) external onlyOwner {
(uint256 _lockedAmount, uint256 finalizedIndexes) = _totalLockedAndStaleBids(tokenAddress);
uint256 tokenBalance = _balanceOfToken(tokenAddress);
uint256 availableToWithdraw = tokenBalance - _lockedAmount;
if (amount > availableToWithdraw) revert RequestExceedsAvailableBalance();
_cleanStaleBids(tokenAddress, finalizedIndexes);
_transferTokens(tokenAddress, amount);
}
/**
* @notice Convenience function to withdraw ERC721 tokens from the stash.
* @param tokenAddress The address of the token to withdraw.
* @param tokenIds An array of token IDs to withdraw.
*/
function withdrawERC721(address tokenAddress, uint256[] calldata tokenIds) external onlyOwner {
IERC721 tokenContract = IERC721(tokenAddress);
for (uint256 i = 0; i < tokenIds.length; ++i) {
tokenContract.transferFrom(address(this), owner, tokenIds[i]);
}
}
/**
* @notice Convenience function to withdraw ERC1155 tokens from the stash.
* @param tokenAddress The address of the token to withdraw.
* @param tokenIds An array of token IDs to withdraw.
* @param amounts An array of amounts to withdraw.
*/
function withdrawERC1155(address tokenAddress, uint256[] calldata tokenIds, uint256[] calldata amounts)
external
onlyOwner
{
IERC1155 tokenContract = IERC1155(tokenAddress);
for (uint256 i = 0; i < tokenIds.length; ++i) {
tokenContract.safeTransferFrom(address(this), owner, tokenIds[i], amounts[i], "");
}
}
/**
* @notice Convenience function to withdraw CryptoPunks from the stash.
* @param tokenIds An array of punk IDs to withdraw.
*/
function withdrawPunks(uint256[] calldata tokenIds) external onlyOwner {
for (uint256 i = 0; i < tokenIds.length; ++i) {
_CRYPTOPUNKS.transferPunk(owner, tokenIds[i]);
}
}
// --------------------- VIEW ---------------------
/**
* @notice Fetches a bid corresponding to an auction.
* @param auction The address of the auction to fetch a bid for.
* @return bid The bid corresponding to the auction.
*/
function getOrder(address auction) external view returns (Order memory) {
(address paymentToken,) = IAuction(auction).bidConfig();
Order[] storage _orders = paymentTokenToOrders[paymentToken];
for (uint256 i = 0; i < _orders.length; ++i) {
Order storage bid = _orders[i];
if (bid.auction == auction) {
if (!IAuction(bid.auction).finalized()) {
return bid;
} else {
// If the auction is finalized, the bid is invalid.
revert OrderNotFound();
}
}
}
revert OrderNotFound();
}
/**
* @notice Fetches the total amount of locked funds for a given token.
* @param tokenAddress The address of the token to fetch the locked amount for. Zero address for ETH.
*/
function totalLocked(address tokenAddress) external view returns (uint256 lockedAmount) {
(lockedAmount,) = _totalLockedAndStaleBids(tokenAddress);
}
/**
* @notice Fetches the total amount of available funds for a given token.
* @param tokenAddress The address of the token to fetch the available amount for. Zero address for ETH.
*/
function availableLiquidity(address tokenAddress) public view returns (uint256 availableAmount) {
uint256 tokenBalance = _balanceOfToken(tokenAddress);
(uint256 lockedAmount,) = _totalLockedAndStaleBids(tokenAddress);
availableAmount = tokenBalance - lockedAmount;
}
/**
* @notice convenience function that returns the total amount of useable ETH and WETH.
* @return availableAmount The total amount of useable ETH and WETH.
*/
function availableLiquidityWETHAndETH() public view returns (uint256 availableAmount) {
uint256 wethHeldByOwner = _WETH.balanceOf(owner);
uint256 wethApprovedByOwner = _WETH.allowance(owner, address(this));
uint256 availableWETH = wethHeldByOwner > wethApprovedByOwner ? wethApprovedByOwner : wethHeldByOwner;
availableAmount = availableWETH + availableLiquidity(address(_WETH)) + availableLiquidity(address(0));
}
/**
* @notice Returns the current version of this particular Stash.
* @return _VERSION The current version of this Stash.
*/
function version() external pure returns (uint256) {
return _VERSION;
}
// --------------------- ERC165 ---------------------
function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(address, address, uint256[] memory, uint256[] memory, bytes memory)
public
virtual
returns (bytes4)
{
return this.onERC1155BatchReceived.selector;
}
function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) {
return this.onERC721Received.selector;
}
// --------------------- INTERNAL ---------------------
function _transferTokens(address tokenAddress, uint256 amount) internal {
if (tokenAddress == address(0)) {
(bool success,) = payable(msg.sender).call{value: amount}("");
if (!success) revert FailedToWithdraw();
} else {
SafeTransferLib.safeTransfer(tokenAddress, msg.sender, amount);
}
}
function _replaceOrIncrementExistingOrders(
Order storage existingOrder,
uint16 updatedNumberOfUnits,
uint80 updatedPricePerUnit,
uint256 _availableLiquidity,
OrderType _type
) internal {
if (_type == OrderType.UNREPLACEABLE) revert InvalidOrderAlteration();
uint16 newTotalNumberOfUnits;
if (_type == OrderType.SUBSEQUENT_BIDS_REPLACE_EXISTING_PRICE_INCREASE_REQUIRED) {
if (existingOrder.pricePerUnit >= updatedPricePerUnit) {
revert InvalidOrderAlteration();
} else {
// `numberOfUnits` is allowed to decrease as long as `pricePerUnit` increases.
newTotalNumberOfUnits = updatedNumberOfUnits;
}
} else if (_type == OrderType.SUBSEQUENT_BIDS_OVERWRITE_PRICE_AND_ADD_UNITS) {
newTotalNumberOfUnits = existingOrder.numberOfUnits + updatedNumberOfUnits;
} else {
revert UnknownOrderType();
}
if (
_bidDeltaExceedsLiquidity(
uint256(existingOrder.numberOfUnits) * existingOrder.pricePerUnit,
uint256(newTotalNumberOfUnits) * updatedPricePerUnit,
_availableLiquidity
)
) revert RequestExceedsAvailableBalance();
existingOrder.numberOfUnits = newTotalNumberOfUnits;
existingOrder.pricePerUnit = updatedPricePerUnit;
}
// internal helpers
function _swapWETH(uint256 wethAmount) internal {
uint256 availableBalance = availableLiquidity(address(_WETH));
// if existing balance is high enough just withdraw it and return early.
if (availableBalance >= wethAmount) {
_WETH.withdraw(wethAmount);
return;
}
uint256 amountToTransfer = wethAmount;
// if existing balance is not high enough but greater than 0, decrement the amount needed by the existing balance.
if (availableBalance > 0 && availableBalance < wethAmount) {
unchecked {
amountToTransfer -= availableBalance;
}
}
_WETH.transferFrom(owner, address(this), amountToTransfer);
_WETH.withdraw(wethAmount);
}
// --------------------- INTERNAL VIEW ---------------------
function _isValidSignature(PunkBid calldata bid, bytes memory signature) internal view returns (bool) {
Order calldata order = bid.order;
bytes32 hashStruct = keccak256(
abi.encode(
_PUNK_BID_TYPEHASH,
keccak256(abi.encode(_ORDER_TYPEHASH, order.numberOfUnits, order.pricePerUnit, order.auction)),
bid.accountNonce,
bid.bidNonce,
bid.expiration,
bid.root
)
);
bytes32 _domainHash = keccak256(
abi.encode(
keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"), block.chainid, address(this)
)
);
bytes32 hash = keccak256(abi.encodePacked("\x19\x01", _domainHash, hashStruct));
// This lib does not include a malleability check, however, the bidNonce will prevent signature reuse.
return SignatureCheckerLib.isValidSignatureNow(owner, hash, signature);
}
function _totalAmountBid(Order storage _order) internal view returns (uint256) {
return uint256(_order.numberOfUnits) * _order.pricePerUnit;
}
function _balanceOfToken(address tokenAddress) internal view returns (uint256) {
if (tokenAddress == address(0)) {
return address(this).balance;
} else {
return IERC20(tokenAddress).balanceOf(address(this));
}
}
function _totalLockedAndStaleBids(address tokenAddress)
internal
view
returns (uint256 _lockedAmount, uint256 finalizedIndexes)
{
Order[] storage _orders = paymentTokenToOrders[tokenAddress];
for (uint256 i = 0; i < _orders.length; ++i) {
Order storage order = _orders[i];
IAuction auction = IAuction(order.auction);
if (auction.finalized()) {
finalizedIndexes = finalizedIndexes | (1 << i);
} else {
(address paymentToken,) = auction.bidConfig();
if (tokenAddress == paymentToken) {
unchecked {
_lockedAmount += _totalAmountBid(order);
}
}
}
}
}
function _cleanStaleBids(address tokenAddress, uint256 finalizedIndexes) internal {
Order[] storage _orders = paymentTokenToOrders[tokenAddress];
// If there are no bids to process, exit early.
if (_orders.length == 0) return;
// Otherwise, use a while loop to iterate and clean up stale bids.
uint256 i = _orders.length;
while (i > 0) {
unchecked {
--i;
}
if ((finalizedIndexes & (1 << i)) != 0) {
_removeBid(tokenAddress, i);
// If the last bid is removed, break out of the loop.
if (_orders.length == 0) break;
}
}
}
function _removeBid(address _key, uint256 _bidIndex) internal {
Order[] storage _orders = paymentTokenToOrders[_key];
Order memory orderToRemove = _orders[_bidIndex];
if (_bidIndex != _orders.length - 1) {
_orders[_bidIndex] = _orders[_orders.length - 1];
}
_orders.pop();
emit OrderRemoved(orderToRemove);
}
function _bidDeltaExceedsLiquidity(uint256 existingTotal, uint256 newTotal, uint256 _availableLiquidity)
internal
pure
returns (bool)
{
if (newTotal > existingTotal && newTotal - existingTotal > _availableLiquidity) {
return true;
}
return false;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "forge-std/interfaces/IERC165.sol";
import {OrderType} from "../helpers/Enum.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IAuction is IERC165 {
error AuctionNotOpen();
error BidTooLow();
error CannotFinalizeOpenAuction();
function bidConfig() external view returns (address, OrderType);
function open() external view returns (bool);
function finalized() external view returns (bool);
function withdraw() external;
function currentPrice() external view returns (uint80);
}// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.23;
interface IWETH {
function transfer(address dst, uint256 wad) external;
function transferFrom(address src, address dst, uint256 wad) external;
function deposit() external payable;
function withdraw(uint256 wad) external;
function balanceOf(address user) external view returns (uint256);
function approve(address guy, uint256 wad) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
enum OrderType
{
// 0: Can replace previous bid. Alters bid price and adds `numberOfUnits`
SUBSEQUENT_BIDS_OVERWRITE_PRICE_AND_ADD_UNITS,
// 1: Can replace previous bid if new bid has higher `pricePerUnit`
SUBSEQUENT_BIDS_REPLACE_EXISTING_PRICE_INCREASE_REQUIRED,
// 2: Cannot replace previous bid under any circumstance
UNREPLACEABLE
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {OrderType} from "./Enum.sol";
struct Order {
uint16 numberOfUnits;
uint80 pricePerUnit;
address auction;
}
struct PunkBid {
Order order;
uint256 accountNonce;
uint256 bidNonce;
uint256 expiration;
bytes32 root;
}// 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 Signature verification helper that supports both ECDSA signatures from EOAs
/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol)
///
/// @dev Note:
/// - The signature checking functions use the ecrecover precompile (0x1).
/// - The `bytes memory signature` variants use the identity precompile (0x4)
/// to copy memory internally.
/// - Unlike ECDSA signatures, contract signatures are revocable.
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both
/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures.
/// See: https://eips.ethereum.org/EIPS/eip-2098
/// This is for calldata efficiency on smart accounts prevalent on L2s.
///
/// WARNING! Do NOT use signatures as unique identifiers:
/// - Use a nonce in the digest to prevent replay attacks on the same contract.
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts.
/// EIP-712 also enables readable signing of typed data for better user safety.
/// This implementation does NOT check if a signature is non-malleable.
library SignatureCheckerLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIGNATURE CHECKING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x40, mload(add(signature, 0x20))) // `r`.
if eq(mload(signature), 64) {
let vs := mload(add(signature, 0x40))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(mload(signature), 65) {
mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
mstore(0x60, mload(add(signature, 0x40))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
if eq(signature.length, 64) {
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(signature.length, 65) {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), mload(0x60)) // `s`.
mstore8(add(m, 0xa4), mload(0x20)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, and(v, 0xff)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, s) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1271 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNowCalldata(
address signer,
bytes32 hash,
bytes calldata signature
) internal view returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`.
mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an Ethereum Signed Message, created from a `hash`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, hash) // Store into scratch space for keccak256.
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
}
}
/// @dev Returns an Ethereum Signed Message, created from `s`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
/// Note: Supports lengths of `s` up to 999999 bytes.
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let sLength := mload(s)
let o := 0x20
mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.
mstore(0x00, 0x00)
// Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.
for { let temp := sLength } 1 {} {
o := sub(o, 1)
mstore8(o, add(48, mod(temp, 10)))
temp := div(temp, 10)
if iszero(temp) { break }
}
let n := sub(0x3a, o) // Header length: `26 + 32 - o`.
// Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
result := keccak256(add(s, sub(0x20, n)), add(n, sLength))
mstore(s, sLength) // Restore the length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMPTY CALLDATA HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an empty calldata bytes.
function emptySignature() internal pure returns (bytes calldata signature) {
/// @solidity memory-safe-assembly
assembly {
signature.length := 0
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
/// responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for
/// the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
if iszero(
and(
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul(
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;
import "./IERC165.sol";
/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the ERC-165 identifier for this interface is 0x80ac58cd.
interface IERC721 is IERC165 {
/// @dev This emits when ownership of any NFT changes by any mechanism.
/// This event emits when NFTs are created (`from` == 0) and destroyed
/// (`to` == 0). Exception: during contract creation, any number of NFTs
/// may be created and assigned without emitting Transfer. At the time of
/// any transfer, the approved address for that NFT (if any) is reset to none.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
/// @dev This emits when the approved address for an NFT is changed or
/// reaffirmed. The zero address indicates there is no approved address.
/// When a Transfer event emits, this also indicates that the approved
/// address for that NFT (if any) is reset to none.
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
/// @dev This emits when an operator is enabled or disabled for an owner.
/// The operator can manage all NFTs of the owner.
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
/// @notice Count all NFTs assigned to an owner
/// @dev NFTs assigned to the zero address are considered invalid, and this
/// function throws for queries about the zero address.
/// @param _owner An address for whom to query the balance
/// @return The number of NFTs owned by `_owner`, possibly zero
function balanceOf(address _owner) external view returns (uint256);
/// @notice Find the owner of an NFT
/// @dev NFTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param _tokenId The identifier for an NFT
/// @return The address of the owner of the NFT
function ownerOf(uint256 _tokenId) external view returns (address);
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param data Additional data with no specified format, sent in call to `_to`
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external payable;
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to "".
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
/// THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
/// @notice Change or reaffirm the approved address for an NFT
/// @dev The zero address indicates there is no approved address.
/// Throws unless `msg.sender` is the current NFT owner, or an authorized
/// operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve
function approve(address _approved, uint256 _tokenId) external payable;
/// @notice Enable or disable approval for a third party ("operator") to manage
/// all of `msg.sender`'s assets
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval
function setApprovalForAll(address _operator, bool _approved) external;
/// @notice Get the approved address for a single NFT
/// @dev Throws if `_tokenId` is not a valid NFT.
/// @param _tokenId The NFT to find the approved address for
/// @return The approved address for this NFT, or the zero address if there is none
function getApproved(uint256 _tokenId) external view returns (address);
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return True if `_operator` is an approved operator for `_owner`, false otherwise
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.
interface IERC721TokenReceiver {
/// @notice Handle the receipt of an NFT
/// @dev The ERC721 smart contract calls this function on the recipient
/// after a `transfer`. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called `safeTransferFrom` function
/// @param _from The address which previously owned the token
/// @param _tokenId The NFT identifier which is being transferred
/// @param _data Additional data with no specified format
/// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
/// unless throwing
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data)
external
returns (bytes4);
}
/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the ERC-165 identifier for this interface is 0x5b5e139f.
interface IERC721Metadata is IERC721 {
/// @notice A descriptive name for a collection of NFTs in this contract
function name() external view returns (string memory _name);
/// @notice An abbreviated name for NFTs in this contract
function symbol() external view returns (string memory _symbol);
/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
/// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
/// 3986. The URI may point to a JSON file that conforms to the "ERC721
/// Metadata JSON Schema".
function tokenURI(uint256 _tokenId) external view returns (string memory);
}
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the ERC-165 identifier for this interface is 0x780e9d63.
interface IERC721Enumerable is IERC721 {
/// @notice Count NFTs tracked by this contract
/// @return A count of valid NFTs tracked by this contract, where each one of
/// them has an assigned and queryable owner not equal to the zero address
function totalSupply() external view returns (uint256);
/// @notice Enumerate valid NFTs
/// @dev Throws if `_index` >= `totalSupply()`.
/// @param _index A counter less than `totalSupply()`
/// @return The token identifier for the `_index`th NFT,
/// (sort order not specified)
function tokenByIndex(uint256 _index) external view returns (uint256);
/// @notice Enumerate NFTs assigned to an owner
/// @dev Throws if `_index` >= `balanceOf(_owner)` or if
/// `_owner` is the zero address, representing invalid NFTs.
/// @param _owner An address where we are interested in NFTs owned by them
/// @param _index A counter less than `balanceOf(_owner)`
/// @return The token identifier for the `_index`th NFT assigned to `_owner`,
/// (sort order not specified)
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;
import "./IERC165.sol";
/// @title ERC-1155 Multi Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-1155
/// Note: The ERC-165 identifier for this interface is 0xd9b67a26.
interface IERC1155 is IERC165 {
/// @dev
/// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard).
/// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).
/// - The `_from` argument MUST be the address of the holder whose balance is decreased.
/// - The `_to` argument MUST be the address of the recipient whose balance is increased.
/// - The `_id` argument MUST be the token type being transferred.
/// - The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by.
/// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).
/// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).
event TransferSingle(
address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value
);
/// @dev
/// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard).
/// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).
/// - The `_from` argument MUST be the address of the holder whose balance is decreased.
/// - The `_to` argument MUST be the address of the recipient whose balance is increased.
/// - The `_ids` argument MUST be the list of tokens being transferred.
/// - The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by.
/// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).
/// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).
event TransferBatch(
address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values
);
/// @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled).
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
/// @dev MUST emit when the URI is updated for a token ID. URIs are defined in RFC 3986.
/// The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema".
event URI(string _value, uint256 indexed _id);
/// @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).
/// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard).
/// - MUST revert if `_to` is the zero address.
/// - MUST revert if balance of holder for token `_id` is lower than the `_value` sent.
/// - MUST revert on any other error.
/// - MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard).
/// - After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard).
/// @param _from Source address
/// @param _to Target address
/// @param _id ID of the token type
/// @param _value Transfer amount
/// @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
/// @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call).
/// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard).
/// - MUST revert if `_to` is the zero address.
/// - MUST revert if length of `_ids` is not the same as length of `_values`.
/// - MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient.
/// - MUST revert on any other error.
/// - MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard).
/// - Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).
/// - After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard).
/// @param _from Source address
/// @param _to Target address
/// @param _ids IDs of each token type (order and length must match _values array)
/// @param _values Transfer amounts per token type (order and length must match _ids array)
/// @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to`
function safeBatchTransferFrom(
address _from,
address _to,
uint256[] calldata _ids,
uint256[] calldata _values,
bytes calldata _data
) external;
/// @notice Get the balance of an account's tokens.
/// @param _owner The address of the token holder
/// @param _id ID of the token
/// @return The _owner's balance of the token type requested
function balanceOf(address _owner, uint256 _id) external view returns (uint256);
/// @notice Get the balance of multiple account/token pairs
/// @param _owners The addresses of the token holders
/// @param _ids ID of the tokens
/// @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair)
function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids)
external
view
returns (uint256[] memory);
/// @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens.
/// @dev MUST emit the ApprovalForAll event on success.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval
function setApprovalForAll(address _operator, bool _approved) external;
/// @notice Queries the approval status of an operator for a given owner.
/// @param _owner The owner of the tokens
/// @param _operator Address of authorized operator
/// @return True if the operator is approved, false if not
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;
/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
/// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
event Transfer(address indexed from, address indexed to, uint256 value);
/// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
/// is the new allowance.
event Approval(address indexed owner, address indexed spender, uint256 value);
/// @notice Returns the amount of tokens in existence.
function totalSupply() external view returns (uint256);
/// @notice Returns the amount of tokens owned by `account`.
function balanceOf(address account) external view returns (uint256);
/// @notice Moves `amount` tokens from the caller's account to `to`.
function transfer(address to, uint256 amount) external returns (bool);
/// @notice Returns the remaining number of tokens that `spender` is allowed
/// to spend on behalf of `owner`
function allowance(address owner, address spender) external view returns (uint256);
/// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
/// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
function approve(address spender, uint256 amount) external returns (bool);
/// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
/// `amount` is then deducted from the caller's allowance.
function transferFrom(address from, address to, uint256 amount) external returns (bool);
/// @notice Returns the name of the token.
function name() external view returns (string memory);
/// @notice Returns the symbol of the token.
function symbol() external view returns (string memory);
/// @notice Returns the decimals places of the token.
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
interface ILegacyWrappedPunks {
function transferFrom(address from, address to, uint256 tokenId) external;
function proxyInfo(address) external view returns (address);
function registerProxy() external;
function burn(uint256 punkId) external;
function mint(uint256 punkId) external;
function approve(address to, uint256 punkId) external;
function ownerOf(uint256 punkId) external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
interface ICryptoPunks721 {
function wrapPunk(uint256 punkIndex) external;
function unwrapPunk(uint256 punkIndex) external;
function wrapPunkBatch(uint256[] calldata punkIndexes) external;
function unwrapPunkBatch(uint256[] calldata punkIndexes) external;
function migrateLegacyWrappedPunks(uint256[] calldata punkIndexes) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 punkId) external;
function ownerOf(uint256 punkId) external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
interface ICryptoPunks {
function punksOfferedForSale(uint256)
external
view
returns (bool isForSale, uint256 punkIndex, address seller, uint256 minValue, address onlySellTo);
function buyPunk(uint256) external payable;
function transferPunk(address, uint256) external;
function balanceOf(address) external view returns (uint256);
function punkIndexToAddress(uint256) external view returns (address);
function pendingWithdrawals(address) external view returns (uint256);
function offerPunkForSaleToAddress(uint256, uint256, address) external;
function getPunk(uint256 punkId) external;
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
interface IStashFactory {
function isStash(address stash) external view returns (bool);
function deployStash(address owner) external returns (address);
function isAuction(address auction) external view returns (bool);
function stashAddressFor(address owner) external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IPunkTransferHelper {
function transfer721PunkToStash(bytes32 _packedData) external;
function transferLegacyWrappedPunkToStash(bytes32 _packedData) external;
}// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;
interface IERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}{
"remappings": [
"forge-std/=lib/forge-std/src/",
"@openzeppelin/=lib/openzeppelin-contracts/",
"ERC721A/=lib/ERC721A/contracts/",
"solady/=lib/solady/src/",
"soladytest/=lib/solady/test/",
"sol-json/=lib/sol-json/src/",
"ds-test/=lib/forge-std/lib/ds-test/src/",
"solmate/=lib/sol-json/lib/solady/lib/solmate/src/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"abi"
]
}
},
"evmVersion": "paris",
"libraries": {}
}Contract ABI
API[{"inputs":[{"internalType":"address","name":"stashFactory","type":"address"},{"internalType":"address","name":"weth","type":"address"},{"internalType":"address","name":"punks","type":"address"},{"internalType":"address","name":"legacyWrappedPunks","type":"address"},{"internalType":"address","name":"cryptoPunksWrapped","type":"address"},{"internalType":"address","name":"punkTransferHelper","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"BidCanceled","type":"error"},{"inputs":[],"name":"BidExpired","type":"error"},{"inputs":[],"name":"CallerNotAuction","type":"error"},{"inputs":[],"name":"CannotTransferMoreThanBidAmount","type":"error"},{"inputs":[],"name":"FailedToBuyPunk","type":"error"},{"inputs":[],"name":"FailedToWithdraw","type":"error"},{"inputs":[],"name":"InvalidBid","type":"error"},{"inputs":[],"name":"InvalidOrderAlteration","type":"error"},{"inputs":[],"name":"InvalidProof","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"NoBidForAuction","type":"error"},{"inputs":[],"name":"OrderNotFound","type":"error"},{"inputs":[],"name":"RequestExceedsAvailableBalance","type":"error"},{"inputs":[],"name":"TooManyOrders","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnknownOrderType","type":"error"},{"anonymous":false,"inputs":[],"name":"AllPunkBidsCanceled","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"indexed":false,"internalType":"struct Order","name":"order","type":"tuple"}],"name":"OrderPlaced","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"indexed":false,"internalType":"struct Order","name":"order","type":"tuple"}],"name":"OrderRemoved","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"indexed":false,"internalType":"struct Order","name":"originalOrder","type":"tuple"},{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"indexed":false,"internalType":"struct Order","name":"updatedOrder","type":"tuple"}],"name":"OrderUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"price","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"punkIndex","type":"uint256"}],"name":"PunkBidAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"bidNonce","type":"uint256"}],"name":"PunkBidCanceled","type":"event"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"availableLiquidity","outputs":[{"internalType":"uint256","name":"availableAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"availableLiquidityWETHAndETH","outputs":[{"internalType":"uint256","name":"availableAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelAllPunkBids","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"bidNonce","type":"uint256"}],"name":"cancelPunkBid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"auction","type":"address"}],"name":"getOrder","outputs":[{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"internalType":"struct Order","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","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":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"paymentToken","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"paymentTokenToOrders","outputs":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"uint16","name":"numberOfUnits","type":"uint16"}],"name":"placeOrder","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint80","name":"costPerUnit","type":"uint80"},{"internalType":"uint16","name":"numberOfUnits","type":"uint16"}],"name":"processOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"internalType":"struct Order","name":"order","type":"tuple"},{"internalType":"uint256","name":"accountNonce","type":"uint256"},{"internalType":"uint256","name":"bidNonce","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"bytes32","name":"root","type":"bytes32"}],"internalType":"struct PunkBid","name":"bid","type":"tuple"},{"internalType":"uint256","name":"punkIndex","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"processPunkBid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"punkAccountNonce","outputs":[{"internalType":"uint56","name":"","type":"uint56"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"punkBidNonce","type":"uint256"}],"name":"punkBidNonceUsesRemaining","outputs":[{"internalType":"uint256","name":"usesRemaining","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"totalLocked","outputs":[{"internalType":"uint256","name":"lockedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"punkBidNonce","type":"uint256"}],"name":"usedPunkBidNonces","outputs":[{"internalType":"bool","name":"isUsed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"withdrawERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"withdrawERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"withdrawPunks","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"punkIndex","type":"uint256"}],"name":"wrapPunk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 33 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.