// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
interface Token {
function allowance(address, address) external view returns (uint256);
function balanceOf(address) external view returns (uint256);
function transfer(address, uint256) external returns (bool);
function transferFrom(address, address, uint256) external returns (bool);
}
contract War {
uint256 constant private SLOTS = 12;
uint256 constant private TIME_PER_SLOT = 5 minutes;
uint256 constant private ROUND_GAP = 60 minutes;
uint256 constant private BUY_EXPONENT_BASE = 2; // x2 per buy
uint256 constant private POT_FEE_PERCENT = 25; // 1/4 of buy
uint256 constant private BURN_FEE_PERCENT = 10; // 1/10 of pot
uint256 private initialCost;
enum Status { READY, OPEN, CLOSED }
uint256 public totalRounds;
uint256 public roundEnd;
uint256 public lastSlot;
address[SLOTS] public lastBuyers;
uint256[SLOTS] public buys;
Token public token;
address public owner;
address public burnAddress;
mapping(address => bool) public whitelisted;
event RoundStarted(uint256 indexed round);
event SlotPurchased(uint256 indexed slot, address indexed buyer, uint256 cost);
event PotWon(uint256 indexed round, address indexed winner, uint256 amount);
modifier _onlyOwner() {
require(msg.sender == owner);
_;
}
constructor(Token _token, uint256 _initialCost) {
token = _token;
initialCost = _initialCost;
roundEnd = block.timestamp;
owner = msg.sender;
}
function setOwner(address _owner) external _onlyOwner {
owner = _owner;
}
function setBurnAddress(address _burnAddress) external _onlyOwner {
burnAddress = _burnAddress;
}
function setWhitelisted(address _address, bool _isWhitelisted) external _onlyOwner {
whitelisted[_address] = _isWhitelisted;
}
function buySlot(uint256 _index, uint256 _maxBuys) external {
require(msg.sender == tx.origin || whitelisted[msg.sender]);
Status _status = currentStatus();
require(_status != Status.CLOSED);
if (_status == Status.READY) {
uint256 _balance = token.balanceOf(address(this));
if (_balance > 0) {
if (burnAddress == address(0x0)) {
token.transfer(lastBuyers[lastSlot], _balance);
emit PotWon(totalRounds, lastBuyers[lastSlot], _balance);
} else {
uint256 _burnFee = _balance * BURN_FEE_PERCENT / 100;
token.transfer(burnAddress, _burnFee);
token.transfer(lastBuyers[lastSlot], _balance - _burnFee);
emit PotWon(totalRounds, lastBuyers[lastSlot], _balance - _burnFee);
}
}
delete lastBuyers;
delete buys;
totalRounds++;
emit RoundStarted(totalRounds);
}
require(_index < SLOTS);
(uint256 _count, uint256 _price) = currentSlotPrice(_index);
require(_count <= _maxBuys);
if (_count == 0) {
token.transferFrom(msg.sender, address(this), _price);
} else {
uint256 _potFee = _price * POT_FEE_PERCENT / 100;
token.transferFrom(msg.sender, address(this), _potFee);
token.transferFrom(msg.sender, lastBuyers[_index], _price - _potFee);
}
roundEnd = block.timestamp + slotTime(_index);
lastSlot = _index;
lastBuyers[_index] = msg.sender;
buys[_index]++;
emit SlotPurchased(_index, msg.sender, _price);
}
function currentPot() public view returns (uint256) {
return token.balanceOf(address(this));
}
function currentStatus() public view returns (Status) {
return block.timestamp < roundEnd ? Status.OPEN : block.timestamp < roundEnd + ROUND_GAP ? Status.CLOSED : Status.READY;
}
function currentSlotPrice(uint256 _index) public view returns (uint256 count, uint256 price) {
require(_index < SLOTS);
count = buys[_index];
price = initialCost * BUY_EXPONENT_BASE**count;
}
function slotTime(uint256 _index) public pure returns (uint256) {
return TIME_PER_SLOT * (_index + 1);
}
function allInfoFor(address _user) external view returns (uint256[5] memory compressedInfo, address[SLOTS] memory buyers, uint256[SLOTS] memory allBuys, uint256 userBalance, uint256 userAllowance) {
return (_compressedInfo(), lastBuyers, buys, token.balanceOf(_user), token.allowance(_user, address(this)));
}
function _compressedInfo() internal view returns (uint256[5] memory info) {
info[0] = totalRounds;
info[1] = currentPot();
info[2] = roundEnd;
info[3] = lastSlot;
info[4] = initialCost;
}
}
{
"compilationTarget": {
"War.sol": "War"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"contract Token","name":"_token","type":"address"},{"internalType":"uint256","name":"_initialCost","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"winner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PotWon","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"}],"name":"RoundStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"slot","type":"uint256"},{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"cost","type":"uint256"}],"name":"SlotPurchased","type":"event"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"allInfoFor","outputs":[{"internalType":"uint256[5]","name":"compressedInfo","type":"uint256[5]"},{"internalType":"address[12]","name":"buyers","type":"address[12]"},{"internalType":"uint256[12]","name":"allBuys","type":"uint256[12]"},{"internalType":"uint256","name":"userBalance","type":"uint256"},{"internalType":"uint256","name":"userAllowance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burnAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_index","type":"uint256"},{"internalType":"uint256","name":"_maxBuys","type":"uint256"}],"name":"buySlot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"buys","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentPot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"currentSlotPrice","outputs":[{"internalType":"uint256","name":"count","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentStatus","outputs":[{"internalType":"enum War.Status","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"lastBuyers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundEnd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_burnAddress","type":"address"}],"name":"setBurnAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"bool","name":"_isWhitelisted","type":"bool"}],"name":"setWhitelisted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"slotTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract Token","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalRounds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]