From 6ea33fabd8806e77cc5f18605171a109a9f235be Mon Sep 17 00:00:00 2001 From: TForge1 Date: Sun, 13 Oct 2024 16:09:21 +0930 Subject: [PATCH] new contract, router etc --- contracts/LottFund.sol | 389 ++++++++++++++++++ contracts/NukeRouter.sol | 72 ++++ contracts/interfaces/ILottFund.sol | 49 +++ .../concrete/lottFund/BasicTests.t.sol | 164 ++++++++ .../concrete/lottFund/fundTesting.t.sol | 126 ++++++ 5 files changed, 800 insertions(+) create mode 100644 contracts/LottFund.sol create mode 100644 contracts/NukeRouter.sol create mode 100644 contracts/interfaces/ILottFund.sol create mode 100644 test/integration/concrete/lottFund/BasicTests.t.sol create mode 100644 test/integration/concrete/lottFund/fundTesting.t.sol diff --git a/contracts/LottFund.sol b/contracts/LottFund.sol new file mode 100644 index 0000000..4824ec4 --- /dev/null +++ b/contracts/LottFund.sol @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import { Pausable } from "@openzeppelin/contracts/security/Pausable.sol"; +import { ILottFund } from "contracts/interfaces/INukeFund.sol"; +import { ITraitForgeNft } from "contracts/interfaces/ITraitForgeNft.sol"; +import { IAirdrop } from "contracts/interfaces/IAirdrop.sol"; +import { AddressProviderResolver } from "contracts/core/AddressProviderResolver.sol"; +import {VRFConsumerBaseV2Plus} from "@chainlink/contracts@1.2.0/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol"; +import {VRFV2PlusClient} from "@chainlink/contracts@1.2.0/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol"; + +contract LottFund is VRFConsumerBaseV2Plus, ILottFund, AddressProviderResolver, ReentrancyGuard, Pausable { + // Structs + struct RequestStatus { + bool fulfilled; // whether the request has been successfully fulfilled + bool exists; // whether a requestId exists + uint256[] randomWords; + } + + // subscription ID. + uint256 public s_subscriptionId; + + // request IDs + uint256[] public requestIds; + uint256 public lastRequestId; + + // State variables + address public nukeFundAddress; + uint256 public constant MAX_DENOMINATOR = 100_000; + uint256 private fund; + uint256 private constant BPS = 10_000; // denominator of basis points + uint256 public taxCut = 1500; //15% + uint256 public maxAllowedClaimDivisor = 2; + uint256 public nukeFactorMaxParam = MAX_DENOMINATOR / 2; + address public ethCollector; // fallback address for devrev + bool public pausedBids = false; + uint256 public maxBidAmount = 1500; + uint256 public amountToBeBurnt = 5; + uint256 public maxBidsPerAddress = 150 + uint256 public bidsAmount; + bool private nativePayment = true; + mapping(uint256 tokenId => uint256 bids) public tokenBidCount; + mapping(uint256 => RequestStatus) public s_requests; + uint256 public currentRound; + mapping(uint256 => mapping(address => uint256)) public bidCountPerRound; + + uint256[] public tokenIdsBidded; + + bytes32 public keyHash = 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae; + uint32 public callbackGasLimit = 100000; + + uint16 public requestConfirmations = 3; // The default is 3, but you can set this higher. + + uint32 public numWords = 6; // For this example, retrieve 2 random values in one request. // Cannot exceed VRFCoordinatorV2_5.MAX_NUM_WORDS. + + // Events + event RequestSent(uint256 requestId, uint32 numWords); + event RequestFulfilled(uint256 requestId, uint256[] randomWords); + + // Errors + error LottFund__TaxCutExceedsLimit(); + error LottFund__TokenOwnerIsAddressZero(); + error LottFund__CallerNotTokenOwner(); + error LottFund__ContractNotApproved(); + error LottFund__TokenNotMature(); + error LottFund__AddressIsZero(); + error LottFund__DivisorIsZero(); + error LottFund__BiddingNotFinished(); + error LottFund__TokenBidAmountDepleted(); + error LottFund__TokenHasNoBidPotential(); + + // Modifiers + + // Functions + + // Constructor + constructor(address addressProvider, address _ethCollector, address _nukefund, uint256 subscriptionId) AddressProviderResolver(addressProvider) VRFConsumerBaseV2Plus(0x9DdfaCa8183c41ad55329BdeeD9F6A8d53168B1B) { + if (_ethCollector == address(0)) revert LottFund__AddressIsZero(); + s_subscriptionId = subscriptionId; + ethCollector = _ethCollector; + nukeFundAddress = _nukefund; + } + + // Fallback function to receive ETH and update fund balance + receive() external payable { + uint256 devShare = (msg.value * taxCut) / BPS; // Calculate developer's share (10%) + uint256 remainingFund = msg.value - devShare; // Calculate remaining funds to add to the fund + + fund += remainingFund; // Update the fund balance + IAirdrop airdropContract = _getAirdrop(); + address devAddress = payable(_getDevFundAddress()); + address daoAddress = payable(_getDaoFundAddress()); + + if (!airdropContract.airdropStarted()) { + (bool success,) = devAddress.call{ value: devShare }(""); + require(success, "ETH send failed"); + emit DevShareDistributed(devShare); + } else if (!airdropContract.daoFundAllowed()) { + (bool success,) = payable(ethCollector).call{ value: devShare }(""); + require(success, "ETH send failed"); + } else { + (bool success,) = daoAddress.call{ value: devShare }(""); + require(success, "ETH send failed"); + emit DevShareDistributed(devShare); + } + emit FundReceived(msg.sender, msg.value); // Log the received funds + emit FundBalanceUpdated(fund); // Update the fund balance + } + + + + + // EXTERNAL FUNCTIONS + + function bid(uint256 tokenId) public whenNotPaused nonReentrant { + ITraitForgeNft traitForgeNft = _getTraitForgeNft(); + if (traitForgeNft.ownerOf(tokenId) != msg.sender) revert LottFund__CallerNotTokenOwner(); + if ( + !( + traitForgeNft.getApproved(tokenId) == address(this) + || traitForgeNft.isApprovedForAll(msg.sender, address(this)) + ) + ) revert NukeFund__ContractNotApproved(); + canTokenBeBidded(tokenId); //requires + bidCountPerRound[currentRound][msg.sender]++; + bidsAmount++; // increase total bid amounts as max is currently 1500 (can be altered) + tokenIdsBidded.push(tokenId); // store the array of tokenIds that have been bidded + tokenBidCount[tokenId]++; + if(bidsAmount >= maxBidAmount) { //if bidsAmount reaches 1500 (currently) then pause bidding and roll the lottery + pauseBiddingBriefly(); + BidPayout(); + } + } + + function migrate(address newAddress) external whenNotPaused onlyProtocolMaintainer { + require(newAddress != address(0), "Invalid new contract address"); + uint256 contractBalance = address(this).balance; + if (contractBalance > 0) { + (bool success, ) = newAddress.call{value: contractBalance}(""); + require(success, "Failed to transfer ETH"); + } + } + + + + + + // INTERNAL FUNCTIONS + + function pauseBiddingBriefly() internal whenNotPaused { + pausedBids = true; + } + + function BidPayout(uint256[] calldata _randomWords) internal whenNotPaused nonReentrant { + if (bidsAmount != maxBidAmount){ //if bidsAmount has no maxxed out then revert + revert LottFund__BiddingNotFinished(); + } + requestRandomWords(nativePayment); + uint256 winnerIndex = _randomWords[0] % tokenIdsBidded.length; // get the index of the array of tokenIds that have been bidded + uint256 winnerTokenId = tokenIdsBidded[winnerIndex]; // Get the winner's token ID + + uint256[] memory tokensToBurn = new uint256[](amountToBeBurnt); //memory to stre the tokens to be burnt + for (uint256 i = 1; i <= amountToBeBurnt; i++) { // Use the next 5 numbers to locate the indexes of 5 tokenIds to burn + uint256 burnIndex = _randomWords[i] % tokenIdsBidded.length; // Find the burn index + tokensToBurn[i - 1] = tokenIdsBidded[burnIndex]; // Store the token ID to burn + } + burnTokens(tokensToBurn); // Burn the selected tokenIds + + uint256 finalNukeFactor = nukeFundAddress.calculateNukeFactor(winnerTokenId); // finalNukeFactor has 5 digits + uint256 potentialClaimAmount = (fund * finalNukeFactor) / MAX_DENOMINATOR; // Calculate the potential claim + // amount based on the finalNukeFactor + uint256 maxAllowedClaimAmount = fund / maxAllowedClaimDivisor; // Define a maximum allowed claim amount as 50% + // of the current fund size + + // Directly assign the value to claimAmount based on the condition, removing the redeclaration + uint256 claimAmount = finalNukeFactor > nukeFactorMaxParam ? maxAllowedClaimAmount : potentialClaimAmount; + + fund -= claimAmount; // Deduct the claim amount from the fund + ITraitForgeNft traitForgeNft = _getTraitForgeNft(); + address payable ownerOfWinningToken = payable(traitForgeNft.ownerOf(winnerTokenId)); + + (bool success,) = payable(ownerOfWinningToken).call{ value: claimAmount }(""); + require(success, "Failed to send Ether"); + resetRound(); + + emit PayedOut(winnerTokenId, claimAmount); // Emit the event with the actual claim amount + emit FundBalanceUpdated(fund); // Update the fund balance + emit TokensBurnt(tokensToBurn); + } + + function burnTokens(uint256[] memory tokenIds) internal whenNotPaused { + ITraitForgeNft traitForgeNft = _getTraitForgeNft(); + for (uint256 i = 0; i < tokenIds.length; i++) { + traitForgeNft.burn(tokenIds[i]); // Burn each token + } + } + + function resetRound() internal whenNotPaused { + bidsAmount = 0; //reset count of bids + delete tokenIdsBidded; // reset array of tokens bidded + pausedBids = false; // set bidding back to active + currentRound++; // increase round to reset the mapping(uint256 => mapping(address => uint256)) public bidCountPerRound; + } + + + + + + // VIEW FUNCTIONS + + function canTokenBeBidded(uint256 tokenId) external view returns (bool){ + ITraitForgeNft traitForgeNft = _getTraitForgeNft(); + entropy = traitForgeNft.getTokenEntropy(tokenId); // get entities 6 digit entropy + maxBidPotential = entropy % 2; // calculation for maxBidPotenital from entropy eg 999999 = + if(entropy == 999_999){ // if golden god (999999) maxBidPotential is +1 over max + maxBidPotential = 3 + } + bidAmountForToken = tokenBidCount[tokenId]; //how many times has the token bidded before? + if(maxBidPotential == 0){ // if tokens maxBidePotential is 0 revert + revert LottFund__TokenHasNoBidPotential(); + } + if(maxBidPotential <= bidAmountForToken){ // if tokens maxBidPotential is less than or equal to how many times it has bidded before then revert + revert LottFund__TokenBidAmountDepleted(); // eg if the tokens maxBidPotential is 2 and it has bidded twice then it cannot bid again + } + return true; + } + + + // GETTER FUNCTIONS + + function getFundBalance() public view returns (uint256) { + return fund; + } + + function getTokenBidAmounts(tokenId) public view returns (uint256) { + return tokenBidCount[tokenId]; + } + + function getMaxBidPotential(tokenId) public view returns (uint256) { + ITraitForgeNft traitForgeNft = _getTraitForgeNft(); + entropy = traitForgeNft.getTokenEntropy(tokenId); + maxBidPotential = entropy % 2; + return maxBidPotential; + } + + function getTokensBidded() public view returns (uint256[] memory) { + return tokenIdsBidded; + } + + function _getDevFundAddress() private view returns (address) { + return _addressProvider.getDevFund(); + } + + function _getDaoFundAddress() private view returns (address) { + return _addressProvider.getDAOFund(); + } + + function _getTraitForgeNft() private view returns (ITraitForgeNft) { + return ITraitForgeNft(_addressProvider.getTraitForgeNft()); + } + + function _getAirdrop() private view returns (IAirdrop) { + return IAirdrop(_addressProvider.getAirdrop()); + } + + + + // SETTER FUNCTIONS + + function setNukeFundAddress(address _nukeFundAddress) external onlyProtocolMaintainer { + nukeFundAddress = _nukeFundAddress; + } + + function setTaxCut(uint256 _taxCut) external onlyProtocolMaintainer { + require(_taxCut <= BPS, "Tax cut exceeds maximum limit."); + taxCut = _taxCut; + } + + function setMaxAllowedClaimDivisor(uint256 _divisor) external onlyProtocolMaintainer { + require(_divisor > 0, "Divisor must be greater than 0."); + maxAllowedClaimDivisor = _divisor; + } + + function setNukeFactorMaxParam(uint256 _nukeFactorMaxParam) external onlyProtocolMaintainer { + require(_nukeFactorMaxParam <= MAX_DENOMINATOR, "Invalid nuke factor parameter."); + nukeFactorMaxParam = _nukeFactorMaxParam; + } + + function setEthCollector(address _ethCollector) external onlyProtocolMaintainer { + ethCollector = _ethCollector; + } + + function setMaxBidsPerAddress(uint256 _limit) external onlyProtocolMaintainer{ + maxBidsPerAddress = _limit; + } + + function setNativePayment(bool isTrue) external onlyProtocolMaintainer { + require(isTrue != nativePayment); + nativePayment = isTrue; + } + + function setAmountToBeBurnt(uint256 _amountToBeBurnt) external onlyProtocolMaintainer { + amountToBeBurnt = _amountToBeBurnt; + } + + function setPausedBids(bool _pausedBids) external onlyProtocolMaintainer { + pausedBids = _pausedBids; + } + + function setMaxBidAmount(uint256 _maxBidAmount) external onlyProtocolMaintainer { + maxBidAmount = _maxBidAmount; + } + + function pause() public onlyProtocolMaintainer { + _pause(); + } + + function unpause() public onlyProtocolMaintainer { + _unpause(); + } + + + + // CHAINLINK VRF FUNCTIONS + + function requestRandomWords( // unsure about the difference of this and fulfillRandomWords + bool enableNativePayment + ) internal returns (uint256 requestId) { + // Will revert if subscription is not set and funded. + requestId = s_vrfCoordinator.requestRandomWords( + VRFV2PlusClient.RandomWordsRequest({ + keyHash: keyHash, + subId: s_subscriptionId, + requestConfirmations: requestConfirmations, + callbackGasLimit: callbackGasLimit, + numWords: numWords, + extraArgs: VRFV2PlusClient._argsToBytes( + VRFV2PlusClient.ExtraArgsV1({ + nativePayment: enableNativePayment + }) + ) + }) + ); + s_requests[requestId] = RequestStatus({ + randomWords: new uint256[](0), + exists: true, + fulfilled: false + }); + requestIds.push(requestId); + lastRequestId = requestId; + emit RequestSent(requestId, numWords); + return requestId; + } + + function fulfillRandomWords( + uint256 _requestId, + uint256[] calldata _randomWords + ) internal override { + require(s_requests[_requestId].exists, "request not found"); + s_requests[_requestId].fulfilled = true; + s_requests[_requestId].randomWords = _randomWords; + emit RequestFulfilled(_requestId, _randomWords); + } + + function getRequestStatus( + uint256 _requestId + ) external view returns (bool fulfilled, uint256[] memory randomWords) { + require(s_requests[_requestId].exists, "request not found"); + RequestStatus memory request = s_requests[_requestId]; + return (request.fulfilled, request.randomWords); + } + + function setRequestConfirmations(uint16 _amount) external onlyProtocolMaintainer { + requestConfirmations = _amount; + } + + function setNumWords(uint32 _amount) external onlyProtocolMaintainer { + numWords = _amount; + } + + function setKeyHash(bytes32 _keyHash) external onlyProtocolMaintainer { + keyHash = _keyHash; + } + + function setCallbackGasLimit(uint32 _limit) external onlyProtocolMaintainer { + callbackGasLimit = _limit; + } +} diff --git a/contracts/NukeRouter.sol b/contracts/NukeRouter.sol new file mode 100644 index 0000000..b9e4942 --- /dev/null +++ b/contracts/NukeRouter.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract FundRouter { + + // routing addresses + address public nukeFund; + address public lottFund; + + // state variables + uint256 public NUKEFUND_PERCENT = 1000; // 10% to NukeFund + uint256 public LOTT_FUND_PERCENT = 9000; // 90% to LottFund + uint256 public constant BPS = 10_000; // 100% for dividing + + // constructor + constructor(address _nukeFund, address _lottFund) { + require(_nukeFund != address(0), "NukeFund address cannot be zero"); + require(_lottFund != address(0), "LottFund address cannot be zero"); + + nukeFund = _nukeFund; + lottFund = _lottFund; + } + + + // RECEIVE + receive() external payable { // Receive ETH and route the funds + uint256 totalAmount = msg.value; + uint256 nukeFundAmount = (totalAmount * NUKEFUND_PERCENT) / BPS; + uint256 lottFundAmount = (totalAmount * LOTT_FUND_PERCENT) / BPS; + + (bool successNuke,) = payable(nukeFund).call{value: nukeFundAmount}(""); // Transfer 10% to NukeFund + require(successNuke, "Failed to send funds to NukeFund"); + + (bool successLott,) = payable(lottFund).call{value: lottFundAmount}(""); // Transfer 90% to LottFund + require(successLott, "Failed to send funds to LottFund"); + } + + + + + // SETTER FUNCTIONS + + function setNukeFund(address _nukeFund) external onlyProtocolMaintainer { + require(_nukeFund != address(0), "NukeFund address cannot be zero"); + nukeFund = _nukeFund; + } + + function setLottFund(address _lottFund) external onlyProtocolMaintainer { + require(_lottFund != address(0), "LottFund address cannot be zero"); + lottFund = _lottFund; + } + + function setLottSendAmount(uint256 _amount) external onlyProtocolMaintainer { + require(_amount != 0, "Amount cannot be zero"); + require(_amount + NUKEFUND_PERCENT <= BPS, "Total percentage exceeds 100%"); + LOTT_FUND_PERCENT = _amount; + } + + function setNukeSendAmount(uint256 _amount) external onlyProtocolMaintainer { + require(_amount != 0, "Amount cannot be zero"); + require(_amount + LOTT_FUND_PERCENT <= BPS, "Total percentage exceeds 100%"); + NUKEFUND_PERCENT = _amount; + } + + + // Fallback for any unintended direct transfers + fallback() external payable { + receive(); // Same logic as receive() + } + + +} diff --git a/contracts/interfaces/ILottFund.sol b/contracts/interfaces/ILottFund.sol new file mode 100644 index 0000000..c87ee0c --- /dev/null +++ b/contracts/interfaces/ILottFund.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface ILottFund { + // Structs + struct RequestStatus { + bool fulfilled; // whether the request has been successfully fulfilled + bool exists; // whether a requestId exists + uint256[] randomWords; + } + + // Events + event RequestSent(uint256 requestId, uint32 numWords); + event RequestFulfilled(uint256 requestId, uint256[] randomWords); + event FundReceived(address indexed sender, uint256 amount); + event FundBalanceUpdated(uint256 newBalance); + event PayedOut(uint256 tokenId, uint256 claimAmount); + event TokensBurnt(uint256[] tokenIds); + event DevShareDistributed(uint256 amount); + + // External Functions + function bid(uint256 tokenId) external; + function migrate(address newAddress) external; + function canTokenBeBidded(uint256 tokenId) external view returns (bool); + function getFundBalance() external view returns (uint256); + function getTokenBidAmounts(uint256 tokenId) external view returns (uint256); + function getMaxBidPotential(uint256 tokenId) external view returns (uint256); + function getTokensBidded() external view returns (uint256[] memory); + function getRequestStatus(uint256 requestId) external view returns (bool fulfilled, uint256[] memory randomWords); + function setNukeFundAddress(address _nukeFundAddress) external; + function setTaxCut(uint256 _taxCut) external; + function setMaxAllowedClaimDivisor(uint256 _divisor) external; + function setMaxBidsPerAddress(uint256 _limit) external; + function setNukeFactorMaxParam(uint256 _nukeFactorMaxParam) external; + function setEthCollector(address _ethCollector) external; + function setNativePayment(bool isTrue) external; + function setAmountToBeBurnt(uint256 _amountToBeBurnt) external; + function setPausedBids(bool _pausedBids) external; + function setMaxBidAmount(uint256 _maxBidAmount) external; + function pause() external; + function unpause() external; + function setRequestConfirmations(uint16 _amount) external; + function setNumWords(uint32 _amount) external; + function setKeyHash(bytes32 _keyHash) external; + function setCallbackGasLimit(uint32 _limit) external; + + // Chainlink VRF Functions + function requestRandomWords(bool enableNativePayment) external returns (uint256 requestId); +} diff --git a/test/integration/concrete/lottFund/BasicTests.t.sol b/test/integration/concrete/lottFund/BasicTests.t.sol new file mode 100644 index 0000000..936e55c --- /dev/null +++ b/test/integration/concrete/lottFund/BasicTests.t.sol @@ -0,0 +1,164 @@ +// 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/fundTesting.t.sol b/test/integration/concrete/lottFund/fundTesting.t.sol new file mode 100644 index 0000000..ccd1076 --- /dev/null +++ b/test/integration/concrete/lottFund/fundTesting.t.sol @@ -0,0 +1,126 @@ +// 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); +} +}