From 738378d93e075a4baa7b7909c2bcdc74d38a5317 Mon Sep 17 00:00:00 2001 From: yellow <8539006+yellowBirdy@users.noreply.github.com> Date: Fri, 25 Oct 2024 02:13:52 +0200 Subject: [PATCH 1/3] bit of a refactor --- src/RaidGeld.sol | 28 ++++++---------------------- src/RaidGeldStructs.sol | 22 ++++++++++++++++++++++ src/RaidGeldUtils.sol | 31 +++++++++++++++++++++++-------- test/RaidGeld.t.sol | 4 +--- test/RaidGeldUtils.t.sol | 21 +++++++++++++-------- 5 files changed, 65 insertions(+), 41 deletions(-) create mode 100644 src/RaidGeldStructs.sol diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index feb5422..e5bf6ba 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -4,29 +4,13 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import {RaidGeldUtils} from "../src/RaidGeldUtils.sol"; - -struct Raider { - uint16 level; -} - -struct Army { - Raider moloch_denier; - Raider apprentice; - Raider anointed; - Raider champion; - uint256 profit_per_second; -} - -struct Player { - uint256 total_minted; - uint256 created_at; - uint256 last_raided_at; -} +import {Army, Player, Raider} from "../src/RaidGeldStructs.sol"; contract RaidGeld is ERC20, Ownable { - uint8 constant DECIMALS = 4; + uint256 public constant MANTISSA = 1e4; + uint256 public constant BUY_IN_AMOUNT = 0.00005 ether; - uint256 public constant INITIAL_GELD = 50 * 10 ** DECIMALS; + uint256 public constant INITIAL_GELD = 50 * MANTISSA; uint256 public constant RAID_WAIT = 15 seconds; mapping(address => Player) private players; @@ -80,7 +64,7 @@ contract RaidGeld is ERC20, Ownable { function performRaid(address player) private { uint256 time_past = block.timestamp - players[player].last_raided_at; - uint256 new_geld = armies[player].profit_per_second * time_past * 10 ** decimals(); + uint256 new_geld = armies[player].profit_per_second * time_past; // TODO: Pink noise, make it so sometimes its better than expected @@ -124,7 +108,7 @@ contract RaidGeld is ERC20, Ownable { currentLevel = army.champion.level; } - uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units) * 10 ** decimals(); + uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units); require(balanceOf(msg.sender) > cost, "Not enough GELD to add this much"); // TODO: Since we are first minting then burning the token, this could be simplified diff --git a/src/RaidGeldStructs.sol b/src/RaidGeldStructs.sol new file mode 100644 index 0000000..c846e87 --- /dev/null +++ b/src/RaidGeldStructs.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + + + +struct Raider { + uint16 level; +} + +struct Army { + Raider moloch_denier; + Raider apprentice; + Raider anointed; + Raider champion; + uint256 profit_per_second; +} + +struct Player { + uint256 total_minted; + uint256 created_at; + uint256 last_raided_at; +} \ No newline at end of file diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index 5a702c7..e5a5427 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -1,18 +1,28 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import {Army} from "../src/RaidGeld.sol"; +import {Army} from "../src/RaidGeldStructs.sol"; library RaidGeldUtils { + + uint256 public constant PRECISION = 10000; + + uint256 constant BASE_PRICE = 380000; + uint256 constant PRICE_FACTOR = 11500; + uint256 constant APPRENTICE_PROFIT = 61000; + uint256 constant ANOINTED_PROFIT = 6 * 64000; + uint256 constant CHAMPION_PROFIT = 67000 * 61000 * 64000/ PRECISION / PRECISION; + + function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) { require(unit <= 3, "No matching unit found"); - uint256 rollingPriceCalculation = uint256(unit + 1) * 38; + + uint256 rollingPriceCalculation = uint256(unit + 1) * BASE_PRICE; uint256 price = rollingPriceCalculation; // Each level costs 15% more than previous - uint256 PERCENT_INCREASE = 115; for (uint256 i = 1; i < currentLevel + units; i++) { - rollingPriceCalculation = rollingPriceCalculation * PERCENT_INCREASE / 100; + rollingPriceCalculation = rollingPriceCalculation * PRICE_FACTOR / PRECISION; if (i >= currentLevel) { price += rollingPriceCalculation; } @@ -22,10 +32,15 @@ library RaidGeldUtils { function calculateProfitsPerSecond(Army memory army) internal pure returns (uint256) { // Each next unit scales progressivelly better - uint256 moloch_denier_profit = army.moloch_denier.level; - uint256 apprentice_profit = army.apprentice.level * 61 / 10; - uint256 anointed_profit = army.anointed.level * 6 * 64 / 10; - uint256 champion_profit = army.champion.level * 61 / 10 * 64 / 10 * 67 / 10; + + uint256 moloch_denier_profit = army.moloch_denier.level * PRECISION; + + uint256 apprentice_profit = army.apprentice.level * APPRENTICE_PROFIT; + + uint256 anointed_profit = army.anointed.level * ANOINTED_PROFIT; + + uint256 champion_profit = army.champion.level * CHAMPION_PROFIT; + return moloch_denier_profit + apprentice_profit + anointed_profit + champion_profit; } } diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 6f31f6e..c704c5d 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -118,10 +118,9 @@ contract raid_geldTest is Test { uint256 unit_level = army.moloch_denier.level; uint256 balance = raid_geld.balanceOf(player1); uint256 income_per_sec = army.profit_per_second; - // Add 1 unit raid_geld.addUnit(0, 1); - uint256 unitPrice = RaidGeldUtils.calculateUnitPrice(0, 0, 1) * 10 ** 4; + 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 @@ -134,7 +133,6 @@ contract raid_geldTest is Test { // 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); diff --git a/test/RaidGeldUtils.t.sol b/test/RaidGeldUtils.t.sol index 2c377ed..4ac0f19 100644 --- a/test/RaidGeldUtils.t.sol +++ b/test/RaidGeldUtils.t.sol @@ -9,7 +9,7 @@ contract raid_geldTest is Test { function test_0_unit_price() public { // buying 1 unit of moloch_denier uint256 basePriceMolochDenier = RaidGeldUtils.calculateUnitPrice(0, 0, 1); - assertEq(basePriceMolochDenier, 38); + assertEq(basePriceMolochDenier, RaidGeldUtils.BASE_PRICE); // buying 3 units // has to be a bit more than 3 * 38 = 114 @@ -28,7 +28,7 @@ contract raid_geldTest is Test { assertGt(price, basePriceChamp * 12); } - function test_1_profits_per_second() public { + function test_1_profits_per_second(uint16 _dLvl, uint16 _apLvl, uint16 _anLvl, uint16 _cLvl) public { Army memory army = Army({ moloch_denier: Raider({ level: 1}), apprentice: Raider({ level: 0}), @@ -37,16 +37,21 @@ contract raid_geldTest is Test { profit_per_second: 0 // irrelevant for this test }); uint256 profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army); - assertEq(profits_per_second, 1); + assertEq(profits_per_second, RaidGeldUtils.PRECISION); army = Army({ - moloch_denier: Raider({ level: 2}), - apprentice: Raider({ level: 0}), - anointed: Raider({ level: 10}), - champion: Raider({ level: 1}), + moloch_denier: Raider({ level: _dLvl}), + apprentice: Raider({ level: _apLvl}), + anointed: Raider({ level: _anLvl}), + champion: Raider({ level: _cLvl}), profit_per_second: 0 // irrelevant for this test }); profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army); - assertEq(profits_per_second, 640); + uint256 expected = + _dLvl * RaidGeldUtils.PRECISION + + _apLvl * RaidGeldUtils.APPRENTICE_PROFIT + + _anLvl * RaidGeldUtils.ANOINTED_PROFIT + + _cLvl * RaidGeldUtils.CHAMPION_PROFIT; + assertEq(profits_per_second, expected); } } From 49b7110a6c38f9b85437b0b3b0ba2446f8c078b9 Mon Sep 17 00:00:00 2001 From: yellow <8539006+yellowBirdy@users.noreply.github.com> Date: Fri, 25 Oct 2024 02:21:50 +0200 Subject: [PATCH 2/3] raid before buy in addUnit --- src/RaidGeld.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index e5bf6ba..a70b9e6 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -109,13 +109,13 @@ contract RaidGeld is ERC20, Ownable { } uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units); + // First trigger a raid so player receives what he is due at to this moment + performRaid(msg.sender); require(balanceOf(msg.sender) > cost, "Not enough GELD to add this much"); // TODO: Since we are first minting then burning the token, this could be simplified // by first calculating the difference and then minting / burning in just one operation - // First trigger a raid so player receives what he is due at to this moment - performRaid(msg.sender); // then burn the cost of the new army _burn(msg.sender, cost); From 7a32129f7e0ef920a7066aeb741967e9bf9f2273 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Fri, 25 Oct 2024 11:12:51 +0200 Subject: [PATCH 3/3] Ran forge fmt + solved client bug with amounts being much too high due to change in contract calculations --- app/src/components/Counter.tsx | 12 +++++------- app/src/providers/PlayerProvider.tsx | 2 +- src/RaidGeld.sol | 8 ++++---- src/RaidGeldStructs.sol | 4 +--- src/RaidGeldUtils.sol | 7 ++----- test/RaidGeld.t.sol | 4 ++-- test/RaidGeldUtils.t.sol | 29 +++++++++++++--------------- 7 files changed, 28 insertions(+), 38 deletions(-) diff --git a/app/src/components/Counter.tsx b/app/src/components/Counter.tsx index 5a85ce7..5d924b3 100644 --- a/app/src/components/Counter.tsx +++ b/app/src/components/Counter.tsx @@ -8,14 +8,12 @@ const calculateBalance = (balance: bigint, perSecond: bigint, lastRaidedAt: bigi (new Date()).getTime() - parseInt(lastRaidedAt.toString()) * 1000; return ( balance + - (BigInt(millisecondsSinceLastRaid) * (perSecond * BigInt(10000) /* decimals */) + (BigInt(millisecondsSinceLastRaid) * perSecond / BigInt(1000) /* deduct milliseconds*/)) } -export const toReadable = (value: bigint, applyTokenDivision?: boolean) => { - if (applyTokenDivision) { - value = value / BigInt(10000); - } +export const toReadable = (value: bigint) => { + value = value / BigInt(10000); const suffixes = [ { value: BigInt('1000'), suffix: 'thousand' }, { value: BigInt('1000000'), suffix: 'million' }, @@ -64,8 +62,8 @@ const Counter = () => { balance, army?.profit_per_second ?? BigInt(0), player?.last_raided_at ?? BigInt(0) - ), true); - availableBalance.current = toReadable(balance, true); + )); + availableBalance.current = toReadable(balance); render(); }, 100); return () => clearInterval(tickInterval) diff --git a/app/src/providers/PlayerProvider.tsx b/app/src/providers/PlayerProvider.tsx index c139b5c..8221e9b 100644 --- a/app/src/providers/PlayerProvider.tsx +++ b/app/src/providers/PlayerProvider.tsx @@ -91,7 +91,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => { } }); - console.log(player) + console.log(player, army) const register = useCallback(() => { writeContract({ diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index a70b9e6..52c96a4 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -63,7 +63,7 @@ contract RaidGeld is ERC20, Ownable { // Helper so we can use it when buying units too function performRaid(address player) private { uint256 time_past = block.timestamp - players[player].last_raided_at; - + uint256 new_geld = armies[player].profit_per_second * time_past; // TODO: Pink noise, make it so sometimes its better than expected @@ -109,14 +109,14 @@ contract RaidGeld is ERC20, Ownable { } uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units); - // First trigger a raid so player receives what he is due at to this moment + // First trigger a raid so player receives what he is due at to this moment performRaid(msg.sender); require(balanceOf(msg.sender) > cost, "Not enough GELD to add this much"); - + // TODO: Since we are first minting then burning the token, this could be simplified // by first calculating the difference and then minting / burning in just one operation - // then burn the cost of the new army + // then burn the cost of the new army _burn(msg.sender, cost); // Increase level diff --git a/src/RaidGeldStructs.sol b/src/RaidGeldStructs.sol index c846e87..9846c09 100644 --- a/src/RaidGeldStructs.sol +++ b/src/RaidGeldStructs.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; - - struct Raider { uint16 level; } @@ -19,4 +17,4 @@ struct Player { uint256 total_minted; uint256 created_at; uint256 last_raided_at; -} \ No newline at end of file +} diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index e5a5427..9b6581e 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -4,15 +4,12 @@ pragma solidity ^0.8.13; import {Army} from "../src/RaidGeldStructs.sol"; library RaidGeldUtils { - uint256 public constant PRECISION = 10000; - uint256 constant BASE_PRICE = 380000; uint256 constant PRICE_FACTOR = 11500; uint256 constant APPRENTICE_PROFIT = 61000; uint256 constant ANOINTED_PROFIT = 6 * 64000; - uint256 constant CHAMPION_PROFIT = 67000 * 61000 * 64000/ PRECISION / PRECISION; - + uint256 constant CHAMPION_PROFIT = 67000 * 61000 * 64000 / PRECISION / PRECISION; function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) { require(unit <= 3, "No matching unit found"); @@ -37,7 +34,7 @@ library RaidGeldUtils { uint256 apprentice_profit = army.apprentice.level * APPRENTICE_PROFIT; - uint256 anointed_profit = army.anointed.level * ANOINTED_PROFIT; + uint256 anointed_profit = army.anointed.level * ANOINTED_PROFIT; uint256 champion_profit = army.champion.level * CHAMPION_PROFIT; diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index c704c5d..375fb31 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {RaidGeld, Army, Player} from "../src/RaidGeld.sol"; -import "../src/RaidGeldUtils.sol"; +import "../src/RaidGeldUtils.sol"; contract raid_geldTest is Test { RaidGeld public raid_geld; @@ -124,7 +124,7 @@ contract raid_geldTest is Test { // 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 + // 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); diff --git a/test/RaidGeldUtils.t.sol b/test/RaidGeldUtils.t.sol index 88771fb..8de1385 100644 --- a/test/RaidGeldUtils.t.sol +++ b/test/RaidGeldUtils.t.sol @@ -3,14 +3,14 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {Army, Raider} from "../src/RaidGeld.sol"; -import "../src/RaidGeldUtils.sol"; +import "../src/RaidGeldUtils.sol"; contract raid_geldTest is Test { - function test_0_unit_price() pure public { + function test_0_unit_price() public pure { // buying 1 unit of moloch_denier uint256 basePriceMolochDenier = RaidGeldUtils.calculateUnitPrice(0, 0, 1); assertEq(basePriceMolochDenier, RaidGeldUtils.BASE_PRICE); - + // buying 3 units // has to be a bit more than 3 * 38 = 114 uint256 price = RaidGeldUtils.calculateUnitPrice(0, 0, 3); @@ -30,28 +30,25 @@ contract raid_geldTest is Test { function test_1_profits_per_second(uint16 _dLvl, uint16 _apLvl, uint16 _anLvl, uint16 _cLvl) public { Army memory army = Army({ - moloch_denier: Raider({ level: 1}), - apprentice: Raider({ level: 0}), - anointed: Raider({ level: 0}), - champion: Raider({ level: 0}), + moloch_denier: Raider({level: 1}), + apprentice: Raider({level: 0}), + anointed: Raider({level: 0}), + champion: Raider({level: 0}), profit_per_second: 0 // irrelevant for this test }); uint256 profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army); assertEq(profits_per_second, RaidGeldUtils.PRECISION); army = Army({ - moloch_denier: Raider({ level: _dLvl}), - apprentice: Raider({ level: _apLvl}), - anointed: Raider({ level: _anLvl}), - champion: Raider({ level: _cLvl}), + moloch_denier: Raider({level: _dLvl}), + apprentice: Raider({level: _apLvl}), + anointed: Raider({level: _anLvl}), + champion: Raider({level: _cLvl}), profit_per_second: 0 // irrelevant for this test }); profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army); - uint256 expected = - _dLvl * RaidGeldUtils.PRECISION + - _apLvl * RaidGeldUtils.APPRENTICE_PROFIT + - _anLvl * RaidGeldUtils.ANOINTED_PROFIT + - _cLvl * RaidGeldUtils.CHAMPION_PROFIT; + uint256 expected = _dLvl * RaidGeldUtils.PRECISION + _apLvl * RaidGeldUtils.APPRENTICE_PROFIT + + _anLvl * RaidGeldUtils.ANOINTED_PROFIT + _cLvl * RaidGeldUtils.CHAMPION_PROFIT; assertEq(profits_per_second, expected); } }