+
+ Branch data Line data Source code
+
+ 1 : : // SPDX-License-Identifier: UNLICENSED
+ 2 : : pragma solidity ^0.8.10;
+ 3 : :
+ 4 : : import "forge-std/Test.sol";
+ 5 : : import "../src/contracts/PurchaseToken.sol";
+ 6 : : import "../src/contracts/TicketNFT.sol";
+ 7 : :
+ 8 : : contract BaseTicketNFTTest is Test {
+ 9 : : // TicketNFT Events
+ 10 : : event Transfer(
+ 11 : : address indexed from,
+ 12 : : address indexed to,
+ 13 : : uint256 indexed ticketID
+ 14 : : );
+ 15 : : event Approval(
+ 16 : : address indexed holder,
+ 17 : : address indexed approved,
+ 18 : : uint256 indexed ticketID
+ 19 : : );
+ 20 : :
+ 21 : : // Primary Market Events
+ 22 : : event Purchase(address indexed holder, string indexed holderName);
+ 23 : :
+ 24 : : // Secondary Market Events
+ 25 : : event Listing(
+ 26 : : uint256 indexed ticketID,
+ 27 : : address indexed holder,
+ 28 : : uint256 price
+ 29 : : );
+ 30 : : event Purchase(
+ 31 : : address indexed purchaser,
+ 32 : : uint256 indexed ticketID,
+ 33 : : uint256 price,
+ 34 : : string newName
+ 35 : : );
+ 36 : : event Delisting(uint256 indexed ticketID);
+ 37 : :
+ 38 : : uint256 public ticketPriceEth = 1e18;
+ 39 : : uint256 public ticketPrice = 100e18;
+ 40 : :
+ 41 : : PurchaseToken public token;
+ 42 : : TicketNFT public nft;
+ 43 : :
+ 44 : : address public alice = makeAddr("alice");
+ 45 : : address public bob = makeAddr("bob");
+ 46 : : address public charlie = makeAddr("charlie");
+ 47 : :
+ 48 : : function setUp() public {
+ 49 : 0 : token = new PurchaseToken();
+ 50 : 0 : nft = new TicketNFT(token);
+ 51 : : }
+ 52 : :
+ 53 : : // Add Purchase Tokens to `recipient` to afford `amount` tickets
+ 54 : : function _topUpTokens(address recipient, uint256 amount) internal {
+ 55 : 0 : vm.deal(recipient, amount * ticketPriceEth);
+ 56 : 0 : vm.prank(recipient);
+ 57 : 0 : token.mint{value: amount * ticketPriceEth}();
+ 58 : : }
+ 59 : :
+ 60 : : function _buyTicket(address recipient, string memory recipientName)
+ 61 : : internal
+ 62 : : {
+ 63 : 0 : _topUpTokens(recipient, 1);
+ 64 : 0 : vm.prank(recipient);
+ 65 : 0 : token.approve(address(nft), ticketPrice);
+ 66 : 0 : vm.prank(recipient);
+ 67 : 0 : nft.purchase(recipientName);
+ 68 : : }
+ 69 : : }
+ 70 : :
+ 71 : : contract TicketNFTTest is BaseTicketNFTTest {
+ 72 : : function testMint() public {
+ 73 : : vm.prank(address(nft));
+ 74 : : vm.expectEmit(true, true, true, false);
+ 75 : : emit Transfer(address(0), alice, 1);
+ 76 : : nft.mint(alice, "alice");
+ 77 : : assertEq(nft.balanceOf(alice), 1);
+ 78 : : assertEq(nft.holderOf(1), alice);
+ 79 : : assertEq(nft.getApproved(1), address(0));
+ 80 : : assertEq(nft.holderNameOf(1), "alice");
+ 81 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 82 : : }
+ 83 : :
+ 84 : : function testMintUnauthorized() public {
+ 85 : : _topUpTokens(alice, 1);
+ 86 : : vm.prank(alice);
+ 87 : : vm.expectRevert("mint: can only be called by primary market");
+ 88 : : nft.mint(alice, "alice");
+ 89 : : assertEq(nft.balanceOf(alice), 0);
+ 90 : : }
+ 91 : :
+ 92 : : function testBalanceOf() public {
+ 93 : : assertEq(nft.balanceOf(alice), 0);
+ 94 : : _buyTicket(alice, "alice");
+ 95 : : assertEq(nft.balanceOf(alice), 1);
+ 96 : : }
+ 97 : :
+ 98 : : function testHolderOf() public {
+ 99 : : _buyTicket(alice, "alice");
+ 100 : : assertEq(nft.holderOf(1), alice);
+ 101 : : vm.expectRevert("holderOf: ticket doesn't exist");
+ 102 : : nft.holderOf(2);
+ 103 : : }
+ 104 : :
+ 105 : : function testHolderNameOf() public {
+ 106 : : _buyTicket(alice, "alice");
+ 107 : : assertEq(nft.holderNameOf(1), "alice");
+ 108 : : vm.expectRevert("holderNameOf: ticket doesn't exist");
+ 109 : : nft.holderNameOf(2);
+ 110 : : }
+ 111 : :
+ 112 : : function testUpdateHolderName() public {
+ 113 : : _buyTicket(alice, "alice1");
+ 114 : : assertEq(nft.holderNameOf(1), "alice1");
+ 115 : : vm.prank(alice);
+ 116 : : nft.updateHolderName(1, "alice2");
+ 117 : : assertEq(nft.holderNameOf(1), "alice2");
+ 118 : : }
+ 119 : :
+ 120 : : function testUpdateHolderNameInvalid() public {
+ 121 : : _buyTicket(alice, "alice1");
+ 122 : : assertEq(nft.holderNameOf(1), "alice1");
+ 123 : : vm.prank(alice);
+ 124 : : vm.expectRevert("updateHolderName: ticket doesn't exist");
+ 125 : : nft.updateHolderName(2, "alice2");
+ 126 : : }
+ 127 : :
+ 128 : : function testUpdateHolderNameUnauthorized() public {
+ 129 : : _buyTicket(alice, "alice");
+ 130 : : assertEq(nft.holderNameOf(1), "alice");
+ 131 : : vm.prank(bob);
+ 132 : : vm.expectRevert("updateHolderName: msg.sender doesn't own ticket");
+ 133 : : nft.updateHolderName(1, "bob");
+ 134 : : assertEq(nft.holderNameOf(1), "alice");
+ 135 : : }
+ 136 : :
+ 137 : : function testTransfer() public {
+ 138 : : _buyTicket(alice, "alice");
+ 139 : : vm.prank(alice);
+ 140 : : vm.expectEmit(true, true, true, false);
+ 141 : : emit Transfer(alice, bob, 1);
+ 142 : : vm.expectEmit(true, true, true, false);
+ 143 : : emit Approval(bob, address(0), 1);
+ 144 : : nft.transferFrom(alice, bob, 1);
+ 145 : : assertEq(nft.balanceOf(alice), 0);
+ 146 : : assertEq(nft.balanceOf(bob), 1);
+ 147 : : assertEq(nft.holderOf(1), bob);
+ 148 : : assertEq(nft.getApproved(1), address(0));
+ 149 : : assertEq(nft.holderNameOf(1), "alice");
+ 150 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 151 : : }
+ 152 : :
+ 153 : : function testTransferInvalid() public {
+ 154 : : _buyTicket(alice, "alice");
+ 155 : : vm.prank(alice);
+ 156 : : vm.expectRevert("transferFrom: from cannot be 0");
+ 157 : : nft.transferFrom(address(0), bob, 2);
+ 158 : : vm.prank(alice);
+ 159 : : vm.expectRevert("transferFrom: to cannot be 0");
+ 160 : : nft.transferFrom(alice, address(0), 2);
+ 161 : : }
+ 162 : :
+ 163 : : function testTransferUnauthorized() public {
+ 164 : : _buyTicket(alice, "alice");
+ 165 : : vm.prank(bob);
+ 166 : : vm.expectRevert(
+ 167 : : "transferFrom: msg.sender must be current holder or approved sender"
+ 168 : : );
+ 169 : : nft.transferFrom(alice, bob, 1);
+ 170 : : }
+ 171 : :
+ 172 : : function testApproval() public {
+ 173 : : _buyTicket(alice, "alice");
+ 174 : : vm.prank(alice);
+ 175 : : vm.expectEmit(true, true, true, false);
+ 176 : : emit Approval(alice, bob, 1);
+ 177 : : nft.approve(bob, 1);
+ 178 : : assertEq(nft.getApproved(1), bob);
+ 179 : : }
+ 180 : :
+ 181 : : function testApprovalInvalid() public {
+ 182 : : _buyTicket(alice, "alice");
+ 183 : : vm.prank(alice);
+ 184 : : vm.expectRevert("approve: ticket doesn't exist");
+ 185 : : nft.approve(bob, 2);
+ 186 : : }
+ 187 : :
+ 188 : : function testApprovalUnauthorized() public {
+ 189 : : _buyTicket(alice, "alice");
+ 190 : : vm.prank(bob);
+ 191 : : vm.expectRevert("approve: msg.sender doesn't own ticket");
+ 192 : : nft.approve(bob, 1);
+ 193 : : assertEq(nft.getApproved(1), address(0));
+ 194 : : }
+ 195 : :
+ 196 : : function testGetApprovedInvalid() public {
+ 197 : : vm.expectRevert("getApproved: ticket doesn't exist");
+ 198 : : nft.getApproved(1);
+ 199 : : }
+ 200 : :
+ 201 : : function testTransferFrom() public {
+ 202 : : _buyTicket(alice, "alice");
+ 203 : : vm.prank(alice);
+ 204 : : nft.approve(bob, 1);
+ 205 : : vm.prank(bob);
+ 206 : : vm.expectEmit(true, true, true, false);
+ 207 : : emit Transfer(alice, charlie, 1);
+ 208 : : vm.expectEmit(true, true, true, false);
+ 209 : : emit Approval(charlie, address(0), 1);
+ 210 : : nft.transferFrom(alice, charlie, 1);
+ 211 : : assertEq(nft.balanceOf(alice), 0);
+ 212 : : assertEq(nft.balanceOf(bob), 0);
+ 213 : : assertEq(nft.balanceOf(charlie), 1);
+ 214 : : assertEq(nft.holderOf(1), charlie);
+ 215 : : assertEq(nft.getApproved(1), address(0));
+ 216 : : }
+ 217 : :
+ 218 : : function testTransferFromInvalid() public {
+ 219 : : _buyTicket(alice, "alice");
+ 220 : : vm.prank(alice);
+ 221 : : nft.approve(bob, 1);
+ 222 : : assertEq(nft.getApproved(1), bob);
+ 223 : : vm.prank(bob);
+ 224 : : vm.expectRevert("transferFrom: from cannot be 0");
+ 225 : : nft.transferFrom(address(0), charlie, 1);
+ 226 : : vm.prank(bob);
+ 227 : : vm.expectRevert("transferFrom: to cannot be 0");
+ 228 : : nft.transferFrom(alice, address(0), 1);
+ 229 : : vm.prank(bob);
+ 230 : : vm.expectRevert("transferFrom: ticket not owned by from");
+ 231 : : nft.transferFrom(bob, charlie, 1);
+ 232 : : vm.prank(bob);
+ 233 : : vm.expectRevert("transferFrom: ticket not owned by from");
+ 234 : : nft.transferFrom(charlie, bob, 1);
+ 235 : : assertEq(nft.balanceOf(alice), 1);
+ 236 : : assertEq(nft.balanceOf(bob), 0);
+ 237 : : assertEq(nft.balanceOf(charlie), 0);
+ 238 : : assertEq(nft.holderOf(1), alice);
+ 239 : : assertEq(nft.getApproved(1), bob);
+ 240 : : }
+ 241 : :
+ 242 : : function testTransferFromUnauthorized() public {
+ 243 : : _buyTicket(alice, "alice");
+ 244 : : vm.prank(alice);
+ 245 : : nft.approve(bob, 1);
+ 246 : : assertEq(nft.getApproved(1), bob);
+ 247 : : vm.prank(charlie);
+ 248 : : vm.expectRevert(
+ 249 : : "transferFrom: msg.sender must be current holder or approved sender"
+ 250 : : );
+ 251 : : nft.transferFrom(alice, charlie, 1);
+ 252 : : assertEq(nft.balanceOf(alice), 1);
+ 253 : : assertEq(nft.balanceOf(bob), 0);
+ 254 : : assertEq(nft.balanceOf(charlie), 0);
+ 255 : : assertEq(nft.holderOf(1), alice);
+ 256 : : assertEq(nft.getApproved(1), bob);
+ 257 : : }
+ 258 : :
+ 259 : : function testIsExpiredOrUsedInvalid() public {
+ 260 : : vm.expectRevert("isExpiredOrUsed: ticket doesn't exist");
+ 261 : : nft.isExpiredOrUsed(1);
+ 262 : : }
+ 263 : :
+ 264 : : function testExpiry() public {
+ 265 : : _buyTicket(alice, "alice");
+ 266 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 267 : : vm.warp(block.timestamp + 10 days);
+ 268 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 269 : : vm.warp(block.timestamp + 1);
+ 270 : : assertEq(nft.isExpiredOrUsed(1), true);
+ 271 : : }
+ 272 : :
+ 273 : : function testSetUsed() public {
+ 274 : : _buyTicket(alice, "alice");
+ 275 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 276 : : vm.prank(address(this));
+ 277 : : nft.setUsed(1);
+ 278 : : assertEq(nft.isExpiredOrUsed(1), true);
+ 279 : : }
+ 280 : :
+ 281 : : function testSetUsedNonExistent() public {
+ 282 : : vm.expectRevert("setUsed: ticket doesn't exist");
+ 283 : : nft.setUsed(1);
+ 284 : : }
+ 285 : :
+ 286 : : function testSetUsedAlreadyUsed() public {
+ 287 : : _buyTicket(alice, "alice");
+ 288 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 289 : : vm.prank(address(this));
+ 290 : : nft.setUsed(1);
+ 291 : : assertEq(nft.isExpiredOrUsed(1), true);
+ 292 : : vm.prank(address(this));
+ 293 : : vm.expectRevert("setUsed: ticket already used");
+ 294 : : nft.setUsed(1);
+ 295 : : }
+ 296 : :
+ 297 : : function testSetUsedExpired() public {
+ 298 : : _buyTicket(alice, "alice");
+ 299 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 300 : : vm.warp(block.timestamp + 10 days + 1);
+ 301 : : vm.prank(address(this));
+ 302 : : vm.expectRevert("setUsed: ticket expired");
+ 303 : : nft.setUsed(1);
+ 304 : : }
+ 305 : :
+ 306 : : function testSetUsedUnauthorized() public {
+ 307 : : _buyTicket(alice, "alice");
+ 308 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 309 : : vm.warp(block.timestamp + 10 days);
+ 310 : : vm.prank(bob);
+ 311 : : vm.expectRevert("setUsed: only admin can setUsed");
+ 312 : : nft.setUsed(1);
+ 313 : : }
+ 314 : : }
+ 315 : :
+ 316 : : contract PrimaryMarketTest is BaseTicketNFTTest {
+ 317 : : function testAdmin() public {
+ 318 : : assertEq(address(this), nft.admin());
+ 319 : : }
+ 320 : :
+ 321 : : function testPrimaryPurchase() public {
+ 322 : : _topUpTokens(alice, 1);
+ 323 : : vm.prank(alice);
+ 324 : : token.approve(address(nft), ticketPrice);
+ 325 : : vm.prank(alice);
+ 326 : : vm.expectEmit(true, true, true, false);
+ 327 : : emit Transfer(address(0), alice, 1);
+ 328 : : vm.expectEmit(true, true, false, false);
+ 329 : : emit Purchase(alice, "alice");
+ 330 : : nft.purchase("alice");
+ 331 : : assertEq(nft.balanceOf(alice), 1);
+ 332 : : assertEq(nft.holderOf(1), alice);
+ 333 : : assertEq(nft.getApproved(1), address(0));
+ 334 : : assertEq(nft.holderNameOf(1), "alice");
+ 335 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 336 : : }
+ 337 : :
+ 338 : : function testPrimaryPurchaseInsufficientBalance() public {
+ 339 : : vm.deal(alice, ticketPriceEth);
+ 340 : : vm.prank(alice);
+ 341 : : token.mint{value: ticketPriceEth - 1}();
+ 342 : : vm.prank(alice);
+ 343 : : token.approve(address(nft), ticketPrice);
+ 344 : : vm.prank(alice);
+ 345 : : vm.expectRevert("ERC20: transfer amount exceeds balance");
+ 346 : : nft.purchase("alice");
+ 347 : : assertEq(nft.balanceOf(alice), 0);
+ 348 : : }
+ 349 : :
+ 350 : : function testPrimaryPurchaseInsufficientAllowance() public {
+ 351 : : vm.deal(alice, ticketPriceEth);
+ 352 : : vm.prank(alice);
+ 353 : : token.mint{value: ticketPriceEth}();
+ 354 : : vm.prank(alice);
+ 355 : : token.approve(address(nft), ticketPrice - 1);
+ 356 : : vm.prank(alice);
+ 357 : : vm.expectRevert("purchase: insufficient token allowance");
+ 358 : : nft.purchase("alice");
+ 359 : : assertEq(nft.balanceOf(alice), 0);
+ 360 : : }
+ 361 : :
+ 362 : : function testPrimaryPurchases() public {
+ 363 : : _topUpTokens(alice, 2);
+ 364 : : vm.prank(alice);
+ 365 : : token.approve(address(nft), ticketPrice * 2);
+ 366 : : vm.prank(alice);
+ 367 : : nft.purchase("alice");
+ 368 : : vm.prank(alice);
+ 369 : : vm.expectEmit(true, true, true, false);
+ 370 : : emit Transfer(address(0), alice, 2);
+ 371 : : vm.expectEmit(true, true, false, false);
+ 372 : : emit Purchase(alice, "alice's plus one");
+ 373 : : nft.purchase("alice's plus one");
+ 374 : : assertEq(nft.balanceOf(alice), 2);
+ 375 : : assertEq(nft.holderOf(2), alice);
+ 376 : : assertEq(nft.getApproved(2), address(0));
+ 377 : : assertEq(nft.holderNameOf(2), "alice's plus one");
+ 378 : : assertEq(nft.isExpiredOrUsed(2), false);
+ 379 : : }
+ 380 : :
+ 381 : : function testPurchaseLimit() public {
+ 382 : : // Set contract totalSupply to 999 using foundry cheats rather than minting repeatedly
+ 383 : : vm.store(address(nft), bytes32(uint256(1)), bytes32(uint256(999)));
+ 384 : : // for (uint256 i = 0; i < 999; i++) {
+ 385 : : // _buyTicket(alice, "alice");
+ 386 : : // }
+ 387 : : _buyTicket(alice, "alice"); // Buy 1000th ticket
+ 388 : : _topUpTokens(alice, 1);
+ 389 : : vm.prank(alice);
+ 390 : : token.approve(address(nft), ticketPrice);
+ 391 : : vm.prank(alice);
+ 392 : : vm.expectRevert("purchase: maximum tickets reached");
+ 393 : : nft.purchase("alice");
+ 394 : : }
+ 395 : : }
+ 396 : :
+ 397 : : contract SecondaryMarketTest is BaseTicketNFTTest {
+ 398 : : function testList() public {
+ 399 : : _buyTicket(alice, "alice");
+ 400 : : vm.prank(alice);
+ 401 : : vm.expectEmit(true, true, true, false);
+ 402 : : emit Transfer(alice, address(nft), 1);
+ 403 : : vm.expectEmit(true, true, true, false);
+ 404 : : emit Approval(address(nft), address(0), 1);
+ 405 : : vm.expectEmit(true, true, false, false);
+ 406 : : emit Listing(1, alice, 200e18);
+ 407 : : nft.listTicket(1, 200e18); // Got a flipper here :)
+ 408 : : assertEq(nft.balanceOf(alice), 0);
+ 409 : : assertEq(nft.balanceOf(address(nft)), 1);
+ 410 : : assertEq(nft.holderOf(1), address(nft));
+ 411 : : assertEq(nft.getApproved(1), address(0));
+ 412 : : assertEq(nft.holderNameOf(1), "alice");
+ 413 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 414 : : }
+ 415 : :
+ 416 : : function testListFree() public {
+ 417 : : _buyTicket(alice, "alice");
+ 418 : : vm.prank(alice);
+ 419 : : vm.expectRevert("listTicket: price cannot be 0");
+ 420 : : nft.listTicket(1, 0); // What's the opposite of a flipper?
+ 421 : : assertEq(nft.balanceOf(alice), 1);
+ 422 : : assertEq(nft.holderOf(1), alice);
+ 423 : : }
+ 424 : :
+ 425 : : function testListUsed() public {
+ 426 : : _buyTicket(alice, "alice");
+ 427 : : vm.prank(address(this));
+ 428 : : nft.setUsed(1);
+ 429 : : vm.prank(alice);
+ 430 : : vm.expectRevert("listTicket: ticket is expired/used");
+ 431 : : nft.listTicket(1, 200e19);
+ 432 : : assertEq(nft.balanceOf(alice), 1);
+ 433 : : assertEq(nft.holderOf(1), alice);
+ 434 : : assertEq(nft.isExpiredOrUsed(1), true);
+ 435 : : }
+ 436 : :
+ 437 : : function testListExpired() public {
+ 438 : : _buyTicket(alice, "alice");
+ 439 : : vm.warp(block.timestamp + 10 days + 1);
+ 440 : : vm.prank(alice);
+ 441 : : vm.expectRevert("listTicket: ticket is expired/used");
+ 442 : : nft.listTicket(1, 200e19);
+ 443 : : assertEq(nft.balanceOf(alice), 1);
+ 444 : : assertEq(nft.holderOf(1), alice);
+ 445 : : assertEq(nft.isExpiredOrUsed(1), true);
+ 446 : : }
+ 447 : :
+ 448 : : function testListUnauthorized() public {
+ 449 : : _buyTicket(alice, "alice");
+ 450 : : vm.prank(bob);
+ 451 : : vm.expectRevert("listTicket: msg.sender doesn't own ticket");
+ 452 : : nft.listTicket(1, 200e19);
+ 453 : : assertEq(nft.balanceOf(alice), 1);
+ 454 : : assertEq(nft.holderOf(1), alice);
+ 455 : : }
+ 456 : :
+ 457 : : function testDelist() public {
+ 458 : : _buyTicket(alice, "alice");
+ 459 : : vm.prank(alice);
+ 460 : : nft.listTicket(1, 200e18);
+ 461 : : vm.prank(alice);
+ 462 : : vm.expectEmit(true, true, true, false);
+ 463 : : emit Transfer(address(nft), alice, 1);
+ 464 : : vm.expectEmit(true, true, true, false);
+ 465 : : emit Approval(alice, address(0), 1);
+ 466 : : vm.expectEmit(true, false, false, false);
+ 467 : : emit Delisting(1);
+ 468 : : nft.delistTicket(1);
+ 469 : : assertEq(nft.balanceOf(alice), 1);
+ 470 : : assertEq(nft.balanceOf(address(nft)), 0);
+ 471 : : assertEq(nft.holderOf(1), alice);
+ 472 : : }
+ 473 : :
+ 474 : : function testDelistUnauthorized() public {
+ 475 : : _buyTicket(alice, "alice");
+ 476 : : vm.prank(alice);
+ 477 : : nft.listTicket(1, 200e18);
+ 478 : : vm.prank(bob);
+ 479 : : vm.expectRevert("delistTicket: msg.sender didn't list ticket");
+ 480 : : nft.delistTicket(1);
+ 481 : : assertEq(nft.balanceOf(alice), 0);
+ 482 : : assertEq(nft.balanceOf(bob), 0);
+ 483 : : assertEq(nft.balanceOf(address(nft)), 1);
+ 484 : : assertEq(nft.holderOf(1), address(nft));
+ 485 : : }
+ 486 : :
+ 487 : : function testPurchase() public {
+ 488 : : uint256 ticketResalePrice = 200e18;
+ 489 : : uint256 ticketResaleFee = 50 * (ticketResalePrice / 1000);
+ 490 : : uint256 ticketResaleRevenue = ticketResalePrice - ticketResaleFee;
+ 491 : : _buyTicket(alice, "alice");
+ 492 : : vm.prank(alice);
+ 493 : : nft.listTicket(1, ticketResalePrice);
+ 494 : : _topUpTokens(bob, ticketResalePrice / 100e18);
+ 495 : : vm.prank(bob);
+ 496 : : token.approve(address(nft), ticketResalePrice);
+ 497 : : vm.prank(bob);
+ 498 : : vm.expectEmit(true, true, false, true);
+ 499 : : emit Purchase(bob, 1, ticketResalePrice, "bob");
+ 500 : : nft.purchase(1, "bob");
+ 501 : : assertEq(token.balanceOf(alice), ticketResaleRevenue);
+ 502 : : assertEq(token.balanceOf(bob), 0);
+ 503 : : assertEq(token.balanceOf(address(this)), ticketPrice + ticketResaleFee);
+ 504 : : assertEq(nft.balanceOf(alice), 0);
+ 505 : : assertEq(nft.balanceOf(bob), 1);
+ 506 : : assertEq(nft.balanceOf(address(nft)), 0);
+ 507 : : assertEq(nft.holderOf(1), bob);
+ 508 : : assertEq(nft.getApproved(1), address(0));
+ 509 : : assertEq(nft.holderNameOf(1), "bob");
+ 510 : : assertEq(nft.isExpiredOrUsed(1), false);
+ 511 : : }
+ 512 : :
+ 513 : : function testPurchaseUsed() public {
+ 514 : : _buyTicket(alice, "alice");
+ 515 : : vm.prank(alice);
+ 516 : : nft.listTicket(1, 200e18);
+ 517 : : _topUpTokens(bob, 2);
+ 518 : : vm.prank(address(this));
+ 519 : : nft.setUsed(1);
+ 520 : : vm.prank(bob);
+ 521 : : token.approve(address(nft), 200e18);
+ 522 : : vm.prank(bob);
+ 523 : : vm.expectRevert("purchase: ticket is expired/used");
+ 524 : : nft.purchase(1, "bob");
+ 525 : : assertEq(nft.balanceOf(alice), 0);
+ 526 : : assertEq(nft.balanceOf(bob), 0);
+ 527 : : assertEq(nft.balanceOf(address(nft)), 1);
+ 528 : : }
+ 529 : :
+ 530 : : function testPurchaseExpired() public {
+ 531 : : _buyTicket(alice, "alice");
+ 532 : : vm.prank(alice);
+ 533 : : nft.listTicket(1, 200e18);
+ 534 : : _topUpTokens(bob, 2);
+ 535 : : vm.warp(block.timestamp + 10 days + 1);
+ 536 : : vm.prank(bob);
+ 537 : : token.approve(address(nft), 200e18);
+ 538 : : vm.prank(bob);
+ 539 : : vm.expectRevert("purchase: ticket is expired/used");
+ 540 : : nft.purchase(1, "bob");
+ 541 : : assertEq(nft.balanceOf(alice), 0);
+ 542 : : assertEq(nft.balanceOf(bob), 0);
+ 543 : : assertEq(nft.balanceOf(address(nft)), 1);
+ 544 : : }
+ 545 : :
+ 546 : : function testPurchaseInsufficientBalance() public {
+ 547 : : _buyTicket(alice, "alice");
+ 548 : : vm.prank(alice);
+ 549 : : nft.listTicket(1, 200e18);
+ 550 : : _topUpTokens(bob, 1);
+ 551 : : vm.prank(bob);
+ 552 : : token.approve(address(nft), 200e18);
+ 553 : : vm.prank(bob);
+ 554 : : vm.expectRevert("ERC20: transfer amount exceeds balance");
+ 555 : : nft.purchase(1, "bob");
+ 556 : : assertEq(nft.balanceOf(alice), 0);
+ 557 : : assertEq(nft.balanceOf(bob), 0);
+ 558 : : assertEq(nft.balanceOf(address(nft)), 1);
+ 559 : : }
+ 560 : :
+ 561 : : function testPurchaseInsufficientAllowance() public {
+ 562 : : _buyTicket(alice, "alice");
+ 563 : : vm.prank(alice);
+ 564 : : nft.listTicket(1, 200e18);
+ 565 : : _topUpTokens(bob, 2);
+ 566 : : vm.prank(bob);
+ 567 : : token.approve(address(nft), 100e18);
+ 568 : : vm.prank(bob);
+ 569 : : vm.expectRevert("purchase: insufficient token allowance");
+ 570 : : nft.purchase(1, "bob");
+ 571 : : assertEq(nft.balanceOf(alice), 0);
+ 572 : : assertEq(nft.balanceOf(bob), 0);
+ 573 : : assertEq(nft.balanceOf(address(nft)), 1);
+ 574 : : }
+ 575 : :
+ 576 : : function testPurchaseUnlisted() public {
+ 577 : : _buyTicket(alice, "alice");
+ 578 : : vm.prank(alice);
+ 579 : : nft.listTicket(1, 200e18);
+ 580 : : vm.prank(alice);
+ 581 : : nft.delistTicket(1);
+ 582 : : _topUpTokens(bob, 2);
+ 583 : : vm.prank(bob);
+ 584 : : token.approve(address(nft), 200e18);
+ 585 : : vm.prank(bob);
+ 586 : : vm.expectRevert(
+ 587 : : "purchase: ticket is not listed, missing lister address or price"
+ 588 : : );
+ 589 : : nft.purchase(1, "bob");
+ 590 : : assertEq(nft.balanceOf(alice), 1);
+ 591 : : assertEq(nft.balanceOf(bob), 0);
+ 592 : : assertEq(nft.balanceOf(address(nft)), 0);
+ 593 : : }
+ 594 : : }
+
+ |
+
+