Skip to content

Commit

Permalink
feat(billboard): allow to set startedAt on mintBoard
Browse files Browse the repository at this point in the history
  • Loading branch information
robertu7 committed Jul 4, 2024
1 parent 25eb421 commit aedeae6
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 159 deletions.
83 changes: 42 additions & 41 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,48 @@ ACLManagerTest:testGrantRole() (gas: 23547)
ACLManagerTest:testRenounceRole() (gas: 27841)
ACLManagerTest:testRoles() (gas: 15393)
ACLManagerTest:testTransferRole() (gas: 21528)
BillboardTest:testAddToWhitelist() (gas: 252212)
BillboardTest:testApproveAndTransfer() (gas: 252906)
BillboardTest:testCalculateTax() (gas: 537121)
BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20110)
BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225785)
BillboardTest:testCannotApproveByAttacker() (gas: 221798)
BillboardTest:testCannotCalculateTax() (gas: 216267)
BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 247699)
BillboardTest:testCannotClearAuctionIfNoBid() (gas: 255383)
BillboardTest:testCannotGetBlockFromEpoch() (gas: 8624)
BillboardTest:testCannotGetEpochFromBlock() (gas: 15101)
BillboardTest:testCannotPlaceBidByAttacker() (gas: 459845)
BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 270400)
BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 225915)
BillboardTest:testCannotSafeTransferByAttacker() (gas: 218934)
BillboardTest:testCannotSetBoardByAttacker() (gas: 226906)
BillboardTest:testCannotSetBoardByOwner() (gas: 359213)
BillboardTest:testCannotTransferByOperator() (gas: 224176)
BillboardTest:testCannotTransferToZeroAddress() (gas: 219620)
BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 8973)
BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 614841, ~: 614841)
BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 730039, ~: 730039)
BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 982330, ~: 982330)
BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1065867, ~: 1065867)
BillboardTest:testClearAuction(uint96) (runs: 256, μ: 712194, ~: 712194)
BillboardTest:testClearAuctions() (gas: 1278631)
BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 4883559, ~: 2141284)
BillboardTest:testGetBlockFromEpoch() (gas: 13456)
BillboardTest:testGetEpochFromBlock() (gas: 14182)
BillboardTest:testGetTokenURI() (gas: 388843)
BillboardTest:testMintBoard() (gas: 417449)
BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 798110, ~: 800443)
BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 975808, ~: 975813)
BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 865928, ~: 869427)
BillboardTest:testPlaceBidZeroPrice() (gas: 416644)
BillboardTest:testRemoveToWhitelist() (gas: 238874)
BillboardTest:testSafeTransferByOperator() (gas: 232665)
BillboardTest:testSetBoardByCreator() (gas: 339666)
BillboardTest:testUpgradeRegistry() (gas: 3326621)
BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1054742, ~: 1054742)
BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 717516, ~: 717516)
BillboardTest:testAddToWhitelist() (gas: 252415)
BillboardTest:testApproveAndTransfer() (gas: 253043)
BillboardTest:testCalculateTax() (gas: 537542)
BillboardTest:testCannnotWithdrawTaxIfZero() (gas: 20183)
BillboardTest:testCannotAddToWhitelistByAttacker() (gas: 225988)
BillboardTest:testCannotApproveByAttacker() (gas: 221891)
BillboardTest:testCannotCalculateTax() (gas: 216404)
BillboardTest:testCannotClearAuctionIfAuctionNotEnded() (gas: 248674)
BillboardTest:testCannotClearAuctionIfNoBid() (gas: 256039)
BillboardTest:testCannotGetBlockFromEpoch() (gas: 8688)
BillboardTest:testCannotGetEpochFromBlock() (gas: 15590)
BillboardTest:testCannotPlaceBidByAttacker() (gas: 460155)
BillboardTest:testCannotPlaceBidIfAuctionEnded() (gas: 271390)
BillboardTest:testCannotRemoveToWhitelistByAttacker() (gas: 226118)
BillboardTest:testCannotSafeTransferByAttacker() (gas: 219003)
BillboardTest:testCannotSetBoardByAttacker() (gas: 227087)
BillboardTest:testCannotSetBoardByOwner() (gas: 359460)
BillboardTest:testCannotTransferByOperator() (gas: 224137)
BillboardTest:testCannotTransferToZeroAddress() (gas: 219713)
BillboardTest:testCannotUpgradeRegistryByAttacker() (gas: 9039)
BillboardTest:testCannotWithdrawBidIfAuctionNotEndedOrCleared(uint96) (runs: 256, μ: 600791, ~: 600791)
BillboardTest:testCannotWithdrawBidIfNotFound(uint96) (runs: 256, μ: 715717, ~: 715717)
BillboardTest:testCannotWithdrawBidIfWon(uint96) (runs: 256, μ: 966732, ~: 966732)
BillboardTest:testCannotWithdrawBidTwice(uint96) (runs: 256, μ: 1050826, ~: 1050826)
BillboardTest:testClearAuction(uint96) (runs: 256, μ: 697518, ~: 697518)
BillboardTest:testClearAuctions() (gas: 1248982)
BillboardTest:testGetBids(uint8,uint8,uint8) (runs: 256, μ: 5071994, ~: 2128035)
BillboardTest:testGetBlockFromEpoch() (gas: 14680)
BillboardTest:testGetEpochFromBlock() (gas: 15789)
BillboardTest:testGetTokenURI() (gas: 389096)
BillboardTest:testMintBoard() (gas: 580688)
BillboardTest:testPlaceBid(uint96) (runs: 256, μ: 781546, ~: 783879)
BillboardTest:testPlaceBidWithHigherPrice(uint96) (runs: 256, μ: 965751, ~: 965756)
BillboardTest:testPlaceBidWithSamePrices(uint96) (runs: 256, μ: 849576, ~: 853075)
BillboardTest:testPlaceBidZeroPrice() (gas: 401269)
BillboardTest:testRemoveToWhitelist() (gas: 239143)
BillboardTest:testSafeTransferByOperator() (gas: 232823)
BillboardTest:testSetBidURIs() (gas: 628156)
BillboardTest:testSetBoardByCreator() (gas: 339913)
BillboardTest:testUpgradeRegistry() (gas: 3411474)
BillboardTest:testWithdrawBid(uint96) (runs: 256, μ: 1039157, ~: 1039157)
BillboardTest:testWithdrawTax(uint96) (runs: 256, μ: 702873, ~: 702873)
CurationTest:testCannotCurateERC20CurateZeroAmount() (gas: 12194)
CurationTest:testCannotCurateERC20EmptyURI() (gas: 15797)
CurationTest:testCannotCurateERC20IfNotApproval() (gas: 21624)
Expand Down
59 changes: 39 additions & 20 deletions src/Billboard/Billboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,18 @@ contract Billboard is IBillboard {
/// @inheritdoc IBillboard
function mintBoard(uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId) {
require(epochInterval_ > 0, "Zero epoch interval");
tokenId = registry.newBoard(msg.sender, taxRate_, epochInterval_);
tokenId = registry.newBoard(msg.sender, taxRate_, epochInterval_, block.number);
whitelist[tokenId][msg.sender] = true;
}

/// @inheritdoc IBillboard
function mintBoard(
uint256 taxRate_,
uint256 epochInterval_,
uint256 startedAt_
) external returns (uint256 tokenId) {
require(epochInterval_ > 0, "Zero epoch interval");
tokenId = registry.newBoard(msg.sender, taxRate_, epochInterval_, startedAt_);
whitelist[tokenId][msg.sender] = true;
}

Expand Down Expand Up @@ -136,15 +147,15 @@ contract Billboard is IBillboard {
IBillboardRegistry.Board memory _board = registry.getBoard(tokenId_);
require(_board.creator != address(0), "Board not found");

uint256 _endedAt = this.getBlockFromEpoch(epoch_ + 1, _board.epochInterval);
uint256 _endedAt = this.getBlockFromEpoch(_board.startedAt, epoch_ + 1, _board.epochInterval);
require(block.number < _endedAt, "Auction ended");

IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, msg.sender);

uint256 _tax = calculateTax(tokenId_, price_);

// create new bid if no bid exists
if (_bid.createdAt == 0) {
if (_bid.placedAt == 0) {
// transfer bid price and tax to the registry
SafeERC20.safeTransferFrom(registry.currency(), msg.sender, address(registry), price_ + _tax);

Expand Down Expand Up @@ -188,14 +199,14 @@ contract Billboard is IBillboard {
require(_board.creator != address(0), "Board not found");

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

address _highestBidder = registry.highestBidder(tokenId_, epoch_);
IBillboardRegistry.Bid memory _highestBid = registry.getBid(tokenId_, epoch_, _highestBidder);

// revert if no bid
require(_highestBid.createdAt != 0, "No bid");
require(_highestBid.placedAt != 0, "No bid");

// skip if auction is already cleared
if (_highestBid.isWon) {
Expand Down Expand Up @@ -282,43 +293,51 @@ contract Billboard is IBillboard {
}

/// @inheritdoc IBillboard
function withdrawBid(uint256 tokenId_, uint256 epoch_) external {
function withdrawBid(uint256 tokenId_, uint256 epoch_, address bidder_) external {
// revert if board not found
IBillboardRegistry.Board memory _board = this.getBoard(tokenId_);
require(_board.creator != address(0), "Board not found");

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

// revert if auction is not cleared
address _highestBidder = registry.highestBidder(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);
IBillboardRegistry.Bid memory _bid = registry.getBid(tokenId_, epoch_, bidder_);
uint256 amount = _bid.price + _bid.tax;

require(_bid.createdAt != 0, "Bid not found");
require(_bid.placedAt != 0, "Bid not found");
require(!_bid.isWithdrawn, "Bid already withdrawn");
require(!_bid.isWon, "Bid already won");
require(amount > 0, "Zero amount");

// set bid.isWithdrawn to true first to prevent reentrancy
registry.setBidWithdrawn(tokenId_, epoch_, msg.sender, true);
registry.setBidWithdrawn(tokenId_, epoch_, bidder_, true);

// transfer bid price and tax back to the bidder
registry.transferCurrencyByOperator(msg.sender, amount);
registry.transferCurrencyByOperator(bidder_, amount);
}

/// @inheritdoc IBillboard
function getEpochFromBlock(uint256 block_, uint256 epochInterval_) public pure returns (uint256 epoch) {
return block_ / epochInterval_;
function getEpochFromBlock(
uint256 startedAt_,
uint256 block_,
uint256 epochInterval_
) public pure returns (uint256 epoch) {
return (block_ - startedAt_) / epochInterval_;
}

/// @inheritdoc IBillboard
function getBlockFromEpoch(uint256 epoch_, uint256 epochInterval_) public pure returns (uint256 blockNumber) {
return epoch_ * epochInterval_;
function getBlockFromEpoch(
uint256 startedAt_,
uint256 epoch_,
uint256 epochInterval_
) public pure returns (uint256 blockNumber) {
return startedAt_ + (epoch_ * epochInterval_);
}

//////////////////////////////
Expand All @@ -335,22 +354,22 @@ contract Billboard is IBillboard {
}

/// @inheritdoc IBillboard
function withdrawTax() external returns (uint256 tax) {
(uint256 _taxAccumulated, uint256 _taxWithdrawn) = registry.taxTreasury(msg.sender);
function withdrawTax(address creator_) external returns (uint256 tax) {
(uint256 _taxAccumulated, uint256 _taxWithdrawn) = registry.taxTreasury(creator_);

uint256 amount = _taxAccumulated - _taxWithdrawn;

require(amount > 0, "Zero amount");

// set taxTreasury.withdrawn to taxTreasury.accumulated first
// to prevent reentrancy
registry.setTaxTreasury(msg.sender, _taxAccumulated, _taxAccumulated);
registry.setTaxTreasury(creator_, _taxAccumulated, _taxAccumulated);

// transfer tax to the owner
registry.transferCurrencyByOperator(msg.sender, amount);
registry.transferCurrencyByOperator(creator_, amount);

// emit TaxWithdrawn
registry.emitTaxWithdrawn(msg.sender, amount);
registry.emitTaxWithdrawn(creator_, amount);

return amount;
}
Expand Down
17 changes: 9 additions & 8 deletions src/Billboard/BillboardRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 {
function newBoard(
address to_,
uint256 taxRate_,
uint256 epochInterval_
uint256 epochInterval_,
uint256 startedAt_
) external isFromOperator returns (uint256 tokenId) {
lastTokenId.increment();
tokenId = lastTokenId.current();
Expand All @@ -95,7 +96,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 {
location: "",
taxRate: taxRate_,
epochInterval: epochInterval_,
createdAt: block.number
startedAt: startedAt_
});

emit BoardCreated(tokenId, to_, taxRate_, epochInterval_);
Expand Down Expand Up @@ -145,7 +146,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 {
tax: tax_,
contentURI: contentURI_,
redirectURI: redirectURI_,
createdAt: block.number,
placedAt: block.number,
updatedAt: block.number,
isWithdrawn: false,
isWon: false
Expand Down Expand Up @@ -173,8 +174,8 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 {
string calldata redirectURI_,
bool hasURIs
) external isFromOperator {
Bid memory _bid = bids[tokenId_][epoch_][bidder_];
require(_bid.createdAt != 0, "Bid not found");
Bid storage _bid = bids[tokenId_][epoch_][bidder_];
require(_bid.placedAt != 0, "Bid not found");

_bid.price = price_;
_bid.tax = tax_;
Expand All @@ -196,7 +197,7 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 {
// the highest bidder since the block.number is always greater.
function _sethighestBidder(uint256 tokenId_, uint256 epoch_, uint256 price_, address bidder_) internal {
address _highestBidder = highestBidder[tokenId_][epoch_];
Bid memory highestBid = bids[tokenId_][epoch_][_highestBidder];
Bid storage highestBid = bids[tokenId_][epoch_][_highestBidder];
if (_highestBidder == address(0) || price_ > highestBid.price) {
highestBidder[tokenId_][epoch_] = bidder_;
}
Expand All @@ -210,8 +211,8 @@ contract BillboardRegistry is IBillboardRegistry, ERC721 {
string calldata contentURI_,
string calldata redirectURI_
) external isFromOperator {
Bid memory _bid = bids[tokenId_][epoch_][bidder_];
require(_bid.createdAt != 0, "Bid not found");
Bid storage _bid = bids[tokenId_][epoch_][bidder_];
require(_bid.placedAt != 0, "Bid not found");

_bid.contentURI = contentURI_;
_bid.redirectURI = redirectURI_;
Expand Down
32 changes: 28 additions & 4 deletions src/Billboard/IBillboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ interface IBillboard {
*/
function mintBoard(uint256 taxRate_, uint256 epochInterval_) external returns (uint256 tokenId);

/**
* @notice Mint a new board (NFT).
*
* @param taxRate_ Tax rate per epoch. (e.g. 1024 for 10.24% per epoch)
* @param epochInterval_ Epoch interval in blocks (e.g. 100 for 100 blocks).
* @param startedAt_ Block number when the board starts the first epoch.
*
* @return tokenId Token ID of the new board.
*/
function mintBoard(uint256 taxRate_, uint256 epochInterval_, uint256 startedAt_) external returns (uint256 tokenId);

/**
* @notice Get metadata of a board .
*
Expand Down Expand Up @@ -213,28 +224,39 @@ interface IBillboard {
*
* @param tokenId_ Token ID.
* @param epoch_ Epoch.
* @param bidder_ Address of bidder.
*/
function withdrawBid(uint256 tokenId_, uint256 epoch_) external;
function withdrawBid(uint256 tokenId_, uint256 epoch_, address bidder_) external;

/**
* @notice Calculate epoch from block number.
*
* @param startedAt_ Started at block number.
* @param block_ Block number.
* @param epochInterval_ Epoch interval.
*
* @return epoch Epoch.
*/
function getEpochFromBlock(uint256 block_, uint256 epochInterval_) external pure returns (uint256 epoch);
function getEpochFromBlock(
uint256 startedAt_,
uint256 block_,
uint256 epochInterval_
) external pure returns (uint256 epoch);

/**
* @notice Calculate block number from epoch.
*
* @param startedAt_ Started at block number.
* @param epoch_ Epoch.
* @param epochInterval_ Epoch interval.
*
* @return blockNumber Block number.
*/
function getBlockFromEpoch(uint256 epoch_, uint256 epochInterval_) external pure returns (uint256 blockNumber);
function getBlockFromEpoch(
uint256 startedAt_,
uint256 epoch_,
uint256 epochInterval_
) external pure returns (uint256 blockNumber);

//////////////////////////////
/// Tax & Withdraw
Expand Down Expand Up @@ -262,8 +284,10 @@ interface IBillboard {
/**
* @notice Withdraw accumulated taxation.
*
* @param creator_ Address of board creator.
*
*/
function withdrawTax() external returns (uint256 tax);
function withdrawTax(address creator_) external returns (uint256 tax);

//////////////////////////////
/// ERC721 related
Expand Down
Loading

0 comments on commit aedeae6

Please sign in to comment.