diff --git a/foundry.toml b/foundry.toml index 67fe317..f8c8478 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,8 @@ out = "out" libs = ["lib"] remappings = [ "@chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/", - "@solmate/=lib/solmate/src/" + "@solmate/=lib/solmate/src/", + "~homesrc=/home/ccacr/solidity-course/foundry-f23/foundry-smart-contract-lottery-cu/src" ] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/src/Raffle.sol b/src/Raffle.sol index 4ffbf43..03fc8a2 100644 --- a/src/Raffle.sol +++ b/src/Raffle.sol @@ -4,22 +4,11 @@ pragma solidity ^0.8.18; import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol"; import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol"; import {Script, console} from "forge-std/Script.sol"; +import {CCEncoder} from "./tools/CCEncoder.sol"; -contract Raffle is VRFConsumerBaseV2Plus,Script{ - error Raffle__NotEnoughEtherSent(); - error Raffle__WinnerWithdrawFailed(); - error Raffle__RaffleStateNotOpen(); - error Raffle__UpKeepNotFeed( - bytes upKeepFlags, - uint256 balance, - uint256 length - ); - - enum RaffleState { - OPEN, - CALCULATING - } +contract Raffle is VRFConsumerBaseV2Plus, Script { + using CCEncoder for bool[]; uint16 private constant REQUEST_CONFIRMATIONS = 3; uint32 private constant NUM_WORDS = 1; uint256 private constant ROLL_IN_PROGRESS = 42; @@ -38,6 +27,22 @@ contract Raffle is VRFConsumerBaseV2Plus,Script{ event EnterRaffle(address indexed player); event WinnerPicked(address indexed winner); + event RequestedRaffleWinner(uint256 indexed requestId); + + error Raffle__NotEnoughEtherSent(); + error Raffle__WinnerWithdrawFailed(); + error Raffle__RaffleStateNotOpen(); + error Raffle__UpKeepNotFeed( + bytes upKeepFlags, + uint256 balance, + uint256 length + ); + + enum RaffleState { + OPEN, + CALCULATING + } + constructor( uint256 entranceFee, @@ -77,28 +82,22 @@ contract Raffle is VRFConsumerBaseV2Plus,Script{ */ function checkUpkeep( bytes memory /* checkData */ - ) - public - view - returns ( - bool upkeepNeeded, - bytes memory /* performData */ - ) - { - bool timeHasPassed = block.timestamp - s_lastTimestamp >= i_interval; - bool stateIsOpen = s_raffleState == RaffleState.OPEN; - bool hasBalance = address(this).balance > 0; - bool hasPlayer = s_players.length > 0; - // console.log(timeHasPassed); - // console.log(stateIsOpen); - // console.log(hasBalance); - // console.log(hasPlayer); - upkeepNeeded = timeHasPassed && stateIsOpen && hasBalance && hasPlayer; + ) public view returns (bool upkeepNeeded, bytes memory /* performData */) { + bool[] memory flags = new bool[](4); + upkeepNeeded = true; + + flags[0] = block.timestamp - s_lastTimestamp >= i_interval; + flags[1] = s_raffleState == RaffleState.OPEN; + flags[2] = address(this).balance > 0; + flags[3] = s_players.length > 0; //TODO if output false ,use event to replace the upKeepFlags, And compare the gas spent // bytes memory outputFlagg = abi.encodePacked(uint8(0x02), timeHasPassed); - // console.log(hasPlayer); - bytes memory upKeepFlags = abi.encode(timeHasPassed,stateIsOpen,hasBalance,hasPlayer); + // console.log(hasPlayer); + bytes memory upKeepFlags = flags.castFlags(); + for (uint256 i = 0; i < flags.length; i++) { + upkeepNeeded = upkeepNeeded && flags[i]; + } return (upkeepNeeded, upKeepFlags); } @@ -113,7 +112,7 @@ contract Raffle is VRFConsumerBaseV2Plus,Script{ } s_raffleState = RaffleState.CALCULATING; - s_vrfCoordinator.requestRandomWords( + uint256 requestId = s_vrfCoordinator.requestRandomWords( VRFV2PlusClient.RandomWordsRequest({ keyHash: i_keyHash, subId: i_subscriptionId, @@ -126,6 +125,8 @@ contract Raffle is VRFConsumerBaseV2Plus,Script{ ) }) ); + + emit RequestedRaffleWinner(requestId); } function fulfillRandomWords( diff --git a/src/tools/CCEncoder.sol b/src/tools/CCEncoder.sol index a04cd4f..f68cddc 100644 --- a/src/tools/CCEncoder.sol +++ b/src/tools/CCEncoder.sol @@ -2,13 +2,22 @@ pragma solidity ^0.8.18; library CCEncoder { - function castFlag(bool flag) public returns (bytes memory output) { + function castFlag(bool flag) public pure returns (bytes memory output) { output = flag ? bytes("1") : bytes("0"); } function castFlags( bool[] memory flags - ) public returns (bytes memory output) { + ) public pure returns (bytes memory output) { + output = ""; + for (uint256 i = 0; i < flags.length; i++) { + output = bytes.concat(output, castFlag(flags[i])); + } + } + + function castFlags( + bool[4] memory flags + ) public pure returns (bytes memory output) { output = ""; for (uint256 i = 0; i < flags.length; i++) { output = bytes.concat(output, castFlag(flags[i])); diff --git a/test/unit/RaffleTest.t.sol b/test/unit/RaffleTest.t.sol index dd24ea7..f748ada 100644 --- a/test/unit/RaffleTest.t.sol +++ b/test/unit/RaffleTest.t.sol @@ -7,8 +7,12 @@ import {HelperConfig} from "../../script/HelperConfig.s.sol"; import {DeployRaffle} from "../../script/DeployRaffle.s.sol"; import {Test, console} from "forge-std/Test.sol"; import {CCEncoder} from "../../src/tools/CCEncoder.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {CCEncoder} from "~homesrc/tools/CCEncoder.sol"; contract RaffleTest is Test { + using CCEncoder for bool[]; + using CCEncoder for bool[4]; event EnterRaffle(address indexed player); Raffle raffle; @@ -19,6 +23,25 @@ contract RaffleTest is Test { address public PlayerAddress = makeAddr("player"); uint256 public constant STARTING_USER_BALANCE = 10 ether; + /* Modifiers */ + modifier M_prankPlayer() { + vm.startPrank(PlayerAddress); + _; + vm.stopPrank(); + } + + modifier M_enterRaffle() { + raffle.enterRaffle{value: entranceFee}(); + _; + } + + modifier M_timePassed() { + vm.warp(block.timestamp + interval + 1); + vm.roll(block.number + 1); + _; + } + + /* Functions */ function setUp() external { DeployRaffle deployRaffle = new DeployRaffle(); (raffle, helperConfig) = deployRaffle.run(); @@ -51,10 +74,12 @@ contract RaffleTest is Test { raffle.enterRaffle{value: entranceFee}(); } - function testCantEnterWhenRaffleIsCalculating() public M_prankPlayer { - raffle.enterRaffle{value: entranceFee}(); - vm.warp(block.timestamp + interval + 1); - vm.roll(block.number + 1); + function testCantEnterWhenRaffleIsCalculating() + public + M_prankPlayer + M_enterRaffle + M_timePassed + { raffle.performUpKeep(""); vm.expectRevert(Raffle.Raffle__RaffleStateNotOpen.selector); @@ -64,9 +89,8 @@ contract RaffleTest is Test { function testCheckUpkeepReturnsFalseIfItHasNoBalance() public M_prankPlayer + M_timePassed { - vm.warp(block.timestamp + interval + 1); - vm.roll(block.number + 1); // raffle.enterRaffle(); (bool upKeepNeeded, ) = raffle.checkUpkeep(""); @@ -74,10 +98,12 @@ contract RaffleTest is Test { assert(!upKeepNeeded); } - function testCheckUpkeepReturnsFalseIfRaffleNotOpen() public M_prankPlayer { - raffle.enterRaffle{value: entranceFee}(); - vm.warp(block.timestamp + interval + 1); - vm.roll(block.number + 1); + function testCheckUpkeepReturnsFalseIfRaffleNotOpen() + public + M_prankPlayer + M_enterRaffle + M_timePassed + { raffle.performUpKeep(""); (bool upkeepFlag, ) = raffle.checkUpkeep(""); @@ -87,22 +113,26 @@ contract RaffleTest is Test { function testCheckUpkeepReturnsFalseIfEnoughTimeHasNotPassed() public M_prankPlayer + M_enterRaffle { - raffle.enterRaffle{value: entranceFee}(); vm.warp(block.timestamp + 1); vm.roll(block.number + 1); - bytes memory upKeepFlags = abi.encode(false, true, true, true); + bool[4] memory inputflags = [false, true, true, true]; + bytes memory upKeepFlags = inputflags.castFlags(); (, bytes memory flags) = raffle.checkUpkeep(""); assertEq(upKeepFlags, flags); } - function testCheckUpkeepReturnsFalseIfStateNotOpen() public M_prankPlayer { - raffle.enterRaffle{value: entranceFee}(); - vm.warp(block.timestamp + interval + 1); - vm.roll(block.number + 1); - bytes memory upKeepFlags = abi.encode(true, false, true, true); + function testCheckUpkeepReturnsFalseIfStateNotOpen() + public + M_prankPlayer + M_enterRaffle + M_timePassed + { + bool[4] memory inputflags = [true, false, true, true]; + bytes memory upKeepFlags = inputflags.castFlags(); raffle.performUpKeep(""); (, bytes memory flags) = raffle.checkUpkeep(""); @@ -110,23 +140,30 @@ contract RaffleTest is Test { assertEq(upKeepFlags, flags); } - function testCheckUpkeepReturnsFalseIfBalanceZero() public M_prankPlayer { - raffle.enterRaffle{value: entranceFee}(); - vm.warp(block.timestamp + interval + 1); - vm.roll(block.number + 1); + function testCheckUpkeepReturnsFalseIfBalanceZero() + public + M_prankPlayer + M_enterRaffle + M_timePassed + { vm.deal(address(raffle), 0); - bytes memory upKeepFlags = abi.encode(true, true, false, true); + bool[4] memory inputflags = [true, true, false, true]; + + bytes memory upKeepFlags = inputflags.castFlags(); (, bytes memory flags) = raffle.checkUpkeep(""); assertEq(upKeepFlags, flags); } - function testCheckUpkeepReturnsFalseIfNoPlayer() public M_prankPlayer { - vm.warp(block.timestamp + interval + 1); - vm.roll(block.number + 1); + function testCheckUpkeepReturnsFalseIfNoPlayer() + public + M_prankPlayer + M_timePassed + { vm.deal(address(raffle), 1 ether); - bytes memory upKeepFlags = abi.encode(true, true, true, false); + bool[4] memory inputflags = [true, true, true, false]; + bytes memory upKeepFlags = inputflags.castFlags(); (, bytes memory flags) = raffle.checkUpkeep(""); @@ -136,10 +173,9 @@ contract RaffleTest is Test { function testCheckUpkeepReturnsTrueWhenParametersAreGood() public M_prankPlayer + M_enterRaffle + M_timePassed { - raffle.enterRaffle{value: entranceFee}(); - vm.warp(block.timestamp + interval + 1); - vm.roll(block.number + 1); (bool upkeepNeeded, ) = raffle.checkUpkeep(""); assert(upkeepNeeded); } @@ -147,22 +183,21 @@ contract RaffleTest is Test { function testPerformUpkeepCanOnlyRunIfCheckUpkeepIsTrue() public M_prankPlayer + M_enterRaffle + M_timePassed { - raffle.enterRaffle{value: entranceFee}(); - vm.warp(block.timestamp + interval + 1); - vm.roll(block.number + 1); raffle.performUpKeep(""); } function testPerformUpkeepRevertsIfCheckUpkeepIsFalse() public M_prankPlayer + M_enterRaffle { - raffle.enterRaffle{value: entranceFee}(); vm.warp(block.timestamp + 1); - vm.roll(block.number + 1); - //TODO encode msg can be shorter like "0,1,1,1", maybe can implement a custom Encoder - bytes memory upKeepFlags = abi.encode(false, true, true, true); + vm.roll(block.number + 1); + bool[4] memory inputflags = [false, true, true, true]; + bytes memory upKeepFlags = inputflags.castFlags(); bytes memory expectedRevertData = abi.encodeWithSelector( Raffle.Raffle__UpKeepNotFeed.selector, @@ -174,16 +209,21 @@ contract RaffleTest is Test { raffle.performUpKeep(""); } - /* Modifiers */ - modifier M_prankPlayer() { - vm.startPrank(PlayerAddress); - _; - vm.stopPrank(); - } + function testPerformUpkeepUpdateRaffleStateAndEmitsRequestId() + public + M_enterRaffle + M_enterRaffle + M_timePassed + { + vm.recordLogs(); + raffle.performUpKeep(""); + Vm.Log[] memory entries = vm.getRecordedLogs(); + bytes32 requestId = entries[1].topics[1]; - modifier M_enterRaffle() { - raffle.enterRaffle{value: entranceFee}(); - _; + Raffle.RaffleState raffleState = raffle.getRaffleState(); + console.log(uint256(raffleState)); + // assert(uint256(requestId) > 0); + assert(raffleState == Raffle.RaffleState.CALCULATING); } } @@ -191,23 +231,26 @@ contract ToolTest is Test { using CCEncoder for bool; using CCEncoder for bool[]; - function testCastSingleTrueFlag() public { + function testCastSingleTrueFlag() public pure{ bool trueFlag = true; assertEq(trueFlag.castFlag(), bytes("1")); } - function testCastSingleFalseFlag() public { + function testCastSingleFalseFlag() public pure{ bool falseFlag = false; assertEq(falseFlag.castFlag(), bytes("0")); } - function testCastMultiFlag() public { + function testCastMultiFlag() public pure { bool[] memory flags = new bool[](4); flags[0] = true; flags[1] = false; flags[2] = false; flags[3] = false; - assertEq(flags.castFlags(), bytes.concat(bytes("1"), bytes("0"), bytes("0"), bytes("0"))); + assertEq( + flags.castFlags(), + bytes.concat(bytes("1"), bytes("0"), bytes("0"), bytes("0")) + ); } }