From ee08b278d22797e1891d06e66127a6bee68f85c6 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:51:34 +0800 Subject: [PATCH 01/24] feat(billboard): revise interface --- src/Billboard/Billboard.sol | 3 - src/Billboard/BillboardRegistry.sol | 257 +++++++++------------ src/Billboard/IBillboard.sol | 91 ++++---- src/Billboard/IBillboardRegistry.sol | 334 +++++++++------------------ 4 files changed, 260 insertions(+), 425 deletions(-) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index f620c57..3656213 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -382,8 +382,5 @@ contract Billboard is IBillboard { // transfer bid price and tax back to the bidder registry.transferAmount(msg.sender, amount); - - // emit BidWithdrawn - registry.emitBidWithdrawn(tokenId_, auctionId_, msg.sender, _bid.price, _bid.tax); } } diff --git a/src/Billboard/BillboardRegistry.sol b/src/Billboard/BillboardRegistry.sol index ce48fb5..7c9c1c5 100644 --- a/src/Billboard/BillboardRegistry.sol +++ b/src/Billboard/BillboardRegistry.sol @@ -9,55 +9,44 @@ import "./IBillboardRegistry.sol"; contract BillboardRegistry is IBillboardRegistry, ERC721 { using Counters for Counters.Counter; + Counters.Counter public lastTokenId; - // access control address public operator; - Counters.Counter public lastTokenId; - + // token to be used for auction IERC20 public immutable token; - uint256 public taxRate; - uint64 public leaseTerm; // tokenId => Board mapping(uint256 => Board) public boards; - // tokenId => auctionId => Auction - mapping(uint256 => mapping(uint256 => Auction)) public boardAuctions; + // tokenId => address => whitelisted + mapping(uint256 => mapping(address => bool)) public boardWhitelists; - // tokenId => nextAuctionId (start from 1 if exists) - mapping(uint256 => uint256) public nextBoardAuctionId; + // tokenId => epoch => bidder + mapping(uint256 => mapping(uint256 => address)) public auctionHiggestBidder; - // tokenId => auctionId => bidders + // tokenId => epoch => bidders mapping(uint256 => mapping(uint256 => address[])) public auctionBidders; - // tokenId => auctionId => bidder => Bid + // tokenId => epoch => bidder => Bid mapping(uint256 => mapping(uint256 => mapping(address => Bid))) public auctionBids; // board creator => TaxTreasury mapping(address => TaxTreasury) public taxTreasury; - constructor( - address token_, - address operator_, - uint256 taxRate_, - uint64 leaseTerm_, - string memory name_, - string memory symbol_ - ) ERC721(name_, symbol_) { + ////////////////////////////// + /// Constructor + ////////////////////////////// + constructor(address token_, address operator_, string memory name_, string memory symbol_) ERC721(name_, symbol_) { require(operator_ != address(0), "Zero address"); require(token_ != address(0), "Zero address"); - - token = IERC20(token_); operator = operator_; - taxRate = taxRate_; - leaseTerm = leaseTerm_; + token = IERC20(token_); } ////////////////////////////// /// Modifier ////////////////////////////// - modifier isFromOperator() { require(msg.sender == operator, "Operator"); _; @@ -78,9 +67,12 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { ////////////////////////////// /// Board ////////////////////////////// - /// @inheritdoc IBillboardRegistry - function mintBoard(address to_) external isFromOperator returns (uint256 tokenId) { + function newBoard( + address to_, + uint256 taxRate_, + uint256 epochInterval_ + ) external isFromOperator returns (uint256 tokenId) { lastTokenId.increment(); tokenId = lastTokenId.current(); @@ -90,170 +82,147 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { creator: to_, name: "", description: "", + imageURI: "", location: "", - contentURI: "", - redirectURI: "" + taxRate: taxRate_, + epochInterval: epochInterval_, + createdAt: block.number }); - } - - /// @inheritdoc IBillboardRegistry - function safeTransferByOperator(address from_, address to_, uint256 tokenId_) external isFromOperator { - _safeTransfer(from_, to_, tokenId_, ""); - } - /// @inheritdoc IBillboardRegistry - function getBoard(uint256 tokenId_) external view returns (Board memory board) { - board = boards[tokenId_]; + emit BoardCreated(tokenId, to_, taxRate_, epochInterval_); } /// @inheritdoc IBillboardRegistry - function setBoardName(uint256 tokenId_, string calldata name_) external isFromOperator { + function setBoard( + uint256 tokenId_, + string calldata name_, + string calldata description_, + string calldata imageURI_, + string calldata location_ + ) external isFromOperator { boards[tokenId_].name = name_; - emit BoardNameUpdated(tokenId_, name_); - } - - /// @inheritdoc IBillboardRegistry - function setBoardDescription(uint256 tokenId_, string calldata description_) external isFromOperator { boards[tokenId_].description = description_; - emit BoardDescriptionUpdated(tokenId_, description_); - } - - /// @inheritdoc IBillboardRegistry - function setBoardLocation(uint256 tokenId_, string calldata location_) external isFromOperator { + boards[tokenId_].imageURI = imageURI_; boards[tokenId_].location = location_; - emit BoardLocationUpdated(tokenId_, location_); - } - - /// @inheritdoc IBillboardRegistry - function setBoardContentURI(uint256 tokenId_, string calldata contentURI_) external isFromOperator { - boards[tokenId_].contentURI = contentURI_; - emit BoardContentURIUpdated(tokenId_, contentURI_); - } - - function setBoardRedirectURI(uint256 tokenId_, string calldata redirectURI_) external isFromOperator { - boards[tokenId_].redirectURI = redirectURI_; - emit BoardRedirectURIUpdated(tokenId_, redirectURI_); + emit BoardUpdated(tokenId_, name_, description_, imageURI_, location_); } ////////////////////////////// - /// Auction + /// Auction & Bid ////////////////////////////// - /// @inheritdoc IBillboardRegistry - function getAuction(uint256 tokenId_, uint256 auctionId_) external view returns (Auction memory auction) { - auction = boardAuctions[tokenId_][auctionId_]; + function getBidCount(uint256 tokenId_, uint256 epoch_) external view returns (uint256 count) { + count = auctionBidders[tokenId_][epoch_].length; } /// @inheritdoc IBillboardRegistry - function newAuction( + function newBid( uint256 tokenId_, - uint64 startAt_, - uint64 endAt_ - ) external isFromOperator returns (uint256 newAuctionId) { - nextBoardAuctionId[tokenId_]++; - - newAuctionId = nextBoardAuctionId[tokenId_]; - - boardAuctions[tokenId_][newAuctionId] = Auction({ - startAt: startAt_, - endAt: endAt_, - leaseStartAt: 0, - leaseEndAt: 0, - highestBidder: address(0) + uint256 epoch_, + address bidder_, + uint256 price_, + uint256 tax_, + string calldata contentURI_, + string calldata redirectURI_ + ) external isFromOperator { + Bid memory _bid = Bid({ + price: price_, + tax: tax_, + contentURI: contentURI_, + redirectURI: redirectURI_, + createdAt: block.number, + updatedAt: block.number, + isWithdrawn: false, + isWon: false }); - emit AuctionCreated(tokenId_, newAuctionId, startAt_, endAt_); - } + // add to auction bids + auctionBids[tokenId_][epoch_][bidder_] = _bid; - /// @inheritdoc IBillboardRegistry - function setAuctionLease( - uint256 tokenId_, - uint256 auctionId_, - uint64 leaseStartAt_, - uint64 leaseEndAt_ - ) external isFromOperator { - boardAuctions[tokenId_][auctionId_].leaseStartAt = leaseStartAt_; - boardAuctions[tokenId_][auctionId_].leaseEndAt = leaseEndAt_; - } + // add to auction bidders if new bid + auctionBidders[tokenId_][epoch_].push(bidder_); - /// @inheritdoc IBillboardRegistry - function getBidCount(uint256 tokenId_, uint256 auctionId_) external view returns (uint256 count) { - count = auctionBidders[tokenId_][auctionId_].length; - } + _setHiggestBidder(tokenId_, epoch_, price_, bidder_); - /// @inheritdoc IBillboardRegistry - function getBid(uint256 tokenId_, uint256 auctionId_, address bidder_) external view returns (Bid memory bid) { - bid = auctionBids[tokenId_][auctionId_][bidder_]; + emit BidUpdated(tokenId_, epoch_, bidder_, price_, tax_, contentURI_, redirectURI_); } /// @inheritdoc IBillboardRegistry - function newBid( + function setBid( uint256 tokenId_, - uint256 auctionId_, + uint256 epoch_, address bidder_, uint256 price_, - uint256 tax_ + uint256 tax_, + string calldata contentURI_, + string calldata redirectURI_ ) external isFromOperator { - Bid memory _bid = Bid({price: price_, tax: tax_, placedAt: block.number, isWithdrawn: false, isWon: false}); + Bid memory _bid = auctionBids[tokenId_][epoch_][bidder_]; + require(_bid.createdAt != 0, "Bid not found"); - // add to auction bids - auctionBids[tokenId_][auctionId_][bidder_] = _bid; + _bid.price = price_; + _bid.tax = tax_; + _bid.contentURI = contentURI_; + _bid.redirectURI = redirectURI_; + _bid.updatedAt = block.number; + + _setHiggestBidder(tokenId_, epoch_, price_, bidder_); - // add to auction bidders - auctionBidders[tokenId_][auctionId_].push(bidder_); + emit BidUpdated(tokenId_, epoch_, bidder_, price_, tax_, contentURI_, redirectURI_); + } - // set auction highest bidder if no highest bidder or price is higher. - // - // Note: for same price, the first bidder will always be - // the highest bidder since the block.number is always greater. - address highestBidder = boardAuctions[tokenId_][auctionId_].highestBidder; - Bid memory highestBid = auctionBids[tokenId_][auctionId_][highestBidder]; + // Set auction highest bidder if no highest bidder or price is higher. + // + // Note: for same price, the first bidder will always be + // the highest bidder since the block.number is always greater. + function _setHiggestBidder(uint256 tokenId_, uint256 epoch_, uint256 price_, address bidder_) internal { + address highestBidder = auctionHiggestBidder[tokenId_][epoch_]; + Bid memory highestBid = auctionBids[tokenId_][epoch_][highestBidder]; if (highestBidder == address(0) || price_ > highestBid.price) { - boardAuctions[tokenId_][auctionId_].highestBidder = bidder_; + auctionHiggestBidder[tokenId_][epoch_] = bidder_; } - - emit BidCreated(tokenId_, auctionId_, bidder_, price_, tax_); } /// @inheritdoc IBillboardRegistry - function setBidWon(uint256 tokenId_, uint256 auctionId_, address bidder_, bool isWon_) external isFromOperator { - auctionBids[tokenId_][auctionId_][bidder_].isWon = isWon_; - - emit BidWon(tokenId_, auctionId_, bidder_); + function setBidWon(uint256 tokenId_, uint256 epoch_, address bidder_, bool isWon_) external isFromOperator { + auctionBids[tokenId_][epoch_][bidder_].isWon = isWon_; + emit BidWon(tokenId_, epoch_, bidder_); } /// @inheritdoc IBillboardRegistry function setBidWithdrawn( uint256 tokenId_, - uint256 auctionId_, + uint256 epoch_, address bidder_, bool isWithdrawn_ ) external isFromOperator { - auctionBids[tokenId_][auctionId_][bidder_].isWithdrawn = isWithdrawn_; + auctionBids[tokenId_][epoch_][bidder_].isWithdrawn = isWithdrawn_; + emit BidWithdrawn(tokenId_, epoch_, msg.sender); } - /// @inheritdoc IBillboardRegistry - function transferAmount(address to_, uint256 amount_) external isFromOperator { - require(to_ != address(0), "Zero address"); + ////////////////////////////// + /// Tax & Withdraw + ////////////////////////////// - require(token.transfer(to_, amount_), "Failed token transfer"); + /// @inheritdoc IBillboardRegistry + function setTaxTreasury(address owner_, uint256 accumulated_, uint256 withdrawn_) external isFromOperator { + taxTreasury[owner_].accumulated = accumulated_; + taxTreasury[owner_].withdrawn = withdrawn_; } ////////////////////////////// - /// Tax & Withdraw + /// Transfer ////////////////////////////// /// @inheritdoc IBillboardRegistry - function setTaxRate(uint256 taxRate_) external isFromOperator { - taxRate = taxRate_; - - emit TaxRateUpdated(taxRate_); + function transferTokenByOperator(address to_, uint256 amount_) external isFromOperator { + require(to_ != address(0), "Zero address"); + require(token.transfer(to_, amount_), "Failed token transfer"); } /// @inheritdoc IBillboardRegistry - function setTaxTreasury(address owner_, uint256 accumulated_, uint256 withdrawn_) external isFromOperator { - taxTreasury[owner_].accumulated = accumulated_; - taxTreasury[owner_].withdrawn = withdrawn_; + function safeTransferByOperator(address from_, address to_, uint256 tokenId_) external isFromOperator { + _safeTransfer(from_, to_, tokenId_, ""); } ////////////////////////////// @@ -264,7 +233,8 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { * @notice See {IERC721-tokenURI}. */ function tokenURI(uint256 tokenId_) public view override(ERC721) returns (string memory uri) { - return boards[tokenId_].contentURI; + // TODO + // return boards[tokenId_].contentURI; } /** @@ -279,25 +249,8 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { ////////////////////////////// /// @inheritdoc IBillboardRegistry - function emitAuctionCleared( - uint256 tokenId_, - uint256 auctionId_, - address highestBidder_, - uint64 leaseStartAt_, - uint64 leaseEndAt_ - ) external { - emit AuctionCleared(tokenId_, auctionId_, highestBidder_, leaseStartAt_, leaseEndAt_); - } - - /// @inheritdoc IBillboardRegistry - function emitBidWithdrawn( - uint256 tokenId_, - uint256 auctionId_, - address bidder_, - uint256 price_, - uint256 tax_ - ) external { - emit BidWithdrawn(tokenId_, auctionId_, bidder_, price_, tax_); + function emitAuctionCleared(uint256 tokenId_, uint256 epoch_, address highestBidder_) external { + emit AuctionCleared(tokenId_, epoch_, highestBidder_); } /// @inheritdoc IBillboardRegistry diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index 18e3ffc..bd5d735 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -41,26 +41,21 @@ interface IBillboard { /// Access control ////////////////////////////// - /** - * @notice Toggle for operation access. - * - * @param value_ Value of access state. - */ - function setIsOpened(bool value_) external; - /** * @notice Add address to white list. * - * @param value_ Address of user will be added into white list. + * @param tokenId_ Token ID. + * @param address_ Address of user will be added into whitelist. */ - function addToWhitelist(address value_) external; + function addToWhitelist(uint256 tokenId_, address address_) external; /** * @notice Remove address from white list. * - * @param value_ Address of user will be removed from white list. + * @param tokenId_ Token ID. + * @param address_ Address of user will be removed from whitelist. */ - function removeFromWhitelist(address value_) external; + function removeFromWhitelist(uint256 tokenId_, address address_) external; ////////////////////////////// /// Board @@ -71,12 +66,12 @@ interface IBillboard { * * @param to_ Address of the new board receiver. */ - function mintBoard(address to_) external returns (uint256 tokenId); + function newBoard(address to_) external returns (uint256 tokenId); /** * @notice Get a board data. * - * @param tokenId_ Token ID of a board. + * @param tokenId_ Token ID. * * @return board Board data. */ @@ -85,7 +80,7 @@ interface IBillboard { /** * @notice Set the name of a board by board creator. * - * @param tokenId_ Token ID of a board. + * @param tokenId_ Token ID. * @param name_ Board name. */ function setBoardName(uint256 tokenId_, string calldata name_) external; @@ -93,7 +88,7 @@ interface IBillboard { /** * @notice Set the name of a board by board creator. * - * @param tokenId_ Token ID of a board. + * @param tokenId_ Token ID. * @param description_ Board description. */ function setBoardDescription(uint256 tokenId_, string calldata description_) external; @@ -101,48 +96,48 @@ interface IBillboard { /** * @notice Set the location of a board by board creator. * - * @param tokenId_ Token ID of a board. + * @param tokenId_ Token ID. * @param location_ Digital address where a board located. */ function setBoardLocation(uint256 tokenId_, string calldata location_) external; /** - * @notice Set the content URI and redirect URI of a board by the tenant + * @notice Set the image of a board by board creator. * - * @param tokenId_ Token ID of a board. - * @param contentURI_ Content URI of a board. + * @param tokenId_ Token ID. + * @param uri_ URI of the image. */ - function setBoardContentURI(uint256 tokenId_, string calldata contentURI_) external; + function setBoardImage(uint256 tokenId_, string calldata uri_) external; /** - * @notice Set the redirect URI and redirect URI of a board by the tenant + * @notice Set the (AD) content URI of a board by the tenant * - * @param tokenId_ Token ID of a board. + * @param tokenId_ Token ID. + * @param epoch_ Epoch. + * @param contentURI_ Content URI. + */ + function setBoardContentURI(uint256 tokenId_, uint256 epoch_, string calldata contentURI_) external; + + /** + * @notice Set the (AD) redirect URI of a board by the tenant + * + * @param tokenId_ Token ID. + * @param epoch_ Epoch. * @param redirectURI_ Redirect URI when users clicking. */ - function setBoardRedirectURI(uint256 tokenId_, string calldata redirectURI_) external; + function setBoardRedirectURI(uint256 tokenId_, uint256 epoch_, string calldata redirectURI_) external; ////////////////////////////// /// Auction ////////////////////////////// /** - * @notice Get auction of a board by auction ID. - * - * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of a board. - */ - function getAuction( - uint256 tokenId_, - uint256 auctionId_ - ) external view returns (IBillboardRegistry.Auction memory auction); - - /** - * @notice Clear the next auction of a board. + * @notice Clear an auction by a given epoch. * - * @param tokenId_ Token ID of a board. + * @param tokenId_ Token ID. + * @param epoch_ Epoch. */ - function clearAuction(uint256 tokenId_) external returns (uint256 price, uint256 tax); + function clearAuction(uint256 tokenId_, uint256 epoch_) external returns (uint256 price, uint256 tax); /** * @notice Clear the next auction of mutiple boards. @@ -154,9 +149,9 @@ interface IBillboard { ) external returns (uint256[] memory prices, uint256[] memory taxes); /** - * @notice Place bid for the next auction of a board. + * @notice Place bid for the next auction. * - * @param tokenId_ Token ID of a board. + * @param tokenId_ Token ID. * @param amount_ Amount of a bid. */ function placeBid(uint256 tokenId_, uint256 amount_) external payable; @@ -164,11 +159,11 @@ interface IBillboard { /** * @notice Get bid of a board auction by auction ID. * - * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of a board. + * @param tokenId_ Token ID. + * @param auctionId_ Auction ID. * @param bidder_ Address of a bidder. * - * @return bid Bid of a board. + * @return bid Bid. */ function getBid( uint256 tokenId_, @@ -179,15 +174,15 @@ interface IBillboard { /** * @notice Get bids of a board auction by auction ID. * - * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of a board. + * @param tokenId_ Token ID. + * @param auctionId_ Auction ID. * @param limit_ Limit of returned bids. * @param offset_ Offset of returned bids. * * @return total Total number of bids. * @return limit Limit of returned bids. * @return offset Offset of returned bids. - * @return bids Bids of a board. + * @return bids Bids. */ function getBids( uint256 tokenId_, @@ -222,7 +217,7 @@ interface IBillboard { function calculateTax(uint256 amount_) external returns (uint256 tax); /** - * @notice Withdraw accumulated taxation of a board. + * @notice Withdraw accumulated taxation. * */ function withdrawTax() external returns (uint256 tax); @@ -230,8 +225,8 @@ interface IBillboard { /** * @notice Withdraw bid that were not won by auction id; * - * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of a board. + * @param tokenId_ Token ID. + * @param auctionId_ Auction ID. */ function withdrawBid(uint256 tokenId_, uint256 auctionId_) external; } diff --git a/src/Billboard/IBillboardRegistry.sol b/src/Billboard/IBillboardRegistry.sol index 4c0a59f..9a4a4ea 100644 --- a/src/Billboard/IBillboardRegistry.sol +++ b/src/Billboard/IBillboardRegistry.sol @@ -19,121 +19,73 @@ interface IBillboardRegistry is IERC721 { event OperatorUpdated(address indexed operator); /** - * @notice Board name is updated. + * @notice Board is created. * - * @param tokenId Token ID of the board. - * @param name New name of the board. - */ - event BoardNameUpdated(uint256 indexed tokenId, string name); - - /** - * @notice Board description is updated. - * - * @param tokenId Token ID of the board. - * @param description New description of the board. - */ - event BoardDescriptionUpdated(uint256 indexed tokenId, string description); - - /** - * @notice Board location is updated. - * - * @param tokenId Token ID of the board. - * @param location New location of the board. + * @param tokenId Token ID of the board + * @param to Address of the board owner. + * @param taxRate Tax rate of the board. + * @param epochInterval Epoch interval of the board. */ - event BoardLocationUpdated(uint256 indexed tokenId, string location); + event BoardCreated(uint256 indexed tokenId, address indexed to, uint256 taxRate, uint256 epochInterval); /** - * @notice Board content URI is updated. + * @notice Board data is updated. * * @param tokenId Token ID of the board. - * @param contentURI New content URI of the board. + * @param name Name of the board. + * @param description Description of the board. + * @param imageURI Image URI of the board. + * @param location Location of the board. */ - event BoardContentURIUpdated(uint256 indexed tokenId, string contentURI); - - /** - * @notice Board redirect URI is updated. - * - * @param tokenId Token ID of the board. - * @param redirectURI New redirect URI of the board. - */ - event BoardRedirectURIUpdated(uint256 indexed tokenId, string redirectURI); - - /** - * @notice Global tax rate is updated. - * - * @param taxRate New tax rate. - */ - event TaxRateUpdated(uint256 taxRate); - - /** - * @notice Auction is created. - * - * @param tokenId Token ID of the board. - * @param auctionId Auction ID of the auction. - * @param startAt Start time of the auction. - * @param endAt End time of the auction. - */ - event AuctionCreated(uint256 indexed tokenId, uint256 indexed auctionId, uint64 startAt, uint64 endAt); + event BoardUpdated(uint256 indexed tokenId, string name, string description, string imageURI, string location); /** * @notice Auction is cleared. * * @param tokenId Token ID of the board. - * @param auctionId Auction ID of the auction. + * @param epoch Epoch of the auction. * @param highestBidder Highest bidder of the auction. - * @param leaseStartAt Start time of the lease. - * @param leaseEndAt End time of the lease. */ - event AuctionCleared( - uint256 indexed tokenId, - uint256 indexed auctionId, - address indexed highestBidder, - uint64 leaseStartAt, - uint64 leaseEndAt - ); + event AuctionCleared(uint256 indexed tokenId, uint256 indexed epoch, address indexed highestBidder); /** - * @notice Bid is created. + * @notice Bid is created or updated. * * @param tokenId Token ID of the board. - * @param auctionId Auction ID of the auction. + * @param epoch Epoch of the auction. * @param bidder Bidder of the auction. * @param price Price of the bid. * @param tax Tax of the bid. + * @param contentURI Content URI of the bid. + * @param redirectURI Redirect URI of the bid. */ - event BidCreated( + event BidUpdated( uint256 indexed tokenId, - uint256 indexed auctionId, + uint256 indexed epoch, address indexed bidder, uint256 price, - uint256 tax + uint256 tax, + string contentURI, + string redirectURI ); /** * @notice Bid is won. * * @param tokenId Token ID of the board. - * @param auctionId Auction ID of the auction. + * @param epoch Epoch of the auction. * @param bidder Bidder of the auction. */ - event BidWon(uint256 indexed tokenId, uint256 indexed auctionId, address indexed bidder); + event BidWon(uint256 indexed tokenId, uint256 indexed epoch, address indexed bidder); /** * @notice Bid is withdrawn. * * @param tokenId Token ID of the board. - * @param auctionId Auction ID of the auction. + * @param epoch Epoch of the auction. * @param bidder Bidder of the auction. - * @param price Price of the bid. - * @param tax Tax of the bid. */ - event BidWithdrawn( - uint256 indexed tokenId, - uint256 indexed auctionId, - address indexed bidder, - uint256 price, - uint256 tax - ); + event BidWithdrawn(uint256 indexed tokenId, uint256 indexed epoch, address indexed bidder); /** * @notice Tax is withdrawn. @@ -148,26 +100,25 @@ interface IBillboardRegistry is IERC721 { ////////////////////////////// struct Board { + // immutable data address creator; + uint256 taxRate; + uint256 epochInterval; // in blocks + uint256 createdAt; // gensis epoch, block number + // mutable data string name; string description; + string imageURI; string location; - string contentURI; - string redirectURI; - } - - struct Auction { - uint64 startAt; // block number - uint64 endAt; // block number - uint64 leaseStartAt; // block number - uint64 leaseEndAt; // block number - address highestBidder; } struct Bid { uint256 price; uint256 tax; - uint256 placedAt; // block number + string contentURI; + string redirectURI; + uint256 createdAt; // block number + uint256 updatedAt; // block number bool isWon; bool isWithdrawn; } @@ -191,180 +142,144 @@ interface IBillboardRegistry is IERC721 { /** * @notice Mint a new board (NFT). * - * @param to_ Address of the new board receiver. + * @param to_ Address of the board owner. + * @param taxRate_ Tax rate of the new board. + * @param epochInterval_ Epoch interval of the new board. * * @return tokenId Token ID of the new board. */ - function mintBoard(address to_) external returns (uint256 tokenId); - - /** - * @notice Transfer a board (NFT) by the operator. - * - * @param from_ Address of the board sender. - * @param to_ Address of the board receiver. - * @param tokenId_ Token ID of the board. - */ - function safeTransferByOperator(address from_, address to_, uint256 tokenId_) external; + function newBoard(address to_, uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId); /** - * @notice Get a board - * - * @param tokenId_ Token ID of a board. - */ - function getBoard(uint256 tokenId_) external view returns (Board memory board); - - /** - * @notice Set the name of a board by board creator. + * @notice Set metadata of a board. * * @param tokenId_ Token ID of a board. * @param name_ Board name. - */ - function setBoardName(uint256 tokenId_, string calldata name_) external; - - /** - * @notice Set the name of a board by board creator. - * - * @param tokenId_ Token ID of a board. * @param description_ Board description. + * @param imageURI_ Image URI of a board. + * @param location_ Location of a board. */ - function setBoardDescription(uint256 tokenId_, string calldata description_) external; - - /** - * @notice Set the location of a board by board creator. - * - * @param tokenId_ Token ID of a board. - * @param location_ Digital address where a board located. - */ - function setBoardLocation(uint256 tokenId_, string calldata location_) external; - - /** - * @notice Set the content URI and redirect URI of a board by the tenant - * - * @param tokenId_ Token ID of a board. - * @param contentURI_ Content URI of a board. - */ - function setBoardContentURI(uint256 tokenId_, string calldata contentURI_) external; - - /** - * @notice Set the redirect URI and redirect URI of a board by the tenant - * - * @param tokenId_ Token ID of a board. - * @param redirectURI_ Redirect URI when users clicking. - */ - function setBoardRedirectURI(uint256 tokenId_, string calldata redirectURI_) external; + function setBoard( + uint256 tokenId_, + string calldata name_, + string calldata description_, + string calldata imageURI_, + string calldata location_ + ) external; ////////////////////////////// - /// Auction + /// Auction & Bid ////////////////////////////// - - /** - * @notice Get an auction - * - * @param tokenId_ Token ID of a board. - * @param auctionId_ Token ID of a board. - */ - function getAuction(uint256 tokenId_, uint256 auctionId_) external view returns (Auction memory auction); - /** - * @notice Create new auction + * @notice Get bid count of an auction * * @param tokenId_ Token ID of a board. - * @param startAt_ Start time of an auction. - * @param endAt_ End time of an auction. - */ - function newAuction(uint256 tokenId_, uint64 startAt_, uint64 endAt_) external returns (uint256 auctionId); - - /** - * @notice Set the data of an auction + * @param epoch_ Epoch. * - * @param tokenId_ Token ID of a board. - * @param auctionId_ Token ID of a board. - * @param leaseStartAt_ Start time of an board lease. - * @param leaseEndAt_ End time of an board lease. + * @return count Count of bids. */ - function setAuctionLease(uint256 tokenId_, uint256 auctionId_, uint64 leaseStartAt_, uint64 leaseEndAt_) external; + function getBidCount(uint256 tokenId_, uint256 epoch_) external view returns (uint256 count); /** - * @notice Get bid count of an auction + * @notice Create or update a bid * * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of an auction. + * @param epoch_ Epoch of an auction. + * @param bidder_ Bidder of an auction. + * @param price_ Price of a bid. + * @param tax_ Tax of a bid. */ - function getBidCount(uint256 tokenId_, uint256 auctionId_) external view returns (uint256 count); + function setBid(uint256 tokenId_, uint256 epoch_, address bidder_, uint256 price_, uint256 tax_) external; /** - * @notice Get a bid of an auction + * @notice Create or update a bid * * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of an auction. + * @param epoch_ Epoch of an auction. * @param bidder_ Bidder of an auction. + * @param price_ Price of a bid. + * @param tax_ Tax of a bid. + * @param contentURI_ Content URI of a bid. + * @param redirectURI_ Redirect URI of a bid. */ - function getBid(uint256 tokenId_, uint256 auctionId_, address bidder_) external view returns (Bid memory bid); + function setBid( + uint256 tokenId_, + uint256 epoch_, + address bidder_, + uint256 price_, + uint256 tax_, + string calldata contentURI_, + string calldata redirectURI_ + ) external; /** - * @notice Create new bid and add it to auction - * - * 1. Create new bid: `new Bid()` - * 2. Add bid to auction: - * - `auction.bids[bidder] = bid` - * - `auction.bidders.push(bidder)` - * - if any `auction.highestBidder = bidder` + * @notice Set the content URI and redirect URI of a board. * * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of an auction. - * @param bidder_ Bidder of an auction. - * @param price_ Price of a bid. - * @param tax_ Tax of a bid. + * @param epoch_ Epoch. + * @param contentURI_ Content URI of a board. + * @param redirectURI_ Redirect URI of a board. */ - function newBid(uint256 tokenId_, uint256 auctionId_, address bidder_, uint256 price_, uint256 tax_) external; + function setBidURIs( + uint256 tokenId_, + uint256 epoch_, + string calldata contentURI_, + string calldata redirectURI_ + ) external; /** * @notice Set isWon of a bid * * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of an auction. + * @param epoch_ Epoch of an auction. * @param bidder_ Bidder of an auction. * @param isWon_ Whether a bid is won. */ - function setBidWon(uint256 tokenId_, uint256 auctionId_, address bidder_, bool isWon_) external; + function setBidWon(uint256 tokenId_, uint256 epoch_, address bidder_, bool isWon_) external; /** * @notice Set isWithdrawn of a bid * * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of an auction. + * @param epoch_ Epoch of an auction. * @param bidder_ Bidder of an auction. * @param isWithdrawn_ Whether a bid is won. */ - function setBidWithdrawn(uint256 tokenId_, uint256 auctionId_, address bidder_, bool isWithdrawn_) external; + function setBidWithdrawn(uint256 tokenId_, uint256 epoch_, address bidder_, bool isWithdrawn_) external; + + ////////////////////////////// + /// Tax & Withdraw + ////////////////////////////// /** - * @notice Transfer amount to a receiver. + * @notice Set the tax treasury. * - * @param to_ Address of a receiver. - * @param amount_ Amount. + * @param owner_ Address of a treasury owner. + * @param accumulated_ Accumulated tax. + * @param withdrawn_ Withdrawn tax. */ - function transferAmount(address to_, uint256 amount_) external; + function setTaxTreasury(address owner_, uint256 accumulated_, uint256 withdrawn_) external; ////////////////////////////// - /// Tax & Withdraw + /// Transfer ////////////////////////////// /** - * @notice Set the global tax rate. + * @notice Transfer a board (NFT). * - * @param taxRate_ Tax rate. + * @param from_ Address of the board sender. + * @param to_ Address of the board receiver. + * @param tokenId_ Token ID of the board. */ - function setTaxRate(uint256 taxRate_) external; + function safeTransferByOperator(address from_, address to_, uint256 tokenId_) external; /** - * @notice Set the tax treasury. + * @notice Transfer amount of token to a receiver. * - * @param owner_ Address of a treasury owner. - * @param accumulated_ Accumulated tax. - * @param withdrawn_ Withdrawn tax. + * @param to_ Address of a receiver. + * @param amount_ Amount. */ - function setTaxTreasury(address owner_, uint256 accumulated_, uint256 withdrawn_) external; + function transferTokenByOperator(address to_, uint256 amount_) external; ////////////////////////////// /// Event emission @@ -374,35 +289,10 @@ interface IBillboardRegistry is IERC721 { * @notice Emit `AuctionCleared` event. * * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of an auction. + * @param epoch_ Epoch of an auction. * @param highestBidder_ Highest bidder of an auction. - * @param leaseStartAt_ Start time of an board lease. - * @param leaseEndAt_ End time of an board lease. */ - function emitAuctionCleared( - uint256 tokenId_, - uint256 auctionId_, - address highestBidder_, - uint64 leaseStartAt_, - uint64 leaseEndAt_ - ) external; - - /** - * @notice Emit `BidWithdrawn` event. - * - * @param tokenId_ Token ID of a board. - * @param auctionId_ Auction ID of an auction. - * @param bidder_ Bidder of an auction. - * @param price_ Price of a bid. - * @param tax_ Tax of a bid. - */ - function emitBidWithdrawn( - uint256 tokenId_, - uint256 auctionId_, - address bidder_, - uint256 price_, - uint256 tax_ - ) external; + function emitAuctionCleared(uint256 tokenId_, uint256 epoch_, address highestBidder_) external; /** * @notice Emit `TaxWithdrawn` event. From 3e3f940c9a9d6c6b17c4e89f811d4566a5a38eba Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Fri, 28 Jun 2024 09:54:04 +0800 Subject: [PATCH 02/24] feat(billboard): revise IBillboard.sol --- src/Billboard/Billboard.sol | 8 -- src/Billboard/BillboardRegistry.sol | 26 ++++- src/Billboard/IBillboard.sol | 146 +++++++++------------------ src/Billboard/IBillboardRegistry.sol | 18 +++- 4 files changed, 84 insertions(+), 114 deletions(-) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 3656213..02e0ce4 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -135,14 +135,6 @@ contract Billboard is IBillboard { /// Auction ////////////////////////////// - /// @inheritdoc IBillboard - function getAuction( - uint256 tokenId_, - uint256 auctionId_ - ) external view returns (IBillboardRegistry.Auction memory auction) { - auction = registry.getAuction(tokenId_, auctionId_); - } - /// @inheritdoc IBillboard function clearAuction(uint256 tokenId_) public returns (uint256 price, uint256 tax) { // revert if board not found diff --git a/src/Billboard/BillboardRegistry.sol b/src/Billboard/BillboardRegistry.sol index 7c9c1c5..7f6a87b 100644 --- a/src/Billboard/BillboardRegistry.sol +++ b/src/Billboard/BillboardRegistry.sol @@ -183,6 +183,22 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { } } + /// @inheritdoc IBillboardRegistry + function setBidURIs( + uint256 tokenId_, + uint256 epoch_, + address bidder_, + string calldata contentURI_, + string calldata redirectURI_ + ) external isFromOperator { + Bid memory _bid = auctionBids[tokenId_][epoch_][bidder_]; + + _bid.contentURI = contentURI_; + _bid.redirectURI = redirectURI_; + + emit BidUpdated(tokenId_, epoch_, bidder_, _bid.price, _bid.tax, contentURI_, redirectURI_); + } + /// @inheritdoc IBillboardRegistry function setBidWon(uint256 tokenId_, uint256 epoch_, address bidder_, bool isWon_) external isFromOperator { auctionBids[tokenId_][epoch_][bidder_].isWon = isWon_; @@ -215,14 +231,14 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { ////////////////////////////// /// @inheritdoc IBillboardRegistry - function transferTokenByOperator(address to_, uint256 amount_) external isFromOperator { - require(to_ != address(0), "Zero address"); - require(token.transfer(to_, amount_), "Failed token transfer"); + function safeTransferByOperator(address from_, address to_, uint256 tokenId_) external isFromOperator { + _safeTransfer(from_, to_, tokenId_, ""); } /// @inheritdoc IBillboardRegistry - function safeTransferByOperator(address from_, address to_, uint256 tokenId_) external isFromOperator { - _safeTransfer(from_, to_, tokenId_, ""); + function transferTokenByOperator(address to_, uint256 amount_) external isFromOperator { + require(to_ != address(0), "Zero address"); + require(token.transfer(to_, amount_), "Failed token transfer"); } ////////////////////////////// diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index bd5d735..41cfd00 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -12,7 +12,7 @@ import "./IBillboardRegistry.sol"; * - Owner of a billboard can set the AD data of a billboard: call `setBoardName`, `setBoardDescription` and `setBoardLocation`. * - Tenant of a billboard can set the AD data of a billboard: call `setBoardContentURI` and `setBoardRedirectURI`. * - * ## Auction + * ## Auction & Bid * - User needs to call `approve` on currency (USDT) contract before starting. * - User can place a bid on a billboard: call `placeBid`. * - User can clear auction on a billboard: call `clearAuction`. @@ -42,7 +42,7 @@ interface IBillboard { ////////////////////////////// /** - * @notice Add address to white list. + * @notice Add address to whitelist. * * @param tokenId_ Token ID. * @param address_ Address of user will be added into whitelist. @@ -50,7 +50,7 @@ interface IBillboard { function addToWhitelist(uint256 tokenId_, address address_) external; /** - * @notice Remove address from white list. + * @notice Remove address from whitelist. * * @param tokenId_ Token ID. * @param address_ Address of user will be removed from whitelist. @@ -64,71 +64,33 @@ interface IBillboard { /** * @notice Mint a new board (NFT). * - * @param to_ Address of the new board receiver. - */ - function newBoard(address to_) external returns (uint256 tokenId); - - /** - * @notice Get a board data. - * - * @param tokenId_ Token ID. + * @param to_ Address of the board owner. + * @param taxRate_ Tax rate of the new board. + * @param epochInterval_ Epoch interval of the new board. * - * @return board Board data. + * @return tokenId Token ID of the new board. */ - function getBoard(uint256 tokenId_) external view returns (IBillboardRegistry.Board memory board); + function newBoard(address to_, uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId); /** - * @notice Set the name of a board by board creator. + * @notice Set metadata of a board by creator. * - * @param tokenId_ Token ID. + * @param tokenId_ Token ID of a board. * @param name_ Board name. - */ - function setBoardName(uint256 tokenId_, string calldata name_) external; - - /** - * @notice Set the name of a board by board creator. - * - * @param tokenId_ Token ID. * @param description_ Board description. + * @param imageURI_ Image URI of a board. + * @param location_ Location of a board. */ - function setBoardDescription(uint256 tokenId_, string calldata description_) external; - - /** - * @notice Set the location of a board by board creator. - * - * @param tokenId_ Token ID. - * @param location_ Digital address where a board located. - */ - function setBoardLocation(uint256 tokenId_, string calldata location_) external; - - /** - * @notice Set the image of a board by board creator. - * - * @param tokenId_ Token ID. - * @param uri_ URI of the image. - */ - function setBoardImage(uint256 tokenId_, string calldata uri_) external; - - /** - * @notice Set the (AD) content URI of a board by the tenant - * - * @param tokenId_ Token ID. - * @param epoch_ Epoch. - * @param contentURI_ Content URI. - */ - function setBoardContentURI(uint256 tokenId_, uint256 epoch_, string calldata contentURI_) external; - - /** - * @notice Set the (AD) redirect URI of a board by the tenant - * - * @param tokenId_ Token ID. - * @param epoch_ Epoch. - * @param redirectURI_ Redirect URI when users clicking. - */ - function setBoardRedirectURI(uint256 tokenId_, uint256 epoch_, string calldata redirectURI_) external; + function setBoard( + uint256 tokenId_, + string calldata name_, + string calldata description_, + string calldata imageURI_, + string calldata location_ + ) external; ////////////////////////////// - /// Auction + /// Auction & Bid ////////////////////////////// /** @@ -136,46 +98,45 @@ interface IBillboard { * * @param tokenId_ Token ID. * @param epoch_ Epoch. + * + * @return highestBidder Address of the highest bidder. + * @return price Price of the highest bid. + * @return tax Tax of the highest bid. */ - function clearAuction(uint256 tokenId_, uint256 epoch_) external returns (uint256 price, uint256 tax); + function clearAuction( + uint256 tokenId_, + uint256 epoch_ + ) external returns (address highestBidder, uint256 price, uint256 tax); /** * @notice Clear the next auction of mutiple boards. * * @param tokenIds_ Token IDs of boards. + * @param epochs_ Epochs of auctions. + * + * @return highestBidders Addresses of the highest bidders. + * @return prices Prices of the highest bids. + * @return taxes Taxes of the highest bids. */ function clearAuctions( - uint256[] calldata tokenIds_ - ) external returns (uint256[] memory prices, uint256[] memory taxes); + uint256[] calldata tokenIds_, + uint256[] calldata epochs_ + ) external returns (address[] memory highestBidders, uint256[] memory prices, uint256[] memory taxes); /** - * @notice Place bid for the next auction. + * @notice Place bid on a board auction. * * @param tokenId_ Token ID. + * @param epoch_ Epoch. * @param amount_ Amount of a bid. */ - function placeBid(uint256 tokenId_, uint256 amount_) external payable; - - /** - * @notice Get bid of a board auction by auction ID. - * - * @param tokenId_ Token ID. - * @param auctionId_ Auction ID. - * @param bidder_ Address of a bidder. - * - * @return bid Bid. - */ - function getBid( - uint256 tokenId_, - uint256 auctionId_, - address bidder_ - ) external view returns (IBillboardRegistry.Bid memory bid); + function placeBid(uint256 tokenId_, uint256 epoch_, uint256 amount_) external payable; /** - * @notice Get bids of a board auction by auction ID. + * @notice Get bids of a board auction. * * @param tokenId_ Token ID. - * @param auctionId_ Auction ID. + * @param epoch_ Epoch. * @param limit_ Limit of returned bids. * @param offset_ Offset of returned bids. * @@ -186,7 +147,7 @@ interface IBillboard { */ function getBids( uint256 tokenId_, - uint256 auctionId_, + uint256 epoch_, uint256 limit_, uint256 offset_ ) external view returns (uint256 total, uint256 limit, uint256 offset, IBillboardRegistry.Bid[] memory bids); @@ -195,26 +156,15 @@ interface IBillboard { /// Tax & Withdraw ////////////////////////////// - /** - * @notice Get the global tax rate. - * - * @return taxRate Tax rate. - */ - function getTaxRate() external view returns (uint256 taxRate); - - /** - * @notice Set the global tax rate. - * - * @param taxRate_ Tax rate. - */ - function setTaxRate(uint256 taxRate_) external; - /** * @notice Calculate tax of a bid. * + * @param tokenId_ Token ID. * @param amount_ Amount of a bid. + * + * @return tax Tax of a bid. */ - function calculateTax(uint256 amount_) external returns (uint256 tax); + function calculateTax(uint256 tokenId_, uint256 amount_) external returns (uint256 tax); /** * @notice Withdraw accumulated taxation. @@ -226,7 +176,7 @@ interface IBillboard { * @notice Withdraw bid that were not won by auction id; * * @param tokenId_ Token ID. - * @param auctionId_ Auction ID. + * @param epoch_ Epoch. */ - function withdrawBid(uint256 tokenId_, uint256 auctionId_) external; + function withdrawBid(uint256 tokenId_, uint256 epoch_) external; } diff --git a/src/Billboard/IBillboardRegistry.sol b/src/Billboard/IBillboardRegistry.sol index 9a4a4ea..ea72071 100644 --- a/src/Billboard/IBillboardRegistry.sol +++ b/src/Billboard/IBillboardRegistry.sol @@ -181,18 +181,28 @@ interface IBillboardRegistry is IERC721 { function getBidCount(uint256 tokenId_, uint256 epoch_) external view returns (uint256 count); /** - * @notice Create or update a bid + * @notice Create a bid * * @param tokenId_ Token ID of a board. * @param epoch_ Epoch of an auction. * @param bidder_ Bidder of an auction. * @param price_ Price of a bid. * @param tax_ Tax of a bid. + * @param contentURI_ Content URI of a bid. + * @param redirectURI_ Redirect URI of a bid. */ - function setBid(uint256 tokenId_, uint256 epoch_, address bidder_, uint256 price_, uint256 tax_) external; + function newBid( + uint256 tokenId_, + uint256 epoch_, + address bidder_, + uint256 price_, + uint256 tax_, + string calldata contentURI_, + string calldata redirectURI_ + ) external; /** - * @notice Create or update a bid + * @notice Update a bid * * @param tokenId_ Token ID of a board. * @param epoch_ Epoch of an auction. @@ -217,12 +227,14 @@ interface IBillboardRegistry is IERC721 { * * @param tokenId_ Token ID of a board. * @param epoch_ Epoch. + * @param bidder_ Bidder of an auction. * @param contentURI_ Content URI of a board. * @param redirectURI_ Redirect URI of a board. */ function setBidURIs( uint256 tokenId_, uint256 epoch_, + address bidder_, string calldata contentURI_, string calldata redirectURI_ ) external; From d01b9e92e73b2d6500bbf90d089f72e0a93b5d12 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Fri, 28 Jun 2024 10:32:18 +0800 Subject: [PATCH 03/24] feat(billboard): revise Billboard.sol --- src/Billboard/Billboard.sol | 103 ++++++++++------------------ src/Billboard/BillboardRegistry.sol | 3 - src/Billboard/IBillboard.sol | 35 +++++++++- 3 files changed, 69 insertions(+), 72 deletions(-) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 02e0ce4..4b5573e 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -11,21 +11,13 @@ contract Billboard is IBillboard { // access control BillboardRegistry public immutable registry; address public immutable admin; - mapping(address => bool) public whitelist; - bool public isOpened = false; - - constructor( - address token_, - address payable registry_, - address admin_, - uint256 taxRate_, - uint64 leaseTerm_, - string memory name_, - string memory symbol_ - ) { + + // tokenId => address => whitelisted + mapping(uint256 => mapping(address => bool)) public boardWhitelists; + + constructor(address token_, address payable registry_, address admin_, string memory name_, string memory symbol_) { require(admin_ != address(0), "Zero address"); admin = admin_; - whitelist[admin_] = true; // deploy operator only if (registry_ != address(0)) { @@ -33,7 +25,7 @@ contract Billboard is IBillboard { } // deploy operator and registry else { - registry = new BillboardRegistry(token_, address(this), taxRate_, leaseTerm_, name_, symbol_); + registry = new BillboardRegistry(token_, address(this), name_, symbol_); } } @@ -46,8 +38,8 @@ contract Billboard is IBillboard { _; } - modifier isFromWhitelist() { - require(whitelist[msg.sender], "Whitelist"); + modifier isFromWhitelist(uint256 tokenId_) { + require(boardWhitelists[tokenId_][msg.sender], "Whitelist"); _; } @@ -76,18 +68,13 @@ contract Billboard is IBillboard { ////////////////////////////// /// @inheritdoc IBillboard - function setIsOpened(bool value_) external isFromAdmin { - isOpened = value_; - } - - /// @inheritdoc IBillboard - function addToWhitelist(address value_) external isFromAdmin { - whitelist[value_] = true; + function addToWhitelist(uint256 tokenId_, address value_) external isFromAdmin { + boardWhitelists[tokenId_][value_] = true; } /// @inheritdoc IBillboard - function removeFromWhitelist(address value_) external isFromAdmin { - whitelist[value_] = false; + function removeFromWhitelist(uint256 tokenId_, address value_) external isFromAdmin { + boardWhitelists[tokenId_][value_] = false; } ////////////////////////////// @@ -95,44 +82,29 @@ contract Billboard is IBillboard { ////////////////////////////// /// @inheritdoc IBillboard - function mintBoard(address to_) external returns (uint256 tokenId) { - require(isOpened || whitelist[msg.sender], "Whitelist"); - - tokenId = registry.mintBoard(to_); + function mintBoard(address to_, uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId) { + tokenId = registry.newBoard(to_, taxRate_, epochInterval_); } /// @inheritdoc IBillboard function getBoard(uint256 tokenId_) external view returns (IBillboardRegistry.Board memory board) { - return registry.getBoard(tokenId_); + return registry.boards(tokenId_); } /// @inheritdoc IBillboard - function setBoardName(uint256 tokenId_, string calldata name_) external isFromBoardCreator(tokenId_) { - registry.setBoardName(tokenId_, name_); - } - /// @inheritdoc IBillboard - function setBoardDescription(uint256 tokenId_, string calldata description_) external isFromBoardCreator(tokenId_) { - registry.setBoardDescription(tokenId_, description_); - } - - /// @inheritdoc IBillboard - function setBoardLocation(uint256 tokenId_, string calldata location_) external isFromBoardCreator(tokenId_) { - registry.setBoardLocation(tokenId_, location_); - } - - /// @inheritdoc IBillboard - function setBoardContentURI(uint256 tokenId_, string calldata contentURI_) external isFromBoardTenant(tokenId_) { - registry.setBoardContentURI(tokenId_, contentURI_); - } - - /// @inheritdoc IBillboard - function setBoardRedirectURI(uint256 tokenId_, string calldata redirectURI_) external isFromBoardTenant(tokenId_) { - registry.setBoardRedirectURI(tokenId_, redirectURI_); + function setBoard( + uint256 tokenId_, + string calldata name_, + string calldata description_, + string calldata imageURI_, + string calldata location_ + ) external isFromBoardCreator(tokenId_) { + registry.setBoard(tokenId_, name_, description_, imageURI_, location_); } ////////////////////////////// - /// Auction + /// Auction & Bid ////////////////////////////// /// @inheritdoc IBillboard @@ -222,7 +194,7 @@ contract Billboard is IBillboard { } /// @inheritdoc IBillboard - function placeBid(uint256 tokenId_, uint256 amount_) external payable isFromWhitelist { + function placeBid(uint256 tokenId_, uint256 amount_) external payable isFromWhitelist(tokenId_) { IBillboardRegistry.Board memory _board = registry.getBoard(tokenId_); require(_board.creator != address(0), "Board not found"); @@ -277,20 +249,20 @@ contract Billboard is IBillboard { /// @inheritdoc IBillboard function getBid( uint256 tokenId_, - uint256 auctionId_, + uint256 epoch_, address bidder_ ) external view returns (IBillboardRegistry.Bid memory bid) { - return registry.getBid(tokenId_, auctionId_, bidder_); + return registry.auctionBids(tokenId_, epoch_, bidder_); } /// @inheritdoc IBillboard function getBids( uint256 tokenId_, - uint256 auctionId_, + uint256 epoch_, uint256 limit_, uint256 offset_ ) external view returns (uint256 total, uint256 limit, uint256 offset, IBillboardRegistry.Bid[] memory bids) { - uint256 _total = registry.getBidCount(tokenId_, auctionId_); + uint256 _total = registry.getBidCount(tokenId_, epoch_); if (limit_ == 0) { return (_total, limit_, offset_, new IBillboardRegistry.Bid[](0)); @@ -306,8 +278,8 @@ contract Billboard is IBillboard { IBillboardRegistry.Bid[] memory _bids = new IBillboardRegistry.Bid[](_size); for (uint256 i = 0; i < _size; i++) { - address _bidder = registry.auctionBidders(tokenId_, auctionId_, offset_ + i); - _bids[i] = registry.getBid(tokenId_, auctionId_, _bidder); + address _bidder = registry.auctionBidders(tokenId_, epoch_, offset_ + i); + _bids[i] = registry.auctionBids(tokenId_, epoch_, bidder_); } return (_total, limit_, offset_, _bids); @@ -318,17 +290,12 @@ contract Billboard is IBillboard { ////////////////////////////// /// @inheritdoc IBillboard - function getTaxRate() external view returns (uint256 taxRate) { - return registry.taxRate(); - } - - /// @inheritdoc IBillboard - function setTaxRate(uint256 taxRate_) external isFromAdmin { - registry.setTaxRate(taxRate_); + function getTaxRate(uint256 tokenId_) external view returns (uint256 taxRate) { + return registry.boards(tokenId_).taxRate; } - function calculateTax(uint256 amount_) public view returns (uint256 tax) { - tax = (amount_ * registry.taxRate()) / 1000; + function calculateTax(uint256 tokenId_, uint256 amount_) public view returns (uint256 tax) { + tax = (amount_ * this.getTaxRate(tokenId_)) / 1000; } /// @inheritdoc IBillboard diff --git a/src/Billboard/BillboardRegistry.sol b/src/Billboard/BillboardRegistry.sol index 7f6a87b..2f84a4d 100644 --- a/src/Billboard/BillboardRegistry.sol +++ b/src/Billboard/BillboardRegistry.sol @@ -19,9 +19,6 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { // tokenId => Board mapping(uint256 => Board) public boards; - // tokenId => address => whitelisted - mapping(uint256 => mapping(address => bool)) public boardWhitelists; - // tokenId => epoch => bidder mapping(uint256 => mapping(uint256 => address)) public auctionHiggestBidder; diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index 41cfd00..edf1575 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -70,7 +70,16 @@ interface IBillboard { * * @return tokenId Token ID of the new board. */ - function newBoard(address to_, uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId); + function mintBoard(address to_, uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId); + + /** + * @notice Get metadata of a board . + * + * @param tokenId_ Token ID of a board. + * + * @return board Board metadata. + */ + function getBoard(uint256 tokenId_) external view returns (IBillboardRegistry.Board memory board); /** * @notice Set metadata of a board by creator. @@ -132,6 +141,21 @@ interface IBillboard { */ function placeBid(uint256 tokenId_, uint256 epoch_, uint256 amount_) external payable; + /** + * @notice Get bid of a board auction. + * + * @param tokenId_ Token ID of a board. + * @param epoch_ Epoch of an auction. + * @param bidder_ Address of a bidder. + * + * @return bid Bid of a board. + */ + function getBid( + uint256 tokenId_, + uint256 epoch_, + address bidder_ + ) external view returns (IBillboardRegistry.Bid memory bid); + /** * @notice Get bids of a board auction. * @@ -156,6 +180,15 @@ interface IBillboard { /// Tax & Withdraw ////////////////////////////// + /** + * @notice Get the global tax rate. + * + * @param tokenId_ Token ID. + * + * @return taxRate Tax rate. + */ + function getTaxRate(uint256 tokenId_) external view returns (uint256 taxRate); + /** * @notice Calculate tax of a bid. * From 4bed635260ca72804ed4e30e78e068c0389f92fc Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Sun, 30 Jun 2024 11:52:40 +0800 Subject: [PATCH 04/24] feat(billboard): revise auction functions --- .gas-snapshot | 66 +- src/Billboard/Billboard.sol | 260 +-- src/Billboard/BillboardRegistry.sol | 48 +- src/Billboard/IBillboard.sol | 59 +- src/Billboard/IBillboardRegistry.sol | 21 +- src/test/Billboard/BillboardTest.t.sol | 2022 ++++++++++---------- src/test/Billboard/BillboardTestBase.t.sol | 128 +- 7 files changed, 1321 insertions(+), 1283 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 30eb59f..84bae87 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,54 +9,6 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testAddToWhitelist() (gas: 35114) -BillboardTest:testApproveAndTransfer() (gas: 162468) -BillboardTest:testCalculateTax() (gas: 21760) -BillboardTest:testCannnotWithdrawTaxIfSmallAmount(uint8) (runs: 256, μ: 521197, ~: 524563) -BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 490384) -BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 9037) -BillboardTest:testCannotApproveByAttacker() (gas: 130281) -BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 700985) -BillboardTest:testCannotClearAuctionOnNewBoard() (gas: 136261) -BillboardTest:testCannotMintBoardByAttacker() (gas: 13321) -BillboardTest:testCannotPlaceBidByAttacker() (gas: 246293) -BillboardTest:testCannotPlaceBidTwice(uint96) (runs: 256, μ: 748745, ~: 754899) -BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 9104) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 127438) -BillboardTest:testCannotSetBoardProprtiesByAttacker() (gas: 157292) -BillboardTest:testCannotSetIsOpenedByAttacker() (gas: 8994) -BillboardTest:testCannotSetTaxRateByAttacker() (gas: 9006) -BillboardTest:testCannotTransferByOperator() (gas: 132771) -BillboardTest:testCannotTransferToZeroAddress() (gas: 128258) -BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9128) -BillboardTest:testCannotWithBidTwice(uint96) (runs: 256, μ: 1079907, ~: 1079907) -BillboardTest:testCannotWithdrawBidIfAuctionNotCleared(uint96) (runs: 256, μ: 911089, ~: 911089) -BillboardTest:testCannotWithdrawBidIfAuctionNotEnded(uint96) (runs: 256, μ: 725782, ~: 725782) -BillboardTest:testCannotWithdrawBidIfNotFound() (gas: 428052) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 834282, ~: 834282) -BillboardTest:testCannotWithdrawTaxByAttacker() (gas: 16687) -BillboardTest:testClearAuctionIfAuctionEnded(uint96) (runs: 256, μ: 837581, ~: 837581) -BillboardTest:testClearAuctionsIfAuctionEnded() (gas: 1379962) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4728587, ~: 2077366) -BillboardTest:testGetTokenURI() (gas: 154936) -BillboardTest:testMintBoard() (gas: 225541) -BillboardTest:testMintBoardByWhitelist() (gas: 154942) -BillboardTest:testMintBoardIfOpened() (gas: 145715) -BillboardTest:testPlaceBidByWhitelist() (gas: 579179) -BillboardTest:testPlaceBidIfAuctionEnded() (gas: 1090700) -BillboardTest:testPlaceBidOnNewBoard(uint96) (runs: 256, μ: 615079, ~: 635089) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 903459, ~: 913254) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 905690, ~: 918050) -BillboardTest:testPlaceBidZeroPrice() (gas: 376947) -BillboardTest:testRemoveToWhitelist() (gas: 23188) -BillboardTest:testSafeTransferByOperator() (gas: 141193) -BillboardTest:testSetBoardProperties() (gas: 305972) -BillboardTest:testSetBoardPropertiesAfterTransfer() (gas: 335477) -BillboardTest:testSetIsOpened() (gas: 22661) -BillboardTest:testSetTaxRate() (gas: 22887) -BillboardTest:testUpgradeRegistry() (gas: 3132722) -BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1081415, ~: 1081415) -BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 597714, ~: 597714) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) @@ -74,8 +26,8 @@ DistributionTest:testCannotClaimIfAlreadyClaimed() (gas: 284835) DistributionTest:testCannotClaimIfInsufficientBalance() (gas: 394264) DistributionTest:testCannotClaimIfInvalidProof() (gas: 245236) DistributionTest:testCannotClaimIfInvalidTreeId() (gas: 243332) -DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 256, μ: 212266, ~: 212285) -DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 256, μ: 214503, ~: 214742) +DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 256, μ: 212265, ~: 212278) +DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 256, μ: 214502, ~: 214742) DistributionTest:testCannotDropIfZeroAmount() (gas: 148793) DistributionTest:testCannotDropTwiceWithSameTreeId() (gas: 307260) DistributionTest:testCannotSetAdminByAdmin() (gas: 17334) @@ -86,13 +38,13 @@ DistributionTest:testClaim() (gas: 414576) DistributionTest:testDrop() (gas: 568791) DistributionTest:testSetAdmin() (gas: 20239) DistributionTest:testSweep() (gas: 253087) -LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2021610, ~: 1310779) +LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2019505, ~: 1310779) LogbookTest:testClaim() (gas: 135608) LogbookTest:testDonate(uint96) (runs: 256, μ: 155485, ~: 156936) -LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 256, μ: 150646, ~: 140444) -LogbookTest:testFork(uint96,string) (runs: 256, μ: 450121, ~: 453928) -LogbookTest:testForkRecursively(uint8,uint96) (runs: 256, μ: 4402585, ~: 1014389) -LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 256, μ: 485550, ~: 257636) +LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 256, μ: 150402, ~: 140444) +LogbookTest:testFork(uint96,string) (runs: 256, μ: 450748, ~: 453928) +LogbookTest:testForkRecursively(uint8,uint96) (runs: 256, μ: 4613856, ~: 1014389) +LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 256, μ: 469806, ~: 257636) LogbookTest:testMulticall() (gas: 284999) LogbookTest:testPublicSale() (gas: 204837) LogbookTest:testPublish(string) (runs: 256, μ: 264065, ~: 263590) @@ -114,7 +66,7 @@ LogbookTest:testPublishZh5000() (gas: 607690) LogbookTest:testSetDescription() (gas: 140760) LogbookTest:testSetForkPrice() (gas: 153925) LogbookTest:testSetTitle() (gas: 168680) -LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 256, μ: 2005914, ~: 801064) +LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 256, μ: 2012966, ~: 636792) LogbookTest:testWithdraw() (gas: 7284400) SnapperTest:testCannotInitRegionByNotOwner() (gas: 11365) SnapperTest:testCannotReInitRegion() (gas: 14373) @@ -125,7 +77,7 @@ SnapperTest:testCannotTakeSnapshotWrongSnapshotBlock() (gas: 23899) SnapperTest:testInitRegion(uint256) (runs: 256, μ: 114408, ~: 114408) SnapperTest:testTakeSnapshot() (gas: 47831) TheSpaceTest:testBatchBid() (gas: 690308) -TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 256, μ: 368737, ~: 370338) +TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 256, μ: 368699, ~: 370338) TheSpaceTest:testBidDefaultedToken() (gas: 409416) TheSpaceTest:testBidExistingToken() (gas: 355023) TheSpaceTest:testBidNewToken() (gas: 301184) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 4b5573e..1ce31e0 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -88,7 +88,7 @@ contract Billboard is IBillboard { /// @inheritdoc IBillboard function getBoard(uint256 tokenId_) external view returns (IBillboardRegistry.Board memory board) { - return registry.boards(tokenId_); + return registry.getBoard(tokenId_); } /// @inheritdoc IBillboard @@ -108,68 +108,122 @@ contract Billboard is IBillboard { ////////////////////////////// /// @inheritdoc IBillboard - function clearAuction(uint256 tokenId_) public returns (uint256 price, uint256 tax) { - // revert if board not found + function placeBid(uint256 tokenId_, uint256 epoch_, uint256 price_) external payable isFromWhitelist(tokenId_) { + _placeBid(tokenId_, epoch_, price_, "", "", false); + } + + /// @inheritdoc IBillboard + function placeBid( + uint256 tokenId_, + uint256 epoch_, + uint256 price_, + string calldata contentURI_, + string calldata redirectURI_ + ) external payable isFromWhitelist(tokenId_) { + _placeBid(tokenId_, epoch_, price_, contentURI_, redirectURI_, true); + } + + function _placeBid( + uint256 tokenId_, + uint256 epoch_, + uint256 price_, + string memory contentURI_, + string memory redirectURI_, + bool hasURIs + ) private { IBillboardRegistry.Board memory _board = registry.getBoard(tokenId_); require(_board.creator != address(0), "Board not found"); - // revert if it's a new board - uint256 _nextAuctionId = registry.nextBoardAuctionId(tokenId_); - require(_nextAuctionId != 0, "Auction not found"); + uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval); - IBillboardRegistry.Auction memory _nextAuction = registry.getAuction(tokenId_, _nextAuctionId); + // clear auction if the auction is ended, + if (_endedAt >= block.number) { + _clearAuction(tokenId_, _board.creator, epoch_); + return; + } + // otherwise, create new bid or update bid + else { + IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, msg.sender); + + uint256 _tax = calculateTax(tokenId_, price_); + + // create new bid + if (_bid.createdAt == 0) { + // transfer bid price and tax to the registry + SafeERC20.safeTransferFrom(registry.token(), msg.sender, address(registry), price_ + _tax); + + // add new bid + registry.newBid(tokenId_, epoch_, msg.sender, price_, _tax, contentURI_, redirectURI_); + } + // update bid + else { + require(price_ > _bid.price, "Price too low"); + + // transfer diff amount to the registry + uint256 _priceDiff = price_ - _bid.price; + uint256 _taxDiff = _tax - _bid.tax; + SafeERC20.safeTransferFrom(registry.token(), msg.sender, address(registry), _priceDiff + _taxDiff); + + if (hasURIs) { + registry.setBid(tokenId_, epoch_, msg.sender, price_, _tax, contentURI_, redirectURI_, true); + } else { + registry.setBid(tokenId_, epoch_, msg.sender, price_, _tax, "", "", false); + } + } + } + } - // revert if auction is still running - require(block.number >= _nextAuction.endAt, "Auction not ended"); + /// @inheritdoc IBillboard + function clearAuction( + uint256 tokenId_, + uint256 epoch_ + ) public returns (address highestBidder, uint256 price, uint256 tax) { + // revert if board not found + IBillboardRegistry.Board memory _board = this.getBoard(tokenId_); + require(_board.creator != address(0), "Board not found"); - // reclaim ownership to board creator if no auction - address _prevOwner = registry.ownerOf(tokenId_); - if (_nextAuction.startAt == 0 && _prevOwner != _board.creator) { - registry.safeTransferByOperator(_prevOwner, _board.creator, tokenId_); - return (0, 0); - } + // revert if auction is still running + uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval); + require(block.number < _endedAt, "Auction not ended"); - return _clearAuction(tokenId_, _board.creator, _nextAuctionId); + return _clearAuction(tokenId_, _board.creator, epoch_); } /// @inheritdoc IBillboard function clearAuctions( - uint256[] calldata tokenIds_ - ) external returns (uint256[] memory prices, uint256[] memory taxes) { + uint256[] calldata tokenIds_, + uint256[] calldata epochs_ + ) external returns (address[] memory highestBidders, uint256[] memory prices, uint256[] memory taxes) { uint256 _size = tokenIds_.length; + address[] memory _highestBidders = new address[](_size); uint256[] memory _prices = new uint256[](_size); uint256[] memory _taxes = new uint256[](_size); for (uint256 i = 0; i < _size; i++) { - (_prices[i], _taxes[i]) = clearAuction(tokenIds_[i]); + (_highestBidders[i], _prices[i], _taxes[i]) = clearAuction(tokenIds_[i], epochs_[i]); } - return (_prices, _taxes); + return (_highestBidders, _prices, _taxes); } function _clearAuction( uint256 tokenId_, address boardCreator_, - uint256 nextAuctionId_ - ) private returns (uint256 price, uint256 tax) { - IBillboardRegistry.Auction memory _nextAuction = registry.getAuction(tokenId_, nextAuctionId_); + uint256 epoch_ + ) private returns (address highestBidder, uint256 price, uint256 tax) { + address _highestBidder = registry.higgestBidder(tokenId_, epoch_); + IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder); // skip if auction is already cleared - if (_nextAuction.leaseEndAt != 0) { - return (0, 0); + if (_highestBid.isWon) { + return (address(0), 0, 0); } address _prevOwner = registry.ownerOf(tokenId_); - IBillboardRegistry.Bid memory _highestBid = registry.getBid( - tokenId_, - nextAuctionId_, - _nextAuction.highestBidder - ); - if (_highestBid.price > 0) { // transfer bid price to board owner (previous tenant or creator) - registry.transferAmount(_prevOwner, _highestBid.price); + registry.transferTokenByOperator(_prevOwner, _highestBid.price); // transfer bid tax to board creator's tax treasury (uint256 _taxAccumulated, uint256 _taxWithdrawn) = registry.taxTreasury(boardCreator_); @@ -177,73 +231,15 @@ contract Billboard is IBillboard { } // transfer ownership - registry.safeTransferByOperator(_prevOwner, _nextAuction.highestBidder, tokenId_); + registry.safeTransferByOperator(_prevOwner, _highestBidder, tokenId_); // mark highest bid as won - registry.setBidWon(tokenId_, nextAuctionId_, _nextAuction.highestBidder, true); - - // set auction lease - uint64 leaseStartAt = uint64(block.number); - uint64 leaseEndAt = uint64(leaseStartAt + registry.leaseTerm()); - registry.setAuctionLease(tokenId_, nextAuctionId_, leaseStartAt, leaseEndAt); + registry.setBidWon(tokenId_, epoch_, _highestBidder, true); // emit AuctionCleared - registry.emitAuctionCleared(tokenId_, nextAuctionId_, _nextAuction.highestBidder, leaseStartAt, leaseEndAt); - - return (_highestBid.price, _highestBid.tax); - } - - /// @inheritdoc IBillboard - function placeBid(uint256 tokenId_, uint256 amount_) external payable isFromWhitelist(tokenId_) { - IBillboardRegistry.Board memory _board = registry.getBoard(tokenId_); - require(_board.creator != address(0), "Board not found"); - - uint256 _nextAuctionId = registry.nextBoardAuctionId(tokenId_); - IBillboardRegistry.Auction memory _nextAuction = registry.getAuction(tokenId_, _nextAuctionId); - - // if it's a new board without next auction, - // create new auction and new bid first, - // then clear auction and transfer ownership to the bidder immediately. - if (_nextAuction.startAt == 0) { - uint256 _auctionId = _newAuctionAndBid(tokenId_, amount_, uint64(block.number)); - _clearAuction(tokenId_, _board.creator, _auctionId); - return; - } - - // if next auction is ended, - // clear auction first, - // then create new auction and new bid - if (block.number >= _nextAuction.endAt) { - _clearAuction(tokenId_, _board.creator, _nextAuctionId); - _newAuctionAndBid(tokenId_, amount_, uint64(block.number + registry.leaseTerm())); - return; - } - // if next auction is not ended, - // push new bid to next auction - else { - require(registry.getBid(tokenId_, _nextAuctionId, msg.sender).placedAt == 0, "Bid already placed"); - - uint256 _tax = calculateTax(amount_); - registry.newBid(tokenId_, _nextAuctionId, msg.sender, amount_, _tax); - - _lockBidPriceAndTax(amount_ + _tax); - } - } - - function _newAuctionAndBid(uint256 tokenId_, uint256 amount_, uint64 endAt_) private returns (uint256 auctionId) { - uint64 _startAt = uint64(block.number); - uint256 _tax = calculateTax(amount_); + registry.emitAuctionCleared(tokenId_, epoch_, _highestBidder); - auctionId = registry.newAuction(tokenId_, _startAt, endAt_); - - registry.newBid(tokenId_, auctionId, msg.sender, amount_, _tax); - - _lockBidPriceAndTax(amount_ + _tax); - } - - function _lockBidPriceAndTax(uint256 amount_) private { - // transfer bid price and tax to the registry - SafeERC20.safeTransferFrom(registry.token(), msg.sender, address(registry), amount_); + return (_highestBidder, _highestBid.price, _highestBid.tax); } /// @inheritdoc IBillboard @@ -252,7 +248,7 @@ contract Billboard is IBillboard { uint256 epoch_, address bidder_ ) external view returns (IBillboardRegistry.Bid memory bid) { - return registry.auctionBids(tokenId_, epoch_, bidder_); + return registry.getBid(tokenId_, epoch_, bidder_); } /// @inheritdoc IBillboard @@ -278,20 +274,62 @@ contract Billboard is IBillboard { IBillboardRegistry.Bid[] memory _bids = new IBillboardRegistry.Bid[](_size); for (uint256 i = 0; i < _size; i++) { - address _bidder = registry.auctionBidders(tokenId_, epoch_, offset_ + i); - _bids[i] = registry.auctionBids(tokenId_, epoch_, bidder_); + address _bidder = registry.bidders(tokenId_, epoch_, offset_ + i); + _bids[i] = registry.getBid(tokenId_, epoch_, _bidder); } return (_total, limit_, offset_, _bids); } + /// @inheritdoc IBillboard + function withdrawBid(uint256 tokenId_, uint256 epoch_) external { + // revert if board not found + IBillboardRegistry.Board memory _board = this.getBoard(tokenId_); + require(_board.creator != address(0), "Board not found"); + + // revert if auction is not ended + uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval); + require(block.number < _endedAt, "Auction not ended"); + + // revert if auction is not cleared + address _highestBidder = registry.higgestBidder(tokenId_, epoch_); + IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder); + require(!_highestBid.isWon, "Auction not cleared"); + + IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, msg.sender); + uint256 amount = _bid.price + _bid.tax; + + require(_bid.createdAt != 0, "Bid not found"); + require(!_bid.isWithdrawn, "Bid already withdrawn"); + require(!_bid.isWon, "Bid already won"); + require(amount > 0, "Zero amount"); + + // set bid.isWithdrawn to true first to prevent reentrancy + registry.setBidWithdrawn(tokenId_, epoch_, msg.sender, true); + + // transfer bid price and tax back to the bidder + registry.transferTokenByOperator(msg.sender, amount); + } + + /// @inheritdoc IBillboard + function getEpochFromBlock(uint256 block_, uint256 epochInterval_) public pure returns (uint256 epoch) { + // TODO: check overflow and underflow + return block_ / epochInterval_; + } + + /// @inheritdoc IBillboard + function getBlockFromEpoch(uint256 epoch_, uint256 epochInterval_) public pure returns (uint256 blockNumber) { + // TODO: check overflow and underflow + return epoch_ * epochInterval_; + } + ////////////////////////////// /// Tax & Withdraw ////////////////////////////// /// @inheritdoc IBillboard function getTaxRate(uint256 tokenId_) external view returns (uint256 taxRate) { - return registry.boards(tokenId_).taxRate; + return registry.getBoard(tokenId_).taxRate; } function calculateTax(uint256 tokenId_, uint256 amount_) public view returns (uint256 tax) { @@ -311,35 +349,11 @@ contract Billboard is IBillboard { registry.setTaxTreasury(msg.sender, _taxAccumulated, _taxAccumulated); // transfer tax to the owner - registry.transferAmount(msg.sender, amount); + registry.transferTokenByOperator(msg.sender, amount); // emit TaxWithdrawn registry.emitTaxWithdrawn(msg.sender, amount); return amount; } - - /// @inheritdoc IBillboard - function withdrawBid(uint256 tokenId_, uint256 auctionId_) external { - // revert if auction is still running - IBillboardRegistry.Auction memory _auction = registry.getAuction(tokenId_, auctionId_); - require(block.number >= _auction.endAt, "Auction not ended"); - - // revert if auction is not cleared - require(_auction.leaseEndAt != 0, "Auction not cleared"); - - IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, auctionId_, msg.sender); - uint256 amount = _bid.price + _bid.tax; - - require(_bid.placedAt != 0, "Bid not found"); - require(!_bid.isWithdrawn, "Bid already withdrawn"); - require(!_bid.isWon, "Bid already won"); - require(amount > 0, "Zero amount"); - - // set bid.isWithdrawn to true first to prevent reentrancy - registry.setBidWithdrawn(tokenId_, auctionId_, msg.sender, true); - - // transfer bid price and tax back to the bidder - registry.transferAmount(msg.sender, amount); - } } diff --git a/src/Billboard/BillboardRegistry.sol b/src/Billboard/BillboardRegistry.sol index 2f84a4d..ebd73be 100644 --- a/src/Billboard/BillboardRegistry.sol +++ b/src/Billboard/BillboardRegistry.sol @@ -20,13 +20,13 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { mapping(uint256 => Board) public boards; // tokenId => epoch => bidder - mapping(uint256 => mapping(uint256 => address)) public auctionHiggestBidder; + mapping(uint256 => mapping(uint256 => address)) public higgestBidder; // tokenId => epoch => bidders - mapping(uint256 => mapping(uint256 => address[])) public auctionBidders; + mapping(uint256 => mapping(uint256 => address[])) public bidders; // tokenId => epoch => bidder => Bid - mapping(uint256 => mapping(uint256 => mapping(address => Bid))) public auctionBids; + mapping(uint256 => mapping(uint256 => mapping(address => Bid))) public bids; // board creator => TaxTreasury mapping(address => TaxTreasury) public taxTreasury; @@ -64,6 +64,12 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { ////////////////////////////// /// Board ////////////////////////////// + + /// @inheritdoc IBillboardRegistry + function getBoard(uint256 tokenId_) external view returns (Board memory board) { + board = boards[tokenId_]; + } + /// @inheritdoc IBillboardRegistry function newBoard( address to_, @@ -107,9 +113,15 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { ////////////////////////////// /// Auction & Bid ////////////////////////////// + + /// @inheritdoc IBillboardRegistry + function getBid(uint256 tokenId_, uint256 auctionId_, address bidder_) external view returns (Bid memory bid) { + bid = bids[tokenId_][auctionId_][bidder_]; + } + /// @inheritdoc IBillboardRegistry function getBidCount(uint256 tokenId_, uint256 epoch_) external view returns (uint256 count) { - count = auctionBidders[tokenId_][epoch_].length; + count = bidders[tokenId_][epoch_].length; } /// @inheritdoc IBillboardRegistry @@ -134,10 +146,10 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { }); // add to auction bids - auctionBids[tokenId_][epoch_][bidder_] = _bid; + bids[tokenId_][epoch_][bidder_] = _bid; // add to auction bidders if new bid - auctionBidders[tokenId_][epoch_].push(bidder_); + bidders[tokenId_][epoch_].push(bidder_); _setHiggestBidder(tokenId_, epoch_, price_, bidder_); @@ -152,17 +164,21 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { uint256 price_, uint256 tax_, string calldata contentURI_, - string calldata redirectURI_ + string calldata redirectURI_, + bool hasURIs ) external isFromOperator { - Bid memory _bid = auctionBids[tokenId_][epoch_][bidder_]; + Bid memory _bid = bids[tokenId_][epoch_][bidder_]; require(_bid.createdAt != 0, "Bid not found"); _bid.price = price_; _bid.tax = tax_; - _bid.contentURI = contentURI_; - _bid.redirectURI = redirectURI_; _bid.updatedAt = block.number; + if (hasURIs) { + _bid.contentURI = contentURI_; + _bid.redirectURI = redirectURI_; + } + _setHiggestBidder(tokenId_, epoch_, price_, bidder_); emit BidUpdated(tokenId_, epoch_, bidder_, price_, tax_, contentURI_, redirectURI_); @@ -173,10 +189,10 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { // Note: for same price, the first bidder will always be // the highest bidder since the block.number is always greater. function _setHiggestBidder(uint256 tokenId_, uint256 epoch_, uint256 price_, address bidder_) internal { - address highestBidder = auctionHiggestBidder[tokenId_][epoch_]; - Bid memory highestBid = auctionBids[tokenId_][epoch_][highestBidder]; + address highestBidder = higgestBidder[tokenId_][epoch_]; + Bid memory highestBid = bids[tokenId_][epoch_][highestBidder]; if (highestBidder == address(0) || price_ > highestBid.price) { - auctionHiggestBidder[tokenId_][epoch_] = bidder_; + higgestBidder[tokenId_][epoch_] = bidder_; } } @@ -188,7 +204,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { string calldata contentURI_, string calldata redirectURI_ ) external isFromOperator { - Bid memory _bid = auctionBids[tokenId_][epoch_][bidder_]; + Bid memory _bid = bids[tokenId_][epoch_][bidder_]; _bid.contentURI = contentURI_; _bid.redirectURI = redirectURI_; @@ -198,7 +214,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { /// @inheritdoc IBillboardRegistry function setBidWon(uint256 tokenId_, uint256 epoch_, address bidder_, bool isWon_) external isFromOperator { - auctionBids[tokenId_][epoch_][bidder_].isWon = isWon_; + bids[tokenId_][epoch_][bidder_].isWon = isWon_; emit BidWon(tokenId_, epoch_, bidder_); } @@ -209,7 +225,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { address bidder_, bool isWithdrawn_ ) external isFromOperator { - auctionBids[tokenId_][epoch_][bidder_].isWithdrawn = isWithdrawn_; + bids[tokenId_][epoch_][bidder_].isWithdrawn = isWithdrawn_; emit BidWithdrawn(tokenId_, epoch_, msg.sender); } diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index edf1575..5ed81f3 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -130,16 +130,33 @@ interface IBillboard { function clearAuctions( uint256[] calldata tokenIds_, uint256[] calldata epochs_ - ) external returns (address[] memory highestBidders, uint256[] memory prices, uint256[] memory taxes); + ) external returns (address[] calldata highestBidders, uint256[] calldata prices, uint256[] calldata taxes); /** * @notice Place bid on a board auction. * * @param tokenId_ Token ID. * @param epoch_ Epoch. - * @param amount_ Amount of a bid. + * @param price_ Amount of a bid. + */ + function placeBid(uint256 tokenId_, uint256 epoch_, uint256 price_) external payable; + + /** + * @notice Place bid on a board auction. + * + * @param tokenId_ Token ID. + * @param epoch_ Epoch. + * @param price_ Amount of a bid. + * @param contentURI_ Content URI of a bid. + * @param redirectURI_ Redirect URI of a bid. */ - function placeBid(uint256 tokenId_, uint256 epoch_, uint256 amount_) external payable; + function placeBid( + uint256 tokenId_, + uint256 epoch_, + uint256 price_, + string calldata contentURI_, + string calldata redirectURI_ + ) external payable; /** * @notice Get bid of a board auction. @@ -176,6 +193,34 @@ interface IBillboard { uint256 offset_ ) external view returns (uint256 total, uint256 limit, uint256 offset, IBillboardRegistry.Bid[] memory bids); + /** + * @notice Withdraw bid that were not won by auction id; + * + * @param tokenId_ Token ID. + * @param epoch_ Epoch. + */ + function withdrawBid(uint256 tokenId_, uint256 epoch_) external; + + /** + * @notice Calculate epoch from block number. + * + * @param block_ Block number. + * @param epochInterval_ Epoch interval. + * + * @return epoch Epoch. + */ + function getEpochFromBlock(uint256 block_, uint256 epochInterval_) external pure returns (uint256 epoch); + + /** + * @notice Calculate block number from epoch. + * + * @param epoch_ Epoch. + * @param epochInterval_ Epoch interval. + * + * @return blockNumber Block number. + */ + function getBlockFromEpoch(uint256 epoch_, uint256 epochInterval_) external pure returns (uint256 blockNumber); + ////////////////////////////// /// Tax & Withdraw ////////////////////////////// @@ -204,12 +249,4 @@ interface IBillboard { * */ function withdrawTax() external returns (uint256 tax); - - /** - * @notice Withdraw bid that were not won by auction id; - * - * @param tokenId_ Token ID. - * @param epoch_ Epoch. - */ - function withdrawBid(uint256 tokenId_, uint256 epoch_) external; } diff --git a/src/Billboard/IBillboardRegistry.sol b/src/Billboard/IBillboardRegistry.sol index ea72071..4237a3f 100644 --- a/src/Billboard/IBillboardRegistry.sol +++ b/src/Billboard/IBillboardRegistry.sol @@ -139,6 +139,13 @@ interface IBillboardRegistry is IERC721 { /// Board ////////////////////////////// + /** + * @notice Get a board + * + * @param tokenId_ Token ID of a board. + */ + function getBoard(uint256 tokenId_) external view returns (Board memory board); + /** * @notice Mint a new board (NFT). * @@ -170,6 +177,16 @@ interface IBillboardRegistry is IERC721 { ////////////////////////////// /// Auction & Bid ////////////////////////////// + + /** + * @notice Get a bid of an auction + * + * @param tokenId_ Token ID of a board. + * @param epoch_ Epoch of an auction. + * @param bidder_ Bidder of an auction. + */ + function getBid(uint256 tokenId_, uint256 epoch_, address bidder_) external view returns (Bid memory bid); + /** * @notice Get bid count of an auction * @@ -211,6 +228,7 @@ interface IBillboardRegistry is IERC721 { * @param tax_ Tax of a bid. * @param contentURI_ Content URI of a bid. * @param redirectURI_ Redirect URI of a bid. + * @param hasURIs_ Whether `contentURI_` or `redirectURI_` is provided. */ function setBid( uint256 tokenId_, @@ -219,7 +237,8 @@ interface IBillboardRegistry is IERC721 { uint256 price_, uint256 tax_, string calldata contentURI_, - string calldata redirectURI_ + string calldata redirectURI_, + bool hasURIs_ ) external; /** diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 9f7a114..4c17315 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -1,1060 +1,1060 @@ //SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import "./BillboardTestBase.t.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -contract BillboardTest is BillboardTestBase { - ////////////////////////////// - /// Upgradability - ////////////////////////////// - - function testUpgradeRegistry() public { - vm.startPrank(ADMIN); - - // deploy new operator - Billboard newOperator = new Billboard( - address(usdt), - payable(registry), - ADMIN, - TAX_RATE, - LEASE_TERM, - "Billboard2", - "BLBD2" - ); - assertEq(newOperator.admin(), ADMIN); - assertEq(registry.name(), "Billboard"); // registry is not changed - assertEq(registry.symbol(), "BLBD"); // registry is not changed - - // upgrade registry's operator - assertEq(registry.operator(), address(operator)); - operator.setRegistryOperator(address(newOperator)); - assertEq(registry.operator(), address(newOperator)); - } - - function testCannotUpgradeRegistryByAttacker() public { - vm.startPrank(ATTACKER); - - vm.expectRevert("Admin"); - operator.setRegistryOperator(FAKE_CONTRACT); - } - - ////////////////////////////// - /// Access control - ////////////////////////////// - - function testSetIsOpened() public { - vm.startPrank(ADMIN); - - operator.setIsOpened(true); - assertEq(operator.isOpened(), true); - - operator.setIsOpened(false); - assertEq(operator.isOpened(), false); - } - - function testCannotSetIsOpenedByAttacker() public { - vm.startPrank(ATTACKER); - - vm.expectRevert("Admin"); - operator.setIsOpened(true); - } - - function testAddToWhitelist() public { - vm.startPrank(ADMIN); - - operator.addToWhitelist(USER_A); - assertEq(operator.whitelist(USER_A), true); - assertEq(operator.whitelist(USER_B), false); - } - - function testCannotAddToWhitelistByAttacker() public { - vm.startPrank(ATTACKER); - - vm.expectRevert("Admin"); - operator.addToWhitelist(USER_A); - } - - function testRemoveToWhitelist() public { - vm.startPrank(ADMIN); - - operator.addToWhitelist(USER_A); - assertEq(operator.whitelist(USER_A), true); - - operator.removeFromWhitelist(USER_A); - assertEq(operator.whitelist(USER_A), false); - } - - function testCannotRemoveToWhitelistByAttacker() public { - vm.startPrank(ATTACKER); - - vm.expectRevert("Admin"); - operator.removeFromWhitelist(USER_B); - } - - ////////////////////////////// - /// Board - ////////////////////////////// - - function testMintBoard() public { - vm.startPrank(ADMIN); - - // mint - vm.expectEmit(true, true, true, true); - emit IERC721.Transfer(address(0), ADMIN, 1); - operator.mintBoard(ADMIN); - assertEq(registry.balanceOf(ADMIN), 1); - - // ownership - assertEq(registry.ownerOf(1), ADMIN); - - // get board & check data - IBillboardRegistry.Board memory board = operator.getBoard(1); - assertEq(board.creator, ADMIN); - assertEq(board.name, ""); - assertEq(board.description, ""); - assertEq(board.location, ""); - assertEq(board.contentURI, ""); - assertEq(board.redirectURI, ""); - - // mint again for checking id generator - vm.expectEmit(true, true, true, true); - emit IERC721.Transfer(address(0), ADMIN, 2); - operator.mintBoard(ADMIN); - assertEq(registry.balanceOf(ADMIN), 2); - board = operator.getBoard(2); - assertEq(board.creator, ADMIN); - } +// import "./BillboardTestBase.t.sol"; +// import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +// contract BillboardTest is BillboardTestBase { +// ////////////////////////////// +// /// Upgradability +// ////////////////////////////// + +// function testUpgradeRegistry() public { +// vm.startPrank(ADMIN); + +// // deploy new operator +// Billboard newOperator = new Billboard( +// address(usdt), +// payable(registry), +// ADMIN, +// TAX_RATE, +// LEASE_TERM, +// "Billboard2", +// "BLBD2" +// ); +// assertEq(newOperator.admin(), ADMIN); +// assertEq(registry.name(), "Billboard"); // registry is not changed +// assertEq(registry.symbol(), "BLBD"); // registry is not changed + +// // upgrade registry's operator +// assertEq(registry.operator(), address(operator)); +// operator.setRegistryOperator(address(newOperator)); +// assertEq(registry.operator(), address(newOperator)); +// } + +// function testCannotUpgradeRegistryByAttacker() public { +// vm.startPrank(ATTACKER); + +// vm.expectRevert("Admin"); +// operator.setRegistryOperator(FAKE_CONTRACT); +// } + +// ////////////////////////////// +// /// Access control +// ////////////////////////////// + +// function testSetIsOpened() public { +// vm.startPrank(ADMIN); + +// operator.setIsOpened(true); +// assertEq(operator.isOpened(), true); + +// operator.setIsOpened(false); +// assertEq(operator.isOpened(), false); +// } + +// function testCannotSetIsOpenedByAttacker() public { +// vm.startPrank(ATTACKER); + +// vm.expectRevert("Admin"); +// operator.setIsOpened(true); +// } + +// function testAddToWhitelist() public { +// vm.startPrank(ADMIN); + +// operator.addToWhitelist(USER_A); +// assertEq(operator.whitelist(USER_A), true); +// assertEq(operator.whitelist(USER_B), false); +// } + +// function testCannotAddToWhitelistByAttacker() public { +// vm.startPrank(ATTACKER); + +// vm.expectRevert("Admin"); +// operator.addToWhitelist(USER_A); +// } + +// function testRemoveToWhitelist() public { +// vm.startPrank(ADMIN); + +// operator.addToWhitelist(USER_A); +// assertEq(operator.whitelist(USER_A), true); + +// operator.removeFromWhitelist(USER_A); +// assertEq(operator.whitelist(USER_A), false); +// } + +// function testCannotRemoveToWhitelistByAttacker() public { +// vm.startPrank(ATTACKER); + +// vm.expectRevert("Admin"); +// operator.removeFromWhitelist(USER_B); +// } + +// ////////////////////////////// +// /// Board +// ////////////////////////////// + +// function testMintBoard() public { +// vm.startPrank(ADMIN); + +// // mint +// vm.expectEmit(true, true, true, true); +// emit IERC721.Transfer(address(0), ADMIN, 1); +// operator.mintBoard(ADMIN); +// assertEq(registry.balanceOf(ADMIN), 1); + +// // ownership +// assertEq(registry.ownerOf(1), ADMIN); + +// // get board & check data +// IBillboardRegistry.Board memory board = operator.getBoard(1); +// assertEq(board.creator, ADMIN); +// assertEq(board.name, ""); +// assertEq(board.description, ""); +// assertEq(board.location, ""); +// assertEq(board.contentURI, ""); +// assertEq(board.redirectURI, ""); + +// // mint again for checking id generator +// vm.expectEmit(true, true, true, true); +// emit IERC721.Transfer(address(0), ADMIN, 2); +// operator.mintBoard(ADMIN); +// assertEq(registry.balanceOf(ADMIN), 2); +// board = operator.getBoard(2); +// assertEq(board.creator, ADMIN); +// } - function testMintBoardIfOpened() public { - vm.startPrank(ADMIN); - operator.setIsOpened(true); +// function testMintBoardIfOpened() public { +// vm.startPrank(ADMIN); +// operator.setIsOpened(true); - vm.startPrank(USER_A); - operator.mintBoard(USER_A); - assertEq(registry.balanceOf(USER_A), 1); - } +// vm.startPrank(USER_A); +// operator.mintBoard(USER_A); +// assertEq(registry.balanceOf(USER_A), 1); +// } - function testMintBoardByWhitelist() public { - vm.prank(USER_A); - vm.expectRevert("Whitelist"); - operator.mintBoard(USER_A); +// function testMintBoardByWhitelist() public { +// vm.prank(USER_A); +// vm.expectRevert("Whitelist"); +// operator.mintBoard(USER_A); - vm.prank(ADMIN); - operator.addToWhitelist(USER_A); +// vm.prank(ADMIN); +// operator.addToWhitelist(USER_A); - vm.prank(USER_A); - operator.mintBoard(USER_A); - assertEq(registry.balanceOf(USER_A), 1); - } +// vm.prank(USER_A); +// operator.mintBoard(USER_A); +// assertEq(registry.balanceOf(USER_A), 1); +// } - function testCannotMintBoardByAttacker() public { - vm.startPrank(ATTACKER); +// function testCannotMintBoardByAttacker() public { +// vm.startPrank(ATTACKER); - vm.expectRevert("Whitelist"); - operator.mintBoard(ATTACKER); - } +// vm.expectRevert("Whitelist"); +// operator.mintBoard(ATTACKER); +// } - function testSetBoardProperties() public { - uint256 _tokenId = _mintBoard(); +// function testSetBoardProperties() public { +// uint256 _tokenId = _mintBoard(); - vm.startPrank(ADMIN); +// vm.startPrank(ADMIN); - vm.expectEmit(true, true, false, false); - emit IBillboardRegistry.BoardNameUpdated(_tokenId, "name"); - operator.setBoardName(_tokenId, "name"); +// vm.expectEmit(true, true, false, false); +// emit IBillboardRegistry.BoardNameUpdated(_tokenId, "name"); +// operator.setBoardName(_tokenId, "name"); - vm.expectEmit(true, true, false, false); - emit IBillboardRegistry.BoardDescriptionUpdated(_tokenId, "description"); - operator.setBoardDescription(_tokenId, "description"); +// vm.expectEmit(true, true, false, false); +// emit IBillboardRegistry.BoardDescriptionUpdated(_tokenId, "description"); +// operator.setBoardDescription(_tokenId, "description"); - vm.expectEmit(true, true, false, false); - emit IBillboardRegistry.BoardLocationUpdated(_tokenId, "location"); - operator.setBoardLocation(_tokenId, "location"); +// vm.expectEmit(true, true, false, false); +// emit IBillboardRegistry.BoardLocationUpdated(_tokenId, "location"); +// operator.setBoardLocation(_tokenId, "location"); - vm.expectEmit(true, true, false, false); - emit IBillboardRegistry.BoardContentURIUpdated(_tokenId, "uri"); - operator.setBoardContentURI(_tokenId, "uri"); +// vm.expectEmit(true, true, false, false); +// emit IBillboardRegistry.BoardContentURIUpdated(_tokenId, "uri"); +// operator.setBoardContentURI(_tokenId, "uri"); - vm.expectEmit(true, true, false, false); - emit IBillboardRegistry.BoardRedirectURIUpdated(_tokenId, "redirect URI"); - operator.setBoardRedirectURI(_tokenId, "redirect URI"); +// vm.expectEmit(true, true, false, false); +// emit IBillboardRegistry.BoardRedirectURIUpdated(_tokenId, "redirect URI"); +// operator.setBoardRedirectURI(_tokenId, "redirect URI"); - IBillboardRegistry.Board memory board = operator.getBoard(1); - assertEq(board.name, "name"); - assertEq(board.description, "description"); - assertEq(board.location, "location"); - assertEq(board.contentURI, "uri"); - assertEq(board.redirectURI, "redirect URI"); - } +// IBillboardRegistry.Board memory board = operator.getBoard(1); +// assertEq(board.name, "name"); +// assertEq(board.description, "description"); +// assertEq(board.location, "location"); +// assertEq(board.contentURI, "uri"); +// assertEq(board.redirectURI, "redirect URI"); +// } - function testCannotSetBoardProprtiesByAttacker() public { - uint256 _tokenId = _mintBoard(); +// function testCannotSetBoardProprtiesByAttacker() public { +// uint256 _tokenId = _mintBoard(); - vm.startPrank(ATTACKER); +// vm.startPrank(ATTACKER); - vm.expectRevert("Creator"); - operator.setBoardName(_tokenId, "name"); +// vm.expectRevert("Creator"); +// operator.setBoardName(_tokenId, "name"); - vm.expectRevert("Creator"); - operator.setBoardDescription(_tokenId, "description"); +// vm.expectRevert("Creator"); +// operator.setBoardDescription(_tokenId, "description"); - vm.expectRevert("Creator"); - operator.setBoardLocation(_tokenId, "location"); +// vm.expectRevert("Creator"); +// operator.setBoardLocation(_tokenId, "location"); - vm.expectRevert("Tenant"); - operator.setBoardContentURI(_tokenId, "uri"); +// vm.expectRevert("Tenant"); +// operator.setBoardContentURI(_tokenId, "uri"); - vm.expectRevert("Tenant"); - operator.setBoardRedirectURI(_tokenId, "redirect URI"); - } +// vm.expectRevert("Tenant"); +// operator.setBoardRedirectURI(_tokenId, "redirect URI"); +// } - function testGetTokenURI() public { - uint256 _tokenId = _mintBoard(); +// function testGetTokenURI() public { +// uint256 _tokenId = _mintBoard(); - vm.startPrank(ADMIN); +// vm.startPrank(ADMIN); - operator.setBoardContentURI(_tokenId, "new uri"); - assertEq(registry.tokenURI(_tokenId), "new uri"); - } +// operator.setBoardContentURI(_tokenId, "new uri"); +// assertEq(registry.tokenURI(_tokenId), "new uri"); +// } - function testSetBoardPropertiesAfterTransfer() public { - // mint - uint256 _tokenId = _mintBoard(); +// function testSetBoardPropertiesAfterTransfer() public { +// // mint +// uint256 _tokenId = _mintBoard(); - // transfer - vm.startPrank(ADMIN); - registry.transferFrom(ADMIN, USER_A, _tokenId); +// // transfer +// vm.startPrank(ADMIN); +// registry.transferFrom(ADMIN, USER_A, _tokenId); - IBillboardRegistry.Board memory board = operator.getBoard(_tokenId); - assertEq(board.creator, ADMIN); - assertEq(registry.balanceOf(ADMIN), 0); - assertEq(registry.ownerOf(_tokenId), USER_A); +// IBillboardRegistry.Board memory board = operator.getBoard(_tokenId); +// assertEq(board.creator, ADMIN); +// assertEq(registry.balanceOf(ADMIN), 0); +// assertEq(registry.ownerOf(_tokenId), USER_A); - // set board properties - vm.stopPrank(); - vm.startPrank(USER_A); +// // set board properties +// vm.stopPrank(); +// vm.startPrank(USER_A); - vm.expectRevert("Creator"); - operator.setBoardName(_tokenId, "name by a"); +// vm.expectRevert("Creator"); +// operator.setBoardName(_tokenId, "name by a"); - vm.expectRevert("Creator"); - operator.setBoardDescription(_tokenId, "description by a"); +// vm.expectRevert("Creator"); +// operator.setBoardDescription(_tokenId, "description by a"); - vm.expectRevert("Creator"); - operator.setBoardLocation(_tokenId, "location by a"); +// vm.expectRevert("Creator"); +// operator.setBoardLocation(_tokenId, "location by a"); - operator.setBoardContentURI(_tokenId, "uri by a"); - operator.setBoardRedirectURI(_tokenId, "redirect URI by a"); +// operator.setBoardContentURI(_tokenId, "uri by a"); +// operator.setBoardRedirectURI(_tokenId, "redirect URI by a"); - board = operator.getBoard(_tokenId); - assertEq(board.name, ""); - assertEq(board.description, ""); - assertEq(board.location, ""); - assertEq(board.contentURI, "uri by a"); - assertEq(board.redirectURI, "redirect URI by a"); +// board = operator.getBoard(_tokenId); +// assertEq(board.name, ""); +// assertEq(board.description, ""); +// assertEq(board.location, ""); +// assertEq(board.contentURI, "uri by a"); +// assertEq(board.redirectURI, "redirect URI by a"); - // transfer board from user_a to user_b - registry.safeTransferFrom(USER_A, USER_B, 1); - board = operator.getBoard(_tokenId); - assertEq(board.creator, ADMIN); - assertEq(registry.ownerOf(_tokenId), USER_B); +// // transfer board from user_a to user_b +// registry.safeTransferFrom(USER_A, USER_B, 1); +// board = operator.getBoard(_tokenId); +// assertEq(board.creator, ADMIN); +// assertEq(registry.ownerOf(_tokenId), USER_B); - vm.stopPrank(); - vm.startPrank(USER_B); +// vm.stopPrank(); +// vm.startPrank(USER_B); - vm.expectRevert("Creator"); - operator.setBoardName(_tokenId, "name by b"); +// vm.expectRevert("Creator"); +// operator.setBoardName(_tokenId, "name by b"); - vm.expectRevert("Creator"); - operator.setBoardDescription(_tokenId, "description by b"); +// vm.expectRevert("Creator"); +// operator.setBoardDescription(_tokenId, "description by b"); - vm.expectRevert("Creator"); - operator.setBoardLocation(_tokenId, "location by b"); +// vm.expectRevert("Creator"); +// operator.setBoardLocation(_tokenId, "location by b"); - operator.setBoardContentURI(_tokenId, "uri by b"); - operator.setBoardRedirectURI(_tokenId, "redirect URI by b"); +// operator.setBoardContentURI(_tokenId, "uri by b"); +// operator.setBoardRedirectURI(_tokenId, "redirect URI by b"); - board = operator.getBoard(_tokenId); - assertEq(board.name, ""); - assertEq(board.description, ""); - assertEq(board.location, ""); - assertEq(board.contentURI, "uri by b"); - assertEq(board.redirectURI, "redirect URI by b"); - } +// board = operator.getBoard(_tokenId); +// assertEq(board.name, ""); +// assertEq(board.description, ""); +// assertEq(board.location, ""); +// assertEq(board.contentURI, "uri by b"); +// assertEq(board.redirectURI, "redirect URI by b"); +// } - function testCannotTransferToZeroAddress() public { - uint256 _tokenId = _mintBoard(); +// function testCannotTransferToZeroAddress() public { +// uint256 _tokenId = _mintBoard(); - vm.startPrank(ADMIN); +// vm.startPrank(ADMIN); - vm.expectRevert("ERC721: transfer to the zero address"); - registry.transferFrom(ADMIN, ZERO_ADDRESS, _tokenId); - } - - function testCannotTransferByOperator() public { - uint256 _tokenId = _mintBoard(); - - vm.startPrank(address(operator)); - - vm.expectRevert("ERC721: caller is not token owner or approved"); - registry.transferFrom(USER_B, USER_C, _tokenId); - } - - function testSafeTransferByOperator() public { - uint256 _tokenId = _mintBoard(); - - vm.expectEmit(true, true, true, true); - emit IERC721.Transfer(ADMIN, USER_A, _tokenId); - - vm.startPrank(address(operator)); - registry.safeTransferByOperator(ADMIN, USER_A, _tokenId); - assertEq(registry.ownerOf(_tokenId), USER_A); - } - - function testCannotSafeTransferByAttacker() public { - uint256 _tokenId = _mintBoard(); - - vm.startPrank(ATTACKER); - - vm.expectRevert("Operator"); - registry.safeTransferByOperator(ADMIN, ATTACKER, _tokenId); - } - - function testApproveAndTransfer() public { - uint256 _tokenId = _mintBoard(); - - vm.expectEmit(true, true, true, true); - emit IERC721.Approval(ADMIN, USER_A, _tokenId); - vm.prank(ADMIN); - registry.approve(USER_A, _tokenId); - assertEq(registry.getApproved(_tokenId), USER_A); - - vm.expectEmit(true, true, true, true); - emit IERC721.Transfer(ADMIN, USER_A, _tokenId); - vm.prank(USER_A); - registry.transferFrom(ADMIN, USER_A, _tokenId); - - IBillboardRegistry.Board memory board = operator.getBoard(_tokenId); - assertEq(board.creator, ADMIN); - assertEq(registry.ownerOf(_tokenId), USER_A); - } - - function testCannotApproveByAttacker() public { - uint256 _tokenId = _mintBoard(); - - vm.stopPrank(); - vm.startPrank(ATTACKER); - vm.expectRevert("ERC721: approve caller is not token owner or approved for all"); - registry.approve(USER_A, _tokenId); - } - - ////////////////////////////// - /// Auction - ////////////////////////////// - - function testPlaceBidOnNewBoard(uint96 _amount) public { - vm.prank(ADMIN); - operator.addToWhitelist(USER_A); - - vm.expectEmit(true, false, false, false); - emit IERC721.Transfer(address(0), ADMIN, 1); - - uint256 _tokenId = _mintBoard(); - uint256 _tax = operator.calculateTax(_amount); - uint256 _overpaid = 0.1 ether; - uint256 _total = _amount + _tax; - deal(address(usdt), USER_A, _total + _overpaid); - - uint256 _prevNextActionId = registry.nextBoardAuctionId(_tokenId); - uint256 _prevCreatorBalance = usdt.balanceOf(ADMIN); - uint256 _prevBidderBalance = usdt.balanceOf(USER_A); - uint256 _prevOperatorBalance = usdt.balanceOf(address(operator)); - uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); - - vm.expectEmit(true, true, true, true); - emit IBillboardRegistry.AuctionCreated( - _tokenId, - _prevNextActionId + 1, - uint64(block.number), - uint64(block.number) - ); - vm.expectEmit(true, true, true, true); - emit IBillboardRegistry.BidCreated(_tokenId, _prevNextActionId + 1, USER_A, _amount, _tax); - vm.expectEmit(true, true, true, true); - emit IBillboardRegistry.BidWon(_tokenId, _prevNextActionId + 1, USER_A); - vm.expectEmit(true, true, true, true); - emit IBillboardRegistry.AuctionCleared( - _tokenId, - _prevNextActionId + 1, - USER_A, - uint64(block.number), - uint64(block.number + registry.leaseTerm()) - ); - - vm.prank(USER_A); - operator.placeBid(_tokenId, _amount); - - // check balances - assertEq(usdt.balanceOf(ADMIN), _prevCreatorBalance + _amount); - assertEq(usdt.balanceOf(USER_A), _prevBidderBalance - _total); - assertEq(usdt.balanceOf(address(operator)), _prevOperatorBalance); - assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance + _tax); - - // check auction - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_prevNextActionId, 0); - assertEq(_nextAuctionId, _prevNextActionId + 1); - assertEq(_auction.startAt, block.number); - assertEq(_auction.endAt, block.number); - assertEq(_auction.leaseStartAt, block.number); - assertEq(_auction.leaseEndAt, block.number + registry.leaseTerm()); - assertEq(_auction.highestBidder, USER_A); - - // check bid - IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); - assertEq(_bid.price, _amount); - assertEq(_bid.tax, _tax); - assertEq(_bid.placedAt, block.number); - assertEq(_bid.isWon, true); - assertEq(_bid.isWithdrawn, false); - } - - function testPlaceBidWithSamePrices(uint96 _amount) public { - (uint256 _tokenId, uint256 _prevNextAuctionId) = _mintBoardAndPlaceBid(); - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - // new auction and new bid with USER_A - deal(address(usdt), USER_A, _total); - vm.prank(USER_A); - operator.placeBid(_tokenId, _amount); - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - assertEq(_nextAuctionId, _prevNextAuctionId + 1); - IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.highestBidder, USER_A); - - // new bid with USER_B - deal(address(usdt), USER_B, _total); - vm.prank(USER_B); - operator.placeBid(_tokenId, _amount); - _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - assertEq(_nextAuctionId, _prevNextAuctionId + 1); // still the same auction - _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.highestBidder, USER_A); // USER_A is still the same highest bidder - - // check if bids exist - IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _nextAuctionId, USER_A); - assertEq(_bidA.placedAt, block.number); - assertEq(_bidA.isWon, false); - IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _nextAuctionId, USER_A); - assertEq(_bidB.placedAt, block.number); - assertEq(_bidB.isWon, false); - - // check registry balance - assertEq(usdt.balanceOf(address(registry)), _total * 2); - } - - function testPlaceBidWithHigherPrice(uint96 _amount) public { - vm.assume(_amount > 0); - vm.assume(_amount < type(uint96).max / 2); - - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - // bid with USER_A - deal(address(usdt), USER_A, _total); - vm.prank(USER_A); - operator.placeBid(_tokenId, _amount); - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.highestBidder, USER_A); - - // bid with USER_B - _amount = _amount * 2; - _tax = operator.calculateTax(_amount); - _total = _amount + _tax; - deal(address(usdt), USER_B, _total); - vm.startPrank(USER_B); - operator.placeBid(_tokenId, _amount); - _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.highestBidder, USER_B); - } - - function testPlaceBidZeroPrice() public { - uint256 _tokenId = _mintBoard(); - - vm.startPrank(ADMIN); - uint256 _prevBalance = usdt.balanceOf(ADMIN); - - operator.placeBid(_tokenId, 0); - - // check balances - uint256 _afterBalance = usdt.balanceOf(ADMIN); - assertEq(_afterBalance, _prevBalance); - assertEq(usdt.balanceOf(address(operator)), 0); - assertEq(usdt.balanceOf(address(registry)), 0); - - // check auction - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.highestBidder, ADMIN); - - // check bid - IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, ADMIN); - assertEq(_bid.placedAt, block.number); - assertEq(_bid.isWon, true); - } - - function testPlaceBidByWhitelist() public { - uint256 _tokenId = _mintBoard(); - uint256 _amount = 1 ether; - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - vm.prank(ADMIN); - operator.addToWhitelist(USER_A); - - deal(address(usdt), USER_A, _total); - vm.prank(USER_A); - operator.placeBid(_tokenId, _amount); - assertEq(usdt.balanceOf(USER_A), 0); - } - - function testPlaceBidIfAuctionEnded() public { - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - uint256 _amount = 1 ether; - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - // place a bid with USER_A - vm.startPrank(USER_A); - deal(address(usdt), USER_A, _total); - operator.placeBid(_tokenId, _amount); - - // check auction - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.highestBidder, USER_A); - assertEq(_auction.endAt, block.number + registry.leaseTerm()); - - // make auction ended - vm.roll(_auction.endAt + 1); - - // place a bid with USER_B - vm.startPrank(USER_B); - deal(address(usdt), USER_B, _total); - operator.placeBid(_tokenId, _amount); - - // check auction - uint256 _newNextAuctionId = registry.nextBoardAuctionId(_tokenId); - IBillboardRegistry.Auction memory _newAuction = registry.getAuction(_tokenId, _newNextAuctionId); - assertEq(_newNextAuctionId, _nextAuctionId + 1); - assertEq(_newAuction.highestBidder, USER_B); - assertEq(_newAuction.endAt, block.number + registry.leaseTerm()); - - // USER_A won the previous auction - IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); - assertEq(_bid.isWon, true); - - // USER_B's bid is still in a running auction - IBillboardRegistry.Bid memory _newBid = registry.getBid(_tokenId, _newNextAuctionId, USER_B); - assertEq(_newBid.isWon, false); - } - - function testCannotPlaceBidTwice(uint96 _amount) public { - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - vm.startPrank(USER_A); - deal(address(usdt), USER_A, _total); - operator.placeBid(_tokenId, _amount); - assertEq(usdt.balanceOf(USER_A), 0); - - deal(address(usdt), USER_A, _total); - vm.expectRevert("Bid already placed"); - operator.placeBid(_tokenId, _amount); - } - - function testCannotPlaceBidByAttacker() public { - uint256 _tokenId = _mintBoard(); - uint256 _amount = 1 ether; - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - vm.startPrank(ATTACKER); - deal(address(usdt), ATTACKER, _total); - vm.expectRevert("Whitelist"); - operator.placeBid(_tokenId, _amount); - } - - function testClearAuctionIfAuctionEnded(uint96 _amount) public { - vm.assume(_amount > 0.001 ether); - - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); - uint64 _placedAt = uint64(block.number); - uint64 _clearedAt = uint64(block.number) + registry.leaseTerm() + 1; - - // place a bid - vm.startPrank(USER_A); - deal(address(usdt), USER_A, _total); - operator.placeBid(_tokenId, _amount); - - // clear auction - vm.expectEmit(true, true, true, true); - emit IBillboardRegistry.AuctionCleared( - _tokenId, - _prevAuctionId + 1, - USER_A, - _clearedAt, - _clearedAt + registry.leaseTerm() - ); - - vm.roll(_clearedAt); - (uint256 _price1, uint256 _tax1) = operator.clearAuction(_tokenId); - assertEq(_price1, _amount); - assertEq(_tax1, _tax); - - // check auction - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.startAt, _placedAt); - assertEq(_auction.endAt, _placedAt + registry.leaseTerm()); - assertEq(_auction.leaseStartAt, _clearedAt); - assertEq(_auction.leaseEndAt, _clearedAt + registry.leaseTerm()); - assertEq(_auction.highestBidder, USER_A); - - // check bid - IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); - assertEq(_bid.price, _amount); - assertEq(_bid.tax, _tax); - assertEq(_bid.placedAt, _placedAt); - assertEq(_bid.isWon, true); - assertEq(_bid.isWithdrawn, false); - } - - function testClearAuctionsIfAuctionEnded() public { - (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); - (uint256 _tokenId2, uint256 _prevAuctionId2) = _mintBoardAndPlaceBid(); - - uint64 _placedAt = uint64(block.number); - uint64 _clearedAt = uint64(block.number) + registry.leaseTerm() + 1; - - // place bids - vm.startPrank(USER_A); - deal(address(usdt), USER_A, 0); - operator.placeBid(_tokenId, 0); - - vm.startPrank(USER_B); - deal(address(usdt), USER_B, 0); - operator.placeBid(_tokenId2, 0); - - // clear auction - vm.expectEmit(true, true, true, true); - emit IBillboardRegistry.AuctionCleared( - _tokenId, - _prevAuctionId + 1, - USER_A, - _clearedAt, - _clearedAt + registry.leaseTerm() - ); - vm.expectEmit(true, true, true, true); - emit IBillboardRegistry.AuctionCleared( - _tokenId2, - _prevAuctionId2 + 1, - USER_B, - _clearedAt, - _clearedAt + registry.leaseTerm() - ); - - vm.roll(_clearedAt); - - uint256[] memory _tokenIds = new uint256[](2); - _tokenIds[0] = _tokenId; - _tokenIds[1] = _tokenId2; - (uint256[] memory prices, uint256[] memory taxes) = operator.clearAuctions(_tokenIds); - assertEq(prices[0], 0); - assertEq(prices[1], 0); - assertEq(taxes[0], 0); - assertEq(taxes[1], 0); - - // check auction - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.startAt, _placedAt); - assertEq(_auction.endAt, _placedAt + registry.leaseTerm()); - assertEq(_auction.leaseStartAt, _clearedAt); - assertEq(_auction.leaseEndAt, _clearedAt + registry.leaseTerm()); - assertEq(_auction.highestBidder, USER_A); - - uint256 _nextAuctionId2 = registry.nextBoardAuctionId(_tokenId2); - IBillboardRegistry.Auction memory _auction2 = registry.getAuction(_tokenId2, _nextAuctionId2); - assertEq(_auction2.startAt, _placedAt); - assertEq(_auction2.endAt, _placedAt + registry.leaseTerm()); - assertEq(_auction2.leaseStartAt, _clearedAt); - assertEq(_auction2.leaseEndAt, _clearedAt + registry.leaseTerm()); - assertEq(_auction2.highestBidder, USER_B); - - // check bid - IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); - assertEq(_bid.price, 0); - assertEq(_bid.tax, 0); - assertEq(_bid.placedAt, _placedAt); - assertEq(_bid.isWon, true); - assertEq(_bid.isWithdrawn, false); - - IBillboardRegistry.Bid memory _bid2 = registry.getBid(_tokenId2, _nextAuctionId2, USER_B); - assertEq(_bid2.price, 0); - assertEq(_bid2.tax, 0); - assertEq(_bid2.placedAt, _placedAt); - assertEq(_bid2.isWon, true); - assertEq(_bid2.isWithdrawn, false); - } - - function testCannotClearAuctionOnNewBoard() public { - uint256 _mintedAt = block.number; - uint256 _clearedAt = _mintedAt + 1; - uint256 _tokenId = _mintBoard(); - - vm.startPrank(ADMIN); - - // clear auction - vm.roll(_clearedAt); - vm.expectRevert("Auction not found"); - operator.clearAuction(_tokenId); - } - - function testCannotClearAuctionIfAuctionNotEnded() public { - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - - // place a bid - vm.startPrank(USER_A); - deal(address(usdt), USER_A, 0); - operator.placeBid(_tokenId, 0); - - // try to clear auction - vm.expectRevert("Auction not ended"); - operator.clearAuction(_tokenId); - - vm.roll(block.number + registry.leaseTerm() - 1); - vm.expectRevert("Auction not ended"); - operator.clearAuction(_tokenId); - } - - function testGetBids(uint8 _bidCount, uint8 _limit, uint8 _offset) public { - vm.assume(_bidCount > 0); - vm.assume(_bidCount <= 64); - vm.assume(_limit <= _bidCount); - vm.assume(_offset <= _limit); - - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - - for (uint8 i = 0; i < _bidCount; i++) { - address _bidder = address(uint160(2000 + i)); - - vm.prank(ADMIN); - operator.addToWhitelist(_bidder); - - uint256 _amount = 1 ether + i; - uint256 _tax = operator.calculateTax(_amount); - uint256 _totalAmount = _amount + _tax; - - deal(address(usdt), _bidder, _totalAmount); - vm.startPrank(_bidder); - usdt.approve(address(operator), _totalAmount); - operator.placeBid(_tokenId, _amount); - vm.stopPrank(); - } - - // get bids - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - (uint256 _t, uint256 _l, uint256 _o, IBillboardRegistry.Bid[] memory _bids) = operator.getBids( - _tokenId, - _nextAuctionId, - _limit, - _offset - ); - uint256 _left = _t - _offset; - uint256 _size = _left > _limit ? _limit : _left; - assertEq(_t, _bidCount); - assertEq(_l, _limit); - assertEq(_bids.length, _size); - assertEq(_o, _offset); - for (uint256 i = 0; i < _size; i++) { - uint256 _amount = 1 ether + _offset + i; - assertEq(_bids[i].price, _amount); - } - } - - ////////////////////////////// - /// Tax & Withdraw - ////////////////////////////// - - function testCalculateTax() public { - uint256 _amount = 100; - uint256 _taxRate = 10; // 10% per lease term - - vm.startPrank(ADMIN); - operator.setTaxRate(_taxRate); - - uint256 _tax = operator.calculateTax(_amount); - assertEq(_tax, (_amount * _taxRate) / 1000); - } - - function testSetTaxRate() public { - vm.startPrank(ADMIN); - - vm.expectEmit(true, true, true, true); - emit IBillboardRegistry.TaxRateUpdated(2); - - operator.setTaxRate(2); - assertEq(operator.getTaxRate(), 2); - } - - function testCannotSetTaxRateByAttacker() public { - vm.startPrank(ATTACKER); - - vm.expectRevert("Admin"); - operator.setTaxRate(2); - } - - function testWithdrawTax(uint96 _amount) public { - vm.assume(_amount > 0.001 ether); - - uint256 _tokenId = _mintBoard(); - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - vm.prank(ADMIN); - operator.addToWhitelist(USER_A); - - // place a bid and win auction - deal(address(usdt), USER_A, _total); - vm.prank(USER_A); - operator.placeBid(_tokenId, _amount); - - uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); - uint256 _prevAdminBalance = usdt.balanceOf(ADMIN); - - // withdraw tax - vm.expectEmit(true, true, true, true); - emit IBillboardRegistry.TaxWithdrawn(ADMIN, _tax); - - vm.prank(ADMIN); - operator.withdrawTax(); - - // check balances - assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance - _tax); - assertEq(usdt.balanceOf(ADMIN), _prevAdminBalance + _tax); - } - - function testCannnotWithdrawTaxIfZero() public { - uint256 _tokenId = _mintBoard(); - - vm.prank(ADMIN); - operator.addToWhitelist(USER_A); - - // place a bid and win auction - deal(address(usdt), USER_A, 0); - vm.prank(USER_A); - operator.placeBid(_tokenId, 0); - - vm.prank(ADMIN); - vm.expectRevert("Zero amount"); - operator.withdrawTax(); - } - - function testCannnotWithdrawTaxIfSmallAmount(uint8 _amount) public { - uint256 _tax = operator.calculateTax(_amount); - vm.assume(_tax <= 0); - - uint256 _tokenId = _mintBoard(); - - vm.prank(ADMIN); - operator.addToWhitelist(USER_A); - - // place a bid and win auction - deal(address(usdt), USER_A, _amount); - vm.prank(USER_A); - operator.placeBid(_tokenId, _amount); - - vm.prank(ADMIN); - vm.expectRevert("Zero amount"); - operator.withdrawTax(); - } - - function testCannotWithdrawTaxByAttacker() public { - vm.startPrank(ATTACKER); +// vm.expectRevert("ERC721: transfer to the zero address"); +// registry.transferFrom(ADMIN, ZERO_ADDRESS, _tokenId); +// } + +// function testCannotTransferByOperator() public { +// uint256 _tokenId = _mintBoard(); + +// vm.startPrank(address(operator)); + +// vm.expectRevert("ERC721: caller is not token owner or approved"); +// registry.transferFrom(USER_B, USER_C, _tokenId); +// } + +// function testSafeTransferByOperator() public { +// uint256 _tokenId = _mintBoard(); + +// vm.expectEmit(true, true, true, true); +// emit IERC721.Transfer(ADMIN, USER_A, _tokenId); + +// vm.startPrank(address(operator)); +// registry.safeTransferByOperator(ADMIN, USER_A, _tokenId); +// assertEq(registry.ownerOf(_tokenId), USER_A); +// } + +// function testCannotSafeTransferByAttacker() public { +// uint256 _tokenId = _mintBoard(); + +// vm.startPrank(ATTACKER); + +// vm.expectRevert("Operator"); +// registry.safeTransferByOperator(ADMIN, ATTACKER, _tokenId); +// } + +// function testApproveAndTransfer() public { +// uint256 _tokenId = _mintBoard(); + +// vm.expectEmit(true, true, true, true); +// emit IERC721.Approval(ADMIN, USER_A, _tokenId); +// vm.prank(ADMIN); +// registry.approve(USER_A, _tokenId); +// assertEq(registry.getApproved(_tokenId), USER_A); + +// vm.expectEmit(true, true, true, true); +// emit IERC721.Transfer(ADMIN, USER_A, _tokenId); +// vm.prank(USER_A); +// registry.transferFrom(ADMIN, USER_A, _tokenId); + +// IBillboardRegistry.Board memory board = operator.getBoard(_tokenId); +// assertEq(board.creator, ADMIN); +// assertEq(registry.ownerOf(_tokenId), USER_A); +// } + +// function testCannotApproveByAttacker() public { +// uint256 _tokenId = _mintBoard(); + +// vm.stopPrank(); +// vm.startPrank(ATTACKER); +// vm.expectRevert("ERC721: approve caller is not token owner or approved for all"); +// registry.approve(USER_A, _tokenId); +// } + +// ////////////////////////////// +// /// Auction +// ////////////////////////////// + +// function testPlaceBidOnNewBoard(uint96 _amount) public { +// vm.prank(ADMIN); +// operator.addToWhitelist(USER_A); + +// vm.expectEmit(true, false, false, false); +// emit IERC721.Transfer(address(0), ADMIN, 1); + +// uint256 _tokenId = _mintBoard(); +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _overpaid = 0.1 ether; +// uint256 _total = _amount + _tax; +// deal(address(usdt), USER_A, _total + _overpaid); + +// uint256 _prevNextActionId = registry.nextBoardAuctionId(_tokenId); +// uint256 _prevCreatorBalance = usdt.balanceOf(ADMIN); +// uint256 _prevBidderBalance = usdt.balanceOf(USER_A); +// uint256 _prevOperatorBalance = usdt.balanceOf(address(operator)); +// uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); + +// vm.expectEmit(true, true, true, true); +// emit IBillboardRegistry.AuctionCreated( +// _tokenId, +// _prevNextActionId + 1, +// uint64(block.number), +// uint64(block.number) +// ); +// vm.expectEmit(true, true, true, true); +// emit IBillboardRegistry.BidCreated(_tokenId, _prevNextActionId + 1, USER_A, _amount, _tax); +// vm.expectEmit(true, true, true, true); +// emit IBillboardRegistry.BidWon(_tokenId, _prevNextActionId + 1, USER_A); +// vm.expectEmit(true, true, true, true); +// emit IBillboardRegistry.AuctionCleared( +// _tokenId, +// _prevNextActionId + 1, +// USER_A, +// uint64(block.number), +// uint64(block.number + registry.leaseTerm()) +// ); + +// vm.prank(USER_A); +// operator.placeBid(_tokenId, _amount); + +// // check balances +// assertEq(usdt.balanceOf(ADMIN), _prevCreatorBalance + _amount); +// assertEq(usdt.balanceOf(USER_A), _prevBidderBalance - _total); +// assertEq(usdt.balanceOf(address(operator)), _prevOperatorBalance); +// assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance + _tax); + +// // check auction +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_prevNextActionId, 0); +// assertEq(_nextAuctionId, _prevNextActionId + 1); +// assertEq(_auction.startAt, block.number); +// assertEq(_auction.endAt, block.number); +// assertEq(_auction.leaseStartAt, block.number); +// assertEq(_auction.leaseEndAt, block.number + registry.leaseTerm()); +// assertEq(_auction.highestBidder, USER_A); + +// // check bid +// IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); +// assertEq(_bid.price, _amount); +// assertEq(_bid.tax, _tax); +// assertEq(_bid.placedAt, block.number); +// assertEq(_bid.isWon, true); +// assertEq(_bid.isWithdrawn, false); +// } + +// function testPlaceBidWithSamePrices(uint96 _amount) public { +// (uint256 _tokenId, uint256 _prevNextAuctionId) = _mintBoardAndPlaceBid(); +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// // new auction and new bid with USER_A +// deal(address(usdt), USER_A, _total); +// vm.prank(USER_A); +// operator.placeBid(_tokenId, _amount); +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// assertEq(_nextAuctionId, _prevNextAuctionId + 1); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.highestBidder, USER_A); + +// // new bid with USER_B +// deal(address(usdt), USER_B, _total); +// vm.prank(USER_B); +// operator.placeBid(_tokenId, _amount); +// _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// assertEq(_nextAuctionId, _prevNextAuctionId + 1); // still the same auction +// _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.highestBidder, USER_A); // USER_A is still the same highest bidder + +// // check if bids exist +// IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _nextAuctionId, USER_A); +// assertEq(_bidA.placedAt, block.number); +// assertEq(_bidA.isWon, false); +// IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _nextAuctionId, USER_A); +// assertEq(_bidB.placedAt, block.number); +// assertEq(_bidB.isWon, false); + +// // check registry balance +// assertEq(usdt.balanceOf(address(registry)), _total * 2); +// } + +// function testPlaceBidWithHigherPrice(uint96 _amount) public { +// vm.assume(_amount > 0); +// vm.assume(_amount < type(uint96).max / 2); + +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// // bid with USER_A +// deal(address(usdt), USER_A, _total); +// vm.prank(USER_A); +// operator.placeBid(_tokenId, _amount); +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.highestBidder, USER_A); + +// // bid with USER_B +// _amount = _amount * 2; +// _tax = operator.calculateTax(_amount); +// _total = _amount + _tax; +// deal(address(usdt), USER_B, _total); +// vm.startPrank(USER_B); +// operator.placeBid(_tokenId, _amount); +// _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.highestBidder, USER_B); +// } + +// function testPlaceBidZeroPrice() public { +// uint256 _tokenId = _mintBoard(); + +// vm.startPrank(ADMIN); +// uint256 _prevBalance = usdt.balanceOf(ADMIN); + +// operator.placeBid(_tokenId, 0); + +// // check balances +// uint256 _afterBalance = usdt.balanceOf(ADMIN); +// assertEq(_afterBalance, _prevBalance); +// assertEq(usdt.balanceOf(address(operator)), 0); +// assertEq(usdt.balanceOf(address(registry)), 0); + +// // check auction +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.highestBidder, ADMIN); + +// // check bid +// IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, ADMIN); +// assertEq(_bid.placedAt, block.number); +// assertEq(_bid.isWon, true); +// } + +// function testPlaceBidByWhitelist() public { +// uint256 _tokenId = _mintBoard(); +// uint256 _amount = 1 ether; +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// vm.prank(ADMIN); +// operator.addToWhitelist(USER_A); + +// deal(address(usdt), USER_A, _total); +// vm.prank(USER_A); +// operator.placeBid(_tokenId, _amount); +// assertEq(usdt.balanceOf(USER_A), 0); +// } + +// function testPlaceBidIfAuctionEnded() public { +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); +// uint256 _amount = 1 ether; +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// // place a bid with USER_A +// vm.startPrank(USER_A); +// deal(address(usdt), USER_A, _total); +// operator.placeBid(_tokenId, _amount); + +// // check auction +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.highestBidder, USER_A); +// assertEq(_auction.endAt, block.number + registry.leaseTerm()); + +// // make auction ended +// vm.roll(_auction.endAt + 1); + +// // place a bid with USER_B +// vm.startPrank(USER_B); +// deal(address(usdt), USER_B, _total); +// operator.placeBid(_tokenId, _amount); + +// // check auction +// uint256 _newNextAuctionId = registry.nextBoardAuctionId(_tokenId); +// IBillboardRegistry.Auction memory _newAuction = registry.getAuction(_tokenId, _newNextAuctionId); +// assertEq(_newNextAuctionId, _nextAuctionId + 1); +// assertEq(_newAuction.highestBidder, USER_B); +// assertEq(_newAuction.endAt, block.number + registry.leaseTerm()); + +// // USER_A won the previous auction +// IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); +// assertEq(_bid.isWon, true); + +// // USER_B's bid is still in a running auction +// IBillboardRegistry.Bid memory _newBid = registry.getBid(_tokenId, _newNextAuctionId, USER_B); +// assertEq(_newBid.isWon, false); +// } + +// function testCannotPlaceBidTwice(uint96 _amount) public { +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// vm.startPrank(USER_A); +// deal(address(usdt), USER_A, _total); +// operator.placeBid(_tokenId, _amount); +// assertEq(usdt.balanceOf(USER_A), 0); + +// deal(address(usdt), USER_A, _total); +// vm.expectRevert("Bid already placed"); +// operator.placeBid(_tokenId, _amount); +// } + +// function testCannotPlaceBidByAttacker() public { +// uint256 _tokenId = _mintBoard(); +// uint256 _amount = 1 ether; +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// vm.startPrank(ATTACKER); +// deal(address(usdt), ATTACKER, _total); +// vm.expectRevert("Whitelist"); +// operator.placeBid(_tokenId, _amount); +// } + +// function testClearAuctionIfAuctionEnded(uint96 _amount) public { +// vm.assume(_amount > 0.001 ether); + +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); +// uint64 _placedAt = uint64(block.number); +// uint64 _clearedAt = uint64(block.number) + registry.leaseTerm() + 1; + +// // place a bid +// vm.startPrank(USER_A); +// deal(address(usdt), USER_A, _total); +// operator.placeBid(_tokenId, _amount); + +// // clear auction +// vm.expectEmit(true, true, true, true); +// emit IBillboardRegistry.AuctionCleared( +// _tokenId, +// _prevAuctionId + 1, +// USER_A, +// _clearedAt, +// _clearedAt + registry.leaseTerm() +// ); + +// vm.roll(_clearedAt); +// (uint256 _price1, uint256 _tax1) = operator.clearAuction(_tokenId); +// assertEq(_price1, _amount); +// assertEq(_tax1, _tax); + +// // check auction +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.startAt, _placedAt); +// assertEq(_auction.endAt, _placedAt + registry.leaseTerm()); +// assertEq(_auction.leaseStartAt, _clearedAt); +// assertEq(_auction.leaseEndAt, _clearedAt + registry.leaseTerm()); +// assertEq(_auction.highestBidder, USER_A); + +// // check bid +// IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); +// assertEq(_bid.price, _amount); +// assertEq(_bid.tax, _tax); +// assertEq(_bid.placedAt, _placedAt); +// assertEq(_bid.isWon, true); +// assertEq(_bid.isWithdrawn, false); +// } + +// function testClearAuctionsIfAuctionEnded() public { +// (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); +// (uint256 _tokenId2, uint256 _prevAuctionId2) = _mintBoardAndPlaceBid(); + +// uint64 _placedAt = uint64(block.number); +// uint64 _clearedAt = uint64(block.number) + registry.leaseTerm() + 1; + +// // place bids +// vm.startPrank(USER_A); +// deal(address(usdt), USER_A, 0); +// operator.placeBid(_tokenId, 0); + +// vm.startPrank(USER_B); +// deal(address(usdt), USER_B, 0); +// operator.placeBid(_tokenId2, 0); + +// // clear auction +// vm.expectEmit(true, true, true, true); +// emit IBillboardRegistry.AuctionCleared( +// _tokenId, +// _prevAuctionId + 1, +// USER_A, +// _clearedAt, +// _clearedAt + registry.leaseTerm() +// ); +// vm.expectEmit(true, true, true, true); +// emit IBillboardRegistry.AuctionCleared( +// _tokenId2, +// _prevAuctionId2 + 1, +// USER_B, +// _clearedAt, +// _clearedAt + registry.leaseTerm() +// ); + +// vm.roll(_clearedAt); + +// uint256[] memory _tokenIds = new uint256[](2); +// _tokenIds[0] = _tokenId; +// _tokenIds[1] = _tokenId2; +// (uint256[] memory prices, uint256[] memory taxes) = operator.clearAuctions(_tokenIds); +// assertEq(prices[0], 0); +// assertEq(prices[1], 0); +// assertEq(taxes[0], 0); +// assertEq(taxes[1], 0); + +// // check auction +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.startAt, _placedAt); +// assertEq(_auction.endAt, _placedAt + registry.leaseTerm()); +// assertEq(_auction.leaseStartAt, _clearedAt); +// assertEq(_auction.leaseEndAt, _clearedAt + registry.leaseTerm()); +// assertEq(_auction.highestBidder, USER_A); + +// uint256 _nextAuctionId2 = registry.nextBoardAuctionId(_tokenId2); +// IBillboardRegistry.Auction memory _auction2 = registry.getAuction(_tokenId2, _nextAuctionId2); +// assertEq(_auction2.startAt, _placedAt); +// assertEq(_auction2.endAt, _placedAt + registry.leaseTerm()); +// assertEq(_auction2.leaseStartAt, _clearedAt); +// assertEq(_auction2.leaseEndAt, _clearedAt + registry.leaseTerm()); +// assertEq(_auction2.highestBidder, USER_B); + +// // check bid +// IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); +// assertEq(_bid.price, 0); +// assertEq(_bid.tax, 0); +// assertEq(_bid.placedAt, _placedAt); +// assertEq(_bid.isWon, true); +// assertEq(_bid.isWithdrawn, false); + +// IBillboardRegistry.Bid memory _bid2 = registry.getBid(_tokenId2, _nextAuctionId2, USER_B); +// assertEq(_bid2.price, 0); +// assertEq(_bid2.tax, 0); +// assertEq(_bid2.placedAt, _placedAt); +// assertEq(_bid2.isWon, true); +// assertEq(_bid2.isWithdrawn, false); +// } + +// function testCannotClearAuctionOnNewBoard() public { +// uint256 _mintedAt = block.number; +// uint256 _clearedAt = _mintedAt + 1; +// uint256 _tokenId = _mintBoard(); + +// vm.startPrank(ADMIN); + +// // clear auction +// vm.roll(_clearedAt); +// vm.expectRevert("Auction not found"); +// operator.clearAuction(_tokenId); +// } + +// function testCannotClearAuctionIfAuctionNotEnded() public { +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + +// // place a bid +// vm.startPrank(USER_A); +// deal(address(usdt), USER_A, 0); +// operator.placeBid(_tokenId, 0); + +// // try to clear auction +// vm.expectRevert("Auction not ended"); +// operator.clearAuction(_tokenId); + +// vm.roll(block.number + registry.leaseTerm() - 1); +// vm.expectRevert("Auction not ended"); +// operator.clearAuction(_tokenId); +// } + +// function testGetBids(uint8 _bidCount, uint8 _limit, uint8 _offset) public { +// vm.assume(_bidCount > 0); +// vm.assume(_bidCount <= 64); +// vm.assume(_limit <= _bidCount); +// vm.assume(_offset <= _limit); + +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + +// for (uint8 i = 0; i < _bidCount; i++) { +// address _bidder = address(uint160(2000 + i)); + +// vm.prank(ADMIN); +// operator.addToWhitelist(_bidder); + +// uint256 _amount = 1 ether + i; +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _totalAmount = _amount + _tax; + +// deal(address(usdt), _bidder, _totalAmount); +// vm.startPrank(_bidder); +// usdt.approve(address(operator), _totalAmount); +// operator.placeBid(_tokenId, _amount); +// vm.stopPrank(); +// } + +// // get bids +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// (uint256 _t, uint256 _l, uint256 _o, IBillboardRegistry.Bid[] memory _bids) = operator.getBids( +// _tokenId, +// _nextAuctionId, +// _limit, +// _offset +// ); +// uint256 _left = _t - _offset; +// uint256 _size = _left > _limit ? _limit : _left; +// assertEq(_t, _bidCount); +// assertEq(_l, _limit); +// assertEq(_bids.length, _size); +// assertEq(_o, _offset); +// for (uint256 i = 0; i < _size; i++) { +// uint256 _amount = 1 ether + _offset + i; +// assertEq(_bids[i].price, _amount); +// } +// } + +// ////////////////////////////// +// /// Tax & Withdraw +// ////////////////////////////// + +// function testCalculateTax() public { +// uint256 _amount = 100; +// uint256 _taxRate = 10; // 10% per lease term + +// vm.startPrank(ADMIN); +// operator.setTaxRate(_taxRate); + +// uint256 _tax = operator.calculateTax(_amount); +// assertEq(_tax, (_amount * _taxRate) / 1000); +// } + +// function testSetTaxRate() public { +// vm.startPrank(ADMIN); + +// vm.expectEmit(true, true, true, true); +// emit IBillboardRegistry.TaxRateUpdated(2); + +// operator.setTaxRate(2); +// assertEq(operator.getTaxRate(), 2); +// } + +// function testCannotSetTaxRateByAttacker() public { +// vm.startPrank(ATTACKER); + +// vm.expectRevert("Admin"); +// operator.setTaxRate(2); +// } + +// function testWithdrawTax(uint96 _amount) public { +// vm.assume(_amount > 0.001 ether); + +// uint256 _tokenId = _mintBoard(); +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// vm.prank(ADMIN); +// operator.addToWhitelist(USER_A); + +// // place a bid and win auction +// deal(address(usdt), USER_A, _total); +// vm.prank(USER_A); +// operator.placeBid(_tokenId, _amount); + +// uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); +// uint256 _prevAdminBalance = usdt.balanceOf(ADMIN); + +// // withdraw tax +// vm.expectEmit(true, true, true, true); +// emit IBillboardRegistry.TaxWithdrawn(ADMIN, _tax); + +// vm.prank(ADMIN); +// operator.withdrawTax(); + +// // check balances +// assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance - _tax); +// assertEq(usdt.balanceOf(ADMIN), _prevAdminBalance + _tax); +// } + +// function testCannnotWithdrawTaxIfZero() public { +// uint256 _tokenId = _mintBoard(); + +// vm.prank(ADMIN); +// operator.addToWhitelist(USER_A); + +// // place a bid and win auction +// deal(address(usdt), USER_A, 0); +// vm.prank(USER_A); +// operator.placeBid(_tokenId, 0); + +// vm.prank(ADMIN); +// vm.expectRevert("Zero amount"); +// operator.withdrawTax(); +// } + +// function testCannnotWithdrawTaxIfSmallAmount(uint8 _amount) public { +// uint256 _tax = operator.calculateTax(_amount); +// vm.assume(_tax <= 0); + +// uint256 _tokenId = _mintBoard(); + +// vm.prank(ADMIN); +// operator.addToWhitelist(USER_A); + +// // place a bid and win auction +// deal(address(usdt), USER_A, _amount); +// vm.prank(USER_A); +// operator.placeBid(_tokenId, _amount); + +// vm.prank(ADMIN); +// vm.expectRevert("Zero amount"); +// operator.withdrawTax(); +// } + +// function testCannotWithdrawTaxByAttacker() public { +// vm.startPrank(ATTACKER); - vm.expectRevert("Zero amount"); - operator.withdrawTax(); - } +// vm.expectRevert("Zero amount"); +// operator.withdrawTax(); +// } - function testWithdrawBid(uint96 _amount) public { - vm.assume(_amount > 0.001 ether); - - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - // new auction and new bid with USER_A - deal(address(usdt), USER_A, _total); - vm.prank(USER_A); - operator.placeBid(_tokenId, _amount); - - // new bid with USER_B - deal(address(usdt), USER_B, _total); - vm.prank(USER_B); - operator.placeBid(_tokenId, _amount); - - // clear auction - vm.roll(block.number + registry.leaseTerm() + 1); - operator.clearAuction(_tokenId); - - // check auction - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.highestBidder, USER_A); - - // check bid - IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _nextAuctionId, USER_A); - assertEq(_bidA.isWon, true); - IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _nextAuctionId, USER_B); - assertEq(_bidB.isWon, false); - - // withdraw bid - vm.expectEmit(true, true, true, true); - emit IBillboardRegistry.BidWithdrawn(_tokenId, _nextAuctionId, USER_B, _amount, _tax); - - vm.prank(USER_B); - operator.withdrawBid(_tokenId, _nextAuctionId); - assertEq(usdt.balanceOf(USER_B), _total); - } - - function testCannotWithBidTwice(uint96 _amount) public { - vm.assume(_amount > 0.001 ether); - - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - // new auction and new bid with USER_A - deal(address(usdt), USER_A, _total); - vm.prank(USER_A); - operator.placeBid(_tokenId, _amount); - - // new bid with USER_B - deal(address(usdt), USER_B, _total); - vm.prank(USER_B); - operator.placeBid(_tokenId, _amount); - - // clear auction - vm.roll(block.number + registry.leaseTerm() + 1); - operator.clearAuction(_tokenId); - - // check auction - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.highestBidder, USER_A); - - // withdraw bid - vm.prank(USER_B); - operator.withdrawBid(_tokenId, _nextAuctionId); - assertEq(usdt.balanceOf(USER_B), _total); - - // withdraw bid again - vm.prank(USER_B); - vm.expectRevert("Bid already withdrawn"); - operator.withdrawBid(_tokenId, _nextAuctionId); - } - - function testCannotWithdrawBidIfWon(uint96 _amount) public { - vm.assume(_amount > 0.001 ether); - - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - // new auction and new bid with USER_A - deal(address(usdt), USER_A, _total); - vm.prank(USER_A); - operator.placeBid(_tokenId, _amount); - - // clear auction - vm.roll(block.number + registry.leaseTerm() + 1); - operator.clearAuction(_tokenId); - - // check auction - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - assertEq(_auction.highestBidder, USER_A); - - // withdraw bid - vm.prank(USER_A); - vm.expectRevert("Bid already won"); - operator.withdrawBid(_tokenId, _nextAuctionId); - } - - function testCannotWithdrawBidIfAuctionNotEnded(uint96 _amount) public { - vm.assume(_amount > 0.001 ether); - - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - // new auction and new bid with USER_A - vm.startPrank(USER_A); - deal(address(usdt), USER_A, _total); - operator.placeBid(_tokenId, _amount); - - // auction is not ended - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - vm.expectRevert("Auction not ended"); - operator.withdrawBid(_tokenId, _nextAuctionId); - - // auction is ended but not cleared - vm.roll(block.number + registry.leaseTerm() + 1); - vm.expectRevert("Auction not cleared"); - operator.withdrawBid(_tokenId, _nextAuctionId); - } - - function testCannotWithdrawBidIfAuctionNotCleared(uint96 _amount) public { - vm.assume(_amount > 0.001 ether); - - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - uint256 _tax = operator.calculateTax(_amount); - uint256 _total = _amount + _tax; - - // new auction and new bid with USER_A - deal(address(usdt), USER_A, _total); - vm.prank(USER_A); - operator.placeBid(_tokenId, _amount); - - // new bid with USER_B - deal(address(usdt), USER_B, _total); - vm.prank(USER_B); - operator.placeBid(_tokenId, _amount); - - // auction is ended but not cleared - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - vm.roll(block.number + registry.leaseTerm() + 1); - vm.prank(USER_B); - vm.expectRevert("Auction not cleared"); - operator.withdrawBid(_tokenId, _nextAuctionId); - } - - function testCannotWithdrawBidIfNotFound() public { - (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - - vm.prank(USER_A); - vm.expectRevert("Bid not found"); - operator.withdrawBid(_tokenId, _nextAuctionId); - } -} +// function testWithdrawBid(uint96 _amount) public { +// vm.assume(_amount > 0.001 ether); + +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// // new auction and new bid with USER_A +// deal(address(usdt), USER_A, _total); +// vm.prank(USER_A); +// operator.placeBid(_tokenId, _amount); + +// // new bid with USER_B +// deal(address(usdt), USER_B, _total); +// vm.prank(USER_B); +// operator.placeBid(_tokenId, _amount); + +// // clear auction +// vm.roll(block.number + registry.leaseTerm() + 1); +// operator.clearAuction(_tokenId); + +// // check auction +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.highestBidder, USER_A); + +// // check bid +// IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _nextAuctionId, USER_A); +// assertEq(_bidA.isWon, true); +// IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _nextAuctionId, USER_B); +// assertEq(_bidB.isWon, false); + +// // withdraw bid +// vm.expectEmit(true, true, true, true); +// emit IBillboardRegistry.BidWithdrawn(_tokenId, _nextAuctionId, USER_B, _amount, _tax); + +// vm.prank(USER_B); +// operator.withdrawBid(_tokenId, _nextAuctionId); +// assertEq(usdt.balanceOf(USER_B), _total); +// } + +// function testCannotWithBidTwice(uint96 _amount) public { +// vm.assume(_amount > 0.001 ether); + +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// // new auction and new bid with USER_A +// deal(address(usdt), USER_A, _total); +// vm.prank(USER_A); +// operator.placeBid(_tokenId, _amount); + +// // new bid with USER_B +// deal(address(usdt), USER_B, _total); +// vm.prank(USER_B); +// operator.placeBid(_tokenId, _amount); + +// // clear auction +// vm.roll(block.number + registry.leaseTerm() + 1); +// operator.clearAuction(_tokenId); + +// // check auction +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.highestBidder, USER_A); + +// // withdraw bid +// vm.prank(USER_B); +// operator.withdrawBid(_tokenId, _nextAuctionId); +// assertEq(usdt.balanceOf(USER_B), _total); + +// // withdraw bid again +// vm.prank(USER_B); +// vm.expectRevert("Bid already withdrawn"); +// operator.withdrawBid(_tokenId, _nextAuctionId); +// } + +// function testCannotWithdrawBidIfWon(uint96 _amount) public { +// vm.assume(_amount > 0.001 ether); + +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// // new auction and new bid with USER_A +// deal(address(usdt), USER_A, _total); +// vm.prank(USER_A); +// operator.placeBid(_tokenId, _amount); + +// // clear auction +// vm.roll(block.number + registry.leaseTerm() + 1); +// operator.clearAuction(_tokenId); + +// // check auction +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); +// assertEq(_auction.highestBidder, USER_A); + +// // withdraw bid +// vm.prank(USER_A); +// vm.expectRevert("Bid already won"); +// operator.withdrawBid(_tokenId, _nextAuctionId); +// } + +// function testCannotWithdrawBidIfAuctionNotEnded(uint96 _amount) public { +// vm.assume(_amount > 0.001 ether); + +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// // new auction and new bid with USER_A +// vm.startPrank(USER_A); +// deal(address(usdt), USER_A, _total); +// operator.placeBid(_tokenId, _amount); + +// // auction is not ended +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// vm.expectRevert("Auction not ended"); +// operator.withdrawBid(_tokenId, _nextAuctionId); + +// // auction is ended but not cleared +// vm.roll(block.number + registry.leaseTerm() + 1); +// vm.expectRevert("Auction not cleared"); +// operator.withdrawBid(_tokenId, _nextAuctionId); +// } + +// function testCannotWithdrawBidIfAuctionNotCleared(uint96 _amount) public { +// vm.assume(_amount > 0.001 ether); + +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); +// uint256 _tax = operator.calculateTax(_amount); +// uint256 _total = _amount + _tax; + +// // new auction and new bid with USER_A +// deal(address(usdt), USER_A, _total); +// vm.prank(USER_A); +// operator.placeBid(_tokenId, _amount); + +// // new bid with USER_B +// deal(address(usdt), USER_B, _total); +// vm.prank(USER_B); +// operator.placeBid(_tokenId, _amount); + +// // auction is ended but not cleared +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); +// vm.roll(block.number + registry.leaseTerm() + 1); +// vm.prank(USER_B); +// vm.expectRevert("Auction not cleared"); +// operator.withdrawBid(_tokenId, _nextAuctionId); +// } + +// function testCannotWithdrawBidIfNotFound() public { +// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); +// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + +// vm.prank(USER_A); +// vm.expectRevert("Bid not found"); +// operator.withdrawBid(_tokenId, _nextAuctionId); +// } +// } diff --git a/src/test/Billboard/BillboardTestBase.t.sol b/src/test/Billboard/BillboardTestBase.t.sol index 6ba6849..67d6bd5 100644 --- a/src/test/Billboard/BillboardTestBase.t.sol +++ b/src/test/Billboard/BillboardTestBase.t.sol @@ -1,81 +1,81 @@ //SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import "forge-std/console.sol"; -import "forge-std/Test.sol"; -import "forge-std/Vm.sol"; +// import "forge-std/console.sol"; +// import "forge-std/Test.sol"; +// import "forge-std/Vm.sol"; -import {USDT} from "../utils/USDT.sol"; -import {Billboard} from "../../Billboard/Billboard.sol"; -import {BillboardRegistry} from "../../Billboard/BillboardRegistry.sol"; -import {IBillboard} from "../../Billboard/IBillboard.sol"; -import {IBillboardRegistry} from "../../Billboard/IBillboardRegistry.sol"; +// import {USDT} from "../utils/USDT.sol"; +// import {Billboard} from "../../Billboard/Billboard.sol"; +// import {BillboardRegistry} from "../../Billboard/BillboardRegistry.sol"; +// import {IBillboard} from "../../Billboard/IBillboard.sol"; +// import {IBillboardRegistry} from "../../Billboard/IBillboardRegistry.sol"; -contract BillboardTestBase is Test { - Billboard internal operator; - BillboardRegistry internal registry; - USDT internal usdt; +// contract BillboardTestBase is Test { +// Billboard internal operator; +// BillboardRegistry internal registry; +// USDT internal usdt; - uint256 constant TAX_RATE = 1; // 1% per lease term - uint64 constant LEASE_TERM = 100; // 100 blocks +// uint256 constant TAX_RATE = 1; // 1% per lease term +// uint64 constant LEASE_TERM = 100; // 100 blocks - address constant ZERO_ADDRESS = address(0); - address constant FAKE_CONTRACT = address(1); +// address constant ZERO_ADDRESS = address(0); +// address constant FAKE_CONTRACT = address(1); - /// Deployer and admin could be the same one - address constant ADMIN = address(100); - address constant USER_A = address(101); - address constant USER_B = address(102); - address constant USER_C = address(103); - address constant ATTACKER = address(200); +// /// Deployer and admin could be the same one +// address constant ADMIN = address(100); +// address constant USER_A = address(101); +// address constant USER_B = address(102); +// address constant USER_C = address(103); +// address constant ATTACKER = address(200); - function setUp() public { - vm.startPrank(ADMIN); +// function setUp() public { +// vm.startPrank(ADMIN); - // deploy USDT - usdt = new USDT(ADMIN, 0); +// // deploy USDT +// usdt = new USDT(ADMIN, 0); - // deploy operator & registry - operator = new Billboard(address(usdt), payable(address(0)), ADMIN, TAX_RATE, LEASE_TERM, "Billboard", "BLBD"); - registry = operator.registry(); - assertEq(operator.admin(), ADMIN); - assertEq(registry.operator(), address(operator)); - assertEq(registry.name(), "Billboard"); - assertEq(registry.symbol(), "BLBD"); +// // deploy operator & registry +// operator = new Billboard(address(usdt), payable(address(0)), ADMIN, TAX_RATE, LEASE_TERM, "Billboard", "BLBD"); +// registry = operator.registry(); +// assertEq(operator.admin(), ADMIN); +// assertEq(registry.operator(), address(operator)); +// assertEq(registry.name(), "Billboard"); +// assertEq(registry.symbol(), "BLBD"); - vm.stopPrank(); +// vm.stopPrank(); - // approve USDT - uint256 MAX_ALLOWANCE = type(uint256).max; - vm.prank(ADMIN); - usdt.approve(address(operator), MAX_ALLOWANCE); - vm.prank(USER_A); - usdt.approve(address(operator), MAX_ALLOWANCE); - vm.prank(USER_B); - usdt.approve(address(operator), MAX_ALLOWANCE); - vm.prank(USER_C); - usdt.approve(address(operator), MAX_ALLOWANCE); - } +// // approve USDT +// uint256 MAX_ALLOWANCE = type(uint256).max; +// vm.prank(ADMIN); +// usdt.approve(address(operator), MAX_ALLOWANCE); +// vm.prank(USER_A); +// usdt.approve(address(operator), MAX_ALLOWANCE); +// vm.prank(USER_B); +// usdt.approve(address(operator), MAX_ALLOWANCE); +// vm.prank(USER_C); +// usdt.approve(address(operator), MAX_ALLOWANCE); +// } - function _mintBoard() public returns (uint256 tokenId) { - vm.prank(ADMIN); - tokenId = operator.mintBoard(ADMIN); - } +// function _mintBoard() public returns (uint256 tokenId) { +// vm.prank(ADMIN); +// tokenId = operator.mintBoard(ADMIN); +// } - function _mintBoardAndPlaceBid() public returns (uint256 tokenId, uint256 _nextAuctionId) { - tokenId = _mintBoard(); +// function _mintBoardAndPlaceBid() public returns (uint256 tokenId, uint256 _nextAuctionId) { +// tokenId = _mintBoard(); - // (new board) ADMIN places first bid and takes the ownership - vm.startPrank(ADMIN); - operator.placeBid(tokenId, 0); - _nextAuctionId = registry.nextBoardAuctionId(tokenId); - IBillboardRegistry.Auction memory _auction = registry.getAuction(tokenId, _nextAuctionId); - assertEq(_nextAuctionId, 1); - assertEq(_auction.highestBidder, ADMIN); +// // (new board) ADMIN places first bid and takes the ownership +// vm.startPrank(ADMIN); +// operator.placeBid(tokenId, 0); +// _nextAuctionId = registry.nextBoardAuctionId(tokenId); +// IBillboardRegistry.Auction memory _auction = registry.getAuction(tokenId, _nextAuctionId); +// assertEq(_nextAuctionId, 1); +// assertEq(_auction.highestBidder, ADMIN); - // add USER_A and USER_B to whitelist - operator.addToWhitelist(USER_A); - operator.addToWhitelist(USER_B); - vm.stopPrank(); - } -} +// // add USER_A and USER_B to whitelist +// operator.addToWhitelist(USER_A); +// operator.addToWhitelist(USER_B); +// vm.stopPrank(); +// } +// } From 1ce6b8776b449785195249b775862b8c215def52 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Sun, 30 Jun 2024 16:42:17 +0800 Subject: [PATCH 05/24] feat(billboard): remove unused and rename --- src/Billboard/Billboard.sol | 94 ++++++++++++++-------------- src/Billboard/BillboardRegistry.sol | 20 +++--- src/Billboard/IBillboard.sol | 49 ++++++++++----- src/Billboard/IBillboardRegistry.sol | 2 +- 4 files changed, 94 insertions(+), 71 deletions(-) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 1ce31e0..10e6d6e 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -13,7 +13,7 @@ contract Billboard is IBillboard { address public immutable admin; // tokenId => address => whitelisted - mapping(uint256 => mapping(address => bool)) public boardWhitelists; + mapping(uint256 => mapping(address => bool)) public boardWhitelist; constructor(address token_, address payable registry_, address admin_, string memory name_, string memory symbol_) { require(admin_ != address(0), "Zero address"); @@ -39,17 +39,17 @@ contract Billboard is IBillboard { } modifier isFromWhitelist(uint256 tokenId_) { - require(boardWhitelists[tokenId_][msg.sender], "Whitelist"); + require(boardWhitelist[tokenId_][msg.sender], "Whitelist"); _; } - modifier isFromBoardCreator(uint256 tokenId_) { + modifier isFromCreator(uint256 tokenId_) { IBillboardRegistry.Board memory _board = registry.getBoard(tokenId_); require(_board.creator == msg.sender, "Creator"); _; } - modifier isFromBoardTenant(uint256 tokenId_) { + modifier isFromTenant(uint256 tokenId_) { require(msg.sender == registry.ownerOf(tokenId_), "Tenant"); _; } @@ -68,13 +68,13 @@ contract Billboard is IBillboard { ////////////////////////////// /// @inheritdoc IBillboard - function addToWhitelist(uint256 tokenId_, address value_) external isFromAdmin { - boardWhitelists[tokenId_][value_] = true; + function addToWhitelist(uint256 tokenId_, address account_) external isFromCreator(tokenId_) { + boardWhitelist[tokenId_][account_] = true; } /// @inheritdoc IBillboard - function removeFromWhitelist(uint256 tokenId_, address value_) external isFromAdmin { - boardWhitelists[tokenId_][value_] = false; + function removeFromWhitelist(uint256 tokenId_, address account_) external isFromCreator(tokenId_) { + boardWhitelist[tokenId_][account_] = false; } ////////////////////////////// @@ -82,8 +82,10 @@ contract Billboard is IBillboard { ////////////////////////////// /// @inheritdoc IBillboard - function mintBoard(address to_, uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId) { - tokenId = registry.newBoard(to_, taxRate_, epochInterval_); + function mintBoard(uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId) { + require(epochInterval_ > 0, "Zero epoch interval"); + + tokenId = registry.newBoard(msg.sender, taxRate_, epochInterval_); } /// @inheritdoc IBillboard @@ -92,14 +94,13 @@ contract Billboard is IBillboard { } /// @inheritdoc IBillboard - function setBoard( uint256 tokenId_, string calldata name_, string calldata description_, string calldata imageURI_, string calldata location_ - ) external isFromBoardCreator(tokenId_) { + ) external isFromCreator(tokenId_) { registry.setBoard(tokenId_, name_, description_, imageURI_, location_); } @@ -135,44 +136,47 @@ contract Billboard is IBillboard { require(_board.creator != address(0), "Board not found"); uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval); + require(_endedAt >= block.number, "Auction ended"); + + IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, msg.sender); - // clear auction if the auction is ended, - if (_endedAt >= block.number) { - _clearAuction(tokenId_, _board.creator, epoch_); - return; + uint256 _tax = calculateTax(tokenId_, price_); + + // create new bid if no bid exists + if (_bid.createdAt == 0) { + // transfer bid price and tax to the registry + SafeERC20.safeTransferFrom(registry.currency(), msg.sender, address(registry), price_ + _tax); + + // add new bid + registry.newBid(tokenId_, epoch_, msg.sender, price_, _tax, contentURI_, redirectURI_); } - // otherwise, create new bid or update bid + // update bid if exists else { - IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, msg.sender); + require(price_ > _bid.price, "Price too low"); - uint256 _tax = calculateTax(tokenId_, price_); + // transfer diff amount to the registry + uint256 _priceDiff = price_ - _bid.price; + uint256 _taxDiff = _tax - _bid.tax; + SafeERC20.safeTransferFrom(registry.currency(), msg.sender, address(registry), _priceDiff + _taxDiff); - // create new bid - if (_bid.createdAt == 0) { - // transfer bid price and tax to the registry - SafeERC20.safeTransferFrom(registry.token(), msg.sender, address(registry), price_ + _tax); - - // add new bid - registry.newBid(tokenId_, epoch_, msg.sender, price_, _tax, contentURI_, redirectURI_); - } - // update bid - else { - require(price_ > _bid.price, "Price too low"); - - // transfer diff amount to the registry - uint256 _priceDiff = price_ - _bid.price; - uint256 _taxDiff = _tax - _bid.tax; - SafeERC20.safeTransferFrom(registry.token(), msg.sender, address(registry), _priceDiff + _taxDiff); - - if (hasURIs) { - registry.setBid(tokenId_, epoch_, msg.sender, price_, _tax, contentURI_, redirectURI_, true); - } else { - registry.setBid(tokenId_, epoch_, msg.sender, price_, _tax, "", "", false); - } + if (hasURIs) { + registry.setBid(tokenId_, epoch_, msg.sender, price_, _tax, contentURI_, redirectURI_, true); + } else { + registry.setBid(tokenId_, epoch_, msg.sender, price_, _tax, "", "", false); } } } + /// @inheritdoc IBillboard + function setBidURIs( + uint256 tokenId_, + uint256 epoch_, + string calldata contentURI_, + string calldata redirectURI_ + ) public { + registry.setBidURIs(tokenId_, epoch_, msg.sender, contentURI_, redirectURI_); + } + /// @inheritdoc IBillboard function clearAuction( uint256 tokenId_, @@ -223,7 +227,7 @@ contract Billboard is IBillboard { if (_highestBid.price > 0) { // transfer bid price to board owner (previous tenant or creator) - registry.transferTokenByOperator(_prevOwner, _highestBid.price); + registry.transferCurrencyByOperator(_prevOwner, _highestBid.price); // transfer bid tax to board creator's tax treasury (uint256 _taxAccumulated, uint256 _taxWithdrawn) = registry.taxTreasury(boardCreator_); @@ -308,18 +312,16 @@ contract Billboard is IBillboard { registry.setBidWithdrawn(tokenId_, epoch_, msg.sender, true); // transfer bid price and tax back to the bidder - registry.transferTokenByOperator(msg.sender, amount); + registry.transferCurrencyByOperator(msg.sender, amount); } /// @inheritdoc IBillboard function getEpochFromBlock(uint256 block_, uint256 epochInterval_) public pure returns (uint256 epoch) { - // TODO: check overflow and underflow return block_ / epochInterval_; } /// @inheritdoc IBillboard function getBlockFromEpoch(uint256 epoch_, uint256 epochInterval_) public pure returns (uint256 blockNumber) { - // TODO: check overflow and underflow return epoch_ * epochInterval_; } @@ -349,7 +351,7 @@ contract Billboard is IBillboard { registry.setTaxTreasury(msg.sender, _taxAccumulated, _taxAccumulated); // transfer tax to the owner - registry.transferTokenByOperator(msg.sender, amount); + registry.transferCurrencyByOperator(msg.sender, amount); // emit TaxWithdrawn registry.emitTaxWithdrawn(msg.sender, amount); diff --git a/src/Billboard/BillboardRegistry.sol b/src/Billboard/BillboardRegistry.sol index ebd73be..2dda923 100644 --- a/src/Billboard/BillboardRegistry.sol +++ b/src/Billboard/BillboardRegistry.sol @@ -13,8 +13,8 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { address public operator; - // token to be used for auction - IERC20 public immutable token; + // currency to be used for auction + IERC20 public immutable currency; // tokenId => Board mapping(uint256 => Board) public boards; @@ -34,11 +34,16 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { ////////////////////////////// /// Constructor ////////////////////////////// - constructor(address token_, address operator_, string memory name_, string memory symbol_) ERC721(name_, symbol_) { + constructor( + address currency_, + address operator_, + string memory name_, + string memory symbol_ + ) ERC721(name_, symbol_) { require(operator_ != address(0), "Zero address"); - require(token_ != address(0), "Zero address"); + require(currency_ != address(0), "Zero address"); operator = operator_; - token = IERC20(token_); + currency = IERC20(currency_); } ////////////////////////////// @@ -205,6 +210,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { string calldata redirectURI_ ) external isFromOperator { Bid memory _bid = bids[tokenId_][epoch_][bidder_]; + require(_bid.createdAt != 0, "Bid not found"); _bid.contentURI = contentURI_; _bid.redirectURI = redirectURI_; @@ -249,9 +255,9 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { } /// @inheritdoc IBillboardRegistry - function transferTokenByOperator(address to_, uint256 amount_) external isFromOperator { + function transferCurrencyByOperator(address to_, uint256 amount_) external isFromOperator { require(to_ != address(0), "Zero address"); - require(token.transfer(to_, amount_), "Failed token transfer"); + require(currency.transfer(to_, amount_), "Failed token transfer"); } ////////////////////////////// diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index 5ed81f3..c9915d0 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -8,19 +8,20 @@ import "./IBillboardRegistry.sol"; * @notice The on-chain billboard system transforms platform attention into NFT billboards based on Harberger tax auctions. Empowering creators with a fair share of tax revenue through quadratic voting. * * ## Billboard - * - User (whitelisted) can mint a billboard: call `mintBoard`. - * - Owner of a billboard can set the AD data of a billboard: call `setBoardName`, `setBoardDescription` and `setBoardLocation`. - * - Tenant of a billboard can set the AD data of a billboard: call `setBoardContentURI` and `setBoardRedirectURI`. + * - User can mint a billboard: `mintBoard`. + * - Creator, who mints the billboard, can set the metadata: `setBoard`. + * - Tenant, who wins the auction, can set the AD data: `setBidURIs`. * * ## Auction & Bid - * - User needs to call `approve` on currency (USDT) contract before starting. - * - User can place a bid on a billboard: call `placeBid`. - * - User can clear auction on a billboard: call `clearAuction`. - * - User can withdraw bid from a billboard: call `withdrawBid`. + * - Creator can set a epoch interval when `mintBoard`. + * - User needs to call `approve` on currency (e.g. USDT) contract before starting. + * - User can place a bid on a billboard: `placeBid`. + * - User can clear auction on a billboard: `clearAuction`. + * - User can withdraw a bid: `withdrawBid`. * * ## Tax - * - Admin of this contract can set global tax rate: call `setTaxRate`. - * - Owner of a billbaord can withdraw tax: call `withdrawTax`. + * - Creator can set a tax rate when `mintBoard`. + * - Creator can withdraw tax: `withdrawTax`. * * @dev This contract holds the logic, while read from and write into {BillboardRegistry}, which is the storage contact. * @dev This contract use the {BillboardRegistry} contract for storage, and can be updated by transfering ownership to a new implementation contract. @@ -45,17 +46,17 @@ interface IBillboard { * @notice Add address to whitelist. * * @param tokenId_ Token ID. - * @param address_ Address of user will be added into whitelist. + * @param account_ Address of user will be added into whitelist. */ - function addToWhitelist(uint256 tokenId_, address address_) external; + function addToWhitelist(uint256 tokenId_, address account_) external; /** * @notice Remove address from whitelist. * * @param tokenId_ Token ID. - * @param address_ Address of user will be removed from whitelist. + * @param account_ Address of user will be removed from whitelist. */ - function removeFromWhitelist(uint256 tokenId_, address address_) external; + function removeFromWhitelist(uint256 tokenId_, address account_) external; ////////////////////////////// /// Board @@ -64,13 +65,12 @@ interface IBillboard { /** * @notice Mint a new board (NFT). * - * @param to_ Address of the board owner. - * @param taxRate_ Tax rate of the new board. - * @param epochInterval_ Epoch interval of the new board. + * @param taxRate_ Tax rate per epoch. (e.g. 1024 for 10.24% per epoch) + * @param epochInterval_ Epoch interval in blocks (e.g. 100 for 100 blocks). * * @return tokenId Token ID of the new board. */ - function mintBoard(address to_, uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId); + function mintBoard(uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId); /** * @notice Get metadata of a board . @@ -158,6 +158,21 @@ interface IBillboard { string calldata redirectURI_ ) external payable; + /** + * @notice Set the content URI and redirect URI of a board. + * + * @param tokenId_ Token ID of a board. + * @param epoch_ Epoch. + * @param contentURI_ Content URI of a board. + * @param redirectURI_ Redirect URI of a board. + */ + function setBidURIs( + uint256 tokenId_, + uint256 epoch_, + string calldata contentURI_, + string calldata redirectURI_ + ) external; + /** * @notice Get bid of a board auction. * diff --git a/src/Billboard/IBillboardRegistry.sol b/src/Billboard/IBillboardRegistry.sol index 4237a3f..546a761 100644 --- a/src/Billboard/IBillboardRegistry.sol +++ b/src/Billboard/IBillboardRegistry.sol @@ -310,7 +310,7 @@ interface IBillboardRegistry is IERC721 { * @param to_ Address of a receiver. * @param amount_ Amount. */ - function transferTokenByOperator(address to_, uint256 amount_) external; + function transferCurrencyByOperator(address to_, uint256 amount_) external; ////////////////////////////// /// Event emission From 1dd56645d2ce8d854c801c3af8e5192491aa494a Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Sun, 30 Jun 2024 16:58:27 +0800 Subject: [PATCH 06/24] test(billboard): revise BillboardTestBase --- src/test/Billboard/BillboardTestBase.t.sol | 125 ++++++++++----------- 1 file changed, 61 insertions(+), 64 deletions(-) diff --git a/src/test/Billboard/BillboardTestBase.t.sol b/src/test/Billboard/BillboardTestBase.t.sol index 67d6bd5..781bbaf 100644 --- a/src/test/Billboard/BillboardTestBase.t.sol +++ b/src/test/Billboard/BillboardTestBase.t.sol @@ -1,81 +1,78 @@ //SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -// import "forge-std/console.sol"; -// import "forge-std/Test.sol"; -// import "forge-std/Vm.sol"; +import "forge-std/console.sol"; +import "forge-std/Test.sol"; +import "forge-std/Vm.sol"; -// import {USDT} from "../utils/USDT.sol"; -// import {Billboard} from "../../Billboard/Billboard.sol"; -// import {BillboardRegistry} from "../../Billboard/BillboardRegistry.sol"; -// import {IBillboard} from "../../Billboard/IBillboard.sol"; -// import {IBillboardRegistry} from "../../Billboard/IBillboardRegistry.sol"; +import {USDT} from "../utils/USDT.sol"; +import {Billboard} from "../../Billboard/Billboard.sol"; +import {BillboardRegistry} from "../../Billboard/BillboardRegistry.sol"; +import {IBillboard} from "../../Billboard/IBillboard.sol"; +import {IBillboardRegistry} from "../../Billboard/IBillboardRegistry.sol"; -// contract BillboardTestBase is Test { -// Billboard internal operator; -// BillboardRegistry internal registry; -// USDT internal usdt; +contract BillboardTestBase is Test { + Billboard internal operator; + BillboardRegistry internal registry; + USDT internal usdt; -// uint256 constant TAX_RATE = 1; // 1% per lease term -// uint64 constant LEASE_TERM = 100; // 100 blocks + uint256 constant TAX_RATE = 1024; // 10.24% per epoch + uint256 constant EPOCH_INTERVAL = 100; // 100 blocks -// address constant ZERO_ADDRESS = address(0); -// address constant FAKE_CONTRACT = address(1); + address constant ZERO_ADDRESS = address(0); + address constant FAKE_CONTRACT = address(1); -// /// Deployer and admin could be the same one -// address constant ADMIN = address(100); -// address constant USER_A = address(101); -// address constant USER_B = address(102); -// address constant USER_C = address(103); -// address constant ATTACKER = address(200); + /// Deployer and admin could be the same one + address constant ADMIN = address(100); + address constant USER_A = address(101); + address constant USER_B = address(102); + address constant USER_C = address(103); + address constant ATTACKER = address(200); -// function setUp() public { -// vm.startPrank(ADMIN); + function setUp() public { + vm.startPrank(ADMIN); -// // deploy USDT -// usdt = new USDT(ADMIN, 0); + // deploy USDT + usdt = new USDT(ADMIN, 0); -// // deploy operator & registry -// operator = new Billboard(address(usdt), payable(address(0)), ADMIN, TAX_RATE, LEASE_TERM, "Billboard", "BLBD"); -// registry = operator.registry(); -// assertEq(operator.admin(), ADMIN); -// assertEq(registry.operator(), address(operator)); -// assertEq(registry.name(), "Billboard"); -// assertEq(registry.symbol(), "BLBD"); + // deploy operator & registry + operator = new Billboard(address(usdt), payable(address(0)), ADMIN, "Billboard", "BLBD"); + registry = operator.registry(); + assertEq(operator.admin(), ADMIN); + assertEq(registry.operator(), address(operator)); + assertEq(registry.name(), "Billboard"); + assertEq(registry.symbol(), "BLBD"); -// vm.stopPrank(); + vm.stopPrank(); -// // approve USDT -// uint256 MAX_ALLOWANCE = type(uint256).max; -// vm.prank(ADMIN); -// usdt.approve(address(operator), MAX_ALLOWANCE); -// vm.prank(USER_A); -// usdt.approve(address(operator), MAX_ALLOWANCE); -// vm.prank(USER_B); -// usdt.approve(address(operator), MAX_ALLOWANCE); -// vm.prank(USER_C); -// usdt.approve(address(operator), MAX_ALLOWANCE); -// } + // approve USDT + uint256 MAX_ALLOWANCE = type(uint256).max; + vm.prank(ADMIN); + usdt.approve(address(operator), MAX_ALLOWANCE); + vm.prank(USER_A); + usdt.approve(address(operator), MAX_ALLOWANCE); + vm.prank(USER_B); + usdt.approve(address(operator), MAX_ALLOWANCE); + vm.prank(USER_C); + usdt.approve(address(operator), MAX_ALLOWANCE); + } -// function _mintBoard() public returns (uint256 tokenId) { -// vm.prank(ADMIN); -// tokenId = operator.mintBoard(ADMIN); -// } + function _mintBoard() public returns (uint256 tokenId) { + vm.prank(ADMIN); + tokenId = operator.mintBoard(TAX_RATE, EPOCH_INTERVAL); + } -// function _mintBoardAndPlaceBid() public returns (uint256 tokenId, uint256 _nextAuctionId) { -// tokenId = _mintBoard(); + function _mintBoardAndPlaceBid() public returns (uint256 tokenId_, uint256 epoch_) { + tokenId_ = _mintBoard(); -// // (new board) ADMIN places first bid and takes the ownership -// vm.startPrank(ADMIN); -// operator.placeBid(tokenId, 0); -// _nextAuctionId = registry.nextBoardAuctionId(tokenId); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(tokenId, _nextAuctionId); -// assertEq(_nextAuctionId, 1); -// assertEq(_auction.highestBidder, ADMIN); + // (new board) ADMIN places first bid and takes the ownership + vm.startPrank(ADMIN); + operator.placeBid(tokenId_, epoch_, 0); + assertEq(registry.higgestBidder(tokenId_, epoch_), ADMIN); -// // add USER_A and USER_B to whitelist -// operator.addToWhitelist(USER_A); -// operator.addToWhitelist(USER_B); -// vm.stopPrank(); -// } -// } + // add USER_A and USER_B to whitelist + operator.addToWhitelist(tokenId_, USER_A); + operator.addToWhitelist(tokenId_, USER_B); + vm.stopPrank(); + } +} From 380ba7ce303e61f23e7065226b03bd3ff9632a27 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:38:43 +0800 Subject: [PATCH 07/24] test(billboard): revise tests for board, ERC20 & ERC721 functions --- .gas-snapshot | 17 + src/Billboard/Billboard.sol | 43 +- src/Billboard/BillboardRegistry.sol | 13 +- src/Billboard/IBillboard.sol | 14 + src/Billboard/IBillboardRegistry.sol | 41 +- src/test/Billboard/BillboardTest.t.sol | 2002 ++++++++++++------------ 6 files changed, 1064 insertions(+), 1066 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 84bae87..d388780 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,6 +9,23 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) +BillboardTest:testAddToWhitelist() (gas: 220255) +BillboardTest:testApproveAndTransfer() (gas: 222603) +BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 193898) +BillboardTest:testCannotApproveByAttacker() (gas: 191753) +BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 193983) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 189023) +BillboardTest:testCannotSetBoardByAttacker() (gas: 195085) +BillboardTest:testCannotSetBoardByOwner() (gas: 329033) +BillboardTest:testCannotTransferByOperator() (gas: 194267) +BillboardTest:testCannotTransferToZeroAddress() (gas: 189754) +BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9016) +BillboardTest:testGetTokenURI() (gas: 358637) +BillboardTest:testMintBoard() (gas: 372377) +BillboardTest:testRemoveToWhitelist() (gas: 206806) +BillboardTest:testSafeTransferByOperator() (gas: 202755) +BillboardTest:testSetBoardByCreator() (gas: 307453) +BillboardTest:testUpgradeRegistry() (gas: 3311256) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 10e6d6e..4386efe 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/utils/Base64.sol"; import "./BillboardRegistry.sol"; import "./IBillboard.sol"; @@ -13,7 +14,7 @@ contract Billboard is IBillboard { address public immutable admin; // tokenId => address => whitelisted - mapping(uint256 => mapping(address => bool)) public boardWhitelist; + mapping(uint256 => mapping(address => bool)) public whitelist; constructor(address token_, address payable registry_, address admin_, string memory name_, string memory symbol_) { require(admin_ != address(0), "Zero address"); @@ -39,7 +40,7 @@ contract Billboard is IBillboard { } modifier isFromWhitelist(uint256 tokenId_) { - require(boardWhitelist[tokenId_][msg.sender], "Whitelist"); + require(whitelist[tokenId_][msg.sender], "Whitelist"); _; } @@ -69,12 +70,12 @@ contract Billboard is IBillboard { /// @inheritdoc IBillboard function addToWhitelist(uint256 tokenId_, address account_) external isFromCreator(tokenId_) { - boardWhitelist[tokenId_][account_] = true; + whitelist[tokenId_][account_] = true; } /// @inheritdoc IBillboard function removeFromWhitelist(uint256 tokenId_, address account_) external isFromCreator(tokenId_) { - boardWhitelist[tokenId_][account_] = false; + whitelist[tokenId_][account_] = false; } ////////////////////////////// @@ -358,4 +359,38 @@ contract Billboard is IBillboard { return amount; } + + ////////////////////////////// + /// ERC721 related + ////////////////////////////// + + /// @inheritdoc IBillboard + function _tokenURI(uint256 tokenId_) external view returns (string memory uri) { + require(msg.sender == address(registry), "Unauthorized"); + require(registry.exists(tokenId_), "Token not found"); + + IBillboardRegistry.Board memory _board = registry.getBoard(tokenId_); + + string memory tokenName = string(abi.encodePacked(registry.name(), " #", Strings.toString(tokenId_))); + + string memory json = Base64.encode( + bytes( + string( + abi.encodePacked( + '{"name": "', + tokenName, + '", "description": "', + _board.description, + '", "location": "', + _board.location, + '", "image": "', + _board.imageURI, + '"}' + ) + ) + ) + ); + + uri = string(abi.encodePacked("data:application/json;base64,", json)); + } } diff --git a/src/Billboard/BillboardRegistry.sol b/src/Billboard/BillboardRegistry.sol index 2dda923..bfa46ba 100644 --- a/src/Billboard/BillboardRegistry.sol +++ b/src/Billboard/BillboardRegistry.sol @@ -5,6 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; +import "./IBillboard.sol"; import "./IBillboardRegistry.sol"; contract BillboardRegistry is IBillboardRegistry, ERC721 { @@ -246,7 +247,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { } ////////////////////////////// - /// Transfer + /// ERC20 & ERC721 related ////////////////////////////// /// @inheritdoc IBillboardRegistry @@ -260,16 +261,16 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { require(currency.transfer(to_, amount_), "Failed token transfer"); } - ////////////////////////////// - /// ERC721 Overrides - ////////////////////////////// + /// @inheritdoc IBillboardRegistry + function exists(uint256 tokenId_) external view returns (bool) { + return _exists(tokenId_); + } /** * @notice See {IERC721-tokenURI}. */ function tokenURI(uint256 tokenId_) public view override(ERC721) returns (string memory uri) { - // TODO - // return boards[tokenId_].contentURI; + uri = IBillboard(operator)._tokenURI(tokenId_); } /** diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index c9915d0..d5c2f48 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -264,4 +264,18 @@ interface IBillboard { * */ function withdrawTax() external returns (uint256 tax); + + ////////////////////////////// + /// ERC721 related + ////////////////////////////// + + /** + * @notice Get token URI by registry contract. + * + * @dev Access: only registry. + * + * @param tokenId_ Token id to be transferred. + * @return uri Base64 encoded URI. + */ + function _tokenURI(uint256 tokenId_) external view returns (string memory uri); } diff --git a/src/Billboard/IBillboardRegistry.sol b/src/Billboard/IBillboardRegistry.sol index 546a761..60a990c 100644 --- a/src/Billboard/IBillboardRegistry.sol +++ b/src/Billboard/IBillboardRegistry.sol @@ -292,44 +292,49 @@ interface IBillboardRegistry is IERC721 { function setTaxTreasury(address owner_, uint256 accumulated_, uint256 withdrawn_) external; ////////////////////////////// - /// Transfer + /// Event emission ////////////////////////////// /** - * @notice Transfer a board (NFT). + * @notice Emit `AuctionCleared` event. * - * @param from_ Address of the board sender. - * @param to_ Address of the board receiver. - * @param tokenId_ Token ID of the board. + * @param tokenId_ Token ID of a board. + * @param epoch_ Epoch of an auction. + * @param highestBidder_ Highest bidder of an auction. */ - function safeTransferByOperator(address from_, address to_, uint256 tokenId_) external; + function emitAuctionCleared(uint256 tokenId_, uint256 epoch_, address highestBidder_) external; /** - * @notice Transfer amount of token to a receiver. + * @notice Emit `TaxWithdrawn` event. * - * @param to_ Address of a receiver. + * @param owner_ Address of a treasury owner. * @param amount_ Amount. */ - function transferCurrencyByOperator(address to_, uint256 amount_) external; + function emitTaxWithdrawn(address owner_, uint256 amount_) external; ////////////////////////////// - /// Event emission + /// ERC20 & ERC721 related ////////////////////////////// /** - * @notice Emit `AuctionCleared` event. + * @notice Transfer a board (NFT). * - * @param tokenId_ Token ID of a board. - * @param epoch_ Epoch of an auction. - * @param highestBidder_ Highest bidder of an auction. + * @param from_ Address of the board sender. + * @param to_ Address of the board receiver. + * @param tokenId_ Token ID of the board. */ - function emitAuctionCleared(uint256 tokenId_, uint256 epoch_, address highestBidder_) external; + function safeTransferByOperator(address from_, address to_, uint256 tokenId_) external; /** - * @notice Emit `TaxWithdrawn` event. + * @notice Transfer amount of token to a receiver. * - * @param owner_ Address of a treasury owner. + * @param to_ Address of a receiver. * @param amount_ Amount. */ - function emitTaxWithdrawn(address owner_, uint256 amount_) external; + function transferCurrencyByOperator(address to_, uint256 amount_) external; + + /** + * @dev If an ERC721 token has been minted. + */ + function exists(uint256 tokenId_) external view returns (bool); } diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 4c17315..57c6f69 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -1,1060 +1,986 @@ //SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -// import "./BillboardTestBase.t.sol"; -// import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -// contract BillboardTest is BillboardTestBase { -// ////////////////////////////// -// /// Upgradability -// ////////////////////////////// - -// function testUpgradeRegistry() public { -// vm.startPrank(ADMIN); - -// // deploy new operator -// Billboard newOperator = new Billboard( -// address(usdt), -// payable(registry), -// ADMIN, -// TAX_RATE, -// LEASE_TERM, -// "Billboard2", -// "BLBD2" -// ); -// assertEq(newOperator.admin(), ADMIN); -// assertEq(registry.name(), "Billboard"); // registry is not changed -// assertEq(registry.symbol(), "BLBD"); // registry is not changed - -// // upgrade registry's operator -// assertEq(registry.operator(), address(operator)); -// operator.setRegistryOperator(address(newOperator)); -// assertEq(registry.operator(), address(newOperator)); -// } - -// function testCannotUpgradeRegistryByAttacker() public { -// vm.startPrank(ATTACKER); - -// vm.expectRevert("Admin"); -// operator.setRegistryOperator(FAKE_CONTRACT); -// } - -// ////////////////////////////// -// /// Access control -// ////////////////////////////// - -// function testSetIsOpened() public { -// vm.startPrank(ADMIN); - -// operator.setIsOpened(true); -// assertEq(operator.isOpened(), true); - -// operator.setIsOpened(false); -// assertEq(operator.isOpened(), false); -// } - -// function testCannotSetIsOpenedByAttacker() public { -// vm.startPrank(ATTACKER); - -// vm.expectRevert("Admin"); -// operator.setIsOpened(true); -// } - -// function testAddToWhitelist() public { -// vm.startPrank(ADMIN); - -// operator.addToWhitelist(USER_A); -// assertEq(operator.whitelist(USER_A), true); -// assertEq(operator.whitelist(USER_B), false); -// } - -// function testCannotAddToWhitelistByAttacker() public { -// vm.startPrank(ATTACKER); - -// vm.expectRevert("Admin"); -// operator.addToWhitelist(USER_A); -// } - -// function testRemoveToWhitelist() public { -// vm.startPrank(ADMIN); - -// operator.addToWhitelist(USER_A); -// assertEq(operator.whitelist(USER_A), true); - -// operator.removeFromWhitelist(USER_A); -// assertEq(operator.whitelist(USER_A), false); -// } - -// function testCannotRemoveToWhitelistByAttacker() public { -// vm.startPrank(ATTACKER); - -// vm.expectRevert("Admin"); -// operator.removeFromWhitelist(USER_B); -// } - -// ////////////////////////////// -// /// Board -// ////////////////////////////// - -// function testMintBoard() public { -// vm.startPrank(ADMIN); - -// // mint -// vm.expectEmit(true, true, true, true); -// emit IERC721.Transfer(address(0), ADMIN, 1); -// operator.mintBoard(ADMIN); -// assertEq(registry.balanceOf(ADMIN), 1); - -// // ownership -// assertEq(registry.ownerOf(1), ADMIN); - -// // get board & check data -// IBillboardRegistry.Board memory board = operator.getBoard(1); -// assertEq(board.creator, ADMIN); -// assertEq(board.name, ""); -// assertEq(board.description, ""); -// assertEq(board.location, ""); -// assertEq(board.contentURI, ""); -// assertEq(board.redirectURI, ""); - -// // mint again for checking id generator -// vm.expectEmit(true, true, true, true); -// emit IERC721.Transfer(address(0), ADMIN, 2); -// operator.mintBoard(ADMIN); -// assertEq(registry.balanceOf(ADMIN), 2); -// board = operator.getBoard(2); -// assertEq(board.creator, ADMIN); -// } +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/utils/Base64.sol"; -// function testMintBoardIfOpened() public { -// vm.startPrank(ADMIN); -// operator.setIsOpened(true); +import "./BillboardTestBase.t.sol"; -// vm.startPrank(USER_A); -// operator.mintBoard(USER_A); -// assertEq(registry.balanceOf(USER_A), 1); -// } +contract BillboardTest is BillboardTestBase { + ////////////////////////////// + /// Upgradability + ////////////////////////////// -// function testMintBoardByWhitelist() public { -// vm.prank(USER_A); -// vm.expectRevert("Whitelist"); -// operator.mintBoard(USER_A); + function testUpgradeRegistry() public { + vm.startPrank(ADMIN); -// vm.prank(ADMIN); -// operator.addToWhitelist(USER_A); + // deploy new operator + Billboard newOperator = new Billboard(address(usdt), payable(registry), ADMIN, "Billboard2", "BLBD2"); + assertEq(newOperator.admin(), ADMIN); + assertEq(registry.name(), "Billboard"); // registry is not changed + assertEq(registry.symbol(), "BLBD"); // registry is not changed -// vm.prank(USER_A); -// operator.mintBoard(USER_A); -// assertEq(registry.balanceOf(USER_A), 1); -// } + // upgrade registry's operator + assertEq(registry.operator(), address(operator)); + operator.setRegistryOperator(address(newOperator)); + assertEq(registry.operator(), address(newOperator)); + } -// function testCannotMintBoardByAttacker() public { -// vm.startPrank(ATTACKER); + function testCannotUpgradeRegistryByAttacker() public { + vm.startPrank(ATTACKER); -// vm.expectRevert("Whitelist"); -// operator.mintBoard(ATTACKER); -// } + vm.expectRevert("Admin"); + operator.setRegistryOperator(FAKE_CONTRACT); + } -// function testSetBoardProperties() public { -// uint256 _tokenId = _mintBoard(); + ////////////////////////////// + /// Access control + ////////////////////////////// + + function testAddToWhitelist() public { + uint256 _tokenId = _mintBoard(); -// vm.startPrank(ADMIN); + vm.startPrank(ADMIN); -// vm.expectEmit(true, true, false, false); -// emit IBillboardRegistry.BoardNameUpdated(_tokenId, "name"); -// operator.setBoardName(_tokenId, "name"); + operator.addToWhitelist(_tokenId, USER_A); + assertEq(operator.whitelist(_tokenId, USER_A), true); -// vm.expectEmit(true, true, false, false); -// emit IBillboardRegistry.BoardDescriptionUpdated(_tokenId, "description"); -// operator.setBoardDescription(_tokenId, "description"); + assertEq(operator.whitelist(_tokenId, USER_B), false); + } -// vm.expectEmit(true, true, false, false); -// emit IBillboardRegistry.BoardLocationUpdated(_tokenId, "location"); -// operator.setBoardLocation(_tokenId, "location"); + function testCannotAddToWhitelistByAttacker() public { + uint256 _tokenId = _mintBoard(); -// vm.expectEmit(true, true, false, false); -// emit IBillboardRegistry.BoardContentURIUpdated(_tokenId, "uri"); -// operator.setBoardContentURI(_tokenId, "uri"); + vm.startPrank(ATTACKER); -// vm.expectEmit(true, true, false, false); -// emit IBillboardRegistry.BoardRedirectURIUpdated(_tokenId, "redirect URI"); -// operator.setBoardRedirectURI(_tokenId, "redirect URI"); + vm.expectRevert("Creator"); + operator.addToWhitelist(_tokenId, USER_A); + } -// IBillboardRegistry.Board memory board = operator.getBoard(1); -// assertEq(board.name, "name"); -// assertEq(board.description, "description"); -// assertEq(board.location, "location"); -// assertEq(board.contentURI, "uri"); -// assertEq(board.redirectURI, "redirect URI"); -// } + function testRemoveToWhitelist() public { + uint256 _tokenId = _mintBoard(); -// function testCannotSetBoardProprtiesByAttacker() public { -// uint256 _tokenId = _mintBoard(); + vm.startPrank(ADMIN); -// vm.startPrank(ATTACKER); + operator.addToWhitelist(_tokenId, USER_A); + assertEq(operator.whitelist(_tokenId, USER_A), true); + + operator.removeFromWhitelist(_tokenId, USER_A); + assertEq(operator.whitelist(_tokenId, USER_A), false); + } + + function testCannotRemoveToWhitelistByAttacker() public { + uint256 _tokenId = _mintBoard(); + + vm.startPrank(ATTACKER); + + vm.expectRevert("Creator"); + operator.removeFromWhitelist(_tokenId, USER_B); + } + + ////////////////////////////// + /// Board + ////////////////////////////// + + function testMintBoard() public { + vm.startPrank(ADMIN); + + // mint + vm.expectEmit(true, true, true, true); + emit IERC721.Transfer(address(0), ADMIN, 1); + operator.mintBoard(TAX_RATE, EPOCH_INTERVAL); + + // ownership + assertEq(registry.balanceOf(ADMIN), 1); + assertEq(registry.ownerOf(1), ADMIN); + + // data + IBillboardRegistry.Board memory board = operator.getBoard(1); + assertEq(board.creator, ADMIN); + assertEq(board.name, ""); + assertEq(board.description, ""); + assertEq(board.imageURI, ""); + assertEq(board.location, ""); + assertEq(board.taxRate, TAX_RATE); + assertEq(board.epochInterval, EPOCH_INTERVAL); + assertEq(board.createdAt, block.number); + + vm.stopPrank(); + vm.startPrank(USER_A); + + // mint by user and check token id + uint256 _newTokenId = 2; + vm.expectEmit(true, true, true, true); + emit IERC721.Transfer(address(0), USER_A, _newTokenId); + uint256 _tokenId = operator.mintBoard(TAX_RATE, EPOCH_INTERVAL); + assertEq(_tokenId, _newTokenId); + assertEq(registry.balanceOf(USER_A), 1); + board = operator.getBoard(_tokenId); + assertEq(board.creator, USER_A); + } + + function testSetBoardByCreator() public { + uint256 _tokenId = _mintBoard(); + string memory _name = "name"; + string memory _description = "description"; + string memory _imageURI = "image URI"; + string memory _location = "location"; + + vm.expectEmit(true, true, true, true); + emit IBillboardRegistry.BoardUpdated(_tokenId, _name, _description, _imageURI, _location); + + vm.startPrank(ADMIN); + operator.setBoard(_tokenId, _name, _description, _imageURI, _location); + + IBillboardRegistry.Board memory board = operator.getBoard(_tokenId); + assertEq(board.name, _name); + assertEq(board.description, _description); + assertEq(board.imageURI, _imageURI); + assertEq(board.location, _location); + } + + function testCannotSetBoardByAttacker() public { + uint256 _tokenId = _mintBoard(); + + vm.startPrank(ATTACKER); + + vm.expectRevert("Creator"); + operator.setBoard(_tokenId, "", "", "", ""); + } + + function testCannotSetBoardByOwner() public { + // mint + uint256 _tokenId = _mintBoard(); + + // transfer + vm.startPrank(ADMIN); + registry.transferFrom(ADMIN, USER_A, _tokenId); + + IBillboardRegistry.Board memory board = operator.getBoard(_tokenId); + assertEq(board.creator, ADMIN); + assertEq(registry.balanceOf(ADMIN), 0); + assertEq(registry.ownerOf(_tokenId), USER_A); + + // cannot set board by new owner + vm.stopPrank(); + vm.startPrank(USER_A); + vm.expectRevert("Creator"); + operator.setBoard(_tokenId, "name", "description", "image URI", "location"); + + // can set board by creator + vm.stopPrank(); + vm.startPrank(ADMIN); + vm.expectEmit(true, true, true, true); + emit IBillboardRegistry.BoardUpdated(_tokenId, "name", "description", "image URI", "location"); + operator.setBoard(_tokenId, "name", "description", "image URI", "location"); + } + + ////////////////////////////// + /// Auction + ////////////////////////////// + + // function testPlaceBidOnNewBoard(uint96 _amount) public { + // vm.prank(ADMIN); + // operator.addToWhitelist(USER_A); + + // vm.expectEmit(true, false, false, false); + // emit IERC721.Transfer(address(0), ADMIN, 1); + + // uint256 _tokenId = _mintBoard(); + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _overpaid = 0.1 ether; + // uint256 _total = _amount + _tax; + // deal(address(usdt), USER_A, _total + _overpaid); + + // uint256 _prevNextActionId = registry.nextBoardAuctionId(_tokenId); + // uint256 _prevCreatorBalance = usdt.balanceOf(ADMIN); + // uint256 _prevBidderBalance = usdt.balanceOf(USER_A); + // uint256 _prevOperatorBalance = usdt.balanceOf(address(operator)); + // uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); + + // vm.expectEmit(true, true, true, true); + // emit IBillboardRegistry.AuctionCreated( + // _tokenId, + // _prevNextActionId + 1, + // uint64(block.number), + // uint64(block.number) + // ); + // vm.expectEmit(true, true, true, true); + // emit IBillboardRegistry.BidCreated(_tokenId, _prevNextActionId + 1, USER_A, _amount, _tax); + // vm.expectEmit(true, true, true, true); + // emit IBillboardRegistry.BidWon(_tokenId, _prevNextActionId + 1, USER_A); + // vm.expectEmit(true, true, true, true); + // emit IBillboardRegistry.AuctionCleared( + // _tokenId, + // _prevNextActionId + 1, + // USER_A, + // uint64(block.number), + // uint64(block.number + registry.leaseTerm()) + // ); + + // vm.prank(USER_A); + // operator.placeBid(_tokenId, _amount); + + // // check balances + // assertEq(usdt.balanceOf(ADMIN), _prevCreatorBalance + _amount); + // assertEq(usdt.balanceOf(USER_A), _prevBidderBalance - _total); + // assertEq(usdt.balanceOf(address(operator)), _prevOperatorBalance); + // assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance + _tax); + + // // check auction + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_prevNextActionId, 0); + // assertEq(_nextAuctionId, _prevNextActionId + 1); + // assertEq(_auction.startAt, block.number); + // assertEq(_auction.endAt, block.number); + // assertEq(_auction.leaseStartAt, block.number); + // assertEq(_auction.leaseEndAt, block.number + registry.leaseTerm()); + // assertEq(_auction.highestBidder, USER_A); + + // // check bid + // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); + // assertEq(_bid.price, _amount); + // assertEq(_bid.tax, _tax); + // assertEq(_bid.placedAt, block.number); + // assertEq(_bid.isWon, true); + // assertEq(_bid.isWithdrawn, false); + // } + + // function testPlaceBidWithSamePrices(uint96 _amount) public { + // (uint256 _tokenId, uint256 _prevNextAuctionId) = _mintBoardAndPlaceBid(); + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // // new auction and new bid with USER_A + // deal(address(usdt), USER_A, _total); + // vm.prank(USER_A); + // operator.placeBid(_tokenId, _amount); + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // assertEq(_nextAuctionId, _prevNextAuctionId + 1); + // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.highestBidder, USER_A); + + // // new bid with USER_B + // deal(address(usdt), USER_B, _total); + // vm.prank(USER_B); + // operator.placeBid(_tokenId, _amount); + // _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // assertEq(_nextAuctionId, _prevNextAuctionId + 1); // still the same auction + // _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.highestBidder, USER_A); // USER_A is still the same highest bidder + + // // check if bids exist + // IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _nextAuctionId, USER_A); + // assertEq(_bidA.placedAt, block.number); + // assertEq(_bidA.isWon, false); + // IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _nextAuctionId, USER_A); + // assertEq(_bidB.placedAt, block.number); + // assertEq(_bidB.isWon, false); + + // // check registry balance + // assertEq(usdt.balanceOf(address(registry)), _total * 2); + // } + + // function testPlaceBidWithHigherPrice(uint96 _amount) public { + // vm.assume(_amount > 0); + // vm.assume(_amount < type(uint96).max / 2); + + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // // bid with USER_A + // deal(address(usdt), USER_A, _total); + // vm.prank(USER_A); + // operator.placeBid(_tokenId, _amount); + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.highestBidder, USER_A); + + // // bid with USER_B + // _amount = _amount * 2; + // _tax = operator.calculateTax(_amount); + // _total = _amount + _tax; + // deal(address(usdt), USER_B, _total); + // vm.startPrank(USER_B); + // operator.placeBid(_tokenId, _amount); + // _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.highestBidder, USER_B); + // } + + // function testPlaceBidZeroPrice() public { + // uint256 _tokenId = _mintBoard(); + + // vm.startPrank(ADMIN); + // uint256 _prevBalance = usdt.balanceOf(ADMIN); + + // operator.placeBid(_tokenId, 0); + + // // check balances + // uint256 _afterBalance = usdt.balanceOf(ADMIN); + // assertEq(_afterBalance, _prevBalance); + // assertEq(usdt.balanceOf(address(operator)), 0); + // assertEq(usdt.balanceOf(address(registry)), 0); + + // // check auction + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.highestBidder, ADMIN); + + // // check bid + // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, ADMIN); + // assertEq(_bid.placedAt, block.number); + // assertEq(_bid.isWon, true); + // } + + // function testPlaceBidByWhitelist() public { + // uint256 _tokenId = _mintBoard(); + // uint256 _amount = 1 ether; + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // vm.prank(ADMIN); + // operator.addToWhitelist(USER_A); + + // deal(address(usdt), USER_A, _total); + // vm.prank(USER_A); + // operator.placeBid(_tokenId, _amount); + // assertEq(usdt.balanceOf(USER_A), 0); + // } + + // function testPlaceBidIfAuctionEnded() public { + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + // uint256 _amount = 1 ether; + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // // place a bid with USER_A + // vm.startPrank(USER_A); + // deal(address(usdt), USER_A, _total); + // operator.placeBid(_tokenId, _amount); + + // // check auction + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.highestBidder, USER_A); + // assertEq(_auction.endAt, block.number + registry.leaseTerm()); + + // // make auction ended + // vm.roll(_auction.endAt + 1); + + // // place a bid with USER_B + // vm.startPrank(USER_B); + // deal(address(usdt), USER_B, _total); + // operator.placeBid(_tokenId, _amount); + + // // check auction + // uint256 _newNextAuctionId = registry.nextBoardAuctionId(_tokenId); + // IBillboardRegistry.Auction memory _newAuction = registry.getAuction(_tokenId, _newNextAuctionId); + // assertEq(_newNextAuctionId, _nextAuctionId + 1); + // assertEq(_newAuction.highestBidder, USER_B); + // assertEq(_newAuction.endAt, block.number + registry.leaseTerm()); + + // // USER_A won the previous auction + // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); + // assertEq(_bid.isWon, true); + + // // USER_B's bid is still in a running auction + // IBillboardRegistry.Bid memory _newBid = registry.getBid(_tokenId, _newNextAuctionId, USER_B); + // assertEq(_newBid.isWon, false); + // } + + // function testCannotPlaceBidTwice(uint96 _amount) public { + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // vm.startPrank(USER_A); + // deal(address(usdt), USER_A, _total); + // operator.placeBid(_tokenId, _amount); + // assertEq(usdt.balanceOf(USER_A), 0); + + // deal(address(usdt), USER_A, _total); + // vm.expectRevert("Bid already placed"); + // operator.placeBid(_tokenId, _amount); + // } + + // function testCannotPlaceBidByAttacker() public { + // uint256 _tokenId = _mintBoard(); + // uint256 _amount = 1 ether; + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // vm.startPrank(ATTACKER); + // deal(address(usdt), ATTACKER, _total); + // vm.expectRevert("Whitelist"); + // operator.placeBid(_tokenId, _amount); + // } + + // function testClearAuctionIfAuctionEnded(uint96 _amount) public { + // vm.assume(_amount > 0.001 ether); + + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); + // uint64 _placedAt = uint64(block.number); + // uint64 _clearedAt = uint64(block.number) + registry.leaseTerm() + 1; + + // // place a bid + // vm.startPrank(USER_A); + // deal(address(usdt), USER_A, _total); + // operator.placeBid(_tokenId, _amount); + + // // clear auction + // vm.expectEmit(true, true, true, true); + // emit IBillboardRegistry.AuctionCleared( + // _tokenId, + // _prevAuctionId + 1, + // USER_A, + // _clearedAt, + // _clearedAt + registry.leaseTerm() + // ); + + // vm.roll(_clearedAt); + // (uint256 _price1, uint256 _tax1) = operator.clearAuction(_tokenId); + // assertEq(_price1, _amount); + // assertEq(_tax1, _tax); + + // // check auction + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.startAt, _placedAt); + // assertEq(_auction.endAt, _placedAt + registry.leaseTerm()); + // assertEq(_auction.leaseStartAt, _clearedAt); + // assertEq(_auction.leaseEndAt, _clearedAt + registry.leaseTerm()); + // assertEq(_auction.highestBidder, USER_A); + + // // check bid + // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); + // assertEq(_bid.price, _amount); + // assertEq(_bid.tax, _tax); + // assertEq(_bid.placedAt, _placedAt); + // assertEq(_bid.isWon, true); + // assertEq(_bid.isWithdrawn, false); + // } + + // function testClearAuctionsIfAuctionEnded() public { + // (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); + // (uint256 _tokenId2, uint256 _prevAuctionId2) = _mintBoardAndPlaceBid(); + + // uint64 _placedAt = uint64(block.number); + // uint64 _clearedAt = uint64(block.number) + registry.leaseTerm() + 1; + + // // place bids + // vm.startPrank(USER_A); + // deal(address(usdt), USER_A, 0); + // operator.placeBid(_tokenId, 0); + + // vm.startPrank(USER_B); + // deal(address(usdt), USER_B, 0); + // operator.placeBid(_tokenId2, 0); + + // // clear auction + // vm.expectEmit(true, true, true, true); + // emit IBillboardRegistry.AuctionCleared( + // _tokenId, + // _prevAuctionId + 1, + // USER_A, + // _clearedAt, + // _clearedAt + registry.leaseTerm() + // ); + // vm.expectEmit(true, true, true, true); + // emit IBillboardRegistry.AuctionCleared( + // _tokenId2, + // _prevAuctionId2 + 1, + // USER_B, + // _clearedAt, + // _clearedAt + registry.leaseTerm() + // ); + + // vm.roll(_clearedAt); + + // uint256[] memory _tokenIds = new uint256[](2); + // _tokenIds[0] = _tokenId; + // _tokenIds[1] = _tokenId2; + // (uint256[] memory prices, uint256[] memory taxes) = operator.clearAuctions(_tokenIds); + // assertEq(prices[0], 0); + // assertEq(prices[1], 0); + // assertEq(taxes[0], 0); + // assertEq(taxes[1], 0); + + // // check auction + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.startAt, _placedAt); + // assertEq(_auction.endAt, _placedAt + registry.leaseTerm()); + // assertEq(_auction.leaseStartAt, _clearedAt); + // assertEq(_auction.leaseEndAt, _clearedAt + registry.leaseTerm()); + // assertEq(_auction.highestBidder, USER_A); + + // uint256 _nextAuctionId2 = registry.nextBoardAuctionId(_tokenId2); + // IBillboardRegistry.Auction memory _auction2 = registry.getAuction(_tokenId2, _nextAuctionId2); + // assertEq(_auction2.startAt, _placedAt); + // assertEq(_auction2.endAt, _placedAt + registry.leaseTerm()); + // assertEq(_auction2.leaseStartAt, _clearedAt); + // assertEq(_auction2.leaseEndAt, _clearedAt + registry.leaseTerm()); + // assertEq(_auction2.highestBidder, USER_B); + + // // check bid + // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); + // assertEq(_bid.price, 0); + // assertEq(_bid.tax, 0); + // assertEq(_bid.placedAt, _placedAt); + // assertEq(_bid.isWon, true); + // assertEq(_bid.isWithdrawn, false); + + // IBillboardRegistry.Bid memory _bid2 = registry.getBid(_tokenId2, _nextAuctionId2, USER_B); + // assertEq(_bid2.price, 0); + // assertEq(_bid2.tax, 0); + // assertEq(_bid2.placedAt, _placedAt); + // assertEq(_bid2.isWon, true); + // assertEq(_bid2.isWithdrawn, false); + // } + + // function testCannotClearAuctionOnNewBoard() public { + // uint256 _mintedAt = block.number; + // uint256 _clearedAt = _mintedAt + 1; + // uint256 _tokenId = _mintBoard(); + + // vm.startPrank(ADMIN); + + // // clear auction + // vm.roll(_clearedAt); + // vm.expectRevert("Auction not found"); + // operator.clearAuction(_tokenId); + // } + + // function testCannotClearAuctionIfAuctionNotEnded() public { + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + + // // place a bid + // vm.startPrank(USER_A); + // deal(address(usdt), USER_A, 0); + // operator.placeBid(_tokenId, 0); + + // // try to clear auction + // vm.expectRevert("Auction not ended"); + // operator.clearAuction(_tokenId); + + // vm.roll(block.number + registry.leaseTerm() - 1); + // vm.expectRevert("Auction not ended"); + // operator.clearAuction(_tokenId); + // } + + // function testGetBids(uint8 _bidCount, uint8 _limit, uint8 _offset) public { + // vm.assume(_bidCount > 0); + // vm.assume(_bidCount <= 64); + // vm.assume(_limit <= _bidCount); + // vm.assume(_offset <= _limit); + + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + + // for (uint8 i = 0; i < _bidCount; i++) { + // address _bidder = address(uint160(2000 + i)); + + // vm.prank(ADMIN); + // operator.addToWhitelist(_bidder); + + // uint256 _amount = 1 ether + i; + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _totalAmount = _amount + _tax; + + // deal(address(usdt), _bidder, _totalAmount); + // vm.startPrank(_bidder); + // usdt.approve(address(operator), _totalAmount); + // operator.placeBid(_tokenId, _amount); + // vm.stopPrank(); + // } + + // // get bids + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // (uint256 _t, uint256 _l, uint256 _o, IBillboardRegistry.Bid[] memory _bids) = operator.getBids( + // _tokenId, + // _nextAuctionId, + // _limit, + // _offset + // ); + // uint256 _left = _t - _offset; + // uint256 _size = _left > _limit ? _limit : _left; + // assertEq(_t, _bidCount); + // assertEq(_l, _limit); + // assertEq(_bids.length, _size); + // assertEq(_o, _offset); + // for (uint256 i = 0; i < _size; i++) { + // uint256 _amount = 1 ether + _offset + i; + // assertEq(_bids[i].price, _amount); + // } + // } + + // ////////////////////////////// + // /// Tax & Withdraw + // ////////////////////////////// + + // function testCalculateTax() public { + // uint256 _amount = 100; + // uint256 _taxRate = 10; // 10% per lease term + + // vm.startPrank(ADMIN); + // operator.setTaxRate(_taxRate); + + // uint256 _tax = operator.calculateTax(_amount); + // assertEq(_tax, (_amount * _taxRate) / 1000); + // } + + // function testSetTaxRate() public { + // vm.startPrank(ADMIN); + + // vm.expectEmit(true, true, true, true); + // emit IBillboardRegistry.TaxRateUpdated(2); + + // operator.setTaxRate(2); + // assertEq(operator.getTaxRate(), 2); + // } + + // function testCannotSetTaxRateByAttacker() public { + // vm.startPrank(ATTACKER); + + // vm.expectRevert("Admin"); + // operator.setTaxRate(2); + // } + + // function testWithdrawTax(uint96 _amount) public { + // vm.assume(_amount > 0.001 ether); + + // uint256 _tokenId = _mintBoard(); + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // vm.prank(ADMIN); + // operator.addToWhitelist(USER_A); + + // // place a bid and win auction + // deal(address(usdt), USER_A, _total); + // vm.prank(USER_A); + // operator.placeBid(_tokenId, _amount); + + // uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); + // uint256 _prevAdminBalance = usdt.balanceOf(ADMIN); + + // // withdraw tax + // vm.expectEmit(true, true, true, true); + // emit IBillboardRegistry.TaxWithdrawn(ADMIN, _tax); + + // vm.prank(ADMIN); + // operator.withdrawTax(); + + // // check balances + // assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance - _tax); + // assertEq(usdt.balanceOf(ADMIN), _prevAdminBalance + _tax); + // } + + // function testCannnotWithdrawTaxIfZero() public { + // uint256 _tokenId = _mintBoard(); + + // vm.prank(ADMIN); + // operator.addToWhitelist(USER_A); + + // // place a bid and win auction + // deal(address(usdt), USER_A, 0); + // vm.prank(USER_A); + // operator.placeBid(_tokenId, 0); + + // vm.prank(ADMIN); + // vm.expectRevert("Zero amount"); + // operator.withdrawTax(); + // } + + // function testCannnotWithdrawTaxIfSmallAmount(uint8 _amount) public { + // uint256 _tax = operator.calculateTax(_amount); + // vm.assume(_tax <= 0); + + // uint256 _tokenId = _mintBoard(); + + // vm.prank(ADMIN); + // operator.addToWhitelist(USER_A); + + // // place a bid and win auction + // deal(address(usdt), USER_A, _amount); + // vm.prank(USER_A); + // operator.placeBid(_tokenId, _amount); + + // vm.prank(ADMIN); + // vm.expectRevert("Zero amount"); + // operator.withdrawTax(); + // } + + // function testCannotWithdrawTaxByAttacker() public { + // vm.startPrank(ATTACKER); -// vm.expectRevert("Creator"); -// operator.setBoardName(_tokenId, "name"); + // vm.expectRevert("Zero amount"); + // operator.withdrawTax(); + // } -// vm.expectRevert("Creator"); -// operator.setBoardDescription(_tokenId, "description"); - -// vm.expectRevert("Creator"); -// operator.setBoardLocation(_tokenId, "location"); - -// vm.expectRevert("Tenant"); -// operator.setBoardContentURI(_tokenId, "uri"); - -// vm.expectRevert("Tenant"); -// operator.setBoardRedirectURI(_tokenId, "redirect URI"); -// } - -// function testGetTokenURI() public { -// uint256 _tokenId = _mintBoard(); - -// vm.startPrank(ADMIN); - -// operator.setBoardContentURI(_tokenId, "new uri"); -// assertEq(registry.tokenURI(_tokenId), "new uri"); -// } - -// function testSetBoardPropertiesAfterTransfer() public { -// // mint -// uint256 _tokenId = _mintBoard(); - -// // transfer -// vm.startPrank(ADMIN); -// registry.transferFrom(ADMIN, USER_A, _tokenId); - -// IBillboardRegistry.Board memory board = operator.getBoard(_tokenId); -// assertEq(board.creator, ADMIN); -// assertEq(registry.balanceOf(ADMIN), 0); -// assertEq(registry.ownerOf(_tokenId), USER_A); - -// // set board properties -// vm.stopPrank(); -// vm.startPrank(USER_A); - -// vm.expectRevert("Creator"); -// operator.setBoardName(_tokenId, "name by a"); - -// vm.expectRevert("Creator"); -// operator.setBoardDescription(_tokenId, "description by a"); - -// vm.expectRevert("Creator"); -// operator.setBoardLocation(_tokenId, "location by a"); - -// operator.setBoardContentURI(_tokenId, "uri by a"); -// operator.setBoardRedirectURI(_tokenId, "redirect URI by a"); - -// board = operator.getBoard(_tokenId); -// assertEq(board.name, ""); -// assertEq(board.description, ""); -// assertEq(board.location, ""); -// assertEq(board.contentURI, "uri by a"); -// assertEq(board.redirectURI, "redirect URI by a"); - -// // transfer board from user_a to user_b -// registry.safeTransferFrom(USER_A, USER_B, 1); -// board = operator.getBoard(_tokenId); -// assertEq(board.creator, ADMIN); -// assertEq(registry.ownerOf(_tokenId), USER_B); - -// vm.stopPrank(); -// vm.startPrank(USER_B); - -// vm.expectRevert("Creator"); -// operator.setBoardName(_tokenId, "name by b"); - -// vm.expectRevert("Creator"); -// operator.setBoardDescription(_tokenId, "description by b"); - -// vm.expectRevert("Creator"); -// operator.setBoardLocation(_tokenId, "location by b"); - -// operator.setBoardContentURI(_tokenId, "uri by b"); -// operator.setBoardRedirectURI(_tokenId, "redirect URI by b"); - -// board = operator.getBoard(_tokenId); -// assertEq(board.name, ""); -// assertEq(board.description, ""); -// assertEq(board.location, ""); -// assertEq(board.contentURI, "uri by b"); -// assertEq(board.redirectURI, "redirect URI by b"); -// } - -// function testCannotTransferToZeroAddress() public { -// uint256 _tokenId = _mintBoard(); - -// vm.startPrank(ADMIN); - -// vm.expectRevert("ERC721: transfer to the zero address"); -// registry.transferFrom(ADMIN, ZERO_ADDRESS, _tokenId); -// } - -// function testCannotTransferByOperator() public { -// uint256 _tokenId = _mintBoard(); - -// vm.startPrank(address(operator)); - -// vm.expectRevert("ERC721: caller is not token owner or approved"); -// registry.transferFrom(USER_B, USER_C, _tokenId); -// } - -// function testSafeTransferByOperator() public { -// uint256 _tokenId = _mintBoard(); - -// vm.expectEmit(true, true, true, true); -// emit IERC721.Transfer(ADMIN, USER_A, _tokenId); - -// vm.startPrank(address(operator)); -// registry.safeTransferByOperator(ADMIN, USER_A, _tokenId); -// assertEq(registry.ownerOf(_tokenId), USER_A); -// } - -// function testCannotSafeTransferByAttacker() public { -// uint256 _tokenId = _mintBoard(); - -// vm.startPrank(ATTACKER); - -// vm.expectRevert("Operator"); -// registry.safeTransferByOperator(ADMIN, ATTACKER, _tokenId); -// } - -// function testApproveAndTransfer() public { -// uint256 _tokenId = _mintBoard(); - -// vm.expectEmit(true, true, true, true); -// emit IERC721.Approval(ADMIN, USER_A, _tokenId); -// vm.prank(ADMIN); -// registry.approve(USER_A, _tokenId); -// assertEq(registry.getApproved(_tokenId), USER_A); - -// vm.expectEmit(true, true, true, true); -// emit IERC721.Transfer(ADMIN, USER_A, _tokenId); -// vm.prank(USER_A); -// registry.transferFrom(ADMIN, USER_A, _tokenId); - -// IBillboardRegistry.Board memory board = operator.getBoard(_tokenId); -// assertEq(board.creator, ADMIN); -// assertEq(registry.ownerOf(_tokenId), USER_A); -// } - -// function testCannotApproveByAttacker() public { -// uint256 _tokenId = _mintBoard(); - -// vm.stopPrank(); -// vm.startPrank(ATTACKER); -// vm.expectRevert("ERC721: approve caller is not token owner or approved for all"); -// registry.approve(USER_A, _tokenId); -// } - -// ////////////////////////////// -// /// Auction -// ////////////////////////////// - -// function testPlaceBidOnNewBoard(uint96 _amount) public { -// vm.prank(ADMIN); -// operator.addToWhitelist(USER_A); - -// vm.expectEmit(true, false, false, false); -// emit IERC721.Transfer(address(0), ADMIN, 1); - -// uint256 _tokenId = _mintBoard(); -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _overpaid = 0.1 ether; -// uint256 _total = _amount + _tax; -// deal(address(usdt), USER_A, _total + _overpaid); - -// uint256 _prevNextActionId = registry.nextBoardAuctionId(_tokenId); -// uint256 _prevCreatorBalance = usdt.balanceOf(ADMIN); -// uint256 _prevBidderBalance = usdt.balanceOf(USER_A); -// uint256 _prevOperatorBalance = usdt.balanceOf(address(operator)); -// uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); - -// vm.expectEmit(true, true, true, true); -// emit IBillboardRegistry.AuctionCreated( -// _tokenId, -// _prevNextActionId + 1, -// uint64(block.number), -// uint64(block.number) -// ); -// vm.expectEmit(true, true, true, true); -// emit IBillboardRegistry.BidCreated(_tokenId, _prevNextActionId + 1, USER_A, _amount, _tax); -// vm.expectEmit(true, true, true, true); -// emit IBillboardRegistry.BidWon(_tokenId, _prevNextActionId + 1, USER_A); -// vm.expectEmit(true, true, true, true); -// emit IBillboardRegistry.AuctionCleared( -// _tokenId, -// _prevNextActionId + 1, -// USER_A, -// uint64(block.number), -// uint64(block.number + registry.leaseTerm()) -// ); - -// vm.prank(USER_A); -// operator.placeBid(_tokenId, _amount); - -// // check balances -// assertEq(usdt.balanceOf(ADMIN), _prevCreatorBalance + _amount); -// assertEq(usdt.balanceOf(USER_A), _prevBidderBalance - _total); -// assertEq(usdt.balanceOf(address(operator)), _prevOperatorBalance); -// assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance + _tax); - -// // check auction -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_prevNextActionId, 0); -// assertEq(_nextAuctionId, _prevNextActionId + 1); -// assertEq(_auction.startAt, block.number); -// assertEq(_auction.endAt, block.number); -// assertEq(_auction.leaseStartAt, block.number); -// assertEq(_auction.leaseEndAt, block.number + registry.leaseTerm()); -// assertEq(_auction.highestBidder, USER_A); - -// // check bid -// IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); -// assertEq(_bid.price, _amount); -// assertEq(_bid.tax, _tax); -// assertEq(_bid.placedAt, block.number); -// assertEq(_bid.isWon, true); -// assertEq(_bid.isWithdrawn, false); -// } - -// function testPlaceBidWithSamePrices(uint96 _amount) public { -// (uint256 _tokenId, uint256 _prevNextAuctionId) = _mintBoardAndPlaceBid(); -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// // new auction and new bid with USER_A -// deal(address(usdt), USER_A, _total); -// vm.prank(USER_A); -// operator.placeBid(_tokenId, _amount); -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// assertEq(_nextAuctionId, _prevNextAuctionId + 1); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.highestBidder, USER_A); - -// // new bid with USER_B -// deal(address(usdt), USER_B, _total); -// vm.prank(USER_B); -// operator.placeBid(_tokenId, _amount); -// _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// assertEq(_nextAuctionId, _prevNextAuctionId + 1); // still the same auction -// _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.highestBidder, USER_A); // USER_A is still the same highest bidder - -// // check if bids exist -// IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _nextAuctionId, USER_A); -// assertEq(_bidA.placedAt, block.number); -// assertEq(_bidA.isWon, false); -// IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _nextAuctionId, USER_A); -// assertEq(_bidB.placedAt, block.number); -// assertEq(_bidB.isWon, false); - -// // check registry balance -// assertEq(usdt.balanceOf(address(registry)), _total * 2); -// } - -// function testPlaceBidWithHigherPrice(uint96 _amount) public { -// vm.assume(_amount > 0); -// vm.assume(_amount < type(uint96).max / 2); - -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// // bid with USER_A -// deal(address(usdt), USER_A, _total); -// vm.prank(USER_A); -// operator.placeBid(_tokenId, _amount); -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.highestBidder, USER_A); - -// // bid with USER_B -// _amount = _amount * 2; -// _tax = operator.calculateTax(_amount); -// _total = _amount + _tax; -// deal(address(usdt), USER_B, _total); -// vm.startPrank(USER_B); -// operator.placeBid(_tokenId, _amount); -// _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.highestBidder, USER_B); -// } - -// function testPlaceBidZeroPrice() public { -// uint256 _tokenId = _mintBoard(); - -// vm.startPrank(ADMIN); -// uint256 _prevBalance = usdt.balanceOf(ADMIN); - -// operator.placeBid(_tokenId, 0); - -// // check balances -// uint256 _afterBalance = usdt.balanceOf(ADMIN); -// assertEq(_afterBalance, _prevBalance); -// assertEq(usdt.balanceOf(address(operator)), 0); -// assertEq(usdt.balanceOf(address(registry)), 0); - -// // check auction -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.highestBidder, ADMIN); - -// // check bid -// IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, ADMIN); -// assertEq(_bid.placedAt, block.number); -// assertEq(_bid.isWon, true); -// } - -// function testPlaceBidByWhitelist() public { -// uint256 _tokenId = _mintBoard(); -// uint256 _amount = 1 ether; -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// vm.prank(ADMIN); -// operator.addToWhitelist(USER_A); - -// deal(address(usdt), USER_A, _total); -// vm.prank(USER_A); -// operator.placeBid(_tokenId, _amount); -// assertEq(usdt.balanceOf(USER_A), 0); -// } - -// function testPlaceBidIfAuctionEnded() public { -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); -// uint256 _amount = 1 ether; -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// // place a bid with USER_A -// vm.startPrank(USER_A); -// deal(address(usdt), USER_A, _total); -// operator.placeBid(_tokenId, _amount); - -// // check auction -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.highestBidder, USER_A); -// assertEq(_auction.endAt, block.number + registry.leaseTerm()); - -// // make auction ended -// vm.roll(_auction.endAt + 1); - -// // place a bid with USER_B -// vm.startPrank(USER_B); -// deal(address(usdt), USER_B, _total); -// operator.placeBid(_tokenId, _amount); - -// // check auction -// uint256 _newNextAuctionId = registry.nextBoardAuctionId(_tokenId); -// IBillboardRegistry.Auction memory _newAuction = registry.getAuction(_tokenId, _newNextAuctionId); -// assertEq(_newNextAuctionId, _nextAuctionId + 1); -// assertEq(_newAuction.highestBidder, USER_B); -// assertEq(_newAuction.endAt, block.number + registry.leaseTerm()); - -// // USER_A won the previous auction -// IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); -// assertEq(_bid.isWon, true); - -// // USER_B's bid is still in a running auction -// IBillboardRegistry.Bid memory _newBid = registry.getBid(_tokenId, _newNextAuctionId, USER_B); -// assertEq(_newBid.isWon, false); -// } - -// function testCannotPlaceBidTwice(uint96 _amount) public { -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// vm.startPrank(USER_A); -// deal(address(usdt), USER_A, _total); -// operator.placeBid(_tokenId, _amount); -// assertEq(usdt.balanceOf(USER_A), 0); - -// deal(address(usdt), USER_A, _total); -// vm.expectRevert("Bid already placed"); -// operator.placeBid(_tokenId, _amount); -// } - -// function testCannotPlaceBidByAttacker() public { -// uint256 _tokenId = _mintBoard(); -// uint256 _amount = 1 ether; -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// vm.startPrank(ATTACKER); -// deal(address(usdt), ATTACKER, _total); -// vm.expectRevert("Whitelist"); -// operator.placeBid(_tokenId, _amount); -// } - -// function testClearAuctionIfAuctionEnded(uint96 _amount) public { -// vm.assume(_amount > 0.001 ether); - -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); -// uint64 _placedAt = uint64(block.number); -// uint64 _clearedAt = uint64(block.number) + registry.leaseTerm() + 1; - -// // place a bid -// vm.startPrank(USER_A); -// deal(address(usdt), USER_A, _total); -// operator.placeBid(_tokenId, _amount); - -// // clear auction -// vm.expectEmit(true, true, true, true); -// emit IBillboardRegistry.AuctionCleared( -// _tokenId, -// _prevAuctionId + 1, -// USER_A, -// _clearedAt, -// _clearedAt + registry.leaseTerm() -// ); - -// vm.roll(_clearedAt); -// (uint256 _price1, uint256 _tax1) = operator.clearAuction(_tokenId); -// assertEq(_price1, _amount); -// assertEq(_tax1, _tax); - -// // check auction -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.startAt, _placedAt); -// assertEq(_auction.endAt, _placedAt + registry.leaseTerm()); -// assertEq(_auction.leaseStartAt, _clearedAt); -// assertEq(_auction.leaseEndAt, _clearedAt + registry.leaseTerm()); -// assertEq(_auction.highestBidder, USER_A); - -// // check bid -// IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); -// assertEq(_bid.price, _amount); -// assertEq(_bid.tax, _tax); -// assertEq(_bid.placedAt, _placedAt); -// assertEq(_bid.isWon, true); -// assertEq(_bid.isWithdrawn, false); -// } - -// function testClearAuctionsIfAuctionEnded() public { -// (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); -// (uint256 _tokenId2, uint256 _prevAuctionId2) = _mintBoardAndPlaceBid(); - -// uint64 _placedAt = uint64(block.number); -// uint64 _clearedAt = uint64(block.number) + registry.leaseTerm() + 1; - -// // place bids -// vm.startPrank(USER_A); -// deal(address(usdt), USER_A, 0); -// operator.placeBid(_tokenId, 0); - -// vm.startPrank(USER_B); -// deal(address(usdt), USER_B, 0); -// operator.placeBid(_tokenId2, 0); - -// // clear auction -// vm.expectEmit(true, true, true, true); -// emit IBillboardRegistry.AuctionCleared( -// _tokenId, -// _prevAuctionId + 1, -// USER_A, -// _clearedAt, -// _clearedAt + registry.leaseTerm() -// ); -// vm.expectEmit(true, true, true, true); -// emit IBillboardRegistry.AuctionCleared( -// _tokenId2, -// _prevAuctionId2 + 1, -// USER_B, -// _clearedAt, -// _clearedAt + registry.leaseTerm() -// ); - -// vm.roll(_clearedAt); - -// uint256[] memory _tokenIds = new uint256[](2); -// _tokenIds[0] = _tokenId; -// _tokenIds[1] = _tokenId2; -// (uint256[] memory prices, uint256[] memory taxes) = operator.clearAuctions(_tokenIds); -// assertEq(prices[0], 0); -// assertEq(prices[1], 0); -// assertEq(taxes[0], 0); -// assertEq(taxes[1], 0); - -// // check auction -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.startAt, _placedAt); -// assertEq(_auction.endAt, _placedAt + registry.leaseTerm()); -// assertEq(_auction.leaseStartAt, _clearedAt); -// assertEq(_auction.leaseEndAt, _clearedAt + registry.leaseTerm()); -// assertEq(_auction.highestBidder, USER_A); - -// uint256 _nextAuctionId2 = registry.nextBoardAuctionId(_tokenId2); -// IBillboardRegistry.Auction memory _auction2 = registry.getAuction(_tokenId2, _nextAuctionId2); -// assertEq(_auction2.startAt, _placedAt); -// assertEq(_auction2.endAt, _placedAt + registry.leaseTerm()); -// assertEq(_auction2.leaseStartAt, _clearedAt); -// assertEq(_auction2.leaseEndAt, _clearedAt + registry.leaseTerm()); -// assertEq(_auction2.highestBidder, USER_B); - -// // check bid -// IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); -// assertEq(_bid.price, 0); -// assertEq(_bid.tax, 0); -// assertEq(_bid.placedAt, _placedAt); -// assertEq(_bid.isWon, true); -// assertEq(_bid.isWithdrawn, false); - -// IBillboardRegistry.Bid memory _bid2 = registry.getBid(_tokenId2, _nextAuctionId2, USER_B); -// assertEq(_bid2.price, 0); -// assertEq(_bid2.tax, 0); -// assertEq(_bid2.placedAt, _placedAt); -// assertEq(_bid2.isWon, true); -// assertEq(_bid2.isWithdrawn, false); -// } - -// function testCannotClearAuctionOnNewBoard() public { -// uint256 _mintedAt = block.number; -// uint256 _clearedAt = _mintedAt + 1; -// uint256 _tokenId = _mintBoard(); - -// vm.startPrank(ADMIN); - -// // clear auction -// vm.roll(_clearedAt); -// vm.expectRevert("Auction not found"); -// operator.clearAuction(_tokenId); -// } - -// function testCannotClearAuctionIfAuctionNotEnded() public { -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - -// // place a bid -// vm.startPrank(USER_A); -// deal(address(usdt), USER_A, 0); -// operator.placeBid(_tokenId, 0); - -// // try to clear auction -// vm.expectRevert("Auction not ended"); -// operator.clearAuction(_tokenId); - -// vm.roll(block.number + registry.leaseTerm() - 1); -// vm.expectRevert("Auction not ended"); -// operator.clearAuction(_tokenId); -// } - -// function testGetBids(uint8 _bidCount, uint8 _limit, uint8 _offset) public { -// vm.assume(_bidCount > 0); -// vm.assume(_bidCount <= 64); -// vm.assume(_limit <= _bidCount); -// vm.assume(_offset <= _limit); - -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - -// for (uint8 i = 0; i < _bidCount; i++) { -// address _bidder = address(uint160(2000 + i)); - -// vm.prank(ADMIN); -// operator.addToWhitelist(_bidder); - -// uint256 _amount = 1 ether + i; -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _totalAmount = _amount + _tax; - -// deal(address(usdt), _bidder, _totalAmount); -// vm.startPrank(_bidder); -// usdt.approve(address(operator), _totalAmount); -// operator.placeBid(_tokenId, _amount); -// vm.stopPrank(); -// } - -// // get bids -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// (uint256 _t, uint256 _l, uint256 _o, IBillboardRegistry.Bid[] memory _bids) = operator.getBids( -// _tokenId, -// _nextAuctionId, -// _limit, -// _offset -// ); -// uint256 _left = _t - _offset; -// uint256 _size = _left > _limit ? _limit : _left; -// assertEq(_t, _bidCount); -// assertEq(_l, _limit); -// assertEq(_bids.length, _size); -// assertEq(_o, _offset); -// for (uint256 i = 0; i < _size; i++) { -// uint256 _amount = 1 ether + _offset + i; -// assertEq(_bids[i].price, _amount); -// } -// } - -// ////////////////////////////// -// /// Tax & Withdraw -// ////////////////////////////// - -// function testCalculateTax() public { -// uint256 _amount = 100; -// uint256 _taxRate = 10; // 10% per lease term - -// vm.startPrank(ADMIN); -// operator.setTaxRate(_taxRate); - -// uint256 _tax = operator.calculateTax(_amount); -// assertEq(_tax, (_amount * _taxRate) / 1000); -// } - -// function testSetTaxRate() public { -// vm.startPrank(ADMIN); - -// vm.expectEmit(true, true, true, true); -// emit IBillboardRegistry.TaxRateUpdated(2); - -// operator.setTaxRate(2); -// assertEq(operator.getTaxRate(), 2); -// } - -// function testCannotSetTaxRateByAttacker() public { -// vm.startPrank(ATTACKER); - -// vm.expectRevert("Admin"); -// operator.setTaxRate(2); -// } - -// function testWithdrawTax(uint96 _amount) public { -// vm.assume(_amount > 0.001 ether); - -// uint256 _tokenId = _mintBoard(); -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// vm.prank(ADMIN); -// operator.addToWhitelist(USER_A); - -// // place a bid and win auction -// deal(address(usdt), USER_A, _total); -// vm.prank(USER_A); -// operator.placeBid(_tokenId, _amount); - -// uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); -// uint256 _prevAdminBalance = usdt.balanceOf(ADMIN); - -// // withdraw tax -// vm.expectEmit(true, true, true, true); -// emit IBillboardRegistry.TaxWithdrawn(ADMIN, _tax); - -// vm.prank(ADMIN); -// operator.withdrawTax(); - -// // check balances -// assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance - _tax); -// assertEq(usdt.balanceOf(ADMIN), _prevAdminBalance + _tax); -// } - -// function testCannnotWithdrawTaxIfZero() public { -// uint256 _tokenId = _mintBoard(); - -// vm.prank(ADMIN); -// operator.addToWhitelist(USER_A); - -// // place a bid and win auction -// deal(address(usdt), USER_A, 0); -// vm.prank(USER_A); -// operator.placeBid(_tokenId, 0); - -// vm.prank(ADMIN); -// vm.expectRevert("Zero amount"); -// operator.withdrawTax(); -// } - -// function testCannnotWithdrawTaxIfSmallAmount(uint8 _amount) public { -// uint256 _tax = operator.calculateTax(_amount); -// vm.assume(_tax <= 0); - -// uint256 _tokenId = _mintBoard(); - -// vm.prank(ADMIN); -// operator.addToWhitelist(USER_A); - -// // place a bid and win auction -// deal(address(usdt), USER_A, _amount); -// vm.prank(USER_A); -// operator.placeBid(_tokenId, _amount); - -// vm.prank(ADMIN); -// vm.expectRevert("Zero amount"); -// operator.withdrawTax(); -// } - -// function testCannotWithdrawTaxByAttacker() public { -// vm.startPrank(ATTACKER); - -// vm.expectRevert("Zero amount"); -// operator.withdrawTax(); -// } - -// function testWithdrawBid(uint96 _amount) public { -// vm.assume(_amount > 0.001 ether); - -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// // new auction and new bid with USER_A -// deal(address(usdt), USER_A, _total); -// vm.prank(USER_A); -// operator.placeBid(_tokenId, _amount); - -// // new bid with USER_B -// deal(address(usdt), USER_B, _total); -// vm.prank(USER_B); -// operator.placeBid(_tokenId, _amount); - -// // clear auction -// vm.roll(block.number + registry.leaseTerm() + 1); -// operator.clearAuction(_tokenId); - -// // check auction -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.highestBidder, USER_A); - -// // check bid -// IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _nextAuctionId, USER_A); -// assertEq(_bidA.isWon, true); -// IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _nextAuctionId, USER_B); -// assertEq(_bidB.isWon, false); - -// // withdraw bid -// vm.expectEmit(true, true, true, true); -// emit IBillboardRegistry.BidWithdrawn(_tokenId, _nextAuctionId, USER_B, _amount, _tax); - -// vm.prank(USER_B); -// operator.withdrawBid(_tokenId, _nextAuctionId); -// assertEq(usdt.balanceOf(USER_B), _total); -// } - -// function testCannotWithBidTwice(uint96 _amount) public { -// vm.assume(_amount > 0.001 ether); - -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// // new auction and new bid with USER_A -// deal(address(usdt), USER_A, _total); -// vm.prank(USER_A); -// operator.placeBid(_tokenId, _amount); - -// // new bid with USER_B -// deal(address(usdt), USER_B, _total); -// vm.prank(USER_B); -// operator.placeBid(_tokenId, _amount); - -// // clear auction -// vm.roll(block.number + registry.leaseTerm() + 1); -// operator.clearAuction(_tokenId); - -// // check auction -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.highestBidder, USER_A); - -// // withdraw bid -// vm.prank(USER_B); -// operator.withdrawBid(_tokenId, _nextAuctionId); -// assertEq(usdt.balanceOf(USER_B), _total); - -// // withdraw bid again -// vm.prank(USER_B); -// vm.expectRevert("Bid already withdrawn"); -// operator.withdrawBid(_tokenId, _nextAuctionId); -// } - -// function testCannotWithdrawBidIfWon(uint96 _amount) public { -// vm.assume(_amount > 0.001 ether); - -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// // new auction and new bid with USER_A -// deal(address(usdt), USER_A, _total); -// vm.prank(USER_A); -// operator.placeBid(_tokenId, _amount); - -// // clear auction -// vm.roll(block.number + registry.leaseTerm() + 1); -// operator.clearAuction(_tokenId); - -// // check auction -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); -// assertEq(_auction.highestBidder, USER_A); - -// // withdraw bid -// vm.prank(USER_A); -// vm.expectRevert("Bid already won"); -// operator.withdrawBid(_tokenId, _nextAuctionId); -// } - -// function testCannotWithdrawBidIfAuctionNotEnded(uint96 _amount) public { -// vm.assume(_amount > 0.001 ether); - -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// // new auction and new bid with USER_A -// vm.startPrank(USER_A); -// deal(address(usdt), USER_A, _total); -// operator.placeBid(_tokenId, _amount); - -// // auction is not ended -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// vm.expectRevert("Auction not ended"); -// operator.withdrawBid(_tokenId, _nextAuctionId); - -// // auction is ended but not cleared -// vm.roll(block.number + registry.leaseTerm() + 1); -// vm.expectRevert("Auction not cleared"); -// operator.withdrawBid(_tokenId, _nextAuctionId); -// } - -// function testCannotWithdrawBidIfAuctionNotCleared(uint96 _amount) public { -// vm.assume(_amount > 0.001 ether); - -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); -// uint256 _tax = operator.calculateTax(_amount); -// uint256 _total = _amount + _tax; - -// // new auction and new bid with USER_A -// deal(address(usdt), USER_A, _total); -// vm.prank(USER_A); -// operator.placeBid(_tokenId, _amount); - -// // new bid with USER_B -// deal(address(usdt), USER_B, _total); -// vm.prank(USER_B); -// operator.placeBid(_tokenId, _amount); - -// // auction is ended but not cleared -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); -// vm.roll(block.number + registry.leaseTerm() + 1); -// vm.prank(USER_B); -// vm.expectRevert("Auction not cleared"); -// operator.withdrawBid(_tokenId, _nextAuctionId); -// } - -// function testCannotWithdrawBidIfNotFound() public { -// (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); -// uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - -// vm.prank(USER_A); -// vm.expectRevert("Bid not found"); -// operator.withdrawBid(_tokenId, _nextAuctionId); -// } -// } + // function testWithdrawBid(uint96 _amount) public { + // vm.assume(_amount > 0.001 ether); + + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // // new auction and new bid with USER_A + // deal(address(usdt), USER_A, _total); + // vm.prank(USER_A); + // operator.placeBid(_tokenId, _amount); + + // // new bid with USER_B + // deal(address(usdt), USER_B, _total); + // vm.prank(USER_B); + // operator.placeBid(_tokenId, _amount); + + // // clear auction + // vm.roll(block.number + registry.leaseTerm() + 1); + // operator.clearAuction(_tokenId); + + // // check auction + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.highestBidder, USER_A); + + // // check bid + // IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _nextAuctionId, USER_A); + // assertEq(_bidA.isWon, true); + // IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _nextAuctionId, USER_B); + // assertEq(_bidB.isWon, false); + + // // withdraw bid + // vm.expectEmit(true, true, true, true); + // emit IBillboardRegistry.BidWithdrawn(_tokenId, _nextAuctionId, USER_B, _amount, _tax); + + // vm.prank(USER_B); + // operator.withdrawBid(_tokenId, _nextAuctionId); + // assertEq(usdt.balanceOf(USER_B), _total); + // } + + // function testCannotWithBidTwice(uint96 _amount) public { + // vm.assume(_amount > 0.001 ether); + + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // // new auction and new bid with USER_A + // deal(address(usdt), USER_A, _total); + // vm.prank(USER_A); + // operator.placeBid(_tokenId, _amount); + + // // new bid with USER_B + // deal(address(usdt), USER_B, _total); + // vm.prank(USER_B); + // operator.placeBid(_tokenId, _amount); + + // // clear auction + // vm.roll(block.number + registry.leaseTerm() + 1); + // operator.clearAuction(_tokenId); + + // // check auction + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.highestBidder, USER_A); + + // // withdraw bid + // vm.prank(USER_B); + // operator.withdrawBid(_tokenId, _nextAuctionId); + // assertEq(usdt.balanceOf(USER_B), _total); + + // // withdraw bid again + // vm.prank(USER_B); + // vm.expectRevert("Bid already withdrawn"); + // operator.withdrawBid(_tokenId, _nextAuctionId); + // } + + // function testCannotWithdrawBidIfWon(uint96 _amount) public { + // vm.assume(_amount > 0.001 ether); + + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // // new auction and new bid with USER_A + // deal(address(usdt), USER_A, _total); + // vm.prank(USER_A); + // operator.placeBid(_tokenId, _amount); + + // // clear auction + // vm.roll(block.number + registry.leaseTerm() + 1); + // operator.clearAuction(_tokenId); + + // // check auction + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); + // assertEq(_auction.highestBidder, USER_A); + + // // withdraw bid + // vm.prank(USER_A); + // vm.expectRevert("Bid already won"); + // operator.withdrawBid(_tokenId, _nextAuctionId); + // } + + // function testCannotWithdrawBidIfAuctionNotEnded(uint96 _amount) public { + // vm.assume(_amount > 0.001 ether); + + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // // new auction and new bid with USER_A + // vm.startPrank(USER_A); + // deal(address(usdt), USER_A, _total); + // operator.placeBid(_tokenId, _amount); + + // // auction is not ended + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // vm.expectRevert("Auction not ended"); + // operator.withdrawBid(_tokenId, _nextAuctionId); + + // // auction is ended but not cleared + // vm.roll(block.number + registry.leaseTerm() + 1); + // vm.expectRevert("Auction not cleared"); + // operator.withdrawBid(_tokenId, _nextAuctionId); + // } + + // function testCannotWithdrawBidIfAuctionNotCleared(uint96 _amount) public { + // vm.assume(_amount > 0.001 ether); + + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + // uint256 _tax = operator.calculateTax(_amount); + // uint256 _total = _amount + _tax; + + // // new auction and new bid with USER_A + // deal(address(usdt), USER_A, _total); + // vm.prank(USER_A); + // operator.placeBid(_tokenId, _amount); + + // // new bid with USER_B + // deal(address(usdt), USER_B, _total); + // vm.prank(USER_B); + // operator.placeBid(_tokenId, _amount); + + // // auction is ended but not cleared + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + // vm.roll(block.number + registry.leaseTerm() + 1); + // vm.prank(USER_B); + // vm.expectRevert("Auction not cleared"); + // operator.withdrawBid(_tokenId, _nextAuctionId); + // } + + // function testCannotWithdrawBidIfNotFound() public { + // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); + + // vm.prank(USER_A); + // vm.expectRevert("Bid not found"); + // operator.withdrawBid(_tokenId, _nextAuctionId); + // } + + ////////////////////////////// + /// ERC20 & ERC721 related + ////////////////////////////// + + function testCannotTransferToZeroAddress() public { + uint256 _tokenId = _mintBoard(); + + vm.startPrank(ADMIN); + + vm.expectRevert("ERC721: transfer to the zero address"); + registry.transferFrom(ADMIN, ZERO_ADDRESS, _tokenId); + } + + function testCannotTransferByOperator() public { + uint256 _tokenId = _mintBoard(); + + vm.startPrank(address(operator)); + + vm.expectRevert("ERC721: caller is not token owner or approved"); + registry.transferFrom(USER_B, USER_C, _tokenId); + } + + function testSafeTransferByOperator() public { + uint256 _tokenId = _mintBoard(); + + vm.expectEmit(true, true, true, true); + emit IERC721.Transfer(ADMIN, USER_A, _tokenId); + + vm.startPrank(address(operator)); + registry.safeTransferByOperator(ADMIN, USER_A, _tokenId); + assertEq(registry.ownerOf(_tokenId), USER_A); + } + + function testCannotSafeTransferByAttacker() public { + uint256 _tokenId = _mintBoard(); + + vm.startPrank(ATTACKER); + + vm.expectRevert("Operator"); + registry.safeTransferByOperator(ADMIN, ATTACKER, _tokenId); + } + + function testApproveAndTransfer() public { + uint256 _tokenId = _mintBoard(); + + vm.expectEmit(true, true, true, true); + emit IERC721.Approval(ADMIN, USER_A, _tokenId); + vm.prank(ADMIN); + registry.approve(USER_A, _tokenId); + assertEq(registry.getApproved(_tokenId), USER_A); + + vm.expectEmit(true, true, true, true); + emit IERC721.Transfer(ADMIN, USER_A, _tokenId); + vm.prank(USER_A); + registry.transferFrom(ADMIN, USER_A, _tokenId); + + IBillboardRegistry.Board memory board = operator.getBoard(_tokenId); + assertEq(board.creator, ADMIN); + assertEq(registry.ownerOf(_tokenId), USER_A); + } + + function testCannotApproveByAttacker() public { + uint256 _tokenId = _mintBoard(); + + vm.stopPrank(); + vm.startPrank(ATTACKER); + vm.expectRevert("ERC721: approve caller is not token owner or approved for all"); + registry.approve(USER_A, _tokenId); + } + + function testGetTokenURI() public { + uint256 _tokenId = _mintBoard(); + + vm.startPrank(ADMIN); + + // new board + string memory json = Base64.encode( + bytes(string(abi.encodePacked('{"name": "Billboard #1", "description": "", "location": "", "image": ""}'))) + ); + assertEq(registry.tokenURI(_tokenId), string(abi.encodePacked("data:application/json;base64,", json))); + + // set board data + string memory _name = "name"; + string memory _description = "description"; + string memory _imageURI = "image URI"; + string memory _location = "location"; + operator.setBoard(_tokenId, _name, _description, _imageURI, _location); + + string memory newJson = Base64.encode( + bytes( + string( + abi.encodePacked( + '{"name": "Billboard #1", "description": "description", "location": "location", "image": "image URI"}' + ) + ) + ) + ); + assertEq(registry.tokenURI(_tokenId), string(abi.encodePacked("data:application/json;base64,", newJson))); + } +} From f84f90602931d63388113551c5f396ff23ce049b Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:27:13 +0800 Subject: [PATCH 08/24] test(billboard): revise tests for auction & bids --- .gas-snapshot | 38 +- src/Billboard/Billboard.sol | 6 +- src/Billboard/BillboardRegistry.sol | 16 +- src/test/Billboard/BillboardTest.t.sol | 517 +++++++++------------ src/test/Billboard/BillboardTestBase.t.sol | 17 +- 5 files changed, 262 insertions(+), 332 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index d388780..128f5d6 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,23 +9,29 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testAddToWhitelist() (gas: 220255) -BillboardTest:testApproveAndTransfer() (gas: 222603) -BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 193898) -BillboardTest:testCannotApproveByAttacker() (gas: 191753) -BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 193983) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 189023) -BillboardTest:testCannotSetBoardByAttacker() (gas: 195085) -BillboardTest:testCannotSetBoardByOwner() (gas: 329033) -BillboardTest:testCannotTransferByOperator() (gas: 194267) -BillboardTest:testCannotTransferToZeroAddress() (gas: 189754) +BillboardTest:testAddToWhitelist() (gas: 252070) +BillboardTest:testApproveAndTransfer() (gas: 252645) +BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225643) +BillboardTest:testCannotApproveByAttacker() (gas: 221634) +BillboardTest:testCannotPlaceBidByAttacker() (gas: 459506) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 598942) +BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225773) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 218748) +BillboardTest:testCannotSetBoardByAttacker() (gas: 226787) +BillboardTest:testCannotSetBoardByOwner() (gas: 358929) +BillboardTest:testCannotTransferByOperator() (gas: 224012) +BillboardTest:testCannotTransferToZeroAddress() (gas: 219434) BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9016) -BillboardTest:testGetTokenURI() (gas: 358637) -BillboardTest:testMintBoard() (gas: 372377) -BillboardTest:testRemoveToWhitelist() (gas: 206806) -BillboardTest:testSafeTransferByOperator() (gas: 202755) -BillboardTest:testSetBoardByCreator() (gas: 307453) -BillboardTest:testUpgradeRegistry() (gas: 3311256) +BillboardTest:testGetTokenURI() (gas: 388561) +BillboardTest:testMintBoard() (gas: 417198) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 599051, ~: 601384) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 953951, ~: 953956) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 846964, ~: 850463) +BillboardTest:testPlaceBidZeroPrice() (gas: 416316) +BillboardTest:testRemoveToWhitelist() (gas: 238710) +BillboardTest:testSafeTransferByOperator() (gas: 232457) +BillboardTest:testSetBoardByCreator() (gas: 339338) +BillboardTest:testUpgradeRegistry() (gas: 3319574) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 4386efe..910e90c 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -85,8 +85,8 @@ contract Billboard is IBillboard { /// @inheritdoc IBillboard function mintBoard(uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId) { require(epochInterval_ > 0, "Zero epoch interval"); - tokenId = registry.newBoard(msg.sender, taxRate_, epochInterval_); + whitelist[tokenId][msg.sender] = true; } /// @inheritdoc IBillboard @@ -216,7 +216,7 @@ contract Billboard is IBillboard { address boardCreator_, uint256 epoch_ ) private returns (address highestBidder, uint256 price, uint256 tax) { - address _highestBidder = registry.higgestBidder(tokenId_, epoch_); + address _highestBidder = registry.highestBidder(tokenId_, epoch_); IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder); // skip if auction is already cleared @@ -297,7 +297,7 @@ contract Billboard is IBillboard { require(block.number < _endedAt, "Auction not ended"); // revert if auction is not cleared - address _highestBidder = registry.higgestBidder(tokenId_, epoch_); + address _highestBidder = registry.highestBidder(tokenId_, epoch_); IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder); require(!_highestBid.isWon, "Auction not cleared"); diff --git a/src/Billboard/BillboardRegistry.sol b/src/Billboard/BillboardRegistry.sol index bfa46ba..c40787c 100644 --- a/src/Billboard/BillboardRegistry.sol +++ b/src/Billboard/BillboardRegistry.sol @@ -21,7 +21,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { mapping(uint256 => Board) public boards; // tokenId => epoch => bidder - mapping(uint256 => mapping(uint256 => address)) public higgestBidder; + mapping(uint256 => mapping(uint256 => address)) public highestBidder; // tokenId => epoch => bidders mapping(uint256 => mapping(uint256 => address[])) public bidders; @@ -157,7 +157,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { // add to auction bidders if new bid bidders[tokenId_][epoch_].push(bidder_); - _setHiggestBidder(tokenId_, epoch_, price_, bidder_); + _sethighestBidder(tokenId_, epoch_, price_, bidder_); emit BidUpdated(tokenId_, epoch_, bidder_, price_, tax_, contentURI_, redirectURI_); } @@ -185,7 +185,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { _bid.redirectURI = redirectURI_; } - _setHiggestBidder(tokenId_, epoch_, price_, bidder_); + _sethighestBidder(tokenId_, epoch_, price_, bidder_); emit BidUpdated(tokenId_, epoch_, bidder_, price_, tax_, contentURI_, redirectURI_); } @@ -194,11 +194,11 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { // // Note: for same price, the first bidder will always be // the highest bidder since the block.number is always greater. - function _setHiggestBidder(uint256 tokenId_, uint256 epoch_, uint256 price_, address bidder_) internal { - address highestBidder = higgestBidder[tokenId_][epoch_]; - Bid memory highestBid = bids[tokenId_][epoch_][highestBidder]; - if (highestBidder == address(0) || price_ > highestBid.price) { - higgestBidder[tokenId_][epoch_] = bidder_; + function _sethighestBidder(uint256 tokenId_, uint256 epoch_, uint256 price_, address bidder_) internal { + address _highestBidder = highestBidder[tokenId_][epoch_]; + Bid memory highestBid = bids[tokenId_][epoch_][_highestBidder]; + if (_highestBidder == address(0) || price_ > highestBid.price) { + highestBidder[tokenId_][epoch_] = bidder_; } } diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 57c6f69..0474d6f 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -38,7 +38,7 @@ contract BillboardTest is BillboardTestBase { ////////////////////////////// function testAddToWhitelist() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ADMIN); @@ -49,7 +49,7 @@ contract BillboardTest is BillboardTestBase { } function testCannotAddToWhitelistByAttacker() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ATTACKER); @@ -58,7 +58,7 @@ contract BillboardTest is BillboardTestBase { } function testRemoveToWhitelist() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ADMIN); @@ -70,7 +70,7 @@ contract BillboardTest is BillboardTestBase { } function testCannotRemoveToWhitelistByAttacker() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ATTACKER); @@ -120,7 +120,7 @@ contract BillboardTest is BillboardTestBase { } function testSetBoardByCreator() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); string memory _name = "name"; string memory _description = "description"; string memory _imageURI = "image URI"; @@ -140,7 +140,7 @@ contract BillboardTest is BillboardTestBase { } function testCannotSetBoardByAttacker() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ATTACKER); @@ -150,7 +150,7 @@ contract BillboardTest is BillboardTestBase { function testCannotSetBoardByOwner() public { // mint - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); // transfer vm.startPrank(ADMIN); @@ -176,252 +176,189 @@ contract BillboardTest is BillboardTestBase { } ////////////////////////////// - /// Auction + /// Auction & Bid ////////////////////////////// - // function testPlaceBidOnNewBoard(uint96 _amount) public { - // vm.prank(ADMIN); - // operator.addToWhitelist(USER_A); - - // vm.expectEmit(true, false, false, false); - // emit IERC721.Transfer(address(0), ADMIN, 1); - - // uint256 _tokenId = _mintBoard(); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _overpaid = 0.1 ether; - // uint256 _total = _amount + _tax; - // deal(address(usdt), USER_A, _total + _overpaid); - - // uint256 _prevNextActionId = registry.nextBoardAuctionId(_tokenId); - // uint256 _prevCreatorBalance = usdt.balanceOf(ADMIN); - // uint256 _prevBidderBalance = usdt.balanceOf(USER_A); - // uint256 _prevOperatorBalance = usdt.balanceOf(address(operator)); - // uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); - - // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.AuctionCreated( - // _tokenId, - // _prevNextActionId + 1, - // uint64(block.number), - // uint64(block.number) - // ); - // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.BidCreated(_tokenId, _prevNextActionId + 1, USER_A, _amount, _tax); - // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.BidWon(_tokenId, _prevNextActionId + 1, USER_A); - // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.AuctionCleared( - // _tokenId, - // _prevNextActionId + 1, - // USER_A, - // uint64(block.number), - // uint64(block.number + registry.leaseTerm()) - // ); - - // vm.prank(USER_A); - // operator.placeBid(_tokenId, _amount); - - // // check balances - // assertEq(usdt.balanceOf(ADMIN), _prevCreatorBalance + _amount); - // assertEq(usdt.balanceOf(USER_A), _prevBidderBalance - _total); - // assertEq(usdt.balanceOf(address(operator)), _prevOperatorBalance); - // assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance + _tax); - - // // check auction - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_prevNextActionId, 0); - // assertEq(_nextAuctionId, _prevNextActionId + 1); - // assertEq(_auction.startAt, block.number); - // assertEq(_auction.endAt, block.number); - // assertEq(_auction.leaseStartAt, block.number); - // assertEq(_auction.leaseEndAt, block.number + registry.leaseTerm()); - // assertEq(_auction.highestBidder, USER_A); - - // // check bid - // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); - // assertEq(_bid.price, _amount); - // assertEq(_bid.tax, _tax); - // assertEq(_bid.placedAt, block.number); - // assertEq(_bid.isWon, true); - // assertEq(_bid.isWithdrawn, false); - // } - - // function testPlaceBidWithSamePrices(uint96 _amount) public { - // (uint256 _tokenId, uint256 _prevNextAuctionId) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; - - // // new auction and new bid with USER_A - // deal(address(usdt), USER_A, _total); - // vm.prank(USER_A); - // operator.placeBid(_tokenId, _amount); - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // assertEq(_nextAuctionId, _prevNextAuctionId + 1); - // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.highestBidder, USER_A); - - // // new bid with USER_B - // deal(address(usdt), USER_B, _total); - // vm.prank(USER_B); - // operator.placeBid(_tokenId, _amount); - // _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // assertEq(_nextAuctionId, _prevNextAuctionId + 1); // still the same auction - // _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.highestBidder, USER_A); // USER_A is still the same highest bidder + function testPlaceBid(uint96 _price) public { + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _total = _price + _tax; + deal(address(usdt), USER_A, _total); - // // check if bids exist - // IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _nextAuctionId, USER_A); - // assertEq(_bidA.placedAt, block.number); - // assertEq(_bidA.isWon, false); - // IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _nextAuctionId, USER_A); - // assertEq(_bidB.placedAt, block.number); - // assertEq(_bidB.isWon, false); - - // // check registry balance - // assertEq(usdt.balanceOf(address(registry)), _total * 2); - // } - - // function testPlaceBidWithHigherPrice(uint96 _amount) public { - // vm.assume(_amount > 0); - // vm.assume(_amount < type(uint96).max / 2); - - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; - - // // bid with USER_A - // deal(address(usdt), USER_A, _total); - // vm.prank(USER_A); - // operator.placeBid(_tokenId, _amount); - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.highestBidder, USER_A); - - // // bid with USER_B - // _amount = _amount * 2; - // _tax = operator.calculateTax(_amount); - // _total = _amount + _tax; - // deal(address(usdt), USER_B, _total); - // vm.startPrank(USER_B); - // operator.placeBid(_tokenId, _amount); - // _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.highestBidder, USER_B); - // } + uint256 _prevCreatorBalance = usdt.balanceOf(ADMIN); + uint256 _prevBidderBalance = usdt.balanceOf(USER_A); + uint256 _prevOperatorBalance = usdt.balanceOf(address(operator)); + uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); - // function testPlaceBidZeroPrice() public { - // uint256 _tokenId = _mintBoard(); - - // vm.startPrank(ADMIN); - // uint256 _prevBalance = usdt.balanceOf(ADMIN); - - // operator.placeBid(_tokenId, 0); - - // // check balances - // uint256 _afterBalance = usdt.balanceOf(ADMIN); - // assertEq(_afterBalance, _prevBalance); - // assertEq(usdt.balanceOf(address(operator)), 0); - // assertEq(usdt.balanceOf(address(registry)), 0); + vm.prank(ADMIN); + operator.addToWhitelist(_tokenId, USER_A); - // // check auction - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.highestBidder, ADMIN); + vm.expectEmit(true, true, true, true); + emit IBillboardRegistry.BidUpdated(_tokenId, _epoch, USER_A, _price, _tax, "", ""); - // // check bid - // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, ADMIN); - // assertEq(_bid.placedAt, block.number); - // assertEq(_bid.isWon, true); - // } + vm.prank(USER_A); + operator.placeBid(_tokenId, _epoch, _price); + + // check balances + assertEq(usdt.balanceOf(ADMIN), _prevCreatorBalance); + assertEq(usdt.balanceOf(USER_A), _prevBidderBalance - _total); + assertEq(usdt.balanceOf(address(operator)), _prevOperatorBalance); + assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance + _total); + + // check bid + IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _epoch, USER_A); + assertEq(_bid.price, _price); + assertEq(_bid.tax, _tax); + assertEq(_bid.createdAt, block.number); + assertEq(_bid.updatedAt, block.number); + assertEq(_bid.isWon, false); + assertEq(_bid.isWithdrawn, false); + } - // function testPlaceBidByWhitelist() public { - // uint256 _tokenId = _mintBoard(); - // uint256 _amount = 1 ether; - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + function testPlaceBidWithSamePrices(uint96 _price) public { + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _total = _price + _tax; - // vm.prank(ADMIN); - // operator.addToWhitelist(USER_A); + vm.startPrank(ADMIN); + operator.addToWhitelist(_tokenId, USER_A); + operator.addToWhitelist(_tokenId, USER_B); + vm.stopPrank(); - // deal(address(usdt), USER_A, _total); - // vm.prank(USER_A); - // operator.placeBid(_tokenId, _amount); - // assertEq(usdt.balanceOf(USER_A), 0); - // } + // bid with USER_A + deal(address(usdt), USER_A, _total); + vm.prank(USER_A); + operator.placeBid(_tokenId, _epoch, _price); + assertEq(registry.highestBidder(_tokenId, _epoch), USER_A); + + // bid with USER_B + deal(address(usdt), USER_B, _total); + vm.prank(USER_B); + operator.placeBid(_tokenId, _epoch, _price); + assertEq(registry.highestBidder(_tokenId, _epoch), USER_A); // USER_A is still the same highest bidder + + // check bids + IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _epoch, USER_A); + assertEq(_bidA.createdAt, block.number); + assertEq(_bidA.isWon, false); + IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _epoch, USER_A); + assertEq(_bidB.createdAt, block.number); + assertEq(_bidB.isWon, false); + + // check registry balance + assertEq(usdt.balanceOf(address(registry)), _total * 2); + } - // function testPlaceBidIfAuctionEnded() public { - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _amount = 1 ether; - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + function testPlaceBidWithHigherPrice(uint96 _price) public { + vm.assume(_price > 0); + vm.assume(_price < type(uint96).max / 4); - // // place a bid with USER_A - // vm.startPrank(USER_A); - // deal(address(usdt), USER_A, _total); - // operator.placeBid(_tokenId, _amount); + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _total = _price + _tax; - // // check auction - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.highestBidder, USER_A); - // assertEq(_auction.endAt, block.number + registry.leaseTerm()); + vm.startPrank(ADMIN); + operator.addToWhitelist(_tokenId, USER_A); + operator.addToWhitelist(_tokenId, USER_B); + vm.stopPrank(); - // // make auction ended - // vm.roll(_auction.endAt + 1); + // bid with USER_A + deal(address(usdt), USER_A, _total); + vm.prank(USER_A); + operator.placeBid(_tokenId, _epoch, _price); + assertEq(registry.highestBidder(_tokenId, _epoch), USER_A); + + // bid with USER_B + uint256 _priceB = _price * 2; + uint256 _taxB = operator.calculateTax(_tokenId, _priceB); + uint256 _totalB = _priceB + _taxB; + deal(address(usdt), USER_B, _totalB); + vm.startPrank(USER_B); + operator.placeBid(_tokenId, _epoch, _priceB); + assertEq(registry.highestBidder(_tokenId, _epoch), USER_B); + + // bid with USER_A + uint256 _priceA = _price * 4; + uint256 _taxA = operator.calculateTax(_tokenId, _priceA); + uint256 _totalA = _priceA + _taxA; + deal(address(usdt), USER_A, _totalA); + vm.startPrank(USER_A); + operator.placeBid(_tokenId, _epoch, _priceA); + assertEq(registry.highestBidder(_tokenId, _epoch), USER_A); + + // check balance of USER_A + uint256 _priceDiff = _priceA - _price; + uint256 _taxDiff = _taxA - _tax; + uint256 _totalDiff = _priceDiff + _taxDiff; + assertEq(usdt.balanceOf(USER_A), _totalA - _totalDiff); + } - // // place a bid with USER_B - // vm.startPrank(USER_B); - // deal(address(usdt), USER_B, _total); - // operator.placeBid(_tokenId, _amount); + function testPlaceBidZeroPrice() public { + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _prevBalance = usdt.balanceOf(ADMIN); - // // check auction - // uint256 _newNextAuctionId = registry.nextBoardAuctionId(_tokenId); - // IBillboardRegistry.Auction memory _newAuction = registry.getAuction(_tokenId, _newNextAuctionId); - // assertEq(_newNextAuctionId, _nextAuctionId + 1); - // assertEq(_newAuction.highestBidder, USER_B); - // assertEq(_newAuction.endAt, block.number + registry.leaseTerm()); + vm.startPrank(ADMIN); + operator.placeBid(_tokenId, _epoch, 0); + assertEq(registry.highestBidder(_tokenId, _epoch), ADMIN); + + // check balances + uint256 _afterBalance = usdt.balanceOf(ADMIN); + assertEq(_afterBalance, _prevBalance); + assertEq(usdt.balanceOf(address(operator)), 0); + assertEq(usdt.balanceOf(address(registry)), 0); + + // check bid + IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _epoch, ADMIN); + assertEq(_bid.createdAt, block.number); + assertEq(_bid.isWon, false); + } - // // USER_A won the previous auction - // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); - // assertEq(_bid.isWon, true); + function testCannotPlaceBidIfAuctionEnded() public { + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _price = 1 ether; + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _total = _price + _tax; + deal(address(usdt), USER_A, _total); - // // USER_B's bid is still in a running auction - // IBillboardRegistry.Bid memory _newBid = registry.getBid(_tokenId, _newNextAuctionId, USER_B); - // assertEq(_newBid.isWon, false); - // } + vm.prank(ADMIN); + operator.addToWhitelist(_tokenId, USER_A); - // function testCannotPlaceBidTwice(uint96 _amount) public { - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + vm.startPrank(USER_A); + operator.placeBid(_tokenId, _epoch, _price); - // vm.startPrank(USER_A); - // deal(address(usdt), USER_A, _total); - // operator.placeBid(_tokenId, _amount); - // assertEq(usdt.balanceOf(USER_A), 0); + vm.roll(block.number + _board.epochInterval); + vm.expectRevert("Auction ended"); + operator.placeBid(_tokenId, _epoch, _price); - // deal(address(usdt), USER_A, _total); - // vm.expectRevert("Bid already placed"); - // operator.placeBid(_tokenId, _amount); - // } + vm.roll(block.number + _board.epochInterval + 1); + vm.expectRevert("Auction ended"); + operator.placeBid(_tokenId, _epoch, _price); + } - // function testCannotPlaceBidByAttacker() public { - // uint256 _tokenId = _mintBoard(); - // uint256 _amount = 1 ether; - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + function testCannotPlaceBidByAttacker() public { + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _price = 1 ether; + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _total = _price + _tax; + deal(address(usdt), USER_A, _total); - // vm.startPrank(ATTACKER); - // deal(address(usdt), ATTACKER, _total); - // vm.expectRevert("Whitelist"); - // operator.placeBid(_tokenId, _amount); - // } + vm.startPrank(ATTACKER); + deal(address(usdt), ATTACKER, _total); + vm.expectRevert("Whitelist"); + operator.placeBid(_tokenId, _epoch, _price); + } - // function testClearAuctionIfAuctionEnded(uint96 _amount) public { - // vm.assume(_amount > 0.001 ether); + // function testClearAuctionIfAuctionEnded(uint96 _price) public { + // vm.assume(_price > 0.001 ether); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + // uint256 _tax = operator.calculateTax(_price); + // uint256 _total = _price + _tax; // (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); // uint64 _placedAt = uint64(block.number); @@ -430,7 +367,7 @@ contract BillboardTest is BillboardTestBase { // // place a bid // vm.startPrank(USER_A); // deal(address(usdt), USER_A, _total); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // // clear auction // vm.expectEmit(true, true, true, true); @@ -444,7 +381,7 @@ contract BillboardTest is BillboardTestBase { // vm.roll(_clearedAt); // (uint256 _price1, uint256 _tax1) = operator.clearAuction(_tokenId); - // assertEq(_price1, _amount); + // assertEq(_price1, _price); // assertEq(_tax1, _tax); // // check auction @@ -458,7 +395,7 @@ contract BillboardTest is BillboardTestBase { // // check bid // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); - // assertEq(_bid.price, _amount); + // assertEq(_bid.price, _price); // assertEq(_bid.tax, _tax); // assertEq(_bid.placedAt, _placedAt); // assertEq(_bid.isWon, true); @@ -546,7 +483,7 @@ contract BillboardTest is BillboardTestBase { // function testCannotClearAuctionOnNewBoard() public { // uint256 _mintedAt = block.number; // uint256 _clearedAt = _mintedAt + 1; - // uint256 _tokenId = _mintBoard(); + // (uint256 _tokenId,) = _mintBoard(); // vm.startPrank(ADMIN); @@ -587,14 +524,14 @@ contract BillboardTest is BillboardTestBase { // vm.prank(ADMIN); // operator.addToWhitelist(_bidder); - // uint256 _amount = 1 ether + i; - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _totalAmount = _amount + _tax; + // uint256 _price = 1 ether + i; + // uint256 _tax = operator.calculateTax(_price); + // uint256 _totalAmount = _price + _tax; // deal(address(usdt), _bidder, _totalAmount); // vm.startPrank(_bidder); // usdt.approve(address(operator), _totalAmount); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // vm.stopPrank(); // } @@ -613,8 +550,8 @@ contract BillboardTest is BillboardTestBase { // assertEq(_bids.length, _size); // assertEq(_o, _offset); // for (uint256 i = 0; i < _size; i++) { - // uint256 _amount = 1 ether + _offset + i; - // assertEq(_bids[i].price, _amount); + // uint256 _price = 1 ether + _offset + i; + // assertEq(_bids[i].price, _price); // } // } @@ -623,14 +560,14 @@ contract BillboardTest is BillboardTestBase { // ////////////////////////////// // function testCalculateTax() public { - // uint256 _amount = 100; + // uint256 _price = 100; // uint256 _taxRate = 10; // 10% per lease term // vm.startPrank(ADMIN); // operator.setTaxRate(_taxRate); - // uint256 _tax = operator.calculateTax(_amount); - // assertEq(_tax, (_amount * _taxRate) / 1000); + // uint256 _tax = operator.calculateTax(_price); + // assertEq(_tax, (_price * _taxRate) / 1000); // } // function testSetTaxRate() public { @@ -650,12 +587,12 @@ contract BillboardTest is BillboardTestBase { // operator.setTaxRate(2); // } - // function testWithdrawTax(uint96 _amount) public { - // vm.assume(_amount > 0.001 ether); + // function testWithdrawTax(uint96 _price) public { + // vm.assume(_price > 0.001 ether); - // uint256 _tokenId = _mintBoard(); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + // (uint256 _tokenId,) = _mintBoard(); + // uint256 _tax = operator.calculateTax(_price); + // uint256 _total = _price + _tax; // vm.prank(ADMIN); // operator.addToWhitelist(USER_A); @@ -663,7 +600,7 @@ contract BillboardTest is BillboardTestBase { // // place a bid and win auction // deal(address(usdt), USER_A, _total); // vm.prank(USER_A); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); // uint256 _prevAdminBalance = usdt.balanceOf(ADMIN); @@ -681,7 +618,7 @@ contract BillboardTest is BillboardTestBase { // } // function testCannnotWithdrawTaxIfZero() public { - // uint256 _tokenId = _mintBoard(); + // (uint256 _tokenId,) = _mintBoard(); // vm.prank(ADMIN); // operator.addToWhitelist(USER_A); @@ -696,19 +633,19 @@ contract BillboardTest is BillboardTestBase { // operator.withdrawTax(); // } - // function testCannnotWithdrawTaxIfSmallAmount(uint8 _amount) public { - // uint256 _tax = operator.calculateTax(_amount); + // function testCannnotWithdrawTaxIfSmallAmount(uint8 _price) public { + // uint256 _tax = operator.calculateTax(_price); // vm.assume(_tax <= 0); - // uint256 _tokenId = _mintBoard(); + // (uint256 _tokenId,) = _mintBoard(); // vm.prank(ADMIN); // operator.addToWhitelist(USER_A); // // place a bid and win auction - // deal(address(usdt), USER_A, _amount); + // deal(address(usdt), USER_A, _price); // vm.prank(USER_A); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // vm.prank(ADMIN); // vm.expectRevert("Zero amount"); @@ -722,22 +659,22 @@ contract BillboardTest is BillboardTestBase { // operator.withdrawTax(); // } - // function testWithdrawBid(uint96 _amount) public { - // vm.assume(_amount > 0.001 ether); + // function testWithdrawBid(uint96 _price) public { + // vm.assume(_price > 0.001 ether); // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + // uint256 _tax = operator.calculateTax(_price); + // uint256 _total = _price + _tax; // // new auction and new bid with USER_A // deal(address(usdt), USER_A, _total); // vm.prank(USER_A); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // // new bid with USER_B // deal(address(usdt), USER_B, _total); // vm.prank(USER_B); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // // clear auction // vm.roll(block.number + registry.leaseTerm() + 1); @@ -756,29 +693,29 @@ contract BillboardTest is BillboardTestBase { // // withdraw bid // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.BidWithdrawn(_tokenId, _nextAuctionId, USER_B, _amount, _tax); + // emit IBillboardRegistry.BidWithdrawn(_tokenId, _nextAuctionId, USER_B, _price, _tax); // vm.prank(USER_B); // operator.withdrawBid(_tokenId, _nextAuctionId); // assertEq(usdt.balanceOf(USER_B), _total); // } - // function testCannotWithBidTwice(uint96 _amount) public { - // vm.assume(_amount > 0.001 ether); + // function testCannotWithBidTwice(uint96 _price) public { + // vm.assume(_price > 0.001 ether); // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + // uint256 _tax = operator.calculateTax(_price); + // uint256 _total = _price + _tax; // // new auction and new bid with USER_A // deal(address(usdt), USER_A, _total); // vm.prank(USER_A); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // // new bid with USER_B // deal(address(usdt), USER_B, _total); // vm.prank(USER_B); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // // clear auction // vm.roll(block.number + registry.leaseTerm() + 1); @@ -800,17 +737,17 @@ contract BillboardTest is BillboardTestBase { // operator.withdrawBid(_tokenId, _nextAuctionId); // } - // function testCannotWithdrawBidIfWon(uint96 _amount) public { - // vm.assume(_amount > 0.001 ether); + // function testCannotWithdrawBidIfWon(uint96 _price) public { + // vm.assume(_price > 0.001 ether); // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + // uint256 _tax = operator.calculateTax(_price); + // uint256 _total = _price + _tax; // // new auction and new bid with USER_A // deal(address(usdt), USER_A, _total); // vm.prank(USER_A); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // // clear auction // vm.roll(block.number + registry.leaseTerm() + 1); @@ -827,17 +764,17 @@ contract BillboardTest is BillboardTestBase { // operator.withdrawBid(_tokenId, _nextAuctionId); // } - // function testCannotWithdrawBidIfAuctionNotEnded(uint96 _amount) public { - // vm.assume(_amount > 0.001 ether); + // function testCannotWithdrawBidIfAuctionNotEnded(uint96 _price) public { + // vm.assume(_price > 0.001 ether); // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + // uint256 _tax = operator.calculateTax(_price); + // uint256 _total = _price + _tax; // // new auction and new bid with USER_A // vm.startPrank(USER_A); // deal(address(usdt), USER_A, _total); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // // auction is not ended // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); @@ -850,22 +787,22 @@ contract BillboardTest is BillboardTestBase { // operator.withdrawBid(_tokenId, _nextAuctionId); // } - // function testCannotWithdrawBidIfAuctionNotCleared(uint96 _amount) public { - // vm.assume(_amount > 0.001 ether); + // function testCannotWithdrawBidIfAuctionNotCleared(uint96 _price) public { + // vm.assume(_price > 0.001 ether); // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_amount); - // uint256 _total = _amount + _tax; + // uint256 _tax = operator.calculateTax(_price); + // uint256 _total = _price + _tax; // // new auction and new bid with USER_A // deal(address(usdt), USER_A, _total); // vm.prank(USER_A); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // // new bid with USER_B // deal(address(usdt), USER_B, _total); // vm.prank(USER_B); - // operator.placeBid(_tokenId, _amount); + // operator.placeBid(_tokenId, _price); // // auction is ended but not cleared // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); @@ -889,7 +826,7 @@ contract BillboardTest is BillboardTestBase { ////////////////////////////// function testCannotTransferToZeroAddress() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ADMIN); @@ -898,7 +835,7 @@ contract BillboardTest is BillboardTestBase { } function testCannotTransferByOperator() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(address(operator)); @@ -907,7 +844,7 @@ contract BillboardTest is BillboardTestBase { } function testSafeTransferByOperator() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.expectEmit(true, true, true, true); emit IERC721.Transfer(ADMIN, USER_A, _tokenId); @@ -918,7 +855,7 @@ contract BillboardTest is BillboardTestBase { } function testCannotSafeTransferByAttacker() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ATTACKER); @@ -927,7 +864,7 @@ contract BillboardTest is BillboardTestBase { } function testApproveAndTransfer() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.expectEmit(true, true, true, true); emit IERC721.Approval(ADMIN, USER_A, _tokenId); @@ -946,7 +883,7 @@ contract BillboardTest is BillboardTestBase { } function testCannotApproveByAttacker() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.stopPrank(); vm.startPrank(ATTACKER); @@ -955,7 +892,7 @@ contract BillboardTest is BillboardTestBase { } function testGetTokenURI() public { - uint256 _tokenId = _mintBoard(); + (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ADMIN); diff --git a/src/test/Billboard/BillboardTestBase.t.sol b/src/test/Billboard/BillboardTestBase.t.sol index 781bbaf..4c2515e 100644 --- a/src/test/Billboard/BillboardTestBase.t.sol +++ b/src/test/Billboard/BillboardTestBase.t.sol @@ -57,22 +57,9 @@ contract BillboardTestBase is Test { usdt.approve(address(operator), MAX_ALLOWANCE); } - function _mintBoard() public returns (uint256 tokenId) { + function _mintBoard() public returns (uint256 tokenId, IBillboardRegistry.Board memory board) { vm.prank(ADMIN); tokenId = operator.mintBoard(TAX_RATE, EPOCH_INTERVAL); - } - - function _mintBoardAndPlaceBid() public returns (uint256 tokenId_, uint256 epoch_) { - tokenId_ = _mintBoard(); - - // (new board) ADMIN places first bid and takes the ownership - vm.startPrank(ADMIN); - operator.placeBid(tokenId_, epoch_, 0); - assertEq(registry.higgestBidder(tokenId_, epoch_), ADMIN); - - // add USER_A and USER_B to whitelist - operator.addToWhitelist(tokenId_, USER_A); - operator.addToWhitelist(tokenId_, USER_B); - vm.stopPrank(); + board = registry.getBoard(tokenId); } } From b0aae9c0f2563788c2d8d58b1d47b030e0e491a3 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:52:53 +0800 Subject: [PATCH 09/24] test(billboard): add tests for auction & bids --- .gas-snapshot | 51 +-- src/Billboard/Billboard.sol | 55 ++- src/test/Billboard/BillboardTest.t.sol | 367 +++++++++------------ src/test/Billboard/BillboardTestBase.t.sol | 13 + 4 files changed, 213 insertions(+), 273 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 128f5d6..86e480f 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,29 +9,34 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testAddToWhitelist() (gas: 252070) -BillboardTest:testApproveAndTransfer() (gas: 252645) -BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225643) -BillboardTest:testCannotApproveByAttacker() (gas: 221634) -BillboardTest:testCannotPlaceBidByAttacker() (gas: 459506) -BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 598942) -BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225773) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 218748) -BillboardTest:testCannotSetBoardByAttacker() (gas: 226787) -BillboardTest:testCannotSetBoardByOwner() (gas: 358929) -BillboardTest:testCannotTransferByOperator() (gas: 224012) -BillboardTest:testCannotTransferToZeroAddress() (gas: 219434) -BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9016) -BillboardTest:testGetTokenURI() (gas: 388561) -BillboardTest:testMintBoard() (gas: 417198) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 599051, ~: 601384) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 953951, ~: 953956) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 846964, ~: 850463) -BillboardTest:testPlaceBidZeroPrice() (gas: 416316) -BillboardTest:testRemoveToWhitelist() (gas: 238710) -BillboardTest:testSafeTransferByOperator() (gas: 232457) -BillboardTest:testSetBoardByCreator() (gas: 339338) -BillboardTest:testUpgradeRegistry() (gas: 3319574) +BillboardTest:testAddToWhitelist() (gas: 252168) +BillboardTest:testApproveAndTransfer() (gas: 252840) +BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225830) +BillboardTest:testCannotApproveByAttacker() (gas: 221688) +BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 247623) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 255406) +BillboardTest:testCannotPlaceBidByAttacker() (gas: 459863) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 270366) +BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225871) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 218868) +BillboardTest:testCannotSetBoardByAttacker() (gas: 226929) +BillboardTest:testCannotSetBoardByOwner() (gas: 359191) +BillboardTest:testCannotTransferByOperator() (gas: 224110) +BillboardTest:testCannotTransferToZeroAddress() (gas: 219554) +BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9038) +BillboardTest:testClearAuction(uint96) (runs: 256, μ: 712150, ~: 712150) +BillboardTest:testClearAuctions() (gas: 1278587) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4938705, ~: 2145225) +BillboardTest:testGetTokenURI() (gas: 388777) +BillboardTest:testMintBoard() (gas: 417504) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 798108, ~: 800441) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 975862, ~: 975867) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 865951, ~: 869450) +BillboardTest:testPlaceBidZeroPrice() (gas: 416622) +BillboardTest:testRemoveToWhitelist() (gas: 238830) +BillboardTest:testSafeTransferByOperator() (gas: 232599) +BillboardTest:testSetBoardByCreator() (gas: 339600) +BillboardTest:testUpgradeRegistry() (gas: 3326886) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 910e90c..d4faa64 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -137,7 +137,7 @@ contract Billboard is IBillboard { require(_board.creator != address(0), "Board not found"); uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval); - require(_endedAt >= block.number, "Auction ended"); + require(block.number < _endedAt, "Auction ended"); IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, msg.sender); @@ -189,36 +189,14 @@ contract Billboard is IBillboard { // revert if auction is still running uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval); - require(block.number < _endedAt, "Auction not ended"); + require(block.number >= _endedAt, "Auction not ended"); - return _clearAuction(tokenId_, _board.creator, epoch_); - } - - /// @inheritdoc IBillboard - function clearAuctions( - uint256[] calldata tokenIds_, - uint256[] calldata epochs_ - ) external returns (address[] memory highestBidders, uint256[] memory prices, uint256[] memory taxes) { - uint256 _size = tokenIds_.length; - address[] memory _highestBidders = new address[](_size); - uint256[] memory _prices = new uint256[](_size); - uint256[] memory _taxes = new uint256[](_size); - - for (uint256 i = 0; i < _size; i++) { - (_highestBidders[i], _prices[i], _taxes[i]) = clearAuction(tokenIds_[i], epochs_[i]); - } - - return (_highestBidders, _prices, _taxes); - } - - function _clearAuction( - uint256 tokenId_, - address boardCreator_, - uint256 epoch_ - ) private returns (address highestBidder, uint256 price, uint256 tax) { address _highestBidder = registry.highestBidder(tokenId_, epoch_); IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder); + // revert if no bid + require(_highestBid.createdAt != 0, "No bid"); + // skip if auction is already cleared if (_highestBid.isWon) { return (address(0), 0, 0); @@ -231,8 +209,8 @@ contract Billboard is IBillboard { registry.transferCurrencyByOperator(_prevOwner, _highestBid.price); // transfer bid tax to board creator's tax treasury - (uint256 _taxAccumulated, uint256 _taxWithdrawn) = registry.taxTreasury(boardCreator_); - registry.setTaxTreasury(boardCreator_, _taxAccumulated + _highestBid.tax, _taxWithdrawn); + (uint256 _taxAccumulated, uint256 _taxWithdrawn) = registry.taxTreasury(_board.creator); + registry.setTaxTreasury(_board.creator, _taxAccumulated + _highestBid.tax, _taxWithdrawn); } // transfer ownership @@ -247,6 +225,23 @@ contract Billboard is IBillboard { return (_highestBidder, _highestBid.price, _highestBid.tax); } + /// @inheritdoc IBillboard + function clearAuctions( + uint256[] calldata tokenIds_, + uint256[] calldata epochs_ + ) external returns (address[] memory highestBidders, uint256[] memory prices, uint256[] memory taxes) { + uint256 _size = tokenIds_.length; + address[] memory _highestBidders = new address[](_size); + uint256[] memory _prices = new uint256[](_size); + uint256[] memory _taxes = new uint256[](_size); + + for (uint256 i = 0; i < _size; i++) { + (_highestBidders[i], _prices[i], _taxes[i]) = clearAuction(tokenIds_[i], epochs_[i]); + } + + return (_highestBidders, _prices, _taxes); + } + /// @inheritdoc IBillboard function getBid( uint256 tokenId_, @@ -294,7 +289,7 @@ contract Billboard is IBillboard { // revert if auction is not ended uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval); - require(block.number < _endedAt, "Auction not ended"); + require(block.number >= _endedAt, "Auction not ended"); // revert if auction is not cleared address _highestBidder = registry.highestBidder(tokenId_, epoch_); diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 0474d6f..037f45d 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -191,8 +191,10 @@ contract BillboardTest is BillboardTestBase { uint256 _prevOperatorBalance = usdt.balanceOf(address(operator)); uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); - vm.prank(ADMIN); + vm.startPrank(ADMIN); operator.addToWhitelist(_tokenId, USER_A); + operator.addToWhitelist(_tokenId, USER_B); + vm.stopPrank(); vm.expectEmit(true, true, true, true); emit IBillboardRegistry.BidUpdated(_tokenId, _epoch, USER_A, _price, _tax, "", ""); @@ -214,6 +216,15 @@ contract BillboardTest is BillboardTestBase { assertEq(_bid.updatedAt, block.number); assertEq(_bid.isWon, false); assertEq(_bid.isWithdrawn, false); + + // bid with AD data + string memory _contentURI = "content URI"; + string memory _redirectURI = "redirect URI"; + vm.expectEmit(true, true, true, true); + emit IBillboardRegistry.BidUpdated(_tokenId, _epoch, USER_B, 0, 0, _contentURI, _redirectURI); + + vm.prank(USER_B); + operator.placeBid(_tokenId, _epoch, 0, _contentURI, _redirectURI); } function testPlaceBidWithSamePrices(uint96 _price) public { @@ -222,21 +233,12 @@ contract BillboardTest is BillboardTestBase { uint256 _tax = operator.calculateTax(_tokenId, _price); uint256 _total = _price + _tax; - vm.startPrank(ADMIN); - operator.addToWhitelist(_tokenId, USER_A); - operator.addToWhitelist(_tokenId, USER_B); - vm.stopPrank(); - // bid with USER_A - deal(address(usdt), USER_A, _total); - vm.prank(USER_A); - operator.placeBid(_tokenId, _epoch, _price); + _placeBid(_tokenId, _epoch, USER_A, _price); assertEq(registry.highestBidder(_tokenId, _epoch), USER_A); // bid with USER_B - deal(address(usdt), USER_B, _total); - vm.prank(USER_B); - operator.placeBid(_tokenId, _epoch, _price); + _placeBid(_tokenId, _epoch, USER_B, _price); assertEq(registry.highestBidder(_tokenId, _epoch), USER_A); // USER_A is still the same highest bidder // check bids @@ -258,35 +260,21 @@ contract BillboardTest is BillboardTestBase { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); uint256 _tax = operator.calculateTax(_tokenId, _price); - uint256 _total = _price + _tax; - - vm.startPrank(ADMIN); - operator.addToWhitelist(_tokenId, USER_A); - operator.addToWhitelist(_tokenId, USER_B); - vm.stopPrank(); // bid with USER_A - deal(address(usdt), USER_A, _total); - vm.prank(USER_A); - operator.placeBid(_tokenId, _epoch, _price); + _placeBid(_tokenId, _epoch, USER_A, _price); assertEq(registry.highestBidder(_tokenId, _epoch), USER_A); // bid with USER_B uint256 _priceB = _price * 2; - uint256 _taxB = operator.calculateTax(_tokenId, _priceB); - uint256 _totalB = _priceB + _taxB; - deal(address(usdt), USER_B, _totalB); - vm.startPrank(USER_B); - operator.placeBid(_tokenId, _epoch, _priceB); + _placeBid(_tokenId, _epoch, USER_B, _priceB); assertEq(registry.highestBidder(_tokenId, _epoch), USER_B); // bid with USER_A uint256 _priceA = _price * 4; uint256 _taxA = operator.calculateTax(_tokenId, _priceA); uint256 _totalA = _priceA + _taxA; - deal(address(usdt), USER_A, _totalA); - vm.startPrank(USER_A); - operator.placeBid(_tokenId, _epoch, _priceA); + _placeBid(_tokenId, _epoch, USER_A, _priceA); assertEq(registry.highestBidder(_tokenId, _epoch), USER_A); // check balance of USER_A @@ -321,21 +309,19 @@ contract BillboardTest is BillboardTestBase { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); uint256 _price = 1 ether; - uint256 _tax = operator.calculateTax(_tokenId, _price); - uint256 _total = _price + _tax; - deal(address(usdt), USER_A, _total); + + uint256 _endedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); vm.prank(ADMIN); operator.addToWhitelist(_tokenId, USER_A); vm.startPrank(USER_A); - operator.placeBid(_tokenId, _epoch, _price); - vm.roll(block.number + _board.epochInterval); + vm.roll(_endedAt); vm.expectRevert("Auction ended"); operator.placeBid(_tokenId, _epoch, _price); - vm.roll(block.number + _board.epochInterval + 1); + vm.roll(_endedAt + 1); vm.expectRevert("Auction ended"); operator.placeBid(_tokenId, _epoch, _price); } @@ -354,210 +340,151 @@ contract BillboardTest is BillboardTestBase { operator.placeBid(_tokenId, _epoch, _price); } - // function testClearAuctionIfAuctionEnded(uint96 _price) public { - // vm.assume(_price > 0.001 ether); - - // uint256 _tax = operator.calculateTax(_price); - // uint256 _total = _price + _tax; - - // (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); - // uint64 _placedAt = uint64(block.number); - // uint64 _clearedAt = uint64(block.number) + registry.leaseTerm() + 1; - - // // place a bid - // vm.startPrank(USER_A); - // deal(address(usdt), USER_A, _total); - // operator.placeBid(_tokenId, _price); - - // // clear auction - // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.AuctionCleared( - // _tokenId, - // _prevAuctionId + 1, - // USER_A, - // _clearedAt, - // _clearedAt + registry.leaseTerm() - // ); - - // vm.roll(_clearedAt); - // (uint256 _price1, uint256 _tax1) = operator.clearAuction(_tokenId); - // assertEq(_price1, _price); - // assertEq(_tax1, _tax); - - // // check auction - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.startAt, _placedAt); - // assertEq(_auction.endAt, _placedAt + registry.leaseTerm()); - // assertEq(_auction.leaseStartAt, _clearedAt); - // assertEq(_auction.leaseEndAt, _clearedAt + registry.leaseTerm()); - // assertEq(_auction.highestBidder, USER_A); - - // // check bid - // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); - // assertEq(_bid.price, _price); - // assertEq(_bid.tax, _tax); - // assertEq(_bid.placedAt, _placedAt); - // assertEq(_bid.isWon, true); - // assertEq(_bid.isWithdrawn, false); - // } - - // function testClearAuctionsIfAuctionEnded() public { - // (uint256 _tokenId, uint256 _prevAuctionId) = _mintBoardAndPlaceBid(); - // (uint256 _tokenId2, uint256 _prevAuctionId2) = _mintBoardAndPlaceBid(); - - // uint64 _placedAt = uint64(block.number); - // uint64 _clearedAt = uint64(block.number) + registry.leaseTerm() + 1; + function testClearAuction(uint96 _price) public { + vm.assume(_price > 0.001 ether); - // // place bids - // vm.startPrank(USER_A); - // deal(address(usdt), USER_A, 0); - // operator.placeBid(_tokenId, 0); - - // vm.startPrank(USER_B); - // deal(address(usdt), USER_B, 0); - // operator.placeBid(_tokenId2, 0); + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _placedAt = block.number; + uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); - // // clear auction - // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.AuctionCleared( - // _tokenId, - // _prevAuctionId + 1, - // USER_A, - // _clearedAt, - // _clearedAt + registry.leaseTerm() - // ); - // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.AuctionCleared( - // _tokenId2, - // _prevAuctionId2 + 1, - // USER_B, - // _clearedAt, - // _clearedAt + registry.leaseTerm() - // ); - - // vm.roll(_clearedAt); - - // uint256[] memory _tokenIds = new uint256[](2); - // _tokenIds[0] = _tokenId; - // _tokenIds[1] = _tokenId2; - // (uint256[] memory prices, uint256[] memory taxes) = operator.clearAuctions(_tokenIds); - // assertEq(prices[0], 0); - // assertEq(prices[1], 0); - // assertEq(taxes[0], 0); - // assertEq(taxes[1], 0); + // place bid + _placeBid(_tokenId, _epoch, USER_A, _price); - // // check auction - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.startAt, _placedAt); - // assertEq(_auction.endAt, _placedAt + registry.leaseTerm()); - // assertEq(_auction.leaseStartAt, _clearedAt); - // assertEq(_auction.leaseEndAt, _clearedAt + registry.leaseTerm()); - // assertEq(_auction.highestBidder, USER_A); + // clear auction + vm.expectEmit(true, true, true, false); + emit IBillboardRegistry.AuctionCleared(_tokenId, _epoch, USER_A); - // uint256 _nextAuctionId2 = registry.nextBoardAuctionId(_tokenId2); - // IBillboardRegistry.Auction memory _auction2 = registry.getAuction(_tokenId2, _nextAuctionId2); - // assertEq(_auction2.startAt, _placedAt); - // assertEq(_auction2.endAt, _placedAt + registry.leaseTerm()); - // assertEq(_auction2.leaseStartAt, _clearedAt); - // assertEq(_auction2.leaseEndAt, _clearedAt + registry.leaseTerm()); - // assertEq(_auction2.highestBidder, USER_B); + vm.roll(_clearedAt); + (address _highestBidder, uint256 _price1, uint256 _tax1) = operator.clearAuction(_tokenId, _epoch); - // // check bid - // IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _nextAuctionId, USER_A); - // assertEq(_bid.price, 0); - // assertEq(_bid.tax, 0); - // assertEq(_bid.placedAt, _placedAt); - // assertEq(_bid.isWon, true); - // assertEq(_bid.isWithdrawn, false); - - // IBillboardRegistry.Bid memory _bid2 = registry.getBid(_tokenId2, _nextAuctionId2, USER_B); - // assertEq(_bid2.price, 0); - // assertEq(_bid2.tax, 0); - // assertEq(_bid2.placedAt, _placedAt); - // assertEq(_bid2.isWon, true); - // assertEq(_bid2.isWithdrawn, false); - // } + assertEq(_price1, _price); + assertEq(_tax1, _tax); + assertEq(_highestBidder, registry.highestBidder(_tokenId, _epoch)); + assertEq(_highestBidder, USER_A); - // function testCannotClearAuctionOnNewBoard() public { - // uint256 _mintedAt = block.number; - // uint256 _clearedAt = _mintedAt + 1; - // (uint256 _tokenId,) = _mintBoard(); + // check auction & bid + IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _epoch, USER_A); + assertEq(_bid.price, _price); + assertEq(_bid.tax, _tax); + assertEq(_bid.createdAt, _placedAt); + assertEq(_bid.isWon, true); + assertEq(_bid.isWithdrawn, false); - // vm.startPrank(ADMIN); + // check balances + assertEq(usdt.balanceOf(address(registry)), _tax); + assertEq(usdt.balanceOf(ADMIN), _price); + assertEq(usdt.balanceOf(USER_A), 0); + } - // // clear auction - // vm.roll(_clearedAt); - // vm.expectRevert("Auction not found"); - // operator.clearAuction(_tokenId); - // } + function testClearAuctions() public { + (uint256 _tokenId1, IBillboardRegistry.Board memory _board1) = _mintBoard(); + (uint256 _tokenId2, IBillboardRegistry.Board memory _board2) = _mintBoard(); + uint256 _epoch1 = operator.getEpochFromBlock(block.number, _board1.epochInterval); + uint256 _epoch2 = operator.getEpochFromBlock(block.number, _board2.epochInterval); + _placeBid(_tokenId1, _epoch1, USER_A, 1 ether); + _placeBid(_tokenId2, _epoch2, USER_B, 1 ether); - // function testCannotClearAuctionIfAuctionNotEnded() public { - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + uint256 _clearedAt = operator.getBlockFromEpoch(_epoch1 + 1, _board1.epochInterval); - // // place a bid - // vm.startPrank(USER_A); - // deal(address(usdt), USER_A, 0); - // operator.placeBid(_tokenId, 0); - - // // try to clear auction - // vm.expectRevert("Auction not ended"); - // operator.clearAuction(_tokenId); + // clear auctions + vm.expectEmit(true, true, true, true); + emit IBillboardRegistry.AuctionCleared(_tokenId1, _epoch1, USER_A); + vm.expectEmit(true, true, true, true); + emit IBillboardRegistry.AuctionCleared(_tokenId2, _epoch2, USER_B); + + vm.roll(_clearedAt); + + uint256[] memory _tokenIds = new uint256[](2); + uint256[] memory _epochs = new uint256[](2); + _tokenIds[0] = _tokenId1; + _tokenIds[1] = _tokenId2; + _epochs[0] = _epoch1; + _epochs[1] = _epoch2; + (address[] memory highestBidders, , ) = operator.clearAuctions(_tokenIds, _epochs); + assertEq(highestBidders[0], USER_A); + assertEq(highestBidders[1], USER_B); + + // check auction & bids + IBillboardRegistry.Bid memory _bid1 = registry.getBid(_tokenId1, _epoch1, USER_A); + assertEq(_bid1.isWon, true); + + IBillboardRegistry.Bid memory _bid2 = registry.getBid(_tokenId2, _epoch2, USER_B); + assertEq(_bid2.isWon, true); + } - // vm.roll(block.number + registry.leaseTerm() - 1); - // vm.expectRevert("Auction not ended"); - // operator.clearAuction(_tokenId); - // } + function testCannotClearAuctionIfAuctionNotEnded() public { + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _endedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); - // function testGetBids(uint8 _bidCount, uint8 _limit, uint8 _offset) public { - // vm.assume(_bidCount > 0); - // vm.assume(_bidCount <= 64); - // vm.assume(_limit <= _bidCount); - // vm.assume(_offset <= _limit); + vm.expectRevert("Auction not ended"); + operator.clearAuction(_tokenId, _epoch); - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); + vm.roll(_endedAt - 1); + vm.expectRevert("Auction not ended"); + operator.clearAuction(_tokenId, _epoch); + } - // for (uint8 i = 0; i < _bidCount; i++) { - // address _bidder = address(uint160(2000 + i)); + function testCannotClearAuctionIfNoBid() public { + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); - // vm.prank(ADMIN); - // operator.addToWhitelist(_bidder); + vm.roll(_clearedAt); + vm.expectRevert("No bid"); + operator.clearAuction(_tokenId, _epoch); + } - // uint256 _price = 1 ether + i; - // uint256 _tax = operator.calculateTax(_price); - // uint256 _totalAmount = _price + _tax; + function testGetBids(uint8 _bidCount, uint8 _limit, uint8 _offset) public { + vm.assume(_bidCount > 0); + vm.assume(_bidCount <= 64); + vm.assume(_limit <= _bidCount); + vm.assume(_offset <= _limit); - // deal(address(usdt), _bidder, _totalAmount); - // vm.startPrank(_bidder); - // usdt.approve(address(operator), _totalAmount); - // operator.placeBid(_tokenId, _price); - // vm.stopPrank(); - // } + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); - // // get bids - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // (uint256 _t, uint256 _l, uint256 _o, IBillboardRegistry.Bid[] memory _bids) = operator.getBids( - // _tokenId, - // _nextAuctionId, - // _limit, - // _offset - // ); - // uint256 _left = _t - _offset; - // uint256 _size = _left > _limit ? _limit : _left; - // assertEq(_t, _bidCount); - // assertEq(_l, _limit); - // assertEq(_bids.length, _size); - // assertEq(_o, _offset); - // for (uint256 i = 0; i < _size; i++) { - // uint256 _price = 1 ether + _offset + i; - // assertEq(_bids[i].price, _price); - // } - // } + for (uint8 i = 0; i < _bidCount; i++) { + address _bidder = address(uint160(2000 + i)); + + vm.prank(ADMIN); + operator.addToWhitelist(_tokenId, _bidder); + + uint256 _price = 1 ether + i; + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _totalAmount = _price + _tax; + + deal(address(usdt), _bidder, _totalAmount); + vm.startPrank(_bidder); + usdt.approve(address(operator), _totalAmount); + operator.placeBid(_tokenId, _epoch, _price); + vm.stopPrank(); + } + + // get bids + (uint256 _t, uint256 _l, uint256 _o, IBillboardRegistry.Bid[] memory _bids) = operator.getBids( + _tokenId, + _epoch, + _limit, + _offset + ); + uint256 _left = _t - _offset; + uint256 _size = _left > _limit ? _limit : _left; + assertEq(_t, _bidCount); + assertEq(_l, _limit); + assertEq(_bids.length, _size); + assertEq(_o, _offset); + for (uint256 i = 0; i < _size; i++) { + uint256 _price = 1 ether + _offset + i; + assertEq(_bids[i].price, _price); + } + } - // ////////////////////////////// - // /// Tax & Withdraw - // ////////////////////////////// + ////////////////////////////// + /// Tax & Withdraw + ////////////////////////////// // function testCalculateTax() public { // uint256 _price = 100; diff --git a/src/test/Billboard/BillboardTestBase.t.sol b/src/test/Billboard/BillboardTestBase.t.sol index 4c2515e..c7ab1c3 100644 --- a/src/test/Billboard/BillboardTestBase.t.sol +++ b/src/test/Billboard/BillboardTestBase.t.sol @@ -62,4 +62,17 @@ contract BillboardTestBase is Test { tokenId = operator.mintBoard(TAX_RATE, EPOCH_INTERVAL); board = registry.getBoard(tokenId); } + + function _placeBid(uint256 _tokenId, uint256 _epoch, address _bidder, uint256 _price) public { + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _total = _price + _tax; + + deal(address(usdt), _bidder, _total); + + vm.prank(ADMIN); + operator.addToWhitelist(_tokenId, _bidder); + + vm.prank(_bidder); + operator.placeBid(_tokenId, _epoch, _price); + } } From db48341548021e5a564dca12c35ccce92a661788 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:15:26 +0800 Subject: [PATCH 10/24] test(billboard): add more tests for withdraw bids --- .gas-snapshot | 41 ++-- src/Billboard/Billboard.sol | 2 +- src/Billboard/BillboardRegistry.sol | 2 +- src/test/Billboard/BillboardTest.t.sol | 294 +++++++++++-------------- 4 files changed, 156 insertions(+), 183 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 86e480f..e0079f3 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -11,32 +11,37 @@ ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) BillboardTest:testAddToWhitelist() (gas: 252168) BillboardTest:testApproveAndTransfer() (gas: 252840) -BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225830) -BillboardTest:testCannotApproveByAttacker() (gas: 221688) -BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 247623) -BillboardTest:testCannotClearAuctionIfNoBid() (gas: 255406) -BillboardTest:testCannotPlaceBidByAttacker() (gas: 459863) -BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 270366) +BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225852) +BillboardTest:testCannotApproveByAttacker() (gas: 221710) +BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 247677) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 255428) +BillboardTest:testCannotPlaceBidByAttacker() (gas: 459880) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 270378) BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225871) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 218868) -BillboardTest:testCannotSetBoardByAttacker() (gas: 226929) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 218890) +BillboardTest:testCannotSetBoardByAttacker() (gas: 226951) BillboardTest:testCannotSetBoardByOwner() (gas: 359191) BillboardTest:testCannotTransferByOperator() (gas: 224110) -BillboardTest:testCannotTransferToZeroAddress() (gas: 219554) +BillboardTest:testCannotTransferToZeroAddress() (gas: 219576) BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9038) -BillboardTest:testClearAuction(uint96) (runs: 256, μ: 712150, ~: 712150) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 614753, ~: 614753) +BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 730039, ~: 730039) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 982286, ~: 982286) +BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1065867, ~: 1065867) +BillboardTest:testClearAuction(uint96) (runs: 256, μ: 712172, ~: 712172) BillboardTest:testClearAuctions() (gas: 1278587) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4938705, ~: 2145225) -BillboardTest:testGetTokenURI() (gas: 388777) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4721233, ~: 1988178) +BillboardTest:testGetTokenURI() (gas: 388799) BillboardTest:testMintBoard() (gas: 417504) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 798108, ~: 800441) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 975862, ~: 975867) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 865951, ~: 869450) -BillboardTest:testPlaceBidZeroPrice() (gas: 416622) -BillboardTest:testRemoveToWhitelist() (gas: 238830) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 798110, ~: 800443) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 975879, ~: 975884) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 865740, ~: 869472) +BillboardTest:testPlaceBidZeroPrice() (gas: 416644) +BillboardTest:testRemoveToWhitelist() (gas: 238874) BillboardTest:testSafeTransferByOperator() (gas: 232599) BillboardTest:testSetBoardByCreator() (gas: 339600) -BillboardTest:testUpgradeRegistry() (gas: 3326886) +BillboardTest:testUpgradeRegistry() (gas: 3326686) +BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1054698, ~: 1054698) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index d4faa64..d080ada 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -294,7 +294,7 @@ contract Billboard is IBillboard { // revert if auction is not cleared address _highestBidder = registry.highestBidder(tokenId_, epoch_); IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder); - require(!_highestBid.isWon, "Auction not cleared"); + require(_highestBid.isWon, "Auction not cleared"); IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, msg.sender); uint256 amount = _bid.price + _bid.tax; diff --git a/src/Billboard/BillboardRegistry.sol b/src/Billboard/BillboardRegistry.sol index c40787c..ec2514c 100644 --- a/src/Billboard/BillboardRegistry.sol +++ b/src/Billboard/BillboardRegistry.sol @@ -233,7 +233,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { bool isWithdrawn_ ) external isFromOperator { bids[tokenId_][epoch_][bidder_].isWithdrawn = isWithdrawn_; - emit BidWithdrawn(tokenId_, epoch_, msg.sender); + emit BidWithdrawn(tokenId_, epoch_, bidder_); } ////////////////////////////// diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 037f45d..6a5d970 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -482,8 +482,138 @@ contract BillboardTest is BillboardTestBase { } } + function testWithdrawBid(uint96 _price) public { + vm.assume(_price > 0.001 ether); + + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _total = _price + _tax; + uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + + // new bid with USER_A + _placeBid(_tokenId, _epoch, USER_A, _price); + + // new bid with USER_B + _placeBid(_tokenId, _epoch, USER_B, _price); + + // clear auction + vm.roll(_clearedAt); + operator.clearAuction(_tokenId, _epoch); + + // check bid + IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _epoch, USER_A); + assertEq(_bidA.isWon, true); + IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _epoch, USER_B); + assertEq(_bidB.isWon, false); + + // withdraw bid + vm.expectEmit(true, true, true, false); + emit IBillboardRegistry.BidWithdrawn(_tokenId, _epoch, USER_B); + + vm.prank(USER_B); + operator.withdrawBid(_tokenId, _epoch); + + // check balances + assertEq(usdt.balanceOf(USER_A), 0); + assertEq(usdt.balanceOf(USER_B), _total); + } + + function testCannotWithdrawBidTwice(uint96 _price) public { + vm.assume(_price > 0.001 ether); + + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _total = _price + _tax; + uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + + // new bid with USER_A + _placeBid(_tokenId, _epoch, USER_A, _price); + + // new bid with USER_B + _placeBid(_tokenId, _epoch, USER_B, _price); + + // clear auction + vm.roll(_clearedAt); + operator.clearAuction(_tokenId, _epoch); + + // withdraw bid + vm.prank(USER_B); + operator.withdrawBid(_tokenId, _epoch); + assertEq(usdt.balanceOf(USER_B), _total); + + // withdraw bid again + vm.prank(USER_B); + vm.expectRevert("Bid already withdrawn"); + operator.withdrawBid(_tokenId, _epoch); + } + + function testCannotWithdrawBidIfWon(uint96 _price) public { + vm.assume(_price > 0.001 ether); + + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + + // new bid with USER_A + _placeBid(_tokenId, _epoch, USER_A, _price); + + // new bid with USER_B + _placeBid(_tokenId, _epoch, USER_B, _price); + + // clear auction + vm.roll(_clearedAt); + operator.clearAuction(_tokenId, _epoch); + + // withdraw bid + vm.prank(USER_A); + vm.expectRevert("Bid already won"); + operator.withdrawBid(_tokenId, _epoch); + } + + function testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96 _price) public { + vm.assume(_price > 0.001 ether); + + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + + // new bid with USER_A + _placeBid(_tokenId, _epoch, USER_A, _price); + + // auction is not ended + vm.roll(_clearedAt - 1); + vm.expectRevert("Auction not ended"); + operator.withdrawBid(_tokenId, _epoch); + + // auction is ended but not cleared + vm.roll(_clearedAt); + vm.expectRevert("Auction not cleared"); + operator.withdrawBid(_tokenId, _epoch); + } + + function testCannotWithdrawBidIfNotFound(uint96 _price) public { + vm.assume(_price > 0.001 ether); + + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + + // new bid with USER_A + _placeBid(_tokenId, _epoch, USER_A, _price); + + // clear auction + vm.roll(_clearedAt); + operator.clearAuction(_tokenId, _epoch); + + vm.prank(USER_B); + vm.expectRevert("Bid not found"); + operator.withdrawBid(_tokenId, _epoch); + } + ////////////////////////////// - /// Tax & Withdraw + /// Tax ////////////////////////////// // function testCalculateTax() public { @@ -586,168 +716,6 @@ contract BillboardTest is BillboardTestBase { // operator.withdrawTax(); // } - // function testWithdrawBid(uint96 _price) public { - // vm.assume(_price > 0.001 ether); - - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_price); - // uint256 _total = _price + _tax; - - // // new auction and new bid with USER_A - // deal(address(usdt), USER_A, _total); - // vm.prank(USER_A); - // operator.placeBid(_tokenId, _price); - - // // new bid with USER_B - // deal(address(usdt), USER_B, _total); - // vm.prank(USER_B); - // operator.placeBid(_tokenId, _price); - - // // clear auction - // vm.roll(block.number + registry.leaseTerm() + 1); - // operator.clearAuction(_tokenId); - - // // check auction - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.highestBidder, USER_A); - - // // check bid - // IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _nextAuctionId, USER_A); - // assertEq(_bidA.isWon, true); - // IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _nextAuctionId, USER_B); - // assertEq(_bidB.isWon, false); - - // // withdraw bid - // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.BidWithdrawn(_tokenId, _nextAuctionId, USER_B, _price, _tax); - - // vm.prank(USER_B); - // operator.withdrawBid(_tokenId, _nextAuctionId); - // assertEq(usdt.balanceOf(USER_B), _total); - // } - - // function testCannotWithBidTwice(uint96 _price) public { - // vm.assume(_price > 0.001 ether); - - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_price); - // uint256 _total = _price + _tax; - - // // new auction and new bid with USER_A - // deal(address(usdt), USER_A, _total); - // vm.prank(USER_A); - // operator.placeBid(_tokenId, _price); - - // // new bid with USER_B - // deal(address(usdt), USER_B, _total); - // vm.prank(USER_B); - // operator.placeBid(_tokenId, _price); - - // // clear auction - // vm.roll(block.number + registry.leaseTerm() + 1); - // operator.clearAuction(_tokenId); - - // // check auction - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.highestBidder, USER_A); - - // // withdraw bid - // vm.prank(USER_B); - // operator.withdrawBid(_tokenId, _nextAuctionId); - // assertEq(usdt.balanceOf(USER_B), _total); - - // // withdraw bid again - // vm.prank(USER_B); - // vm.expectRevert("Bid already withdrawn"); - // operator.withdrawBid(_tokenId, _nextAuctionId); - // } - - // function testCannotWithdrawBidIfWon(uint96 _price) public { - // vm.assume(_price > 0.001 ether); - - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_price); - // uint256 _total = _price + _tax; - - // // new auction and new bid with USER_A - // deal(address(usdt), USER_A, _total); - // vm.prank(USER_A); - // operator.placeBid(_tokenId, _price); - - // // clear auction - // vm.roll(block.number + registry.leaseTerm() + 1); - // operator.clearAuction(_tokenId); - - // // check auction - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // IBillboardRegistry.Auction memory _auction = registry.getAuction(_tokenId, _nextAuctionId); - // assertEq(_auction.highestBidder, USER_A); - - // // withdraw bid - // vm.prank(USER_A); - // vm.expectRevert("Bid already won"); - // operator.withdrawBid(_tokenId, _nextAuctionId); - // } - - // function testCannotWithdrawBidIfAuctionNotEnded(uint96 _price) public { - // vm.assume(_price > 0.001 ether); - - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_price); - // uint256 _total = _price + _tax; - - // // new auction and new bid with USER_A - // vm.startPrank(USER_A); - // deal(address(usdt), USER_A, _total); - // operator.placeBid(_tokenId, _price); - - // // auction is not ended - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // vm.expectRevert("Auction not ended"); - // operator.withdrawBid(_tokenId, _nextAuctionId); - - // // auction is ended but not cleared - // vm.roll(block.number + registry.leaseTerm() + 1); - // vm.expectRevert("Auction not cleared"); - // operator.withdrawBid(_tokenId, _nextAuctionId); - // } - - // function testCannotWithdrawBidIfAuctionNotCleared(uint96 _price) public { - // vm.assume(_price > 0.001 ether); - - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _tax = operator.calculateTax(_price); - // uint256 _total = _price + _tax; - - // // new auction and new bid with USER_A - // deal(address(usdt), USER_A, _total); - // vm.prank(USER_A); - // operator.placeBid(_tokenId, _price); - - // // new bid with USER_B - // deal(address(usdt), USER_B, _total); - // vm.prank(USER_B); - // operator.placeBid(_tokenId, _price); - - // // auction is ended but not cleared - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - // vm.roll(block.number + registry.leaseTerm() + 1); - // vm.prank(USER_B); - // vm.expectRevert("Auction not cleared"); - // operator.withdrawBid(_tokenId, _nextAuctionId); - // } - - // function testCannotWithdrawBidIfNotFound() public { - // (uint256 _tokenId, ) = _mintBoardAndPlaceBid(); - // uint256 _nextAuctionId = registry.nextBoardAuctionId(_tokenId); - - // vm.prank(USER_A); - // vm.expectRevert("Bid not found"); - // operator.withdrawBid(_tokenId, _nextAuctionId); - // } - ////////////////////////////// /// ERC20 & ERC721 related ////////////////////////////// From 0ab131ea278826caa013d176f2215ad5401cdb1b Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:09:00 +0800 Subject: [PATCH 11/24] test(billboard): add tests for getBlockFromEpoch and getEpochFromBlock --- .gas-snapshot | 60 ++++++++++++++------------ src/test/Billboard/BillboardTest.t.sol | 59 +++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 28 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index e0079f3..f338eba 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,39 +9,43 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testAddToWhitelist() (gas: 252168) -BillboardTest:testApproveAndTransfer() (gas: 252840) -BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225852) -BillboardTest:testCannotApproveByAttacker() (gas: 221710) -BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 247677) -BillboardTest:testCannotClearAuctionIfNoBid() (gas: 255428) -BillboardTest:testCannotPlaceBidByAttacker() (gas: 459880) -BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 270378) -BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225871) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 218890) -BillboardTest:testCannotSetBoardByAttacker() (gas: 226951) -BillboardTest:testCannotSetBoardByOwner() (gas: 359191) -BillboardTest:testCannotTransferByOperator() (gas: 224110) -BillboardTest:testCannotTransferToZeroAddress() (gas: 219576) -BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9038) -BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 614753, ~: 614753) +BillboardTest:testAddToWhitelist() (gas: 252190) +BillboardTest:testApproveAndTransfer() (gas: 252884) +BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225763) +BillboardTest:testCannotApproveByAttacker() (gas: 221754) +BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 247699) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 255361) +BillboardTest:testCannotGetBlockFromEpoch() (gas: 8580) +BillboardTest:testCannotGetEpochFromBlock() (gas: 15189) +BillboardTest:testCannotPlaceBidByAttacker() (gas: 459810) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 270400) +BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225893) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 218912) +BillboardTest:testCannotSetBoardByAttacker() (gas: 226995) +BillboardTest:testCannotSetBoardByOwner() (gas: 359213) +BillboardTest:testCannotTransferByOperator() (gas: 224154) +BillboardTest:testCannotTransferToZeroAddress() (gas: 219598) +BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9082) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 614797, ~: 614797) BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 730039, ~: 730039) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 982286, ~: 982286) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 982308, ~: 982308) BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1065867, ~: 1065867) BillboardTest:testClearAuction(uint96) (runs: 256, μ: 712172, ~: 712172) -BillboardTest:testClearAuctions() (gas: 1278587) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4721233, ~: 1988178) -BillboardTest:testGetTokenURI() (gas: 388799) -BillboardTest:testMintBoard() (gas: 417504) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 798110, ~: 800443) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 975879, ~: 975884) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 865740, ~: 869472) +BillboardTest:testClearAuctions() (gas: 1278609) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4979403, ~: 2145243) +BillboardTest:testGetBlockFromEpoch() (gas: 13412) +BillboardTest:testGetEpochFromBlock() (gas: 14160) +BillboardTest:testGetTokenURI() (gas: 388821) +BillboardTest:testMintBoard() (gas: 417427) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 797955, ~: 800443) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 975808, ~: 975813) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 865906, ~: 869405) BillboardTest:testPlaceBidZeroPrice() (gas: 416644) BillboardTest:testRemoveToWhitelist() (gas: 238874) -BillboardTest:testSafeTransferByOperator() (gas: 232599) -BillboardTest:testSetBoardByCreator() (gas: 339600) -BillboardTest:testUpgradeRegistry() (gas: 3326686) -BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1054698, ~: 1054698) +BillboardTest:testSafeTransferByOperator() (gas: 232643) +BillboardTest:testSetBoardByCreator() (gas: 339644) +BillboardTest:testUpgradeRegistry() (gas: 3326599) +BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1054720, ~: 1054720) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 6a5d970..bf04e1a 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -612,6 +612,65 @@ contract BillboardTest is BillboardTestBase { operator.withdrawBid(_tokenId, _epoch); } + function testGetEpochFromBlock() public { + // epoch interval = 1 + assertEq(operator.getEpochFromBlock(0, 1), 0); + assertEq(operator.getEpochFromBlock(1, 1), 1); + + // epoch interval = 101 + assertEq(operator.getEpochFromBlock(0, 101), 0); + assertEq(operator.getEpochFromBlock(1, 101), 0); + assertEq(operator.getEpochFromBlock(100, 101), 0); + assertEq(operator.getEpochFromBlock(101, 101), 1); + assertEq(operator.getEpochFromBlock(203, 101), 2); + + // epoch interval = MAX + assertEq(operator.getEpochFromBlock(0, type(uint256).max), 0); + assertEq(operator.getEpochFromBlock(1, type(uint256).max), 0); + assertEq(operator.getEpochFromBlock(type(uint256).max, type(uint256).max), 1); + } + + function testCannotGetEpochFromBlock() public { + // panic: division or modulo by zero + vm.expectRevert(); + assertEq(operator.getEpochFromBlock(0, 0), 0); + vm.expectRevert(); + assertEq(operator.getEpochFromBlock(1, 0), 0); + vm.expectRevert(); + assertEq(operator.getEpochFromBlock(0, type(uint256).min), 0); + + // panic: arithmetic underflow or overflow + vm.expectRevert(); + assertEq(operator.getEpochFromBlock(type(uint256).max + 1, type(uint256).max), 1); + vm.expectRevert(); + assertEq(operator.getEpochFromBlock(type(uint256).min - 1, type(uint256).min), 1); + } + + function testGetBlockFromEpoch() public { + // epoch interval = 1 + assertEq(operator.getBlockFromEpoch(0, 1), 0); + assertEq(operator.getBlockFromEpoch(1, 1), 1); + + // epoch interval = 101 + assertEq(operator.getBlockFromEpoch(0, 101), 0); + assertEq(operator.getBlockFromEpoch(1, 101), 101); + assertEq(operator.getBlockFromEpoch(2, 101), 202); + + // epoch interval = MAX + assertEq(operator.getBlockFromEpoch(0, type(uint256).max), 0); + assertEq(operator.getBlockFromEpoch(1, type(uint256).max), type(uint256).max); + + // epoch interval = MIN + assertEq(operator.getBlockFromEpoch(0, type(uint256).min), 0); + assertEq(operator.getBlockFromEpoch(1, type(uint256).min), 0); + } + + function testCannotGetBlockFromEpoch() public { + // panic: arithmetic underflow or overflow + vm.expectRevert(); + assertEq(operator.getBlockFromEpoch(2, type(uint256).max), 0); + } + ////////////////////////////// /// Tax ////////////////////////////// From 28e9c6a03722ed863300f432a490494d97844734 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:21:19 +0800 Subject: [PATCH 12/24] test(billboard): add tests for calculateTax --- .gas-snapshot | 38 ++++++++++--------- src/test/Billboard/BillboardTest.t.sol | 52 ++++++++++++++------------ 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index f338eba..00ca1fb 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,35 +9,37 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testAddToWhitelist() (gas: 252190) +BillboardTest:testAddToWhitelist() (gas: 252212) BillboardTest:testApproveAndTransfer() (gas: 252884) -BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225763) -BillboardTest:testCannotApproveByAttacker() (gas: 221754) +BillboardTest:testCalculateTax() (gas: 537121) +BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225785) +BillboardTest:testCannotApproveByAttacker() (gas: 221776) +BillboardTest:testCannotCalculateTax() (gas: 216289) BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 247699) BillboardTest:testCannotClearAuctionIfNoBid() (gas: 255361) -BillboardTest:testCannotGetBlockFromEpoch() (gas: 8580) -BillboardTest:testCannotGetEpochFromBlock() (gas: 15189) -BillboardTest:testCannotPlaceBidByAttacker() (gas: 459810) +BillboardTest:testCannotGetBlockFromEpoch() (gas: 8602) +BillboardTest:testCannotGetEpochFromBlock() (gas: 15079) +BillboardTest:testCannotPlaceBidByAttacker() (gas: 459828) BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 270400) -BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225893) +BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225915) BillboardTest:testCannotSafeTransferByAttacker() (gas: 218912) -BillboardTest:testCannotSetBoardByAttacker() (gas: 226995) +BillboardTest:testCannotSetBoardByAttacker() (gas: 227017) BillboardTest:testCannotSetBoardByOwner() (gas: 359213) -BillboardTest:testCannotTransferByOperator() (gas: 224154) +BillboardTest:testCannotTransferByOperator() (gas: 224176) BillboardTest:testCannotTransferToZeroAddress() (gas: 219598) BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9082) -BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 614797, ~: 614797) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 614819, ~: 614819) BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 730039, ~: 730039) BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 982308, ~: 982308) BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1065867, ~: 1065867) -BillboardTest:testClearAuction(uint96) (runs: 256, μ: 712172, ~: 712172) -BillboardTest:testClearAuctions() (gas: 1278609) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4979403, ~: 2145243) -BillboardTest:testGetBlockFromEpoch() (gas: 13412) -BillboardTest:testGetEpochFromBlock() (gas: 14160) +BillboardTest:testClearAuction(uint96) (runs: 256, μ: 712194, ~: 712194) +BillboardTest:testClearAuctions() (gas: 1278631) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 5070617, ~: 2145261) +BillboardTest:testGetBlockFromEpoch() (gas: 13434) +BillboardTest:testGetEpochFromBlock() (gas: 14182) BillboardTest:testGetTokenURI() (gas: 388821) -BillboardTest:testMintBoard() (gas: 417427) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 797955, ~: 800443) +BillboardTest:testMintBoard() (gas: 417449) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 798110, ~: 800443) BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 975808, ~: 975813) BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 865906, ~: 869405) BillboardTest:testPlaceBidZeroPrice() (gas: 416644) @@ -45,7 +47,7 @@ BillboardTest:testRemoveToWhitelist() (gas: 238874) BillboardTest:testSafeTransferByOperator() (gas: 232643) BillboardTest:testSetBoardByCreator() (gas: 339644) BillboardTest:testUpgradeRegistry() (gas: 3326599) -BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1054720, ~: 1054720) +BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1054742, ~: 1054742) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index bf04e1a..99e9a70 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -675,33 +675,37 @@ contract BillboardTest is BillboardTestBase { /// Tax ////////////////////////////// - // function testCalculateTax() public { - // uint256 _price = 100; - // uint256 _taxRate = 10; // 10% per lease term - - // vm.startPrank(ADMIN); - // operator.setTaxRate(_taxRate); - - // uint256 _tax = operator.calculateTax(_price); - // assertEq(_tax, (_price * _taxRate) / 1000); - // } - - // function testSetTaxRate() public { - // vm.startPrank(ADMIN); - - // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.TaxRateUpdated(2); + function testCalculateTax() public { + vm.prank(ADMIN); + uint256 _price = 1000; + uint256 _taxRate = 2; + uint256 _tokenId = operator.mintBoard(_taxRate, EPOCH_INTERVAL); + uint256 _tax = operator.calculateTax(_tokenId, _price); + assertEq(_tax, 2); - // operator.setTaxRate(2); - // assertEq(operator.getTaxRate(), 2); - // } + vm.prank(ADMIN); + uint256 _price1 = 1007; + uint256 _taxRate1 = 2; + uint256 _tokenId1 = operator.mintBoard(_taxRate1, EPOCH_INTERVAL); + uint256 _tax1 = operator.calculateTax(_tokenId1, _price1); + assertEq(_tax1, 2); - // function testCannotSetTaxRateByAttacker() public { - // vm.startPrank(ATTACKER); + vm.prank(ADMIN); + uint256 _price2 = 0; + uint256 _taxRate2 = 100; + uint256 _tokenId2 = operator.mintBoard(_taxRate2, EPOCH_INTERVAL); + uint256 _tax2 = operator.calculateTax(_tokenId2, _price2); + assertEq(_tax2, 0); + } - // vm.expectRevert("Admin"); - // operator.setTaxRate(2); - // } + function testCannotCalculateTax() public { + vm.prank(ADMIN); + uint256 _price = 1000; + uint256 _taxRate = type(uint256).max; + uint256 _tokenId = operator.mintBoard(_taxRate, EPOCH_INTERVAL); + vm.expectRevert(); + operator.calculateTax(_tokenId, _price); + } // function testWithdrawTax(uint96 _price) public { // vm.assume(_price > 0.001 ether); From 25eb42115ee98c35d7e603fec4f3ee2087bcfae4 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:30:24 +0800 Subject: [PATCH 13/24] test(billboard): add tests for withdrawTax --- .gas-snapshot | 42 ++++++------ src/test/Billboard/BillboardTest.t.sol | 95 +++++++++----------------- 2 files changed, 55 insertions(+), 82 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 00ca1fb..bfda432 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -10,44 +10,46 @@ ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) BillboardTest:testAddToWhitelist() (gas: 252212) -BillboardTest:testApproveAndTransfer() (gas: 252884) +BillboardTest:testApproveAndTransfer() (gas: 252906) BillboardTest:testCalculateTax() (gas: 537121) +BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20110) BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225785) -BillboardTest:testCannotApproveByAttacker() (gas: 221776) -BillboardTest:testCannotCalculateTax() (gas: 216289) +BillboardTest:testCannotApproveByAttacker() (gas: 221798) +BillboardTest:testCannotCalculateTax() (gas: 216267) BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 247699) -BillboardTest:testCannotClearAuctionIfNoBid() (gas: 255361) -BillboardTest:testCannotGetBlockFromEpoch() (gas: 8602) -BillboardTest:testCannotGetEpochFromBlock() (gas: 15079) -BillboardTest:testCannotPlaceBidByAttacker() (gas: 459828) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 255383) +BillboardTest:testCannotGetBlockFromEpoch() (gas: 8624) +BillboardTest:testCannotGetEpochFromBlock() (gas: 15101) +BillboardTest:testCannotPlaceBidByAttacker() (gas: 459845) BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 270400) BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225915) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 218912) -BillboardTest:testCannotSetBoardByAttacker() (gas: 227017) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 218934) +BillboardTest:testCannotSetBoardByAttacker() (gas: 226906) BillboardTest:testCannotSetBoardByOwner() (gas: 359213) BillboardTest:testCannotTransferByOperator() (gas: 224176) -BillboardTest:testCannotTransferToZeroAddress() (gas: 219598) -BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9082) -BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 614819, ~: 614819) +BillboardTest:testCannotTransferToZeroAddress() (gas: 219620) +BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 8973) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 614841, ~: 614841) BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 730039, ~: 730039) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 982308, ~: 982308) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 982330, ~: 982330) BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1065867, ~: 1065867) BillboardTest:testClearAuction(uint96) (runs: 256, μ: 712194, ~: 712194) BillboardTest:testClearAuctions() (gas: 1278631) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 5070617, ~: 2145261) -BillboardTest:testGetBlockFromEpoch() (gas: 13434) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4883559, ~: 2141284) +BillboardTest:testGetBlockFromEpoch() (gas: 13456) BillboardTest:testGetEpochFromBlock() (gas: 14182) -BillboardTest:testGetTokenURI() (gas: 388821) +BillboardTest:testGetTokenURI() (gas: 388843) BillboardTest:testMintBoard() (gas: 417449) BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 798110, ~: 800443) BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 975808, ~: 975813) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 865906, ~: 869405) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 865928, ~: 869427) BillboardTest:testPlaceBidZeroPrice() (gas: 416644) BillboardTest:testRemoveToWhitelist() (gas: 238874) -BillboardTest:testSafeTransferByOperator() (gas: 232643) -BillboardTest:testSetBoardByCreator() (gas: 339644) -BillboardTest:testUpgradeRegistry() (gas: 3326599) +BillboardTest:testSafeTransferByOperator() (gas: 232665) +BillboardTest:testSetBoardByCreator() (gas: 339666) +BillboardTest:testUpgradeRegistry() (gas: 3326621) BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1054742, ~: 1054742) +BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 717516, ~: 717516) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 99e9a70..06627f9 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -707,77 +707,48 @@ contract BillboardTest is BillboardTestBase { operator.calculateTax(_tokenId, _price); } - // function testWithdrawTax(uint96 _price) public { - // vm.assume(_price > 0.001 ether); - - // (uint256 _tokenId,) = _mintBoard(); - // uint256 _tax = operator.calculateTax(_price); - // uint256 _total = _price + _tax; - - // vm.prank(ADMIN); - // operator.addToWhitelist(USER_A); - - // // place a bid and win auction - // deal(address(usdt), USER_A, _total); - // vm.prank(USER_A); - // operator.placeBid(_tokenId, _price); - - // uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); - // uint256 _prevAdminBalance = usdt.balanceOf(ADMIN); - - // // withdraw tax - // vm.expectEmit(true, true, true, true); - // emit IBillboardRegistry.TaxWithdrawn(ADMIN, _tax); - - // vm.prank(ADMIN); - // operator.withdrawTax(); - - // // check balances - // assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance - _tax); - // assertEq(usdt.balanceOf(ADMIN), _prevAdminBalance + _tax); - // } - - // function testCannnotWithdrawTaxIfZero() public { - // (uint256 _tokenId,) = _mintBoard(); - - // vm.prank(ADMIN); - // operator.addToWhitelist(USER_A); + function testWithdrawTax(uint96 _price) public { + vm.assume(_price > 0.001 ether); - // // place a bid and win auction - // deal(address(usdt), USER_A, 0); - // vm.prank(USER_A); - // operator.placeBid(_tokenId, 0); + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); - // vm.prank(ADMIN); - // vm.expectRevert("Zero amount"); - // operator.withdrawTax(); - // } + // place bid with USER_A + _placeBid(_tokenId, _epoch, USER_A, _price); - // function testCannnotWithdrawTaxIfSmallAmount(uint8 _price) public { - // uint256 _tax = operator.calculateTax(_price); - // vm.assume(_tax <= 0); + // clear auction + vm.roll(_clearedAt); + operator.clearAuction(_tokenId, _epoch); - // (uint256 _tokenId,) = _mintBoard(); + uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); + uint256 _prevAdminBalance = usdt.balanceOf(ADMIN); + (uint256 _taxAccumulated, uint256 _taxWithdrawn) = registry.taxTreasury(ADMIN); + assertEq(_taxAccumulated, _tax); + assertEq(_taxWithdrawn, 0); - // vm.prank(ADMIN); - // operator.addToWhitelist(USER_A); + // withdraw tax + vm.expectEmit(true, true, true, true); + emit IBillboardRegistry.TaxWithdrawn(ADMIN, _tax); - // // place a bid and win auction - // deal(address(usdt), USER_A, _price); - // vm.prank(USER_A); - // operator.placeBid(_tokenId, _price); + vm.prank(ADMIN); + operator.withdrawTax(); - // vm.prank(ADMIN); - // vm.expectRevert("Zero amount"); - // operator.withdrawTax(); - // } + // check balances + assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance - _tax); + assertEq(usdt.balanceOf(ADMIN), _prevAdminBalance + _tax); + } - // function testCannotWithdrawTaxByAttacker() public { - // vm.startPrank(ATTACKER); + function testCannnotWithdrawTaxIfZero() public { + (uint256 _taxAccumulated, uint256 _taxWithdrawn) = registry.taxTreasury(ADMIN); + assertEq(_taxAccumulated, 0); + assertEq(_taxWithdrawn, 0); - // vm.expectRevert("Zero amount"); - // operator.withdrawTax(); - // } + vm.prank(ADMIN); + vm.expectRevert("Zero amount"); + operator.withdrawTax(); + } ////////////////////////////// /// ERC20 & ERC721 related From aedeae604b69f6e9672ab076b7ce25466245005a Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:30:14 +0800 Subject: [PATCH 14/24] feat(billboard): allow to set startedAt on mintBoard --- .gas-snapshot | 83 +++++----- src/Billboard/Billboard.sol | 59 ++++--- src/Billboard/BillboardRegistry.sol | 17 +- src/Billboard/IBillboard.sol | 32 +++- src/Billboard/IBillboardRegistry.sol | 12 +- src/test/Billboard/BillboardTest.t.sol | 206 +++++++++++++++---------- 6 files changed, 250 insertions(+), 159 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index bfda432..4df7cca 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,47 +9,48 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testAddToWhitelist() (gas: 252212) -BillboardTest:testApproveAndTransfer() (gas: 252906) -BillboardTest:testCalculateTax() (gas: 537121) -BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20110) -BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225785) -BillboardTest:testCannotApproveByAttacker() (gas: 221798) -BillboardTest:testCannotCalculateTax() (gas: 216267) -BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 247699) -BillboardTest:testCannotClearAuctionIfNoBid() (gas: 255383) -BillboardTest:testCannotGetBlockFromEpoch() (gas: 8624) -BillboardTest:testCannotGetEpochFromBlock() (gas: 15101) -BillboardTest:testCannotPlaceBidByAttacker() (gas: 459845) -BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 270400) -BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225915) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 218934) -BillboardTest:testCannotSetBoardByAttacker() (gas: 226906) -BillboardTest:testCannotSetBoardByOwner() (gas: 359213) -BillboardTest:testCannotTransferByOperator() (gas: 224176) -BillboardTest:testCannotTransferToZeroAddress() (gas: 219620) -BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 8973) -BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 614841, ~: 614841) -BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 730039, ~: 730039) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 982330, ~: 982330) -BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1065867, ~: 1065867) -BillboardTest:testClearAuction(uint96) (runs: 256, μ: 712194, ~: 712194) -BillboardTest:testClearAuctions() (gas: 1278631) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4883559, ~: 2141284) -BillboardTest:testGetBlockFromEpoch() (gas: 13456) -BillboardTest:testGetEpochFromBlock() (gas: 14182) -BillboardTest:testGetTokenURI() (gas: 388843) -BillboardTest:testMintBoard() (gas: 417449) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 798110, ~: 800443) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 975808, ~: 975813) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 865928, ~: 869427) -BillboardTest:testPlaceBidZeroPrice() (gas: 416644) -BillboardTest:testRemoveToWhitelist() (gas: 238874) -BillboardTest:testSafeTransferByOperator() (gas: 232665) -BillboardTest:testSetBoardByCreator() (gas: 339666) -BillboardTest:testUpgradeRegistry() (gas: 3326621) -BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1054742, ~: 1054742) -BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 717516, ~: 717516) +BillboardTest:testAddToWhitelist() (gas: 252415) +BillboardTest:testApproveAndTransfer() (gas: 253043) +BillboardTest:testCalculateTax() (gas: 537542) +BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20183) +BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225988) +BillboardTest:testCannotApproveByAttacker() (gas: 221891) +BillboardTest:testCannotCalculateTax() (gas: 216404) +BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 248674) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 256039) +BillboardTest:testCannotGetBlockFromEpoch() (gas: 8688) +BillboardTest:testCannotGetEpochFromBlock() (gas: 15590) +BillboardTest:testCannotPlaceBidByAttacker() (gas: 460155) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 271390) +BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 226118) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 219003) +BillboardTest:testCannotSetBoardByAttacker() (gas: 227087) +BillboardTest:testCannotSetBoardByOwner() (gas: 359460) +BillboardTest:testCannotTransferByOperator() (gas: 224137) +BillboardTest:testCannotTransferToZeroAddress() (gas: 219713) +BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9039) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 600791, ~: 600791) +BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 715717, ~: 715717) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 966732, ~: 966732) +BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1050826, ~: 1050826) +BillboardTest:testClearAuction(uint96) (runs: 256, μ: 697518, ~: 697518) +BillboardTest:testClearAuctions() (gas: 1248982) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 5071994, ~: 2128035) +BillboardTest:testGetBlockFromEpoch() (gas: 14680) +BillboardTest:testGetEpochFromBlock() (gas: 15789) +BillboardTest:testGetTokenURI() (gas: 389096) +BillboardTest:testMintBoard() (gas: 580688) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 781546, ~: 783879) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 965751, ~: 965756) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 849576, ~: 853075) +BillboardTest:testPlaceBidZeroPrice() (gas: 401269) +BillboardTest:testRemoveToWhitelist() (gas: 239143) +BillboardTest:testSafeTransferByOperator() (gas: 232823) +BillboardTest:testSetBidURIs() (gas: 628156) +BillboardTest:testSetBoardByCreator() (gas: 339913) +BillboardTest:testUpgradeRegistry() (gas: 3411474) +BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1039157, ~: 1039157) +BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 702873, ~: 702873) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index d080ada..6378945 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -85,7 +85,18 @@ contract Billboard is IBillboard { /// @inheritdoc IBillboard function mintBoard(uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId) { require(epochInterval_ > 0, "Zero epoch interval"); - tokenId = registry.newBoard(msg.sender, taxRate_, epochInterval_); + tokenId = registry.newBoard(msg.sender, taxRate_, epochInterval_, block.number); + whitelist[tokenId][msg.sender] = true; + } + + /// @inheritdoc IBillboard + function mintBoard( + uint256 taxRate_, + uint256 epochInterval_, + uint256 startedAt_ + ) external returns (uint256 tokenId) { + require(epochInterval_ > 0, "Zero epoch interval"); + tokenId = registry.newBoard(msg.sender, taxRate_, epochInterval_, startedAt_); whitelist[tokenId][msg.sender] = true; } @@ -136,7 +147,7 @@ contract Billboard is IBillboard { IBillboardRegistry.Board memory _board = registry.getBoard(tokenId_); require(_board.creator != address(0), "Board not found"); - uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval); + uint256 _endedAt = this.getBlockFromEpoch(_board.startedAt, epoch_ + 1, _board.epochInterval); require(block.number < _endedAt, "Auction ended"); IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, msg.sender); @@ -144,7 +155,7 @@ contract Billboard is IBillboard { uint256 _tax = calculateTax(tokenId_, price_); // create new bid if no bid exists - if (_bid.createdAt == 0) { + if (_bid.placedAt == 0) { // transfer bid price and tax to the registry SafeERC20.safeTransferFrom(registry.currency(), msg.sender, address(registry), price_ + _tax); @@ -188,14 +199,14 @@ contract Billboard is IBillboard { require(_board.creator != address(0), "Board not found"); // revert if auction is still running - uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval); + uint256 _endedAt = this.getBlockFromEpoch(_board.startedAt, epoch_ + 1, _board.epochInterval); require(block.number >= _endedAt, "Auction not ended"); address _highestBidder = registry.highestBidder(tokenId_, epoch_); IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder); // revert if no bid - require(_highestBid.createdAt != 0, "No bid"); + require(_highestBid.placedAt != 0, "No bid"); // skip if auction is already cleared if (_highestBid.isWon) { @@ -282,13 +293,13 @@ contract Billboard is IBillboard { } /// @inheritdoc IBillboard - function withdrawBid(uint256 tokenId_, uint256 epoch_) external { + function withdrawBid(uint256 tokenId_, uint256 epoch_, address bidder_) external { // revert if board not found IBillboardRegistry.Board memory _board = this.getBoard(tokenId_); require(_board.creator != address(0), "Board not found"); // revert if auction is not ended - uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval); + uint256 _endedAt = this.getBlockFromEpoch(_board.startedAt, epoch_ + 1, _board.epochInterval); require(block.number >= _endedAt, "Auction not ended"); // revert if auction is not cleared @@ -296,29 +307,37 @@ contract Billboard is IBillboard { IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder); require(_highestBid.isWon, "Auction not cleared"); - IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, msg.sender); + IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, bidder_); uint256 amount = _bid.price + _bid.tax; - require(_bid.createdAt != 0, "Bid not found"); + require(_bid.placedAt != 0, "Bid not found"); require(!_bid.isWithdrawn, "Bid already withdrawn"); require(!_bid.isWon, "Bid already won"); require(amount > 0, "Zero amount"); // set bid.isWithdrawn to true first to prevent reentrancy - registry.setBidWithdrawn(tokenId_, epoch_, msg.sender, true); + registry.setBidWithdrawn(tokenId_, epoch_, bidder_, true); // transfer bid price and tax back to the bidder - registry.transferCurrencyByOperator(msg.sender, amount); + registry.transferCurrencyByOperator(bidder_, amount); } /// @inheritdoc IBillboard - function getEpochFromBlock(uint256 block_, uint256 epochInterval_) public pure returns (uint256 epoch) { - return block_ / epochInterval_; + function getEpochFromBlock( + uint256 startedAt_, + uint256 block_, + uint256 epochInterval_ + ) public pure returns (uint256 epoch) { + return (block_ - startedAt_) / epochInterval_; } /// @inheritdoc IBillboard - function getBlockFromEpoch(uint256 epoch_, uint256 epochInterval_) public pure returns (uint256 blockNumber) { - return epoch_ * epochInterval_; + function getBlockFromEpoch( + uint256 startedAt_, + uint256 epoch_, + uint256 epochInterval_ + ) public pure returns (uint256 blockNumber) { + return startedAt_ + (epoch_ * epochInterval_); } ////////////////////////////// @@ -335,8 +354,8 @@ contract Billboard is IBillboard { } /// @inheritdoc IBillboard - function withdrawTax() external returns (uint256 tax) { - (uint256 _taxAccumulated, uint256 _taxWithdrawn) = registry.taxTreasury(msg.sender); + function withdrawTax(address creator_) external returns (uint256 tax) { + (uint256 _taxAccumulated, uint256 _taxWithdrawn) = registry.taxTreasury(creator_); uint256 amount = _taxAccumulated - _taxWithdrawn; @@ -344,13 +363,13 @@ contract Billboard is IBillboard { // set taxTreasury.withdrawn to taxTreasury.accumulated first // to prevent reentrancy - registry.setTaxTreasury(msg.sender, _taxAccumulated, _taxAccumulated); + registry.setTaxTreasury(creator_, _taxAccumulated, _taxAccumulated); // transfer tax to the owner - registry.transferCurrencyByOperator(msg.sender, amount); + registry.transferCurrencyByOperator(creator_, amount); // emit TaxWithdrawn - registry.emitTaxWithdrawn(msg.sender, amount); + registry.emitTaxWithdrawn(creator_, amount); return amount; } diff --git a/src/Billboard/BillboardRegistry.sol b/src/Billboard/BillboardRegistry.sol index ec2514c..fd22883 100644 --- a/src/Billboard/BillboardRegistry.sol +++ b/src/Billboard/BillboardRegistry.sol @@ -80,7 +80,8 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { function newBoard( address to_, uint256 taxRate_, - uint256 epochInterval_ + uint256 epochInterval_, + uint256 startedAt_ ) external isFromOperator returns (uint256 tokenId) { lastTokenId.increment(); tokenId = lastTokenId.current(); @@ -95,7 +96,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { location: "", taxRate: taxRate_, epochInterval: epochInterval_, - createdAt: block.number + startedAt: startedAt_ }); emit BoardCreated(tokenId, to_, taxRate_, epochInterval_); @@ -145,7 +146,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { tax: tax_, contentURI: contentURI_, redirectURI: redirectURI_, - createdAt: block.number, + placedAt: block.number, updatedAt: block.number, isWithdrawn: false, isWon: false @@ -173,8 +174,8 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { string calldata redirectURI_, bool hasURIs ) external isFromOperator { - Bid memory _bid = bids[tokenId_][epoch_][bidder_]; - require(_bid.createdAt != 0, "Bid not found"); + Bid storage _bid = bids[tokenId_][epoch_][bidder_]; + require(_bid.placedAt != 0, "Bid not found"); _bid.price = price_; _bid.tax = tax_; @@ -196,7 +197,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { // the highest bidder since the block.number is always greater. function _sethighestBidder(uint256 tokenId_, uint256 epoch_, uint256 price_, address bidder_) internal { address _highestBidder = highestBidder[tokenId_][epoch_]; - Bid memory highestBid = bids[tokenId_][epoch_][_highestBidder]; + Bid storage highestBid = bids[tokenId_][epoch_][_highestBidder]; if (_highestBidder == address(0) || price_ > highestBid.price) { highestBidder[tokenId_][epoch_] = bidder_; } @@ -210,8 +211,8 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { string calldata contentURI_, string calldata redirectURI_ ) external isFromOperator { - Bid memory _bid = bids[tokenId_][epoch_][bidder_]; - require(_bid.createdAt != 0, "Bid not found"); + Bid storage _bid = bids[tokenId_][epoch_][bidder_]; + require(_bid.placedAt != 0, "Bid not found"); _bid.contentURI = contentURI_; _bid.redirectURI = redirectURI_; diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index d5c2f48..581d767 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -72,6 +72,17 @@ interface IBillboard { */ function mintBoard(uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId); + /** + * @notice Mint a new board (NFT). + * + * @param taxRate_ Tax rate per epoch. (e.g. 1024 for 10.24% per epoch) + * @param epochInterval_ Epoch interval in blocks (e.g. 100 for 100 blocks). + * @param startedAt_ Block number when the board starts the first epoch. + * + * @return tokenId Token ID of the new board. + */ + function mintBoard(uint256 taxRate_, uint256 epochInterval_, uint256 startedAt_) external returns (uint256 tokenId); + /** * @notice Get metadata of a board . * @@ -213,28 +224,39 @@ interface IBillboard { * * @param tokenId_ Token ID. * @param epoch_ Epoch. + * @param bidder_ Address of bidder. */ - function withdrawBid(uint256 tokenId_, uint256 epoch_) external; + function withdrawBid(uint256 tokenId_, uint256 epoch_, address bidder_) external; /** * @notice Calculate epoch from block number. * + * @param startedAt_ Started at block number. * @param block_ Block number. * @param epochInterval_ Epoch interval. * * @return epoch Epoch. */ - function getEpochFromBlock(uint256 block_, uint256 epochInterval_) external pure returns (uint256 epoch); + function getEpochFromBlock( + uint256 startedAt_, + uint256 block_, + uint256 epochInterval_ + ) external pure returns (uint256 epoch); /** * @notice Calculate block number from epoch. * + * @param startedAt_ Started at block number. * @param epoch_ Epoch. * @param epochInterval_ Epoch interval. * * @return blockNumber Block number. */ - function getBlockFromEpoch(uint256 epoch_, uint256 epochInterval_) external pure returns (uint256 blockNumber); + function getBlockFromEpoch( + uint256 startedAt_, + uint256 epoch_, + uint256 epochInterval_ + ) external pure returns (uint256 blockNumber); ////////////////////////////// /// Tax & Withdraw @@ -262,8 +284,10 @@ interface IBillboard { /** * @notice Withdraw accumulated taxation. * + * @param creator_ Address of board creator. + * */ - function withdrawTax() external returns (uint256 tax); + function withdrawTax(address creator_) external returns (uint256 tax); ////////////////////////////// /// ERC721 related diff --git a/src/Billboard/IBillboardRegistry.sol b/src/Billboard/IBillboardRegistry.sol index 60a990c..c9eae4e 100644 --- a/src/Billboard/IBillboardRegistry.sol +++ b/src/Billboard/IBillboardRegistry.sol @@ -104,7 +104,7 @@ interface IBillboardRegistry is IERC721 { address creator; uint256 taxRate; uint256 epochInterval; // in blocks - uint256 createdAt; // gensis epoch, block number + uint256 startedAt; // gensis epoch, block number // mutable data string name; string description; @@ -117,7 +117,7 @@ interface IBillboardRegistry is IERC721 { uint256 tax; string contentURI; string redirectURI; - uint256 createdAt; // block number + uint256 placedAt; // block number uint256 updatedAt; // block number bool isWon; bool isWithdrawn; @@ -152,10 +152,16 @@ interface IBillboardRegistry is IERC721 { * @param to_ Address of the board owner. * @param taxRate_ Tax rate of the new board. * @param epochInterval_ Epoch interval of the new board. + * @param startedAt_ Block number when the board starts the first epoch. * * @return tokenId Token ID of the new board. */ - function newBoard(address to_, uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId); + function newBoard( + address to_, + uint256 taxRate_, + uint256 epochInterval_, + uint256 startedAt_ + ) external returns (uint256 tokenId); /** * @notice Set metadata of a board. diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 06627f9..7714f38 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -88,22 +88,22 @@ contract BillboardTest is BillboardTestBase { // mint vm.expectEmit(true, true, true, true); emit IERC721.Transfer(address(0), ADMIN, 1); - operator.mintBoard(TAX_RATE, EPOCH_INTERVAL); + uint256 _tokenId = operator.mintBoard(TAX_RATE, EPOCH_INTERVAL); // ownership assertEq(registry.balanceOf(ADMIN), 1); - assertEq(registry.ownerOf(1), ADMIN); + assertEq(registry.ownerOf(_tokenId), ADMIN); // data - IBillboardRegistry.Board memory board = operator.getBoard(1); - assertEq(board.creator, ADMIN); - assertEq(board.name, ""); - assertEq(board.description, ""); - assertEq(board.imageURI, ""); - assertEq(board.location, ""); - assertEq(board.taxRate, TAX_RATE); - assertEq(board.epochInterval, EPOCH_INTERVAL); - assertEq(board.createdAt, block.number); + IBillboardRegistry.Board memory _board = operator.getBoard(_tokenId); + assertEq(_board.creator, ADMIN); + assertEq(_board.name, ""); + assertEq(_board.description, ""); + assertEq(_board.imageURI, ""); + assertEq(_board.location, ""); + assertEq(_board.taxRate, TAX_RATE); + assertEq(_board.epochInterval, EPOCH_INTERVAL); + assertEq(_board.startedAt, block.number); vm.stopPrank(); vm.startPrank(USER_A); @@ -112,11 +112,17 @@ contract BillboardTest is BillboardTestBase { uint256 _newTokenId = 2; vm.expectEmit(true, true, true, true); emit IERC721.Transfer(address(0), USER_A, _newTokenId); - uint256 _tokenId = operator.mintBoard(TAX_RATE, EPOCH_INTERVAL); - assertEq(_tokenId, _newTokenId); + uint256 _tokenId2 = operator.mintBoard(TAX_RATE, EPOCH_INTERVAL); + assertEq(_tokenId2, _newTokenId); assertEq(registry.balanceOf(USER_A), 1); - board = operator.getBoard(_tokenId); - assertEq(board.creator, USER_A); + IBillboardRegistry.Board memory _board2 = operator.getBoard(_tokenId2); + assertEq(_board2.creator, USER_A); + + // mint with startedAt + uint256 _startedAt = block.number + 100; + uint256 _tokenId3 = operator.mintBoard(TAX_RATE, EPOCH_INTERVAL, _startedAt); + IBillboardRegistry.Board memory _board3 = operator.getBoard(_tokenId3); + assertEq(_board3.startedAt, _startedAt); } function testSetBoardByCreator() public { @@ -181,7 +187,7 @@ contract BillboardTest is BillboardTestBase { function testPlaceBid(uint96 _price) public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _tax = operator.calculateTax(_tokenId, _price); uint256 _total = _price + _tax; deal(address(usdt), USER_A, _total); @@ -212,7 +218,7 @@ contract BillboardTest is BillboardTestBase { IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _epoch, USER_A); assertEq(_bid.price, _price); assertEq(_bid.tax, _tax); - assertEq(_bid.createdAt, block.number); + assertEq(_bid.placedAt, block.number); assertEq(_bid.updatedAt, block.number); assertEq(_bid.isWon, false); assertEq(_bid.isWithdrawn, false); @@ -229,7 +235,7 @@ contract BillboardTest is BillboardTestBase { function testPlaceBidWithSamePrices(uint96 _price) public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _tax = operator.calculateTax(_tokenId, _price); uint256 _total = _price + _tax; @@ -243,10 +249,10 @@ contract BillboardTest is BillboardTestBase { // check bids IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _epoch, USER_A); - assertEq(_bidA.createdAt, block.number); + assertEq(_bidA.placedAt, block.number); assertEq(_bidA.isWon, false); IBillboardRegistry.Bid memory _bidB = registry.getBid(_tokenId, _epoch, USER_A); - assertEq(_bidB.createdAt, block.number); + assertEq(_bidB.placedAt, block.number); assertEq(_bidB.isWon, false); // check registry balance @@ -258,9 +264,12 @@ contract BillboardTest is BillboardTestBase { vm.assume(_price < type(uint96).max / 4); (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _placedAt = block.number; + uint256 _updatedAt = block.number + 1; + // bid with USER_A _placeBid(_tokenId, _epoch, USER_A, _price); assertEq(registry.highestBidder(_tokenId, _epoch), USER_A); @@ -271,6 +280,7 @@ contract BillboardTest is BillboardTestBase { assertEq(registry.highestBidder(_tokenId, _epoch), USER_B); // bid with USER_A + vm.roll(_updatedAt); uint256 _priceA = _price * 4; uint256 _taxA = operator.calculateTax(_tokenId, _priceA); uint256 _totalA = _priceA + _taxA; @@ -282,11 +292,18 @@ contract BillboardTest is BillboardTestBase { uint256 _taxDiff = _taxA - _tax; uint256 _totalDiff = _priceDiff + _taxDiff; assertEq(usdt.balanceOf(USER_A), _totalA - _totalDiff); + + // check bid + IBillboardRegistry.Bid memory _bidA = registry.getBid(_tokenId, _epoch, USER_A); + assertEq(_bidA.price, _priceA); + assertEq(_bidA.tax, _taxA); + assertEq(_bidA.placedAt, _placedAt); + assertEq(_bidA.updatedAt, _updatedAt); } function testPlaceBidZeroPrice() public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _prevBalance = usdt.balanceOf(ADMIN); vm.startPrank(ADMIN); @@ -301,16 +318,16 @@ contract BillboardTest is BillboardTestBase { // check bid IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _epoch, ADMIN); - assertEq(_bid.createdAt, block.number); + assertEq(_bid.placedAt, block.number); assertEq(_bid.isWon, false); } function testCannotPlaceBidIfAuctionEnded() public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _price = 1 ether; - uint256 _endedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + uint256 _endedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); vm.prank(ADMIN); operator.addToWhitelist(_tokenId, USER_A); @@ -328,7 +345,7 @@ contract BillboardTest is BillboardTestBase { function testCannotPlaceBidByAttacker() public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _price = 1 ether; uint256 _tax = operator.calculateTax(_tokenId, _price); uint256 _total = _price + _tax; @@ -340,14 +357,37 @@ contract BillboardTest is BillboardTestBase { operator.placeBid(_tokenId, _epoch, _price); } + function testSetBidURIs() public { + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + + _placeBid(_tokenId, _epoch, USER_A, 1 ether); + + // check bid + IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _epoch, USER_A); + assertEq(_bid.contentURI, ""); + assertEq(_bid.redirectURI, ""); + + // set bid AD data + string memory _contentURI = "content URI"; + string memory _redirectURI = "redirect URI"; + + vm.prank(USER_A); + operator.setBidURIs(_tokenId, _epoch, _contentURI, _redirectURI); + + IBillboardRegistry.Bid memory _newBid = registry.getBid(_tokenId, _epoch, USER_A); + assertEq(_newBid.contentURI, _contentURI); + assertEq(_newBid.redirectURI, _redirectURI); + } + function testClearAuction(uint96 _price) public { vm.assume(_price > 0.001 ether); (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _tax = operator.calculateTax(_tokenId, _price); uint256 _placedAt = block.number; - uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); // place bid _placeBid(_tokenId, _epoch, USER_A, _price); @@ -368,7 +408,7 @@ contract BillboardTest is BillboardTestBase { IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _epoch, USER_A); assertEq(_bid.price, _price); assertEq(_bid.tax, _tax); - assertEq(_bid.createdAt, _placedAt); + assertEq(_bid.placedAt, _placedAt); assertEq(_bid.isWon, true); assertEq(_bid.isWithdrawn, false); @@ -381,12 +421,12 @@ contract BillboardTest is BillboardTestBase { function testClearAuctions() public { (uint256 _tokenId1, IBillboardRegistry.Board memory _board1) = _mintBoard(); (uint256 _tokenId2, IBillboardRegistry.Board memory _board2) = _mintBoard(); - uint256 _epoch1 = operator.getEpochFromBlock(block.number, _board1.epochInterval); - uint256 _epoch2 = operator.getEpochFromBlock(block.number, _board2.epochInterval); + uint256 _epoch1 = operator.getEpochFromBlock(_board1.startedAt, block.number, _board1.epochInterval); + uint256 _epoch2 = operator.getEpochFromBlock(_board2.startedAt, block.number, _board2.epochInterval); _placeBid(_tokenId1, _epoch1, USER_A, 1 ether); _placeBid(_tokenId2, _epoch2, USER_B, 1 ether); - uint256 _clearedAt = operator.getBlockFromEpoch(_epoch1 + 1, _board1.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_board1.startedAt, _epoch1 + 1, _board1.epochInterval); // clear auctions vm.expectEmit(true, true, true, true); @@ -416,8 +456,8 @@ contract BillboardTest is BillboardTestBase { function testCannotClearAuctionIfAuctionNotEnded() public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); - uint256 _endedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + uint256 _endedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); vm.expectRevert("Auction not ended"); operator.clearAuction(_tokenId, _epoch); @@ -429,8 +469,8 @@ contract BillboardTest is BillboardTestBase { function testCannotClearAuctionIfNoBid() public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); - uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); vm.roll(_clearedAt); vm.expectRevert("No bid"); @@ -444,7 +484,7 @@ contract BillboardTest is BillboardTestBase { vm.assume(_offset <= _limit); (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); for (uint8 i = 0; i < _bidCount; i++) { address _bidder = address(uint160(2000 + i)); @@ -486,10 +526,10 @@ contract BillboardTest is BillboardTestBase { vm.assume(_price > 0.001 ether); (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _tax = operator.calculateTax(_tokenId, _price); uint256 _total = _price + _tax; - uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); // new bid with USER_A _placeBid(_tokenId, _epoch, USER_A, _price); @@ -512,7 +552,7 @@ contract BillboardTest is BillboardTestBase { emit IBillboardRegistry.BidWithdrawn(_tokenId, _epoch, USER_B); vm.prank(USER_B); - operator.withdrawBid(_tokenId, _epoch); + operator.withdrawBid(_tokenId, _epoch, USER_B); // check balances assertEq(usdt.balanceOf(USER_A), 0); @@ -523,10 +563,10 @@ contract BillboardTest is BillboardTestBase { vm.assume(_price > 0.001 ether); (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _tax = operator.calculateTax(_tokenId, _price); uint256 _total = _price + _tax; - uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); // new bid with USER_A _placeBid(_tokenId, _epoch, USER_A, _price); @@ -540,21 +580,21 @@ contract BillboardTest is BillboardTestBase { // withdraw bid vm.prank(USER_B); - operator.withdrawBid(_tokenId, _epoch); + operator.withdrawBid(_tokenId, _epoch, USER_B); assertEq(usdt.balanceOf(USER_B), _total); // withdraw bid again vm.prank(USER_B); vm.expectRevert("Bid already withdrawn"); - operator.withdrawBid(_tokenId, _epoch); + operator.withdrawBid(_tokenId, _epoch, USER_B); } function testCannotWithdrawBidIfWon(uint96 _price) public { vm.assume(_price > 0.001 ether); (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); - uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); // new bid with USER_A _placeBid(_tokenId, _epoch, USER_A, _price); @@ -569,15 +609,15 @@ contract BillboardTest is BillboardTestBase { // withdraw bid vm.prank(USER_A); vm.expectRevert("Bid already won"); - operator.withdrawBid(_tokenId, _epoch); + operator.withdrawBid(_tokenId, _epoch, USER_A); } function testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96 _price) public { vm.assume(_price > 0.001 ether); (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); - uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); // new bid with USER_A _placeBid(_tokenId, _epoch, USER_A, _price); @@ -585,20 +625,20 @@ contract BillboardTest is BillboardTestBase { // auction is not ended vm.roll(_clearedAt - 1); vm.expectRevert("Auction not ended"); - operator.withdrawBid(_tokenId, _epoch); + operator.withdrawBid(_tokenId, _epoch, USER_A); // auction is ended but not cleared vm.roll(_clearedAt); vm.expectRevert("Auction not cleared"); - operator.withdrawBid(_tokenId, _epoch); + operator.withdrawBid(_tokenId, _epoch, USER_A); } function testCannotWithdrawBidIfNotFound(uint96 _price) public { vm.assume(_price > 0.001 ether); (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); - uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); // new bid with USER_A _placeBid(_tokenId, _epoch, USER_A, _price); @@ -609,66 +649,66 @@ contract BillboardTest is BillboardTestBase { vm.prank(USER_B); vm.expectRevert("Bid not found"); - operator.withdrawBid(_tokenId, _epoch); + operator.withdrawBid(_tokenId, _epoch, USER_B); } function testGetEpochFromBlock() public { // epoch interval = 1 - assertEq(operator.getEpochFromBlock(0, 1), 0); - assertEq(operator.getEpochFromBlock(1, 1), 1); + assertEq(operator.getEpochFromBlock(0, 0, 1), 0); + assertEq(operator.getEpochFromBlock(0, 1, 1), 1); // epoch interval = 101 - assertEq(operator.getEpochFromBlock(0, 101), 0); - assertEq(operator.getEpochFromBlock(1, 101), 0); - assertEq(operator.getEpochFromBlock(100, 101), 0); - assertEq(operator.getEpochFromBlock(101, 101), 1); - assertEq(operator.getEpochFromBlock(203, 101), 2); + assertEq(operator.getEpochFromBlock(0, 0, 101), 0); + assertEq(operator.getEpochFromBlock(0, 1, 101), 0); + assertEq(operator.getEpochFromBlock(0, 100, 101), 0); + assertEq(operator.getEpochFromBlock(0, 101, 101), 1); + assertEq(operator.getEpochFromBlock(0, 203, 101), 2); // epoch interval = MAX - assertEq(operator.getEpochFromBlock(0, type(uint256).max), 0); - assertEq(operator.getEpochFromBlock(1, type(uint256).max), 0); - assertEq(operator.getEpochFromBlock(type(uint256).max, type(uint256).max), 1); + assertEq(operator.getEpochFromBlock(0, 0, type(uint256).max), 0); + assertEq(operator.getEpochFromBlock(0, 1, type(uint256).max), 0); + assertEq(operator.getEpochFromBlock(0, type(uint256).max, type(uint256).max), 1); } function testCannotGetEpochFromBlock() public { // panic: division or modulo by zero vm.expectRevert(); - assertEq(operator.getEpochFromBlock(0, 0), 0); + assertEq(operator.getEpochFromBlock(0, 0, 0), 0); vm.expectRevert(); - assertEq(operator.getEpochFromBlock(1, 0), 0); + assertEq(operator.getEpochFromBlock(0, 1, 0), 0); vm.expectRevert(); - assertEq(operator.getEpochFromBlock(0, type(uint256).min), 0); + assertEq(operator.getEpochFromBlock(0, 0, type(uint256).min), 0); // panic: arithmetic underflow or overflow vm.expectRevert(); - assertEq(operator.getEpochFromBlock(type(uint256).max + 1, type(uint256).max), 1); + assertEq(operator.getEpochFromBlock(0, type(uint256).max + 1, type(uint256).max), 1); vm.expectRevert(); - assertEq(operator.getEpochFromBlock(type(uint256).min - 1, type(uint256).min), 1); + assertEq(operator.getEpochFromBlock(0, type(uint256).min - 1, type(uint256).min), 1); } function testGetBlockFromEpoch() public { // epoch interval = 1 - assertEq(operator.getBlockFromEpoch(0, 1), 0); - assertEq(operator.getBlockFromEpoch(1, 1), 1); + assertEq(operator.getBlockFromEpoch(0, 0, 1), 0); + assertEq(operator.getBlockFromEpoch(0, 1, 1), 1); // epoch interval = 101 - assertEq(operator.getBlockFromEpoch(0, 101), 0); - assertEq(operator.getBlockFromEpoch(1, 101), 101); - assertEq(operator.getBlockFromEpoch(2, 101), 202); + assertEq(operator.getBlockFromEpoch(0, 0, 101), 0); + assertEq(operator.getBlockFromEpoch(0, 1, 101), 101); + assertEq(operator.getBlockFromEpoch(0, 2, 101), 202); // epoch interval = MAX - assertEq(operator.getBlockFromEpoch(0, type(uint256).max), 0); - assertEq(operator.getBlockFromEpoch(1, type(uint256).max), type(uint256).max); + assertEq(operator.getBlockFromEpoch(0, 0, type(uint256).max), 0); + assertEq(operator.getBlockFromEpoch(0, 1, type(uint256).max), type(uint256).max); // epoch interval = MIN - assertEq(operator.getBlockFromEpoch(0, type(uint256).min), 0); - assertEq(operator.getBlockFromEpoch(1, type(uint256).min), 0); + assertEq(operator.getBlockFromEpoch(0, 0, type(uint256).min), 0); + assertEq(operator.getBlockFromEpoch(0, 1, type(uint256).min), 0); } function testCannotGetBlockFromEpoch() public { // panic: arithmetic underflow or overflow vm.expectRevert(); - assertEq(operator.getBlockFromEpoch(2, type(uint256).max), 0); + assertEq(operator.getBlockFromEpoch(0, 2, type(uint256).max), 0); } ////////////////////////////// @@ -711,9 +751,9 @@ contract BillboardTest is BillboardTestBase { vm.assume(_price > 0.001 ether); (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); - uint256 _epoch = operator.getEpochFromBlock(block.number, _board.epochInterval); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _tax = operator.calculateTax(_tokenId, _price); - uint256 _clearedAt = operator.getBlockFromEpoch(_epoch + 1, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); // place bid with USER_A _placeBid(_tokenId, _epoch, USER_A, _price); @@ -733,7 +773,7 @@ contract BillboardTest is BillboardTestBase { emit IBillboardRegistry.TaxWithdrawn(ADMIN, _tax); vm.prank(ADMIN); - operator.withdrawTax(); + operator.withdrawTax(ADMIN); // check balances assertEq(usdt.balanceOf(address(registry)), _prevRegistryBalance - _tax); @@ -747,7 +787,7 @@ contract BillboardTest is BillboardTestBase { vm.prank(ADMIN); vm.expectRevert("Zero amount"); - operator.withdrawTax(); + operator.withdrawTax(ADMIN); } ////////////////////////////// From ee35f02213d93de5d7632b5ed8938f74f15701ef Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:02:29 +0800 Subject: [PATCH 15/24] feat(billboard): support to close board --- .gas-snapshot | 85 ++++++++++---------- src/Billboard/Billboard.sol | 32 +++++--- src/Billboard/IBillboard.sol | 11 +-- src/test/Billboard/BillboardTest.t.sol | 90 ++++++++++++++++++---- src/test/Billboard/BillboardTestBase.t.sol | 2 +- 5 files changed, 148 insertions(+), 72 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 4df7cca..a15ddcf 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,48 +9,51 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testAddToWhitelist() (gas: 252415) -BillboardTest:testApproveAndTransfer() (gas: 253043) -BillboardTest:testCalculateTax() (gas: 537542) -BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20183) -BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225988) -BillboardTest:testCannotApproveByAttacker() (gas: 221891) -BillboardTest:testCannotCalculateTax() (gas: 216404) -BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 248674) -BillboardTest:testCannotClearAuctionIfNoBid() (gas: 256039) -BillboardTest:testCannotGetBlockFromEpoch() (gas: 8688) -BillboardTest:testCannotGetEpochFromBlock() (gas: 15590) -BillboardTest:testCannotPlaceBidByAttacker() (gas: 460155) -BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 271390) -BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 226118) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 219003) -BillboardTest:testCannotSetBoardByAttacker() (gas: 227087) -BillboardTest:testCannotSetBoardByOwner() (gas: 359460) -BillboardTest:testCannotTransferByOperator() (gas: 224137) -BillboardTest:testCannotTransferToZeroAddress() (gas: 219713) -BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9039) -BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 600791, ~: 600791) -BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 715717, ~: 715717) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 966732, ~: 966732) -BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1050826, ~: 1050826) -BillboardTest:testClearAuction(uint96) (runs: 256, μ: 697518, ~: 697518) -BillboardTest:testClearAuctions() (gas: 1248982) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 5071994, ~: 2128035) -BillboardTest:testGetBlockFromEpoch() (gas: 14680) -BillboardTest:testGetEpochFromBlock() (gas: 15789) -BillboardTest:testGetTokenURI() (gas: 389096) -BillboardTest:testMintBoard() (gas: 580688) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 781546, ~: 783879) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 965751, ~: 965756) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 849576, ~: 853075) -BillboardTest:testPlaceBidZeroPrice() (gas: 401269) -BillboardTest:testRemoveToWhitelist() (gas: 239143) +BillboardTest:testApproveAndTransfer() (gas: 253021) +BillboardTest:testCalculateTax() (gas: 537366) +BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20184) +BillboardTest:testCannotApproveByAttacker() (gas: 221803) +BillboardTest:testCannotCalculateTax() (gas: 216449) +BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 253188) +BillboardTest:testCannotClearAuctionIfClosed() (gas: 250314) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 258354) +BillboardTest:testCannotGetBlockFromEpoch() (gas: 8666) +BillboardTest:testCannotGetEpochFromBlock() (gas: 15634) +BillboardTest:testCannotPlaceBidByAttacker() (gas: 461915) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 276192) +BillboardTest:testCannotPlaceBidIfClosed() (gas: 250190) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 219025) +BillboardTest:testCannotSetBoardByAttacker() (gas: 227022) +BillboardTest:testCannotSetBoardByOwner() (gas: 359240) +BillboardTest:testCannotSetClosedByAttacker() (gas: 225976) +BillboardTest:testCannotSetWhitelistByAttacker() (gas: 226186) +BillboardTest:testCannotTransferByOperator() (gas: 224159) +BillboardTest:testCannotTransferToZeroAddress() (gas: 219669) +BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9105) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 603933, ~: 603933) +BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 718940, ~: 718940) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 970407, ~: 970407) +BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1054869, ~: 1054869) +BillboardTest:testClearAuction(uint96) (runs: 256, μ: 700310, ~: 700310) +BillboardTest:testClearAuctions() (gas: 1254674) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4921861, ~: 2132068) +BillboardTest:testGetBlockFromEpoch() (gas: 14900) +BillboardTest:testGetEpochFromBlock() (gas: 15987) +BillboardTest:testGetTokenURI() (gas: 388974) +BillboardTest:testMintBoard() (gas: 580732) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 784616, ~: 786949) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 968453, ~: 968458) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 852569, ~: 856068) +BillboardTest:testPlaceBidZeroPrice() (gas: 403633) BillboardTest:testSafeTransferByOperator() (gas: 232823) -BillboardTest:testSetBidURIs() (gas: 628156) -BillboardTest:testSetBoardByCreator() (gas: 339913) -BillboardTest:testUpgradeRegistry() (gas: 3411474) -BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1039157, ~: 1039157) -BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 702873, ~: 702873) +BillboardTest:testSetBidURIs() (gas: 630718) +BillboardTest:testSetBoardByCreator() (gas: 339694) +BillboardTest:testSetClosed() (gas: 238386) +BillboardTest:testSetWhitelist() (gas: 242793) +BillboardTest:testUpgradeRegistry() (gas: 3494596) +BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1042812, ~: 1042812) +BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 667875, ~: 667875) +BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 705598, ~: 705598) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 6378945..7328a6d 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -16,6 +16,9 @@ contract Billboard is IBillboard { // tokenId => address => whitelisted mapping(uint256 => mapping(address => bool)) public whitelist; + // tokenId => closed + mapping(uint256 => bool) public closed; + constructor(address token_, address payable registry_, address admin_, string memory name_, string memory symbol_) { require(admin_ != address(0), "Zero address"); admin = admin_; @@ -44,6 +47,11 @@ contract Billboard is IBillboard { _; } + modifier isNotClosed(uint256 tokenId_) { + require(closed[tokenId_] != true, "Closed"); + _; + } + modifier isFromCreator(uint256 tokenId_) { IBillboardRegistry.Board memory _board = registry.getBoard(tokenId_); require(_board.creator == msg.sender, "Creator"); @@ -69,13 +77,13 @@ contract Billboard is IBillboard { ////////////////////////////// /// @inheritdoc IBillboard - function addToWhitelist(uint256 tokenId_, address account_) external isFromCreator(tokenId_) { - whitelist[tokenId_][account_] = true; + function setWhitelist(uint256 tokenId_, address account_, bool whitelisted) external isFromCreator(tokenId_) { + whitelist[tokenId_][account_] = whitelisted; } /// @inheritdoc IBillboard - function removeFromWhitelist(uint256 tokenId_, address account_) external isFromCreator(tokenId_) { - whitelist[tokenId_][account_] = false; + function setClosed(uint256 tokenId_, bool closed_) external isFromCreator(tokenId_) { + closed[tokenId_] = closed_; } ////////////////////////////// @@ -121,7 +129,11 @@ contract Billboard is IBillboard { ////////////////////////////// /// @inheritdoc IBillboard - function placeBid(uint256 tokenId_, uint256 epoch_, uint256 price_) external payable isFromWhitelist(tokenId_) { + function placeBid( + uint256 tokenId_, + uint256 epoch_, + uint256 price_ + ) external payable isNotClosed(tokenId_) isFromWhitelist(tokenId_) { _placeBid(tokenId_, epoch_, price_, "", "", false); } @@ -132,7 +144,7 @@ contract Billboard is IBillboard { uint256 price_, string calldata contentURI_, string calldata redirectURI_ - ) external payable isFromWhitelist(tokenId_) { + ) external payable isNotClosed(tokenId_) isFromWhitelist(tokenId_) { _placeBid(tokenId_, epoch_, price_, contentURI_, redirectURI_, true); } @@ -193,7 +205,7 @@ contract Billboard is IBillboard { function clearAuction( uint256 tokenId_, uint256 epoch_ - ) public returns (address highestBidder, uint256 price, uint256 tax) { + ) public isNotClosed(tokenId_) returns (address highestBidder, uint256 price, uint256 tax) { // revert if board not found IBillboardRegistry.Board memory _board = this.getBoard(tokenId_); require(_board.creator != address(0), "Board not found"); @@ -294,18 +306,20 @@ contract Billboard is IBillboard { /// @inheritdoc IBillboard function withdrawBid(uint256 tokenId_, uint256 epoch_, address bidder_) external { + bool _isClosed = closed[tokenId_]; + // revert if board not found IBillboardRegistry.Board memory _board = this.getBoard(tokenId_); require(_board.creator != address(0), "Board not found"); // revert if auction is not ended uint256 _endedAt = this.getBlockFromEpoch(_board.startedAt, epoch_ + 1, _board.epochInterval); - require(block.number >= _endedAt, "Auction not ended"); + require(_isClosed || block.number >= _endedAt, "Auction not ended"); // revert if auction is not cleared address _highestBidder = registry.highestBidder(tokenId_, epoch_); IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder); - require(_highestBid.isWon, "Auction not cleared"); + require(_isClosed || _highestBid.isWon, "Auction not cleared"); IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, bidder_); uint256 amount = _bid.price + _bid.tax; diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index 581d767..44f0ed8 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -43,20 +43,21 @@ interface IBillboard { ////////////////////////////// /** - * @notice Add address to whitelist. + * @notice Add or remove whitelist address. * * @param tokenId_ Token ID. * @param account_ Address of user will be added into whitelist. + * @param whitelisted Whitelisted or not. */ - function addToWhitelist(uint256 tokenId_, address account_) external; + function setWhitelist(uint256 tokenId_, address account_, bool whitelisted) external; /** - * @notice Remove address from whitelist. + * @notice Open or close a board. * * @param tokenId_ Token ID. - * @param account_ Address of user will be removed from whitelist. + * @param closed Closed or not. */ - function removeFromWhitelist(uint256 tokenId_, address account_) external; + function setClosed(uint256 tokenId_, bool closed) external; ////////////////////////////// /// Board diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 7714f38..d6c5038 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -37,45 +37,51 @@ contract BillboardTest is BillboardTestBase { /// Access control ////////////////////////////// - function testAddToWhitelist() public { + function testSetWhitelist() public { (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ADMIN); - operator.addToWhitelist(_tokenId, USER_A); + // add to whitelist + operator.setWhitelist(_tokenId, USER_A, true); assertEq(operator.whitelist(_tokenId, USER_A), true); - assertEq(operator.whitelist(_tokenId, USER_B), false); + + // remove from whitelist + operator.setWhitelist(_tokenId, USER_A, false); + assertEq(operator.whitelist(_tokenId, USER_A), false); } - function testCannotAddToWhitelistByAttacker() public { + function testCannotSetWhitelistByAttacker() public { (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ATTACKER); vm.expectRevert("Creator"); - operator.addToWhitelist(_tokenId, USER_A); + operator.setWhitelist(_tokenId, USER_B, false); } - function testRemoveToWhitelist() public { + function testSetClosed() public { (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ADMIN); - operator.addToWhitelist(_tokenId, USER_A); - assertEq(operator.whitelist(_tokenId, USER_A), true); + // set closed + operator.setClosed(_tokenId, true); + assertEq(operator.closed(_tokenId), true); - operator.removeFromWhitelist(_tokenId, USER_A); - assertEq(operator.whitelist(_tokenId, USER_A), false); + // set open + operator.setClosed(_tokenId, false); + assertEq(operator.closed(_tokenId), false); } - function testCannotRemoveToWhitelistByAttacker() public { + function testCannotSetClosedByAttacker() public { (uint256 _tokenId, ) = _mintBoard(); vm.startPrank(ATTACKER); vm.expectRevert("Creator"); - operator.removeFromWhitelist(_tokenId, USER_B); + operator.setClosed(_tokenId, true); } ////////////////////////////// @@ -198,8 +204,8 @@ contract BillboardTest is BillboardTestBase { uint256 _prevRegistryBalance = usdt.balanceOf(address(registry)); vm.startPrank(ADMIN); - operator.addToWhitelist(_tokenId, USER_A); - operator.addToWhitelist(_tokenId, USER_B); + operator.setWhitelist(_tokenId, USER_A, true); + operator.setWhitelist(_tokenId, USER_B, true); vm.stopPrank(); vm.expectEmit(true, true, true, true); @@ -322,6 +328,17 @@ contract BillboardTest is BillboardTestBase { assertEq(_bid.isWon, false); } + function testCannotPlaceBidIfClosed() public { + (uint256 _tokenId, ) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, block.number, 1); + + vm.startPrank(ADMIN); + operator.setClosed(_tokenId, true); + + vm.expectRevert("Closed"); + operator.placeBid(_tokenId, _epoch, 1 ether); + } + function testCannotPlaceBidIfAuctionEnded() public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); @@ -330,7 +347,7 @@ contract BillboardTest is BillboardTestBase { uint256 _endedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); vm.prank(ADMIN); - operator.addToWhitelist(_tokenId, USER_A); + operator.setWhitelist(_tokenId, USER_A, true); vm.startPrank(USER_A); @@ -454,6 +471,17 @@ contract BillboardTest is BillboardTestBase { assertEq(_bid2.isWon, true); } + function testCannotClearAuctionIfClosed() public { + (uint256 _tokenId, ) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(block.number, block.number, 1); + + vm.startPrank(ADMIN); + operator.setClosed(_tokenId, true); + + vm.expectRevert("Closed"); + operator.clearAuction(_tokenId, _epoch); + } + function testCannotClearAuctionIfAuctionNotEnded() public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); @@ -490,7 +518,7 @@ contract BillboardTest is BillboardTestBase { address _bidder = address(uint160(2000 + i)); vm.prank(ADMIN); - operator.addToWhitelist(_tokenId, _bidder); + operator.setWhitelist(_tokenId, _bidder, true); uint256 _price = 1 ether + i; uint256 _tax = operator.calculateTax(_tokenId, _price); @@ -559,6 +587,36 @@ contract BillboardTest is BillboardTestBase { assertEq(usdt.balanceOf(USER_B), _total); } + function testWithdrawBidIfClosed(uint96 _price) public { + vm.assume(_price > 0.001 ether); + + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _total = _price + _tax; + + // bid with USER_A + _placeBid(_tokenId, _epoch, USER_A, _price); + + // set closed + vm.prank(ADMIN); + operator.setClosed(_tokenId, true); + + // withdraw bid + vm.expectEmit(true, true, true, false); + emit IBillboardRegistry.BidWithdrawn(_tokenId, _epoch, USER_A); + + vm.prank(USER_A); + operator.withdrawBid(_tokenId, _epoch, USER_A); + + // check balances + assertEq(usdt.balanceOf(USER_A), _total); + + // check bid + IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _epoch, USER_A); + assertEq(_bid.isWithdrawn, true); + } + function testCannotWithdrawBidTwice(uint96 _price) public { vm.assume(_price > 0.001 ether); diff --git a/src/test/Billboard/BillboardTestBase.t.sol b/src/test/Billboard/BillboardTestBase.t.sol index c7ab1c3..ba03350 100644 --- a/src/test/Billboard/BillboardTestBase.t.sol +++ b/src/test/Billboard/BillboardTestBase.t.sol @@ -70,7 +70,7 @@ contract BillboardTestBase is Test { deal(address(usdt), _bidder, _total); vm.prank(ADMIN); - operator.addToWhitelist(_tokenId, _bidder); + operator.setWhitelist(_tokenId, _bidder, true); vm.prank(_bidder); operator.placeBid(_tokenId, _epoch, _price); From 5e3029d78718f619ebfb19008d8ec192f63e1d8f Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Thu, 4 Jul 2024 13:08:24 +0800 Subject: [PATCH 16/24] feat(billboard): rename BILLBOARD_ERC20_TOKEN to BILLBOARD_CURRENCY_TOKEN --- .env.local.example | 2 +- .env.op-mainnet.example | 2 +- .env.op-sepolia.example | 2 +- .env.polygon-mainnet.example | 2 +- .env.polygon-mumbai.example | 2 +- .gas-snapshot | 14 +++++++------- Makefile | 4 ++-- src/test/Billboard/BillboardTest.t.sol | 20 +++++++++++++------- 8 files changed, 27 insertions(+), 21 deletions(-) diff --git a/.env.local.example b/.env.local.example index 149539a..886f467 100644 --- a/.env.local.example +++ b/.env.local.example @@ -17,7 +17,7 @@ THESPACE_INCENTIVES_ADDRESS= THESPACE_INCENTIVES_TOKENS= THESPACE_LP_ADDRESS= THESPACE_LP_TOKENS= -BILLBOARD_ERC20_TOKEN= +BILLBOARD_CURRENCY_TOKEN= BILLBOARD_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 BILLBOARD_LEASE_TERM= BILLBOARD_ADMIN_ADDRESS= diff --git a/.env.op-mainnet.example b/.env.op-mainnet.example index 880ce74..b12db00 100644 --- a/.env.op-mainnet.example +++ b/.env.op-mainnet.example @@ -18,7 +18,7 @@ THESPACE_INCENTIVES_ADDRESS= THESPACE_INCENTIVES_TOKENS= THESPACE_LP_ADDRESS= THESPACE_LP_TOKENS= -BILLBOARD_ERC20_TOKEN= +BILLBOARD_CURRENCY_TOKEN= BILLBOARD_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 BILLBOARD_LEASE_TERM=900 BILLBOARD_ADMIN_ADDRESS= diff --git a/.env.op-sepolia.example b/.env.op-sepolia.example index 0db906a..e172a74 100644 --- a/.env.op-sepolia.example +++ b/.env.op-sepolia.example @@ -18,7 +18,7 @@ THESPACE_INCENTIVES_ADDRESS= THESPACE_INCENTIVES_TOKENS= THESPACE_LP_ADDRESS= THESPACE_LP_TOKENS= -BILLBOARD_ERC20_TOKEN= +BILLBOARD_CURRENCY_TOKEN= BILLBOARD_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 BILLBOARD_LEASE_TERM=900 BILLBOARD_ADMIN_ADDRESS= diff --git a/.env.polygon-mainnet.example b/.env.polygon-mainnet.example index cf571ce..91af6c3 100644 --- a/.env.polygon-mainnet.example +++ b/.env.polygon-mainnet.example @@ -18,7 +18,7 @@ THESPACE_INCENTIVES_ADDRESS= THESPACE_INCENTIVES_TOKENS= THESPACE_LP_ADDRESS= THESPACE_LP_TOKENS= -BILLBOARD_ERC20_TOKEN= +BILLBOARD_CURRENCY_TOKEN= BILLBOARD_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 BILLBOARD_LEASE_TERM= BILLBOARD_ADMIN_ADDRESS= diff --git a/.env.polygon-mumbai.example b/.env.polygon-mumbai.example index e0bfc51..2381660 100644 --- a/.env.polygon-mumbai.example +++ b/.env.polygon-mumbai.example @@ -18,7 +18,7 @@ THESPACE_INCENTIVES_ADDRESS= THESPACE_INCENTIVES_TOKENS= THESPACE_LP_ADDRESS= THESPACE_LP_TOKENS= -BILLBOARD_ERC20_TOKEN=0x0FA8781a83E46826621b3BC094Ea2A0212e71B23 +BILLBOARD_CURRENCY_TOKEN=0x0FA8781a83E46826621b3BC094Ea2A0212e71B23 BILLBOARD_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 BILLBOARD_LEASE_TERM=900 BILLBOARD_ADMIN_ADDRESS= diff --git a/.gas-snapshot b/.gas-snapshot index a15ddcf..ae4c316 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -17,11 +17,11 @@ BillboardTest:testCannotCalculateTax() (gas: 216449) BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 253188) BillboardTest:testCannotClearAuctionIfClosed() (gas: 250314) BillboardTest:testCannotClearAuctionIfNoBid() (gas: 258354) -BillboardTest:testCannotGetBlockFromEpoch() (gas: 8666) -BillboardTest:testCannotGetEpochFromBlock() (gas: 15634) -BillboardTest:testCannotPlaceBidByAttacker() (gas: 461915) +BillboardTest:testCannotGetBlockFromEpoch() (gas: 8622) +BillboardTest:testCannotGetEpochFromBlock() (gas: 18458) BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 276192) BillboardTest:testCannotPlaceBidIfClosed() (gas: 250190) +BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 461932) BillboardTest:testCannotSafeTransferByAttacker() (gas: 219025) BillboardTest:testCannotSetBoardByAttacker() (gas: 227022) BillboardTest:testCannotSetBoardByOwner() (gas: 359240) @@ -36,9 +36,9 @@ BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 970407, ~: 9704 BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1054869, ~: 1054869) BillboardTest:testClearAuction(uint96) (runs: 256, μ: 700310, ~: 700310) BillboardTest:testClearAuctions() (gas: 1254674) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4921861, ~: 2132068) -BillboardTest:testGetBlockFromEpoch() (gas: 14900) -BillboardTest:testGetEpochFromBlock() (gas: 15987) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4891748, ~: 2132068) +BillboardTest:testGetBlockFromEpoch() (gas: 17135) +BillboardTest:testGetEpochFromBlock() (gas: 18232) BillboardTest:testGetTokenURI() (gas: 388974) BillboardTest:testMintBoard() (gas: 580732) BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 784616, ~: 786949) @@ -49,7 +49,7 @@ BillboardTest:testSafeTransferByOperator() (gas: 232823) BillboardTest:testSetBidURIs() (gas: 630718) BillboardTest:testSetBoardByCreator() (gas: 339694) BillboardTest:testSetClosed() (gas: 238386) -BillboardTest:testSetWhitelist() (gas: 242793) +BillboardTest:testSetWhitelist() (gas: 242815) BillboardTest:testUpgradeRegistry() (gas: 3494596) BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1042812, ~: 1042812) BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 667875, ~: 667875) diff --git a/Makefile b/Makefile index cb91043..0c3d0ec 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ deploy-curation: clean ## Billboard deploy-billboard: clean - @forge create Billboard --rpc-url ${ETH_RPC_URL} --private-key ${DEPLOYER_PRIVATE_KEY} --constructor-args ${BILLBOARD_ERC20_TOKEN} ${BILLBOARD_REGISTRY_ADDRESS} ${BILLBOARD_ADMIN_ADDRESS} 504 ${BILLBOARD_LEASE_TERM} "Billboard" "BLBD" --legacy --verify --etherscan-api-key ${ETHERSCAN_API_KEY} + @forge create Billboard --rpc-url ${ETH_RPC_URL} --private-key ${DEPLOYER_PRIVATE_KEY} --constructor-args ${BILLBOARD_CURRENCY_TOKEN} ${BILLBOARD_REGISTRY_ADDRESS} ${BILLBOARD_ADMIN_ADDRESS} "Billboard" "BLBD" --legacy --verify --etherscan-api-key ${ETHERSCAN_API_KEY} deploy-billboard-distribution: clean - @forge create Distribution --rpc-url ${ETH_RPC_URL} --private-key ${DEPLOYER_PRIVATE_KEY} --constructor-args ${BILLBOARD_ERC20_TOKEN} ${BILLBOARD_ADMIN_ADDRESS} --legacy --verify --etherscan-api-key ${ETHERSCAN_API_KEY} + @forge create Distribution --rpc-url ${ETH_RPC_URL} --private-key ${DEPLOYER_PRIVATE_KEY} --constructor-args ${BILLBOARD_CURRENCY_TOKEN} ${BILLBOARD_ADMIN_ADDRESS} --legacy --verify --etherscan-api-key ${ETHERSCAN_API_KEY} diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index d6c5038..a1dcf40 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -360,7 +360,7 @@ contract BillboardTest is BillboardTestBase { operator.placeBid(_tokenId, _epoch, _price); } - function testCannotPlaceBidByAttacker() public { + function testCannotPlaceBidIfNotWhitelisted() public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); uint256 _price = 1 ether; @@ -714,6 +714,8 @@ contract BillboardTest is BillboardTestBase { // epoch interval = 1 assertEq(operator.getEpochFromBlock(0, 0, 1), 0); assertEq(operator.getEpochFromBlock(0, 1, 1), 1); + assertEq(operator.getEpochFromBlock(100, 100, 1), 0); + assertEq(operator.getEpochFromBlock(100, 101, 1), 1); // epoch interval = 101 assertEq(operator.getEpochFromBlock(0, 0, 101), 0); @@ -731,23 +733,27 @@ contract BillboardTest is BillboardTestBase { function testCannotGetEpochFromBlock() public { // panic: division or modulo by zero vm.expectRevert(); - assertEq(operator.getEpochFromBlock(0, 0, 0), 0); + operator.getEpochFromBlock(0, 0, 0); vm.expectRevert(); - assertEq(operator.getEpochFromBlock(0, 1, 0), 0); + operator.getEpochFromBlock(0, 1, 0); vm.expectRevert(); - assertEq(operator.getEpochFromBlock(0, 0, type(uint256).min), 0); + operator.getEpochFromBlock(0, 0, type(uint256).min); // panic: arithmetic underflow or overflow vm.expectRevert(); - assertEq(operator.getEpochFromBlock(0, type(uint256).max + 1, type(uint256).max), 1); + operator.getEpochFromBlock(100, 99, 1); vm.expectRevert(); - assertEq(operator.getEpochFromBlock(0, type(uint256).min - 1, type(uint256).min), 1); + operator.getEpochFromBlock(0, type(uint256).max + 1, type(uint256).max); + vm.expectRevert(); + operator.getEpochFromBlock(0, type(uint256).min - 1, type(uint256).min); } function testGetBlockFromEpoch() public { // epoch interval = 1 assertEq(operator.getBlockFromEpoch(0, 0, 1), 0); assertEq(operator.getBlockFromEpoch(0, 1, 1), 1); + assertEq(operator.getBlockFromEpoch(100, 0, 1), 100); + assertEq(operator.getBlockFromEpoch(100, 1, 1), 101); // epoch interval = 101 assertEq(operator.getBlockFromEpoch(0, 0, 101), 0); @@ -766,7 +772,7 @@ contract BillboardTest is BillboardTestBase { function testCannotGetBlockFromEpoch() public { // panic: arithmetic underflow or overflow vm.expectRevert(); - assertEq(operator.getBlockFromEpoch(0, 2, type(uint256).max), 0); + operator.getBlockFromEpoch(0, 2, type(uint256).max); } ////////////////////////////// From b9dcf1ddc04d0579b2767d41aae3514e589640e2 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:25:27 +0800 Subject: [PATCH 17/24] docs: update Billboard contract addresses in OP Sepolia --- .env.local.example | 1 - .env.op-mainnet.example | 1 - .env.op-sepolia.example | 1 - .env.polygon-mainnet.example | 1 - .env.polygon-mumbai.example | 1 - .gas-snapshot | 186 +++++++++++++++---------------- README.md | 4 +- src/Billboard/Billboard.sol | 10 +- src/test/TheSpace/TheSpace.t.sol | 2 +- 9 files changed, 104 insertions(+), 103 deletions(-) diff --git a/.env.local.example b/.env.local.example index 886f467..8627d8c 100644 --- a/.env.local.example +++ b/.env.local.example @@ -19,5 +19,4 @@ THESPACE_LP_ADDRESS= THESPACE_LP_TOKENS= BILLBOARD_CURRENCY_TOKEN= BILLBOARD_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 -BILLBOARD_LEASE_TERM= BILLBOARD_ADMIN_ADDRESS= diff --git a/.env.op-mainnet.example b/.env.op-mainnet.example index b12db00..85cb5b5 100644 --- a/.env.op-mainnet.example +++ b/.env.op-mainnet.example @@ -20,5 +20,4 @@ THESPACE_LP_ADDRESS= THESPACE_LP_TOKENS= BILLBOARD_CURRENCY_TOKEN= BILLBOARD_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 -BILLBOARD_LEASE_TERM=900 BILLBOARD_ADMIN_ADDRESS= diff --git a/.env.op-sepolia.example b/.env.op-sepolia.example index e172a74..09d6453 100644 --- a/.env.op-sepolia.example +++ b/.env.op-sepolia.example @@ -20,5 +20,4 @@ THESPACE_LP_ADDRESS= THESPACE_LP_TOKENS= BILLBOARD_CURRENCY_TOKEN= BILLBOARD_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 -BILLBOARD_LEASE_TERM=900 BILLBOARD_ADMIN_ADDRESS= diff --git a/.env.polygon-mainnet.example b/.env.polygon-mainnet.example index 91af6c3..c30a796 100644 --- a/.env.polygon-mainnet.example +++ b/.env.polygon-mainnet.example @@ -20,5 +20,4 @@ THESPACE_LP_ADDRESS= THESPACE_LP_TOKENS= BILLBOARD_CURRENCY_TOKEN= BILLBOARD_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 -BILLBOARD_LEASE_TERM= BILLBOARD_ADMIN_ADDRESS= diff --git a/.env.polygon-mumbai.example b/.env.polygon-mumbai.example index 2381660..ec2d6de 100644 --- a/.env.polygon-mumbai.example +++ b/.env.polygon-mumbai.example @@ -20,5 +20,4 @@ THESPACE_LP_ADDRESS= THESPACE_LP_TOKENS= BILLBOARD_CURRENCY_TOKEN=0x0FA8781a83E46826621b3BC094Ea2A0212e71B23 BILLBOARD_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 -BILLBOARD_LEASE_TERM=900 BILLBOARD_ADMIN_ADDRESS= diff --git a/.gas-snapshot b/.gas-snapshot index ae4c316..316a069 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,51 +9,51 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testApproveAndTransfer() (gas: 253021) -BillboardTest:testCalculateTax() (gas: 537366) +BillboardTest:testApproveAndTransfer() (gas: 258021) +BillboardTest:testCalculateTax() (gas: 539866) BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20184) -BillboardTest:testCannotApproveByAttacker() (gas: 221803) -BillboardTest:testCannotCalculateTax() (gas: 216449) -BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 253188) -BillboardTest:testCannotClearAuctionIfClosed() (gas: 250314) -BillboardTest:testCannotClearAuctionIfNoBid() (gas: 258354) +BillboardTest:testCannotApproveByAttacker() (gas: 224303) +BillboardTest:testCannotCalculateTax() (gas: 218949) +BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 255688) +BillboardTest:testCannotClearAuctionIfClosed() (gas: 252814) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260854) BillboardTest:testCannotGetBlockFromEpoch() (gas: 8622) BillboardTest:testCannotGetEpochFromBlock() (gas: 18458) -BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 276192) -BillboardTest:testCannotPlaceBidIfClosed() (gas: 250190) -BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 461932) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 219025) -BillboardTest:testCannotSetBoardByAttacker() (gas: 227022) -BillboardTest:testCannotSetBoardByOwner() (gas: 359240) -BillboardTest:testCannotSetClosedByAttacker() (gas: 225976) -BillboardTest:testCannotSetWhitelistByAttacker() (gas: 226186) -BillboardTest:testCannotTransferByOperator() (gas: 224159) -BillboardTest:testCannotTransferToZeroAddress() (gas: 219669) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 278692) +BillboardTest:testCannotPlaceBidIfClosed() (gas: 252690) +BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 463932) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 221525) +BillboardTest:testCannotSetBoardByAttacker() (gas: 229522) +BillboardTest:testCannotSetBoardByOwner() (gas: 361740) +BillboardTest:testCannotSetClosedByAttacker() (gas: 228476) +BillboardTest:testCannotSetWhitelistByAttacker() (gas: 228686) +BillboardTest:testCannotTransferByOperator() (gas: 226659) +BillboardTest:testCannotTransferToZeroAddress() (gas: 222169) BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9105) -BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 603933, ~: 603933) -BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 718940, ~: 718940) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 970407, ~: 970407) -BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1054869, ~: 1054869) -BillboardTest:testClearAuction(uint96) (runs: 256, μ: 700310, ~: 700310) -BillboardTest:testClearAuctions() (gas: 1254674) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4891748, ~: 2132068) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 606433, ~: 606433) +BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 723940, ~: 723940) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 975407, ~: 975407) +BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1059869, ~: 1059869) +BillboardTest:testClearAuction(uint96) (runs: 256, μ: 705310, ~: 705310) +BillboardTest:testClearAuctions() (gas: 1262174) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 7743197, ~: 5910727) BillboardTest:testGetBlockFromEpoch() (gas: 17135) BillboardTest:testGetEpochFromBlock() (gas: 18232) -BillboardTest:testGetTokenURI() (gas: 388974) -BillboardTest:testMintBoard() (gas: 580732) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 784616, ~: 786949) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 968453, ~: 968458) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 852569, ~: 856068) -BillboardTest:testPlaceBidZeroPrice() (gas: 403633) -BillboardTest:testSafeTransferByOperator() (gas: 232823) -BillboardTest:testSetBidURIs() (gas: 630718) -BillboardTest:testSetBoardByCreator() (gas: 339694) -BillboardTest:testSetClosed() (gas: 238386) -BillboardTest:testSetWhitelist() (gas: 242815) +BillboardTest:testGetTokenURI() (gas: 391474) +BillboardTest:testMintBoard() (gas: 585732) +BillboardTest:testPlaceBid(uint96) (runs: 257, μ: 788829, ~: 789449) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 257, μ: 970451, ~: 970458) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 257, μ: 857638, ~: 858568) +BillboardTest:testPlaceBidZeroPrice() (gas: 406133) +BillboardTest:testSafeTransferByOperator() (gas: 235323) +BillboardTest:testSetBidURIs() (gas: 633218) +BillboardTest:testSetBoardByCreator() (gas: 342194) +BillboardTest:testSetClosed() (gas: 240886) +BillboardTest:testSetWhitelist() (gas: 245315) BillboardTest:testUpgradeRegistry() (gas: 3494596) -BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1042812, ~: 1042812) -BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 667875, ~: 667875) -BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 705598, ~: 705598) +BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1047812, ~: 1047812) +BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 670375, ~: 670375) +BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 710598, ~: 710598) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) @@ -71,8 +71,8 @@ DistributionTest:testCannotClaimIfAlreadyClaimed() (gas: 284835) DistributionTest:testCannotClaimIfInsufficientBalance() (gas: 394264) DistributionTest:testCannotClaimIfInvalidProof() (gas: 245236) DistributionTest:testCannotClaimIfInvalidTreeId() (gas: 243332) -DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 256, μ: 212265, ~: 212278) -DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 256, μ: 214502, ~: 214742) +DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 257, μ: 212269, ~: 212285) +DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 257, μ: 214708, ~: 214740) DistributionTest:testCannotDropIfZeroAmount() (gas: 148793) DistributionTest:testCannotDropTwiceWithSameTreeId() (gas: 307260) DistributionTest:testCannotSetAdminByAdmin() (gas: 17334) @@ -83,16 +83,16 @@ DistributionTest:testClaim() (gas: 414576) DistributionTest:testDrop() (gas: 568791) DistributionTest:testSetAdmin() (gas: 20239) DistributionTest:testSweep() (gas: 253087) -LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2019505, ~: 1310779) +LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2613180, ~: 1746428) LogbookTest:testClaim() (gas: 135608) -LogbookTest:testDonate(uint96) (runs: 256, μ: 155485, ~: 156936) -LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 256, μ: 150402, ~: 140444) -LogbookTest:testFork(uint96,string) (runs: 256, μ: 450748, ~: 453928) -LogbookTest:testForkRecursively(uint8,uint96) (runs: 256, μ: 4613856, ~: 1014389) -LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 256, μ: 469806, ~: 257636) +LogbookTest:testDonate(uint96) (runs: 257, μ: 156550, ~: 156936) +LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 257, μ: 146620, ~: 140444) +LogbookTest:testFork(uint96,string) (runs: 257, μ: 452543, ~: 453928) +LogbookTest:testForkRecursively(uint8,uint96) (runs: 257, μ: 5363774, ~: 1801537) +LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 257, μ: 342132, ~: 257636) LogbookTest:testMulticall() (gas: 284999) -LogbookTest:testPublicSale() (gas: 204837) -LogbookTest:testPublish(string) (runs: 256, μ: 264065, ~: 263590) +LogbookTest:testPublicSale() (gas: 207337) +LogbookTest:testPublish(string) (runs: 257, μ: 264063, ~: 263590) LogbookTest:testPublishEn1000() (gas: 243477) LogbookTest:testPublishEn140() (gas: 221241) LogbookTest:testPublishEn200() (gas: 222826) @@ -111,7 +111,7 @@ LogbookTest:testPublishZh5000() (gas: 607690) LogbookTest:testSetDescription() (gas: 140760) LogbookTest:testSetForkPrice() (gas: 153925) LogbookTest:testSetTitle() (gas: 168680) -LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 256, μ: 2012966, ~: 636792) +LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 257, μ: 1963734, ~: 965338) LogbookTest:testWithdraw() (gas: 7284400) SnapperTest:testCannotInitRegionByNotOwner() (gas: 11365) SnapperTest:testCannotReInitRegion() (gas: 14373) @@ -119,56 +119,56 @@ SnapperTest:testCannotTakeSnapshotBeforeInit() (gas: 15717) SnapperTest:testCannotTakeSnapshotByNotOwner() (gas: 12478) SnapperTest:testCannotTakeSnapshotWrongLastBlock() (gas: 49242) SnapperTest:testCannotTakeSnapshotWrongSnapshotBlock() (gas: 23899) -SnapperTest:testInitRegion(uint256) (runs: 256, μ: 114408, ~: 114408) +SnapperTest:testInitRegion(uint256) (runs: 257, μ: 114408, ~: 114408) SnapperTest:testTakeSnapshot() (gas: 47831) -TheSpaceTest:testBatchBid() (gas: 690308) -TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 256, μ: 368699, ~: 370338) -TheSpaceTest:testBidDefaultedToken() (gas: 409416) -TheSpaceTest:testBidExistingToken() (gas: 355023) -TheSpaceTest:testBidNewToken() (gas: 301184) -TheSpaceTest:testCanTransferFromIfSettleTax() (gas: 355069) +TheSpaceTest:testBatchBid() (gas: 695308) +TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 257, μ: 371405, ~: 372904) +TheSpaceTest:testBidDefaultedToken() (gas: 413399) +TheSpaceTest:testBidExistingToken() (gas: 360023) +TheSpaceTest:testBidNewToken() (gas: 303729) +TheSpaceTest:testCanTransferFromIfSettleTax() (gas: 357547) TheSpaceTest:testCannotBidExceedAllowance() (gas: 60910) TheSpaceTest:testCannotBidOutBoundTokens() (gas: 260482) -TheSpaceTest:testCannotBidPriceTooLow() (gas: 341674) -TheSpaceTest:testCannotGetTaxWithNonExistingToken() (gas: 16401) -TheSpaceTest:testCannotGetTokenURIInLogicContract() (gas: 298473) -TheSpaceTest:testCannotSetColorByAttacker() (gas: 302848) -TheSpaceTest:testCannotSetConfigByAttacker() (gas: 12053) -TheSpaceTest:testCannotSetPixel(uint256) (runs: 256, μ: 312357, ~: 312357) -TheSpaceTest:testCannotSetPriceByNonOwner() (gas: 302924) +TheSpaceTest:testCannotBidPriceTooLow() (gas: 344174) +TheSpaceTest:testCannotGetTaxWithNonExistingToken() (gas: 16379) +TheSpaceTest:testCannotGetTokenURIInLogicContract() (gas: 300973) +TheSpaceTest:testCannotSetColorByAttacker() (gas: 305326) +TheSpaceTest:testCannotSetConfigByAttacker() (gas: 12031) +TheSpaceTest:testCannotSetPixel(uint256) (runs: 257, μ: 314857, ~: 314857) +TheSpaceTest:testCannotSetPriceByNonOwner() (gas: 305424) TheSpaceTest:testCannotSetTokenImageURIByNonACLManager() (gas: 11862) -TheSpaceTest:testCannotSetTotalSupplyByAttacker() (gas: 11858) -TheSpaceTest:testCannotTransferFromIfDefault() (gas: 394147) -TheSpaceTest:testCannotUpgradeByAttacker() (gas: 11539) -TheSpaceTest:testCollectableTax() (gas: 333364) -TheSpaceTest:testDefault() (gas: 390575) +TheSpaceTest:testCannotSetTotalSupplyByAttacker() (gas: 11836) +TheSpaceTest:testCannotTransferFromIfDefault() (gas: 398147) +TheSpaceTest:testCannotUpgradeByAttacker() (gas: 11517) +TheSpaceTest:testCollectableTax() (gas: 335864) +TheSpaceTest:testDefault() (gas: 394548) TheSpaceTest:testGetConfig() (gas: 14302) -TheSpaceTest:testGetExistingPixel() (gas: 309428) -TheSpaceTest:testGetNonExistingPixel() (gas: 60258) +TheSpaceTest:testGetExistingPixel() (gas: 311906) +TheSpaceTest:testGetNonExistingPixel() (gas: 60303) TheSpaceTest:testGetNonExistingPrice() (gas: 19529) -TheSpaceTest:testGetOwner() (gas: 346931) +TheSpaceTest:testGetOwner() (gas: 351931) TheSpaceTest:testGetOwnerOfNonExistingToken() (gas: 13346) -TheSpaceTest:testGetPixelsByOwnerWithNoPixels() (gas: 24283) -TheSpaceTest:testGetPixelsByOwnerWithOnePixel() (gas: 319322) -TheSpaceTest:testGetPixelsPageByOwnerWithPixels() (gas: 585976) -TheSpaceTest:testGetPrice() (gas: 298001) -TheSpaceTest:testGetTax() (gas: 375416) -TheSpaceTest:testGetTokenImageURI() (gas: 14307) -TheSpaceTest:testGetTokenURI() (gas: 330962) -TheSpaceTest:testSetColor() (gas: 328848) -TheSpaceTest:testSetMintTax() (gas: 269237) -TheSpaceTest:testSetPixel(uint256) (runs: 256, μ: 398816, ~: 398816) -TheSpaceTest:testSetPrice(uint256) (runs: 256, μ: 302152, ~: 302152) -TheSpaceTest:testSetPriceByOperator(uint256) (runs: 256, μ: 352105, ~: 352105) -TheSpaceTest:testSetPriceTooHigh() (gas: 312004) -TheSpaceTest:testSetTaxRate() (gas: 345451) -TheSpaceTest:testSetTokenImageURI() (gas: 353313) -TheSpaceTest:testSetTotalSupply(uint256) (runs: 256, μ: 349701, ~: 349708) -TheSpaceTest:testSetTreasuryShare() (gas: 381788) -TheSpaceTest:testSettleTax() (gas: 336965) -TheSpaceTest:testTaxCalculation() (gas: 397405) -TheSpaceTest:testTokenShouldBeDefaulted() (gas: 323029) +TheSpaceTest:testGetPixelsByOwnerWithNoPixels() (gas: 24348) +TheSpaceTest:testGetPixelsByOwnerWithOnePixel() (gas: 321800) +TheSpaceTest:testGetPixelsPageByOwnerWithPixels() (gas: 588454) +TheSpaceTest:testGetPrice() (gas: 300501) +TheSpaceTest:testGetTax() (gas: 380480) +TheSpaceTest:testGetTokenImageURI() (gas: 14285) +TheSpaceTest:testGetTokenURI() (gas: 333462) +TheSpaceTest:testSetColor() (gas: 331348) +TheSpaceTest:testSetMintTax() (gas: 271715) +TheSpaceTest:testSetPixel(uint256) (runs: 256, μ: 403816, ~: 403816) +TheSpaceTest:testSetPrice(uint256) (runs: 257, μ: 304652, ~: 304652) +TheSpaceTest:testSetPriceByOperator(uint96) (runs: 257, μ: 354785, ~: 354785) +TheSpaceTest:testSetPriceTooHigh() (gas: 314504) +TheSpaceTest:testSetTaxRate() (gas: 347951) +TheSpaceTest:testSetTokenImageURI() (gas: 355813) +TheSpaceTest:testSetTotalSupply(uint256) (runs: 257, μ: 352202, ~: 352208) +TheSpaceTest:testSetTreasuryShare() (gas: 384288) +TheSpaceTest:testSettleTax() (gas: 339465) +TheSpaceTest:testTaxCalculation() (gas: 402405) +TheSpaceTest:testTokenShouldBeDefaulted() (gas: 325529) TheSpaceTest:testTotalSupply() (gas: 7613) TheSpaceTest:testUpgradeTo() (gas: 3215197) -TheSpaceTest:testWithdrawTreasury() (gas: 352672) -TheSpaceTest:testWithdrawUBI() (gas: 375819) \ No newline at end of file +TheSpaceTest:testWithdrawTreasury() (gas: 355172) +TheSpaceTest:testWithdrawUBI() (gas: 378319) \ No newline at end of file diff --git a/README.md b/README.md index 4b9b47c..cc08eae 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ | Billboard (Operator) | OP Mainnet | [0x88ea16c2a69f50b9bc2e8f7684d425f33f29225f](https://optimistic.etherscan.io/address/0x88ea16c2a69f50b9bc2e8f7684d425f33f29225f) | | Billboard (Registry) | OP Mainnet | [0x031Dc7C68D4A7057D28814dCc8c61e6f83c7DF25](https://optimistic.etherscan.io/address/0x031Dc7C68D4A7057D28814dCc8c61e6f83c7DF25) | | Billboard (Distribution) | OP Mainnet | [0xad5caac6910f5a737ec53847000c13122b09eada](https://optimistic.etherscan.io/address/0xad5caac6910f5a737ec53847000c13122b09eada) | -| Billboard (Operator) | OP Sepolia | [0x6a72820E1CCCba1B1FE02E37881cEa3F9Aa6375C](https://sepolia-optimism.etherscan.io/address/0x6a72820E1CCCba1B1FE02E37881cEa3F9Aa6375C) | -| Billboard (Registry) | OP Sepolia | [0x29822FDFC36247A5C3b5E92a8E26991DC4D74a2a](https://sepolia-optimism.etherscan.io/address/0x29822FDFC36247A5C3b5E92a8E26991DC4D74a2a) | +| Billboard (Operator) | OP Sepolia | [0x2412316A9fA929CA5D476b9160Fd2688C76614Fe](https://sepolia-optimism.etherscan.io/address/0x2412316A9fA929CA5D476b9160Fd2688C76614Fe) | +| Billboard (Registry) | OP Sepolia | [0xF28e390E51ef279170E2Ccbd3Ffcd9c069A9332d](https://sepolia-optimism.etherscan.io/address/0xF28e390E51ef279170E2Ccbd3Ffcd9c069A9332d) | | Billboard (Distribution) | OP Sepolia | [0x32C838d74f8b8f49a5B27c74E71797dEd3CCE8A3](https://sepolia-optimism.etherscan.io/address/0x32C838d74f8b8f49a5B27c74E71797dEd3CCE8A3) | In the "Contract" tab of Polygonscan/Etherscan, you can see the contract code and ABI. diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 7328a6d..a884c37 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -19,7 +19,13 @@ contract Billboard is IBillboard { // tokenId => closed mapping(uint256 => bool) public closed; - constructor(address token_, address payable registry_, address admin_, string memory name_, string memory symbol_) { + constructor( + address currency_, + address payable registry_, + address admin_, + string memory name_, + string memory symbol_ + ) { require(admin_ != address(0), "Zero address"); admin = admin_; @@ -29,7 +35,7 @@ contract Billboard is IBillboard { } // deploy operator and registry else { - registry = new BillboardRegistry(token_, address(this), name_, symbol_); + registry = new BillboardRegistry(currency_, address(this), name_, symbol_); } } diff --git a/src/test/TheSpace/TheSpace.t.sol b/src/test/TheSpace/TheSpace.t.sol index f62e7d4..d52af61 100644 --- a/src/test/TheSpace/TheSpace.t.sol +++ b/src/test/TheSpace/TheSpace.t.sol @@ -447,7 +447,7 @@ contract TheSpaceTest is BaseTheSpaceTest { assertEq(thespace.getPrice(PIXEL_ID), price); } - function testSetPriceByOperator(uint256 price) public { + function testSetPriceByOperator(uint96 price) public { vm.assume(price <= registry.currency().totalSupply()); vm.assume(price > 0); From 0a39cebcf3655696001131b8bdedcdc442862f2b Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:29:02 +0700 Subject: [PATCH 18/24] feat(billboard): return cleared auctions instead of empty --- .gas-snapshot | 79 +++++++++++++------------- src/Billboard/Billboard.sol | 2 +- src/test/Billboard/BillboardTest.t.sol | 24 ++++++++ 3 files changed, 65 insertions(+), 40 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 316a069..3c2652c 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,50 +9,51 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testApproveAndTransfer() (gas: 258021) -BillboardTest:testCalculateTax() (gas: 539866) +BillboardTest:testApproveAndTransfer() (gas: 258043) +BillboardTest:testCalculateTax() (gas: 539888) BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20184) BillboardTest:testCannotApproveByAttacker() (gas: 224303) BillboardTest:testCannotCalculateTax() (gas: 218949) BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 255688) -BillboardTest:testCannotClearAuctionIfClosed() (gas: 252814) -BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260854) +BillboardTest:testCannotClearAuctionIfClosed() (gas: 252836) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260876) BillboardTest:testCannotGetBlockFromEpoch() (gas: 8622) BillboardTest:testCannotGetEpochFromBlock() (gas: 18458) BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 278692) -BillboardTest:testCannotPlaceBidIfClosed() (gas: 252690) +BillboardTest:testCannotPlaceBidIfClosed() (gas: 252735) BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 463932) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 221525) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 221547) BillboardTest:testCannotSetBoardByAttacker() (gas: 229522) BillboardTest:testCannotSetBoardByOwner() (gas: 361740) -BillboardTest:testCannotSetClosedByAttacker() (gas: 228476) +BillboardTest:testCannotSetClosedByAttacker() (gas: 228498) BillboardTest:testCannotSetWhitelistByAttacker() (gas: 228686) -BillboardTest:testCannotTransferByOperator() (gas: 226659) -BillboardTest:testCannotTransferToZeroAddress() (gas: 222169) -BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9105) +BillboardTest:testCannotTransferByOperator() (gas: 226704) +BillboardTest:testCannotTransferToZeroAddress() (gas: 222191) +BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9127) BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 606433, ~: 606433) BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 723940, ~: 723940) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 975407, ~: 975407) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 975429, ~: 975429) BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1059869, ~: 1059869) BillboardTest:testClearAuction(uint96) (runs: 256, μ: 705310, ~: 705310) -BillboardTest:testClearAuctions() (gas: 1262174) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 7743197, ~: 5910727) +BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 712435) +BillboardTest:testClearAuctions() (gas: 1262152) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 7753801, ~: 5910727) BillboardTest:testGetBlockFromEpoch() (gas: 17135) -BillboardTest:testGetEpochFromBlock() (gas: 18232) -BillboardTest:testGetTokenURI() (gas: 391474) -BillboardTest:testMintBoard() (gas: 585732) -BillboardTest:testPlaceBid(uint96) (runs: 257, μ: 788829, ~: 789449) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 257, μ: 970451, ~: 970458) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 257, μ: 857638, ~: 858568) +BillboardTest:testGetEpochFromBlock() (gas: 18167) +BillboardTest:testGetTokenURI() (gas: 391429) +BillboardTest:testMintBoard() (gas: 585710) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 788827, ~: 789449) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 970451, ~: 970458) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 857635, ~: 858568) BillboardTest:testPlaceBidZeroPrice() (gas: 406133) -BillboardTest:testSafeTransferByOperator() (gas: 235323) -BillboardTest:testSetBidURIs() (gas: 633218) -BillboardTest:testSetBoardByCreator() (gas: 342194) +BillboardTest:testSafeTransferByOperator() (gas: 235236) +BillboardTest:testSetBidURIs() (gas: 633240) +BillboardTest:testSetBoardByCreator() (gas: 342216) BillboardTest:testSetClosed() (gas: 240886) BillboardTest:testSetWhitelist() (gas: 245315) -BillboardTest:testUpgradeRegistry() (gas: 3494596) +BillboardTest:testUpgradeRegistry() (gas: 3495108) BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1047812, ~: 1047812) -BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 670375, ~: 670375) +BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 670397, ~: 670397) BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 710598, ~: 710598) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) @@ -71,8 +72,8 @@ DistributionTest:testCannotClaimIfAlreadyClaimed() (gas: 284835) DistributionTest:testCannotClaimIfInsufficientBalance() (gas: 394264) DistributionTest:testCannotClaimIfInvalidProof() (gas: 245236) DistributionTest:testCannotClaimIfInvalidTreeId() (gas: 243332) -DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 257, μ: 212269, ~: 212285) -DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 257, μ: 214708, ~: 214740) +DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 256, μ: 212269, ~: 212284) +DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 256, μ: 214708, ~: 214740) DistributionTest:testCannotDropIfZeroAmount() (gas: 148793) DistributionTest:testCannotDropTwiceWithSameTreeId() (gas: 307260) DistributionTest:testCannotSetAdminByAdmin() (gas: 17334) @@ -85,14 +86,14 @@ DistributionTest:testSetAdmin() (gas: 20239) DistributionTest:testSweep() (gas: 253087) LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2613180, ~: 1746428) LogbookTest:testClaim() (gas: 135608) -LogbookTest:testDonate(uint96) (runs: 257, μ: 156550, ~: 156936) -LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 257, μ: 146620, ~: 140444) -LogbookTest:testFork(uint96,string) (runs: 257, μ: 452543, ~: 453928) -LogbookTest:testForkRecursively(uint8,uint96) (runs: 257, μ: 5363774, ~: 1801537) -LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 257, μ: 342132, ~: 257636) +LogbookTest:testDonate(uint96) (runs: 256, μ: 156549, ~: 156936) +LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 256, μ: 146644, ~: 140444) +LogbookTest:testFork(uint96,string) (runs: 256, μ: 452537, ~: 453928) +LogbookTest:testForkRecursively(uint8,uint96) (runs: 256, μ: 5351224, ~: 1801537) +LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 256, μ: 342465, ~: 257636) LogbookTest:testMulticall() (gas: 284999) LogbookTest:testPublicSale() (gas: 207337) -LogbookTest:testPublish(string) (runs: 257, μ: 264063, ~: 263590) +LogbookTest:testPublish(string) (runs: 256, μ: 264065, ~: 263590) LogbookTest:testPublishEn1000() (gas: 243477) LogbookTest:testPublishEn140() (gas: 221241) LogbookTest:testPublishEn200() (gas: 222826) @@ -111,7 +112,7 @@ LogbookTest:testPublishZh5000() (gas: 607690) LogbookTest:testSetDescription() (gas: 140760) LogbookTest:testSetForkPrice() (gas: 153925) LogbookTest:testSetTitle() (gas: 168680) -LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 257, μ: 1963734, ~: 965338) +LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 256, μ: 1959072, ~: 965338) LogbookTest:testWithdraw() (gas: 7284400) SnapperTest:testCannotInitRegionByNotOwner() (gas: 11365) SnapperTest:testCannotReInitRegion() (gas: 14373) @@ -119,10 +120,10 @@ SnapperTest:testCannotTakeSnapshotBeforeInit() (gas: 15717) SnapperTest:testCannotTakeSnapshotByNotOwner() (gas: 12478) SnapperTest:testCannotTakeSnapshotWrongLastBlock() (gas: 49242) SnapperTest:testCannotTakeSnapshotWrongSnapshotBlock() (gas: 23899) -SnapperTest:testInitRegion(uint256) (runs: 257, μ: 114408, ~: 114408) +SnapperTest:testInitRegion(uint256) (runs: 256, μ: 114408, ~: 114408) SnapperTest:testTakeSnapshot() (gas: 47831) TheSpaceTest:testBatchBid() (gas: 695308) -TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 257, μ: 371405, ~: 372904) +TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 256, μ: 371399, ~: 372904) TheSpaceTest:testBidDefaultedToken() (gas: 413399) TheSpaceTest:testBidExistingToken() (gas: 360023) TheSpaceTest:testBidNewToken() (gas: 303729) @@ -134,7 +135,7 @@ TheSpaceTest:testCannotGetTaxWithNonExistingToken() (gas: 16379) TheSpaceTest:testCannotGetTokenURIInLogicContract() (gas: 300973) TheSpaceTest:testCannotSetColorByAttacker() (gas: 305326) TheSpaceTest:testCannotSetConfigByAttacker() (gas: 12031) -TheSpaceTest:testCannotSetPixel(uint256) (runs: 257, μ: 314857, ~: 314857) +TheSpaceTest:testCannotSetPixel(uint256) (runs: 256, μ: 314857, ~: 314857) TheSpaceTest:testCannotSetPriceByNonOwner() (gas: 305424) TheSpaceTest:testCannotSetTokenImageURIByNonACLManager() (gas: 11862) TheSpaceTest:testCannotSetTotalSupplyByAttacker() (gas: 11836) @@ -158,12 +159,12 @@ TheSpaceTest:testGetTokenURI() (gas: 333462) TheSpaceTest:testSetColor() (gas: 331348) TheSpaceTest:testSetMintTax() (gas: 271715) TheSpaceTest:testSetPixel(uint256) (runs: 256, μ: 403816, ~: 403816) -TheSpaceTest:testSetPrice(uint256) (runs: 257, μ: 304652, ~: 304652) -TheSpaceTest:testSetPriceByOperator(uint96) (runs: 257, μ: 354785, ~: 354785) +TheSpaceTest:testSetPrice(uint256) (runs: 256, μ: 304652, ~: 304652) +TheSpaceTest:testSetPriceByOperator(uint96) (runs: 256, μ: 354785, ~: 354785) TheSpaceTest:testSetPriceTooHigh() (gas: 314504) TheSpaceTest:testSetTaxRate() (gas: 347951) TheSpaceTest:testSetTokenImageURI() (gas: 355813) -TheSpaceTest:testSetTotalSupply(uint256) (runs: 257, μ: 352202, ~: 352208) +TheSpaceTest:testSetTotalSupply(uint256) (runs: 256, μ: 352202, ~: 352208) TheSpaceTest:testSetTreasuryShare() (gas: 384288) TheSpaceTest:testSettleTax() (gas: 339465) TheSpaceTest:testTaxCalculation() (gas: 402405) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index a884c37..d784302 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -228,7 +228,7 @@ contract Billboard is IBillboard { // skip if auction is already cleared if (_highestBid.isWon) { - return (address(0), 0, 0); + return (_highestBidder, _highestBid.price, _highestBid.tax); } address _prevOwner = registry.ownerOf(tokenId_); diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index a1dcf40..00742b6 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -435,6 +435,30 @@ contract BillboardTest is BillboardTestBase { assertEq(usdt.balanceOf(USER_A), 0); } + function testClearAuctionIfAlreadyCleared() public { + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + uint256 _clearedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); + uint256 _price = 1 ether; + uint256 _tax = operator.calculateTax(_tokenId, _price); + + // place bid + _placeBid(_tokenId, _epoch, USER_A, 1 ether); + + // clear auction + vm.roll(_clearedAt); + (address _highestBidder1, uint256 _price1, uint256 _tax1) = operator.clearAuction(_tokenId, _epoch); + assertEq(_highestBidder1, USER_A); + assertEq(_price1, _price); + assertEq(_tax1, _tax); + + // clear auction again + (address _highestBidder2, uint256 _price2, uint256 _tax2) = operator.clearAuction(_tokenId, _epoch); + assertEq(_highestBidder2, USER_A); + assertEq(_price2, _price); + assertEq(_tax2, _tax); + } + function testClearAuctions() public { (uint256 _tokenId1, IBillboardRegistry.Board memory _board1) = _mintBoard(); (uint256 _tokenId2, IBillboardRegistry.Board memory _board2) = _mintBoard(); From d83e7dfa9a384860a1910d219fc90da65474420b Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:36:30 +0700 Subject: [PATCH 19/24] feat(billboard): add `getBidderBids` --- .gas-snapshot | 119 +++++++++++++------------ src/Billboard/Billboard.sol | 34 +++++-- src/Billboard/BillboardRegistry.sol | 11 +++ src/Billboard/IBillboard.sol | 20 +++++ src/Billboard/IBillboardRegistry.sol | 10 +++ src/test/Billboard/BillboardTest.t.sol | 44 +++++++++ 6 files changed, 173 insertions(+), 65 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 3c2652c..b3c2aec 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,52 +9,53 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testApproveAndTransfer() (gas: 258043) -BillboardTest:testCalculateTax() (gas: 539888) -BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20184) -BillboardTest:testCannotApproveByAttacker() (gas: 224303) -BillboardTest:testCannotCalculateTax() (gas: 218949) -BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 255688) -BillboardTest:testCannotClearAuctionIfClosed() (gas: 252836) -BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260876) -BillboardTest:testCannotGetBlockFromEpoch() (gas: 8622) -BillboardTest:testCannotGetEpochFromBlock() (gas: 18458) -BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 278692) -BillboardTest:testCannotPlaceBidIfClosed() (gas: 252735) -BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 463932) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 221547) -BillboardTest:testCannotSetBoardByAttacker() (gas: 229522) -BillboardTest:testCannotSetBoardByOwner() (gas: 361740) -BillboardTest:testCannotSetClosedByAttacker() (gas: 228498) -BillboardTest:testCannotSetWhitelistByAttacker() (gas: 228686) -BillboardTest:testCannotTransferByOperator() (gas: 226704) -BillboardTest:testCannotTransferToZeroAddress() (gas: 222191) -BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9127) -BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 606433, ~: 606433) -BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 723940, ~: 723940) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 975429, ~: 975429) -BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1059869, ~: 1059869) -BillboardTest:testClearAuction(uint96) (runs: 256, μ: 705310, ~: 705310) -BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 712435) -BillboardTest:testClearAuctions() (gas: 1262152) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 7753801, ~: 5910727) -BillboardTest:testGetBlockFromEpoch() (gas: 17135) -BillboardTest:testGetEpochFromBlock() (gas: 18167) -BillboardTest:testGetTokenURI() (gas: 391429) -BillboardTest:testMintBoard() (gas: 585710) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 788827, ~: 789449) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 970451, ~: 970458) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 857635, ~: 858568) -BillboardTest:testPlaceBidZeroPrice() (gas: 406133) -BillboardTest:testSafeTransferByOperator() (gas: 235236) -BillboardTest:testSetBidURIs() (gas: 633240) -BillboardTest:testSetBoardByCreator() (gas: 342216) -BillboardTest:testSetClosed() (gas: 240886) -BillboardTest:testSetWhitelist() (gas: 245315) -BillboardTest:testUpgradeRegistry() (gas: 3495108) -BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1047812, ~: 1047812) -BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 670397, ~: 670397) -BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 710598, ~: 710598) +BillboardTest:testApproveAndTransfer() (gas: 258088) +BillboardTest:testCalculateTax() (gas: 539668) +BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20140) +BillboardTest:testCannotApproveByAttacker() (gas: 224347) +BillboardTest:testCannotCalculateTax() (gas: 218883) +BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 255578) +BillboardTest:testCannotClearAuctionIfClosed() (gas: 252770) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260744) +BillboardTest:testCannotGetBlockFromEpoch() (gas: 8600) +BillboardTest:testCannotGetEpochFromBlock() (gas: 18393) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 278628) +BillboardTest:testCannotPlaceBidIfClosed() (gas: 252692) +BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 463880) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 221503) +BillboardTest:testCannotSetBoardByAttacker() (gas: 229566) +BillboardTest:testCannotSetBoardByOwner() (gas: 362072) +BillboardTest:testCannotSetClosedByAttacker() (gas: 228454) +BillboardTest:testCannotSetWhitelistByAttacker() (gas: 228664) +BillboardTest:testCannotTransferByOperator() (gas: 226749) +BillboardTest:testCannotTransferToZeroAddress() (gas: 222236) +BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9105) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 630648, ~: 630648) +BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 748156, ~: 748156) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 1024014, ~: 1024014) +BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1108455, ~: 1108455) +BillboardTest:testClearAuction(uint96) (runs: 256, μ: 729526, ~: 729526) +BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 736674) +BillboardTest:testClearAuctions() (gas: 1310672) +BillboardTest:testGetBidderBids(uint8,uint8,uint8) (runs: 256, μ: 1503234, ~: 1134296) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 257, μ: 8185787, ~: 6273841) +BillboardTest:testGetBlockFromEpoch() (gas: 16849) +BillboardTest:testGetEpochFromBlock() (gas: 17968) +BillboardTest:testGetTokenURI() (gas: 391497) +BillboardTest:testMintBoard() (gas: 585644) +BillboardTest:testPlaceBid(uint96) (runs: 257, μ: 837566, ~: 838186) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 257, μ: 1009249, ~: 1009253) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 257, μ: 906266, ~: 907196) +BillboardTest:testPlaceBidZeroPrice() (gas: 430502) +BillboardTest:testSafeTransferByOperator() (gas: 235301) +BillboardTest:testSetBidURIs() (gas: 657544) +BillboardTest:testSetBoardByCreator() (gas: 342349) +BillboardTest:testSetClosed() (gas: 240866) +BillboardTest:testSetWhitelist() (gas: 245316) +BillboardTest:testUpgradeRegistry() (gas: 3655978) +BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1096420, ~: 1096420) +BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 694679, ~: 694679) +BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 734859, ~: 734859) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) @@ -72,8 +73,8 @@ DistributionTest:testCannotClaimIfAlreadyClaimed() (gas: 284835) DistributionTest:testCannotClaimIfInsufficientBalance() (gas: 394264) DistributionTest:testCannotClaimIfInvalidProof() (gas: 245236) DistributionTest:testCannotClaimIfInvalidTreeId() (gas: 243332) -DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 256, μ: 212269, ~: 212284) -DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 256, μ: 214708, ~: 214740) +DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 257, μ: 212269, ~: 212283) +DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 257, μ: 214708, ~: 214740) DistributionTest:testCannotDropIfZeroAmount() (gas: 148793) DistributionTest:testCannotDropTwiceWithSameTreeId() (gas: 307260) DistributionTest:testCannotSetAdminByAdmin() (gas: 17334) @@ -86,14 +87,14 @@ DistributionTest:testSetAdmin() (gas: 20239) DistributionTest:testSweep() (gas: 253087) LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2613180, ~: 1746428) LogbookTest:testClaim() (gas: 135608) -LogbookTest:testDonate(uint96) (runs: 256, μ: 156549, ~: 156936) -LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 256, μ: 146644, ~: 140444) -LogbookTest:testFork(uint96,string) (runs: 256, μ: 452537, ~: 453928) -LogbookTest:testForkRecursively(uint8,uint96) (runs: 256, μ: 5351224, ~: 1801537) -LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 256, μ: 342465, ~: 257636) +LogbookTest:testDonate(uint96) (runs: 257, μ: 156550, ~: 156936) +LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 257, μ: 146620, ~: 140444) +LogbookTest:testFork(uint96,string) (runs: 257, μ: 452543, ~: 453928) +LogbookTest:testForkRecursively(uint8,uint96) (runs: 257, μ: 5348891, ~: 1801537) +LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 257, μ: 342132, ~: 257636) LogbookTest:testMulticall() (gas: 284999) LogbookTest:testPublicSale() (gas: 207337) -LogbookTest:testPublish(string) (runs: 256, μ: 264065, ~: 263590) +LogbookTest:testPublish(string) (runs: 257, μ: 264063, ~: 263590) LogbookTest:testPublishEn1000() (gas: 243477) LogbookTest:testPublishEn140() (gas: 221241) LogbookTest:testPublishEn200() (gas: 222826) @@ -112,7 +113,7 @@ LogbookTest:testPublishZh5000() (gas: 607690) LogbookTest:testSetDescription() (gas: 140760) LogbookTest:testSetForkPrice() (gas: 153925) LogbookTest:testSetTitle() (gas: 168680) -LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 256, μ: 1959072, ~: 965338) +LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 257, μ: 1963054, ~: 965338) LogbookTest:testWithdraw() (gas: 7284400) SnapperTest:testCannotInitRegionByNotOwner() (gas: 11365) SnapperTest:testCannotReInitRegion() (gas: 14373) @@ -120,10 +121,10 @@ SnapperTest:testCannotTakeSnapshotBeforeInit() (gas: 15717) SnapperTest:testCannotTakeSnapshotByNotOwner() (gas: 12478) SnapperTest:testCannotTakeSnapshotWrongLastBlock() (gas: 49242) SnapperTest:testCannotTakeSnapshotWrongSnapshotBlock() (gas: 23899) -SnapperTest:testInitRegion(uint256) (runs: 256, μ: 114408, ~: 114408) +SnapperTest:testInitRegion(uint256) (runs: 257, μ: 114408, ~: 114408) SnapperTest:testTakeSnapshot() (gas: 47831) TheSpaceTest:testBatchBid() (gas: 695308) -TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 256, μ: 371399, ~: 372904) +TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 257, μ: 371405, ~: 372904) TheSpaceTest:testBidDefaultedToken() (gas: 413399) TheSpaceTest:testBidExistingToken() (gas: 360023) TheSpaceTest:testBidNewToken() (gas: 303729) @@ -160,11 +161,11 @@ TheSpaceTest:testSetColor() (gas: 331348) TheSpaceTest:testSetMintTax() (gas: 271715) TheSpaceTest:testSetPixel(uint256) (runs: 256, μ: 403816, ~: 403816) TheSpaceTest:testSetPrice(uint256) (runs: 256, μ: 304652, ~: 304652) -TheSpaceTest:testSetPriceByOperator(uint96) (runs: 256, μ: 354785, ~: 354785) +TheSpaceTest:testSetPriceByOperator(uint96) (runs: 257, μ: 354785, ~: 354785) TheSpaceTest:testSetPriceTooHigh() (gas: 314504) TheSpaceTest:testSetTaxRate() (gas: 347951) TheSpaceTest:testSetTokenImageURI() (gas: 355813) -TheSpaceTest:testSetTotalSupply(uint256) (runs: 256, μ: 352202, ~: 352208) +TheSpaceTest:testSetTotalSupply(uint256) (runs: 257, μ: 352202, ~: 352208) TheSpaceTest:testSetTreasuryShare() (gas: 384288) TheSpaceTest:testSettleTax() (gas: 339465) TheSpaceTest:testTaxCalculation() (gas: 402405) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index d784302..6a719fb 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -289,25 +289,47 @@ contract Billboard is IBillboard { ) external view returns (uint256 total, uint256 limit, uint256 offset, IBillboardRegistry.Bid[] memory bids) { uint256 _total = registry.getBidCount(tokenId_, epoch_); - if (limit_ == 0) { + if (limit_ == 0 || offset_ >= _total) { return (_total, limit_, offset_, new IBillboardRegistry.Bid[](0)); } - if (offset_ >= _total) { + uint256 _left = _total - offset_; + uint256 _size = _left > limit_ ? limit_ : _left; + + bids = new IBillboardRegistry.Bid[](_size); + + for (uint256 i = 0; i < _size; i++) { + address _bidder = registry.bidders(tokenId_, epoch_, offset_ + i); + bids[i] = registry.getBid(tokenId_, epoch_, _bidder); + } + + return (_total, limit_, offset_, bids); + } + + /// @inheritdoc IBillboard + function getBidderBids( + uint256 tokenId_, + address bidder_, + uint256 limit_, + uint256 offset_ + ) external view returns (uint256 total, uint256 limit, uint256 offset, IBillboardRegistry.Bid[] memory bids) { + uint256 _total = registry.getBidderBidCount(tokenId_, bidder_); + + if (limit_ == 0 || offset_ >= _total) { return (_total, limit_, offset_, new IBillboardRegistry.Bid[](0)); } uint256 _left = _total - offset_; uint256 _size = _left > limit_ ? limit_ : _left; - IBillboardRegistry.Bid[] memory _bids = new IBillboardRegistry.Bid[](_size); + bids = new IBillboardRegistry.Bid[](_size); for (uint256 i = 0; i < _size; i++) { - address _bidder = registry.bidders(tokenId_, epoch_, offset_ + i); - _bids[i] = registry.getBid(tokenId_, epoch_, _bidder); + uint256 _epoch = registry.bidderBids(tokenId_, bidder_, offset_ + i); + bids[i] = registry.getBid(tokenId_, _epoch, bidder_); } - return (_total, limit_, offset_, _bids); + return (_total, limit_, offset_, bids); } /// @inheritdoc IBillboard diff --git a/src/Billboard/BillboardRegistry.sol b/src/Billboard/BillboardRegistry.sol index fd22883..5b3c6d2 100644 --- a/src/Billboard/BillboardRegistry.sol +++ b/src/Billboard/BillboardRegistry.sol @@ -29,6 +29,9 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { // tokenId => epoch => bidder => Bid mapping(uint256 => mapping(uint256 => mapping(address => Bid))) public bids; + // tokenId => address => epoches + mapping(uint256 => mapping(address => uint256[])) public bidderBids; + // board creator => TaxTreasury mapping(address => TaxTreasury) public taxTreasury; @@ -131,6 +134,11 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { count = bidders[tokenId_][epoch_].length; } + /// @inheritdoc IBillboardRegistry + function getBidderBidCount(uint256 tokenId_, address bidder_) external view returns (uint256 count) { + count = bidderBids[tokenId_][bidder_].length; + } + /// @inheritdoc IBillboardRegistry function newBid( uint256 tokenId_, @@ -155,6 +163,9 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 { // add to auction bids bids[tokenId_][epoch_][bidder_] = _bid; + // add to bidder's bids + bidderBids[tokenId_][bidder_].push(epoch_); + // add to auction bidders if new bid bidders[tokenId_][epoch_].push(bidder_); diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index 44f0ed8..ef00f6d 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -220,6 +220,26 @@ interface IBillboard { uint256 offset_ ) external view returns (uint256 total, uint256 limit, uint256 offset, IBillboardRegistry.Bid[] memory bids); + /** + * @notice Get all bids of bidder by token ID. + * + * @param tokenId_ Token ID. + * @param bidder_ Address of bidder. + * @param limit_ Limit of returned bids. + * @param offset_ Offset of returned bids. + * + * @return total Total number of bids. + * @return limit Limit of returned bids. + * @return offset Offset of returned bids. + * @return bids Bids. + */ + function getBidderBids( + uint256 tokenId_, + address bidder_, + uint256 limit_, + uint256 offset_ + ) external view returns (uint256 total, uint256 limit, uint256 offset, IBillboardRegistry.Bid[] memory bids); + /** * @notice Withdraw bid that were not won by auction id; * diff --git a/src/Billboard/IBillboardRegistry.sol b/src/Billboard/IBillboardRegistry.sol index c9eae4e..5a270bc 100644 --- a/src/Billboard/IBillboardRegistry.sol +++ b/src/Billboard/IBillboardRegistry.sol @@ -203,6 +203,16 @@ interface IBillboardRegistry is IERC721 { */ function getBidCount(uint256 tokenId_, uint256 epoch_) external view returns (uint256 count); + /** + * @notice Get the count of bidder bids + * + * @param tokenId_ Token ID of a board. + * @param bidder_ Bidder of an auction. + * + * @return count Count of bids. + */ + function getBidderBidCount(uint256 tokenId_, address bidder_) external view returns (uint256 count); + /** * @notice Create a bid * diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 00742b6..f569e0a 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -574,6 +574,50 @@ contract BillboardTest is BillboardTestBase { } } + function testGetBidderBids(uint8 _bidCount, uint8 _limit, uint8 _offset) public { + vm.assume(_bidCount > 0); + vm.assume(_bidCount <= 10); + vm.assume(_limit <= _bidCount); + vm.assume(_offset <= _limit); + + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + + for (uint8 i = 0; i < _bidCount; i++) { + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval) + i; + + vm.prank(ADMIN); + operator.setWhitelist(_tokenId, USER_A, true); + + uint256 _price = 1 ether + i; + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _totalAmount = _price + _tax; + + deal(address(usdt), USER_A, _totalAmount); + vm.startPrank(USER_A); + usdt.approve(address(operator), _totalAmount); + operator.placeBid(_tokenId, _epoch, _price); + vm.stopPrank(); + } + + // get bidder bids + (uint256 _t, uint256 _l, uint256 _o, IBillboardRegistry.Bid[] memory _bids) = operator.getBidderBids( + _tokenId, + USER_A, + _limit, + _offset + ); + uint256 _left = _t - _offset; + uint256 _size = _left > _limit ? _limit : _left; + assertEq(_t, _bidCount); + assertEq(_l, _limit); + assertEq(_bids.length, _size); + assertEq(_o, _offset); + for (uint256 i = 0; i < _size; i++) { + uint256 _price = 1 ether + _offset + i; + assertEq(_bids[i].price, _price); + } + } + function testWithdrawBid(uint96 _price) public { vm.assume(_price > 0.001 ether); From 115eab6ffa1d9881f66594f7278e36ffa26ff1f1 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Tue, 13 Aug 2024 21:45:23 +0800 Subject: [PATCH 20/24] feat(billboard): make `getBiddersBids` returns `epochs` --- .gas-snapshot | 38 ++++++++-------- src/Billboard/Billboard.sol | 33 +++++++++++--- src/Billboard/IBillboard.sol | 12 ++++- src/test/Billboard/BillboardTest.t.sol | 62 +++++++++++++++++++------- 4 files changed, 103 insertions(+), 42 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index b3c2aec..8f28159 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -37,22 +37,22 @@ BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1108455, ~: 110 BillboardTest:testClearAuction(uint96) (runs: 256, μ: 729526, ~: 729526) BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 736674) BillboardTest:testClearAuctions() (gas: 1310672) -BillboardTest:testGetBidderBids(uint8,uint8,uint8) (runs: 256, μ: 1503234, ~: 1134296) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 257, μ: 8185787, ~: 6273841) +BillboardTest:testGetBidderBids(uint8,uint8,uint8) (runs: 256, μ: 1505979, ~: 1138329) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 8242518, ~: 6452578) BillboardTest:testGetBlockFromEpoch() (gas: 16849) BillboardTest:testGetEpochFromBlock() (gas: 17968) BillboardTest:testGetTokenURI() (gas: 391497) BillboardTest:testMintBoard() (gas: 585644) -BillboardTest:testPlaceBid(uint96) (runs: 257, μ: 837566, ~: 838186) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 257, μ: 1009249, ~: 1009253) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 257, μ: 906266, ~: 907196) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 837564, ~: 838186) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 1009249, ~: 1009253) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 906263, ~: 907196) BillboardTest:testPlaceBidZeroPrice() (gas: 430502) BillboardTest:testSafeTransferByOperator() (gas: 235301) BillboardTest:testSetBidURIs() (gas: 657544) BillboardTest:testSetBoardByCreator() (gas: 342349) BillboardTest:testSetClosed() (gas: 240866) BillboardTest:testSetWhitelist() (gas: 245316) -BillboardTest:testUpgradeRegistry() (gas: 3655978) +BillboardTest:testUpgradeRegistry() (gas: 3708725) BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1096420, ~: 1096420) BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 694679, ~: 694679) BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 734859, ~: 734859) @@ -73,8 +73,8 @@ DistributionTest:testCannotClaimIfAlreadyClaimed() (gas: 284835) DistributionTest:testCannotClaimIfInsufficientBalance() (gas: 394264) DistributionTest:testCannotClaimIfInvalidProof() (gas: 245236) DistributionTest:testCannotClaimIfInvalidTreeId() (gas: 243332) -DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 257, μ: 212269, ~: 212283) -DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 257, μ: 214708, ~: 214740) +DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 256, μ: 212269, ~: 212284) +DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 256, μ: 214708, ~: 214740) DistributionTest:testCannotDropIfZeroAmount() (gas: 148793) DistributionTest:testCannotDropTwiceWithSameTreeId() (gas: 307260) DistributionTest:testCannotSetAdminByAdmin() (gas: 17334) @@ -87,14 +87,14 @@ DistributionTest:testSetAdmin() (gas: 20239) DistributionTest:testSweep() (gas: 253087) LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2613180, ~: 1746428) LogbookTest:testClaim() (gas: 135608) -LogbookTest:testDonate(uint96) (runs: 257, μ: 156550, ~: 156936) -LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 257, μ: 146620, ~: 140444) -LogbookTest:testFork(uint96,string) (runs: 257, μ: 452543, ~: 453928) -LogbookTest:testForkRecursively(uint8,uint96) (runs: 257, μ: 5348891, ~: 1801537) -LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 257, μ: 342132, ~: 257636) +LogbookTest:testDonate(uint96) (runs: 256, μ: 156549, ~: 156936) +LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 256, μ: 146644, ~: 140444) +LogbookTest:testFork(uint96,string) (runs: 256, μ: 452537, ~: 453928) +LogbookTest:testForkRecursively(uint8,uint96) (runs: 256, μ: 5351224, ~: 1801537) +LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 256, μ: 342465, ~: 257636) LogbookTest:testMulticall() (gas: 284999) LogbookTest:testPublicSale() (gas: 207337) -LogbookTest:testPublish(string) (runs: 257, μ: 264063, ~: 263590) +LogbookTest:testPublish(string) (runs: 256, μ: 264065, ~: 263590) LogbookTest:testPublishEn1000() (gas: 243477) LogbookTest:testPublishEn140() (gas: 221241) LogbookTest:testPublishEn200() (gas: 222826) @@ -113,7 +113,7 @@ LogbookTest:testPublishZh5000() (gas: 607690) LogbookTest:testSetDescription() (gas: 140760) LogbookTest:testSetForkPrice() (gas: 153925) LogbookTest:testSetTitle() (gas: 168680) -LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 257, μ: 1963054, ~: 965338) +LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 256, μ: 1959072, ~: 965338) LogbookTest:testWithdraw() (gas: 7284400) SnapperTest:testCannotInitRegionByNotOwner() (gas: 11365) SnapperTest:testCannotReInitRegion() (gas: 14373) @@ -121,10 +121,10 @@ SnapperTest:testCannotTakeSnapshotBeforeInit() (gas: 15717) SnapperTest:testCannotTakeSnapshotByNotOwner() (gas: 12478) SnapperTest:testCannotTakeSnapshotWrongLastBlock() (gas: 49242) SnapperTest:testCannotTakeSnapshotWrongSnapshotBlock() (gas: 23899) -SnapperTest:testInitRegion(uint256) (runs: 257, μ: 114408, ~: 114408) +SnapperTest:testInitRegion(uint256) (runs: 256, μ: 114408, ~: 114408) SnapperTest:testTakeSnapshot() (gas: 47831) TheSpaceTest:testBatchBid() (gas: 695308) -TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 257, μ: 371405, ~: 372904) +TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 256, μ: 371399, ~: 372904) TheSpaceTest:testBidDefaultedToken() (gas: 413399) TheSpaceTest:testBidExistingToken() (gas: 360023) TheSpaceTest:testBidNewToken() (gas: 303729) @@ -161,11 +161,11 @@ TheSpaceTest:testSetColor() (gas: 331348) TheSpaceTest:testSetMintTax() (gas: 271715) TheSpaceTest:testSetPixel(uint256) (runs: 256, μ: 403816, ~: 403816) TheSpaceTest:testSetPrice(uint256) (runs: 256, μ: 304652, ~: 304652) -TheSpaceTest:testSetPriceByOperator(uint96) (runs: 257, μ: 354785, ~: 354785) +TheSpaceTest:testSetPriceByOperator(uint96) (runs: 256, μ: 354785, ~: 354785) TheSpaceTest:testSetPriceTooHigh() (gas: 314504) TheSpaceTest:testSetTaxRate() (gas: 347951) TheSpaceTest:testSetTokenImageURI() (gas: 355813) -TheSpaceTest:testSetTotalSupply(uint256) (runs: 257, μ: 352202, ~: 352208) +TheSpaceTest:testSetTotalSupply(uint256) (runs: 256, μ: 352202, ~: 352208) TheSpaceTest:testSetTreasuryShare() (gas: 384288) TheSpaceTest:testSettleTax() (gas: 339465) TheSpaceTest:testTaxCalculation() (gas: 402405) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 6a719fb..0102c93 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -312,24 +312,45 @@ contract Billboard is IBillboard { address bidder_, uint256 limit_, uint256 offset_ - ) external view returns (uint256 total, uint256 limit, uint256 offset, IBillboardRegistry.Bid[] memory bids) { + ) + external + view + returns ( + uint256 total, + uint256 limit, + uint256 offset, + IBillboardRegistry.Bid[] memory bids, + uint256[] memory epochs + ) + { uint256 _total = registry.getBidderBidCount(tokenId_, bidder_); if (limit_ == 0 || offset_ >= _total) { - return (_total, limit_, offset_, new IBillboardRegistry.Bid[](0)); + return (_total, limit_, offset_, new IBillboardRegistry.Bid[](0), new uint256[](0)); } uint256 _left = _total - offset_; uint256 _size = _left > limit_ ? limit_ : _left; - bids = new IBillboardRegistry.Bid[](_size); + (bids, epochs) = _getBidsAndEpochs(tokenId_, bidder_, offset_, _size); - for (uint256 i = 0; i < _size; i++) { + return (_total, limit_, offset_, bids, epochs); + } + + function _getBidsAndEpochs( + uint256 tokenId_, + address bidder_, + uint256 offset_, + uint256 size_ + ) internal view returns (IBillboardRegistry.Bid[] memory bids, uint256[] memory epochs) { + bids = new IBillboardRegistry.Bid[](size_); + epochs = new uint256[](size_); + + for (uint256 i = 0; i < size_; i++) { uint256 _epoch = registry.bidderBids(tokenId_, bidder_, offset_ + i); bids[i] = registry.getBid(tokenId_, _epoch, bidder_); + epochs[i] = _epoch; } - - return (_total, limit_, offset_, bids); } /// @inheritdoc IBillboard diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index ef00f6d..09cf895 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -232,13 +232,23 @@ interface IBillboard { * @return limit Limit of returned bids. * @return offset Offset of returned bids. * @return bids Bids. + * @return epoches Epoches of bids. */ function getBidderBids( uint256 tokenId_, address bidder_, uint256 limit_, uint256 offset_ - ) external view returns (uint256 total, uint256 limit, uint256 offset, IBillboardRegistry.Bid[] memory bids); + ) + external + view + returns ( + uint256 total, + uint256 limit, + uint256 offset, + IBillboardRegistry.Bid[] memory bids, + uint256[] memory epoches + ); /** * @notice Withdraw bid that were not won by auction id; diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index f569e0a..ae95514 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -574,6 +574,7 @@ contract BillboardTest is BillboardTestBase { } } + // Main function to test getting bidder bids function testGetBidderBids(uint8 _bidCount, uint8 _limit, uint8 _offset) public { vm.assume(_bidCount > 0); vm.assume(_bidCount <= 10); @@ -582,6 +583,22 @@ contract BillboardTest is BillboardTestBase { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + _placeBids(_bidCount, _tokenId, _board); + + // Get bidder bids + ( + uint256 totalBids, + uint256 limit, + uint256 offset, + IBillboardRegistry.Bid[] memory bids, + uint256[] memory epoches + ) = operator.getBidderBids(_tokenId, USER_A, _limit, _offset); + + _assertBidderBids(_bidCount, _limit, _offset, totalBids, limit, offset, bids, epoches, _board); + } + + // Helper function to place bids + function _placeBids(uint8 _bidCount, uint256 _tokenId, IBillboardRegistry.Board memory _board) internal { for (uint8 i = 0; i < _bidCount; i++) { uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval) + i; @@ -598,23 +615,36 @@ contract BillboardTest is BillboardTestBase { operator.placeBid(_tokenId, _epoch, _price); vm.stopPrank(); } + } - // get bidder bids - (uint256 _t, uint256 _l, uint256 _o, IBillboardRegistry.Bid[] memory _bids) = operator.getBidderBids( - _tokenId, - USER_A, - _limit, - _offset - ); - uint256 _left = _t - _offset; - uint256 _size = _left > _limit ? _limit : _left; - assertEq(_t, _bidCount); - assertEq(_l, _limit); - assertEq(_bids.length, _size); - assertEq(_o, _offset); - for (uint256 i = 0; i < _size; i++) { - uint256 _price = 1 ether + _offset + i; - assertEq(_bids[i].price, _price); + // Helper function to assert bidder bids + function _assertBidderBids( + uint8 _bidCount, + uint8 _limit, + uint8 _offset, + uint256 totalBids, + uint256 limit, + uint256 offset, + IBillboardRegistry.Bid[] memory bids, + uint256[] memory epoches, + IBillboardRegistry.Board memory _board + ) internal { + uint256 remainingBids = totalBids - offset; + uint256 size = remainingBids > limit ? limit : remainingBids; + + assertEq(totalBids, _bidCount); + assertEq(limit, _limit); + assertEq(bids.length, size); + assertEq(offset, _offset); + + for (uint256 i = 0; i < size; i++) { + uint256 expectedPrice = 1 ether + _offset + i; + uint256 expectedEpoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval) + + _offset + + i; + + assertEq(bids[i].price, expectedPrice); + assertEq(epoches[i], expectedEpoch); } } From 61774f06866888c5aac9944898e437527ee904a3 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:14:01 +0800 Subject: [PATCH 21/24] feat(billboard): allow to enable/disable board whitelist --- .gas-snapshot | 79 +++++++++++++------------- src/Billboard/Billboard.sol | 10 +++- src/Billboard/IBillboard.sol | 8 +++ src/test/Billboard/BillboardTest.t.sol | 16 ++++++ 4 files changed, 73 insertions(+), 40 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 8f28159..28b66a3 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -11,51 +11,52 @@ ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) BillboardTest:testApproveAndTransfer() (gas: 258088) BillboardTest:testCalculateTax() (gas: 539668) -BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20140) +BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20118) BillboardTest:testCannotApproveByAttacker() (gas: 224347) BillboardTest:testCannotCalculateTax() (gas: 218883) -BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 255578) -BillboardTest:testCannotClearAuctionIfClosed() (gas: 252770) -BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260744) -BillboardTest:testCannotGetBlockFromEpoch() (gas: 8600) -BillboardTest:testCannotGetEpochFromBlock() (gas: 18393) -BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 278628) -BillboardTest:testCannotPlaceBidIfClosed() (gas: 252692) -BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 463880) +BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 255623) +BillboardTest:testCannotClearAuctionIfClosed() (gas: 252826) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260800) +BillboardTest:testCannotGetBlockFromEpoch() (gas: 8645) +BillboardTest:testCannotGetEpochFromBlock() (gas: 18305) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 283099) +BillboardTest:testCannotPlaceBidIfClosed() (gas: 252700) +BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 465606) BillboardTest:testCannotSafeTransferByAttacker() (gas: 221503) -BillboardTest:testCannotSetBoardByAttacker() (gas: 229566) -BillboardTest:testCannotSetBoardByOwner() (gas: 362072) -BillboardTest:testCannotSetClosedByAttacker() (gas: 228454) +BillboardTest:testCannotSetBoardByAttacker() (gas: 229501) +BillboardTest:testCannotSetBoardByOwner() (gas: 361920) +BillboardTest:testCannotSetClosedByAttacker() (gas: 228521) BillboardTest:testCannotSetWhitelistByAttacker() (gas: 228664) BillboardTest:testCannotTransferByOperator() (gas: 226749) BillboardTest:testCannotTransferToZeroAddress() (gas: 222236) -BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9105) -BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 630648, ~: 630648) -BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 748156, ~: 748156) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 1024014, ~: 1024014) -BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1108455, ~: 1108455) -BillboardTest:testClearAuction(uint96) (runs: 256, μ: 729526, ~: 729526) -BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 736674) -BillboardTest:testClearAuctions() (gas: 1310672) -BillboardTest:testGetBidderBids(uint8,uint8,uint8) (runs: 256, μ: 1505979, ~: 1138329) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 8242518, ~: 6452578) -BillboardTest:testGetBlockFromEpoch() (gas: 16849) -BillboardTest:testGetEpochFromBlock() (gas: 17968) -BillboardTest:testGetTokenURI() (gas: 391497) -BillboardTest:testMintBoard() (gas: 585644) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 837564, ~: 838186) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 1009249, ~: 1009253) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 906263, ~: 907196) -BillboardTest:testPlaceBidZeroPrice() (gas: 430502) -BillboardTest:testSafeTransferByOperator() (gas: 235301) -BillboardTest:testSetBidURIs() (gas: 657544) -BillboardTest:testSetBoardByCreator() (gas: 342349) -BillboardTest:testSetClosed() (gas: 240866) -BillboardTest:testSetWhitelist() (gas: 245316) -BillboardTest:testUpgradeRegistry() (gas: 3708725) -BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1096420, ~: 1096420) -BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 694679, ~: 694679) -BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 734859, ~: 734859) +BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9061) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 632897, ~: 632897) +BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 750415, ~: 750415) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 1026497, ~: 1026497) +BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1110939, ~: 1110939) +BillboardTest:testClearAuction(uint96) (runs: 256, μ: 731784, ~: 731784) +BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 738943) +BillboardTest:testClearAuctions() (gas: 1315188) +BillboardTest:testGetBidderBids(uint8,uint8,uint8) (runs: 256, μ: 1508380, ~: 1140352) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 8175186, ~: 6266659) +BillboardTest:testGetBlockFromEpoch() (gas: 17344) +BillboardTest:testGetEpochFromBlock() (gas: 17704) +BillboardTest:testGetTokenURI() (gas: 391476) +BillboardTest:testMintBoard() (gas: 585623) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 839990, ~: 840612) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 1011369, ~: 1011373) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 908689, ~: 909622) +BillboardTest:testPlaceBidZeroPrice() (gas: 432704) +BillboardTest:testSafeTransferByOperator() (gas: 235237) +BillboardTest:testSetBidURIs() (gas: 659724) +BillboardTest:testSetBoardByCreator() (gas: 342307) +BillboardTest:testSetBoardWhitelistDisabled() (gas: 244291) +BillboardTest:testSetClosed() (gas: 240956) +BillboardTest:testSetWhitelist() (gas: 245250) +BillboardTest:testUpgradeRegistry() (gas: 3788318) +BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1098903, ~: 1098903) +BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 696905, ~: 696905) +BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 737095, ~: 737095) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 0102c93..815c3a1 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -16,6 +16,9 @@ contract Billboard is IBillboard { // tokenId => address => whitelisted mapping(uint256 => mapping(address => bool)) public whitelist; + // tokenId => disabled + mapping(uint256 => bool) public isBoardWhitelistDisabled; + // tokenId => closed mapping(uint256 => bool) public closed; @@ -49,7 +52,7 @@ contract Billboard is IBillboard { } modifier isFromWhitelist(uint256 tokenId_) { - require(whitelist[tokenId_][msg.sender], "Whitelist"); + require(isBoardWhitelistDisabled[tokenId_] || whitelist[tokenId_][msg.sender], "Whitelist"); _; } @@ -87,6 +90,11 @@ contract Billboard is IBillboard { whitelist[tokenId_][account_] = whitelisted; } + /// @inheritdoc IBillboard + function setBoardWhitelistDisabled(uint256 tokenId_, bool disabled) external isFromCreator(tokenId_) { + isBoardWhitelistDisabled[tokenId_] = disabled; + } + /// @inheritdoc IBillboard function setClosed(uint256 tokenId_, bool closed_) external isFromCreator(tokenId_) { closed[tokenId_] = closed_; diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index 09cf895..0309aac 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -51,6 +51,14 @@ interface IBillboard { */ function setWhitelist(uint256 tokenId_, address account_, bool whitelisted) external; + /** + * @notice Enable or disable a board whitelist feature + * + * @param tokenId_ Token ID. + * @param disabled Disabled or not. + */ + function setBoardWhitelistDisabled(uint256 tokenId_, bool disabled) external; + /** * @notice Open or close a board. * diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index ae95514..ed6e9a3 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -61,6 +61,22 @@ contract BillboardTest is BillboardTestBase { operator.setWhitelist(_tokenId, USER_B, false); } + function testSetBoardWhitelistDisabled() public { + (uint256 _tokenId, ) = _mintBoard(); + + vm.startPrank(ADMIN); + + assertEq(operator.isBoardWhitelistDisabled(_tokenId), false); + + // disable + operator.setBoardWhitelistDisabled(_tokenId, true); + assertEq(operator.isBoardWhitelistDisabled(_tokenId), true); + + // enable + operator.setBoardWhitelistDisabled(_tokenId, false); + assertEq(operator.whitelist(_tokenId, USER_A), false); + } + function testSetClosed() public { (uint256 _tokenId, ) = _mintBoard(); From 93f3319becaa0339da6b5fa9a7a7809ec5bedd4c Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:25:24 +0800 Subject: [PATCH 22/24] feat(billboard): add testPlaceBidIfBoardWhitelistDisabled --- .gas-snapshot | 31 +++++++++++++------------- src/test/Billboard/BillboardTest.t.sol | 19 ++++++++++++++++ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 28b66a3..e93edb5 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -11,52 +11,53 @@ ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) BillboardTest:testApproveAndTransfer() (gas: 258088) BillboardTest:testCalculateTax() (gas: 539668) -BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20118) +BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20184) BillboardTest:testCannotApproveByAttacker() (gas: 224347) -BillboardTest:testCannotCalculateTax() (gas: 218883) +BillboardTest:testCannotCalculateTax() (gas: 218839) BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 255623) BillboardTest:testCannotClearAuctionIfClosed() (gas: 252826) BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260800) -BillboardTest:testCannotGetBlockFromEpoch() (gas: 8645) -BillboardTest:testCannotGetEpochFromBlock() (gas: 18305) +BillboardTest:testCannotGetBlockFromEpoch() (gas: 8623) +BillboardTest:testCannotGetEpochFromBlock() (gas: 18283) BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 283099) BillboardTest:testCannotPlaceBidIfClosed() (gas: 252700) -BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 465606) +BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 465588) BillboardTest:testCannotSafeTransferByAttacker() (gas: 221503) BillboardTest:testCannotSetBoardByAttacker() (gas: 229501) BillboardTest:testCannotSetBoardByOwner() (gas: 361920) BillboardTest:testCannotSetClosedByAttacker() (gas: 228521) -BillboardTest:testCannotSetWhitelistByAttacker() (gas: 228664) +BillboardTest:testCannotSetWhitelistByAttacker() (gas: 228642) BillboardTest:testCannotTransferByOperator() (gas: 226749) BillboardTest:testCannotTransferToZeroAddress() (gas: 222236) BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9061) BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 632897, ~: 632897) -BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 750415, ~: 750415) +BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 750371, ~: 750371) BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 1026497, ~: 1026497) -BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1110939, ~: 1110939) -BillboardTest:testClearAuction(uint96) (runs: 256, μ: 731784, ~: 731784) +BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1110962, ~: 1110962) +BillboardTest:testClearAuction(uint96) (runs: 256, μ: 731762, ~: 731762) BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 738943) BillboardTest:testClearAuctions() (gas: 1315188) BillboardTest:testGetBidderBids(uint8,uint8,uint8) (runs: 256, μ: 1508380, ~: 1140352) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 8175186, ~: 6266659) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 8393237, ~: 6579579) BillboardTest:testGetBlockFromEpoch() (gas: 17344) BillboardTest:testGetEpochFromBlock() (gas: 17704) BillboardTest:testGetTokenURI() (gas: 391476) BillboardTest:testMintBoard() (gas: 585623) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 839990, ~: 840612) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 840013, ~: 840635) +BillboardTest:testPlaceBidIfBoardWhitelistDisabled() (gas: 598100) BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 1011369, ~: 1011373) BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 908689, ~: 909622) -BillboardTest:testPlaceBidZeroPrice() (gas: 432704) +BillboardTest:testPlaceBidZeroPrice() (gas: 432727) BillboardTest:testSafeTransferByOperator() (gas: 235237) BillboardTest:testSetBidURIs() (gas: 659724) BillboardTest:testSetBoardByCreator() (gas: 342307) BillboardTest:testSetBoardWhitelistDisabled() (gas: 244291) -BillboardTest:testSetClosed() (gas: 240956) -BillboardTest:testSetWhitelist() (gas: 245250) +BillboardTest:testSetClosed() (gas: 241001) +BillboardTest:testSetWhitelist() (gas: 245228) BillboardTest:testUpgradeRegistry() (gas: 3788318) BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1098903, ~: 1098903) BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 696905, ~: 696905) -BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 737095, ~: 737095) +BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 737140, ~: 737140) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index ed6e9a3..0e4715f 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -390,6 +390,25 @@ contract BillboardTest is BillboardTestBase { operator.placeBid(_tokenId, _epoch, _price); } + function testPlaceBidIfBoardWhitelistDisabled() public { + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + + vm.prank(ADMIN); + operator.setBoardWhitelistDisabled(_tokenId, true); + + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + uint256 _price = 1 ether; + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _total = _price + _tax; + deal(address(usdt), USER_A, _total); + + vm.startPrank(USER_A); + operator.placeBid(_tokenId, _epoch, _price); + IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _epoch, USER_A); + assertEq(_bid.price, _price); + assertEq(_bid.tax, _tax); + } + function testSetBidURIs() public { (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); From 21a13cb7143ca4982d70a6e49a569c776b2d7b9b Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Thu, 15 Aug 2024 18:50:24 +0800 Subject: [PATCH 23/24] feat(billboard): add `getLatestEpoch` and `clearLastAuction` --- .gas-snapshot | 128 +++++++++++++------------ src/Billboard/Billboard.sol | 28 ++++++ src/Billboard/IBillboard.sol | 35 ++++++- src/test/Billboard/BillboardTest.t.sol | 55 +++++++++++ 4 files changed, 182 insertions(+), 64 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index e93edb5..047372d 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,55 +9,57 @@ ACLManagerTest:testGrantRole() (gas: 23547) ACLManagerTest:testRenounceRole() (gas: 27841) ACLManagerTest:testRoles() (gas: 15393) ACLManagerTest:testTransferRole() (gas: 21528) -BillboardTest:testApproveAndTransfer() (gas: 258088) -BillboardTest:testCalculateTax() (gas: 539668) -BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20184) -BillboardTest:testCannotApproveByAttacker() (gas: 224347) -BillboardTest:testCannotCalculateTax() (gas: 218839) -BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 255623) -BillboardTest:testCannotClearAuctionIfClosed() (gas: 252826) -BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260800) -BillboardTest:testCannotGetBlockFromEpoch() (gas: 8623) -BillboardTest:testCannotGetEpochFromBlock() (gas: 18283) -BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 283099) -BillboardTest:testCannotPlaceBidIfClosed() (gas: 252700) -BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 465588) -BillboardTest:testCannotSafeTransferByAttacker() (gas: 221503) -BillboardTest:testCannotSetBoardByAttacker() (gas: 229501) -BillboardTest:testCannotSetBoardByOwner() (gas: 361920) -BillboardTest:testCannotSetClosedByAttacker() (gas: 228521) -BillboardTest:testCannotSetWhitelistByAttacker() (gas: 228642) -BillboardTest:testCannotTransferByOperator() (gas: 226749) -BillboardTest:testCannotTransferToZeroAddress() (gas: 222236) -BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9061) -BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 632897, ~: 632897) -BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 750371, ~: 750371) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 1026497, ~: 1026497) -BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1110962, ~: 1110962) -BillboardTest:testClearAuction(uint96) (runs: 256, μ: 731762, ~: 731762) -BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 738943) -BillboardTest:testClearAuctions() (gas: 1315188) -BillboardTest:testGetBidderBids(uint8,uint8,uint8) (runs: 256, μ: 1508380, ~: 1140352) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 8393237, ~: 6579579) -BillboardTest:testGetBlockFromEpoch() (gas: 17344) -BillboardTest:testGetEpochFromBlock() (gas: 17704) -BillboardTest:testGetTokenURI() (gas: 391476) -BillboardTest:testMintBoard() (gas: 585623) -BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 840013, ~: 840635) -BillboardTest:testPlaceBidIfBoardWhitelistDisabled() (gas: 598100) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 1011369, ~: 1011373) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 908689, ~: 909622) -BillboardTest:testPlaceBidZeroPrice() (gas: 432727) -BillboardTest:testSafeTransferByOperator() (gas: 235237) -BillboardTest:testSetBidURIs() (gas: 659724) -BillboardTest:testSetBoardByCreator() (gas: 342307) -BillboardTest:testSetBoardWhitelistDisabled() (gas: 244291) -BillboardTest:testSetClosed() (gas: 241001) -BillboardTest:testSetWhitelist() (gas: 245228) -BillboardTest:testUpgradeRegistry() (gas: 3788318) -BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1098903, ~: 1098903) -BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 696905, ~: 696905) -BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 737140, ~: 737140) +BillboardTest:testApproveAndTransfer() (gas: 258132) +BillboardTest:testCalculateTax() (gas: 539822) +BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20140) +BillboardTest:testCannotApproveByAttacker() (gas: 224303) +BillboardTest:testCannotCalculateTax() (gas: 218883) +BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 255576) +BillboardTest:testCannotClearAuctionIfClosed() (gas: 252825) +BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260776) +BillboardTest:testCannotGetBlockFromEpoch() (gas: 8600) +BillboardTest:testCannotGetEpochFromBlock() (gas: 18393) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 282918) +BillboardTest:testCannotPlaceBidIfClosed() (gas: 252721) +BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 465605) +BillboardTest:testCannotSafeTransferByAttacker() (gas: 221525) +BillboardTest:testCannotSetBoardByAttacker() (gas: 229589) +BillboardTest:testCannotSetBoardByOwner() (gas: 362030) +BillboardTest:testCannotSetClosedByAttacker() (gas: 228479) +BillboardTest:testCannotSetWhitelistByAttacker() (gas: 228664) +BillboardTest:testCannotTransferByOperator() (gas: 226771) +BillboardTest:testCannotTransferToZeroAddress() (gas: 222258) +BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9017) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 257, μ: 632826, ~: 632826) +BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 257, μ: 750322, ~: 750322) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 257, μ: 1026468, ~: 1026468) +BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 257, μ: 1110954, ~: 1110954) +BillboardTest:testClearAuction(uint96) (runs: 257, μ: 731791, ~: 731791) +BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 738871) +BillboardTest:testClearAuctions() (gas: 1315269) +BillboardTest:testClearLastAuction(uint96) (runs: 257, μ: 732715, ~: 732715) +BillboardTest:testClearLastAuctions() (gas: 1332571) +BillboardTest:testGetBidderBids(uint8,uint8,uint8) (runs: 256, μ: 1508516, ~: 1140480) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 8284065, ~: 6311026) +BillboardTest:testGetBlockFromEpoch() (gas: 16893) +BillboardTest:testGetEpochFromBlock() (gas: 17903) +BillboardTest:testGetTokenURI() (gas: 391345) +BillboardTest:testMintBoard() (gas: 585666) +BillboardTest:testPlaceBid(uint96) (runs: 257, μ: 840034, ~: 840654) +BillboardTest:testPlaceBidIfBoardWhitelistDisabled() (gas: 598142) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 1011469, ~: 1011474) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 257, μ: 908798, ~: 909728) +BillboardTest:testPlaceBidZeroPrice() (gas: 432681) +BillboardTest:testSafeTransferByOperator() (gas: 235259) +BillboardTest:testSetBidURIs() (gas: 659810) +BillboardTest:testSetBoardByCreator() (gas: 342395) +BillboardTest:testSetBoardWhitelistDisabled() (gas: 244401) +BillboardTest:testSetClosed() (gas: 241110) +BillboardTest:testSetWhitelist() (gas: 245426) +BillboardTest:testUpgradeRegistry() (gas: 3997817) +BillboardTest:testWithdrawBid(uint96) (runs: 257, μ: 1098918, ~: 1098918) +BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 257, μ: 697012, ~: 697012) +BillboardTest:testWithdrawTax(uint96) (runs: 257, μ: 737092, ~: 737092) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) @@ -75,8 +77,8 @@ DistributionTest:testCannotClaimIfAlreadyClaimed() (gas: 284835) DistributionTest:testCannotClaimIfInsufficientBalance() (gas: 394264) DistributionTest:testCannotClaimIfInvalidProof() (gas: 245236) DistributionTest:testCannotClaimIfInvalidTreeId() (gas: 243332) -DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 256, μ: 212269, ~: 212284) -DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 256, μ: 214708, ~: 214740) +DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 257, μ: 212269, ~: 212283) +DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 257, μ: 214708, ~: 214740) DistributionTest:testCannotDropIfZeroAmount() (gas: 148793) DistributionTest:testCannotDropTwiceWithSameTreeId() (gas: 307260) DistributionTest:testCannotSetAdminByAdmin() (gas: 17334) @@ -89,14 +91,14 @@ DistributionTest:testSetAdmin() (gas: 20239) DistributionTest:testSweep() (gas: 253087) LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2613180, ~: 1746428) LogbookTest:testClaim() (gas: 135608) -LogbookTest:testDonate(uint96) (runs: 256, μ: 156549, ~: 156936) -LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 256, μ: 146644, ~: 140444) -LogbookTest:testFork(uint96,string) (runs: 256, μ: 452537, ~: 453928) -LogbookTest:testForkRecursively(uint8,uint96) (runs: 256, μ: 5351224, ~: 1801537) -LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 256, μ: 342465, ~: 257636) +LogbookTest:testDonate(uint96) (runs: 257, μ: 156550, ~: 156936) +LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 257, μ: 146620, ~: 140444) +LogbookTest:testFork(uint96,string) (runs: 257, μ: 452543, ~: 453928) +LogbookTest:testForkRecursively(uint8,uint96) (runs: 257, μ: 5332405, ~: 1801537) +LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 257, μ: 342132, ~: 257636) LogbookTest:testMulticall() (gas: 284999) LogbookTest:testPublicSale() (gas: 207337) -LogbookTest:testPublish(string) (runs: 256, μ: 264065, ~: 263590) +LogbookTest:testPublish(string) (runs: 257, μ: 264067, ~: 263590) LogbookTest:testPublishEn1000() (gas: 243477) LogbookTest:testPublishEn140() (gas: 221241) LogbookTest:testPublishEn200() (gas: 222826) @@ -115,7 +117,7 @@ LogbookTest:testPublishZh5000() (gas: 607690) LogbookTest:testSetDescription() (gas: 140760) LogbookTest:testSetForkPrice() (gas: 153925) LogbookTest:testSetTitle() (gas: 168680) -LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 256, μ: 1959072, ~: 965338) +LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 257, μ: 1952649, ~: 965338) LogbookTest:testWithdraw() (gas: 7284400) SnapperTest:testCannotInitRegionByNotOwner() (gas: 11365) SnapperTest:testCannotReInitRegion() (gas: 14373) @@ -123,10 +125,10 @@ SnapperTest:testCannotTakeSnapshotBeforeInit() (gas: 15717) SnapperTest:testCannotTakeSnapshotByNotOwner() (gas: 12478) SnapperTest:testCannotTakeSnapshotWrongLastBlock() (gas: 49242) SnapperTest:testCannotTakeSnapshotWrongSnapshotBlock() (gas: 23899) -SnapperTest:testInitRegion(uint256) (runs: 256, μ: 114408, ~: 114408) +SnapperTest:testInitRegion(uint256) (runs: 257, μ: 114408, ~: 114408) SnapperTest:testTakeSnapshot() (gas: 47831) TheSpaceTest:testBatchBid() (gas: 695308) -TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 256, μ: 371399, ~: 372904) +TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 257, μ: 371405, ~: 372904) TheSpaceTest:testBidDefaultedToken() (gas: 413399) TheSpaceTest:testBidExistingToken() (gas: 360023) TheSpaceTest:testBidNewToken() (gas: 303729) @@ -138,7 +140,7 @@ TheSpaceTest:testCannotGetTaxWithNonExistingToken() (gas: 16379) TheSpaceTest:testCannotGetTokenURIInLogicContract() (gas: 300973) TheSpaceTest:testCannotSetColorByAttacker() (gas: 305326) TheSpaceTest:testCannotSetConfigByAttacker() (gas: 12031) -TheSpaceTest:testCannotSetPixel(uint256) (runs: 256, μ: 314857, ~: 314857) +TheSpaceTest:testCannotSetPixel(uint256) (runs: 257, μ: 314857, ~: 314857) TheSpaceTest:testCannotSetPriceByNonOwner() (gas: 305424) TheSpaceTest:testCannotSetTokenImageURIByNonACLManager() (gas: 11862) TheSpaceTest:testCannotSetTotalSupplyByAttacker() (gas: 11836) @@ -162,12 +164,12 @@ TheSpaceTest:testGetTokenURI() (gas: 333462) TheSpaceTest:testSetColor() (gas: 331348) TheSpaceTest:testSetMintTax() (gas: 271715) TheSpaceTest:testSetPixel(uint256) (runs: 256, μ: 403816, ~: 403816) -TheSpaceTest:testSetPrice(uint256) (runs: 256, μ: 304652, ~: 304652) +TheSpaceTest:testSetPrice(uint256) (runs: 257, μ: 304652, ~: 304652) TheSpaceTest:testSetPriceByOperator(uint96) (runs: 256, μ: 354785, ~: 354785) TheSpaceTest:testSetPriceTooHigh() (gas: 314504) TheSpaceTest:testSetTaxRate() (gas: 347951) TheSpaceTest:testSetTokenImageURI() (gas: 355813) -TheSpaceTest:testSetTotalSupply(uint256) (runs: 256, μ: 352202, ~: 352208) +TheSpaceTest:testSetTotalSupply(uint256) (runs: 257, μ: 352202, ~: 352208) TheSpaceTest:testSetTreasuryShare() (gas: 384288) TheSpaceTest:testSettleTax() (gas: 339465) TheSpaceTest:testTaxCalculation() (gas: 402405) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index 815c3a1..dfebb4b 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -279,6 +279,28 @@ contract Billboard is IBillboard { return (_highestBidders, _prices, _taxes); } + /// @inheritdoc IBillboard + function clearLastAuction(uint256 tokenId_) external returns (address highestBidder, uint256 price, uint256 tax) { + uint256 _lastEpoch = getLatestEpoch(tokenId_) - 1; + return clearAuction(tokenId_, _lastEpoch); + } + + /// @inheritdoc IBillboard + function clearLastAuctions( + uint256[] calldata tokenIds_ + ) external returns (address[] memory highestBidders, uint256[] memory prices, uint256[] memory taxes) { + uint256 _size = tokenIds_.length; + address[] memory _highestBidders = new address[](_size); + uint256[] memory _prices = new uint256[](_size); + uint256[] memory _taxes = new uint256[](_size); + + for (uint256 i = 0; i < _size; i++) { + (_highestBidders[i], _prices[i], _taxes[i]) = this.clearLastAuction(tokenIds_[i]); + } + + return (_highestBidders, _prices, _taxes); + } + /// @inheritdoc IBillboard function getBid( uint256 tokenId_, @@ -411,6 +433,12 @@ contract Billboard is IBillboard { return startedAt_ + (epoch_ * epochInterval_); } + /// @inheritdoc IBillboard + function getLatestEpoch(uint256 tokenId_) public view returns (uint256 epoch) { + IBillboardRegistry.Board memory _board = registry.getBoard(tokenId_); + return this.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + } + ////////////////////////////// /// Tax & Withdraw ////////////////////////////// diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index 0309aac..36bf502 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -138,7 +138,7 @@ interface IBillboard { ) external returns (address highestBidder, uint256 price, uint256 tax); /** - * @notice Clear the next auction of mutiple boards. + * @notice Clear auctions by given epochs. * * @param tokenIds_ Token IDs of boards. * @param epochs_ Epochs of auctions. @@ -152,6 +152,30 @@ interface IBillboard { uint256[] calldata epochs_ ) external returns (address[] calldata highestBidders, uint256[] calldata prices, uint256[] calldata taxes); + /** + * @notice Clear an auction from the last epoch. + * + * @param tokenId_ Token ID. + * + * @return highestBidder Address of the highest bidder. + * @return price Price of the highest bid. + * @return tax Tax of the highest bid. + */ + function clearLastAuction(uint256 tokenId_) external returns (address highestBidder, uint256 price, uint256 tax); + + /** + * @notice Clear auctions from the last epoch. + * + * @param tokenIds_ Token IDs of boards. + * + * @return highestBidders Addresses of the highest bidders. + * @return prices Prices of the highest bids. + * @return taxes Taxes of the highest bids. + */ + function clearLastAuctions( + uint256[] calldata tokenIds_ + ) external returns (address[] calldata highestBidders, uint256[] calldata prices, uint256[] calldata taxes); + /** * @notice Place bid on a board auction. * @@ -297,6 +321,15 @@ interface IBillboard { uint256 epochInterval_ ) external pure returns (uint256 blockNumber); + /** + * @notice Get the latest epoch based on current block number. + * + * @param tokenId_ Token ID. + * + * @return epoch Epoch. + */ + function getLatestEpoch(uint256 tokenId_) external view returns (uint256 epoch); + ////////////////////////////// /// Tax & Withdraw ////////////////////////////// diff --git a/src/test/Billboard/BillboardTest.t.sol b/src/test/Billboard/BillboardTest.t.sol index 0e4715f..d0ca40f 100644 --- a/src/test/Billboard/BillboardTest.t.sol +++ b/src/test/Billboard/BillboardTest.t.sol @@ -564,6 +564,61 @@ contract BillboardTest is BillboardTestBase { operator.clearAuction(_tokenId, _epoch); } + function testClearLastAuction(uint96 _price) public { + vm.assume(_price > 0.001 ether); + + (uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard(); + uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval); + uint256 _tax = operator.calculateTax(_tokenId, _price); + uint256 _clearedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval); + + // place bid + _placeBid(_tokenId, _epoch, USER_A, _price); + + // clear auction + vm.expectEmit(true, true, true, false); + emit IBillboardRegistry.AuctionCleared(_tokenId, _epoch, USER_A); + + vm.roll(_clearedAt); + operator.clearLastAuction(_tokenId); + + // check balances + assertEq(usdt.balanceOf(address(registry)), _tax); + assertEq(usdt.balanceOf(ADMIN), _price); + assertEq(usdt.balanceOf(USER_A), 0); + } + + function testClearLastAuctions() public { + (uint256 _tokenId1, IBillboardRegistry.Board memory _board1) = _mintBoard(); + (uint256 _tokenId2, IBillboardRegistry.Board memory _board2) = _mintBoard(); + uint256 _epoch1 = operator.getEpochFromBlock(_board1.startedAt, block.number, _board1.epochInterval); + uint256 _epoch2 = operator.getEpochFromBlock(_board2.startedAt, block.number, _board2.epochInterval); + _placeBid(_tokenId1, _epoch1, USER_A, 1 ether); + _placeBid(_tokenId2, _epoch2, USER_B, 1 ether); + + uint256 _clearedAt = operator.getBlockFromEpoch(_board1.startedAt, _epoch1 + 1, _board1.epochInterval); + + // clear auctions + vm.expectEmit(true, true, true, true); + emit IBillboardRegistry.AuctionCleared(_tokenId1, _epoch1, USER_A); + vm.expectEmit(true, true, true, true); + emit IBillboardRegistry.AuctionCleared(_tokenId2, _epoch2, USER_B); + + vm.roll(_clearedAt); + + uint256[] memory _tokenIds = new uint256[](2); + _tokenIds[0] = _tokenId1; + _tokenIds[1] = _tokenId2; + operator.clearLastAuctions(_tokenIds); + + // check auction & bids + IBillboardRegistry.Bid memory _bid1 = registry.getBid(_tokenId1, _epoch1, USER_A); + assertEq(_bid1.isWon, true); + + IBillboardRegistry.Bid memory _bid2 = registry.getBid(_tokenId2, _epoch2, USER_B); + assertEq(_bid2.isWon, true); + } + function testGetBids(uint8 _bidCount, uint8 _limit, uint8 _offset) public { vm.assume(_bidCount > 0); vm.assume(_bidCount <= 64); From 36926d8f3420a852972f4c342592b8329211fad1 Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Thu, 15 Aug 2024 22:51:41 +0800 Subject: [PATCH 24/24] feat(billboard): remove unused "payable" --- .gas-snapshot | 76 ++++++++++++++++++------------------ src/Billboard/Billboard.sol | 4 +- src/Billboard/IBillboard.sol | 4 +- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 047372d..52ceb47 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -19,9 +19,9 @@ BillboardTest:testCannotClearAuctionIfClosed() (gas: 252825) BillboardTest:testCannotClearAuctionIfNoBid() (gas: 260776) BillboardTest:testCannotGetBlockFromEpoch() (gas: 8600) BillboardTest:testCannotGetEpochFromBlock() (gas: 18393) -BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 282918) -BillboardTest:testCannotPlaceBidIfClosed() (gas: 252721) -BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 465605) +BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 282966) +BillboardTest:testCannotPlaceBidIfClosed() (gas: 252745) +BillboardTest:testCannotPlaceBidIfNotWhitelisted() (gas: 465624) BillboardTest:testCannotSafeTransferByAttacker() (gas: 221525) BillboardTest:testCannotSetBoardByAttacker() (gas: 229589) BillboardTest:testCannotSetBoardByOwner() (gas: 362030) @@ -30,36 +30,36 @@ BillboardTest:testCannotSetWhitelistByAttacker() (gas: 228664) BillboardTest:testCannotTransferByOperator() (gas: 226771) BillboardTest:testCannotTransferToZeroAddress() (gas: 222258) BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9017) -BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 257, μ: 632826, ~: 632826) -BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 257, μ: 750322, ~: 750322) -BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 257, μ: 1026468, ~: 1026468) -BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 257, μ: 1110954, ~: 1110954) -BillboardTest:testClearAuction(uint96) (runs: 257, μ: 731791, ~: 731791) -BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 738871) -BillboardTest:testClearAuctions() (gas: 1315269) -BillboardTest:testClearLastAuction(uint96) (runs: 257, μ: 732715, ~: 732715) -BillboardTest:testClearLastAuctions() (gas: 1332571) -BillboardTest:testGetBidderBids(uint8,uint8,uint8) (runs: 256, μ: 1508516, ~: 1140480) -BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 8284065, ~: 6311026) +BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 632850, ~: 632850) +BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 750346, ~: 750346) +BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 1026516, ~: 1026516) +BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1111002, ~: 1111002) +BillboardTest:testClearAuction(uint96) (runs: 256, μ: 731815, ~: 731815) +BillboardTest:testClearAuctionIfAlreadyCleared() (gas: 738895) +BillboardTest:testClearAuctions() (gas: 1315317) +BillboardTest:testClearLastAuction(uint96) (runs: 256, μ: 732739, ~: 732739) +BillboardTest:testClearLastAuctions() (gas: 1332619) +BillboardTest:testGetBidderBids(uint8,uint8,uint8) (runs: 256, μ: 1508601, ~: 1140537) +BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 8312589, ~: 6311372) BillboardTest:testGetBlockFromEpoch() (gas: 16893) BillboardTest:testGetEpochFromBlock() (gas: 17903) BillboardTest:testGetTokenURI() (gas: 391345) BillboardTest:testMintBoard() (gas: 585666) -BillboardTest:testPlaceBid(uint96) (runs: 257, μ: 840034, ~: 840654) -BillboardTest:testPlaceBidIfBoardWhitelistDisabled() (gas: 598142) -BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 1011469, ~: 1011474) -BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 257, μ: 908798, ~: 909728) -BillboardTest:testPlaceBidZeroPrice() (gas: 432681) +BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 840080, ~: 840702) +BillboardTest:testPlaceBidIfBoardWhitelistDisabled() (gas: 598166) +BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 1011527, ~: 1011532) +BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 908843, ~: 909776) +BillboardTest:testPlaceBidZeroPrice() (gas: 432705) BillboardTest:testSafeTransferByOperator() (gas: 235259) -BillboardTest:testSetBidURIs() (gas: 659810) +BillboardTest:testSetBidURIs() (gas: 659834) BillboardTest:testSetBoardByCreator() (gas: 342395) BillboardTest:testSetBoardWhitelistDisabled() (gas: 244401) BillboardTest:testSetClosed() (gas: 241110) BillboardTest:testSetWhitelist() (gas: 245426) -BillboardTest:testUpgradeRegistry() (gas: 3997817) -BillboardTest:testWithdrawBid(uint96) (runs: 257, μ: 1098918, ~: 1098918) -BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 257, μ: 697012, ~: 697012) -BillboardTest:testWithdrawTax(uint96) (runs: 257, μ: 737092, ~: 737092) +BillboardTest:testUpgradeRegistry() (gas: 3924811) +BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1098966, ~: 1098966) +BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 697036, ~: 697036) +BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 737116, ~: 737116) CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194) CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797) CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624) @@ -77,8 +77,8 @@ DistributionTest:testCannotClaimIfAlreadyClaimed() (gas: 284835) DistributionTest:testCannotClaimIfInsufficientBalance() (gas: 394264) DistributionTest:testCannotClaimIfInvalidProof() (gas: 245236) DistributionTest:testCannotClaimIfInvalidTreeId() (gas: 243332) -DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 257, μ: 212269, ~: 212283) -DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 257, μ: 214708, ~: 214740) +DistributionTest:testCannotDropIfInsufficientAllowance(uint256) (runs: 256, μ: 212269, ~: 212284) +DistributionTest:testCannotDropIfInsufficientBalance(uint256) (runs: 256, μ: 214708, ~: 214740) DistributionTest:testCannotDropIfZeroAmount() (gas: 148793) DistributionTest:testCannotDropTwiceWithSameTreeId() (gas: 307260) DistributionTest:testCannotSetAdminByAdmin() (gas: 17334) @@ -91,14 +91,14 @@ DistributionTest:testSetAdmin() (gas: 20239) DistributionTest:testSweep() (gas: 253087) LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2613180, ~: 1746428) LogbookTest:testClaim() (gas: 135608) -LogbookTest:testDonate(uint96) (runs: 257, μ: 156550, ~: 156936) -LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 257, μ: 146620, ~: 140444) -LogbookTest:testFork(uint96,string) (runs: 257, μ: 452543, ~: 453928) -LogbookTest:testForkRecursively(uint8,uint96) (runs: 257, μ: 5332405, ~: 1801537) -LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 257, μ: 342132, ~: 257636) +LogbookTest:testDonate(uint96) (runs: 256, μ: 156549, ~: 156936) +LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 256, μ: 146644, ~: 140444) +LogbookTest:testFork(uint96,string) (runs: 256, μ: 452537, ~: 453928) +LogbookTest:testForkRecursively(uint8,uint96) (runs: 256, μ: 5351224, ~: 1801537) +LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 256, μ: 342465, ~: 257636) LogbookTest:testMulticall() (gas: 284999) LogbookTest:testPublicSale() (gas: 207337) -LogbookTest:testPublish(string) (runs: 257, μ: 264067, ~: 263590) +LogbookTest:testPublish(string) (runs: 256, μ: 264065, ~: 263590) LogbookTest:testPublishEn1000() (gas: 243477) LogbookTest:testPublishEn140() (gas: 221241) LogbookTest:testPublishEn200() (gas: 222826) @@ -117,7 +117,7 @@ LogbookTest:testPublishZh5000() (gas: 607690) LogbookTest:testSetDescription() (gas: 140760) LogbookTest:testSetForkPrice() (gas: 153925) LogbookTest:testSetTitle() (gas: 168680) -LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 257, μ: 1952649, ~: 965338) +LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 256, μ: 1959072, ~: 965338) LogbookTest:testWithdraw() (gas: 7284400) SnapperTest:testCannotInitRegionByNotOwner() (gas: 11365) SnapperTest:testCannotReInitRegion() (gas: 14373) @@ -125,10 +125,10 @@ SnapperTest:testCannotTakeSnapshotBeforeInit() (gas: 15717) SnapperTest:testCannotTakeSnapshotByNotOwner() (gas: 12478) SnapperTest:testCannotTakeSnapshotWrongLastBlock() (gas: 49242) SnapperTest:testCannotTakeSnapshotWrongSnapshotBlock() (gas: 23899) -SnapperTest:testInitRegion(uint256) (runs: 257, μ: 114408, ~: 114408) +SnapperTest:testInitRegion(uint256) (runs: 256, μ: 114408, ~: 114408) SnapperTest:testTakeSnapshot() (gas: 47831) TheSpaceTest:testBatchBid() (gas: 695308) -TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 257, μ: 371405, ~: 372904) +TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 256, μ: 371399, ~: 372904) TheSpaceTest:testBidDefaultedToken() (gas: 413399) TheSpaceTest:testBidExistingToken() (gas: 360023) TheSpaceTest:testBidNewToken() (gas: 303729) @@ -140,7 +140,7 @@ TheSpaceTest:testCannotGetTaxWithNonExistingToken() (gas: 16379) TheSpaceTest:testCannotGetTokenURIInLogicContract() (gas: 300973) TheSpaceTest:testCannotSetColorByAttacker() (gas: 305326) TheSpaceTest:testCannotSetConfigByAttacker() (gas: 12031) -TheSpaceTest:testCannotSetPixel(uint256) (runs: 257, μ: 314857, ~: 314857) +TheSpaceTest:testCannotSetPixel(uint256) (runs: 256, μ: 314857, ~: 314857) TheSpaceTest:testCannotSetPriceByNonOwner() (gas: 305424) TheSpaceTest:testCannotSetTokenImageURIByNonACLManager() (gas: 11862) TheSpaceTest:testCannotSetTotalSupplyByAttacker() (gas: 11836) @@ -164,12 +164,12 @@ TheSpaceTest:testGetTokenURI() (gas: 333462) TheSpaceTest:testSetColor() (gas: 331348) TheSpaceTest:testSetMintTax() (gas: 271715) TheSpaceTest:testSetPixel(uint256) (runs: 256, μ: 403816, ~: 403816) -TheSpaceTest:testSetPrice(uint256) (runs: 257, μ: 304652, ~: 304652) +TheSpaceTest:testSetPrice(uint256) (runs: 256, μ: 304652, ~: 304652) TheSpaceTest:testSetPriceByOperator(uint96) (runs: 256, μ: 354785, ~: 354785) TheSpaceTest:testSetPriceTooHigh() (gas: 314504) TheSpaceTest:testSetTaxRate() (gas: 347951) TheSpaceTest:testSetTokenImageURI() (gas: 355813) -TheSpaceTest:testSetTotalSupply(uint256) (runs: 257, μ: 352202, ~: 352208) +TheSpaceTest:testSetTotalSupply(uint256) (runs: 256, μ: 352202, ~: 352208) TheSpaceTest:testSetTreasuryShare() (gas: 384288) TheSpaceTest:testSettleTax() (gas: 339465) TheSpaceTest:testTaxCalculation() (gas: 402405) diff --git a/src/Billboard/Billboard.sol b/src/Billboard/Billboard.sol index dfebb4b..5eb0a08 100644 --- a/src/Billboard/Billboard.sol +++ b/src/Billboard/Billboard.sol @@ -147,7 +147,7 @@ contract Billboard is IBillboard { uint256 tokenId_, uint256 epoch_, uint256 price_ - ) external payable isNotClosed(tokenId_) isFromWhitelist(tokenId_) { + ) external isNotClosed(tokenId_) isFromWhitelist(tokenId_) { _placeBid(tokenId_, epoch_, price_, "", "", false); } @@ -158,7 +158,7 @@ contract Billboard is IBillboard { uint256 price_, string calldata contentURI_, string calldata redirectURI_ - ) external payable isNotClosed(tokenId_) isFromWhitelist(tokenId_) { + ) external isNotClosed(tokenId_) isFromWhitelist(tokenId_) { _placeBid(tokenId_, epoch_, price_, contentURI_, redirectURI_, true); } diff --git a/src/Billboard/IBillboard.sol b/src/Billboard/IBillboard.sol index 36bf502..8ac161f 100644 --- a/src/Billboard/IBillboard.sol +++ b/src/Billboard/IBillboard.sol @@ -183,7 +183,7 @@ interface IBillboard { * @param epoch_ Epoch. * @param price_ Amount of a bid. */ - function placeBid(uint256 tokenId_, uint256 epoch_, uint256 price_) external payable; + function placeBid(uint256 tokenId_, uint256 epoch_, uint256 price_) external; /** * @notice Place bid on a board auction. @@ -200,7 +200,7 @@ interface IBillboard { uint256 price_, string calldata contentURI_, string calldata redirectURI_ - ) external payable; + ) external; /** * @notice Set the content URI and redirect URI of a board.