From 7567cc6789e7dfa774bceda4c96368776cebda37 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Sun, 20 Oct 2024 19:17:16 +0200 Subject: [PATCH] Tests done, registration and raid mechanic --- script/{Counter.s.sol => Geld.s.sol} | 10 +-- src/Counter.sol | 14 --- src/Geld.sol | 93 ++++++++++--------- test/Counter.t.sol | 24 ----- test/Geld.t.sol | 128 +++++++++++++++++++++++++-- 5 files changed, 175 insertions(+), 94 deletions(-) rename script/{Counter.s.sol => Geld.s.sol} (63%) delete mode 100644 src/Counter.sol delete mode 100644 test/Counter.t.sol diff --git a/script/Counter.s.sol b/script/Geld.s.sol similarity index 63% rename from script/Counter.s.sol rename to script/Geld.s.sol index cdc1fe9..6e8f703 100644 --- a/script/Counter.s.sol +++ b/script/Geld.s.sol @@ -2,18 +2,16 @@ pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; +import {Geld} from "../src/Geld.sol"; -contract CounterScript is Script { - Counter public counter; +contract GeldScript is Script { + Geld public geld; function setUp() public {} function run() public { vm.startBroadcast(); - - counter = new Counter(); - + geld = new Geld(); vm.stopBroadcast(); } } diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/Geld.sol b/src/Geld.sol index 6cefff1..fb693ca 100644 --- a/src/Geld.sol +++ b/src/Geld.sol @@ -4,33 +4,32 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +struct Raider { + uint16 level; +} + +struct Army { + Raider moloch_denier; + Raider apprentice; + Raider annointed; + Raider champion; +} + +struct Player { + uint256 total_minted; + uint256 created_at; + uint256 last_raided_at; +} + contract Geld is ERC20, Ownable { - - struct Raider { - uint16 level; - } - - struct Army { - Raider moloch_denier; - Raider apprentice; - Raider annointed; - Raider champion; - } - - struct Player { - uint256 total_minted; - uint256 created_at; - uint256 last_minted_at; - } - uint8 constant DECIMALS = 4; uint256 public constant BUY_IN_AMOUNT = 0.0005 ether; - uint256 public constant GELD_PER_PURCHASE = 50 * 10 ** DECIMALS; - uint256 public constant MINT_TIME = 15; + uint256 public constant INITIAL_GELD = 50 * 10 ** DECIMALS; + uint256 public constant RAID_WAIT = 15; uint256 public total_minted = 0; mapping(address => Player) private players; - mapping(address => Army) private armies; + mapping(address => Army) private armies; // Modifier for functions that should only be available to registered players modifier onlyPlayer() { @@ -41,28 +40,27 @@ contract Geld is ERC20, Ownable { constructor() ERC20("Geld", "GELD") Ownable(msg.sender) {} // This effectively registers the user - receive() external payable { - require(players[msg.sender].created_at != 0, "Whoops, player already exists :)"); + function register() external payable { + require(players[msg.sender].created_at == 0, "Whoops, player already exists :)"); require(msg.value == BUY_IN_AMOUNT, "Incorrect buy in amount"); - _mint(msg.sender, GELD_PER_PURCHASE); - players[msg.sender] = Player({ - total_minted: GELD_PER_PURCHASE, - created_at: block.timestamp, - last_minted_at: 0 - }); - armies[msg.sender] = Army({ - moloch_denier: Raider({ level: 0 }), - apprentice: 0, - annointed: 0, - champion: 0 - }) - total_minted += GELD_PER_PURCHASE; + // Mint some starting tokens to the player + _mint(msg.sender, INITIAL_GELD); + + // Set initial states + players[msg.sender] = Player({total_minted: INITIAL_GELD, created_at: block.timestamp, last_raided_at: 0}); + armies[msg.sender] = Army({ + moloch_denier: Raider({level: 0}), + apprentice: Raider({level: 0}), + annointed: Raider({level: 0}), + champion: Raider({level: 0}) + }); + total_minted += INITIAL_GELD; } // Override for default number of decimals function decimals() public view virtual override returns (uint8) { - return DECIMALS; + return 4; } // Allows the owner to withdraw @@ -71,13 +69,13 @@ contract Geld is ERC20, Ownable { } // Manual minting for itchy fingers - function manualMint() external onlyPlayer { - require(block.timestamp >= players[msg.sender].last_minted_at + MINT_TIME, "Tried minting too soon"); - - // TODO: Make real calculation based on army - _mint(msg.sender, 50); + function raid() external onlyPlayer { + require(block.timestamp >= players[msg.sender].last_raided_at + RAID_WAIT, "Tried minting too soon"); - players[msg.sender].last_minted_at = block.timestamp; + // TODO: Make real calculation based on army + _mint(msg.sender, 50 * 10 ** decimals()); + + players[msg.sender].last_raided_at = block.timestamp; } // Function to get Player struct @@ -89,4 +87,13 @@ contract Geld is ERC20, Ownable { function getArmy() public view onlyPlayer returns (Army memory) { return armies[msg.sender]; } + + receive() external payable { + revert("No plain Ether accepted, use register() function to check in :)"); + } + + // Revert any non-function-call Ether transfers or calls to non-existent functions + fallback() external payable { + revert("No fallback calls accepted"); + } } diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/Geld.t.sol b/test/Geld.t.sol index 5e78870..b5bfb6e 100644 --- a/test/Geld.t.sol +++ b/test/Geld.t.sol @@ -1,16 +1,130 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import { Test, console } from "forge-std/Test.sol"; -import { Geld } from "../src/Geld.sol"; +import {Test, console} from "forge-std/Test.sol"; +import {Geld, Army, Player} from "../src/Geld.sol"; -contract Gels is Test { - Geld public geld; +contract GeldTest is Test { + Geld public geld; + address public player1; + address public player2; + address public owner; - function setUp() public { - geld = new Geld(); - } + function setUp() public { + owner = address(0x126); + player1 = address(0x123); + player2 = address(0x124); + vm.deal(owner, 10 ether); + vm.deal(player1, 10 ether); + vm.deal(player2, 10 ether); + vm.prank(owner); + geld = new Geld(); + } + function registerPlayer() private { + geld.register{value: geld.BUY_IN_AMOUNT()}(); + } + function test_00_no_fallback() public { + vm.expectRevert(); + // Send Ether with some data to trigger fallback + address(geld).call{value: 0.1 ether}("0x1234"); + } + function test_01_no_receive() public { + vm.startPrank(player1); + vm.expectRevert(); + payable(address(geld)).transfer(0.1 ether); + } + + function test_02_registration() public { + vm.startPrank(player1); + + uint256 initialBalance = address(geld).balance; + uint256 initialTotalMinted = geld.total_minted(); + + // Send registration fee ETH to the contract + registerPlayer(); + + // Check that initial GELD is received by the player + assertEq(geld.balanceOf(player1), geld.INITIAL_GELD()); + + // Verify the contract balance is updated + assertEq(address(geld).balance, initialBalance + geld.BUY_IN_AMOUNT()); + + // Verify player is set initially + Player memory player = geld.getPlayer(); + assertEq(player.total_minted, geld.INITIAL_GELD()); + assertEq(player.created_at, block.timestamp); + assertEq(player.last_raided_at, 0); + + Army memory army = geld.getArmy(); + + assertEq(army.moloch_denier.level, 0); + assertEq(army.apprentice.level, 0); + assertEq(army.annointed.level, 0); + assertEq(army.champion.level, 0); + + // Verify that total_minted is updated + assertEq(geld.total_minted(), initialTotalMinted + geld.INITIAL_GELD()); + } + + function test_03_fundsCanBeWithdrawn() public { + uint256 initialBalance = owner.balance; + + // Switch to Player 1 and register it + vm.startPrank(player1); + registerPlayer(); + + // Switch back to owner and withdraw funds + vm.startPrank(owner); + geld.withdraw(); + uint256 newBalance = owner.balance; + uint256 newContractBalance = address(geld).balance; + + // contract balance should be empty + assertEq(newContractBalance, 0); + // owner should have the extra funds + assertEq(newBalance, initialBalance + geld.BUY_IN_AMOUNT()); + } + + function test_04_onlyOwnerCanWithdraw() public { + // Register player 1 + vm.startPrank(player1); + registerPlayer(); + + // attempt to withdraw with player 1, it should fail + vm.expectRevert(); + geld.withdraw(); + } + + function test_05_raid() public { + // Let some time pass so we dont start at block timestamp 0 + vm.warp(120); + + // Register player 1 + vm.startPrank(player1); + registerPlayer(); + + uint256 balance = geld.balanceOf(player1); + + // Trigger raid funds minting + geld.raid(); + + // New balance should be larger + uint256 newBalance = geld.balanceOf(player1); + assertLt(balance, newBalance); + + // Expect fail if we raid again, we need to wait a bit + vm.expectRevert(); + geld.raid(); + + // After wait time passes raid should work again + vm.warp(block.timestamp + geld.RAID_WAIT()); + geld.raid(); + + // Balance should reflect that + uint256 newestBalance = geld.balanceOf(player1); + assertLt(newBalance, newestBalance); + } }