Compare commits
7 Commits
1afd975e0f
...
2e5d2143a8
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e5d2143a8 | |||
| 2e6bee24bf | |||
| ecbadf3809 | |||
| 64832617cc | |||
| d976f13a04 | |||
| ee6c9ec710 | |||
|
|
81170fd8ec |
@ -108,7 +108,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
abi,
|
abi,
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
functionName: 'register_eth',
|
functionName: 'register_eth',
|
||||||
value: parseEther("0.0005"),
|
value: parseEther("0.00005"),
|
||||||
}, {
|
}, {
|
||||||
onSuccess: (hash) => {
|
onSuccess: (hash) => {
|
||||||
setHashAndCallback([hash, resetHashAndCallback])
|
setHashAndCallback([hash, resetHashAndCallback])
|
||||||
@ -119,7 +119,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
abi,
|
abi,
|
||||||
address: daoTokenAddress,
|
address: daoTokenAddress,
|
||||||
functionName: 'approve',
|
functionName: 'approve',
|
||||||
args: [contractAddress, parseEther("500")],
|
args: [contractAddress, parseEther("50")],
|
||||||
}, {
|
}, {
|
||||||
onSuccess: (hash) => {
|
onSuccess: (hash) => {
|
||||||
setHashAndCallback([
|
setHashAndCallback([
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import "../src/Constants.sol";
|
|||||||
|
|
||||||
contract RaidGeld is ERC20, Ownable, Constants {
|
contract RaidGeld is ERC20, Ownable, Constants {
|
||||||
uint256 public constant MANTISSA = 1e4;
|
uint256 public constant MANTISSA = 1e4;
|
||||||
uint256 public constant BUY_IN_AMOUNT = 0.0005 ether;
|
uint256 public constant BUY_IN_AMOUNT = 0.00005 ether;
|
||||||
uint256 public immutable BUY_IN_DAO_TOKEN_AMOUNT;
|
uint256 public immutable BUY_IN_DAO_TOKEN_AMOUNT;
|
||||||
uint256 public constant INITIAL_GELD = 500 * MANTISSA;
|
uint256 public constant INITIAL_GELD = 500 * MANTISSA;
|
||||||
mapping(address => Player) private players;
|
mapping(address => Player) private players;
|
||||||
@ -48,14 +48,14 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
modifier onlyActiveSession() {
|
modifier onlyActiveSession() {
|
||||||
require(players[msg.sender].active_session, "Session is not active, you need to buy into the game first");
|
require(players[msg.sender].has_active_session, "Session is not active, you need to buy into the game first");
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier newPlay() {
|
modifier newPlay() {
|
||||||
Player memory player = players[msg.sender];
|
Player memory player = players[msg.sender];
|
||||||
bool notRegistered = player.created_at == 0;
|
bool notRegistered = player.created_at == 0;
|
||||||
bool returningPlayer = player.has_registered && !player.has_active_session;
|
bool returningPlayer = player.is_registered && !player.has_active_session;
|
||||||
require(notRegistered || returningPlayer, "Active session already in progress");
|
require(notRegistered || returningPlayer, "Active session already in progress");
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
@ -66,13 +66,14 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
BUY_IN_DAO_TOKEN_AMOUNT = 500 * 10 ** daoToken.decimals();
|
BUY_IN_DAO_TOKEN_AMOUNT = 500 * 10 ** daoToken.decimals();
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_player(address player) private {
|
function start_game(address player) private {
|
||||||
bool existing_player = players[player].is_registered;
|
bool existing_player = players[player].is_registered;
|
||||||
|
|
||||||
// Mint some starting tokens to the player
|
// Mint some starting tokens to the player
|
||||||
_mint(player, INITIAL_GELD);
|
_mint(player, INITIAL_GELD);
|
||||||
// Reset or set player
|
// Reset or set player
|
||||||
reset_player(player);
|
reset_player(player);
|
||||||
|
players[player].has_active_session = true;
|
||||||
|
|
||||||
if (existing_player) {
|
if (existing_player) {
|
||||||
// TODO: Emit new run
|
// TODO: Emit new run
|
||||||
@ -86,24 +87,27 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
Player memory player = players[addr];
|
Player memory player = players[addr];
|
||||||
uint32 current_n_runs = player.n_runs + 1;
|
uint32 current_n_runs = player.n_runs + 1;
|
||||||
uint256 current_total_rewards = player.total_rewards;
|
uint256 current_total_rewards = player.total_rewards;
|
||||||
bool has_registered = player.is_registered;
|
bool current_is_registered = player.is_registered;
|
||||||
|
uint32 current_prestige_level = player.prestige_level;
|
||||||
// Set initial states
|
// Set initial states
|
||||||
players[msg.sender] = Player({
|
players[addr] = Player({
|
||||||
total_minted: INITIAL_GELD,
|
total_minted: INITIAL_GELD,
|
||||||
created_at: block.timestamp,
|
created_at: block.timestamp,
|
||||||
last_raided_at: block.timestamp,
|
last_raided_at: block.timestamp,
|
||||||
n_runs: current_n_runs,
|
n_runs: current_n_runs,
|
||||||
total_rewards: current_total_rewards,
|
total_rewards: current_total_rewards,
|
||||||
active_session: false
|
has_active_session: false,
|
||||||
|
is_registered: current_is_registered,
|
||||||
|
prestige_level: current_prestige_level
|
||||||
});
|
});
|
||||||
armies[msg.sender] = Army({
|
armies[addr] = Army({
|
||||||
moloch_denier: Raider({level: 0}),
|
moloch_denier: Raider({level: 0}),
|
||||||
apprentice: Raider({level: 0}),
|
apprentice: Raider({level: 0}),
|
||||||
anointed: Raider({level: 0}),
|
anointed: Raider({level: 0}),
|
||||||
champion: Raider({level: 0}),
|
champion: Raider({level: 0}),
|
||||||
profit_per_second: 0
|
profit_per_second: 0
|
||||||
});
|
});
|
||||||
bosses[msg.sender] = Boss({level: 0, variant: 0});
|
bosses[addr] = Boss({level: 0, variants: RaidGeldUtils.generate_boss_variants(block.prevrandao)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// New player want to register with ETH
|
// New player want to register with ETH
|
||||||
@ -121,7 +125,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
sqrtPriceLimitX96: 0
|
sqrtPriceLimitX96: 0
|
||||||
});
|
});
|
||||||
router.exactInputSingle(params);
|
router.exactInputSingle(params);
|
||||||
init_player(msg.sender);
|
start_game(msg.sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
// New player wants to register with dao
|
// New player wants to register with dao
|
||||||
@ -131,8 +135,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
require(
|
require(
|
||||||
daoToken.transferFrom(msg.sender, address(this), BUY_IN_DAO_TOKEN_AMOUNT), "Failed to transfer DAO tokens"
|
daoToken.transferFrom(msg.sender, address(this), BUY_IN_DAO_TOKEN_AMOUNT), "Failed to transfer DAO tokens"
|
||||||
);
|
);
|
||||||
// Init player
|
start_game(msg.sender);
|
||||||
init_player(msg.sender);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override for default number of decimals
|
// Override for default number of decimals
|
||||||
@ -143,13 +146,12 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
// Allows the owner to withdraw DAO tokens
|
// Allows the owner to withdraw DAO tokens
|
||||||
function withdraw() external onlyOwner {
|
function withdraw() external onlyOwner {
|
||||||
uint256 amount = daoToken.balanceOf(address(this));
|
uint256 amount = daoToken.balanceOf(address(this));
|
||||||
daoToken.approve(address(this), amount);
|
daoToken.transfer(owner(), amount);
|
||||||
daoToken.transferFrom(address(this), owner(), amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manual minting for itchy fingers
|
// Manual minting for itchy fingers
|
||||||
function raid() external onlyActiveSession {
|
function raid() external onlyActiveSession {
|
||||||
performRaid();
|
performRaid(msg.sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper so we can use it when buying units too
|
// Helper so we can use it when buying units too
|
||||||
@ -162,16 +164,21 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
emit RaidPerformed(player, players[player].total_minted, balanceOf(player));
|
emit RaidPerformed(player, players[player].total_minted, balanceOf(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get Player struct
|
// Function to get the Player struct
|
||||||
function getPlayer(address addr) public view returns (Player memory) {
|
function getPlayer(address addr) public view returns (Player memory) {
|
||||||
return players[addr];
|
return players[addr];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get Army struct
|
// Function to get the Army struct
|
||||||
function getArmy(address addr) public view returns (Army memory) {
|
function getArmy(address addr) public view returns (Army memory) {
|
||||||
return armies[addr];
|
return armies[addr];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to get the Boss struct
|
||||||
|
function getBoss(address addr) public view returns (Boss memory) {
|
||||||
|
return bosses[addr];
|
||||||
|
}
|
||||||
|
|
||||||
// Quick fn to check if user is registered
|
// Quick fn to check if user is registered
|
||||||
function isRegistered(address addr) public view returns (bool) {
|
function isRegistered(address addr) public view returns (bool) {
|
||||||
return players[addr].created_at != 0;
|
return players[addr].created_at != 0;
|
||||||
@ -203,9 +210,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
uint256 time_past = block.timestamp - players[msg.sender].last_raided_at;
|
uint256 time_past = block.timestamp - players[msg.sender].last_raided_at;
|
||||||
uint256 new_geld = armies[msg.sender].profit_per_second * time_past;
|
uint256 new_geld = armies[msg.sender].profit_per_second * time_past;
|
||||||
require(balanceOf(msg.sender) + new_geld >= cost, "Not enough GELD to add this unit");
|
require(balanceOf(msg.sender) + new_geld >= cost, "Not enough GELD to add this unit");
|
||||||
uint256 totalMinted = performRaid(msg.sender);
|
performRaid(msg.sender);
|
||||||
|
|
||||||
// Emit event
|
|
||||||
|
|
||||||
// TODO: Since we are first minting then burning the token, this could be simplified
|
// 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
|
// by first calculating the difference and then minting / burning in just one operation
|
||||||
@ -245,31 +250,39 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function battle_with_boss() external onlyActiveSession {
|
function battle_with_boss() external onlyActiveSession returns (bool[2] memory hasWonOrAscended) {
|
||||||
// first perform raid
|
// first perform raid
|
||||||
performRaid();
|
performRaid(msg.sender);
|
||||||
Boss memory boss_to_attack = bosses[msg.sender];
|
Boss memory boss_to_attack = bosses[msg.sender];
|
||||||
// calculate how much the player will put into battle
|
// calculate how much the player will put into battle
|
||||||
uint256 geld_to_burn = balanceOf(msg.sender) >= RaidGeldUtils.BOSS_POWERS[boss_to_attack.level]
|
uint256 geld_to_burn = balanceOf(msg.sender) >= RaidGeldUtils.getBossPower(boss_to_attack.level)
|
||||||
? RaidGeldUtils.BOSS_POWERS[boss_to_attack.level]
|
? RaidGeldUtils.getBossPower(boss_to_attack.level)
|
||||||
: balanceOf(msg.sender);
|
: balanceOf(msg.sender);
|
||||||
bool hasWonBattle = RaidGeldUtils.calculateBossFight(
|
bool hasWonBattle = RaidGeldUtils.calculateBossFight(boss_to_attack.level, geld_to_burn, block.prevrandao);
|
||||||
boss_to_attack.level, geld_to_burn, block.timestamp, block.difficulty
|
|
||||||
);
|
|
||||||
if (hasWonBattle) {
|
if (hasWonBattle) {
|
||||||
// Burn geld, get some sweet DAO Token and continue
|
// Burn geld, send some sweet DAO Token and continue
|
||||||
_burn(msg.sender, geld_to_burn);
|
_burn(msg.sender, geld_to_burn);
|
||||||
uint256 reward = RaidGeldUtils.calculateBossReward(boss_to_attack.level);
|
uint256 reward = RaidGeldUtils.calculateBossReward(boss_to_attack.level, BUY_IN_DAO_TOKEN_AMOUNT);
|
||||||
players[msg.sender].total_rewards += reward;
|
players[msg.sender].total_rewards += reward;
|
||||||
daoToken.transferFrom(address(this), msg.sender, reward);
|
daoToken.transferFrom(address(this), msg.sender, reward);
|
||||||
|
if (boss_to_attack.level == 6) {
|
||||||
|
// User ascends! Moloch is defeated, user can start a new run
|
||||||
|
players[msg.sender].prestige_level += 1;
|
||||||
|
player_dies(msg.sender);
|
||||||
|
return [hasWonBattle, true /* New prestige level! */ ];
|
||||||
} else {
|
} else {
|
||||||
// Whoops u die
|
// else go to next boss
|
||||||
|
bosses[msg.sender].level += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Whoops u died, boss defeated you
|
||||||
player_dies(msg.sender);
|
player_dies(msg.sender);
|
||||||
}
|
}
|
||||||
|
return [hasWonBattle, false /* hasnt gotten prestige level */ ];
|
||||||
}
|
}
|
||||||
|
|
||||||
function player_dies(address player) private {
|
function player_dies(address player) private {
|
||||||
reset_player();
|
reset_player(player);
|
||||||
players[player].has_active_session = false;
|
players[player].has_active_session = false;
|
||||||
_burn(msg.sender, balanceOf(player));
|
_burn(msg.sender, balanceOf(player));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,11 +19,12 @@ struct Player {
|
|||||||
uint256 last_raided_at;
|
uint256 last_raided_at;
|
||||||
uint256 total_rewards;
|
uint256 total_rewards;
|
||||||
uint32 n_runs;
|
uint32 n_runs;
|
||||||
|
uint32 prestige_level;
|
||||||
bool is_registered;
|
bool is_registered;
|
||||||
bool has_active_session;
|
bool has_active_session;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Boss {
|
struct Boss {
|
||||||
uint8 variant;
|
|
||||||
uint8 level;
|
uint8 level;
|
||||||
|
uint8[7] variants;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
pragma solidity ^0.8.13;
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
import {Army} from "../src/RaidGeldStructs.sol";
|
import {Army} from "../src/RaidGeldStructs.sol";
|
||||||
|
import {console} from "forge-std/Test.sol";
|
||||||
|
|
||||||
library RaidGeldUtils {
|
library RaidGeldUtils {
|
||||||
uint256 public constant PRECISION = 10000;
|
uint256 public constant PRECISION = 10000;
|
||||||
@ -19,26 +20,48 @@ library RaidGeldUtils {
|
|||||||
uint256 constant ANOINTED_BASE_COST = 30096000;
|
uint256 constant ANOINTED_BASE_COST = 30096000;
|
||||||
uint256 constant CHAMPION_BASE_COST = 255816000;
|
uint256 constant CHAMPION_BASE_COST = 255816000;
|
||||||
|
|
||||||
// Boss lvels
|
|
||||||
uint256[7] constant BOSS_POWERS = [
|
|
||||||
9000000, // 900e4
|
|
||||||
90000000, // 900e5
|
|
||||||
900000000, // 900e6
|
|
||||||
9000000000, // 900e7
|
|
||||||
90000000000, // 900e8
|
|
||||||
900000000000, // 900e9
|
|
||||||
9000000000000 // 900e10
|
|
||||||
];
|
|
||||||
|
|
||||||
// First boss is almost guaranteed, others arent
|
|
||||||
uint256[7] constant BOSS_CHANCES = [99, 89, 80, 70, 62, 51, 40];
|
|
||||||
|
|
||||||
// These are cumulative chances from above with precision 18
|
|
||||||
uint256[7] constant CUMULATIVE_BOSS_CHANCES =
|
|
||||||
[99e16, 8811e14, 7048e14, 493416e12, 30591792e10, 1560181392e8, 6240725568e7];
|
|
||||||
|
|
||||||
uint256 constant RISK_BETA = 15e17;
|
uint256 constant RISK_BETA = 15e17;
|
||||||
|
|
||||||
|
function getBossPower(uint8 level) internal pure returns (uint256 power) {
|
||||||
|
require(level <= 7, "Bosses only go to level 7");
|
||||||
|
if (level == 0) return 9000000;
|
||||||
|
else if (level == 1) return 90000000;
|
||||||
|
else if (level == 2) return 900000000;
|
||||||
|
else if (level == 3) return 9000000000;
|
||||||
|
else if (level == 4) return 90000000000;
|
||||||
|
else if (level == 5) return 900000000000;
|
||||||
|
else if (level == 6) return 9000000000000;
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBossCumulativeChance(uint8 level) internal pure returns (uint256 chance) {
|
||||||
|
// for boss chances (percent) [99, 89, 80, 70, 62, 51, 40]
|
||||||
|
// where cumulative chance is in the form of 18 precision related to 1e18
|
||||||
|
require(level <= 7, "Bosses only go to level 7");
|
||||||
|
if (level == 0) return 99e16;
|
||||||
|
else if (level == 1) return 8811e14;
|
||||||
|
else if (level == 2) return 70488e13;
|
||||||
|
else if (level == 3) return 493416e12;
|
||||||
|
else if (level == 4) return 30591792e10;
|
||||||
|
else if (level == 5) return 1560181392e8;
|
||||||
|
else if (level == 6) return 6240725568e7;
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBossChance(uint8 level) internal pure returns (uint256 chance) {
|
||||||
|
// for boss chances (percent) [99, 89, 80, 70, 62, 51, 40]
|
||||||
|
// where cumulative chance is in the form of 18 precision related to 1e18
|
||||||
|
require(level <= 7, "Bosses only go to level 7");
|
||||||
|
if (level == 0) return 99;
|
||||||
|
else if (level == 1) return 89;
|
||||||
|
else if (level == 2) return 80;
|
||||||
|
else if (level == 3) return 70;
|
||||||
|
else if (level == 4) return 62;
|
||||||
|
else if (level == 5) return 51;
|
||||||
|
else if (level == 6) return 40;
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
|
||||||
function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) {
|
function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) {
|
||||||
require(unit <= 3, "No matching unit found");
|
require(unit <= 3, "No matching unit found");
|
||||||
uint256 rollingPriceCalculation = MOLOCH_DENIER_BASE_COST;
|
uint256 rollingPriceCalculation = MOLOCH_DENIER_BASE_COST;
|
||||||
@ -67,36 +90,48 @@ library RaidGeldUtils {
|
|||||||
uint256 apprentice_profit = army.apprentice.level * APPRENTICE_PROFIT;
|
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;
|
uint256 champion_profit = army.champion.level * CHAMPION_PROFIT;
|
||||||
|
|
||||||
return moloch_denier_profit + apprentice_profit + anointed_profit + champion_profit;
|
return moloch_denier_profit + apprentice_profit + anointed_profit + champion_profit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns how much Dao Token player is due for winning
|
// Returns how much Dao Token player is due for winning
|
||||||
function calculateBossReward(uint256 bossLevel) public view returns (uint256) {
|
function calculateBossReward(uint8 bossLevel, uint256 baseReward) internal pure returns (uint256) {
|
||||||
return BOSS_POWERS[bossLevel] * (CUMULATIVE_BOSS_CHANCES[bossLevel] * 10 / 5) ** 2;
|
// TODO: This could as well just be pre-calculated
|
||||||
|
uint256 cumulativeChance = getBossCumulativeChance(bossLevel); // 0 - 1e18 range
|
||||||
|
uint256 rewardMultiplier = ((2 * (1e18 - cumulativeChance)) ** 2) / 1e18;
|
||||||
|
return (baseReward * rewardMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculates whether user survives the fight
|
// Calculates whether user survives the fight
|
||||||
function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 timestamp, uint256 difficulty)
|
function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 prevrandao) internal pure returns (bool) {
|
||||||
internal
|
uint256 bossPower = getBossPower(bossLevel);
|
||||||
pure
|
uint256 bossRoll = getBossChance(bossLevel);
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
uint256 bossPower = BOSS_POWERS[bossLevel];
|
|
||||||
require(geldBurnt <= bossPower, "Cant try to defeat boss with more than what boss power is");
|
require(geldBurnt <= bossPower, "Cant try to defeat boss with more than what boss power is");
|
||||||
uint256 random_n = random(timestamp, difficulty, 1, 100);
|
uint256 random_n = random(prevrandao, 1, 100);
|
||||||
// Relative power as in, you can only put in 800 geld to defeat 900 geld boss,
|
// Relative power as in, you can only put in 800 geld to defeat 900 geld boss,
|
||||||
// but you will get exponentially worse chances
|
// but you will get exponentially worse chances
|
||||||
uint256 relativePower = ((geldBurnt ** 2) * 100) / bossPower ** 2;
|
uint256 relativePower = ((geldBurnt ** 2) * 100) / bossPower ** 2;
|
||||||
uint256 roll = (random_n * relativePower * PRECISION) / (100 * PRECISION);
|
uint256 roll = random_n * relativePower / 1e2;
|
||||||
return roll >= bossPower;
|
return roll < bossRoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generate_boss_variants(uint256 prevrandao) internal pure returns (uint8[7] memory boss_variants) {
|
||||||
|
// We shuffle the possible variants so each run is a bit different
|
||||||
|
uint8[6] memory array = [0, 1, 2, 3, 4, 5];
|
||||||
|
for (uint256 i = array.length - 1; i > 0; i--) {
|
||||||
|
// generate a pseudo-random index based on the prevrandao
|
||||||
|
uint256 j = uint256(keccak256(abi.encodePacked(prevrandao, i))) % (i + 1);
|
||||||
|
// swap elements at i and j
|
||||||
|
(array[i], array[j]) = (array[j], array[i]);
|
||||||
|
}
|
||||||
|
// Last boss is always the same old super Moloch
|
||||||
|
return [array[0], array[1], array[2], array[3], array[4], array[5], 6];
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement actual randomness
|
// TODO: Implement actual randomness
|
||||||
function random(uint256 timestamp, uint256 difficulty, uint256 min, uint256 max) internal pure returns (uint256) {
|
function random(uint256 prevrandao, uint256 min, uint256 max) internal pure returns (uint256) {
|
||||||
// returns 0 - 100
|
// returns 0 - 100
|
||||||
require(max >= min, "Max must be greater than or equal to min");
|
require(max >= min, "Max must be greater than or equal to min");
|
||||||
uint256 range = max - min + 1;
|
uint256 range = max - min + 1;
|
||||||
return min + (uint256(keccak256(abi.encodePacked(timestamp, difficulty))) % range);
|
return min + (uint256(keccak256(abi.encodePacked(prevrandao))) % range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ pragma solidity ^0.8.13;
|
|||||||
|
|
||||||
import {Test, console} from "forge-std/Test.sol";
|
import {Test, console} from "forge-std/Test.sol";
|
||||||
import {stdStorage, StdStorage} from "forge-std/Test.sol";
|
import {stdStorage, StdStorage} from "forge-std/Test.sol";
|
||||||
import {RaidGeld, Army, Player} from "../src/RaidGeld.sol";
|
import {RaidGeld, Army, Player, Boss} from "../src/RaidGeld.sol";
|
||||||
import "../src/RaidGeldUtils.sol";
|
import "../src/RaidGeldUtils.sol";
|
||||||
import {Constants} from "../src/Constants.sol";
|
import {Constants} from "../src/Constants.sol";
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ contract raid_geldTest is Test, Constants {
|
|||||||
|
|
||||||
function fundAccount(address _acc) private {
|
function fundAccount(address _acc) private {
|
||||||
vm.deal(_acc, 10 ether);
|
vm.deal(_acc, 10 ether);
|
||||||
stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(_acc).checked_write(100 ether);
|
stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(_acc).checked_write(1000 ether);
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerPlayer() private {
|
function registerPlayer() private {
|
||||||
@ -285,4 +285,32 @@ contract raid_geldTest is Test, Constants {
|
|||||||
assertLt(newBalance, newestBalance);
|
assertLt(newBalance, newestBalance);
|
||||||
assertLt(last_raided_at, last_raided_at_2);
|
assertLt(last_raided_at, last_raided_at_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_08_attack_boss() public {
|
||||||
|
// Let some time pass so we dont start at block timestamp 0
|
||||||
|
vm.warp(120);
|
||||||
|
|
||||||
|
// Register player 1
|
||||||
|
vm.startPrank(player1);
|
||||||
|
registerPlayer();
|
||||||
|
raid_geld.addUnit(0, 1);
|
||||||
|
|
||||||
|
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();
|
||||||
|
console.log(results[0]);
|
||||||
|
console.log(results[1]);
|
||||||
|
|
||||||
|
// Should almost always defeat first boss
|
||||||
|
assertEq(results[0], true);
|
||||||
|
// First boss doesnt grant a new prestige level (ascension)
|
||||||
|
assertEq(results[1], false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ pragma solidity ^0.8.13;
|
|||||||
import {Test, console} from "forge-std/Test.sol";
|
import {Test, console} from "forge-std/Test.sol";
|
||||||
import {Army, Raider} from "../src/RaidGeldStructs.sol";
|
import {Army, Raider} from "../src/RaidGeldStructs.sol";
|
||||||
import "../src/RaidGeldUtils.sol";
|
import "../src/RaidGeldUtils.sol";
|
||||||
|
import "@openzeppelin/contracts/utils/math/Math.sol";
|
||||||
|
|
||||||
contract raid_geldTest is Test {
|
contract raid_geldTest is Test {
|
||||||
function test_0_unit_price() public pure {
|
function test_0_unit_price() public pure {
|
||||||
@ -28,7 +29,7 @@ contract raid_geldTest is Test {
|
|||||||
assertGt(price, basePriceChamp * 12);
|
assertGt(price, basePriceChamp * 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_1_profits_per_second(uint16 _dLvl, uint16 _apLvl, uint16 _anLvl, uint16 _cLvl) public {
|
function test_1_profits_per_second(uint16 _dLvl, uint16 _apLvl, uint16 _anLvl, uint16 _cLvl) public pure {
|
||||||
Army memory army = Army({
|
Army memory army = Army({
|
||||||
moloch_denier: Raider({level: 1}),
|
moloch_denier: Raider({level: 1}),
|
||||||
apprentice: Raider({level: 0}),
|
apprentice: Raider({level: 0}),
|
||||||
@ -51,4 +52,64 @@ contract raid_geldTest is Test {
|
|||||||
+ _anLvl * RaidGeldUtils.ANOINTED_PROFIT + _cLvl * RaidGeldUtils.CHAMPION_PROFIT;
|
+ _anLvl * RaidGeldUtils.ANOINTED_PROFIT + _cLvl * RaidGeldUtils.CHAMPION_PROFIT;
|
||||||
assertEq(profits_per_second, expected);
|
assertEq(profits_per_second, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConfidenceInterval(uint8 bossLevel)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (uint256 lower, uint256 upper)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
man i struggled getting this math with just integers so its
|
||||||
|
easier to just do it like this (precalculated):
|
||||||
|
z = 1.96 (95%)
|
||||||
|
Probability | Expected | Variance | Margin | Lower Bound | Upper Bound
|
||||||
|
99% 990 0.00099 62 928 1052
|
||||||
|
89% 890 0.000979 61 829 951
|
||||||
|
80% 800 0.00016 49 751 849
|
||||||
|
70% 700 0.00021 45 655 745
|
||||||
|
62% 620 0.0002356 48 572 668
|
||||||
|
51% 510 0.0002499 49 461 559
|
||||||
|
40% 400 0.00024 48 352 448
|
||||||
|
*/
|
||||||
|
if (bossLevel == 0) {
|
||||||
|
return (928, 1000);
|
||||||
|
} else if (bossLevel == 1) {
|
||||||
|
return (829, 951);
|
||||||
|
} else if (bossLevel == 2) {
|
||||||
|
return (751, 849);
|
||||||
|
} else if (bossLevel == 3) {
|
||||||
|
return (655, 745);
|
||||||
|
} else if (bossLevel == 4) {
|
||||||
|
return (572, 668);
|
||||||
|
} else if (bossLevel == 5) {
|
||||||
|
return (461, 559);
|
||||||
|
} else if (bossLevel == 6) {
|
||||||
|
return (352, 448);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_2_calculateBossFight_probabilities() public {
|
||||||
|
uint256[7] memory probabilities =
|
||||||
|
[uint256(99), uint256(89), uint256(80), uint256(70), uint256(62), uint256(51), uint256(40)];
|
||||||
|
uint256 totalRuns = 1000;
|
||||||
|
console.log("Checking boss rolls (1000 times)");
|
||||||
|
for (uint8 bossLevel = 0; bossLevel < 7; bossLevel++) {
|
||||||
|
console.log("Boss level: ", bossLevel);
|
||||||
|
uint256 bossPower = RaidGeldUtils.getBossPower(bossLevel);
|
||||||
|
uint256 geldBurnt = bossPower;
|
||||||
|
uint256 successCount = 0;
|
||||||
|
for (uint256 testRun = 0; testRun < totalRuns; testRun++) {
|
||||||
|
uint256 prevrandao = uint256(keccak256(abi.encodePacked(block.prevrandao, testRun * 1e9))); // Unique seed each time
|
||||||
|
if (RaidGeldUtils.calculateBossFight(bossLevel, geldBurnt, prevrandao)) {
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(uint256 lowerProb, uint256 upperProb) = getConfidenceInterval(bossLevel);
|
||||||
|
console.log("expected at least wins", lowerProb);
|
||||||
|
console.log("expected at most wins", upperProb);
|
||||||
|
console.log("actual wins", successCount);
|
||||||
|
console.log("----------");
|
||||||
|
vm.assertTrue(successCount >= lowerProb && successCount <= upperProb, "Success rate not within expected range");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user