WIP adding bosses
This commit is contained in:
parent
52aec1ae9d
commit
1afd975e0f
132
src/RaidGeld.sol
132
src/RaidGeld.sol
@ -5,7 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
import {RaidGeldUtils} from "../src/RaidGeldUtils.sol";
|
||||
import {Army, Player, Raider} from "../src/RaidGeldStructs.sol";
|
||||
import {Army, Player, Raider, Boss} from "../src/RaidGeldStructs.sol";
|
||||
import "../src/Constants.sol";
|
||||
|
||||
contract RaidGeld is ERC20, Ownable, Constants {
|
||||
@ -15,6 +15,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
||||
uint256 public constant INITIAL_GELD = 500 * MANTISSA;
|
||||
mapping(address => Player) private players;
|
||||
mapping(address => Army) private armies;
|
||||
mapping(address => Boss) private bosses;
|
||||
|
||||
// WETH
|
||||
IWETH public immutable weth = IWETH(WETH);
|
||||
@ -25,12 +26,9 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
||||
// Uniswap
|
||||
ISwapRouter02 private constant router = ISwapRouter02(SWAP_ROUTER);
|
||||
// Events
|
||||
|
||||
event PlayerRegistered(address indexed player, uint256 initialGeld);
|
||||
event RaidPerformed(
|
||||
address indexed player,
|
||||
uint256 totalMinted,
|
||||
uint256 geldBalance
|
||||
);
|
||||
event RaidPerformed(address indexed player, uint256 totalMinted, uint256 geldBalance);
|
||||
event UnitAdded(
|
||||
address indexed player,
|
||||
uint8 unitType,
|
||||
@ -49,25 +47,54 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
||||
_;
|
||||
}
|
||||
|
||||
modifier newPlayer() {
|
||||
require(players[msg.sender].created_at == 0, "Whoops, player already exists :)");
|
||||
modifier onlyActiveSession() {
|
||||
require(players[msg.sender].active_session, "Session is not active, you need to buy into the game first");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier newPlay() {
|
||||
Player memory player = players[msg.sender];
|
||||
bool notRegistered = player.created_at == 0;
|
||||
bool returningPlayer = player.has_registered && !player.has_active_session;
|
||||
require(notRegistered || returningPlayer, "Active session already in progress");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(address _daoToken, address _pool) ERC20("Raid Geld", "GELD") Ownable(msg.sender) {
|
||||
daoToken = ERC20(_daoToken);
|
||||
pool = _pool;
|
||||
BUY_IN_DAO_TOKEN_AMOUNT = 50 * 10 ** daoToken.decimals();
|
||||
BUY_IN_DAO_TOKEN_AMOUNT = 500 * 10 ** daoToken.decimals();
|
||||
}
|
||||
|
||||
function init_player(address player) private {
|
||||
bool existing_player = players[player].is_registered;
|
||||
|
||||
// Mint some starting tokens to the player
|
||||
_mint(player, INITIAL_GELD);
|
||||
// Reset or set player
|
||||
reset_player(player);
|
||||
|
||||
if (existing_player) {
|
||||
// TODO: Emit new run
|
||||
} else {
|
||||
// Emit event
|
||||
emit PlayerRegistered(msg.sender, INITIAL_GELD);
|
||||
}
|
||||
}
|
||||
|
||||
function reset_player(address addr) private {
|
||||
Player memory player = players[addr];
|
||||
uint32 current_n_runs = player.n_runs + 1;
|
||||
uint256 current_total_rewards = player.total_rewards;
|
||||
bool has_registered = player.is_registered;
|
||||
// Set initial states
|
||||
players[msg.sender] = Player({
|
||||
total_minted: INITIAL_GELD,
|
||||
created_at: block.timestamp,
|
||||
last_raided_at: block.timestamp
|
||||
last_raided_at: block.timestamp,
|
||||
n_runs: current_n_runs,
|
||||
total_rewards: current_total_rewards,
|
||||
active_session: false
|
||||
});
|
||||
armies[msg.sender] = Army({
|
||||
moloch_denier: Raider({level: 0}),
|
||||
@ -76,13 +103,11 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
||||
champion: Raider({level: 0}),
|
||||
profit_per_second: 0
|
||||
});
|
||||
|
||||
// Emit event
|
||||
emit PlayerRegistered(msg.sender, INITIAL_GELD);
|
||||
bosses[msg.sender] = Boss({level: 0, variant: 0});
|
||||
}
|
||||
|
||||
// New player want to register with ETH
|
||||
function register_eth() external payable newPlayer {
|
||||
function register_eth() external payable newPlay {
|
||||
require(msg.value == BUY_IN_AMOUNT, "Incorrect buy in amount");
|
||||
weth.deposit{value: BUY_IN_AMOUNT}();
|
||||
weth.approve(address(router), BUY_IN_AMOUNT);
|
||||
@ -100,7 +125,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
||||
}
|
||||
|
||||
// New player wants to register with dao
|
||||
function register_dao() external payable newPlayer {
|
||||
function register_dao() external payable newPlay {
|
||||
//@notice this is not safe for arbitrary tokens, which may not follow the interface eg. USDT
|
||||
//@notice but should be fine for the DAO token
|
||||
require(
|
||||
@ -123,25 +148,18 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
||||
}
|
||||
|
||||
// Manual minting for itchy fingers
|
||||
function raid() external onlyPlayer {
|
||||
uint256 totalMinted = performRaid(msg.sender);
|
||||
|
||||
// Emit event
|
||||
emit RaidPerformed(msg.sender, totalMinted, balanceOf(msg.sender));
|
||||
function raid() external onlyActiveSession {
|
||||
performRaid();
|
||||
}
|
||||
|
||||
// Helper so we can use it when buying units too
|
||||
function performRaid(address player) private returns (uint256) {
|
||||
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
|
||||
|
||||
_mint(player, new_geld);
|
||||
players[player].last_raided_at = block.timestamp;
|
||||
players[player].total_minted += new_geld;
|
||||
|
||||
return players[player].total_minted;
|
||||
emit RaidPerformed(player, players[player].total_minted, balanceOf(player));
|
||||
}
|
||||
|
||||
// Function to get Player struct
|
||||
@ -160,7 +178,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
||||
}
|
||||
|
||||
// Add a unit to your army
|
||||
function addUnit(uint8 unit, uint16 n_units) external onlyPlayer {
|
||||
function addUnit(uint8 unit, uint16 n_units) external onlyActiveSession {
|
||||
require(unit <= 3, "Unknown unit");
|
||||
|
||||
Army storage army = armies[msg.sender];
|
||||
@ -179,24 +197,15 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
||||
currentLevel = army.champion.level;
|
||||
}
|
||||
|
||||
uint256 cost = RaidGeldUtils.calculateUnitPrice(
|
||||
unit,
|
||||
currentLevel,
|
||||
n_units
|
||||
);
|
||||
// First trigger a raid so player receives what he is due at to this moment
|
||||
uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units);
|
||||
|
||||
uint256 time_past = block.timestamp -
|
||||
players[msg.sender].last_raided_at;
|
||||
// First trigger a raid so player receives what he is due at to this moment
|
||||
uint256 time_past = block.timestamp - players[msg.sender].last_raided_at;
|
||||
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);
|
||||
|
||||
// Emit event
|
||||
emit RaidPerformed(msg.sender, totalMinted, balanceOf(msg.sender));
|
||||
|
||||
// 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
|
||||
@ -236,10 +245,37 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
||||
);
|
||||
}
|
||||
|
||||
receive() external payable {
|
||||
revert(
|
||||
"No plain Ether accepted, use register() function to check in :)"
|
||||
function battle_with_boss() external onlyActiveSession {
|
||||
// first perform raid
|
||||
performRaid();
|
||||
Boss memory boss_to_attack = bosses[msg.sender];
|
||||
// calculate how much the player will put into battle
|
||||
uint256 geld_to_burn = balanceOf(msg.sender) >= RaidGeldUtils.BOSS_POWERS[boss_to_attack.level]
|
||||
? RaidGeldUtils.BOSS_POWERS[boss_to_attack.level]
|
||||
: balanceOf(msg.sender);
|
||||
bool hasWonBattle = RaidGeldUtils.calculateBossFight(
|
||||
boss_to_attack.level, geld_to_burn, block.timestamp, block.difficulty
|
||||
);
|
||||
if (hasWonBattle) {
|
||||
// Burn geld, get some sweet DAO Token and continue
|
||||
_burn(msg.sender, geld_to_burn);
|
||||
uint256 reward = RaidGeldUtils.calculateBossReward(boss_to_attack.level);
|
||||
players[msg.sender].total_rewards += reward;
|
||||
daoToken.transferFrom(address(this), msg.sender, reward);
|
||||
} else {
|
||||
// Whoops u die
|
||||
player_dies(msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
function player_dies(address player) private {
|
||||
reset_player();
|
||||
players[player].has_active_session = false;
|
||||
_burn(msg.sender, balanceOf(player));
|
||||
}
|
||||
|
||||
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
|
||||
@ -259,10 +295,7 @@ interface ISwapRouter02 {
|
||||
uint160 sqrtPriceLimitX96;
|
||||
}
|
||||
|
||||
function exactInputSingle(ExactInputSingleParams calldata params)
|
||||
external
|
||||
payable
|
||||
returns (uint256 amountOut);
|
||||
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
|
||||
|
||||
struct ExactOutputSingleParams {
|
||||
address tokenIn;
|
||||
@ -274,10 +307,7 @@ interface ISwapRouter02 {
|
||||
uint160 sqrtPriceLimitX96;
|
||||
}
|
||||
|
||||
function exactOutputSingle(ExactOutputSingleParams calldata params)
|
||||
external
|
||||
payable
|
||||
returns (uint256 amountIn);
|
||||
function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);
|
||||
}
|
||||
|
||||
interface IWETH is IERC20 {
|
||||
|
||||
@ -17,4 +17,13 @@ struct Player {
|
||||
uint256 total_minted;
|
||||
uint256 created_at;
|
||||
uint256 last_raided_at;
|
||||
uint256 total_rewards;
|
||||
uint32 n_runs;
|
||||
bool is_registered;
|
||||
bool has_active_session;
|
||||
}
|
||||
|
||||
struct Boss {
|
||||
uint8 variant;
|
||||
uint8 level;
|
||||
}
|
||||
|
||||
@ -19,6 +19,26 @@ library RaidGeldUtils {
|
||||
uint256 constant ANOINTED_BASE_COST = 30096000;
|
||||
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;
|
||||
|
||||
function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) {
|
||||
require(unit <= 3, "No matching unit found");
|
||||
uint256 rollingPriceCalculation = MOLOCH_DENIER_BASE_COST;
|
||||
@ -43,7 +63,6 @@ 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 * MOLOCH_DENIER_PROFIT;
|
||||
uint256 apprentice_profit = army.apprentice.level * APPRENTICE_PROFIT;
|
||||
uint256 anointed_profit = army.anointed.level * ANOINTED_PROFIT;
|
||||
@ -51,4 +70,33 @@ library RaidGeldUtils {
|
||||
|
||||
return moloch_denier_profit + apprentice_profit + anointed_profit + champion_profit;
|
||||
}
|
||||
|
||||
// Returns how much Dao Token player is due for winning
|
||||
function calculateBossReward(uint256 bossLevel) public view returns (uint256) {
|
||||
return BOSS_POWERS[bossLevel] * (CUMULATIVE_BOSS_CHANCES[bossLevel] * 10 / 5) ** 2;
|
||||
}
|
||||
|
||||
// Calculates whether user survives the fight
|
||||
function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 timestamp, uint256 difficulty)
|
||||
internal
|
||||
pure
|
||||
returns (uint256)
|
||||
{
|
||||
uint256 bossPower = BOSS_POWERS[bossLevel];
|
||||
require(geldBurnt <= bossPower, "Cant try to defeat boss with more than what boss power is");
|
||||
uint256 random_n = random(timestamp, difficulty, 1, 100);
|
||||
// Relative power as in, you can only put in 800 geld to defeat 900 geld boss,
|
||||
// but you will get exponentially worse chances
|
||||
uint256 relativePower = ((geldBurnt ** 2) * 100) / bossPower ** 2;
|
||||
uint256 roll = (random_n * relativePower * PRECISION) / (100 * PRECISION);
|
||||
return roll >= bossPower;
|
||||
}
|
||||
|
||||
// TODO: Implement actual randomness
|
||||
function random(uint256 timestamp, uint256 difficulty, uint256 min, uint256 max) internal pure returns (uint256) {
|
||||
// returns 0 - 100
|
||||
require(max >= min, "Max must be greater than or equal to min");
|
||||
uint256 range = max - min + 1;
|
||||
return min + (uint256(keccak256(abi.encodePacked(timestamp, difficulty))) % range);
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,11 +17,7 @@ contract raid_geldTest is Test, Constants {
|
||||
|
||||
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 RaidPerformed(address indexed player, uint256 totalMinted, uint256 geldBalance);
|
||||
event UnitAdded(
|
||||
address indexed player,
|
||||
uint8 unitType,
|
||||
@ -61,7 +57,7 @@ contract raid_geldTest is Test, Constants {
|
||||
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");
|
||||
(bool success,) = address(raid_geld).call{value: 0.1 ether}("0x1234");
|
||||
}
|
||||
|
||||
function test_01_no_receive() public {
|
||||
@ -113,7 +109,7 @@ contract raid_geldTest is Test, Constants {
|
||||
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
|
||||
// doesnt test player emitted event because other events get emitted before it
|
||||
registerPlayerWithDaoToken();
|
||||
|
||||
// Check that initial raid_geld is received by the player
|
||||
@ -136,7 +132,7 @@ contract raid_geldTest is Test, Constants {
|
||||
assertEq(army.apprentice.level, 0);
|
||||
assertEq(army.anointed.level, 0);
|
||||
assertEq(army.champion.level, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function test_03_dao_token_can_be_withdrawn() public {
|
||||
uint256 initialBalance = raid_geld.daoToken().balanceOf(address(raid_geld));
|
||||
@ -144,7 +140,7 @@ contract raid_geldTest is Test, Constants {
|
||||
// Switch to Player 1 and register it
|
||||
vm.startPrank(player1);
|
||||
|
||||
// doesnt test player emitted event because other events get emitted before it
|
||||
// doesnt test player emitted event because other events get emitted before it
|
||||
registerPlayerWithDaoToken();
|
||||
|
||||
// Switch back to owner and withdraw funds
|
||||
@ -211,17 +207,7 @@ contract raid_geldTest is Test, Constants {
|
||||
// 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
|
||||
);
|
||||
emit UnitAdded(address(player1), 0, 1, cost, playerBalance - cost, 1, 0, 0, 0);
|
||||
|
||||
// Add 1 unit
|
||||
raid_geld.addUnit(0, 1);
|
||||
@ -257,17 +243,7 @@ contract raid_geldTest is Test, Constants {
|
||||
// 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
|
||||
);
|
||||
emit UnitAdded(address(player1), 0, 1, cost, playerBalance - cost, 1, 0, 0, 0);
|
||||
|
||||
// bought 1 moloch_denier
|
||||
raid_geld.addUnit(0, 1);
|
||||
@ -282,11 +258,7 @@ contract raid_geldTest is Test, Constants {
|
||||
// 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
|
||||
);
|
||||
emit RaidPerformed(address(player1), player.total_minted + amountMinted, balance + amountMinted);
|
||||
|
||||
// Trigger raid funds minting
|
||||
raid_geld.raid();
|
||||
@ -302,11 +274,7 @@ contract raid_geldTest is Test, Constants {
|
||||
|
||||
amountMinted = army.profit_per_second * 15;
|
||||
|
||||
emit RaidPerformed(
|
||||
address(player1),
|
||||
player.total_minted + amountMinted,
|
||||
balance + amountMinted
|
||||
);
|
||||
emit RaidPerformed(address(player1), player.total_minted + amountMinted, balance + amountMinted);
|
||||
|
||||
raid_geld.raid();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user