// Copyright (c) 2017, 2018 EtherJack.io. All rights reserved.
// This code is disclosed only to be used for inspection and audit purposes.
// Code modification and use for any purpose other than security audit
// is prohibited. Creation of derived works or unauthorized deployment
// of the code or any its portion to a blockchain is prohibited.
pragma solidity ^0.4.19;
contract HouseOwned {
address house;
modifier onlyHouse {
require(msg.sender == house);
_;
}
/// @dev Contract constructor
function HouseOwned() public {
house = msg.sender;
}
}
// SafeMath is a part of Zeppelin Solidity library
// licensed under MIT License
// https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/LICENSE
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
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;
}
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;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
// ----------------------------------------------------------------------------
// ERC Token Standard #20 Interface
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md
// ----------------------------------------------------------------------------
contract ERC20Interface {
function totalSupply() public constant returns (uint);
function balanceOf(address _owner) public constant returns (uint balance);
function allowance(address _owner, address _spender) public constant returns (uint remaining);
function transfer(address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed tokenOwner, address indexed spender, uint value);
}
contract Token is HouseOwned, ERC20Interface {
using SafeMath for uint;
// Public variables of the token
string public name;
string public symbol;
uint8 public constant decimals = 0;
uint256 public supply;
// Trusted addresses
Jackpot public jackpot;
address public croupier;
// All users' balances
mapping (address => uint256) internal balances;
// Users' deposits with Croupier
mapping (address => uint256) public depositOf;
// Total amount of deposits
uint256 public totalDeposit;
// Total amount of "Frozen Deposit Pool" -- the tokens for sale at Croupier
uint256 public frozenPool;
// Allowance mapping
mapping (address => mapping (address => uint256)) internal allowed;
//////
/// @title Modifiers
//
/// @dev Only Croupier
modifier onlyCroupier {
require(msg.sender == croupier);
_;
}
/// @dev Only Jackpot
modifier onlyJackpot {
require(msg.sender == address(jackpot));
_;
}
/// @dev Protection from short address attack
modifier onlyPayloadSize(uint size) {
assert(msg.data.length == size + 4);
_;
}
//////
/// @title Events
//
/// @dev Fired when a token is burned (bet made)
event Burn(address indexed from, uint256 value);
/// @dev Fired when a deposit is made or withdrawn
/// direction == 0: deposit
/// direction == 1: withdrawal
event Deposit(address indexed from, uint256 value, uint8 direction, uint256 newDeposit);
/// @dev Fired when a deposit with Croupier is frozen (set for sale)
event DepositFrozen(address indexed from, uint256 value);
/// @dev Fired when a deposit with Croupier is unfrozen (removed from sale)
// Value is the resulting deposit, NOT the unfrozen amount
event DepositUnfrozen(address indexed from, uint256 value);
//////
/// @title Constructor and Initialization
//
/// @dev Initializes contract with initial supply tokens to the creator of the contract
function Token() HouseOwned() public {
name = "JACK Token";
symbol = "JACK";
supply = 1000000;
}
/// @dev Function to set address of Jackpot contract once after creation
/// @param _jackpot Address of the Jackpot contract
function setJackpot(address _jackpot) onlyHouse public {
require(address(jackpot) == 0x0);
require(_jackpot != address(this)); // Protection from admin's mistake
jackpot = Jackpot(_jackpot);
uint256 bountyPortion = supply / 40; // 2.5% is the bounty portion for marketing expenses
balances[house] = bountyPortion; // House receives the bounty tokens
balances[jackpot] = supply - bountyPortion; // Jackpot gets the rest
croupier = jackpot.croupier();
}
//////
/// @title Public Methods
//
/// @dev Croupier invokes this method to return deposits to players
/// @param _to The address of the recipient
/// @param _extra Additional off-chain credit (AirDrop support), so that croupier can return more than the user has actually deposited
function returnDeposit(address _to, uint256 _extra) onlyCroupier public {
require(depositOf[_to] > 0 || _extra > 0);
uint256 amount = depositOf[_to];
depositOf[_to] = 0;
totalDeposit = totalDeposit.sub(amount);
_transfer(croupier, _to, amount.add(_extra));
Deposit(_to, amount, 1, 0);
}
/// @dev Gets the balance of the specified address.
/// @param _owner The address
function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
function totalSupply() public view returns (uint256) {
return supply;
}
/// @dev Send `_value` tokens to `_to`
/// @param _to The address of the recipient
/// @param _value the amount to send
function transfer(address _to, uint256 _value) onlyPayloadSize(2 * 32) public returns (bool) {
require(address(jackpot) != 0x0);
require(croupier != 0x0);
if (_to == address(jackpot)) {
// It is a token bet. Ignoring _value, only using 1 token
_burnFromAccount(msg.sender, 1);
jackpot.betToken(msg.sender);
return true;
}
if (_to == croupier && msg.sender != house) {
// It's a deposit to Croupier. In addition to transferring the token,
// mark it in the deposits table
// House can't make deposits. If House is transferring something to
// Croupier, it's just a transfer, nothing more
depositOf[msg.sender] += _value;
totalDeposit = totalDeposit.add(_value);
Deposit(msg.sender, _value, 0, depositOf[msg.sender]);
}
// In all cases but Jackpot transfer (which is terminated by a return), actually
// do perform the transfer
return _transfer(msg.sender, _to, _value);
}
/// @dev Transfer tokens from one address to another
/// @param _from address The address which you want to send tokens from
/// @param _to address The address which you want to transfer to
/// @param _value uint256 the amount of tokens to be transferred
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
Transfer(_from, _to, _value);
return true;
}
/// @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
/// @param _spender The address which will spend the funds.
/// @param _value The amount of tokens to be spent.
function approve(address _spender, uint256 _value) public returns (bool) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
/// @dev Function to check the amount of tokens that an owner allowed to a spender.
/// @param _owner address The address which owns the funds.
/// @param _spender address The address which will spend the funds.
/// @return A uint256 specifying the amount of tokens still available for the spender.
function allowance(address _owner, address _spender) public view returns (uint256) {
return allowed[_owner][_spender];
}
/// @dev Increase the amount of tokens that an owner allowed to a spender.
/// @param _spender The address which will spend the funds.
/// @param _addedValue The amount of tokens to increase the allowance by.
function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
/// @dev Decrease the amount of tokens that an owner allowed to a spender.
/// @param _spender The address which will spend the funds.
/// @param _subtractedValue The amount of tokens to decrease the allowance by.
function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
uint oldValue = allowed[msg.sender][_spender];
if (_subtractedValue > oldValue) {
allowed[msg.sender][_spender] = 0;
} else {
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
}
Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
/// @dev Croupier uses this method to set deposited credits of a player for sale
/// @param _user The address of the user
/// @param _extra Additional off-chain credit (AirDrop support), so that croupier could have frozen more than the user had invested
function freezeDeposit(address _user, uint256 _extra) onlyCroupier public {
require(depositOf[_user] > 0 || _extra > 0);
uint256 deposit = depositOf[_user];
depositOf[_user] = depositOf[_user].sub(deposit);
totalDeposit = totalDeposit.sub(deposit);
uint256 depositWithExtra = deposit.add(_extra);
frozenPool = frozenPool.add(depositWithExtra);
DepositFrozen(_user, depositWithExtra);
}
/// @dev Croupier uses this method stop selling user's tokens and return them to normal deposit
/// @param _user The user whose deposit is being unfrozen
/// @param _value The value to unfreeze according to Croupier's records (off-chain sale data)
function unfreezeDeposit(address _user, uint256 _value) onlyCroupier public {
require(_value > 0);
require(frozenPool >= _value);
depositOf[_user] = depositOf[_user].add(_value);
totalDeposit = totalDeposit.add(_value);
frozenPool = frozenPool.sub(_value);
DepositUnfrozen(_user, depositOf[_user]);
}
/// @dev The Jackpot contract invokes this method when selling tokens from Croupier
/// @param _to The recipient of the tokens
/// @param _value The amount
function transferFromCroupier(address _to, uint256 _value) onlyJackpot public {
require(_value > 0);
require(frozenPool >= _value);
frozenPool = frozenPool.sub(_value);
_transfer(croupier, _to, _value);
}
//////
/// @title Internal Methods
//
/// @dev Internal transfer function
/// @param _from From address
/// @param _to To address
/// @param _value The value to transfer
/// @return success
function _transfer(address _from, address _to, uint256 _value) internal returns (bool) {
require(_to != address(0)); // Prevent transfer to 0x0 address
require(balances[_from] >= _value); // Check if the sender has enough
balances[_from] = balances[_from].sub(_value); // Subtract from the sender
balances[_to] = balances[_to].add(_value); // Add the same to the recipient
Transfer(_from, _to, _value);
return true;
}
/// @dev Internal function for burning tokens
/// @param _sender The token sender (whose tokens are being burned)
/// @param _value The amount of tokens to burn
function _burnFromAccount(address _sender, uint256 _value) internal {
require(balances[_sender] >= _value); // Check if the sender has enough
balances[_sender] = balances[_sender].sub(_value); // Subtract from the sender
supply = supply.sub(_value); // Updates totalSupply
Burn(_sender, _value);
}
}
contract Jackpot is HouseOwned {
using SafeMath for uint;
enum Stages {
InitialOffer, // ICO stage: forming the jackpot fund
GameOn, // The game is running
GameOver, // The jackpot is won, paying out the jackpot
Aborted // ICO aborted, refunding investments
}
uint256 constant initialIcoTokenPrice = 4 finney;
uint256 constant initialBetAmount = 10 finney;
uint constant gameStartJackpotThreshold = 333 ether;
uint constant icoTerminationTimeout = 48 hours;
// These variables hold the values needed for minor prize checking:
// - when they were last won (once the number reaches the corresponding amount, the
// minor prize is won, and it should be reset)
// - how much ether was bet since it was last won
// `etherSince*` variables start with value of 1 and always have +1 in their value
// so that the variables never go 0, for gas consumption consistency
uint32 public totalBets = 0;
uint256 public etherSince20 = 1;
uint256 public etherSince50 = 1;
uint256 public etherSince100 = 1;
uint256 public pendingEtherForCroupier = 0;
// ICO status
uint32 public icoSoldTokens;
uint256 public icoEndTime;
// Jackpot status
address public lastBetUser;
uint256 public terminationTime;
address public winner;
uint256 public pendingJackpotForHouse;
uint256 public pendingJackpotForWinner;
// General configuration and stage
address public croupier;
Token public token;
Stages public stage = Stages.InitialOffer;
// Price state
uint256 public currentIcoTokenPrice = initialIcoTokenPrice;
uint256 public currentBetAmount = initialBetAmount;
// Investment tracking for emergency ICO termination
mapping (address => uint256) public investmentOf;
uint256 public abortTime;
//////
/// @title Modifiers
//
/// @dev Only Token
modifier onlyToken {
require(msg.sender == address(token));
_;
}
/// @dev Only Croupier
modifier onlyCroupier {
require(msg.sender == address(croupier));
_;
}
//////
/// @title Events
//
/// @dev Fired when tokens are sold for Ether in ICO
event EtherIco(address indexed from, uint256 value, uint256 tokens);
/// @dev Fired when a bid with Ether is made
event EtherBet(address indexed from, uint256 value, uint256 dividends);
/// @dev Fired when a bid with a Token is made
event TokenBet(address indexed from);
/// @dev Fired when a bidder wins a minor prize
/// Type: 1: 20, 2: 50, 3: 100
event MinorPrizePayout(address indexed from, uint256 value, uint8 prizeType);
/// @dev Fired when as a result of ether bid, tokens are sold from the Croupier's pool
/// The parameters are who bought them, how many tokens, and for how much Ether they were sold
event SoldTokensFromCroupier(address indexed from, uint256 value, uint256 tokens);
/// @dev Fired when the jackpot is won
event JackpotWon(address indexed from, uint256 value);
//////
/// @title Constructor and Initialization
//
/// @dev The contract constructor
/// @param _croupier The address of the trusted Croupier bot's account
function Jackpot(address _croupier)
HouseOwned()
public
{
require(_croupier != 0x0);
croupier = _croupier;
// There are no bets (it even starts in ICO stage), so initialize
// lastBetUser, just so that value is not zero and is meaningful
// The game can't end until at least one bid is made, and once
// a bid is made, this value is permanently overwritten.
lastBetUser = _croupier;
}
/// @dev Function to set address of Token contract once after creation
/// @param _token Address of the Token contract (JACK Token)
function setToken(address _token) onlyHouse public {
require(address(token) == 0x0);
require(_token != address(this)); // Protection from admin's mistake
token = Token(_token);
}
//////
/// @title Default Function
//
/// @dev The fallback function for receiving ether (bets)
/// Action depends on stages:
/// - ICO: just sell the tokens
/// - Game: accept bets, award tokens, award minor (20, 50, 100) prizes
/// - Game Over: pay out jackpot
/// - Aborted: fail
function() payable public {
require(croupier != 0x0);
require(address(token) != 0x0);
require(stage != Stages.Aborted);
uint256 tokens;
if (stage == Stages.InitialOffer) {
// First, check if the ICO is over. If it is, trigger the events and
// refund sent ether
bool started = checkGameStart();
if (started) {
// Refund ether without failing the transaction
// (because side-effect is needed)
msg.sender.transfer(msg.value);
return;
}
require(msg.value >= currentIcoTokenPrice);
// THE PLAN
// 1. [CHECK + EFFECT] Calculate how much times price, the investment amount is,
// calculate how many tokens the investor is going to get
// 2. [EFFECT] Log and count
// 3. [EFFECT] Check game start conditions and maybe start the game
// 4. [INT] Award the tokens
// 5. [INT] Transfer 20% to house
// 1. [CHECK + EFFECT] Checking the amount
tokens = _icoTokensForEther(msg.value);
// 2. [EFFECT] Log
// Log the ICO event and count investment
EtherIco(msg.sender, msg.value, tokens);
investmentOf[msg.sender] = investmentOf[msg.sender].add(
msg.value.sub(msg.value / 5)
);
// 3. [EFFECT] Game start
// Check if we have accumulated the jackpot amount required for game start
if (icoEndTime == 0 && this.balance >= gameStartJackpotThreshold) {
icoEndTime = now + icoTerminationTimeout;
}
// 4. [INT] Awarding tokens
// Award the deserved tokens (if any)
if (tokens > 0) {
token.transfer(msg.sender, tokens);
}
// 5. [INT] House
// House gets 20% of ICO according to the rules
house.transfer(msg.value / 5);
} else if (stage == Stages.GameOn) {
// First, check if the game is over. If it is, trigger the events and
// refund sent ether
bool terminated = checkTermination();
if (terminated) {
// Refund ether without failing the transaction
// (because side-effect is needed)
msg.sender.transfer(msg.value);
return;
}
// Now processing an Ether bid
require(msg.value >= currentBetAmount);
// THE PLAN
// 1. [CHECK] Calculate how much times min-bet, the bet amount is,
// calculate how many tokens the player is going to get
// 2. [CHECK] Check how much is sold from the Croupier's pool, and how much from Jackpot
// 3. [EFFECT] Deposit 25% to the Croupier (for dividends and house's benefit)
// 4. [EFFECT] Log and mark bid
// 6. [INT] Check and reward (if won) minor (20, 100, 1000) prizes
// 7. [EFFECT] Update bet amount
// 8. [INT] Award the tokens
// 1. [CHECK + EFFECT] Checking the bet amount and token reward
tokens = _betTokensForEther(msg.value);
// 2. [CHECK] Check how much is sold from the Croupier's pool, and how much from Jackpot
// The priority is (1) Croupier, (2) Jackpot
uint256 sellingFromJackpot = 0;
uint256 sellingFromCroupier = 0;
if (tokens > 0) {
uint256 croupierPool = token.frozenPool();
uint256 jackpotPool = token.balanceOf(this);
if (croupierPool == 0) {
// Simple case: only Jackpot is selling
sellingFromJackpot = tokens;
if (sellingFromJackpot > jackpotPool) {
sellingFromJackpot = jackpotPool;
}
} else if (jackpotPool == 0 || tokens <= croupierPool) {
// Simple case: only Croupier is selling
// either because Jackpot has 0, or because Croupier takes over
// by priority and has enough tokens in its pool
sellingFromCroupier = tokens;
if (sellingFromCroupier > croupierPool) {
sellingFromCroupier = croupierPool;
}
} else {
// Complex case: both are selling now
sellingFromCroupier = croupierPool; // (tokens > croupierPool) is guaranteed at this point
sellingFromJackpot = tokens.sub(sellingFromCroupier);
if (sellingFromJackpot > jackpotPool) {
sellingFromJackpot = jackpotPool;
}
}
}
// 3. [EFFECT] Croupier deposit
// Transfer a portion to the Croupier for dividend payout and house benefit
// Dividends are a sum of:
// + 25% of bet
// + 50% of price of tokens sold from Jackpot (or just anything other than the bet and Croupier payment)
// + 0% of price of tokens sold from Croupier
// (that goes in SoldTokensFromCroupier instead)
uint256 tokenValue = msg.value.sub(currentBetAmount);
uint256 croupierSaleRevenue = 0;
if (sellingFromCroupier > 0) {
croupierSaleRevenue = tokenValue.div(
sellingFromJackpot.add(sellingFromCroupier)
).mul(sellingFromCroupier);
}
uint256 jackpotSaleRevenue = tokenValue.sub(croupierSaleRevenue);
uint256 dividends = (currentBetAmount.div(4)).add(jackpotSaleRevenue.div(2));
// 100% of money for selling from Croupier still goes to Croupier
// so that it's later paid out to the selling user
pendingEtherForCroupier = pendingEtherForCroupier.add(dividends.add(croupierSaleRevenue));
// 4. [EFFECT] Log and mark bid
// Log the bet with actual amount charged (value less change)
EtherBet(msg.sender, msg.value, dividends);
lastBetUser = msg.sender;
terminationTime = now + _terminationDuration();
// If anything was sold from Croupier, log it appropriately
if (croupierSaleRevenue > 0) {
SoldTokensFromCroupier(msg.sender, croupierSaleRevenue, sellingFromCroupier);
}
// 5. [INT] Minor prizes
// Check for winning minor prizes
_checkMinorPrizes(msg.sender, currentBetAmount);
// 6. [EFFECT] Update bet amount
_updateBetAmount();
// 7. [INT] Awarding tokens
if (sellingFromJackpot > 0) {
token.transfer(msg.sender, sellingFromJackpot);
}
if (sellingFromCroupier > 0) {
token.transferFromCroupier(msg.sender, sellingFromCroupier);
}
} else if (stage == Stages.GameOver) {
require(msg.sender == winner || msg.sender == house);
if (msg.sender == winner) {
require(pendingJackpotForWinner > 0);
uint256 winnersPay = pendingJackpotForWinner;
pendingJackpotForWinner = 0;
msg.sender.transfer(winnersPay);
} else if (msg.sender == house) {
require(pendingJackpotForHouse > 0);
uint256 housePay = pendingJackpotForHouse;
pendingJackpotForHouse = 0;
msg.sender.transfer(housePay);
}
}
}
// Croupier will call this function when the jackpot is won
// If Croupier fails to call the function for any reason, house and winner
// still can claim their jackpot portion by sending ether to Jackpot
function payOutJackpot() onlyCroupier public {
require(winner != 0x0);
if (pendingJackpotForHouse > 0) {
uint256 housePay = pendingJackpotForHouse;
pendingJackpotForHouse = 0;
house.transfer(housePay);
}
if (pendingJackpotForWinner > 0) {
uint256 winnersPay = pendingJackpotForWinner;
pendingJackpotForWinner = 0;
winner.transfer(winnersPay);
}
}
//////
/// @title Public Functions
//
/// @dev View function to check whether the game should be terminated
/// Used as internal function by checkTermination, as well as by the
/// Croupier bot, to check whether it should call checkTermination
/// @return Whether the game should be terminated by timeout
function shouldBeTerminated() public view returns (bool should) {
return stage == Stages.GameOn && terminationTime != 0 && now > terminationTime;
}
/// @dev Check whether the game should be terminated, and if it should, terminate it
/// @return Whether the game was terminated as the result
function checkTermination() public returns (bool terminated) {
if (shouldBeTerminated()) {
stage = Stages.GameOver;
winner = lastBetUser;
// Flush amount due for Croupier immediately
_flushEtherToCroupier();
// The rest should be claimed by the winner (except what house gets)
JackpotWon(winner, this.balance);
uint256 jackpot = this.balance;
pendingJackpotForHouse = jackpot.div(5);
pendingJackpotForWinner = jackpot.sub(pendingJackpotForHouse);
return true;
}
return false;
}
/// @dev View function to check whether the game should be started
/// Used as internal function by `checkGameStart`, as well as by the
/// Croupier bot, to check whether it should call `checkGameStart`
/// @return Whether the game should be started
function shouldBeStarted() public view returns (bool should) {
return stage == Stages.InitialOffer && icoEndTime != 0 && now > icoEndTime;
}
/// @dev Check whether the game should be started, and if it should, start it
/// @return Whether the game was started as the result
function checkGameStart() public returns (bool started) {
if (shouldBeStarted()) {
stage = Stages.GameOn;
return true;
}
return false;
}
/// @dev Bet 1 token in the game
/// The token has already been burned having passed all checks, so
/// just process the bet of 1 token
function betToken(address _user) onlyToken public {
// Token bets can only be accepted in the game stage
require(stage == Stages.GameOn);
bool terminated = checkTermination();
if (terminated) {
return;
}
TokenBet(_user);
lastBetUser = _user;
terminationTime = now + _terminationDuration();
// Check for winning minor prizes
_checkMinorPrizes(_user, 0);
}
/// @dev Allows House to terminate ICO as an emergency measure
function abort() onlyHouse public {
require(stage == Stages.InitialOffer);
stage = Stages.Aborted;
abortTime = now;
}
/// @dev In case the ICO is emergency-terminated by House, allows investors
/// to pull back the investments
function claimRefund() public {
require(stage == Stages.Aborted);
require(investmentOf[msg.sender] > 0);
uint256 payment = investmentOf[msg.sender];
investmentOf[msg.sender] = 0;
msg.sender.transfer(payment);
}
/// @dev In case the ICO was terminated, allows House to kill the contract in 2 months
/// after the termination date
function killAborted() onlyHouse public {
require(stage == Stages.Aborted);
require(now > abortTime + 60 days);
selfdestruct(house);
}
//////
/// @title Internal Functions
//
/// @dev Get current bid timer duration
/// @return duration The duration
function _terminationDuration() internal view returns (uint256 duration) {
return (5 + 19200 / (100 + totalBets)) * 1 minutes;
}
/// @dev Updates the current ICO price according to the rules
function _updateIcoPrice() internal {
uint256 newIcoTokenPrice = currentIcoTokenPrice;
if (icoSoldTokens < 10000) {
newIcoTokenPrice = 4 finney;
} else if (icoSoldTokens < 20000) {
newIcoTokenPrice = 5 finney;
} else if (icoSoldTokens < 30000) {
newIcoTokenPrice = 5.3 finney;
} else if (icoSoldTokens < 40000) {
newIcoTokenPrice = 5.7 finney;
} else {
newIcoTokenPrice = 6 finney;
}
if (newIcoTokenPrice != currentIcoTokenPrice) {
currentIcoTokenPrice = newIcoTokenPrice;
}
}
/// @dev Updates the current bid price according to the rules
function _updateBetAmount() internal {
uint256 newBetAmount = 10 finney + (totalBets / 100) * 6 finney;
if (newBetAmount != currentBetAmount) {
currentBetAmount = newBetAmount;
}
}
/// @dev Calculates how many tokens a user should get with a given Ether bid
/// @param value The bid amount
/// @return tokens The number of tokens
function _betTokensForEther(uint256 value) internal view returns (uint256 tokens) {
// One bet amount is for the bet itself, for the rest we will sell
// tokens
tokens = (value / currentBetAmount) - 1;
if (tokens >= 1000) {
tokens = tokens + tokens / 4; // +25%
} else if (tokens >= 300) {
tokens = tokens + tokens / 5; // +20%
} else if (tokens >= 100) {
tokens = tokens + tokens / 7; // ~ +14.3%
} else if (tokens >= 50) {
tokens = tokens + tokens / 10; // +10%
} else if (tokens >= 20) {
tokens = tokens + tokens / 20; // +5%
}
}
/// @dev Calculates how many tokens a user should get with a given ICO transfer
/// @param value The transfer amount
/// @return tokens The number of tokens
function _icoTokensForEther(uint256 value) internal returns (uint256 tokens) {
// How many times the input is greater than current token price
tokens = value / currentIcoTokenPrice;
if (tokens >= 10000) {
tokens = tokens + tokens / 4; // +25%
} else if (tokens >= 5000) {
tokens = tokens + tokens / 5; // +20%
} else if (tokens >= 1000) {
tokens = tokens + tokens / 7; // ~ +14.3%
} else if (tokens >= 500) {
tokens = tokens + tokens / 10; // +10%
} else if (tokens >= 200) {
tokens = tokens + tokens / 20; // +5%
}
// Checking if Jackpot has the tokens in reserve
if (tokens > token.balanceOf(this)) {
tokens = token.balanceOf(this);
}
icoSoldTokens += (uint32)(tokens);
_updateIcoPrice();
}
/// @dev Flush the currently pending Ether to Croupier
function _flushEtherToCroupier() internal {
if (pendingEtherForCroupier > 0) {
uint256 willTransfer = pendingEtherForCroupier;
pendingEtherForCroupier = 0;
croupier.transfer(willTransfer);
}
}
/// @dev Count the bid towards minor prize fund, check if the user
/// wins a minor prize, and if they did, transfer the prize to them
/// @param user The user in question
/// @param value The bid value
function _checkMinorPrizes(address user, uint256 value) internal {
// First and foremost, increment the counters and ether counters
totalBets ++;
if (value > 0) {
etherSince20 = etherSince20.add(value);
etherSince50 = etherSince50.add(value);
etherSince100 = etherSince100.add(value);
}
// Now actually check if the bets won
uint256 etherPayout;
if ((totalBets + 30) % 100 == 0) {
// Won 100th
etherPayout = (etherSince100 - 1) / 10;
etherSince100 = 1;
MinorPrizePayout(user, etherPayout, 3);
user.transfer(etherPayout);
return;
}
if ((totalBets + 5) % 50 == 0) {
// Won 100th
etherPayout = (etherSince50 - 1) / 10;
etherSince50 = 1;
MinorPrizePayout(user, etherPayout, 2);
user.transfer(etherPayout);
return;
}
if (totalBets % 20 == 0) {
// Won 20th
etherPayout = (etherSince20 - 1) / 10;
etherSince20 = 1;
_flushEtherToCroupier();
MinorPrizePayout(user, etherPayout, 1);
user.transfer(etherPayout);
return;
}
return;
}
}
{
"compilationTarget": {
"Token.sol": "Token"
},
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":true,"inputs":[],"name":"supply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_user","type":"address"},{"name":"_value","type":"uint256"}],"name":"unfreezeDeposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFromCroupier","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"depositOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"jackpot","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"croupier","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_jackpot","type":"address"}],"name":"setJackpot","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_user","type":"address"},{"name":"_extra","type":"uint256"}],"name":"freezeDeposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_extra","type":"uint256"}],"name":"returnDeposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"frozenPool","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalDeposit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"direction","type":"uint8"},{"indexed":false,"name":"newDeposit","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"DepositFrozen","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"DepositUnfrozen","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tokenOwner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}]