// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {stdStorage, StdStorage} from "forge-std/Test.sol"; import {RaidGeld, Army, Player, Boss, LastBossResult} from "../src/RaidGeld.sol"; import "../src/RaidGeldUtils.sol"; import {Constants} from "../src/Constants.sol"; contract raid_geldTest is Test, Constants { using stdStorage for StdStorage; RaidGeld public raid_geld; address public player1; address public player2; address public owner; event Approval(address indexed owner, address indexed spender, uint256 value); event PlayerRegistered(address indexed player, uint256 initialGeld); event RaidPerformed(address indexed player, uint256 totalMinted, uint256 geldBalance); event UnitAdded( address indexed player, uint8 unitType, uint16 nUnits, uint256 cost, uint256 geldBalance, uint16 molochDenierLevel, uint16 apprenticeLevel, uint16 anointedLevel, uint16 championLevel ); function setUp() public { owner = address(0x126); player1 = address(0x123); vm.deal(owner, 10 ether); fundAccount(player1); vm.prank(owner); raid_geld = new RaidGeld(DAO_TOKEN, POOL); raid_geld.weth().deposit{value: 5 ether}(); } function fundAccount(address _acc) private { vm.deal(_acc, 10 ether); stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(_acc).checked_write(1000 ether); } function registerPlayer() private { raid_geld.register_eth{value: raid_geld.BUY_IN_AMOUNT()}(); } function registerPlayerWithDaoToken() private { raid_geld.daoToken().approve(address(raid_geld), raid_geld.BUY_IN_DAO_TOKEN_AMOUNT()); raid_geld.register_dao(); } function test_00_no_fallback() public { vm.expectRevert(); // Send Ether with some data to trigger fallback (bool success,) = address(raid_geld).call{value: 0.1 ether}("0x1234"); } function test_01_no_receive() public { vm.startPrank(player1); vm.expectRevert(); payable(address(raid_geld)).transfer(0.1 ether); } function test_02_1_registrationWithEth() public { vm.startPrank(player1); uint256 contractBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); uint256 userBalance = address(player1).balance; // Making sure event is emitted when player is registered vm.expectEmit(address(raid_geld)); emit PlayerRegistered(player1, raid_geld.INITIAL_GELD()); registerPlayer(); // Check that initialraid_geld.is received by the player assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD()); // Verify the contract balance is updated uint256 contractBalance2 = raid_geld.daoToken().balanceOf(address(raid_geld)); uint256 userBalance2 = address(player1).balance; // Contract should get DAO tokens assertLt(contractBalance, contractBalance2); // Player should lose ETH assertEq(userBalance2, userBalance - raid_geld.BUY_IN_AMOUNT()); // Verify player is set initially Player memory player = raid_geld.getPlayer(player1); assertEq(player.total_minted, raid_geld.INITIAL_GELD()); assertEq(player.created_at, block.timestamp); assertEq(player.last_raided_at, block.timestamp); Army memory army = raid_geld.getArmy(player1); assertEq(army.moloch_denier.level, 0); assertEq(army.apprentice.level, 0); assertEq(army.anointed.level, 0); assertEq(army.champion.level, 0); } function test_02_2_registrationWithDaoToken() public { vm.startPrank(player1); uint256 initialBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); // Making sure event is emitted when player is registered // doesnt test player emitted event because other events get emitted before it registerPlayerWithDaoToken(); // Check that initial raid_geld is received by the player assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD()); // Verify the contract dao token balance is updated assertEq( raid_geld.daoToken().balanceOf(address(raid_geld)), initialBalance + raid_geld.BUY_IN_DAO_TOKEN_AMOUNT() ); // Verify player is set initially Player memory player = raid_geld.getPlayer(player1); assertEq(player.total_minted, raid_geld.INITIAL_GELD()); assertEq(player.created_at, block.timestamp); assertEq(player.last_raided_at, block.timestamp); Army memory army = raid_geld.getArmy(player1); assertEq(army.moloch_denier.level, 0); assertEq(army.apprentice.level, 0); assertEq(army.anointed.level, 0); assertEq(army.champion.level, 0); } function test_03_is_registered() public { bool is_registered = raid_geld.isRegistered(player1); assertEq(is_registered, false); vm.startPrank(player1); // Making sure event is emitted when player is registered vm.expectEmit(address(raid_geld)); emit PlayerRegistered(player1, raid_geld.INITIAL_GELD()); registerPlayer(); is_registered = raid_geld.isRegistered(player1); assertEq(is_registered, true); } function test_04_add_unit() public { vm.startPrank(player1); // Making sure event is emitted when player is registered vm.expectEmit(address(raid_geld)); emit PlayerRegistered(player1, raid_geld.INITIAL_GELD()); registerPlayer(); vm.expectRevert(); // units should be in range raid_geld.addUnit(100, 1); // Player should have enough tokens to burn to get units vm.expectRevert(); raid_geld.addUnit(1, 100); Army memory army = raid_geld.getArmy(player1); uint256 unit_level = army.moloch_denier.level; uint256 balance = raid_geld.balanceOf(player1); uint256 income_per_sec = army.profit_per_second; uint256 cost = RaidGeldUtils.calculateUnitPrice(0, 0, 1); uint256 playerBalance = raid_geld.balanceOf(address(player1)); // Making sure event is emitted when player adds a unit vm.expectEmit(address(raid_geld)); emit UnitAdded(address(player1), 0, 1, cost, playerBalance - cost, 1, 0, 0, 0); // Add 1 unit raid_geld.addUnit(0, 1); uint256 unitPrice = RaidGeldUtils.calculateUnitPrice(0, 0, 1); // Check that those tokens were burnt // WARN: In addUnit will mint additional tokens but they are not calculated // into this because the test doesnt move the blockchain so no extra tokens // are minted to the user uint256 newBalance = raid_geld.balanceOf(player1); assertEq(newBalance, balance - unitPrice); army = raid_geld.getArmy(player1); // Check that unit level increased uint256 new_unit_level = army.moloch_denier.level; assertEq(new_unit_level, unit_level + 1); // Check that user income per second increased uint256 new_income_per_sec = army.profit_per_second; assertLt(income_per_sec, new_income_per_sec); } 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 cost = RaidGeldUtils.calculateUnitPrice(0, 0, 1); uint256 playerBalance = raid_geld.balanceOf(address(player1)); // Making sure event is emitted when player adds a unit vm.expectEmit(address(raid_geld)); emit UnitAdded(address(player1), 0, 1, cost, playerBalance - cost, 1, 0, 0, 0); // bought 1 moloch_denier raid_geld.addUnit(0, 1); vm.warp(block.timestamp + 15); uint256 balance = raid_geld.balanceOf(player1); Army memory army = raid_geld.getArmy(player1); Player memory player = raid_geld.getPlayer(player1); uint256 amountMinted = army.profit_per_second * 15; // Making sure event is emitted when player performs a raid vm.expectEmit(address(raid_geld)); emit RaidPerformed(address(player1), player.total_minted + amountMinted, balance + amountMinted); // Trigger raid funds minting raid_geld.raid(); // New balance should be larger uint256 newBalance = raid_geld.balanceOf(player1); player = raid_geld.getPlayer(player1); uint256 last_raided_at = player.last_raided_at; assertLt(balance, newBalance); // After wait time passes raid should bring in profits again vm.warp(block.timestamp + 15); amountMinted = army.profit_per_second * 15; emit RaidPerformed(address(player1), player.total_minted + amountMinted, balance + amountMinted); raid_geld.raid(); // Balance should reflect that uint256 newestBalance = raid_geld.balanceOf(player1); player = raid_geld.getPlayer(player1); uint256 last_raided_at_2 = player.last_raided_at; assertLt(newBalance, newestBalance); assertLt(last_raided_at, last_raided_at_2); } function test_06_attack_boss() public { // Let some time pass so we dont start at block timestamp 0 vm.warp(120); // Register player 1 vm.startPrank(player1); registerPlayerWithDaoToken(); raid_geld.addUnit(0, 1); uint256 initialDaoBalance = raid_geld.daoToken().balanceOf(player1); uint256 initialContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); Boss memory boss = raid_geld.getBoss(player1); // assert boss is initialized assertEq(boss.level, 0); // make sure variants shuffled assertNotEq(boss.variants[1], boss.variants[2]); // Make a lot of time pass so user deffo has GELD to attack the boss vm.warp(1200000); bool[2] memory results = raid_geld.battle_with_boss(); // Should almost always defeat first boss assertEq(results[0], true); // First boss doesnt grant a new prestige level (ascension) assertEq(results[1], false); uint256 afterBossDaoBalance = raid_geld.daoToken().balanceOf(player1); uint256 afterBossContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); LastBossResult memory bossResult = raid_geld.getLastBossResult(player1); assertEq(bossResult.battled_at, block.timestamp); assertEq(bossResult.reward, afterBossDaoBalance - initialDaoBalance); assertEq(bossResult.level, boss.level); assertEq(bossResult.variant, boss.variants[boss.level]); assertEq(bossResult.prestigeGained, false); // User should receive funs, contract should lose them assertLt(initialDaoBalance, afterBossDaoBalance); assertGt(initialContractBalance, afterBossContractBalance); // Boss should level up boss = raid_geld.getBoss(player1); assertEq(boss.level, 1); Player memory player = raid_geld.getPlayer(player1); // Players total rewards should increase assertGt(player.total_rewards, 0); } function test_07_attack_boss_fail() public { // Let some time pass so we dont start at block timestamp 0 vm.warp(120); // Register player 1 vm.startPrank(player1); registerPlayerWithDaoToken(); raid_geld.addUnit(0, 1); Boss memory boss = raid_geld.getBoss(player1); bool[2] memory results = raid_geld.battle_with_boss(); // Should lose with just starting GELD assertEq(results[0], false); // First boss doesnt grant a new prestige level (ascension) assertEq(results[1], false); Player memory player = raid_geld.getPlayer(player1); Army memory army = raid_geld.getArmy(player1); // player sessions should end assertEq(player.has_active_session, false); // player should lose all geld assertEq(raid_geld.balanceOf(player1), 0); // Units should reset assertEq(army.moloch_denier.level, 0); LastBossResult memory bossResult = raid_geld.getLastBossResult(player1); assertEq(bossResult.battled_at, block.timestamp); assertEq(bossResult.reward, 0); assertEq(bossResult.level, boss.level); assertEq(bossResult.variant, boss.variants[boss.level]); assertEq(bossResult.prestigeGained, false); } function test_08_player_who_lost_can_restart() public { // Let some time pass so we dont start at block timestamp 0 vm.warp(120); uint256 balance1 = raid_geld.daoToken().balanceOf(address(raid_geld)); // Register player 1 vm.startPrank(player1); registerPlayerWithDaoToken(); raid_geld.addUnit(0, 1); uint256 balance2 = raid_geld.daoToken().balanceOf(address(raid_geld)); // Contract gets DAO Tokens with first register assertLt(balance1, balance2); bool[2] memory results = raid_geld.battle_with_boss(); // Should lose with just starting GELD assertEq(results[0], false); Player memory player = raid_geld.getPlayer(player1); // player sessions should end assertEq(player.has_active_session, false); assertEq(player.is_registered, true); registerPlayerWithDaoToken(); player = raid_geld.getPlayer(player1); assertEq(player.has_active_session, true); uint256 balance3 = raid_geld.daoToken().balanceOf(address(raid_geld)); assertLt(balance2, balance3); } function test_09_player_can_gain_prestige(uint256 prevrandao) public { vm.assume(prevrandao < type(uint256).max - 1e4); vm.startPrank(player1); stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(player1).checked_write(10000000 ether); stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(address(raid_geld)).checked_write(10000000 ether); bool success = false; uint256 tries = 0; uint256 streak = 0; for (uint i = 0; i < 1000; i++) { vm.prevrandao(prevrandao + i * 59); bool[2] memory results; bool alreadyLost = false; uint256 newStreak = 0; tries += 1; console.log("NEW TRY, number: ", tries); registerPlayerWithDaoToken(); // Give bajillion GELD to player so player can battle boss after boss stdstore.target(address(raid_geld)).sig("balanceOf(address)").with_key(player1).checked_write(10000000 ether); for (uint j = 0; j < 6; j++) { vm.prevrandao(prevrandao + j * 7 + i * 59); newStreak += 1; results = raid_geld.battle_with_boss(); if (results[0] == false) { alreadyLost = true; break; } } if (alreadyLost) { if (newStreak > streak) { streak = newStreak; } continue; } results = raid_geld.battle_with_boss(); if (results[0] == true && results[1] == true) { success = true; Player memory player = raid_geld.getPlayer(player1); LastBossResult memory bossResult = raid_geld.getLastBossResult(player1); assertEq(bossResult.prestigeGained, true); vm.assertEq(player.prestige_level, 1); vm.assertEq(player.n_runs, tries); break; } } require(success, "Player should eventually succeed"); } }