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(); +// } +// }