-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
158 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.21; | ||
|
||
/// @notice Interface for TinyENS. | ||
interface ILastCallJackpot { | ||
/// @notice Call the contract and attempt to win the jackpot. | ||
function call() external payable; | ||
} | ||
|
||
/// @title A game where the last caller, before a 10-block inactivity period, wins the entire contract's ETH balance. | ||
/// @author leovct | ||
/// @notice https://www.paradigm.xyz/2024/06/paradigm-fellowship-2024 | ||
/// An Ethereum contract is funded with 1,000 ETH. It costs 1 ETH to call, which is added to the balance. | ||
/// If the contract isn't called for 10 blocks, the last caller gets the entire ETH balance. | ||
/// How might this game unfold and end? Describe your thinking. | ||
contract LastCallJackpot is ILastCallJackpot { | ||
/// @notice Track the block number when the last call occurred. | ||
uint256 public lastBlock; | ||
/// @notice Store the address of the last caller. | ||
address public lastCaller; | ||
|
||
/// @notice Log each call made to the contract. | ||
event Called(address indexed caller, uint256 blockNumber); | ||
/// @notice Log the transfer of the jackpot amount to the winner. | ||
event WinnerPaid(address indexed winner, uint256 amount); | ||
|
||
constructor() payable { | ||
require(msg.value == 50 ether, "Fund the contract with 1000 ETH"); | ||
lastBlock = block.number; | ||
} | ||
|
||
function call() external payable { | ||
require(msg.value == 1 ether, "Call the contract with 1 ETH"); | ||
|
||
if (block.number >= lastBlock + 10) { | ||
// Pay the winner. | ||
uint256 balance = address(this).balance; | ||
address winner = lastCaller; | ||
|
||
// Update state before external call | ||
lastBlock = block.number; | ||
lastCaller = msg.sender; | ||
|
||
payable(winner).transfer(balance); | ||
emit WinnerPaid(winner, balance); | ||
} else { | ||
// Update the last block and caller. | ||
lastBlock = block.number; | ||
lastCaller = msg.sender; | ||
emit Called(msg.sender, block.number); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.21; | ||
|
||
import "../src/LastCallJackpot.sol"; | ||
import "@forge-std/Test.sol"; | ||
|
||
contract LastCallJackpotTest is Test { | ||
LastCallJackpot private lastCallJackpot; | ||
|
||
address private owner = makeAddr("owner"); | ||
address private alice = makeAddr("alice"); | ||
address private bob = makeAddr("bob"); | ||
|
||
function setUp() public { | ||
// Fund accounts. | ||
vm.deal(owner, 10000 ether); | ||
vm.deal(alice, 1000 ether); | ||
vm.deal(bob, 1000 ether); | ||
|
||
// Deploy contract. | ||
vm.startPrank(owner); | ||
lastCallJackpot = new LastCallJackpot{value: 1000 ether}(); | ||
console2.log("LastCallJackpot deployed"); | ||
vm.stopPrank(); | ||
} | ||
|
||
// This is just a test to make sure the call method works. | ||
function test_SingleCall() public { | ||
vm.startPrank(alice); | ||
lastCallJackpot.call{value: 1 ether}(); | ||
vm.stopPrank(); | ||
|
||
assertEq(lastCallJackpot.lastBlock(), block.number); | ||
assertEq(lastCallJackpot.lastCaller(), alice); | ||
assertEq(address(lastCallJackpot).balance, 1001 ether); | ||
assertEq(alice.balance, 999 ether); | ||
console2.log("Alice calls the contract"); | ||
} | ||
|
||
// This is another test to make sure that one can win the jackpot according to the rules. | ||
function test_WinJackpot() public { | ||
uint256 bn = block.number; | ||
|
||
// Alice calls the contract. | ||
vm.startPrank(alice); | ||
lastCallJackpot.call{value: 1 ether}(); | ||
vm.stopPrank(); | ||
|
||
assertEq(lastCallJackpot.lastBlock(), block.number); | ||
assertEq(lastCallJackpot.lastCaller(), alice); | ||
assertEq(address(lastCallJackpot).balance, 1001 ether); | ||
assertEq(alice.balance, 999 ether); | ||
console2.log("Alice calls the contract"); | ||
|
||
// 10 blocks have passed without anyone calling the contract. | ||
vm.roll(bn + 10); | ||
console2.log("10 blocks have passed"); | ||
|
||
// Bob calls the contract and Alice wins the jackpot. | ||
vm.startPrank(bob); | ||
lastCallJackpot.call{value: 1 ether}(); | ||
vm.stopPrank(); | ||
|
||
assertEq(lastCallJackpot.lastBlock(), block.number); | ||
assertEq(lastCallJackpot.lastCaller(), bob); | ||
console2.log("Bob calls the contract"); | ||
|
||
assertEq(address(lastCallJackpot).balance, 0 ether); | ||
assertEq(alice.balance, 999 ether + 1_001 ether + 1 ether); | ||
console2.log("Alice wins the jackpot"); | ||
} | ||
|
||
// This is another test to make sure that one can only win the jackpot if nobody called the contract for 10 blocks. | ||
// This scenario | ||
function test_MultiCall() public { | ||
uint256 bn = block.number; | ||
|
||
// Alice calls the contract. | ||
vm.startPrank(alice); | ||
lastCallJackpot.call{value: 1 ether}(); | ||
vm.stopPrank(); | ||
|
||
assertEq(lastCallJackpot.lastBlock(), block.number); | ||
assertEq(lastCallJackpot.lastCaller(), alice); | ||
assertEq(address(lastCallJackpot).balance, 1001 ether); | ||
assertEq(alice.balance, 999 ether); | ||
console2.log("Alice calls the contract"); | ||
|
||
// 9 blocks have passed without anyone calling the contract. | ||
vm.roll(bn + 9); | ||
console2.log("9 blocks have passed"); | ||
|
||
// Bob calls the contract and nobody wins the jackpot. | ||
vm.startPrank(bob); | ||
lastCallJackpot.call{value: 1 ether}(); | ||
vm.stopPrank(); | ||
|
||
assertEq(lastCallJackpot.lastBlock(), block.number); | ||
assertEq(lastCallJackpot.lastCaller(), bob); | ||
assertEq(address(lastCallJackpot).balance, 1002 ether); | ||
assertEq(bob.balance, 999 ether); | ||
console2.log("Bob calls the contract"); | ||
console2.log("Nobody wins the jackpot"); | ||
} | ||
} |