Skip to content

Commit

Permalink
feat(billboard): support to close board
Browse files Browse the repository at this point in the history
  • Loading branch information
robertu7 committed Jul 4, 2024
1 parent aedeae6 commit ee35f02
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 72 deletions.
85 changes: 44 additions & 41 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,51 @@ ACLManagerTest:testGrantRole() (gas: 23547)
ACLManagerTest:testRenounceRole() (gas: 27841)
ACLManagerTest:testRoles() (gas: 15393)
ACLManagerTest:testTransferRole() (gas: 21528)
BillboardTest:testAddToWhitelist() (gas: 252415)
BillboardTest:testApproveAndTransfer() (gas: 253043)
BillboardTest:testCalculateTax() (gas: 537542)
BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20183)
BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225988)
BillboardTest:testCannotApproveByAttacker() (gas: 221891)
BillboardTest:testCannotCalculateTax() (gas: 216404)
BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 248674)
BillboardTest:testCannotClearAuctionIfNoBid() (gas: 256039)
BillboardTest:testCannotGetBlockFromEpoch() (gas: 8688)
BillboardTest:testCannotGetEpochFromBlock() (gas: 15590)
BillboardTest:testCannotPlaceBidByAttacker() (gas: 460155)
BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 271390)
BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 226118)
BillboardTest:testCannotSafeTransferByAttacker() (gas: 219003)
BillboardTest:testCannotSetBoardByAttacker() (gas: 227087)
BillboardTest:testCannotSetBoardByOwner() (gas: 359460)
BillboardTest:testCannotTransferByOperator() (gas: 224137)
BillboardTest:testCannotTransferToZeroAddress() (gas: 219713)
BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9039)
BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 600791, ~: 600791)
BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 715717, ~: 715717)
BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 966732, ~: 966732)
BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1050826, ~: 1050826)
BillboardTest:testClearAuction(uint96) (runs: 256, μ: 697518, ~: 697518)
BillboardTest:testClearAuctions() (gas: 1248982)
BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 5071994, ~: 2128035)
BillboardTest:testGetBlockFromEpoch() (gas: 14680)
BillboardTest:testGetEpochFromBlock() (gas: 15789)
BillboardTest:testGetTokenURI() (gas: 389096)
BillboardTest:testMintBoard() (gas: 580688)
BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 781546, ~: 783879)
BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 965751, ~: 965756)
BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 849576, ~: 853075)
BillboardTest:testPlaceBidZeroPrice() (gas: 401269)
BillboardTest:testRemoveToWhitelist() (gas: 239143)
BillboardTest:testApproveAndTransfer() (gas: 253021)
BillboardTest:testCalculateTax() (gas: 537366)
BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20184)
BillboardTest:testCannotApproveByAttacker() (gas: 221803)
BillboardTest:testCannotCalculateTax() (gas: 216449)
BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 253188)
BillboardTest:testCannotClearAuctionIfClosed() (gas: 250314)
BillboardTest:testCannotClearAuctionIfNoBid() (gas: 258354)
BillboardTest:testCannotGetBlockFromEpoch() (gas: 8666)
BillboardTest:testCannotGetEpochFromBlock() (gas: 15634)
BillboardTest:testCannotPlaceBidByAttacker() (gas: 461915)
BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 276192)
BillboardTest:testCannotPlaceBidIfClosed() (gas: 250190)
BillboardTest:testCannotSafeTransferByAttacker() (gas: 219025)
BillboardTest:testCannotSetBoardByAttacker() (gas: 227022)
BillboardTest:testCannotSetBoardByOwner() (gas: 359240)
BillboardTest:testCannotSetClosedByAttacker() (gas: 225976)
BillboardTest:testCannotSetWhitelistByAttacker() (gas: 226186)
BillboardTest:testCannotTransferByOperator() (gas: 224159)
BillboardTest:testCannotTransferToZeroAddress() (gas: 219669)
BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9105)
BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 603933, ~: 603933)
BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 718940, ~: 718940)
BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 970407, ~: 970407)
BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1054869, ~: 1054869)
BillboardTest:testClearAuction(uint96) (runs: 256, μ: 700310, ~: 700310)
BillboardTest:testClearAuctions() (gas: 1254674)
BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4921861, ~: 2132068)
BillboardTest:testGetBlockFromEpoch() (gas: 14900)
BillboardTest:testGetEpochFromBlock() (gas: 15987)
BillboardTest:testGetTokenURI() (gas: 388974)
BillboardTest:testMintBoard() (gas: 580732)
BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 784616, ~: 786949)
BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 968453, ~: 968458)
BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 852569, ~: 856068)
BillboardTest:testPlaceBidZeroPrice() (gas: 403633)
BillboardTest:testSafeTransferByOperator() (gas: 232823)
BillboardTest:testSetBidURIs() (gas: 628156)
BillboardTest:testSetBoardByCreator() (gas: 339913)
BillboardTest:testUpgradeRegistry() (gas: 3411474)
BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1039157, ~: 1039157)
BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 702873, ~: 702873)
BillboardTest:testSetBidURIs() (gas: 630718)
BillboardTest:testSetBoardByCreator() (gas: 339694)
BillboardTest:testSetClosed() (gas: 238386)
BillboardTest:testSetWhitelist() (gas: 242793)
BillboardTest:testUpgradeRegistry() (gas: 3494596)
BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1042812, ~: 1042812)
BillboardTest:testWithdrawBidIfClosed(uint96) (runs: 256, μ: 667875, ~: 667875)
BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 705598, ~: 705598)
CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194)
CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797)
CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624)
Expand Down
32 changes: 23 additions & 9 deletions src/Billboard/Billboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ contract Billboard is IBillboard {
// tokenId => address => whitelisted
mapping(uint256 => mapping(address => bool)) public whitelist;

// tokenId => closed
mapping(uint256 => bool) public closed;

constructor(address token_, address payable registry_, address admin_, string memory name_, string memory symbol_) {
require(admin_ != address(0), "Zero address");
admin = admin_;
Expand Down Expand Up @@ -44,6 +47,11 @@ contract Billboard is IBillboard {
_;
}

modifier isNotClosed(uint256 tokenId_) {
require(closed[tokenId_] != true, "Closed");
_;
}

modifier isFromCreator(uint256 tokenId_) {
IBillboardRegistry.Board memory _board = registry.getBoard(tokenId_);
require(_board.creator == msg.sender, "Creator");
Expand All @@ -69,13 +77,13 @@ contract Billboard is IBillboard {
//////////////////////////////

/// @inheritdoc IBillboard
function addToWhitelist(uint256 tokenId_, address account_) external isFromCreator(tokenId_) {
whitelist[tokenId_][account_] = true;
function setWhitelist(uint256 tokenId_, address account_, bool whitelisted) external isFromCreator(tokenId_) {
whitelist[tokenId_][account_] = whitelisted;
}

/// @inheritdoc IBillboard
function removeFromWhitelist(uint256 tokenId_, address account_) external isFromCreator(tokenId_) {
whitelist[tokenId_][account_] = false;
function setClosed(uint256 tokenId_, bool closed_) external isFromCreator(tokenId_) {
closed[tokenId_] = closed_;
}

//////////////////////////////
Expand Down Expand Up @@ -121,7 +129,11 @@ contract Billboard is IBillboard {
//////////////////////////////

/// @inheritdoc IBillboard
function placeBid(uint256 tokenId_, uint256 epoch_, uint256 price_) external payable isFromWhitelist(tokenId_) {
function placeBid(
uint256 tokenId_,
uint256 epoch_,
uint256 price_
) external payable isNotClosed(tokenId_) isFromWhitelist(tokenId_) {
_placeBid(tokenId_, epoch_, price_, "", "", false);
}

Expand All @@ -132,7 +144,7 @@ contract Billboard is IBillboard {
uint256 price_,
string calldata contentURI_,
string calldata redirectURI_
) external payable isFromWhitelist(tokenId_) {
) external payable isNotClosed(tokenId_) isFromWhitelist(tokenId_) {
_placeBid(tokenId_, epoch_, price_, contentURI_, redirectURI_, true);
}

Expand Down Expand Up @@ -193,7 +205,7 @@ contract Billboard is IBillboard {
function clearAuction(
uint256 tokenId_,
uint256 epoch_
) public returns (address highestBidder, uint256 price, uint256 tax) {
) public isNotClosed(tokenId_) returns (address highestBidder, uint256 price, uint256 tax) {
// revert if board not found
IBillboardRegistry.Board memory _board = this.getBoard(tokenId_);
require(_board.creator != address(0), "Board not found");
Expand Down Expand Up @@ -294,18 +306,20 @@ contract Billboard is IBillboard {

/// @inheritdoc IBillboard
function withdrawBid(uint256 tokenId_, uint256 epoch_, address bidder_) external {
bool _isClosed = closed[tokenId_];

// revert if board not found
IBillboardRegistry.Board memory _board = this.getBoard(tokenId_);
require(_board.creator != address(0), "Board not found");

// revert if auction is not ended
uint256 _endedAt = this.getBlockFromEpoch(_board.startedAt, epoch_ + 1, _board.epochInterval);
require(block.number >= _endedAt, "Auction not ended");
require(_isClosed || block.number >= _endedAt, "Auction not ended");

// revert if auction is not cleared
address _highestBidder = registry.highestBidder(tokenId_, epoch_);
IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder);
require(_highestBid.isWon, "Auction not cleared");
require(_isClosed || _highestBid.isWon, "Auction not cleared");

IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, bidder_);
uint256 amount = _bid.price + _bid.tax;
Expand Down
11 changes: 6 additions & 5 deletions src/Billboard/IBillboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,21 @@ interface IBillboard {
//////////////////////////////

/**
* @notice Add address to whitelist.
* @notice Add or remove whitelist address.
*
* @param tokenId_ Token ID.
* @param account_ Address of user will be added into whitelist.
* @param whitelisted Whitelisted or not.
*/
function addToWhitelist(uint256 tokenId_, address account_) external;
function setWhitelist(uint256 tokenId_, address account_, bool whitelisted) external;

/**
* @notice Remove address from whitelist.
* @notice Open or close a board.
*
* @param tokenId_ Token ID.
* @param account_ Address of user will be removed from whitelist.
* @param closed Closed or not.
*/
function removeFromWhitelist(uint256 tokenId_, address account_) external;
function setClosed(uint256 tokenId_, bool closed) external;

//////////////////////////////
/// Board
Expand Down
90 changes: 74 additions & 16 deletions src/test/Billboard/BillboardTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,45 +37,51 @@ contract BillboardTest is BillboardTestBase {
/// Access control
//////////////////////////////

function testAddToWhitelist() public {
function testSetWhitelist() public {
(uint256 _tokenId, ) = _mintBoard();

vm.startPrank(ADMIN);

operator.addToWhitelist(_tokenId, USER_A);
// add to whitelist
operator.setWhitelist(_tokenId, USER_A, true);
assertEq(operator.whitelist(_tokenId, USER_A), true);

assertEq(operator.whitelist(_tokenId, USER_B), false);

// remove from whitelist
operator.setWhitelist(_tokenId, USER_A, false);
assertEq(operator.whitelist(_tokenId, USER_A), false);
}

function testCannotAddToWhitelistByAttacker() public {
function testCannotSetWhitelistByAttacker() public {
(uint256 _tokenId, ) = _mintBoard();

vm.startPrank(ATTACKER);

vm.expectRevert("Creator");
operator.addToWhitelist(_tokenId, USER_A);
operator.setWhitelist(_tokenId, USER_B, false);
}

function testRemoveToWhitelist() public {
function testSetClosed() public {
(uint256 _tokenId, ) = _mintBoard();

vm.startPrank(ADMIN);

operator.addToWhitelist(_tokenId, USER_A);
assertEq(operator.whitelist(_tokenId, USER_A), true);
// set closed
operator.setClosed(_tokenId, true);
assertEq(operator.closed(_tokenId), true);

operator.removeFromWhitelist(_tokenId, USER_A);
assertEq(operator.whitelist(_tokenId, USER_A), false);
// set open
operator.setClosed(_tokenId, false);
assertEq(operator.closed(_tokenId), false);
}

function testCannotRemoveToWhitelistByAttacker() public {
function testCannotSetClosedByAttacker() public {
(uint256 _tokenId, ) = _mintBoard();

vm.startPrank(ATTACKER);

vm.expectRevert("Creator");
operator.removeFromWhitelist(_tokenId, USER_B);
operator.setClosed(_tokenId, true);
}

//////////////////////////////
Expand Down Expand Up @@ -198,8 +204,8 @@ contract BillboardTest is BillboardTestBase {
uint256 _prevRegistryBalance = usdt.balanceOf(address(registry));

vm.startPrank(ADMIN);
operator.addToWhitelist(_tokenId, USER_A);
operator.addToWhitelist(_tokenId, USER_B);
operator.setWhitelist(_tokenId, USER_A, true);
operator.setWhitelist(_tokenId, USER_B, true);
vm.stopPrank();

vm.expectEmit(true, true, true, true);
Expand Down Expand Up @@ -322,6 +328,17 @@ contract BillboardTest is BillboardTestBase {
assertEq(_bid.isWon, false);
}

function testCannotPlaceBidIfClosed() public {
(uint256 _tokenId, ) = _mintBoard();
uint256 _epoch = operator.getEpochFromBlock(block.number, block.number, 1);

vm.startPrank(ADMIN);
operator.setClosed(_tokenId, true);

vm.expectRevert("Closed");
operator.placeBid(_tokenId, _epoch, 1 ether);
}

function testCannotPlaceBidIfAuctionEnded() public {
(uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard();
uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval);
Expand All @@ -330,7 +347,7 @@ contract BillboardTest is BillboardTestBase {
uint256 _endedAt = operator.getBlockFromEpoch(_board.startedAt, _epoch + 1, _board.epochInterval);

vm.prank(ADMIN);
operator.addToWhitelist(_tokenId, USER_A);
operator.setWhitelist(_tokenId, USER_A, true);

vm.startPrank(USER_A);

Expand Down Expand Up @@ -454,6 +471,17 @@ contract BillboardTest is BillboardTestBase {
assertEq(_bid2.isWon, true);
}

function testCannotClearAuctionIfClosed() public {
(uint256 _tokenId, ) = _mintBoard();
uint256 _epoch = operator.getEpochFromBlock(block.number, block.number, 1);

vm.startPrank(ADMIN);
operator.setClosed(_tokenId, true);

vm.expectRevert("Closed");
operator.clearAuction(_tokenId, _epoch);
}

function testCannotClearAuctionIfAuctionNotEnded() public {
(uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard();
uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval);
Expand Down Expand Up @@ -490,7 +518,7 @@ contract BillboardTest is BillboardTestBase {
address _bidder = address(uint160(2000 + i));

vm.prank(ADMIN);
operator.addToWhitelist(_tokenId, _bidder);
operator.setWhitelist(_tokenId, _bidder, true);

uint256 _price = 1 ether + i;
uint256 _tax = operator.calculateTax(_tokenId, _price);
Expand Down Expand Up @@ -559,6 +587,36 @@ contract BillboardTest is BillboardTestBase {
assertEq(usdt.balanceOf(USER_B), _total);
}

function testWithdrawBidIfClosed(uint96 _price) public {
vm.assume(_price > 0.001 ether);

(uint256 _tokenId, IBillboardRegistry.Board memory _board) = _mintBoard();
uint256 _epoch = operator.getEpochFromBlock(_board.startedAt, block.number, _board.epochInterval);
uint256 _tax = operator.calculateTax(_tokenId, _price);
uint256 _total = _price + _tax;

// bid with USER_A
_placeBid(_tokenId, _epoch, USER_A, _price);

// set closed
vm.prank(ADMIN);
operator.setClosed(_tokenId, true);

// withdraw bid
vm.expectEmit(true, true, true, false);
emit IBillboardRegistry.BidWithdrawn(_tokenId, _epoch, USER_A);

vm.prank(USER_A);
operator.withdrawBid(_tokenId, _epoch, USER_A);

// check balances
assertEq(usdt.balanceOf(USER_A), _total);

// check bid
IBillboardRegistry.Bid memory _bid = registry.getBid(_tokenId, _epoch, USER_A);
assertEq(_bid.isWithdrawn, true);
}

function testCannotWithdrawBidTwice(uint96 _price) public {
vm.assume(_price > 0.001 ether);

Expand Down
2 changes: 1 addition & 1 deletion src/test/Billboard/BillboardTestBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ contract BillboardTestBase is Test {
deal(address(usdt), _bidder, _total);

vm.prank(ADMIN);
operator.addToWhitelist(_tokenId, _bidder);
operator.setWhitelist(_tokenId, _bidder, true);

vm.prank(_bidder);
operator.placeBid(_tokenId, _epoch, _price);
Expand Down

0 comments on commit ee35f02

Please sign in to comment.