finish up lottery course

This commit is contained in:
han
2025-01-06 21:19:15 +07:00
parent b6fb56fd2c
commit 54dda02722
12 changed files with 601 additions and 17 deletions

View File

@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
// @dev This contract has been adapted to fit with foundry
pragma solidity ^0.8.0;
import {ERC20} from "@solmate/tokens/ERC20.sol";
interface ERC677Receiver {
function onTokenTransfer(address _sender, uint256 _value, bytes memory _data) external;
}
contract LinkToken is ERC20 {
uint256 constant INITIAL_SUPPLY = 1000000000000000000000000;
uint8 constant DECIMALS = 18;
constructor() ERC20("LinkToken", "LINK", DECIMALS) {
_mint(msg.sender, INITIAL_SUPPLY);
}
function mint(address to, uint256 value) public {
_mint(to, value);
}
event Transfer(address indexed from, address indexed to, uint256 value, bytes data);
/**
* @dev transfer token to a contract address with additional data if the recipient is a contact.
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
* @param _data The extra data to be passed to the receiving contract.
*/
function transferAndCall(address _to, uint256 _value, bytes memory _data) public virtual returns (bool success) {
super.transfer(_to, _value);
// emit Transfer(msg.sender, _to, _value, _data);
emit Transfer(msg.sender, _to, _value, _data);
if (isContract(_to)) {
contractFallback(_to, _value, _data);
}
return true;
}
// PRIVATE
function contractFallback(address _to, uint256 _value, bytes memory _data) private {
ERC677Receiver receiver = ERC677Receiver(_to);
receiver.onTokenTransfer(msg.sender, _value, _data);
}
function isContract(address _addr) private view returns (bool hasCode) {
uint256 length;
assembly {
length := extcodesize(_addr)
}
return length > 0;
}
}

View File

@@ -0,0 +1,249 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {Test} from "forge-std/Test.sol";
import {DeployRaffle} from "script/DeployRaffle.s.sol";
import {Raffle} from "src/Raffle.sol";
import {CodeConstants, HelperConfig} from "script/HelperConfig.s.sol";
import {Vm} from "forge-std/Vm.sol";
import {VRFCoordinatorV2_5Mock} from "@chainlink/contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2_5Mock.sol";
contract RaffleTest is CodeConstants, Test {
Raffle public raffle;
HelperConfig public helperConfig;
uint256 entranceFee;
uint256 interval;
address vrfCoordinator;
bytes32 gasLane;
uint32 callbackGasLimit;
uint256 subscriptionId;
address public PLAYER = makeAddr("player");
uint256 public constant STARTING_PLAYER_BALANCE = 10 ether;
event RaffleEntered(address indexed player);
function setUp() external {
DeployRaffle deployer = new DeployRaffle();
(raffle, helperConfig) = deployer.deployContract();
HelperConfig.NetworkConfig memory config = helperConfig.getConfig();
entranceFee = config.entranceFee;
interval = config.interval;
vrfCoordinator = config.vrfCoordinator;
gasLane = config.gasLane;
callbackGasLimit = config.callbackGasLimit;
subscriptionId = config.subscriptionId;
vm.deal(PLAYER, STARTING_PLAYER_BALANCE);
}
function testRaffleInitializedInOpenState() public view {
assert(raffle.getRaffleState() == Raffle.RaffleState.OPEN);
}
function testRaffleRevertsWhenYouDontPayEnough() public {
// Arrange
vm.prank(PLAYER);
// Act / Asset
vm.expectRevert(Raffle.Raffle__SendMoreToEnterRaffle.selector);
// Assert
raffle.enterRaffle();
}
function testRaffleRecordsPlayersWhenTheyEnter() public {
// Arrange
vm.prank(PLAYER);
// Act
raffle.enterRaffle{value: entranceFee}();
// Assert
address playerRecorded = raffle.getPlayer(0);
assert(playerRecorded == PLAYER);
}
function testEnteringRaffleEmitsEvent() public {
// Arrange
vm.prank(PLAYER);
// Act
vm.expectEmit(true, false, false, false, address(raffle));
emit RaffleEntered(PLAYER);
// Assert
raffle.enterRaffle{value: entranceFee}();
}
function testDontAllowPlayersToEnterWhileRaffleIsCalculating() public {
// Arrange
vm.prank(PLAYER);
raffle.enterRaffle{value: entranceFee}();
vm.warp(block.timestamp + interval + 1);
vm.roll(block.number + 1);
raffle.performUpkeep("");
// Act
vm.expectRevert(Raffle.Raffle__RaffleNotOpen.selector);
vm.prank(PLAYER);
raffle.enterRaffle{value: entranceFee}();
// Assert
}
// Check Upkeep
function testCheckUpkeepReturnsFalseIfItHasNoBalance() public {
// Arrange
vm.warp(block.timestamp + interval + 1);
vm.roll(block.number + 1);
// Act
(bool upkeepNeeded, ) = raffle.checkUpkeep("");
// Assert
assert(!upkeepNeeded);
}
function testCheckUpkeepReturnsFalseIfRaffleIsntOpen() public {
// Arrange
vm.prank(PLAYER);
raffle.enterRaffle{value: entranceFee}();
vm.warp(block.timestamp + interval + 1);
vm.roll(block.number + 1);
raffle.performUpkeep("");
// Act
(bool upkeepNeeded, ) = raffle.checkUpkeep("");
// Assert
assert(!upkeepNeeded);
}
function testCheckUpkeepReturnsFalseIfEnoughTimeHasPassed() public {
// Arrange
vm.prank(PLAYER);
raffle.enterRaffle{value: entranceFee}();
vm.warp(block.timestamp + interval + 1);
vm.roll(block.number + 1);
raffle.performUpkeep("");
vm.warp(block.timestamp + interval + 1);
vm.roll(block.number + 1);
// Act
(bool upkeepNeeded, ) = raffle.checkUpkeep("");
// Assert
assert(!upkeepNeeded);
}
function testCheckUpkeepReturnsTrueWhenParametersAreGood() public {
// Arrange
vm.prank(PLAYER);
raffle.enterRaffle{value: entranceFee}();
vm.warp(block.timestamp + interval + 1);
vm.roll(block.number + 1);
// Act
(bool upkeepNeeded, ) = raffle.checkUpkeep("");
// Assert
assert(upkeepNeeded);
}
// Perform Upkeep
function testPerformUpkeepCanOnlyRunIfCheckUpkeepIsTrue() public {
// Arrange
vm.prank(PLAYER);
raffle.enterRaffle{value: entranceFee}();
vm.warp(block.timestamp + interval + 1);
vm.roll(block.number + 1);
// Act / Assert
raffle.performUpkeep("");
}
function testPerformUpkeepRevertsIfCheckUpkeepIsFalse() public {
// Arrange
uint256 currentBalance = 0;
uint256 numPlayers = 0;
Raffle.RaffleState rState = raffle.getRaffleState();
vm.prank(PLAYER);
raffle.enterRaffle{value: entranceFee}();
currentBalance = currentBalance + entranceFee;
numPlayers = 1;
// Act / Assert
vm.expectRevert(
abi.encodeWithSelector(Raffle.Raffle__UpkeepNotNeeded.selector, currentBalance, numPlayers, rState)
);
raffle.performUpkeep("");
}
modifier raffleEntered() {
// Arrange
vm.prank(PLAYER);
raffle.enterRaffle{value: entranceFee}();
vm.warp(block.timestamp + interval + 1);
vm.roll(block.number + 1);
_;
}
function testPerformUpkeepUpdatesRaffleStateAndEmitsRequested() public raffleEntered {
// Act
vm.recordLogs();
raffle.performUpkeep("");
Vm.Log[] memory entries = vm.getRecordedLogs();
bytes32 requestId = entries[1].topics[1];
// Assert
Raffle.RaffleState raffleState = raffle.getRaffleState();
assert(uint256(requestId) > 0);
assert(uint256(raffleState) == 1);
}
// Fulfill Random Words
modifier skipFork() {
if (block.chainid != LOCAL_CHAIN_ID) {
return;
}
_;
}
function testFulfillRandomWordsCanOnlyBeCalledAfterPerformUpkeep(uint256 randomRequestId) public raffleEntered skipFork {
// Arrange / Assert
vm.expectRevert(VRFCoordinatorV2_5Mock.InvalidRequest.selector);
VRFCoordinatorV2_5Mock(vrfCoordinator).fulfillRandomWords(randomRequestId, address(raffle));
}
function testFulfillRandomWordsPicksAWinnerResetsAndSendsMoney() public raffleEntered skipFork {
// Arrange
uint256 additionalEntrants = 3;
uint256 startingIndex = 1;
address expectedWinner = address(1);
for(uint256 i = startingIndex; i < startingIndex + additionalEntrants; i++) {
address newPlayer = address(uint160(i)); // address(1)
hoax(newPlayer, 1 ether);
raffle.enterRaffle{value: entranceFee}();
}
uint256 startingTimeStamp = raffle.getLastTimeStamp();
uint256 winnerStartingBalance = expectedWinner.balance;
// Act
vm.recordLogs();
raffle.performUpkeep("");
Vm.Log[] memory entries = vm.getRecordedLogs();
bytes32 requestId = entries[1].topics[1];
VRFCoordinatorV2_5Mock(vrfCoordinator).fulfillRandomWords(uint256(requestId), address(raffle));
// Assert
address recentWinner = raffle.getRecentWinner();
Raffle.RaffleState raffleState = raffle.getRaffleState();
uint256 winnerBalance = recentWinner.balance;
uint256 endingTimeStamp = raffle.getLastTimeStamp();
uint256 prize = entranceFee * (additionalEntrants + 1);
assert(recentWinner == expectedWinner);
assert(uint256(raffleState) == 0);
assert(winnerBalance == winnerStartingBalance + prize);
assert(endingTimeStamp > startingTimeStamp);
}
}