Skip to content

Commit

Permalink
feat: add experiment jackpot game
Browse files Browse the repository at this point in the history
  • Loading branch information
leovct committed Jul 10, 2024
1 parent e49d9a1 commit 6d89266
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 0 deletions.
53 changes: 53 additions & 0 deletions src/LastCallJackpot.sol
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);
}
}
}
105 changes: 105 additions & 0 deletions test/LastCallJackpot.t.sol
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");
}
}

0 comments on commit 6d89266

Please sign in to comment.