By Hamed Mohammed
This project develops a foundational on-chain ticketing service using Solidity, focusing on NFT-based ticket issuance and marketplace functionality. It integrates primary and secondary marketplaces for ticket creation, purchase, and bidding, with adherence to ERC721 and ERC20 standards. The implementation encompasses ticket metadata management, including event names, unique IDs, holder information, and validity timestamps. Additionally, it features admin-controlled ticket usage flags and efficient transaction handling serveing as a conceptual model, demonstrating basic blockchain ticketing functionalities. Core components:
- a non-fungible token (NFT) contract for implementing ticket logic,
- a primary marketplace that allows users to create tickets (by deploying new instances of an NFT contract) and mint these tickets in exchange for an ERC20 token payment,
- a secondary marketplace that allows users to create bids for tickets listed for sale by current ticket holders.
- TicketNFT Contract
- Primary Marketplace Contract
- Secondary Marketplace Contract
- Purchase Token Contract (unchanged)
-
The Expiry Date is set when the NFT contract is deloyed and is not tied to the time each Ticket is minted.
-
When querying for the mint price of a Ticket the Event must exist.
-
Tickets for an Event can only be minted via the Primary Marketplace.
-
Tickets can be set as "used" by the Event Creator at any time.
-
Sellers on the Secondary Marketplace are not allowed to bid on their own listings (at least not from the same address haha).
-
A listed Ticket may only be delisted by the seller unless:
- Event has expired
- Ticket has been used
-
Immediate Refund for the Highest Bidder when:
- Outbid by another party
- Ticket is delisted from auction
- TicketNFT (This file tests the TicketNFT, PrimaryMarket and PurchaseToken contracts)
- Primary Marketplace
- Secondary Marketplace
- End to End (unchanged)
Breakdown of tests for each function:
| Function Name | Test Function Name | Check |
|---|---|---|
| Primary Market | ||
| createNewEvent | testCreateNewEvent | PASS |
| purchase | testPurchaseTicket | PASS |
| getPrice | testGetPrice | PASS |
| TicketNFT | ||
| mint | Tested in Multiple Functions/Files | PASS |
| holderOf | Tested in Multiple Functions/Files | PASS |
| transferFrom | testTransferAndCheckNewOwner | PASS |
| approve | testApproveAndGetApproved | PASS |
| getApproved | testApproveAndGetApproved | PASS |
| updateHolderName | testApproveAndGetApproved | PASS |
| setUsed | testSetUsed | PASS |
| isExpiredOrUsed | testIsExpiredOrUsed | PASS |
| holderNameOf | testUpdateHolderName | |
| Secondary Market | ||
| listTicket | testListSubmitAccept | PASS |
| submitBid | testListSubmitAccept | PASS |
| getHighestBid | testGetHighestBidAndBidder | PASS |
| getHighestBidder | testGetHighestBidAndBidder | PASS |
| acceptBid | testListSubmitAccept | PASS |
| delistTicket | testDelistTicket | PASS |
| PurchaseToken | ||
| transfer | Tested in Multiple Functions/Files | PASS |
| approve | Tested in Multiple Functions/Files | PASS |
| transferFrom | Tested in Multiple Functions/Files | PASS |
| increaseAllowance | Tested in within other Functions/Files | PASS |
| decreaseAllowance | Tested in within other Functions/Files | PASS |
| Function | Error Msg Expected | Tested by | Check |
|---|---|---|---|
| Primary Market | |||
| createNewEvent | "Event creator cannot be the zero/burn address" | testCreateNewEventApprovePurchaseBy0 | PASS |
| "Event name cannot be empty" | testBadEventCreation | PASS | |
| "Maximum number of tickets must be positive" | testBadEventCreation | PASS | |
| "Price must be positive" | testBadEventCreation | PASS | |
| purchase | "ERC20: approve from the zero address" + "ERC20: insufficient allowance" | testCreateNewEventApprovePurchaseBy0 | PASS |
| "ERC20: insufficient allowance" | testPurchaseNoApprovalBalance | PASS | |
| "ERC20: transfer amount exceeds balance" | testPurchaseNoApprovalBalance | PASS | |
| getPrice | "Ticket Collection Not Found" | testNonExistentEvent | PASS |
| Secondary Market | |||
| listTicket | "Ticket expired or used" | testInvalidListSubmitAccept | PASS |
| submitBid | "Ticket expired or used" | testInvalidListSubmitAccept + testDelistTicket | PASS |
| "Ticket not for sale" | testInvalidListSubmitAccept | PASS | |
| "Bid too low" | testListSubmitAccept | PASS | |
| "Seller cannot be on his own listings" | testPriceManipulation | PASS | |
| getHighestBid | testGetHighestBidAndBidder | PASS | |
| getHighestBidder | testGetHighestBidAndBidder | PASS | |
| acceptBid | "No active listing" | testInvalidListSubmitAccept | PASS |
| "Not seller" | testListSubmitAccept | PASS | |
| "No bids yet" | testListSubmitAccept | PASS | |
| "Ticket expired or used" | testInvalidListSubmitAccept + testDelistTicket | PASS | |
| delistTicket | "No active listing" | testDelistTicket | PASS |
| "Delister is not seller" unless expired or used | testDelistTicket | PASS | |
| TicketNFT | |||
| mint | "Only Primary Market can mint tickets" | testUnAuthMint | PASS |
| "Max ticket limit reached" | testPurchaseExceedsMaxTickets | PASS | |
| "Event has expired" | testPurchaseAfterTicketExpiry | PASS | |
| holderOf | "Ticket does not exist" | testTicketDoesNotExist | PASS |
| transferFrom | "Transfer from incorrect owner" | testTransferAndCheckNewOwner | PASS |
| "Transfer to the zero/burn address" | testTransferFromBy0 | PASS | |
| "Transfer from the zero/burn address" | testTransferFromBy0 | PASS | |
| "Ticket does not exist" | testTicketDoesNotExist | PASS | |
| "Caller is not owner nor approved" | testStealTransferWithoutOwnership | PASS | |
| approve | "Caller is not ticket owner" | testApproveAndGetApproved | PASS |
| "Ticket does not exist" | testTicketDoesNotExist | PASS | |
| getApproved | "Ticket does not exist" | testTicketDoesNotExist | PASS |
| updateHolderName | "Ticket does not exist" | testTicketDoesNotExist | PASS |
| "Only ticket holder can update name" | testUpdateHolderName | PASS | |
| setUsed | "Ticket does not exist" | testTicketDoesNotExist | PASS |
| "Only creator can set ticket as used" | testSetUsedAndIsExpiredOrUsed | PASS | |
| "Ticket already used" | testSetUsedAndIsExpiredOrUsed | PASS | |
| "Event TicketNFT expired" | testSetUsedAndIsExpiredOrUsed | PASS | |
| isExpiredOrUsed | "Ticket does not exist" | testTicketDoesNotExist | PASS |
| holderNameOf | "Ticket does not exist" | testTicketDoesNotExist | PASS |
Principles of Distributed Ledgers: Smart Contract Coursework for 2023, Imperial College London.