/** * Source Code first verified at https://etherscan.io on Monday, March 18, 2019 (UTC) */ pragma solidity ^0.4.25; /* * CryptoMiningWar - Blockchain-based strategy 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; } } 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 CryptoMiningWarInterface { function calCurrentCrystals(address /*_addr*/) external view returns(uint256 /*_currentCrystals*/); function subCrystal( address /*_addr*/, uint256 /*_value*/ ) external pure; function fallback() external payable; function isMiningWarContract() external pure returns(bool); } interface MiniGameInterface { function isContractMiniGame() external pure returns( bool _isContractMiniGame ); function fallback() external payable; } contract CryptoEngineer is PullPayment{ // engineer info address public administrator; uint256 public prizePool = 0; uint256 public numberOfEngineer = 8; uint256 public numberOfBoosts = 5; address public gameSponsor; uint256 public gameSponsorPrice = 0.32 ether; uint256 public VIRUS_MINING_PERIOD = 86400; // mining war game infomation uint256 public CRTSTAL_MINING_PERIOD = 86400; uint256 public BASE_PRICE = 0.01 ether; address public miningWarAddress; CryptoMiningWarInterface public MiningWar; // engineer player information mapping(address => Player) public players; // engineer boost information mapping(uint256 => BoostData) public boostData; // engineer information mapping(uint256 => EngineerData) public engineers; // minigame info mapping(address => bool) public miniGames; struct Player { mapping(uint256 => uint256) engineersCount; uint256 virusNumber; uint256 research; uint256 lastUpdateTime; bool endLoadOldData; } struct BoostData { address owner; uint256 boostRate; uint256 basePrice; } struct EngineerData { uint256 basePrice; uint256 baseETH; uint256 baseResearch; uint256 limit; } modifier disableContract() { require(tx.origin == msg.sender); _; } modifier isAdministrator() { require(msg.sender == administrator); _; } modifier onlyContractsMiniGame() { require(miniGames[msg.sender] == true); _; } event BuyEngineer(address _addr, uint256[8] engineerNumbers, uint256 _crytalsPrice, uint256 _ethPrice, uint256 _researchBuy); event BuyBooster(address _addr, uint256 _boostIdx, address beneficiary); event ChangeVirus(address _addr, uint256 _virus, uint256 _type); // 1: add, 2: sub event BecomeGameSponsor(address _addr, uint256 _price); event UpdateResearch(address _addr, uint256 _currentResearch); //-------------------------------------------------------------------------- // INIT CONTRACT //-------------------------------------------------------------------------- constructor() public { administrator = msg.sender; initBoostData(); initEngineer(); // set interface main contract setMiningWarInterface(0x65c347702b66ff8f1a28cf9a9768487fbe97765f); } function initEngineer() private { // price crystals price ETH research limit engineers[0] = EngineerData(10, BASE_PRICE * 0, 10, 10 ); //lv1 engineers[1] = EngineerData(50, BASE_PRICE * 1, 3356, 2 ); //lv2 engineers[2] = EngineerData(200, BASE_PRICE * 2, 8390, 4 ); //lv3 engineers[3] = EngineerData(800, BASE_PRICE * 4, 20972, 8 ); //lv4 engineers[4] = EngineerData(3200, BASE_PRICE * 8, 52430, 16 ); //lv5 engineers[5] = EngineerData(12800, BASE_PRICE * 16, 131072, 32 ); //lv6 engineers[6] = EngineerData(102400, BASE_PRICE * 32, 327680, 64 ); //lv7 engineers[7] = EngineerData(819200, BASE_PRICE * 64, 819200, 65536); //lv8 } function initBoostData() private { boostData[0] = BoostData(0x0, 150, BASE_PRICE * 1); boostData[1] = BoostData(0x0, 175, BASE_PRICE * 2); boostData[2] = BoostData(0x0, 200, BASE_PRICE * 4); boostData[3] = BoostData(0x0, 225, BASE_PRICE * 8); boostData[4] = BoostData(0x0, 250, BASE_PRICE * 16); } /** * @dev MainContract used this function to verify game's contract */ function isContractMiniGame() public pure returns(bool _isContractMiniGame) { _isContractMiniGame = true; } function isEngineerContract() public pure returns(bool) { return true; } function () public payable { addPrizePool(msg.value); } /** * @dev Main Contract call this function to setup mini game. */ function setupMiniGame( uint256 /*_miningWarRoundNumber*/, uint256 /*_miningWarDeadline*/ ) public { require(msg.sender == miningWarAddress); MiningWar.fallback.value(SafeMath.div(SafeMath.mul(prizePool, 5), 100))(); prizePool = SafeMath.sub(prizePool, SafeMath.div(SafeMath.mul(prizePool, 5), 100)); } //-------------------------------------------------------------------------- // SETTING CONTRACT MINI GAME //-------------------------------------------------------------------------- function setMiningWarInterface(address _addr) public isAdministrator { CryptoMiningWarInterface miningWarInterface = CryptoMiningWarInterface(_addr); require(miningWarInterface.isMiningWarContract() == true); miningWarAddress = _addr; MiningWar = miningWarInterface; } function setContractsMiniGame( address _addr ) public isAdministrator { MiniGameInterface MiniGame = MiniGameInterface( _addr ); if( MiniGame.isContractMiniGame() == false ) { revert(); } miniGames[_addr] = true; } /** * @dev remove mini game contract from main contract * @param _addr mini game contract address */ function removeContractMiniGame(address _addr) public isAdministrator { miniGames[_addr] = false; } //@dev use this function in case of bug function upgrade(address addr) public isAdministrator { selfdestruct(addr); } //-------------------------------------------------------------------------- // BOOSTER //-------------------------------------------------------------------------- function buyBooster(uint256 idx) public payable { require(idx < numberOfBoosts); BoostData storage b = boostData[idx]; if (msg.value < b.basePrice || msg.sender == b.owner) revert(); address beneficiary = b.owner; uint256 devFeePrize = devFee(b.basePrice); distributedToOwner(devFeePrize); addMiningWarPrizePool(devFeePrize); addPrizePool(SafeMath.sub(msg.value, SafeMath.mul(devFeePrize,3))); updateVirus(msg.sender); if ( beneficiary != 0x0 ) updateVirus(beneficiary); // transfer ownership b.owner = msg.sender; emit BuyBooster(msg.sender, idx, beneficiary ); } function getBoosterData(uint256 idx) public view returns (address _owner,uint256 _boostRate, uint256 _basePrice) { require(idx < numberOfBoosts); BoostData memory b = boostData[idx]; _owner = b.owner; _boostRate = b.boostRate; _basePrice = b.basePrice; } 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; } } } //-------------------------------------------------------------------------- // GAME SPONSOR //-------------------------------------------------------------------------- /** */ function becomeGameSponsor() public payable disableContract { uint256 gameSponsorPriceFee = SafeMath.div(SafeMath.mul(gameSponsorPrice, 150), 100); require(msg.value >= gameSponsorPriceFee); require(msg.sender != gameSponsor); // uint256 repayPrice = SafeMath.div(SafeMath.mul(gameSponsorPrice, 110), 100); gameSponsor.transfer(repayPrice); // add to prize pool addPrizePool(SafeMath.sub(msg.value, repayPrice)); // update game sponsor info gameSponsor = msg.sender; gameSponsorPrice = gameSponsorPriceFee; emit BecomeGameSponsor(msg.sender, msg.value); } function addEngineer(address _addr, uint256 idx, uint256 _value) public isAdministrator { require(idx < numberOfEngineer); require(_value != 0); Player storage p = players[_addr]; EngineerData memory e = engineers[idx]; if (SafeMath.add(p.engineersCount[idx], _value) > e.limit) revert(); updateVirus(_addr); p.engineersCount[idx] = SafeMath.add(p.engineersCount[idx], _value); updateResearch(_addr, SafeMath.mul(_value, e.baseResearch)); } // ---------------------------------------------------------------------------------------- // USING FOR MINI GAME CONTRACT // --------------------------------------------------------------------------------------- function setBoostData(uint256 idx, address owner, uint256 boostRate, uint256 basePrice) public onlyContractsMiniGame { require(owner != 0x0); BoostData storage b = boostData[idx]; b.owner = owner; b.boostRate = boostRate; b.basePrice = basePrice; } function setGameSponsorInfo(address _addr, uint256 _value) public onlyContractsMiniGame { gameSponsor = _addr; gameSponsorPrice = _value; } function setPlayerLastUpdateTime(address _addr) public onlyContractsMiniGame { require(players[_addr].endLoadOldData == false); players[_addr].lastUpdateTime = now; players[_addr].endLoadOldData = true; } function setPlayerEngineersCount( address _addr, uint256 idx, uint256 _value) public onlyContractsMiniGame { players[_addr].engineersCount[idx] = _value; } function setPlayerResearch(address _addr, uint256 _value) public onlyContractsMiniGame { players[_addr].research = _value; } function setPlayerVirusNumber(address _addr, uint256 _value) public onlyContractsMiniGame { players[_addr].virusNumber = _value; } function addResearch(address _addr, uint256 _value) public onlyContractsMiniGame { updateVirus(_addr); Player storage p = players[_addr]; p.research = SafeMath.add(p.research, _value); emit UpdateResearch(_addr, p.research); } function subResearch(address _addr, uint256 _value) public onlyContractsMiniGame { updateVirus(_addr); Player storage p = players[_addr]; if (p.research < _value) revert(); p.research = SafeMath.sub(p.research, _value); emit UpdateResearch(_addr, p.research); } /** * @dev add virus for player * @param _addr player address * @param _value number of virus */ function addVirus(address _addr, uint256 _value) public onlyContractsMiniGame { Player storage p = players[_addr]; uint256 additionalVirus = SafeMath.mul(_value,VIRUS_MINING_PERIOD); p.virusNumber = SafeMath.add(p.virusNumber, additionalVirus); emit ChangeVirus(_addr, _value, 1); } /** * @dev subtract virus of player * @param _addr player address * @param _value number virus subtract */ function subVirus(address _addr, uint256 _value) public onlyContractsMiniGame { updateVirus(_addr); Player storage p = players[_addr]; uint256 subtractVirus = SafeMath.mul(_value,VIRUS_MINING_PERIOD); if ( p.virusNumber < subtractVirus ) { revert(); } p.virusNumber = SafeMath.sub(p.virusNumber, subtractVirus); emit ChangeVirus(_addr, _value, 2); } /** * @dev claim price pool to next new game * @param _addr mini game contract address * @param _value eth claim; */ function claimPrizePool(address _addr, uint256 _value) public onlyContractsMiniGame { require(prizePool > _value); prizePool = SafeMath.sub(prizePool, _value); MiniGameInterface MiniGame = MiniGameInterface( _addr ); MiniGame.fallback.value(_value)(); } //-------------------------------------------------------------------------- // PLAYERS //-------------------------------------------------------------------------- /** */ function buyEngineer(uint256[8] engineerNumbers) public payable disableContract { updateVirus(msg.sender); Player storage p = players[msg.sender]; uint256 priceCrystals = 0; uint256 priceEth = 0; uint256 research = 0; for (uint256 engineerIdx = 0; engineerIdx < numberOfEngineer; engineerIdx++) { uint256 engineerNumber = engineerNumbers[engineerIdx]; EngineerData memory e = engineers[engineerIdx]; // require for engineerNumber if(engineerNumber > e.limit || engineerNumber < 0) revert(); // engineer you want buy if (engineerNumber > 0) { uint256 currentEngineerCount = p.engineersCount[engineerIdx]; // update player data p.engineersCount[engineerIdx] = SafeMath.min(e.limit, SafeMath.add(p.engineersCount[engineerIdx], engineerNumber)); // calculate no research you want buy research = SafeMath.add(research, SafeMath.mul(SafeMath.sub(p.engineersCount[engineerIdx],currentEngineerCount), e.baseResearch)); // calculate price crystals and eth you will pay priceCrystals = SafeMath.add(priceCrystals, SafeMath.mul(e.basePrice, engineerNumber)); priceEth = SafeMath.add(priceEth, SafeMath.mul(e.baseETH, engineerNumber)); } } // check price eth if (priceEth < msg.value) revert(); uint256 devFeePrize = devFee(priceEth); distributedToOwner(devFeePrize); addMiningWarPrizePool(devFeePrize); addPrizePool(SafeMath.sub(msg.value, SafeMath.mul(devFeePrize,3))); // pay and update MiningWar.subCrystal(msg.sender, priceCrystals); updateResearch(msg.sender, research); emit BuyEngineer(msg.sender, engineerNumbers, priceCrystals, priceEth, research); } /** * @dev update virus for player * @param _addr player address */ function updateVirus(address _addr) private { Player storage p = players[_addr]; p.virusNumber = calCurrentVirus(_addr); p.lastUpdateTime = now; } function calCurrentVirus(address _addr) public view returns(uint256 _currentVirus) { Player memory p = players[_addr]; uint256 secondsPassed = SafeMath.sub(now, p.lastUpdateTime); uint256 researchPerDay = getResearchPerDay(_addr); _currentVirus = p.virusNumber; if (researchPerDay > 0) { _currentVirus = SafeMath.add(_currentVirus, SafeMath.mul(researchPerDay, secondsPassed)); } } /** * @dev update research for player * @param _addr player address * @param _research number research want to add */ function updateResearch(address _addr, uint256 _research) private { Player storage p = players[_addr]; p.research = SafeMath.add(p.research, _research); emit UpdateResearch(_addr, p.research); } function getResearchPerDay(address _addr) public view returns( uint256 _researchPerDay) { Player memory p = players[_addr]; _researchPerDay = p.research; uint256 boosterIdx = hasBooster(_addr); if (boosterIdx != 999) { BoostData memory b = boostData[boosterIdx]; _researchPerDay = SafeMath.div(SafeMath.mul(_researchPerDay, b.boostRate), 100); } } /** * @dev get player data * @param _addr player address */ function getPlayerData(address _addr) public view returns( uint256 _virusNumber, uint256 _currentVirus, uint256 _research, uint256 _researchPerDay, uint256 _lastUpdateTime, uint256[8] _engineersCount ) { Player storage p = players[_addr]; for ( uint256 idx = 0; idx < numberOfEngineer; idx++ ) { _engineersCount[idx] = p.engineersCount[idx]; } _currentVirus= SafeMath.div(calCurrentVirus(_addr), VIRUS_MINING_PERIOD); _virusNumber = SafeMath.div(p.virusNumber, VIRUS_MINING_PERIOD); _lastUpdateTime = p.lastUpdateTime; _research = p.research; _researchPerDay = getResearchPerDay(_addr); } //-------------------------------------------------------------------------- // INTERNAL //-------------------------------------------------------------------------- function addPrizePool(uint256 _value) private { prizePool = SafeMath.add(prizePool, _value); } /** * @dev add 5% value of transaction payable */ function addMiningWarPrizePool(uint256 _value) private { MiningWar.fallback.value(_value)(); } /** * @dev calculate current crystals of player * @param _addr player address */ function calCurrentCrystals(address _addr) public view returns(uint256 _currentCrystals) { _currentCrystals = SafeMath.div(MiningWar.calCurrentCrystals(_addr), CRTSTAL_MINING_PERIOD); } function devFee(uint256 _amount) private pure returns(uint256) { return SafeMath.div(SafeMath.mul(_amount, 5), 100); } /** * @dev with transaction payable send 5% value for admin and sponsor * @param _value fee */ function distributedToOwner(uint256 _value) private { gameSponsor.transfer(_value); administrator.transfer(_value); } }