diff --git a/contracts/LottFund.sol b/contracts/LottFund.sol index 25de0e6..e9a187d 100644 --- a/contracts/LottFund.sol +++ b/contracts/LottFund.sol @@ -75,7 +75,7 @@ contract LottFund is VRFConsumerBaseV2Plus, ILottFund, AddressProviderResolver, error LottFund__BiddingNotFinished(); error LottFund__TokenBidAmountDepleted(); error LottFund__TokenCannotBeBidded(); - error LottFund__AddressHasBiddedTooManyTimes(); + error LottFund__AddressHasBiddedTooManyTimes(address caller); constructor( address addressProvider, @@ -121,6 +121,9 @@ contract LottFund is VRFConsumerBaseV2Plus, ILottFund, AddressProviderResolver, } function bid(uint256 tokenId) public whenNotPaused nonReentrant { + if (bidCountPerRound[currentRound][msg.sender] >= maxBidsPerAddress) { + revert LottFund__AddressHasBiddedTooManyTimes(msg.sender); + } ITraitForgeNft traitForgeNft = _getTraitForgeNft(); if (traitForgeNft.ownerOf(tokenId) != msg.sender) revert LottFund__CallerNotTokenOwner(); if ( @@ -150,7 +153,7 @@ contract LottFund is VRFConsumerBaseV2Plus, ILottFund, AddressProviderResolver, uint256 tokenId = tokenIds[i]; if (bidCountPerRound[currentRound][sender] >= maxBidsPerAddress) { - revert LottFund__AddressHasBiddedTooManyTimes(); + revert LottFund__AddressHasBiddedTooManyTimes(sender); } if (traitForgeNft.ownerOf(tokenId) != sender) { revert LottFund__CallerNotTokenOwner(); @@ -163,7 +166,7 @@ contract LottFund is VRFConsumerBaseV2Plus, ILottFund, AddressProviderResolver, ) { revert LottFund__ContractNotApproved(); } - canTokenBeBidded(tokenId); + if (!canTokenBeBidded(tokenId)) revert LottFund__TokenCannotBeBidded(); bidCountPerRound[currentRound][sender]++; bidsAmount++; tokenIdsBidded.push(tokenId); @@ -355,7 +358,7 @@ contract LottFund is VRFConsumerBaseV2Plus, ILottFund, AddressProviderResolver, revert LottFund__BiddingNotFinished(); } requestRandomWords(nativePayment); - // uint256[] memory tokensToWin = new uint256[](quantityToWin); //memory to stre the tokens to be burnt + // uint256[] memory tokensToWin = new uint256[](quantityToWin); //memory to stre the tokens to be burnt for (uint256 i = 1; i <= quantityToWin; i++) { // A for loop incase we want to add multiple winners later uint256 winnerIndex = _randomWords[0] % tokenIdsBidded.length; // get the index of the array of tokenIds diff --git a/test/integration/concrete/lottFund/BasicTests.t.sol b/test/integration/concrete/lottFund/BasicTests.t.sol deleted file mode 100644 index 914a7ec..0000000 --- a/test/integration/concrete/lottFund/BasicTests.t.sol +++ /dev/null @@ -1,156 +0,0 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.20; - -// import "forge-std/Test.sol"; -// import "../src/LottFund.sol"; -// import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -// import "@chainlink/contracts/src/v0.8/mocks/VRFCoordinatorV2Mock.sol"; - -// contract LottFundTest is Test { -// LottFund lottFund; -// address nukeFund = address(0x123); -// address lottFundAddr = address(0x456); -// address ethCollector = address(0x789); -// address owner = address(this); -// address user1 = address(0x1111); -// address user2 = address(0x2222); -// uint256 subscriptionId = 1; -// VRFCoordinatorV2Mock vrfCoordinator; - -// function setUp() public { -// // Deploying the mocked VRFCoordinator for testing -// vrfCoordinator = new VRFCoordinatorV2Mock(0.1 ether, 0.1 ether); -// // Deploy the contract with the mocked VRFCoordinator -// lottFund = new LottFund( -// address(vrfCoordinator), -// ethCollector, -// nukeFund, -// subscriptionId -// ); -// } - -// function testConstructorRevertsWithZeroAddress() public { -// vm.expectRevert("NukeFund address cannot be zero"); -// new LottFund( -// address(vrfCoordinator), -// ethCollector, -// address(0), -// subscriptionId -// ); -// } - -// function testConstructorInitializesProperly() public { -// assertEq(lottFund.nukeFundAddress(), nukeFund); -// assertEq(lottFund.getFundBalance(), 0); -// assertEq(lottFund.ethCollector(), ethCollector); -// } - -// function testBidRevertsIfNotTokenOwner() public { -// vm.prank(user2); -// vm.expectRevert(LottFund.LottFund__CallerNotTokenOwner.selector); -// lottFund.bid(1); -// } - -// function testBidIncreasesBidsAmount() public { -// // Assuming user1 owns a token and has approved the contract -// vm.prank(user1); -// lottFund.bid(1); - -// // Check that the bidsAmount increased -// assertEq(lottFund.bidsAmount(), 1); -// assertEq(lottFund.getTokenBidAmounts(1), 1); -// } - -// function testBidRevertsIfMaxBidsReached() public { -// // Simulate reaching the max bids amount -// for (uint256 i = 0; i < lottFund.maxBidAmount(); i++) { -// vm.prank(user1); -// lottFund.bid(i + 1); -// } - -// vm.prank(user1); -// vm.expectRevert(LottFund.LottFund__BiddingNotFinished.selector); -// lottFund.bid(100); -// } - -// function testReceiveFunction() public { -// vm.deal(user1, 10 ether); -// vm.prank(user1); -// (bool success, ) = address(lottFund).call{value: 1 ether}(""); -// assert(success); - -// uint256 devShare = (1 ether * lottFund.taxCut()) / lottFund.BPS(); -// assertEq(address(lottFund).balance, 1 ether - devShare); -// } - -// function testReceiveRevertsIfNoETH() public { -// vm.expectRevert(); -// vm.prank(user1); -// (bool success, ) = address(lottFund).call(""); -// assert(!success); -// } - -// function testPauseAndUnpause() public { -// lottFund.pause(); -// assertTrue(lottFund.paused()); - -// vm.expectRevert("Pausable: paused"); -// vm.prank(user1); -// lottFund.bid(1); - -// lottFund.unpause(); -// assertFalse(lottFund.paused()); -// } - -// function testSetNativePayment() public { -// lottFund.setNativePayment(false); -// assertFalse(lottFund.nativePayment()); - -// lottFund.setNativePayment(true); -// assertTrue(lottFund.nativePayment()); -// } - -// function testSetNativePaymentRevertsIfSameValue() public { -// vm.expectRevert("Native payment already set to this value"); -// lottFund.setNativePayment(true); -// } - -// function testRequestRandomWords() public { -// uint256 requestId = lottFund.requestRandomWords(true); -// assertEq(lottFund.lastRequestId(), requestId); - -// uint256; -// randomWords[0] = 42; -// vrfCoordinator.fulfillRandomWords(requestId, address(lottFund), randomWords); - -// (bool fulfilled, ) = lottFund.getRequestStatus(requestId); -// assertTrue(fulfilled); -// } - -// function testBurnTokens() public { -// uint256; -// for (uint256 i = 0; i < 5; i++) { -// tokenIds[i] = i + 1; -// } - -// lottFund.burnTokens(tokenIds); -// } - -// function testSetMaxBidAmount() public { -// uint256 newMax = 2000; -// lottFund.setMaxBidAmount(newMax); -// assertEq(lottFund.maxBidAmount(), newMax); -// } - -// function testSetMaxBidAmountRevertsIfNotMaintainer() public { -// vm.prank(user1); -// vm.expectRevert("Not authorized"); -// lottFund.setMaxBidAmount(2000); -// } - -// function testSetNukeFundAddress() public { -// address newAddress = address(0x987); -// lottFund.setNukeFundAddress(newAddress); -// assertEq(lottFund.nukeFundAddress(), newAddress); -// } -// } diff --git a/test/integration/concrete/lottFund/LottFundTest.t.sol b/test/integration/concrete/lottFund/LottFundTest.t.sol new file mode 100644 index 0000000..be6d907 --- /dev/null +++ b/test/integration/concrete/lottFund/LottFundTest.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import { Deploys } from "test/shared/Deploys.sol"; + +contract LottFundTest is Deploys { + address public user = makeAddr("user"); + + function setUp() public virtual override { + super.setUp(); + deal(user, 1_000_000 ether); + } +} \ No newline at end of file diff --git a/test/integration/concrete/lottFund/LottFundTest_Bid.t.sol b/test/integration/concrete/lottFund/LottFundTest_Bid.t.sol new file mode 100644 index 0000000..719a940 --- /dev/null +++ b/test/integration/concrete/lottFund/LottFundTest_Bid.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import { LottFundTest } from "test/integration/concrete/lottFund/LottFundTest.t.sol"; +import { LottFund } from "contracts/LottFund.sol"; + +contract LottFundTest_Bid is LottFundTest { + function testRevert_lottFund_bid_whenPaused() public { + vm.prank(_protocolMaintainer); + _lottFund.pause(); + + vm.expectRevert(bytes("Pausable: paused")); + vm.prank(_randomUser); + _lottFund.bid(1); + } + + function testRevert_lottFund_bid_whenCallerAddressHasBiddedTwoManyTimes() public { + uint256 maxBidsPerAddress = _lottFund.maxBidsPerAddress(); + _mintTraitForgeNft(user, 1000); + uint256 tokenIdWithMaxBidPotential; + for (uint256 i = 0; i < maxBidsPerAddress; i++) { + tokenIdWithMaxBidPotential = _getTheNthMaxBidPotentialNotZeroId(0, 1000, i + 1); + + vm.startPrank(user); + _traitForgeNft.approve(address(_lottFund), tokenIdWithMaxBidPotential); + _lottFund.bid(tokenIdWithMaxBidPotential); + vm.stopPrank(); + } + + tokenIdWithMaxBidPotential = _getTheNthMaxBidPotentialNotZeroId(0, 1000, maxBidsPerAddress + 1); + + vm.startPrank(user); + _traitForgeNft.approve(address(_lottFund), tokenIdWithMaxBidPotential); + vm.expectRevert(abi.encodeWithSelector(LottFund.LottFund__AddressHasBiddedTooManyTimes.selector, user)); + _lottFund.bid(tokenIdWithMaxBidPotential); + vm.stopPrank(); + } + + function testRevert_lottFund_bid_whenCallerNotTokenOwner() public { + address otherUser = makeAddr("otherUser"); + _mintTraitForgeNft(otherUser, 1); + + vm.expectRevert(LottFund.LottFund__CallerNotTokenOwner.selector); + vm.prank(user); + _lottFund.bid(1); + } + + function testRevert_lottFund_bid_whenContractNotApproved() public { + _mintTraitForgeNft(user, 1); + + vm.expectRevert(LottFund.LottFund__ContractNotApproved.selector); + vm.prank(user); + _lottFund.bid(1); + } + + function testRevert_lottFund_bid_whenMaxBidPotentialIsZero() public { + _mintTraitForgeNft(user, 100); + uint256 tokenIdWithMaxBidPotentialZero = _getTheNthMaxBidPotentialIsZeroId(0, 100, 1); + + vm.startPrank(user); + _traitForgeNft.approve(address(_lottFund), tokenIdWithMaxBidPotentialZero); + vm.expectRevert(LottFund.LottFund__TokenCannotBeBidded.selector); + _lottFund.bid(tokenIdWithMaxBidPotentialZero); + } + + function testRevert_lottFund_bid_whenTokenBidCountHigherThanPotential() public { + _mintTraitForgeNft(user, 100); + uint256 tokenIdWithMaxBidPotential = _getTheNthMaxBidPotentialNotZeroId(0, 100, 1); + uint256 tokenMaxBidPotential = _lottFund.getMaxBidPotential(tokenIdWithMaxBidPotential); + + vm.startPrank(user); + _traitForgeNft.approve(address(_lottFund), tokenIdWithMaxBidPotential); + for (uint256 i = 0; i < tokenMaxBidPotential; i++) { + _lottFund.bid(tokenIdWithMaxBidPotential); + } + + vm.expectRevert(LottFund.LottFund__TokenCannotBeBidded.selector); + _lottFund.bid(tokenIdWithMaxBidPotential); + } + + function test_lottFund_bid() public { + _mintTraitForgeNft(user, 100); + uint256 tokenIdWithMaxBidPotential = _getTheNthMaxBidPotentialNotZeroId(0, 100, 1); + + vm.startPrank(user); + _traitForgeNft.approve(address(_lottFund), tokenIdWithMaxBidPotential); + _lottFund.bid(tokenIdWithMaxBidPotential); + + assertEq(_lottFund.bidCountPerRound(_lottFund.currentRound(), user), 1); + assertEq(_lottFund.bidsAmount(), 1); + assertEq(_lottFund.tokenIdsBidded(0), tokenIdWithMaxBidPotential); + assertEq(_lottFund.tokenBidCount(tokenIdWithMaxBidPotential), 1); + } +} diff --git a/test/integration/concrete/lottFund/fundTesting.t.sol b/test/integration/concrete/lottFund/fundTesting.t.sol deleted file mode 100644 index 4c42581..0000000 --- a/test/integration/concrete/lottFund/fundTesting.t.sol +++ /dev/null @@ -1,126 +0,0 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.20; - -// import "forge-std/Test.sol"; -// import "../src/LottFund.sol"; -// import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -// contract LottFundTest is Test { -// LottFund lottFund; -// address nukeFundAddress = address(0x123); -// address devFundAddress = address(0x456); -// address daoFundAddress = address(0x789); -// address ethCollector = address(0xabc); -// address user = address(0x1111); -// address user2 = address(0x2222); - -// // State variables for tests -// uint256 public subscriptionId = 1; - -// function setUp() public { -// // Mock AddressProvider -// AddressProviderResolver addressProvider = new MockAddressProvider(devFundAddress, daoFundAddress, nukeFundAddress); - -// // Deploy LottFund -// lottFund = new LottFund( -// address(addressProvider), -// ethCollector, -// nukeFundAddress, -// subscriptionId -// ); - -// // Fund setup for testing -// vm.deal(user, 10 ether); // user has 10 ETH -// vm.deal(user2, 10 ether); // user2 has 10 ETH -// } - -// function testReceiveFundsCorrectDistribution() public { -// // Send 1 ETH to the contract from user1 -// uint256 amountSent = 1 ether; -// uint256 expectedDevShare = (amountSent * lottFund.taxCut()) / lottFund.BPS(); -// uint256 expectedRemainingFund = amountSent - expectedDevShare; - -// // Prank to simulate the user sending the ETH -// vm.prank(user); -// (bool success, ) = address(lottFund).call{value: amountSent}(""); -// assertTrue(success); - -// // Assert the funds are correctly split -// assertEq(address(lottFund).balance, expectedRemainingFund); - -// // Check if devShare was sent to devFundAddress -// assertEq(address(devFundAddress).balance, expectedDevShare); - -// // If the DAO fund should also receive funds, check its balance as well -// assertEq(address(daoFundAddress).balance, 0); // Assuming DAO Fund doesn't get share in this case -// } - -// function testEthCollectorGetsShareWhenDaoFundNotAllowed() public { -// // Assuming airdropContract.daoFundAllowed() returns false in this case -// uint256 amountSent = 1 ether; -// uint256 expectedDevShare = (amountSent * lottFund.taxCut()) / lottFund.BPS(); - -// // Ensure that the dev share is sent to ethCollector -// vm.prank(user); -// (bool success, ) = address(lottFund).call{value: amountSent}(""); -// assertTrue(success); - -// // Assert ethCollector received the correct amount -// assertEq(address(ethCollector).balance, expectedDevShare); -// } - -// function testNukeFundReceivesNothingIfNotAllowed() public { -// uint256 amountSent = 1 ether; - -// // Simulate user sending funds -// vm.prank(user); -// (bool success, ) = address(lottFund).call{value: amountSent}(""); -// assertTrue(success); - -// // Ensure that nukeFundAddress gets nothing (depending on the test case logic) -// assertEq(address(nukeFundAddress).balance, 0); -// } - -// function testFallbackFunctionCorrectDistribution() public { -// uint256 amountSent = 2 ether; -// uint256 expectedDevShare = (amountSent * lottFund.taxCut()) / lottFund.BPS(); -// uint256 expectedRemainingFund = amountSent - expectedDevShare; - -// // Use fallback to send ETH -// vm.prank(user); -// (bool success, ) = address(lottFund).call{value: amountSent}(""); -// assertTrue(success); - -// // Assert the funds are correctly split and balance is updated -// assertEq(address(lottFund).balance, expectedRemainingFund); -// assertEq(address(devFundAddress).balance, expectedDevShare); -// } - -// function testSetNukeFundAddress() public { -// address newNukeFund = address(0x999); - -// // Change the nuke fund address -// lottFund.setNukeFundAddress(newNukeFund); - -// // Verify the new address -// assertEq(lottFund.nukeFundAddress(), newNukeFund); -// } - -// function testReceiveZeroEthReverts() public { -// vm.expectRevert(); // Expect revert since no ETH is being sent -// vm.prank(user); -// (bool success, ) = address(lottFund).call{value: 0 ether}(""); -// assertFalse(success); // Should fail -// } - -// function testMaxAllowedClaimDivisor() public { -// uint256 amountSent = 5 ether; -// uint256 expectedClaimAmount = (amountSent / lottFund.maxAllowedClaimDivisor()); - -// vm.prank(user); -// (bool success, ) = address(lottFund).call{value: amountSent}(""); -// assertTrue(success); - -// assertEq(lottFund.getFundBalance(), expectedClaimAmount); -// } -// } diff --git a/test/shared/Deploys.sol b/test/shared/Deploys.sol index 5bf2ca6..0f50a9a 100644 --- a/test/shared/Deploys.sol +++ b/test/shared/Deploys.sol @@ -31,6 +31,8 @@ contract Deploys is Test { mapping(uint256 tokenId => bool) isTokenNoPotentialForger; mapping(uint256 tokenId => bool) isTokenNoPotentialMerger; mapping(uint256 tokenId => bool) isEMPToken; + mapping(uint256 tokenId => bool) isMaxBidPotentialZero; + mapping(uint256 tokenId => bool) isMaxBidPotentialNotZero; AddressProvider internal _addressProvider; Airdrop internal _airdrop; @@ -182,6 +184,11 @@ contract Deploys is Test { if (_traitForgeNft.getTokenEntropy(tokenId) % 10 == 7) { isEMPToken[tokenId] = true; } + if (_lottFund.getMaxBidPotential(tokenId) == 0) { + isMaxBidPotentialZero[tokenId] = true; + } else { + isMaxBidPotentialNotZero[tokenId] = true; + } vm.stopPrank(); } } @@ -245,4 +252,44 @@ contract Deploys is Test { } } } + + function _getTheNthMaxBidPotentialIsZeroId( + uint256 startIndex, + uint256 endIndex, + uint256 n + ) + internal + view + returns (uint256 theNthTokenId) + { + uint256 count = 0; + for (uint256 i = startIndex; i < endIndex; i++) { + if (isMaxBidPotentialZero[i + 1]) { + count++; + if (count == n) { + theNthTokenId = i + 1; + } + } + } + } + + function _getTheNthMaxBidPotentialNotZeroId( + uint256 startIndex, + uint256 endIndex, + uint256 n + ) + internal + view + returns (uint256 theNthTokenId) + { + uint256 count = 0; + for (uint256 i = startIndex; i < endIndex; i++) { + if (isMaxBidPotentialNotZero[i + 1]) { + count++; + if (count == n) { + theNthTokenId = i + 1; + } + } + } + } }