Contract Name:
CryptoMiningWar
Contract Source Code:
File 1 of 1 : CryptoMiningWar
pragma solidity ^0.4.24;
/*
* CryptoMiningWar - Mining Contest Game
* Author: InspiGames
* Website: https://cryptominingwar.github.io/
*/
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
/**
* @title PullPayment
* @dev Base contract supporting async send for pull payments. Inherit from this
* contract and use asyncSend instead of send or transfer.
*/
contract PullPayment {
using SafeMath for uint256;
mapping(address => uint256) public payments;
uint256 public totalPayments;
/**
* @dev Withdraw accumulated balance, called by payee.
*/
function withdrawPayments() public {
address payee = msg.sender;
uint256 payment = payments[payee];
require(payment != 0);
require(address(this).balance >= payment);
totalPayments = totalPayments.sub(payment);
payments[payee] = 0;
payee.transfer(payment);
}
/**
* @dev Called by the payer to store the sent amount as credit to be pulled.
* @param dest The destination address of the funds.
* @param amount The amount to transfer.
*/
function asyncSend(address dest, uint256 amount) internal {
payments[dest] = payments[dest].add(amount);
totalPayments = totalPayments.add(amount);
}
}
interface MiniGameInterface {
function setupMiniGame(uint256 _miningWarRoundNumber, uint256 _miningWarDeadline) external;
function isContractMiniGame() external pure returns( bool _isContractMiniGame );
}
contract CryptoMiningWar is PullPayment {
bool public initialized = false;
uint256 public roundNumber = 0;
uint256 public deadline;
uint256 public CRTSTAL_MINING_PERIOD = 86400;
uint256 public HALF_TIME = 8 hours;
uint256 public ROUND_TIME = 86400 * 7;
uint256 public prizePool = 0;
uint256 BASE_PRICE = 0.005 ether;
uint256 RANK_LIST_LIMIT = 10000;
uint256 randNonce = 0;
uint256 public totalContractMiniGame = 0;
mapping(uint256 => address) public contractsMiniGameAddress;
//miner info
mapping(uint256 => MinerData) private minerData;
uint256 private numberOfMiners;
// plyer info
mapping(address => PlayerData) public players;
//booster info
uint256 private numberOfBoosts;
mapping(uint256 => BoostData) private boostData;
//mini game contract info
mapping(address => bool) public miniGames;
uint256 private numberOfRank;
address[21] rankList;
address public sponsor;
uint256 public sponsorLevel;
address public administrator;
/*** DATATYPES ***/
struct PlayerData {
uint256 roundNumber;
mapping(uint256 => uint256) minerCount;
uint256 hashrate;
uint256 crystals;
uint256 lastUpdateTime;
uint256 referral_count;
uint256 noQuest;
}
struct MinerData {
uint256 basePrice;
uint256 baseProduct;
uint256 limit;
}
struct BoostData {
address owner;
uint256 boostRate;
uint256 startingLevel;
uint256 startingTime;
uint256 halfLife;
}
modifier isNotOver()
{
require(now <= deadline);
_;
}
modifier disableContract()
{
require(tx.origin == msg.sender);
_;
}
modifier isCurrentRound()
{
require(players[msg.sender].roundNumber == roundNumber);
_;
}
modifier onlyContractsMiniGame()
{
require(miniGames[msg.sender] == true);
_;
}
event eventDoQuest(
uint clientNumber,
uint randomNumber
);
constructor() public {
administrator = msg.sender;
numberOfMiners = 8;
numberOfBoosts = 5;
numberOfRank = 21;
//init miner data
// price, prod. limit
minerData[0] = MinerData(10, 10, 10); //lv1
minerData[1] = MinerData(100, 200, 2); //lv2
minerData[2] = MinerData(400, 800, 4); //lv3
minerData[3] = MinerData(1600, 3200, 8); //lv4
minerData[4] = MinerData(6400, 9600, 16); //lv5
minerData[5] = MinerData(25600, 38400, 32); //lv6
minerData[6] = MinerData(204800, 204800, 64); //lv7
minerData[7] = MinerData(1638400, 819200, 65536); //lv8
}
function () public payable
{
prizePool = SafeMath.add(prizePool, msg.value);
}
function startGame() public
{
require(msg.sender == administrator);
require(!initialized);
startNewRound();
initialized = true;
}
/**
* @dev add crystals to a player
* msg.sender should be in the list of mini game
*/
function addCrystal( address _addr, uint256 _value ) public onlyContractsMiniGame
{
require(players[_addr].roundNumber == roundNumber);
uint256 crystals = SafeMath.mul(_value, CRTSTAL_MINING_PERIOD);
PlayerData storage p = players[_addr];
p.crystals = SafeMath.add( p.crystals, crystals );
}
/**
* @dev sub player's crystals
* msg.sender should be in the list of mini game
* @param _addr player address
*/
function subCrystal( address _addr, uint256 _value ) public onlyContractsMiniGame
{
require(players[_addr].roundNumber == roundNumber);
updateCrystal( _addr );
uint256 crystals = SafeMath.mul(_value,CRTSTAL_MINING_PERIOD);
require(crystals <= players[_addr].crystals);
PlayerData storage p = players[_addr];
p.crystals = SafeMath.sub( p.crystals, crystals );
}
/**
* @dev add hashrate to a player.
* msg.sender should be in the list of mini game
*/
function addHashrate( address _addr, uint256 _value ) public onlyContractsMiniGame
{
require(players[_addr].roundNumber == roundNumber);
PlayerData storage p = players[_addr];
p.hashrate = SafeMath.add( p.hashrate, _value );
}
/**
* @dev sub player's hashrate
* msg.sender should be in the list of mini game
*/
function subHashrate( address _addr, uint256 _value ) public onlyContractsMiniGame
{
require(players[_addr].roundNumber == roundNumber && players[_addr].hashrate >= _value);
PlayerData storage p = players[_addr];
p.hashrate = SafeMath.sub( p.hashrate, _value );
}
function setContractsMiniGame( address _contractMiniGameAddress ) public
{
require(administrator == msg.sender);
MiniGameInterface MiniGame = MiniGameInterface( _contractMiniGameAddress );
bool isContractMiniGame = MiniGame.isContractMiniGame();
require( isContractMiniGame == true );
if ( miniGames[_contractMiniGameAddress] == false ) {
miniGames[_contractMiniGameAddress] = true;
contractsMiniGameAddress[totalContractMiniGame] = _contractMiniGameAddress;
totalContractMiniGame = totalContractMiniGame + 1;
}
}
/**
* @dev remove mini game contract from main contract
* @param _contractMiniGameAddress mini game contract address
*/
function removeContractMiniGame(address _contractMiniGameAddress) public
{
require(administrator == msg.sender);
miniGames[_contractMiniGameAddress] = false;
}
function startNewRound() private
{
deadline = SafeMath.add(now, ROUND_TIME);
roundNumber = SafeMath.add(roundNumber, 1);
initData();
setupMiniGame();
}
function setupMiniGame() private
{
for ( uint256 index = 0; index < totalContractMiniGame; index++ ) {
if (miniGames[contractsMiniGameAddress[index]] == true) {
MiniGameInterface MiniGame = MiniGameInterface( contractsMiniGameAddress[index] );
MiniGame.setupMiniGame(roundNumber,deadline);
}
}
}
function initData() private
{
sponsor = administrator;
sponsorLevel = 6;
//init booster data
boostData[0] = BoostData(0, 150, 1, now, HALF_TIME);
boostData[1] = BoostData(0, 175, 1, now, HALF_TIME);
boostData[2] = BoostData(0, 200, 1, now, HALF_TIME);
boostData[3] = BoostData(0, 225, 1, now, HALF_TIME);
boostData[4] = BoostData(msg.sender, 250, 2, now, HALF_TIME);
for (uint256 idx = 0; idx < numberOfRank; idx++) {
rankList[idx] = 0;
}
}
function lottery() public disableContract
{
require(now > deadline);
uint256 balance = SafeMath.div(SafeMath.mul(prizePool, 90), 100);
uint256 devFee = SafeMath.div(SafeMath.mul(prizePool, 5), 100);
asyncSend(administrator, devFee);
uint8[10] memory profit = [30,20,10,8,7,5,5,5,5,5];
uint256 totalPayment = 0;
uint256 rankPayment = 0;
for(uint256 idx = 0; idx < 10; idx++){
if(rankList[idx] != 0){
rankPayment = SafeMath.div(SafeMath.mul(balance, profit[idx]),100);
asyncSend(rankList[idx], rankPayment);
totalPayment = SafeMath.add(totalPayment, rankPayment);
}
}
prizePool = SafeMath.add(devFee, SafeMath.sub(balance, totalPayment));
startNewRound();
}
function getRankList() public view returns(address[21])
{
return rankList;
}
//sponser
function becomeSponsor() public isNotOver payable
{
require(msg.value >= getSponsorFee());
require(msg.sender != sponsor);
uint256 sponsorPrice = getCurrentPrice(sponsorLevel);
asyncSend(sponsor, sponsorPrice);
prizePool = SafeMath.add(prizePool, SafeMath.sub(msg.value, sponsorPrice));
sponsor = msg.sender;
sponsorLevel = SafeMath.add(sponsorLevel, 1);
}
function getSponsorFee() public view returns(uint256 sponsorFee)
{
sponsorFee = getCurrentPrice(SafeMath.add(sponsorLevel, 1));
}
//--------------------------------------------------------------------------
// Miner
//--------------------------------------------------------------------------
/**
* @dev get a free miner
*/
function getFreeMiner() public disableContract isNotOver
{
require(players[msg.sender].roundNumber != roundNumber);
PlayerData storage p = players[msg.sender];
//reset player data
if(p.hashrate > 0){
for (uint idx = 1; idx < numberOfMiners; idx++) {
p.minerCount[idx] = 0;
}
}
MinerData storage m0 = minerData[0];
p.crystals = 0;
p.roundNumber = roundNumber;
//free miner
p.lastUpdateTime = now;
p.referral_count = 0;
p.noQuest = 0;
p.minerCount[0] = 1;
p.hashrate = m0.baseProduct;
}
function doQuest(uint256 clientNumber) disableContract isCurrentRound isNotOver public
{
PlayerData storage p = players[msg.sender];
p.noQuest = SafeMath.add(p.noQuest, 1);
uint256 randomNumber = getRandomNumber(msg.sender);
if(clientNumber == randomNumber) {
p.referral_count = SafeMath.add(p.referral_count, 1);
}
emit eventDoQuest(clientNumber, randomNumber);
}
function buyMiner(uint256[] minerNumbers) public isNotOver isCurrentRound
{
require(minerNumbers.length == numberOfMiners);
uint256 minerIdx = 0;
MinerData memory m;
for (; minerIdx < numberOfMiners; minerIdx++) {
m = minerData[minerIdx];
if(minerNumbers[minerIdx] > m.limit || minerNumbers[minerIdx] < 0){
revert();
}
}
updateCrystal(msg.sender);
PlayerData storage p = players[msg.sender];
uint256 price = 0;
uint256 minerNumber = 0;
for (minerIdx = 0; minerIdx < numberOfMiners; minerIdx++) {
minerNumber = minerNumbers[minerIdx];
if (minerNumber > 0) {
m = minerData[minerIdx];
price = SafeMath.add(price, SafeMath.mul(m.basePrice, minerNumber));
}
}
price = SafeMath.mul(price, CRTSTAL_MINING_PERIOD);
if(p.crystals < price){
revert();
}
p.crystals = SafeMath.sub(p.crystals, price);
uint256 hashrate = 0;
for (minerIdx = 0; minerIdx < numberOfMiners; minerIdx++) {
minerNumber = minerNumbers[minerIdx];
if (minerNumber > 0) {
m = minerData[minerIdx];
uint256 currentMinerCount = p.minerCount[minerIdx];
p.minerCount[minerIdx] = SafeMath.min(m.limit, SafeMath.add(p.minerCount[minerIdx], minerNumber));
// calculate no hashrate you want buy
hashrate = SafeMath.add(hashrate, SafeMath.mul(SafeMath.sub(p.minerCount[minerIdx],currentMinerCount), minerData[minerIdx].baseProduct));
}
}
updateHashrate(msg.sender, hashrate);
}
function getPlayerData(address addr) public view
returns (uint256 crystals, uint256 lastupdate, uint256 hashratePerDay, uint256[8] miners, uint256 hasBoost, uint256 referral_count, uint256 playerBalance, uint256 noQuest )
{
PlayerData storage p = players[addr];
if(p.roundNumber != roundNumber){
p = players[0];
}
crystals = SafeMath.div(p.crystals, CRTSTAL_MINING_PERIOD);
lastupdate = p.lastUpdateTime;
hashratePerDay = addReferralHashrate(addr, p.hashrate);
uint256 i = 0;
for(i = 0; i < numberOfMiners; i++)
{
miners[i] = p.minerCount[i];
}
hasBoost = hasBooster(addr);
referral_count = p.referral_count;
noQuest = p.noQuest;
playerBalance = payments[addr];
}
function getHashratePerDay(address minerAddr) public view returns (uint256 personalProduction)
{
PlayerData storage p = players[minerAddr];
personalProduction = addReferralHashrate(minerAddr, p.hashrate);
uint256 boosterIdx = hasBooster(minerAddr);
if (boosterIdx != 999) {
BoostData storage b = boostData[boosterIdx];
personalProduction = SafeMath.div(SafeMath.mul(personalProduction, b.boostRate), 100);
}
}
//--------------------------------------------------------------------------
// BOOSTER
//--------------------------------------------------------------------------
function buyBooster(uint256 idx) public isNotOver isCurrentRound payable
{
require(idx < numberOfBoosts);
BoostData storage b = boostData[idx];
if(msg.value < getBoosterPrice(idx) || msg.sender == b.owner){
revert();
}
address beneficiary = b.owner;
uint256 devFeePrize = devFee(getBoosterPrice(idx));
asyncSend(sponsor, devFeePrize);
uint256 refundPrize = 0;
if(beneficiary != 0){
refundPrize = SafeMath.div(SafeMath.mul(getBoosterPrice(idx), 55), 100);
asyncSend(beneficiary, refundPrize);
}
prizePool = SafeMath.add(prizePool, SafeMath.sub(msg.value, SafeMath.add(devFeePrize, refundPrize)));
updateCrystal(msg.sender);
updateCrystal(beneficiary);
uint256 level = getCurrentLevel(b.startingLevel, b.startingTime, b.halfLife);
b.startingLevel = SafeMath.add(level, 1);
b.startingTime = now;
// transfer ownership
b.owner = msg.sender;
}
function getBoosterData(uint256 idx) public view returns (address owner,uint256 boostRate, uint256 startingLevel,
uint256 startingTime, uint256 currentPrice, uint256 halfLife)
{
require(idx < numberOfBoosts);
owner = boostData[idx].owner;
boostRate = boostData[idx].boostRate;
startingLevel = boostData[idx].startingLevel;
startingTime = boostData[idx].startingTime;
currentPrice = getBoosterPrice(idx);
halfLife = boostData[idx].halfLife;
}
function getBoosterPrice(uint256 index) public view returns (uint256)
{
BoostData storage booster = boostData[index];
return getCurrentPrice(getCurrentLevel(booster.startingLevel, booster.startingTime, booster.halfLife));
}
function hasBooster(address addr) public view returns (uint256 boostIdx)
{
boostIdx = 999;
for(uint256 i = 0; i < numberOfBoosts; i++){
uint256 revert_i = numberOfBoosts - i - 1;
if(boostData[revert_i].owner == addr){
boostIdx = revert_i;
break;
}
}
}
//--------------------------------------------------------------------------
// Other
//--------------------------------------------------------------------------
function devFee(uint256 amount) public pure returns(uint256)
{
return SafeMath.div(SafeMath.mul(amount, 5), 100);
}
function getBalance() public view returns(uint256)
{
return address(this).balance;
}
//@dev use this function in case of bug
function upgrade(address addr) public
{
require(msg.sender == administrator);
selfdestruct(addr);
}
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @param addr is player address you want add hash rate
* @param _hashrate is no hashrate you want add for this player
*/
function updateHashrate(address addr, uint256 _hashrate) private
{
PlayerData storage p = players[addr];
p.hashrate = SafeMath.add(p.hashrate, _hashrate);
if(p.hashrate > RANK_LIST_LIMIT){
updateRankList(addr);
}
}
function updateCrystal(address addr) private
{
require(now > players[addr].lastUpdateTime);
if (players[addr].lastUpdateTime != 0) {
PlayerData storage p = players[addr];
uint256 secondsPassed = SafeMath.sub(now, p.lastUpdateTime);
uint256 revenue = getHashratePerDay(addr);
p.lastUpdateTime = now;
if (revenue > 0) {
revenue = SafeMath.mul(revenue, secondsPassed);
p.crystals = SafeMath.add(p.crystals, revenue);
}
}
}
function addReferralHashrate(address addr, uint256 hashrate) private view returns(uint256 personalProduction)
{
PlayerData storage p = players[addr];
if(p.referral_count < 5){
personalProduction = SafeMath.add(hashrate, SafeMath.mul(p.referral_count, 10));
}else if(p.referral_count < 10){
personalProduction = SafeMath.add(hashrate, SafeMath.add(50, SafeMath.mul(p.referral_count, 10)));
}else{
personalProduction = SafeMath.add(hashrate, 200);
}
}
function getCurrentLevel(uint256 startingLevel, uint256 startingTime, uint256 halfLife) private view returns(uint256)
{
uint256 timePassed=SafeMath.sub(now, startingTime);
uint256 levelsPassed=SafeMath.div(timePassed, halfLife);
if (startingLevel < levelsPassed) {
return 0;
}
return SafeMath.sub(startingLevel, levelsPassed);
}
function getCurrentPrice(uint256 currentLevel) private view returns(uint256)
{
return SafeMath.mul(BASE_PRICE, 2**currentLevel);
}
function updateRankList(address addr) private returns(bool)
{
uint256 idx = 0;
PlayerData storage insert = players[addr];
PlayerData storage lastOne = players[rankList[19]];
if(insert.hashrate < lastOne.hashrate) {
return false;
}
address[21] memory tempList = rankList;
if(!inRankList(addr)){
tempList[20] = addr;
quickSort(tempList, 0, 20);
}else{
quickSort(tempList, 0, 19);
}
for(idx = 0;idx < 21; idx++){
if(tempList[idx] != rankList[idx]){
rankList[idx] = tempList[idx];
}
}
return true;
}
function inRankList(address addr) internal view returns(bool)
{
for(uint256 idx = 0;idx < 20; idx++){
if(addr == rankList[idx]){
return true;
}
}
return false;
}
function getRandomNumber(address playerAddress) internal returns(uint256 randomNumber) {
randNonce++;
randomNumber = uint256(keccak256(abi.encodePacked(now, playerAddress, randNonce))) % 3;
}
function quickSort(address[21] list, int left, int right) internal
{
int i = left;
int j = right;
if(i == j) return;
address addr = list[uint(left + (right - left) / 2)];
PlayerData storage p = players[addr];
while (i <= j) {
while (players[list[uint(i)]].hashrate > p.hashrate) i++;
while (p.hashrate > players[list[uint(j)]].hashrate) j--;
if (i <= j) {
(list[uint(i)], list[uint(j)]) = (list[uint(j)], list[uint(i)]);
i++;
j--;
}
}
if (left < j)
quickSort(list, left, j);
if (i < right)
quickSort(list, i, right);
}
}