idle_moloch/src/RaidGeld.sol
Mitja Belak aaf1c0fd46
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled
Swapping WETH -> RGCVII
2024-10-27 21:20:38 +01:00

203 lines
7.2 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import {RaidGeldUtils} from "../src/RaidGeldUtils.sol";
import {Army, Player, Raider} from "../src/RaidGeldStructs.sol";
import "../src/Constants.sol";
contract RaidGeld is ERC20, Ownable, Constants {
uint256 public constant MANTISSA = 1e4;
uint256 public constant BUY_IN_AMOUNT = 0.0005 ether;
uint256 public immutable BUY_IN_DAO_TOKEN_AMOUNT;
uint256 public constant INITIAL_GELD = 50 * MANTISSA;
mapping(address => Player) private players;
mapping(address => Army) private armies;
// WETH
IERC20 public immutable weth = IERC20(WETH);
// RGCVII token
ERC20 public daoToken;
// RGCVII pool
address public pool;
// Uniswap
ISwapRouter private constant router = ISwapRouter(SWAP_ROUTER);
// Modifier for functions that should only be available to registered players
modifier onlyPlayer() {
require(players[msg.sender].created_at != 0, "Not an initiated player");
_;
}
// Modifier for functions that should only be available to non initialized players
modifier newPlayer() {
require(players[msg.sender].created_at == 0, "Whoops, player already exists :)");
_;
}
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();
}
function init_player(address player) private {
// Mint some starting tokens to the player
_mint(player, INITIAL_GELD);
// Set initial states
players[player] =
Player({total_minted: INITIAL_GELD, created_at: block.timestamp, last_raided_at: block.timestamp});
armies[player] = Army({
moloch_denier: Raider({level: 0}),
apprentice: Raider({level: 0}),
anointed: Raider({level: 0}),
champion: Raider({level: 0}),
profit_per_second: 0
});
}
// New player want to register with ETH
function register_eth() external payable newPlayer {
require(weth.transferFrom(msg.sender, address(this), BUY_IN_AMOUNT), "Make sure to send exactly 0.0005 eth");
TransferHelper.safeApprove(WETH, address(router), BUY_IN_AMOUNT);
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: WETH,
tokenOut: DAO_TOKEN,
fee: 10000,
recipient: address(this),
deadline: block.timestamp,
amountIn: BUY_IN_AMOUNT,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});
router.exactInputSingle(params);
init_player(msg.sender);
}
// New player wants to register with dao
function register_dao() external payable newPlayer {
//@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(
daoToken.transferFrom(msg.sender, address(this), BUY_IN_DAO_TOKEN_AMOUNT),
"Failed to transfer DAO tokens"
);
// Init player
init_player(msg.sender);
}
// Override for default number of decimals
function decimals() public view virtual override returns (uint8) {
return 4;
}
// Allows the owner to withdraw
function withdraw() external onlyOwner {
payable(owner()).transfer(address(this).balance);
}
// Allows the owner to withdraw DAO tokens
function withdraw_dao() external onlyOwner {
uint256 amount = daoToken.balanceOf(address(this));
daoToken.approve(address(this), amount);
daoToken.transferFrom(address(this), owner(), amount);
}
// Manual minting for itchy fingers
function raid() external onlyPlayer {
performRaid(msg.sender);
}
// 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
_mint(player, new_geld);
players[player].last_raided_at = block.timestamp;
players[player].total_minted += new_geld;
}
// Function to get Player struct
function getPlayer(address addr) public view returns (Player memory) {
return players[addr];
}
// Function to get Army struct
function getArmy(address addr) public view returns (Army memory) {
return armies[addr];
}
// Quick fn to check if user is registered
function isRegistered(address addr) public view returns (bool) {
return players[addr].created_at != 0;
}
// Add a unit to your army
function addUnit(uint8 unit, uint16 n_units) external onlyPlayer {
require(unit <= 3, "Unknown unit");
Army storage army = armies[msg.sender];
uint16 currentLevel = 0;
if (unit == 0) {
// moloch_denier
currentLevel = army.moloch_denier.level;
} else if (unit == 1) {
// apprentice
currentLevel = army.apprentice.level;
} else if (unit == 2) {
// anointed
currentLevel = army.anointed.level;
} else if (unit == 3) {
// champion
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 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");
performRaid(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
// then burn the cost of the new army
_burn(msg.sender, cost);
// Increase level
if (unit == 0) {
// moloch_denier
army.moloch_denier.level += n_units;
} else if (unit == 1) {
// apprentice
army.apprentice.level += n_units;
} else if (unit == 2) {
// anointed
army.anointed.level += n_units;
} else if (unit == 3) {
// champion
army.champion.level += n_units;
}
// update profite per second
army.profit_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
}
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");
}
}