diff --git a/README.md b/README.md index 7b35d4f..d6921ea 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,32 @@ Deployment scripts are located in the `/script` directory. Example: forge script script/DeployHostItTickets.s.sol --rpc-url --private-key --broadcast ``` +## Dimensional Units + +The inline `// {unit}` comments throughout the Solidity source annotate dimensional types to prevent unit-mismatch bugs. + +### Base Units + +| Unit | Meaning | +|------|---------| +| `{tok}` | Token amounts (ETH or ERC20), in the smallest unit of the payment token selected via `FeeType` | +| `{s}` | Timestamps and durations in seconds (Unix epoch for timestamps, raw seconds for durations) | +| `{1}` | Dimensionless quantities (basis-point ratios, boolean flags, counters) | +| `{ticket}` | Ticket token IDs and counts (NFT minting counters, `maxTickets`, `soldTickets`) | +| `{ticketId}` | Ticket type identifiers (sequential `uint64` IDs assigned by the factory) | +| `{day}` | Day index offset from event start (used in check-in tracking, derived from seconds) | +| `{addr}` | Ethereum addresses (token, admin, buyer) | + +### Derived Units + +| Unit | Meaning | +|------|---------| +| `{1/1}` | Fee fraction: `HOSTIT_FEE_BPS / FEE_BASIS_POINTS` = 300 / 10,000 = 0.03 | + +### Precision + +- **BPS** -- Basis points (1/10,000). `FEE_BASIS_POINTS = 10_000`, `HOSTIT_FEE_BPS = 300` (3%). + ## Security - All contracts are licensed under AGPL-3.0-only. - Uses OpenZeppelin libraries and patterns for security. diff --git a/src/facets/FactoryFacet.sol b/src/facets/FactoryFacet.sol index c9849ed..e4d3bd1 100644 --- a/src/facets/FactoryFacet.sol +++ b/src/facets/FactoryFacet.sol @@ -13,6 +13,8 @@ contract FactoryFacet is IFactory { // EXTERNAL FUNCTIONS //////////////////////////////////////////////////////////////////////////*// + /// @param _fees {tok} per-ticket prices in each fee token + /// @return {ticketId} function createTicket(TicketData calldata _ticketData, FeeType[] calldata _feeTypes, uint256[] calldata _fees) external returns (uint64) @@ -20,6 +22,7 @@ contract FactoryFacet is IFactory { return _ticketData._createTicket(_feeTypes, _fees); } + /// @param _ticketId {ticketId} function updateTicket(TicketData calldata _ticketData, uint64 _ticketId) external { _ticketData._updateTicket(_ticketId); } @@ -28,14 +31,17 @@ contract FactoryFacet is IFactory { // VIEW FUNCTIONS //////////////////////////////////////////////////////////////////////////*// + /// @return {ticketId} total ticket type count function ticketCount() public view returns (uint64) { return LibFactory._getTicketCount(); } + /// @param _ticketId {ticketId} function ticketExists(uint64 _ticketId) public view returns (bool) { return _ticketId._ticketExists(); } + /// @param _ticketId {ticketId} function ticketData(uint64 _ticketId) public view returns (FullTicketData memory) { return _ticketId._getFullTicketData(); } @@ -44,10 +50,12 @@ contract FactoryFacet is IFactory { return LibFactory._getAllFullTicketData(); } + /// @param _ticketAdmin {addr} function adminTickets(address _ticketAdmin) public view returns (uint64[] memory) { return _ticketAdmin._getAdminTicketIds(); } + /// @param _ticketAdmin {addr} function adminTicketData(address _ticketAdmin) public view returns (FullTicketData[] memory) { return _ticketAdmin._getAdminFullTicketData(); } @@ -60,14 +68,17 @@ contract FactoryFacet is IFactory { return LibFactory._getHostItTicketHash(); } + /// @param _ticketId {ticketId} function ticketHash(uint64 _ticketId) public pure returns (bytes32) { return _ticketId._generateTicketHash(); } + /// @param _ticketId {ticketId} function mainAdminRole(uint64 _ticketId) public pure returns (uint256) { return _ticketId._generateMainTicketAdminRole(); } + /// @param _ticketId {ticketId} function ticketAdminRole(uint64 _ticketId) public pure returns (uint256) { return _ticketId._generateTicketAdminRole(); } diff --git a/src/facets/MarketplaceFacet.sol b/src/facets/MarketplaceFacet.sol index 4a4b784..a0b9bf6 100644 --- a/src/facets/MarketplaceFacet.sol +++ b/src/facets/MarketplaceFacet.sol @@ -12,7 +12,9 @@ contract MarketplaceFacet is IMarketplace { // EXTERNAL FUNCTIONS //////////////////////////////////////////////////////////////////////////*// + /// @inheritdoc IMarketplace function mintTicket(uint64 _ticketId, FeeType _feeType, address _buyer) external payable returns (uint40) { + // {ticket} return _ticketId._mintTicket(_feeType, _buyer); } @@ -36,18 +38,22 @@ contract MarketplaceFacet is IMarketplace { // VIEW FUNCTIONS //////////////////////////////////////////////////////////////////////////*// + // @return {1} function isFeeEnabled(uint64 _ticketId, FeeType _feeType) external view returns (bool) { return _ticketId._isFeeEnabled(_feeType); } + // @return {addr} function getFeeTokenAddress(FeeType _feeType) external view returns (address) { return _feeType._getFeeTokenAddress(); } + // @return {tok} function getTicketFee(uint64 _ticketId, FeeType _feeType) external view returns (uint256) { return _ticketId._getTicketFee(_feeType); } + // @return ticketFee_ {tok}, hostItFee_ {tok}, totalFee_ {tok} function getAllFees(uint64 _ticketId, FeeType _feeType) external view @@ -56,10 +62,12 @@ contract MarketplaceFacet is IMarketplace { return _ticketId._getFees(_feeType); } + // @return {tok} function getTicketBalance(uint64 _ticketId, FeeType _feeType) external view returns (uint256) { return _ticketId._getTicketBalance(_feeType); } + // @return {tok} function getHostItBalance(FeeType _feeType) external view returns (uint256) { return _feeType._getHostItBalance(); } @@ -68,10 +76,12 @@ contract MarketplaceFacet is IMarketplace { // PURE FUNCTIONS //////////////////////////////////////////////////////////////////////////*// + // @param _fee {tok} @return {tok} function getHostItFee(uint256 _fee) external pure returns (uint256) { return _fee._getHostItFee(); } + // @return {s} function getRefundPeriod() external pure returns (uint256) { return LibMarketplace.REFUND_PERIOD; } diff --git a/src/inits/HostItInit.sol b/src/inits/HostItInit.sol index 56dd90a..2bc5e07 100644 --- a/src/inits/HostItInit.sol +++ b/src/inits/HostItInit.sol @@ -8,6 +8,8 @@ import {LibMarketplace} from "@ticket/libs/LibMarketplace.sol"; event HostItInitialized(address ticketProxy, FeeType[] feeTypes, address[] tokens); contract HostItInit { + /// @param _ticketProxy {addr} ticket implementation proxy + /// @param _tokens {addr} ERC20 token addresses for each fee type function initHostIt(address _ticketProxy, FeeType[] calldata _feeTypes, address[] calldata _tokens) public { LibFactory._factoryStorage().ticketProxy = _ticketProxy; LibMarketplace._setFeeTokenAddresses(_feeTypes, _tokens); diff --git a/src/interfaces/IFactory.sol b/src/interfaces/IFactory.sol index 648be0a..1f5eadb 100644 --- a/src/interfaces/IFactory.sol +++ b/src/interfaces/IFactory.sol @@ -14,14 +14,14 @@ interface IFactory { /// @notice Creates a new ticket /// @param _ticketData The ticket data /// @param _feeTypes The fee types - /// @param _fees The fees + /// @param _fees {tok} The fees (per-ticket prices in each fee token) function createTicket(TicketData calldata _ticketData, FeeType[] calldata _feeTypes, uint256[] calldata _fees) external - returns (uint64); + returns (uint64); // {ticketId} /// @notice Updates an existing ticket /// @param _ticketData The ticket data - /// @param _ticketId The ID of the ticket to update + /// @param _ticketId {ticketId} The ID of the ticket to update function updateTicket(TicketData calldata _ticketData, uint64 _ticketId) external; //*////////////////////////////////////////////////////////////////////////// @@ -29,16 +29,16 @@ interface IFactory { //////////////////////////////////////////////////////////////////////////*// /// @notice Gets the total number of tickets - /// @return The total number of tickets + /// @return {ticketId} The total number of tickets function ticketCount() external view returns (uint64); /// @notice Checks if a ticket exists - /// @param _ticketId The ID of the ticket to check + /// @param _ticketId {ticketId} The ID of the ticket to check /// @return Whether the ticket exists function ticketExists(uint64 _ticketId) external view returns (bool); /// @notice Gets the ticket data for a ticket - /// @param _ticketId The ID of the ticket to get data for + /// @param _ticketId {ticketId} The ID of the ticket to get data for /// @return The ticket data function ticketData(uint64 _ticketId) external view returns (FullTicketData memory); @@ -47,12 +47,12 @@ interface IFactory { function allTicketData() external view returns (FullTicketData[] memory); /// @notice Gets the list of tickets for a ticket admin - /// @param _ticketAdmin The ticket admin to get tickets for + /// @param _ticketAdmin {addr} The ticket admin to get tickets for /// @return The list of tickets for the ticket admin function adminTickets(address _ticketAdmin) external view returns (uint64[] memory); /// @notice Gets the ticket data for a ticket admin - /// @param _ticketAdmin The ticket admin to get ticket data for + /// @param _ticketAdmin {addr} The ticket admin to get ticket data for /// @return The ticket data for the ticket admin function adminTicketData(address _ticketAdmin) external view returns (FullTicketData[] memory); @@ -65,17 +65,17 @@ interface IFactory { function hostItTicketHash() external pure returns (bytes32); /// @notice Gets the hash of a ticket - /// @param _ticketId The ID of the ticket to get the hash for + /// @param _ticketId {ticketId} The ID of the ticket to get the hash for /// @return The hash of the ticket function ticketHash(uint64 _ticketId) external pure returns (bytes32); /// @notice Gets the main admin role for a ticket - /// @param _ticketId The ID of the ticket to get the main admin role for + /// @param _ticketId {ticketId} The ID of the ticket to get the main admin role for /// @return The main admin role for the ticket function mainAdminRole(uint64 _ticketId) external pure returns (uint256); /// @notice Gets the ticket admin role for a ticket - /// @param _ticketId The ID of the ticket to get the ticket admin role for + /// @param _ticketId {ticketId} The ID of the ticket to get the ticket admin role for /// @return The ticket admin role for the ticket function ticketAdminRole(uint64 _ticketId) external pure returns (uint256); } diff --git a/src/interfaces/IMarketplace.sol b/src/interfaces/IMarketplace.sol index f529792..93ee15e 100644 --- a/src/interfaces/IMarketplace.sol +++ b/src/interfaces/IMarketplace.sol @@ -12,34 +12,34 @@ interface IMarketplace { //////////////////////////////////////////////////////////////////////////*// /// @notice Mints a ticket for the specified buyer - /// @param ticketId The ID of the ticket to mint + /// @param ticketId {ticketId} The ID of the ticket to mint /// @param feeType The type of fee to use for the ticket - /// @param buyer The address of the buyer - /// @return The token ID of the minted ticket + /// @param buyer {addr} The address of the buyer + /// @return {ticket} The token ID of the minted ticket function mintTicket(uint64 ticketId, FeeType feeType, address buyer) external payable returns (uint40); /// @notice Sets the fees for the specified ticket - /// @param ticketId The ID of the ticket to set fees for + /// @param ticketId {ticketId} The ID of the ticket to set fees for /// @param feeTypes The types of fees to set - /// @param fees The fees to set + /// @param fees {tok} The fees to set function setTicketFees(uint64 ticketId, FeeType[] calldata feeTypes, uint256[] calldata fees) external; /// @notice Claims a refund for the specified ticket - /// @param ticketId The ID of the ticket to claim a refund for + /// @param ticketId {ticketId} The ID of the ticket to claim a refund for /// @param feeType The type of fee to claim a refund for - /// @param tokenId The token ID of the ticket to claim a refund for - /// @param to The address to send the refund to + /// @param tokenId {ticket} The token ID of the ticket to claim a refund for + /// @param to {addr} The address to send the refund to function claimRefund(uint64 ticketId, FeeType feeType, uint256 tokenId, address to) external; /// @notice Withdraws the ticket balance for the specified ticket - /// @param ticketId The ID of the ticket to withdraw the balance for + /// @param ticketId {ticketId} The ID of the ticket to withdraw the balance for /// @param feeType The type of fee to withdraw the balance for - /// @param to The address to send the balance to + /// @param to {addr} The address to send the balance to function withdrawTicketBalance(uint64 ticketId, FeeType feeType, address to) external; /// @notice Withdraws the HostIt balance for the specified fee type /// @param feeType The type of fee to withdraw the balance for - /// @param to The address to send the balance to + /// @param to {addr} The address to send the balance to function withdrawHostItBalance(FeeType feeType, address to) external; //*////////////////////////////////////////////////////////////////////////// @@ -47,42 +47,42 @@ interface IMarketplace { //////////////////////////////////////////////////////////////////////////*// /// @notice Checks if the specified fee type is enabled for the specified ticket - /// @param ticketId The ID of the ticket to check + /// @param ticketId {ticketId} The ID of the ticket to check /// @param feeType The type of fee to check - /// @return True if the fee type is enabled, false otherwise + /// @return {1} True if the fee type is enabled, false otherwise function isFeeEnabled(uint64 ticketId, FeeType feeType) external view returns (bool); /// @notice Gets the address of the fee token for the specified fee type /// @param feeType The type of fee to get the address for - /// @return The address of the fee token + /// @return {addr} The address of the fee token function getFeeTokenAddress(FeeType feeType) external view returns (address); /// @notice Gets the fee for the specified ticket and fee type - /// @param ticketId The ID of the ticket to get the fee for + /// @param ticketId {ticketId} The ID of the ticket to get the fee for /// @param feeType The type of fee to get - /// @return The fee for the ticket and fee type + /// @return {tok} The fee for the ticket and fee type function getTicketFee(uint64 ticketId, FeeType feeType) external view returns (uint256); /// @notice Gets the fees for the specified ticket - /// @param ticketId The ID of the ticket to get the fees for + /// @param ticketId {ticketId} The ID of the ticket to get the fees for /// @param feeType The type of fee to get - /// @return ticketFee The ticket fee for the ticket - /// @return hostItFee The HostIt fee for the ticket - /// @return totalFee The total fee for the ticket + /// @return ticketFee {tok} The ticket fee for the ticket + /// @return hostItFee {tok} The HostIt fee for the ticket + /// @return totalFee {tok} The total fee for the ticket function getAllFees(uint64 ticketId, FeeType feeType) external view returns (uint256 ticketFee, uint256 hostItFee, uint256 totalFee); /// @notice Gets the balance of the specified ticket for the specified fee type - /// @param ticketId The ID of the ticket to get the balance for + /// @param ticketId {ticketId} The ID of the ticket to get the balance for /// @param feeType The type of fee to get the balance for - /// @return The balance of the ticket for the fee type + /// @return {tok} The balance of the ticket for the fee type function getTicketBalance(uint64 ticketId, FeeType feeType) external view returns (uint256); /// @notice Gets the balance of HostIt for the specified fee type /// @param feeType The type of fee to get the balance for - /// @return The balance of HostIt for the fee type + /// @return {tok} The balance of HostIt for the fee type function getHostItBalance(FeeType feeType) external view returns (uint256); //*////////////////////////////////////////////////////////////////////////// @@ -90,11 +90,11 @@ interface IMarketplace { //////////////////////////////////////////////////////////////////////////*// /// @notice Calculates the HostIt fee for the specified fee - /// @param fee The fee to calculate the HostIt fee for - /// @return The HostIt fee for the fee + /// @param fee {tok} The fee to calculate the HostIt fee for + /// @return {tok} The HostIt fee for the fee function getHostItFee(uint256 fee) external pure returns (uint256); /// @notice Gets the refund period - /// @return The refund period + /// @return {s} The refund period function getRefundPeriod() external pure returns (uint256); } diff --git a/src/libs/LibCheckIn.sol b/src/libs/LibCheckIn.sol index 5450289..625d86c 100644 --- a/src/libs/LibCheckIn.sol +++ b/src/libs/LibCheckIn.sol @@ -31,10 +31,13 @@ library LibCheckIn { // INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////////////////*// + /// @param _ticketId {ticketId} + /// @param _ticketOwner {addr} + /// @param _tokenId {ticket} function _checkin(uint64 _ticketId, address _ticketOwner, uint40 _tokenId) internal onlyTicketAdmin(_ticketId) { _ticketId._checkTicketExists(); - uint40 time = uint40(block.timestamp); + uint40 time = uint40(block.timestamp); // {s} ExtraTicketData memory ticketData = _ticketId._getExtraTicketData(); if (time < ticketData.startTime) revert TicketUsePeriodNotStarted(); @@ -54,6 +57,7 @@ library LibCheckIn { cs.checkedIn[_ticketId].add(_ticketOwner); + // {day} = ({s} - {s}) / {s} uint8 day = uint8((time - ticketData.startTime) / 1 days); if (!cs.checkedInByDay[_ticketId][day].add(_ticketOwner)) revert AlreadyCheckedInForDay(day); @@ -93,6 +97,7 @@ library LibCheckIn { return _checkInStorage().checkedIn[_ticketId].contains(_ticketOwner); } + /// @param _day {day} function _isCheckedInForDay(uint64 _ticketId, uint8 _day, address _ticketOwner) internal view returns (bool) { return _checkInStorage().checkedInByDay[_ticketId][_day].contains(_ticketOwner); } @@ -101,6 +106,7 @@ library LibCheckIn { return _checkInStorage().checkedIn[_ticketId].values(); } + /// @param _day {day} function _getCheckedInForDay(uint64 _ticketId, uint8 _day) internal view returns (address[] memory) { return _checkInStorage().checkedInByDay[_ticketId][_day].values(); } diff --git a/src/libs/LibFactory.sol b/src/libs/LibFactory.sol index a005604..f00f43e 100644 --- a/src/libs/LibFactory.sol +++ b/src/libs/LibFactory.sol @@ -47,6 +47,8 @@ library LibFactory { // INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////////////////*// + /// @param _fees {tok} per-ticket prices in each fee token + /// @return ticketId_ {ticketId} function _createTicket(TicketData calldata _ticketData, FeeType[] calldata _feeTypes, uint256[] calldata _fees) internal returns (uint64 ticketId_) @@ -55,12 +57,15 @@ library LibFactory { if (bytes(_ticketData.name).length == 0) revert EmptyName(); if (bytes(_ticketData.uri).length == 0) revert EmptyURI(); + // {s} < {s} if (_ticketData.startTime < block.timestamp) { revert StartTimeShouldBeAhead(); } + // {s} < {s} + {s} if (_ticketData.endTime < _ticketData.startTime + 1 days) { revert EndTimeShouldBeOneDayAfterStartTime(); } + // {s} > {s} - {s} if (_ticketData.purchaseStartTime > _ticketData.startTime - 1 days) { revert PurchaseStartTimeShouldBeOneDayBeforeStartTime(); } @@ -68,8 +73,8 @@ library LibFactory { } FactoryStorage storage fs = _factoryStorage(); - ticketId_ = ++fs.ticketId; - address ticketAdmin = LibContext._msgSender(); + ticketId_ = ++fs.ticketId; // {ticketId} + address ticketAdmin = LibContext._msgSender(); // {addr} _grantTicketAdminRoles(ticketAdmin, ticketId_); ExtraTicketData memory extraTicketData = _createExtraTicketData(fs, _ticketData, ticketId_, ticketAdmin); @@ -90,13 +95,14 @@ library LibFactory { if (_fees[i] == 0) revert ZeroFee(feeType); mps.feeEnabled[ticketId_][feeType] = true; - mps.ticketFee[ticketId_][feeType] = _fees[i]; + mps.ticketFee[ticketId_][feeType] = _fees[i]; // {tok} } } emit TicketCreated(ticketId_, ticketAdmin, extraTicketData); } + /// @param _ticketId {ticketId} function _updateTicket(TicketData calldata _ticketData, uint64 _ticketId) internal { _checkTicketExists(_ticketId); _generateMainTicketAdminRole(_ticketId)._checkRoles(); @@ -104,39 +110,43 @@ library LibFactory { ExtraTicketData memory extraTicketData = _getExtraTicketData(_ticketId); if (_ticketData.startTime > 0) { + // {s} < {s} if (_ticketData.startTime < uint40(block.timestamp)) { revert StartTimeShouldBeAhead(); } - extraTicketData.startTime = _ticketData.startTime; + extraTicketData.startTime = _ticketData.startTime; // {s} } if (_ticketData.endTime > 0) { + // {s} < {s} + {s} if (_ticketData.endTime < _ticketData.startTime + 1 days) { revert EndTimeShouldBeOneDayAfterStartTime(); } - extraTicketData.endTime = _ticketData.endTime; + extraTicketData.endTime = _ticketData.endTime; // {s} } if (_ticketData.purchaseStartTime > 0) { + // {s} > {s} - {s} if (_ticketData.purchaseStartTime > _ticketData.startTime - 1 days) { revert PurchaseStartTimeShouldBeOneDayBeforeStartTime(); } - extraTicketData.purchaseStartTime = _ticketData.purchaseStartTime; + extraTicketData.purchaseStartTime = _ticketData.purchaseStartTime; // {s} } if (_ticketData.maxTicketsPerUser > 0) { - extraTicketData.maxTicketsPerUser = _ticketData.maxTicketsPerUser; + extraTicketData.maxTicketsPerUser = _ticketData.maxTicketsPerUser; // {ticket} } ITicket ticket = ITicket(extraTicketData.ticketAddress); if (_ticketData.maxTickets > 0) { + // {ticket} < {ticket} if (_ticketData.maxTickets < ticket.totalSupply()) { revert MaxTicketsShouldEqualSupply(); } - extraTicketData.maxTickets = _ticketData.maxTickets; + extraTicketData.maxTickets = _ticketData.maxTickets; // {ticket} } - extraTicketData.updatedAt = uint48(block.timestamp); + extraTicketData.updatedAt = uint48(block.timestamp); // {s} _factoryStorage().ticketIdToData[_ticketId] = extraTicketData; if (bytes(_ticketData.name).length > 0) { @@ -187,19 +197,19 @@ library LibFactory { } extraTicketData_ = ExtraTicketData({ - id: _ticketId, - createdAt: uint48(block.timestamp), + id: _ticketId, // {ticketId} + createdAt: uint48(block.timestamp), // {s} updatedAt: 0, - startTime: _ticketData.startTime, - endTime: _ticketData.endTime, - purchaseStartTime: _ticketData.purchaseStartTime, - maxTickets: _ticketData.maxTickets, - soldTickets: 0, - maxTicketsPerUser: _ticketData.maxTicketsPerUser, + startTime: _ticketData.startTime, // {s} + endTime: _ticketData.endTime, // {s} + purchaseStartTime: _ticketData.purchaseStartTime, // {s} + maxTickets: _ticketData.maxTickets, // {ticket} + soldTickets: 0, // {ticket} + maxTicketsPerUser: _ticketData.maxTicketsPerUser, // {ticket} isFree: _ticketData.isFree, isRefundable: _ticketData.isRefundable, - ticketAdmin: _ticketAdmin, - ticketAddress: ticketAddress + ticketAdmin: _ticketAdmin, // {addr} + ticketAddress: ticketAddress // {addr} }); } @@ -207,6 +217,7 @@ library LibFactory { // VIEW FUNCTIONS //////////////////////////////////////////////////////////////////////////*// + /// @return {ticketId} total ticket type count function _getTicketCount() internal view returns (uint64) { return LibFactory._factoryStorage().ticketId; } diff --git a/src/libs/LibMarketplace.sol b/src/libs/LibMarketplace.sol index fdff203..1364567 100644 --- a/src/libs/LibMarketplace.sol +++ b/src/libs/LibMarketplace.sol @@ -27,10 +27,10 @@ library LibMarketplace { // STORAGE //////////////////////////////////////////////////////////////////////////*// - uint256 internal constant REFUND_PERIOD = 3 days; + uint256 internal constant REFUND_PERIOD = 3 days; // {s} - uint256 private constant HOSTIT_FEE_BPS = 300; // 3% fee in basis points - uint256 private constant FEE_BASIS_POINTS = 10_000; // 10,000 basis points + uint256 private constant HOSTIT_FEE_BPS = 300; // BPS{1} 3% fee in basis points + uint256 private constant FEE_BASIS_POINTS = 10_000; // BPS{1} 10,000 basis points function _marketplaceStorage() internal pure returns (MarketplaceStorage storage ms_) { assembly { @@ -42,43 +42,52 @@ library LibMarketplace { // INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////////////////*// + /// @param _ticketId {ticketId} + /// @param _buyer {addr} + /// @return tokenId_ {ticket} function _mintTicket(uint64 _ticketId, FeeType _feeType, address _buyer) internal returns (uint40 tokenId_) { _ticketId._checkTicketExists(); ExtraTicketData memory ticketData = _ticketId._getExtraTicketData(); { - uint48 time = block.timestamp.toUint48(); + uint48 time = block.timestamp.toUint48(); // {s} + // {s} < {s} if (time < ticketData.purchaseStartTime) { revert PurchaseTimeNotReached(); } - if (time > ticketData.endTime) revert PurchaseTimeNotReached(); + if (time > ticketData.endTime) revert PurchaseTimeNotReached(); // {s} > {s} if (ticketData.soldTickets == ticketData.maxTickets) { + // {ticket} == {ticket} revert TicketSoldOut(); } } ITicket ticket = ITicket(ticketData.ticketAddress); + // {ticket} > {ticket} if (ticket.balanceOf(_buyer) > ticketData.maxTicketsPerUser) { revert MaxTicketsHeld(); } MarketplaceStorage storage ms = _marketplaceStorage(); + // {tok}, {tok}, {tok} (uint256 fee, uint256 hostItFee, uint256 totalFee) = _getFees(ms, _ticketId, _feeType); if (!ticketData.isFree) { if (!_isFeeEnabled(ms, _ticketId, _feeType)) revert FeeNotEnabled(); if (ticketData.isRefundable) { if (_feeType == FeeType.ETH) { + // {tok} < {tok} if (msg.value < totalFee) { revert TicketPurchaseFailed(_feeType, totalFee); } } else { _payWithToken(ms, _feeType, totalFee, address(this)); } - ms.ticketBalance[_ticketId][_feeType] += fee; + ms.ticketBalance[_ticketId][_feeType] += fee; // {tok} += {tok} } else { if (_feeType == FeeType.ETH) { + // {tok} < {tok} if (msg.value < totalFee) { revert TicketPurchaseFailed(_feeType, totalFee); } @@ -88,11 +97,12 @@ library LibMarketplace { _payWithToken(ms, _feeType, hostItFee, address(this)); } } - ms.hostItBalance[_feeType] += hostItFee; + ms.hostItBalance[_feeType] += hostItFee; // {tok} += {tok} } - tokenId_ = ticket.mint(_buyer).toUint40(); - ++LibFactory._factoryStorage().ticketIdToData[_ticketId].soldTickets; + tokenId_ = ticket.mint(_buyer).toUint40(); // {ticket} + ++LibFactory._factoryStorage().ticketIdToData[_ticketId].soldTickets; // {ticket} + // {ticket} != {ticket} if (tokenId_ != LibFactory._factoryStorage().ticketIdToData[_ticketId].soldTickets) { revert TicketAccountingMismatch(); } @@ -100,6 +110,8 @@ library LibMarketplace { emit TicketMinted(_ticketId, _feeType, totalFee, tokenId_); } + /// @param _ticketId {ticketId} + /// @param _fees {tok} per-ticket price in each fee token function _setTicketFees(uint64 _ticketId, FeeType[] calldata _feeTypes, uint256[] calldata _fees) internal onlyMainTicketAdmin(_ticketId) @@ -120,12 +132,15 @@ library LibMarketplace { if (_fees[i] == 0) revert ZeroFee(); ms.feeEnabled[_ticketId][_feeTypes[i]] = true; - ms.ticketFee[_ticketId][_feeTypes[i]] = _fees[i]; + ms.ticketFee[_ticketId][_feeTypes[i]] = _fees[i]; // {tok} } emit TicketFeeSet(_ticketId, _feeTypes, _fees); } + /// @param _ticketId {ticketId} + /// @param _tokenId {ticket} + /// @param _to {addr} function _claimRefund(uint64 _ticketId, FeeType _feeType, uint256 _tokenId, address _to) internal { _ticketId._checkTicketExists(); @@ -133,18 +148,19 @@ library LibMarketplace { if (!ticketData.isRefundable) revert RefundNotEnabled(); - uint48 time = block.timestamp.toUint48(); - if (time < ticketData.endTime) revert RefundPeriodNotReached(); + uint48 time = block.timestamp.toUint48(); // {s} + if (time < ticketData.endTime) revert RefundPeriodNotReached(); // {s} < {s} + // {s} > {s} + {s} if (time > ticketData.endTime + REFUND_PERIOD) { revert RefundPeriodExpired(); } - address caller = LibContext._msgSender(); + address caller = LibContext._msgSender(); // {addr} ITicket ticket = ITicket(ticketData.ticketAddress); - if (caller != ticket.ownerOf(_tokenId)) revert TicketNotOwned(_tokenId); + if (caller != ticket.ownerOf(_tokenId)) revert TicketNotOwned(_tokenId); // {addr} != {addr} - uint256 ticketFee = _getTicketFee(_ticketId, _feeType); - _marketplaceStorage().ticketBalance[_ticketId][_feeType] -= ticketFee; + uint256 ticketFee = _getTicketFee(_ticketId, _feeType); // {tok} + _marketplaceStorage().ticketBalance[_ticketId][_feeType] -= ticketFee; // {tok} -= {tok} try ticket.safeTransferFrom(caller, ticketData.ticketAdmin, _tokenId) {} catch { @@ -160,6 +176,8 @@ library LibMarketplace { emit TicketRefunded(_ticketId, _feeType, ticketFee, _to); } + /// @param _ticketId {ticketId} + /// @param _to {addr} function _withdrawTicketBalance(uint64 _ticketId, FeeType _feeType, address _to) internal onlyMainTicketAdmin(_ticketId) @@ -170,12 +188,13 @@ library LibMarketplace { ExtraTicketData memory ticketData = _ticketId._getExtraTicketData(); if (ticketData.isRefundable) { + // {s} < {s} + {s} if (block.timestamp < ticketData.endTime + REFUND_PERIOD) { revert WithdrawPeriodNotReached(); } } - uint256 balance = _getTicketBalance(_ticketId, _feeType); + uint256 balance = _getTicketBalance(_ticketId, _feeType); // {tok} if (balance == 0) revert InsufficientWithdrawBalance(); delete _marketplaceStorage().ticketBalance[_ticketId][_feeType]; @@ -196,10 +215,11 @@ library LibMarketplace { emit TicketBalanceWithdrawn(_ticketId, _feeType, balance, _to); } + /// @param _to {addr} function _withdrawHostItBalance(FeeType _feeType, address _to) internal onlyOwner { _checkIfContract(_to); - uint256 balance = _getHostItBalance(_feeType); + uint256 balance = _getHostItBalance(_feeType); // {tok} if (balance == 0) revert InsufficientWithdrawBalance(); delete _marketplaceStorage().hostItBalance[_feeType]; @@ -211,14 +231,18 @@ library LibMarketplace { emit HostItBalanceWithdrawn(_feeType, balance, _to); } + /// @param _totalFee {tok} + /// @param _to {addr} function _payWithToken(MarketplaceStorage storage _ms, FeeType _feeType, uint256 _totalFee, address _to) internal { - address caller = LibContext._msgSender(); + address caller = LibContext._msgSender(); // {addr} - address tokenAddress = _getFeeTokenAddress(_ms, _feeType); + address tokenAddress = _getFeeTokenAddress(_ms, _feeType); // {addr} IERC20 token = IERC20(tokenAddress); + // {tok} < {tok} if (token.balanceOf(caller) < _totalFee) { revert InsufficientBalance(tokenAddress, _feeType, _totalFee); } + // {tok} < {tok} if (token.allowance(caller, address(this)) < _totalFee) { revert InsufficientAllowance(tokenAddress, _feeType, _totalFee); } @@ -285,10 +309,12 @@ library LibMarketplace { if (tokenAddress_ == address(0)) revert TokenAddressZero(); } + /// @return {tok} function _getTicketFee(uint64 _ticketId, FeeType _feeType) internal view returns (uint256) { return _getTicketFee(_marketplaceStorage(), _ticketId, _feeType); } + /// @return {tok} function _getTicketFee(MarketplaceStorage storage _ms, uint64 _ticketId, FeeType _feeType) internal view @@ -297,30 +323,38 @@ library LibMarketplace { return _ms.ticketFee[_ticketId][_feeType]; } + /// @return ticketFee_ {tok} + /// @return hostItFee_ {tok} + /// @return totalFee_ {tok} function _getFees(uint64 _ticketId, FeeType _feeType) internal view returns (uint256 ticketFee_, uint256 hostItFee_, uint256 totalFee_) { - ticketFee_ = _getTicketFee(_ticketId, _feeType); - hostItFee_ = _getHostItFee(ticketFee_); - totalFee_ = ticketFee_ + hostItFee_; + ticketFee_ = _getTicketFee(_ticketId, _feeType); // {tok} + hostItFee_ = _getHostItFee(ticketFee_); // {tok} + totalFee_ = ticketFee_ + hostItFee_; // {tok} = {tok} + {tok} } + /// @return ticketFee_ {tok} + /// @return hostItFee_ {tok} + /// @return totalFee_ {tok} function _getFees(MarketplaceStorage storage _ms, uint64 _ticketId, FeeType _feeType) internal view returns (uint256 ticketFee_, uint256 hostItFee_, uint256 totalFee_) { - ticketFee_ = _getTicketFee(_ms, _ticketId, _feeType); - hostItFee_ = _getHostItFee(ticketFee_); - totalFee_ = ticketFee_ + hostItFee_; + ticketFee_ = _getTicketFee(_ms, _ticketId, _feeType); // {tok} + hostItFee_ = _getHostItFee(ticketFee_); // {tok} + totalFee_ = ticketFee_ + hostItFee_; // {tok} = {tok} + {tok} } + /// @return {tok} function _getTicketBalance(uint64 _ticketId, FeeType _feeType) internal view returns (uint256) { return _getTicketBalance(_marketplaceStorage(), _ticketId, _feeType); } + /// @return {tok} function _getTicketBalance(MarketplaceStorage storage _ms, uint64 _ticketId, FeeType _feeType) internal view @@ -329,10 +363,12 @@ library LibMarketplace { return _ms.ticketBalance[_ticketId][_feeType]; } + /// @return {tok} function _getHostItBalance(FeeType _feeType) internal view returns (uint256) { return _getHostItBalance(_marketplaceStorage(), _feeType); } + /// @return {tok} function _getHostItBalance(MarketplaceStorage storage _ms, FeeType _feeType) internal view returns (uint256) { return _ms.hostItBalance[_feeType]; } @@ -341,7 +377,10 @@ library LibMarketplace { if (_address.code.length > 0) revert ContractNotAllowed(); } + /// @param _fee {tok} + /// @return {tok} function _getHostItFee(uint256 _fee) internal pure returns (uint256) { + // {tok} = ({tok} * BPS{1}) / BPS{1} return ((_fee * HOSTIT_FEE_BPS) / FEE_BASIS_POINTS); } diff --git a/src/libs/logs/MarketplaceLogs.sol b/src/libs/logs/MarketplaceLogs.sol index ed6a0ce..79f719f 100644 --- a/src/libs/logs/MarketplaceLogs.sol +++ b/src/libs/logs/MarketplaceLogs.sol @@ -3,18 +3,18 @@ pragma solidity 0.8.30; import {FeeType} from "@ticket-storage/MarketplaceStorage.sol"; -event TicketFeeSet(uint64 indexed ticketId, FeeType[] feeType, uint256[] fee); +event TicketFeeSet(uint64 indexed ticketId, FeeType[] feeType, uint256[] fee); // ticketId:{ticketId}, fee:{tok} -event TicketRefunded(uint64 indexed ticketId, FeeType indexed feeType, uint256 fee, address indexed to); +event TicketRefunded(uint64 indexed ticketId, FeeType indexed feeType, uint256 fee, address indexed to); // fee:{tok} -event HostItFeeBpsSet(uint16 indexed hostItFeeBps); +event HostItFeeBpsSet(uint16 indexed hostItFeeBps); // hostItFeeBps:BPS{1} -event TicketFeeAddressSet(FeeType[] feeType, address[] token); +event TicketFeeAddressSet(FeeType[] feeType, address[] token); // token:{addr} -event TicketMinted(uint64 indexed ticketId, FeeType indexed feeType, uint256 fee, uint40 tokenId); +event TicketMinted(uint64 indexed ticketId, FeeType indexed feeType, uint256 fee, uint40 tokenId); // fee:{tok}, tokenId:{ticket} -event TicketBalanceWithdrawn(uint64 indexed ticketId, FeeType indexed feeType, uint256 fee, address indexed to); +event TicketBalanceWithdrawn(uint64 indexed ticketId, FeeType indexed feeType, uint256 fee, address indexed to); // fee:{tok} -event HostItBalanceWithdrawn(FeeType indexed feeType, uint256 fee, address indexed to); +event HostItBalanceWithdrawn(FeeType indexed feeType, uint256 fee, address indexed to); // fee:{tok} error ContractNotAllowed(); diff --git a/src/libs/storage/MarketplaceStorage.sol b/src/libs/storage/MarketplaceStorage.sol index 908220a..da62fbf 100644 --- a/src/libs/storage/MarketplaceStorage.sol +++ b/src/libs/storage/MarketplaceStorage.sol @@ -8,11 +8,11 @@ bytes32 constant MARKETPLACE_STORAGE_LOCATION = 0x3f09c55b469305b27ecae2a46b3f36 /// @notice Storage structure for managing marketplace data /// @custom:storage-location erc7201:host.it.ticket.marketplace.storage struct MarketplaceStorage { - mapping(uint64 => mapping(FeeType => bool)) feeEnabled; - mapping(uint64 => mapping(FeeType => uint256)) ticketFee; - mapping(FeeType => address) feeTokenAddress; - mapping(uint64 => mapping(FeeType => uint256)) ticketBalance; - mapping(FeeType => uint256) hostItBalance; + mapping(uint64 => mapping(FeeType => bool)) feeEnabled; // {ticketId} => FeeType => {1} + mapping(uint64 => mapping(FeeType => uint256)) ticketFee; // {ticketId} => FeeType => {tok} + mapping(FeeType => address) feeTokenAddress; // FeeType => {addr} + mapping(uint64 => mapping(FeeType => uint256)) ticketBalance; // {ticketId} => FeeType => {tok} + mapping(FeeType => uint256) hostItBalance; // FeeType => {tok} } /// @title FeeType