pragma solidity ^0.4.18;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
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;
}
}
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
/**
* @title Claimable
* @dev Extension for the Ownable contract, where the ownership needs to be claimed.
* This allows the new owner to accept the transfer.
*/
contract Claimable is Ownable {
address public pendingOwner;
/**
* @dev Modifier throws if called by any account other than the pendingOwner.
*/
modifier onlyPendingOwner() {
require(msg.sender == pendingOwner);
_;
}
/**
* @dev Allows the current owner to set the pendingOwner address.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) onlyOwner public {
pendingOwner = newOwner;
}
/**
* @dev Allows the pendingOwner address to finalize the transfer.
*/
function claimOwnership() onlyPendingOwner public {
OwnershipTransferred(owner, pendingOwner);
owner = pendingOwner;
pendingOwner = address(0);
}
}
/**
* @title Pausable
* @dev Base contract which allows children to implement an emergency stop mechanism.
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
bool public paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!paused);
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*/
modifier whenPaused() {
require(paused);
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() onlyOwner whenNotPaused public {
paused = true;
Pause();
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() onlyOwner whenPaused public {
paused = false;
Unpause();
}
}
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/179
*/
contract ERC20Basic {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20 is ERC20Basic {
function allowance(address owner, address spender) public view returns (uint256);
function transferFrom(address from, address to, uint256 value) public returns (bool);
function approve(address spender, uint256 value) public returns (bool);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
function safeTransfer(ERC20Basic token, address to, uint256 value) internal {
assert(token.transfer(to, value));
}
function safeTransferFrom(ERC20 token, address from, address to, uint256 value) internal {
assert(token.transferFrom(from, to, value));
}
function safeApprove(ERC20 token, address spender, uint256 value) internal {
assert(token.approve(spender, value));
}
}
/**
* @title Contracts that should be able to recover tokens
* @author SylTi
* @dev This allow a contract to recover any ERC20 token received in a contract by transferring the balance to the contract owner.
* This will prevent any accidental loss of tokens.
*/
contract CanReclaimToken is Ownable {
using SafeERC20 for ERC20Basic;
/**
* @dev Reclaim all ERC20Basic compatible tokens
* @param token ERC20Basic The address of the token contract
*/
function reclaimToken(ERC20Basic token) external onlyOwner {
uint256 balance = token.balanceOf(this);
token.safeTransfer(owner, balance);
}
}
/// @dev Implements access control to the DWorld contract.
contract BurnupGameAccessControl is Claimable, Pausable, CanReclaimToken {
address public cfoAddress;
address public cooAddress;
function BurnupGameAccessControl() public {
// The creator of the contract is the initial CFO.
cfoAddress = msg.sender;
// The creator of the contract is the initial COO.
cooAddress = msg.sender;
}
/// @dev Access modifier for CFO-only functionality.
modifier onlyCFO() {
require(msg.sender == cfoAddress);
_;
}
/// @dev Access modifier for COO-only functionality.
modifier onlyCOO() {
require(msg.sender == cooAddress);
_;
}
/// @dev Assigns a new address to act as the CFO. Only available to the current contract owner.
/// @param _newCFO The address of the new CFO.
function setCFO(address _newCFO) external onlyOwner {
require(_newCFO != address(0));
cfoAddress = _newCFO;
}
/// @dev Assigns a new address to act as the COO. Only available to the current contract owner.
/// @param _newCOO The address of the new COO.
function setCOO(address _newCOO) external onlyOwner {
require(_newCOO != address(0));
cooAddress = _newCOO;
}
}
/// @dev Defines base data structures for DWorld.
contract BurnupGameBase is BurnupGameAccessControl {
using SafeMath for uint256;
event NextGame(uint256 rows, uint256 cols, uint256 activityTimer, uint256 unclaimedTilePrice, uint256 buyoutReferralBonusPercentage, uint256 buyoutPrizePoolPercentage, uint256 buyoutDividendPercentage, uint256 buyoutFeePercentage);
event Start(uint256 indexed gameIndex, address indexed starter, uint256 timestamp, uint256 prizePool, uint256 rows, uint256 cols, uint256 activityTimer, uint256 unclaimedTilePrice, uint256 buyoutReferralBonusPercentage, uint256 buyoutPrizePoolPercentage, uint256 buyoutDividendPercentage, uint256 buyoutFeePercentage);
event End(uint256 indexed gameIndex, address indexed winner, uint256 indexed identifier, uint256 x, uint256 y, uint256 timestamp, uint256 prize);
event Buyout(uint256 indexed gameIndex, address indexed player, uint256 indexed identifier, uint256 x, uint256 y, uint256 timestamp, uint256 timeoutTimestamp, uint256 newPrice, uint256 newPrizePool);
event SpiceUpPrizePool(uint256 indexed gameIndex, address indexed spicer, uint256 spiceAdded, string message, uint256 newPrizePool);
/// @dev Struct to hold game settings.
struct GameSettings {
uint256 rows; // 5
uint256 cols; // 8
/// @dev Time after last trade after which tiles become inactive.
uint256 activityTimer; // 3600
/// @dev Base price for unclaimed tiles.
uint256 unclaimedTilePrice; // 0.01 ether
/// @dev Percentage of the buyout price that goes towards the referral
/// bonus. In 1/1000th of a percentage.
uint256 buyoutReferralBonusPercentage; // 750
/// @dev Percentage of the buyout price that goes towards the prize
/// pool. In 1/1000th of a percentage.
uint256 buyoutPrizePoolPercentage; // 10000
/// @dev Percentage of the buyout price that goes towards dividends
/// surrounding the tile that is bought out. In in 1/1000th of
/// a percentage.
uint256 buyoutDividendPercentage; // 5000
/// @dev Buyout fee in 1/1000th of a percentage.
uint256 buyoutFeePercentage; // 2500
}
/// @dev Struct to hold game state.
struct GameState {
/// @dev Boolean indicating whether the game is live.
bool gameStarted;
/// @dev Time at which the game started.
uint256 gameStartTimestamp;
/// @dev Keep track of tile ownership.
mapping (uint256 => address) identifierToOwner;
/// @dev Keep track of the timestamp at which a tile was flipped last.
mapping (uint256 => uint256) identifierToBuyoutTimestamp;
/// @dev Current tile price.
mapping (uint256 => uint256) identifierToBuyoutPrice;
/// @dev Keep track of the tile that was flipped last.
uint256 lastFlippedTile;
/// @dev The prize pool.
uint256 prizePool;
}
/// @notice Mapping from game indices to game settings.
mapping (uint256 => GameSettings) public gameSettings;
/// @notice Mapping from game indices to game states.
mapping (uint256 => GameState) public gameStates;
/// @notice The index of the current game.
uint256 public gameIndex = 0;
/// @dev Settings for the next game
GameSettings public nextGameSettings;
function BurnupGameBase() public {
// Initial settings.
setNextGameSettings(
4, // rows
5, // cols
3600, // activityTimer
0.01 ether, // unclaimedTilePrice
750, // buyoutReferralBonusPercentage
10000, // buyoutPrizePoolPercentage
5000, // buyoutDividendPercentage
2500 // buyoutFeePercentage
);
}
/// @dev Test whether the coordinate is valid.
/// @param x The x-part of the coordinate to test.
/// @param y The y-part of the coordinate to test.
function validCoordinate(uint256 x, uint256 y) public view returns(bool) {
return x < gameSettings[gameIndex].cols && y < gameSettings[gameIndex].rows;
}
/// @dev Represent a 2D coordinate as a single uint.
/// @param x The x-coordinate.
/// @param y The y-coordinate.
function coordinateToIdentifier(uint256 x, uint256 y) public view returns(uint256) {
require(validCoordinate(x, y));
return (y * gameSettings[gameIndex].cols) + x;
}
/// @dev Turn a single uint representation of a coordinate into its x and y parts.
/// @param identifier The uint representation of a coordinate.
/// Assumes the identifier is valid.
function identifierToCoordinate(uint256 identifier) public view returns(uint256 x, uint256 y) {
y = identifier / gameSettings[gameIndex].cols;
x = identifier - (y * gameSettings[gameIndex].cols);
}
/// @notice Sets the settings for the next game.
function setNextGameSettings(
uint256 rows,
uint256 cols,
uint256 activityTimer,
uint256 unclaimedTilePrice,
uint256 buyoutReferralBonusPercentage,
uint256 buyoutPrizePoolPercentage,
uint256 buyoutDividendPercentage,
uint256 buyoutFeePercentage
)
public
onlyCFO
{
// Buyout dividend must be 2% at the least.
// Buyout dividend percentage may be 12.5% at the most.
require(2000 <= buyoutDividendPercentage && buyoutDividendPercentage <= 12500);
// Buyout fee may be 5% at the most.
require(buyoutFeePercentage <= 5000);
nextGameSettings = GameSettings({
rows: rows,
cols: cols,
activityTimer: activityTimer,
unclaimedTilePrice: unclaimedTilePrice,
buyoutReferralBonusPercentage: buyoutReferralBonusPercentage,
buyoutPrizePoolPercentage: buyoutPrizePoolPercentage,
buyoutDividendPercentage: buyoutDividendPercentage,
buyoutFeePercentage: buyoutFeePercentage
});
NextGame(rows, cols, activityTimer, unclaimedTilePrice, buyoutReferralBonusPercentage, buyoutPrizePoolPercentage, buyoutDividendPercentage, buyoutFeePercentage);
}
}
/// @dev Holds ownership functionality such as transferring.
contract BurnupGameOwnership is BurnupGameBase {
event Transfer(address indexed from, address indexed to, uint256 indexed deedId);
/// @notice Name of the collection of deeds (non-fungible token), as defined in ERC721Metadata.
function name() public pure returns (string _deedName) {
_deedName = "Burnup Tiles";
}
/// @notice Symbol of the collection of deeds (non-fungible token), as defined in ERC721Metadata.
function symbol() public pure returns (string _deedSymbol) {
_deedSymbol = "BURN";
}
/// @dev Checks if a given address owns a particular tile.
/// @param _owner The address of the owner to check for.
/// @param _identifier The tile identifier to check for.
function _owns(address _owner, uint256 _identifier) internal view returns (bool) {
return gameStates[gameIndex].identifierToOwner[_identifier] == _owner;
}
/// @dev Assigns ownership of a specific deed to an address.
/// @param _from The address to transfer the deed from.
/// @param _to The address to transfer the deed to.
/// @param _identifier The identifier of the deed to transfer.
function _transfer(address _from, address _to, uint256 _identifier) internal {
// Transfer ownership.
gameStates[gameIndex].identifierToOwner[_identifier] = _to;
// Emit the transfer event.
Transfer(_from, _to, _identifier);
}
/// @notice Returns the address currently assigned ownership of a given deed.
/// @dev Required for ERC-721 compliance.
function ownerOf(uint256 _identifier) external view returns (address _owner) {
_owner = gameStates[gameIndex].identifierToOwner[_identifier];
require(_owner != address(0));
}
/// @notice Transfer a deed to another address. If transferring to a smart
/// contract be VERY CAREFUL to ensure that it is aware of ERC-721, or your
/// deed may be lost forever.
/// @param _to The address of the recipient, can be a user or contract.
/// @param _identifier The identifier of the deed to transfer.
/// @dev Required for ERC-721 compliance.
function transfer(address _to, uint256 _identifier) external whenNotPaused {
// One can only transfer their own deeds.
require(_owns(msg.sender, _identifier));
// Transfer ownership
_transfer(msg.sender, _to, _identifier);
}
}
/**
* @title PullPayment
* @dev Base contract supporting async send for pull payments. Inherit from this
* contract and use asyncSend instead of send.
*/
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(this.balance >= payment);
totalPayments = totalPayments.sub(payment);
payments[payee] = 0;
assert(payee.send(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);
}
}
/// @dev Implements access control to the BurnUp wallet.
contract BurnupHoldingAccessControl is Claimable, Pausable, CanReclaimToken {
address public cfoAddress;
/// Boolean indicating whether an address is a BurnUp Game contract.
mapping (address => bool) burnupGame;
function BurnupHoldingAccessControl() public {
// The creator of the contract is the initial CFO.
cfoAddress = msg.sender;
}
/// @dev Access modifier for CFO-only functionality.
modifier onlyCFO() {
require(msg.sender == cfoAddress);
_;
}
/// @dev Access modifier for functionality that may only be called by a BurnUp game.
modifier onlyBurnupGame() {
// The sender must be a recognized BurnUp game address.
require(burnupGame[msg.sender]);
_;
}
/// @dev Assigns a new address to act as the CFO. Only available to the current contract owner.
/// @param _newCFO The address of the new CFO.
function setCFO(address _newCFO) external onlyOwner {
require(_newCFO != address(0));
cfoAddress = _newCFO;
}
/// @dev Add a Burnup game contract address.
/// @param addr The address of the Burnup game contract.
function addBurnupGame(address addr) external onlyOwner {
burnupGame[addr] = true;
}
/// @dev Remove a Burnup game contract address.
/// @param addr The address of the Burnup game contract.
function removeBurnupGame(address addr) external onlyOwner {
delete burnupGame[addr];
}
}
/// @dev Implements the BurnUp wallet.
contract BurnupHoldingReferral is BurnupHoldingAccessControl {
event SetReferrer(address indexed referral, address indexed referrer);
/// Referrer of player.
mapping (address => address) addressToReferrerAddress;
/// Get the referrer of a player.
/// @param player The address of the player to get the referrer of.
function referrerOf(address player) public view returns (address) {
return addressToReferrerAddress[player];
}
/// Set the referrer for a player.
/// @param playerAddr The address of the player to set the referrer for.
/// @param referrerAddr The address of the referrer to set.
function _setReferrer(address playerAddr, address referrerAddr) internal {
addressToReferrerAddress[playerAddr] = referrerAddr;
// Emit event.
SetReferrer(playerAddr, referrerAddr);
}
}
/// @dev Implements the BurnUp wallet.
contract BurnupHoldingCore is BurnupHoldingReferral, PullPayment {
using SafeMath for uint256;
address public beneficiary1;
address public beneficiary2;
function BurnupHoldingCore(address _beneficiary1, address _beneficiary2) public {
// The creator of the contract is the initial CFO.
cfoAddress = msg.sender;
// Set the two beneficiaries.
beneficiary1 = _beneficiary1;
beneficiary2 = _beneficiary2;
}
/// Pay the two beneficiaries. Sends both beneficiaries
/// a halve of the payment.
function payBeneficiaries() external payable {
uint256 paymentHalve = msg.value.div(2);
// We do not want a single wei to get stuck.
uint256 otherPaymentHalve = msg.value.sub(paymentHalve);
// Send payment for manual withdrawal.
asyncSend(beneficiary1, paymentHalve);
asyncSend(beneficiary2, otherPaymentHalve);
}
/// Sets a new address for Beneficiary one.
/// @param addr The new address.
function setBeneficiary1(address addr) external onlyCFO {
beneficiary1 = addr;
}
/// Sets a new address for Beneficiary two.
/// @param addr The new address.
function setBeneficiary2(address addr) external onlyCFO {
beneficiary2 = addr;
}
/// Set a referrer.
/// @param playerAddr The address to set the referrer for.
/// @param referrerAddr The address of the referrer to set.
function setReferrer(address playerAddr, address referrerAddr) external onlyBurnupGame whenNotPaused returns(bool) {
if (referrerOf(playerAddr) == address(0x0) && playerAddr != referrerAddr) {
// Set the referrer, if no referrer has been set yet, and the player
// and referrer are not the same address.
_setReferrer(playerAddr, referrerAddr);
// Indicate success.
return true;
}
// Indicate failure.
return false;
}
}
/// @dev Holds functionality for finance related to tiles.
contract BurnupGameFinance is BurnupGameOwnership, PullPayment {
/// Address of Burnup wallet
BurnupHoldingCore burnupHolding;
function BurnupGameFinance(address burnupHoldingAddress) public {
burnupHolding = BurnupHoldingCore(burnupHoldingAddress);
}
/// @dev Find the _claimed_ tiles surrounding a tile.
/// @param _deedId The identifier of the tile to get the surrounding tiles for.
function _claimedSurroundingTiles(uint256 _deedId) internal view returns (uint256[] memory) {
var (x, y) = identifierToCoordinate(_deedId);
// Find all claimed surrounding tiles.
uint256 claimed = 0;
// Create memory buffer capable of holding all tiles.
uint256[] memory _tiles = new uint256[](8);
// Loop through all neighbors.
for (int256 dx = -1; dx <= 1; dx++) {
for (int256 dy = -1; dy <= 1; dy++) {
if (dx == 0 && dy == 0) {
// Skip the center (i.e., the tile itself).
continue;
}
uint256 nx = uint256(int256(x) + dx);
uint256 ny = uint256(int256(y) + dy);
if (nx >= gameSettings[gameIndex].cols || ny >= gameSettings[gameIndex].rows) {
// This coordinate is outside the game bounds.
continue;
}
// Get the coordinates of this neighboring identifier.
uint256 neighborIdentifier = coordinateToIdentifier(
nx,
ny
);
if (gameStates[gameIndex].identifierToOwner[neighborIdentifier] != address(0x0)) {
_tiles[claimed] = neighborIdentifier;
claimed++;
}
}
}
// Memory arrays cannot be resized, so copy all
// tiles from the buffer to the tile array.
uint256[] memory tiles = new uint256[](claimed);
for (uint256 i = 0; i < claimed; i++) {
tiles[i] = _tiles[i];
}
return tiles;
}
/// @dev Calculate the next buyout price given the current total buyout cost.
/// @param price The current buyout price.
function nextBuyoutPrice(uint256 price) public pure returns (uint256) {
if (price < 0.02 ether) {
return price.mul(200).div(100); // * 2.0
} else {
return price.mul(150).div(100); // * 1.5
}
}
/// @dev Assign the proceeds of the buyout.
function _assignBuyoutProceeds(
address currentOwner,
uint256[] memory claimedSurroundingTiles,
uint256 fee,
uint256 currentOwnerWinnings,
uint256 totalDividendPerBeneficiary,
uint256 referralBonus,
uint256 prizePoolFunds
)
internal
{
if (currentOwner != 0x0) {
// Send the current owner's winnings.
_sendFunds(currentOwner, currentOwnerWinnings);
} else {
// There is no current owner.
fee = fee.add(currentOwnerWinnings);
}
// Assign dividends to owners of surrounding tiles.
for (uint256 i = 0; i < claimedSurroundingTiles.length; i++) {
address beneficiary = gameStates[gameIndex].identifierToOwner[claimedSurroundingTiles[i]];
_sendFunds(beneficiary, totalDividendPerBeneficiary);
}
/// Distribute the referral bonuses (if any) for an address.
address referrer1 = burnupHolding.referrerOf(msg.sender);
if (referrer1 != 0x0) {
_sendFunds(referrer1, referralBonus);
address referrer2 = burnupHolding.referrerOf(referrer1);
if (referrer2 != 0x0) {
_sendFunds(referrer2, referralBonus);
} else {
// There is no second-level referrer.
fee = fee.add(referralBonus);
}
} else {
// There are no first and second-level referrers.
fee = fee.add(referralBonus.mul(2));
}
// Send the fee to the holding contract.
burnupHolding.payBeneficiaries.value(fee)();
// Increase the prize pool.
gameStates[gameIndex].prizePool = gameStates[gameIndex].prizePool.add(prizePoolFunds);
}
/// @dev Calculate and assign the proceeds from the buyout.
/// @param currentOwner The current owner of the tile that is being bought out.
/// @param _deedId The identifier of the tile that is being bought out.
/// @param claimedSurroundingTiles The surrounding tiles that have been claimed.
function _calculateAndAssignBuyoutProceeds(address currentOwner, uint256 _deedId, uint256[] memory claimedSurroundingTiles)
internal
returns (uint256 price)
{
// The current price.
if (currentOwner == 0x0) {
price = gameSettings[gameIndex].unclaimedTilePrice;
} else {
price = gameStates[gameIndex].identifierToBuyoutPrice[_deedId];
}
// Calculate the variable dividends based on the buyout price
// (only to be paid if there are surrounding tiles).
uint256 variableDividends = price.mul(gameSettings[gameIndex].buyoutDividendPercentage).div(100000);
// Calculate fees, referral bonus, and prize pool funds.
uint256 fee = price.mul(gameSettings[gameIndex].buyoutFeePercentage).div(100000);
uint256 referralBonus = price.mul(gameSettings[gameIndex].buyoutReferralBonusPercentage).div(100000);
uint256 prizePoolFunds = price.mul(gameSettings[gameIndex].buyoutPrizePoolPercentage).div(100000);
// Calculate and assign buyout proceeds.
uint256 currentOwnerWinnings = price.sub(fee).sub(referralBonus.mul(2)).sub(prizePoolFunds);
uint256 totalDividendPerBeneficiary;
if (claimedSurroundingTiles.length > 0) {
// If there are surrounding tiles, variable dividend is to be paid
// based on the buyout price.
// Calculate the dividend per surrounding tile.
totalDividendPerBeneficiary = variableDividends / claimedSurroundingTiles.length;
currentOwnerWinnings = currentOwnerWinnings.sub(variableDividends);
// currentOwnerWinnings = currentOwnerWinnings.sub(totalDividendPerBeneficiary * claimedSurroundingTiles.length);
}
_assignBuyoutProceeds(
currentOwner,
claimedSurroundingTiles,
fee,
currentOwnerWinnings,
totalDividendPerBeneficiary,
referralBonus,
prizePoolFunds
);
}
/// @dev Send funds to a beneficiary. If sending fails, assign
/// funds to the beneficiary's balance for manual withdrawal.
/// @param beneficiary The beneficiary's address to send funds to
/// @param amount The amount to send.
function _sendFunds(address beneficiary, uint256 amount) internal {
if (!beneficiary.send(amount)) {
// Failed to send funds. This can happen due to a failure in
// fallback code of the beneficiary, or because of callstack
// depth.
// Send funds asynchronously for manual withdrawal by the
// beneficiary.
asyncSend(beneficiary, amount);
}
}
}
/// @dev Holds core game functionality.
contract BurnupGameCore is BurnupGameFinance {
function BurnupGameCore(address burnupHoldingAddress) public BurnupGameFinance(burnupHoldingAddress) {}
/// @notice Buy the current owner out of the tile.
/// @param _gameIndex The index of the game to play on.
/// @param startNewGameIfIdle Start a new game if the current game is idle.
/// @param x The x-coordinate of the tile to buy.
/// @param y The y-coordinate of the tile to buy.
function buyout(uint256 _gameIndex, bool startNewGameIfIdle, uint256 x, uint256 y) public payable {
// Check to see if the game should end. Process payment.
_processGameEnd();
if (!gameStates[gameIndex].gameStarted) {
// If the game is not started, the contract must not be paused.
require(!paused);
// If the game is not started, the player must be willing to start
// a new game.
require(startNewGameIfIdle);
// Set the price and timeout.
gameSettings[gameIndex] = nextGameSettings;
// Start the game.
gameStates[gameIndex].gameStarted = true;
// Set game started timestamp.
gameStates[gameIndex].gameStartTimestamp = block.timestamp;
// Emit start event.
Start(gameIndex, msg.sender, block.timestamp, gameStates[gameIndex].prizePool, gameSettings[gameIndex].rows, gameSettings[gameIndex].cols, gameSettings[gameIndex].activityTimer, gameSettings[gameIndex].unclaimedTilePrice, gameSettings[gameIndex].buyoutReferralBonusPercentage, gameSettings[gameIndex].buyoutPrizePoolPercentage, gameSettings[gameIndex].buyoutDividendPercentage, gameSettings[gameIndex].buyoutFeePercentage);
}
// Check the game index.
if (startNewGameIfIdle) {
// The given game index must be the current game index, or the previous
// game index.
require(_gameIndex == gameIndex || _gameIndex.add(1) == gameIndex);
} else {
// Only play on the game indicated by the player.
require(_gameIndex == gameIndex);
}
uint256 identifier = coordinateToIdentifier(x, y);
address currentOwner = gameStates[gameIndex].identifierToOwner[identifier];
// Tile must be unowned, or active.
if (currentOwner == address(0x0)) {
// Tile must still be flippable.
require(gameStates[gameIndex].gameStartTimestamp.add(gameSettings[gameIndex].activityTimer) >= block.timestamp);
} else {
// Tile must be active.
require(gameStates[gameIndex].identifierToBuyoutTimestamp[identifier].add(gameSettings[gameIndex].activityTimer) >= block.timestamp);
}
// Get existing surrounding tiles.
uint256[] memory claimedSurroundingTiles = _claimedSurroundingTiles(identifier);
// Assign the buyout proceeds and retrieve the total cost.
uint256 price = _calculateAndAssignBuyoutProceeds(currentOwner, identifier, claimedSurroundingTiles);
// Enough Ether must be supplied.
require(msg.value >= price);
// Transfer the tile.
_transfer(currentOwner, msg.sender, identifier);
// Set this tile to be the most recently bought out.
gameStates[gameIndex].lastFlippedTile = identifier;
// Calculate and set the new tile price.
gameStates[gameIndex].identifierToBuyoutPrice[identifier] = nextBuyoutPrice(price);
// Set the buyout timestamp.
gameStates[gameIndex].identifierToBuyoutTimestamp[identifier] = block.timestamp;
// Emit event
Buyout(gameIndex, msg.sender, identifier, x, y, block.timestamp, block.timestamp + gameSettings[gameIndex].activityTimer, gameStates[gameIndex].identifierToBuyoutPrice[identifier], gameStates[gameIndex].prizePool);
// Calculate the excess Ether sent.
// msg.value is greater than or equal to price,
// so this cannot underflow.
uint256 excess = msg.value - price;
if (excess > 0) {
// Refund any excess Ether (not susceptible to re-entry attack, as
// the owner is assigned before the transfer takes place).
msg.sender.transfer(excess);
}
}
/// @notice Buy the current owner out of the tile. Set the player's referrer.
/// @param _gameIndex The index of the game to play on.
/// @param startNewGameIfIdle Start a new game if the current game is idle.
/// @param x The x-coordinate of the tile to buy.
/// @param y The y-coordinate of the tile to buy.
function buyoutAndSetReferrer(uint256 _gameIndex, bool startNewGameIfIdle, uint256 x, uint256 y, address referrerAddress) external payable {
// Set the referrer.
burnupHolding.setReferrer(msg.sender, referrerAddress);
// Play.
buyout(_gameIndex, startNewGameIfIdle, x, y);
}
/// @notice Spice up the prize pool.
/// @param _gameIndex The index of the game to add spice to.
/// @param message An optional message to be sent along with the spice.
function spiceUp(uint256 _gameIndex, string message) external payable {
// Check to see if the game should end. Process payment.
_processGameEnd();
// Check the game index.
require(_gameIndex == gameIndex);
// Game must be live or unpaused.
require(gameStates[gameIndex].gameStarted || !paused);
// Funds must be sent.
require(msg.value > 0);
// Add funds to the prize pool.
gameStates[gameIndex].prizePool = gameStates[gameIndex].prizePool.add(msg.value);
// Emit event.
SpiceUpPrizePool(gameIndex, msg.sender, msg.value, message, gameStates[gameIndex].prizePool);
}
/// @notice End the game. Pay prize.
function endGame() external {
require(_processGameEnd());
}
/// @dev End the game. Pay prize.
function _processGameEnd() internal returns(bool) {
address currentOwner = gameStates[gameIndex].identifierToOwner[gameStates[gameIndex].lastFlippedTile];
// The game must be started.
if (!gameStates[gameIndex].gameStarted) {
return false;
}
// The last flipped tile must be owned (i.e. there has been at
// least one flip).
if (currentOwner == address(0x0)) {
return false;
}
// The last flipped tile must have become inactive.
if (gameStates[gameIndex].identifierToBuyoutTimestamp[gameStates[gameIndex].lastFlippedTile].add(gameSettings[gameIndex].activityTimer) >= block.timestamp) {
return false;
}
// Assign prize pool to the owner of the last-flipped tile.
if (gameStates[gameIndex].prizePool > 0) {
_sendFunds(currentOwner, gameStates[gameIndex].prizePool);
}
// Get coordinates of last flipped tile.
var (x, y) = identifierToCoordinate(gameStates[gameIndex].lastFlippedTile);
// Emit event.
End(gameIndex, currentOwner, gameStates[gameIndex].lastFlippedTile, x, y, gameStates[gameIndex].identifierToBuyoutTimestamp[gameStates[gameIndex].lastFlippedTile].add(gameSettings[gameIndex].activityTimer), gameStates[gameIndex].prizePool);
// Increment the game index. This won't overflow before the heat death of the universe.
gameIndex++;
// Indicate ending the game was successful.
return true;
}
}
{
"compilationTarget": {
"BurnupGameCore.sol": "BurnupGameCore"
},
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":true,"inputs":[],"name":"totalPayments","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cfoAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"gameSettings","outputs":[{"name":"rows","type":"uint256"},{"name":"cols","type":"uint256"},{"name":"activityTimer","type":"uint256"},{"name":"unclaimedTilePrice","type":"uint256"},{"name":"buyoutReferralBonusPercentage","type":"uint256"},{"name":"buyoutPrizePoolPercentage","type":"uint256"},{"name":"buyoutDividendPercentage","type":"uint256"},{"name":"buyoutFeePercentage","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"_deedName","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"}],"name":"reclaimToken","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"coordinateToIdentifier","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newCOO","type":"address"}],"name":"setCOO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"nextGameSettings","outputs":[{"name":"rows","type":"uint256"},{"name":"cols","type":"uint256"},{"name":"activityTimer","type":"uint256"},{"name":"unclaimedTilePrice","type":"uint256"},{"name":"buyoutReferralBonusPercentage","type":"uint256"},{"name":"buyoutPrizePoolPercentage","type":"uint256"},{"name":"buyoutDividendPercentage","type":"uint256"},{"name":"buyoutFeePercentage","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCFO","type":"address"}],"name":"setCFO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"claimOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"gameIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"price","type":"uint256"}],"name":"nextBuyoutPrice","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"withdrawPayments","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_identifier","type":"uint256"}],"name":"ownerOf","outputs":[{"name":"_owner","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"endGame","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"identifier","type":"uint256"}],"name":"identifierToCoordinate","outputs":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_gameIndex","type":"uint256"},{"name":"startNewGameIfIdle","type":"bool"},{"name":"x","type":"uint256"},{"name":"y","type":"uint256"},{"name":"referrerAddress","type":"address"}],"name":"buyoutAndSetReferrer","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_gameIndex","type":"uint256"},{"name":"startNewGameIfIdle","type":"bool"},{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"buyout","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"_deedSymbol","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"gameStates","outputs":[{"name":"gameStarted","type":"bool"},{"name":"gameStartTimestamp","type":"uint256"},{"name":"lastFlippedTile","type":"uint256"},{"name":"prizePool","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"validCoordinate","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_identifier","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"cooAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"rows","type":"uint256"},{"name":"cols","type":"uint256"},{"name":"activityTimer","type":"uint256"},{"name":"unclaimedTilePrice","type":"uint256"},{"name":"buyoutReferralBonusPercentage","type":"uint256"},{"name":"buyoutPrizePoolPercentage","type":"uint256"},{"name":"buyoutDividendPercentage","type":"uint256"},{"name":"buyoutFeePercentage","type":"uint256"}],"name":"setNextGameSettings","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"payments","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pendingOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_gameIndex","type":"uint256"},{"name":"message","type":"string"}],"name":"spiceUp","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"burnupHoldingAddress","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":true,"name":"deedId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"rows","type":"uint256"},{"indexed":false,"name":"cols","type":"uint256"},{"indexed":false,"name":"activityTimer","type":"uint256"},{"indexed":false,"name":"unclaimedTilePrice","type":"uint256"},{"indexed":false,"name":"buyoutReferralBonusPercentage","type":"uint256"},{"indexed":false,"name":"buyoutPrizePoolPercentage","type":"uint256"},{"indexed":false,"name":"buyoutDividendPercentage","type":"uint256"},{"indexed":false,"name":"buyoutFeePercentage","type":"uint256"}],"name":"NextGame","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"gameIndex","type":"uint256"},{"indexed":true,"name":"starter","type":"address"},{"indexed":false,"name":"timestamp","type":"uint256"},{"indexed":false,"name":"prizePool","type":"uint256"},{"indexed":false,"name":"rows","type":"uint256"},{"indexed":false,"name":"cols","type":"uint256"},{"indexed":false,"name":"activityTimer","type":"uint256"},{"indexed":false,"name":"unclaimedTilePrice","type":"uint256"},{"indexed":false,"name":"buyoutReferralBonusPercentage","type":"uint256"},{"indexed":false,"name":"buyoutPrizePoolPercentage","type":"uint256"},{"indexed":false,"name":"buyoutDividendPercentage","type":"uint256"},{"indexed":false,"name":"buyoutFeePercentage","type":"uint256"}],"name":"Start","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"gameIndex","type":"uint256"},{"indexed":true,"name":"winner","type":"address"},{"indexed":true,"name":"identifier","type":"uint256"},{"indexed":false,"name":"x","type":"uint256"},{"indexed":false,"name":"y","type":"uint256"},{"indexed":false,"name":"timestamp","type":"uint256"},{"indexed":false,"name":"prize","type":"uint256"}],"name":"End","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"gameIndex","type":"uint256"},{"indexed":true,"name":"player","type":"address"},{"indexed":true,"name":"identifier","type":"uint256"},{"indexed":false,"name":"x","type":"uint256"},{"indexed":false,"name":"y","type":"uint256"},{"indexed":false,"name":"timestamp","type":"uint256"},{"indexed":false,"name":"timeoutTimestamp","type":"uint256"},{"indexed":false,"name":"newPrice","type":"uint256"},{"indexed":false,"name":"newPrizePool","type":"uint256"}],"name":"Buyout","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"gameIndex","type":"uint256"},{"indexed":true,"name":"spicer","type":"address"},{"indexed":false,"name":"spiceAdded","type":"uint256"},{"indexed":false,"name":"message","type":"string"},{"indexed":false,"name":"newPrizePool","type":"uint256"}],"name":"SpiceUpPrizePool","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"}]