Transaction Hash:
Block:
21183568 at Nov-14-2024 04:32:47 AM +UTC
Transaction Fee:
0.00169983859877892 ETH
$4.27
Gas Used:
62,991 Gas / 26.98542012 Gwei
Emitted Events:
215 |
SealedArtMarket.AuctionCancelled( auctionId=EF22B65F6DD2858CB53356064D7E45B70E69979A362E038505EF65C033679BE1 )
|
216 |
SealedArtMarket.AuctionCreated( owner=[Sender] 0x1277cc1f5d06b7f791ade9f5a326de68e524ff44, nftContract=0x89e57522...85a3de5f3, auctionDuration=259200, auctionType=E600001800000000000000000000000000000000000000000000000000000002, nftId=22, reserve=30000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x1277Cc1F...8e524ff44 | (GIFT: Deployer) |
0.005538443129125341 Eth
Nonce: 993
|
0.003838604530346421 Eth
Nonce: 994
| 0.00169983859877892 | |
0x2cBe14b7...16C137F3C | |||||
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 10.180224380908250037 Eth | 10.180230302062250037 Eth | 0.000005921154 |
Execution Trace
SealedArtMarket.changeAuction( nftContract=0x89e575222d9F1Bb3Fcc4A3d4aB9127E85a3de5f3, auctionType=E600001B00000000000000000000000000000000000000000000000000000002, nftId=22, reserve=60000000000000000, newAuctionDuration=259200, newAuctionType=E600001800000000000000000000000000000000000000000000000000000002, newReserve=30000000000000000, cancelAuctionPacket=[{name:v, type:uint8, order:1, indexed:false, value:28, valueString:28}, {name:r, type:bytes32, order:2, indexed:false, value:B2AE4A6731CE1C4EFA4CFE2E774976C6877881BD49F8771A231CAA42CAFCDEBF, valueString:B2AE4A6731CE1C4EFA4CFE2E774976C6877881BD49F8771A231CAA42CAFCDEBF}, {name:s, type:bytes32, order:3, indexed:false, value:5E09BE17F79E456BE55AEFF70FBFC74152ABC83AB96EDAEDC84967E9F0BAE03D, valueString:5E09BE17F79E456BE55AEFF70FBFC74152ABC83AB96EDAEDC84967E9F0BAE03D}, {name:auctionId, type:bytes32, order:4, indexed:false, value:EF22B65F6DD2858CB53356064D7E45B70E69979A362E038505EF65C033679BE1, valueString:EF22B65F6DD2858CB53356064D7E45B70E69979A362E038505EF65C033679BE1}, {name:deadline, type:uint256, order:5, indexed:false, value:1731559320, valueString:1731559320}] )

-
Null: 0x000...001.96fbdc2c( )
changeAuction[SealedArtMarket (ln:543)]
_cancelAuction[SealedArtMarket (ln:553)]
_verifyCancelAuction[SealedArtMarket (ln:526)]
calculateAuctionHash[SealedArtMarket (ln:527)]
AuctionCancelled[SealedArtMarket (ln:531)]
_createAuction[SealedArtMarket (ln:554)]
calculateAuctionHash[SealedArtMarket (ln:503)]
AuctionCreated[SealedArtMarket (ln:506)]
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) pragma solidity ^0.8.0; import "../utils/Context.sol"; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() { _transferOwnership(_msgSender()); } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { _checkOwner(); _; } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { require(owner() == _msgSender(), "Ownable: caller is not the owner"); } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby disabling any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) pragma solidity ^0.8.0; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/BitMaps.sol) pragma solidity ^0.8.0; /** * @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential. * Largely inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor]. */ library BitMaps { struct BitMap { mapping(uint256 => uint256) _data; } /** * @dev Returns whether the bit at `index` is set. */ function get(BitMap storage bitmap, uint256 index) internal view returns (bool) { uint256 bucket = index >> 8; uint256 mask = 1 << (index & 0xff); return bitmap._data[bucket] & mask != 0; } /** * @dev Sets the bit at `index` to the boolean `value`. */ function setTo(BitMap storage bitmap, uint256 index, bool value) internal { if (value) { set(bitmap, index); } else { unset(bitmap, index); } } /** * @dev Sets the bit at `index`. */ function set(BitMap storage bitmap, uint256 index) internal { uint256 bucket = index >> 8; uint256 mask = 1 << (index & 0xff); bitmap._data[bucket] |= mask; } /** * @dev Unsets the bit at `index`. */ function unset(BitMap storage bitmap, uint256 index) internal { uint256 bucket = index >> 8; uint256 mask = 1 << (index & 0xff); bitmap._data[bucket] &= ~mask; } } // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.4; abstract contract EIP712 { /// ----------------------------------------------------------------------- /// Structs /// ----------------------------------------------------------------------- /// @param v Part of the ECDSA signature /// @param r Part of the ECDSA signature /// @param s Part of the ECDSA signature struct WithdrawalPacket { uint8 v; bytes32 r; bytes32 s; uint256 deadline; uint256 amount; uint256 nonce; address account; } /// ----------------------------------------------------------------------- /// Immutable parameters /// ----------------------------------------------------------------------- /// @notice The chain ID used by EIP-712 uint256 internal immutable INITIAL_CHAIN_ID; /// @notice The domain separator used by EIP-712 bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- constructor() { INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(); } /// ----------------------------------------------------------------------- /// Packet verification /// ----------------------------------------------------------------------- function _verifySig(bytes memory data, uint8 v, bytes32 r, bytes32 s) internal virtual returns (address) { // verify signature address recoveredAddress = ecrecover(keccak256(abi.encodePacked("\\x19\\x01", DOMAIN_SEPARATOR(), keccak256(data))), v, r, s); return recoveredAddress; } /// @notice Verifies whether a packet is valid and returns the result. /// @dev The deadline, request, and signature are verified. /// @param packet The packet provided by the offchain data provider function _verifyWithdrawal(WithdrawalPacket calldata packet) internal virtual returns (address) { // verify deadline require(block.timestamp < packet.deadline, ">deadline"); // verify signature address recoveredAddress = _verifySig( abi.encode( keccak256("VerifyWithdrawal(uint256 deadline,uint256 amount,uint256 nonce,address account)"), packet.deadline, packet.amount, packet.nonce, packet.account ), packet.v, packet.r, packet.s ); return recoveredAddress; // Invariant: sequencer != address(0), we maintain this every time sequencer is set } struct Bid { uint8 v; bytes32 r; bytes32 s; bytes32 auctionId; uint256 maxAmount; } function _verifyBid(Bid calldata packet) internal virtual returns (address) { address recoveredAddress = _verifySig( abi.encode(keccak256("Bid(bytes32 auctionId,uint256 maxAmount)"), packet.auctionId, packet.maxAmount), packet.v, packet.r, packet.s ); require(recoveredAddress != address(0), "sig"); return recoveredAddress; } struct BidWinner { uint8 v; bytes32 r; bytes32 s; bytes32 auctionId; uint256 amount; address winner; } function _verifyBidWinner(BidWinner calldata packet) internal virtual returns (address) { return _verifySig( abi.encode( keccak256("BidWinner(bytes32 auctionId,uint256 amount,address winner)"), packet.auctionId, packet.amount, packet.winner ), packet.v, packet.r, packet.s ); } struct CancelAuction { uint8 v; bytes32 r; bytes32 s; bytes32 auctionId; uint256 deadline; } function _verifyCancelAuction(CancelAuction calldata packet) internal virtual returns (address) { require(block.timestamp <= packet.deadline, "deadline"); return _verifySig( abi.encode( keccak256("CancelAuction(bytes32 auctionId,uint256 deadline)"), packet.auctionId, packet.deadline ), packet.v, packet.r, packet.s ); } struct Offer { uint8 v; bytes32 r; bytes32 s; address nftContract; uint256 nftId; uint256 amount; uint256 deadline; uint256 counter; uint256 nonce; } function _verifyBuyOffer(Offer calldata packet) internal virtual returns (address) { return _verifySig( abi.encode( keccak256( "BuyOffer(address nftContract,uint256 nftId,uint256 amount,uint256 deadline,uint256 counter,uint256 nonce)" ), packet.nftContract, packet.nftId, packet.amount, packet.deadline, packet.counter, packet.nonce ), packet.v, packet.r, packet.s ); } function _verifySellOffer(Offer calldata packet) internal virtual returns (address) { return _verifySig( abi.encode( keccak256( "SellOffer(address nftContract,uint256 nftId,uint256 amount,uint256 deadline,uint256 counter,uint256 nonce)" ), packet.nftContract, packet.nftId, packet.amount, packet.deadline, packet.counter, packet.nonce ), packet.v, packet.r, packet.s ); } struct OfferAttestation { uint8 v; bytes32 r; bytes32 s; bytes32 auctionId; uint256 amount; address buyer; address seller; uint256 deadline; } function _verifyOfferAttestation(OfferAttestation calldata packet) internal virtual returns (address) { return _verifySig( abi.encode( keccak256( "OfferAttestation(bytes32 auctionId,uint256 amount,address buyer,address seller,uint256 deadline)" ), packet.auctionId, packet.amount, packet.buyer, packet.seller, packet.deadline ), packet.v, packet.r, packet.s ); } /// ----------------------------------------------------------------------- /// EIP-712 compliance /// ----------------------------------------------------------------------- /// @notice The domain separator used by EIP-712 function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator(); } /// @notice Computes the domain separator used by EIP-712 function _computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256("SealedArtMarket"), keccak256("1"), block.chainid, address(this) ) ); } } pragma solidity ^0.8.7; import "./EIP712.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "./SealedFundingFactory.sol"; import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; interface IERC721 { function ownerOf(uint256 _tokenId) external view returns (address); function transferFrom(address _from, address _to, uint256 _tokenId) external; } interface RoyaltyEngine { function getRoyalty(address tokenAddress, uint256 tokenId, uint256 value) external returns (address payable[] memory recipients, uint256[] memory amounts); } contract SealedArtMarket is EIP712, Ownable { using BitMaps for BitMaps.BitMap; event Transfer(address indexed from, address indexed to, uint256 value); mapping(address => uint256) private _balances; string public constant name = "Sealed ETH"; string public constant symbol = "SETH"; uint8 public constant decimals = 18; function totalSupply() public view returns (uint256) { return address(this).balance; } // sequencer and settleSequencer are separated as an extra security measure against key leakage through side attacks // If a side channel attack is possible that requires multiple signatures to be made, settleSequencer will be more protected // against it because each signature will require an onchain action, which will make the attack extremely expensive // It also allows us to use different security systems for the two keys, since settleSequencer is much more sensitive address public sequencer; // Invariant: always different than address(0) address public settleSequencer; // Invariant: always different than address(0) address payable public treasury; SealedFundingFactory public immutable sealedFundingFactory; uint256 internal constant MAX_PROTOCOL_FEE = 0.1e18; // 10% uint256 public feeMultiplier; uint256 public forcedWithdrawDelay = 2 days; RoyaltyEngine public constant royaltyEngine = RoyaltyEngine(0xBc40d21999b4BF120d330Ee3a2DE415287f626C9); enum AuctionState { NONE, // 0 -> doesnt exist, default state CREATED, CLOSED } mapping(bytes32 => AuctionState) public auctionState; mapping(address => mapping(uint256 => mapping(uint256 => uint256))) public pendingWithdrawals; mapping(bytes32 => uint256) public pendingAuctionCancels; mapping(address => bool) public guardians; BitMaps.BitMap private usedNonces; mapping(address => BitMaps.BitMap) private usedOrderNonces; mapping(address => uint256) public accountCounter; function balanceOf(address account) public view returns (uint256) { return _balances[account]; } constructor(address _sequencer, address payable _treasury, address _settleSequencer) { require(_sequencer != address(0) && _settleSequencer != address(0), "0x0 sequencer not allowed"); sequencer = _sequencer; treasury = _treasury; settleSequencer = _settleSequencer; sealedFundingFactory = new SealedFundingFactory(address(this)); } event SequencerChanged(address newSequencer, address newSettleSequencer); function changeSequencer(address newSequencer, address newSettleSequencer) external onlyOwner { require(newSequencer != address(0) && newSettleSequencer != address(0), "0x0 sequencer not allowed"); sequencer = newSequencer; settleSequencer = newSettleSequencer; emit SequencerChanged(newSequencer, newSettleSequencer); } event ForcedWithdrawDelayChanged(uint256 newDelay); function changeForcedWithdrawDelay(uint256 newDelay) external onlyOwner { require(newDelay < 10 days, "<10 days"); forcedWithdrawDelay = newDelay; emit ForcedWithdrawDelayChanged(newDelay); } event TreasuryChanged(address newTreasury); function changeTreasury(address payable newTreasury) external onlyOwner { treasury = newTreasury; emit TreasuryChanged(newTreasury); } event GuardianSet(address guardian, bool value); function setGuardian(address guardian, bool value) external onlyOwner { guardians[guardian] = value; emit GuardianSet(guardian, value); } event SequencerDisabled(address guardian); function emergencyDisableSequencer() external { require(guardians[msg.sender] == true, "not guardian"); // Maintain the invariant that sequencers are not 0x0 sequencer = address(0x000000000000000000000000000000000000dEaD); settleSequencer = address(0x000000000000000000000000000000000000dEaD); emit SequencerDisabled(msg.sender); } event FeeChanged(uint256 newFeeMultiplier); function changeFee(uint256 newFeeMultiplier) external onlyOwner { require(newFeeMultiplier <= MAX_PROTOCOL_FEE, "fee too high"); feeMultiplier = newFeeMultiplier; emit FeeChanged(newFeeMultiplier); } function deposit(address receiver) public payable { _balances[receiver] += msg.value; emit Transfer(address(0), receiver, msg.value); } function _withdraw(uint256 amount) internal { _balances[msg.sender] -= amount; (bool success,) = payable(msg.sender).call{value: amount}(""); require(success); emit Transfer(msg.sender, address(0), amount); } event WithdrawNonceUsed(uint256 nonce); function withdraw(WithdrawalPacket calldata packet) public { require(_verifyWithdrawal(packet) == sequencer, "!sequencer"); require(nonceState(packet.nonce) == false, "replayed"); usedNonces.set(packet.nonce); require(packet.account == msg.sender, "not sender"); _withdraw(packet.amount); emit WithdrawNonceUsed(packet.nonce); } event StartWithdrawal(address owner, uint256 timestamp, uint256 nonce, uint256 amount); function startWithdrawal(uint256 amount, uint256 nonce) external { pendingWithdrawals[msg.sender][block.timestamp][nonce] = amount; emit StartWithdrawal(msg.sender, block.timestamp, nonce, amount); } event CancelWithdrawal(address owner, uint256 timestamp, uint256 nonce); function cancelPendingWithdrawal(uint256 timestamp, uint256 nonce) external { pendingWithdrawals[msg.sender][timestamp][nonce] = 0; emit CancelWithdrawal(msg.sender, timestamp, nonce); } event ExecuteDelayedWithdrawal(address owner, uint256 timestamp, uint256 nonce); function executePendingWithdrawal(uint256 timestamp, uint256 nonce) external { require(timestamp + forcedWithdrawDelay < block.timestamp, "too soon"); uint256 amount = pendingWithdrawals[msg.sender][timestamp][nonce]; pendingWithdrawals[msg.sender][timestamp][nonce] = 0; _withdraw(amount); emit ExecuteDelayedWithdrawal(msg.sender, timestamp, nonce); } function calculateAuctionHash( address owner, address nftContract, bytes32 auctionType, uint256 nftId, uint256 reserve ) public pure returns (bytes32) { return keccak256(abi.encode(owner, nftContract, auctionType, nftId, reserve)); } event AuctionCreated( address owner, address nftContract, uint256 auctionDuration, bytes32 auctionType, uint256 nftId, uint256 reserve ); function _createAuction( address nftContract, uint256 auctionDuration, bytes32 auctionType, uint256 nftId, uint256 reserve ) internal { bytes32 auctionId = calculateAuctionHash(msg.sender, nftContract, auctionType, nftId, reserve); require(auctionState[auctionId] == AuctionState.NONE, "repeated auction id"); // maybe this is not needed? auctionState[auctionId] = AuctionState.CREATED; emit AuctionCreated(msg.sender, nftContract, auctionDuration, auctionType, nftId, reserve); } function createAuction( address nftContract, uint256 auctionDuration, bytes32 auctionType, uint256 nftId, uint256 reserve ) external { IERC721(nftContract).transferFrom(msg.sender, address(this), nftId); _createAuction(nftContract, auctionDuration, auctionType, nftId, reserve); } event AuctionCancelled(bytes32 auctionId); function _cancelAuction( address nftContract, bytes32 auctionType, uint256 nftId, uint256 reserve, CancelAuction calldata cancelAuctionPacket ) internal { require(_verifyCancelAuction(cancelAuctionPacket) == sequencer, "!sequencer"); bytes32 auctionId = calculateAuctionHash(msg.sender, nftContract, auctionType, nftId, reserve); require(auctionState[auctionId] == AuctionState.CREATED, "bad state"); require(cancelAuctionPacket.auctionId == auctionId, "!auctionId"); auctionState[auctionId] = AuctionState.CLOSED; emit AuctionCancelled(auctionId); } function cancelAuction( address nftContract, bytes32 auctionType, uint256 nftId, uint256 reserve, CancelAuction calldata cancelAuctionPacket ) external { _cancelAuction(nftContract, auctionType, nftId, reserve, cancelAuctionPacket); IERC721(nftContract).transferFrom(address(this), msg.sender, nftId); } function changeAuction( address nftContract, bytes32 auctionType, uint256 nftId, uint256 reserve, uint256 newAuctionDuration, bytes32 newAuctionType, uint256 newReserve, CancelAuction calldata cancelAuctionPacket ) external { _cancelAuction(nftContract, auctionType, nftId, reserve, cancelAuctionPacket); _createAuction(nftContract, newAuctionDuration, newAuctionType, nftId, newReserve); } event StartDelayedAuctionCancel(bytes32 auctionId); function startCancelAuction( address nftContract, bytes32 auctionType, uint256 nftId, uint256 reserve ) external { bytes32 auctionId = calculateAuctionHash(msg.sender, nftContract, auctionType, nftId, reserve); require(auctionState[auctionId] == AuctionState.CREATED, "bad auction state"); pendingAuctionCancels[auctionId] = block.timestamp; emit StartDelayedAuctionCancel(auctionId); } event ExecuteDelayedAuctionCancel(bytes32 auctionId); function executeCancelAuction( address nftContract, bytes32 auctionType, uint256 nftId, uint256 reserve ) external { bytes32 auctionId = calculateAuctionHash(msg.sender, nftContract, auctionType, nftId, reserve); uint256 timestamp = pendingAuctionCancels[auctionId]; require(timestamp != 0 && (timestamp + forcedWithdrawDelay) < block.timestamp, "too soon"); require(auctionState[auctionId] == AuctionState.CREATED, "not open"); auctionState[auctionId] = AuctionState.CLOSED; pendingAuctionCancels[auctionId] = 0; emit AuctionCancelled(auctionId); IERC721(nftContract).transferFrom(address(this), msg.sender, nftId); emit ExecuteDelayedAuctionCancel(auctionId); } function _transferETH(address payable receiver, uint256 amount) internal { (bool success,) = receiver.call{value: amount, gas: 300_000}(""); if (success == false) { _balances[receiver] += amount; emit Transfer(address(0), receiver, amount); } } function _distributeSale(address nftContract, uint256 nftId, uint256 amount, address payable seller) internal { uint256 totalRoyalty = 0; try royaltyEngine.getRoyalty{gas: 500_000}(nftContract, nftId, amount) returns (address payable[] memory recipients, uint256[] memory amounts) { uint length = 5; // Use a maximum of 5 items to avoid attacks that blow up gas limit if(recipients.length < length){ length = recipients.length; } if(amounts.length < length){ length = amounts.length; } for (uint256 i; i < length;) { _transferETH(recipients[i], amounts[i]); totalRoyalty += amounts[i]; unchecked { ++i; } } require(totalRoyalty <= (amount / 3), "Royalty too high"); // Protect against royalty hacks } catch {} uint256 feeAmount = (amount * feeMultiplier) / 1e18; _transferETH(treasury, feeAmount); _transferETH(seller, amount - (totalRoyalty + feeAmount)); // totalRoyalty+feeAmount <= amount*0.43 } event AuctionSettled(bytes32 auctionId); function settleAuction( address payable nftOwner, address nftContract, bytes32 auctionType, uint256 nftId, uint256 reserve, Bid calldata bid, BidWinner calldata bidWinner ) public { bytes32 auctionId = calculateAuctionHash(nftOwner, nftContract, auctionType, nftId, reserve); require(auctionState[auctionId] == AuctionState.CREATED, "bad auction state"); auctionState[auctionId] = AuctionState.CLOSED; require(bidWinner.auctionId == auctionId && bid.auctionId == auctionId, "!auctionId"); uint256 amount = bidWinner.amount; require(amount <= bid.maxAmount && amount >= reserve, "!amount"); require(_verifyBid(bid) == bidWinner.winner, "!winner"); require(_verifyBidWinner(bidWinner) == settleSequencer, "!settleSequencer"); _balances[bidWinner.winner] -= amount; emit Transfer(bidWinner.winner, address(0), amount); IERC721(nftContract).transferFrom(address(this), bidWinner.winner, nftId); _distributeSale(nftContract, nftId, amount, nftOwner); emit AuctionSettled(auctionId); } function _revealBids(bytes32[] calldata salts, address owner) internal { for (uint256 i = 0; i < salts.length;) { // We use try/catch here to prevent a griefing attack where someone could deploySealedFunding() one of the // sealed fundings of the buyer right before another user calls this function, thus making it revert // It's still possible for the buyer to perform this attack by frontrunning the call with a withdraw() // but that's trivial to solve by just revealing all the salts of the griefing user try sealedFundingFactory.deploySealedFunding{gas: 100_000}(salts[i], owner) {} // cost of deploySealedFunding() is between 55k and 82k catch {} unchecked { ++i; } } } function settleAuctionWithSealedBids( bytes32[] calldata salts, address payable nftOwner, address nftContract, bytes32 auctionType, uint256 nftId, uint256 reserve, Bid calldata bid, BidWinner calldata bidWinner ) external { _revealBids(salts, bidWinner.winner); settleAuction(nftOwner, nftContract, auctionType, nftId, reserve, bid, bidWinner); } function withdrawWithSealedBids(bytes32[] calldata salts, WithdrawalPacket calldata packet) external { _revealBids(salts, msg.sender); withdraw(packet); } event CounterIncreased(address account, uint256 newCounter); function increaseCounter(uint256 newCounter) external { require(newCounter > accountCounter[msg.sender], "too low"); accountCounter[msg.sender] = newCounter; emit CounterIncreased(msg.sender, newCounter); } event OfferCancelled(address account, uint256 nonce); function cancelOffer(uint256 nonce) external { usedOrderNonces[msg.sender].set(nonce); emit OfferCancelled(msg.sender, nonce); } function _verifyOffer(Offer calldata offer, address creator) private { require(offer.deadline > block.timestamp, "!deadline"); require(orderNonces(creator, offer.nonce) == false, "!orderNonce"); usedOrderNonces[msg.sender].set(offer.nonce); require(offer.counter > accountCounter[creator], "!counter"); } event OrdersMatched(bytes32 auctionId, address buyer, address sender, uint256 buyerNonce, uint256 sellerNonce); function matchOrders( Offer calldata sellerOffer, Offer calldata buyerOffer, OfferAttestation calldata sequencerStamp, address nftContract, bytes32 auctionType, uint256 nftId, uint256 reserve ) external { // First run verifications that can fail due to a delayed tx require(sequencerStamp.deadline > block.timestamp, "!deadline"); if (msg.sender != sequencerStamp.buyer) { _verifyOffer(buyerOffer, sequencerStamp.buyer); require(_verifyBuyOffer(buyerOffer) == sequencerStamp.buyer && sequencerStamp.buyer != address(0), "!buyer"); } if (msg.sender != sequencerStamp.seller) { _verifyOffer(sellerOffer, sequencerStamp.seller); require( _verifySellOffer(sellerOffer) == sequencerStamp.seller && sequencerStamp.seller != address(0), "!seller" ); } // Verify NFT is owned by seller bytes32 auctionId = calculateAuctionHash( sequencerStamp.seller, nftContract, auctionType, nftId, reserve ); require(auctionState[auctionId] == AuctionState.CREATED && sequencerStamp.auctionId == auctionId, "bad auction state"); // Execute sale _balances[sequencerStamp.buyer] -= sequencerStamp.amount; emit Transfer(sequencerStamp.buyer, address(0), sequencerStamp.amount); auctionState[auctionId] = AuctionState.CLOSED; // Run verifications that can't fail due to external factors require(sequencerStamp.amount == sellerOffer.amount && sequencerStamp.amount == buyerOffer.amount, "!amount"); require( nftContract == sellerOffer.nftContract && nftContract == buyerOffer.nftContract, "!nftContract" ); require(nftId == sellerOffer.nftId && nftId == buyerOffer.nftId, "!nftId"); require(_verifyOfferAttestation(sequencerStamp) == sequencer, "!sequencer"); // This needs sequencer approval to avoid someone rugging their bids by buying another NFT // Finish executing sale IERC721(nftContract).transferFrom(address(this), sequencerStamp.buyer, nftId); _distributeSale( nftContract, nftId, sequencerStamp.amount, payable(sequencerStamp.seller) ); emit OrdersMatched(auctionId, sequencerStamp.buyer, msg.sender, buyerOffer.nonce, sellerOffer.nonce); } function nonceState(uint256 nonce) public view returns (bool) { return usedNonces.get(nonce); } function orderNonces(address account, uint256 nonce) public view returns (bool) { return usedOrderNonces[account].get(nonce); } } pragma solidity ^0.8.7; interface IExchange { function deposit(address receiver) external payable; } contract SealedFunding { constructor(address _owner, address _exchange) { IExchange(_exchange).deposit{value: address(this).balance}(_owner); assembly { // Ensures the runtime bytecode is a single opcode: `INVALID`. This reduces contract // deploy costs & ensures that no one can accidentally send ETH to the contract once // deployed. mstore8(0, 0xfe) return(0, 1) } } } pragma solidity ^0.8.7; import "./SealedFunding.sol"; contract SealedFundingFactory { address public immutable exchange; constructor(address _exchange) { exchange = _exchange; } event SealedFundingRevealed(bytes32 salt, address owner); function deploySealedFunding(bytes32 salt, address owner) public { new SealedFunding{salt: salt}(owner, exchange); emit SealedFundingRevealed(salt, owner); } function computeSealedFundingAddress(bytes32 salt, address owner) external view returns (address predictedAddress, bool isDeployed) { predictedAddress = address( uint160( uint256( keccak256( abi.encodePacked( bytes1(0xff), address(this), salt, keccak256(abi.encodePacked(type(SealedFunding).creationCode, abi.encode(owner, exchange))) ) ) ) ) ); isDeployed = predictedAddress.code.length != 0; } }