Smart Pool Lottery

Here you can review the smart contract:

Review the smart contract:


- You can check it on etherscan: https://etherscan.io/address/0x00fc2e075bc935c7c4283d277b90e6b9c822a105#code

- Contract deployed at address: 0x00fc2e075bc935c7c4283d277b90e6b9c822a105.


- Review it here:


pragma solidity ^0.4.16;


//Define the pool
contract SmartPool {

//Pool info
uint currAmount; //Current amount in the pool (=balance)
uint ticketPrice; //Price of one ticket
uint startDate; //The date of opening
uint endDate; //The date of closing (or 0 if still open)

//Block infos (better to use block number than dates to trigger the end)
uint startBlock;
uint endBlock;

//End triggers
uint duration; //The pool ends when the duration expire
uint ticketCount; //Or when the reserve of tickets has been sold
bool ended; //Current state (can't buy tickets when ended)
bool terminated; //true if a winner has been picked
bool moneySent; //true if the winner has picked his money

//Min wait duration between ended and terminated states
uint constant blockDuration = 15; // we use 15 sec for the block duration
uint constant minWaitDuration = 240; // (= 3600 / blockDuration => 60 minutes waiting between 'ended' and 'terminated')

//Players
address[] players; //List of tickets owners, each ticket gives an entry in the array

//Winning info
address winner; //The final winner (only available when terminated == true)

//Pool manager address (only the manager can call modifiers of this contract, see PoolManager.sol)
address poolManager;

//Create a pool with a fixed ticket price, a ticket reserve and/or a duration)
function SmartPool(uint _ticketPrice, uint _ticketCount, uint _duration) public
{
//Positive ticket price and either ticketCount or duration must be provided
require(_ticketPrice > 0 && (_ticketCount > 0 || _duration > blockDuration));

//Check for overflows
require(now + _duration >= now);

//Set ticketCount if needed (according to max balance)

if (_ticketCount == 0)
{
_ticketCount = (2 ** 256 - 1) / _ticketPrice;
}

require(_ticketCount * _ticketPrice >= _ticketPrice);

//Store manager
poolManager = msg.sender;

//Init
currAmount = 0;
startDate = now;
endDate = 0;
startBlock = block.number;
endBlock = 0;
ticketPrice = _ticketPrice;
ticketCount = _ticketCount;
duration = _duration / blockDuration; // compute duration in blocks
ended = false;
terminated = false;
moneySent = false;
winner = 0x0000000000000000000000000000000000000000;
}
//Accessors
function getPlayers() public constant returns (address[])
{
return players;
}

function getStartDate() public constant returns (uint)
{
return startDate;
}

function getStartBlock() public constant returns (uint)
{
return startBlock;
}

function getCurrAmount() public constant returns (uint)
{
return currAmount;
}

function getTicketPrice() public constant returns (uint)
{
return ticketPrice;
}

function getTicketCount() public constant returns (uint)
{
return ticketCount;
}

function getBoughtTicketCount() public constant returns (uint)
{
return players.length;
}

function getAvailableTicketCount() public constant returns (uint)
{
return ticketCount - players.length;
}

function getEndDate() public constant returns (uint)
{
return endDate;
}

function getEndBlock() public constant returns (uint)
{
return endBlock;
}

function getDuration() public constant returns (uint)
{
return duration; // duration in blocks
}

function getDurationS() public constant returns (uint)
{
return duration * blockDuration; // duration in seconds
}

function isEnded() public constant returns (bool)
{
return ended;
}

function isTerminated() public constant returns (bool)
{
return terminated;
}

function isMoneySent() public constant returns (bool)
{
return moneySent;
}

function getWinner() public constant returns (address)
{
return winner;
}

//End trigger
function checkEnd() public
{
if ( (duration > 0 && block.number >= startBlock + duration) || (players.length >= ticketCount) )
{
ended = true;
endDate = now;
endBlock = block.number;
}
}

//Add player with ticketCount to the pool (only poolManager can do this)
function addPlayer(address player, uint ticketBoughtCount, uint amount) public
{
//Only manager can call this
require(msg.sender == poolManager);

//Revert if pool ended (should not happen because the manager check this too)
require (!ended);

//Add amount to the pool
currAmount += amount; // amount has been checked by the manager

//Add player to the ticket owner array, for each bought ticket
for (uint i = 0; i < ticketBoughtCount; i++)
players.push(player);

//Check end
checkEnd();
}

function canTerminate() public constant returns(bool)
{
return ended && !terminated && block.number - endBlock >= minWaitDuration;
}

//Terminate the pool by picking a winner (only poolManager can do this, after the pool is ended and some time has passed so the seed has changed many times)
function terminate(uint randSeed) public
{
//Only manager can call this
require(msg.sender == poolManager);

//The pool need to be ended, but not terminated
require(ended && !terminated);

//Min duration between ended and terminated
require(block.number - endBlock >= minWaitDuration);

//Only one call to this function
terminated = true;

//Pick a winner
if (players.length > 0)
winner = players[randSeed % players.length];
}

//Update pool state (only poolManager can call this when the money has been sent)
function onMoneySent() public
{
//Only manager can call this
require(msg.sender == poolManager);

//The pool must be terminated (winner picked)
require(terminated);

//Update money sent (only one call to this function)
require(!moneySent);
moneySent = true;
}
}


//Wallet interface
contract WalletContract
{
function payMe() public payable;
}


contract PoolManager {

//Pool owner (address which manage the pool creation)
address owner;

//Wallet which receive the fees (1% of ticket price)
address wallet;

//Fees infos (external websites providing access to pools get 1% too)
mapping(address => uint) fees;

//Fees divider (1% for the wallet, and 1% for external website where player can buy tickets)
uint constant feeDivider = 100; //(1/100 of the amount)

//The ticket price for pools must be a multiple of 0.010205 ether (to avoid truncating the fees, and having a minimum to send to the winner)
uint constant ticketPriceMultiple = 10205000000000000; //(multiple of 0.010205 ether for ticketPrice)

//Pools infos (current active pools. When a pool is done, it goes into the poolsDone array bellow and a new pool is created to replace it at the same index)
SmartPool[] pools;

//Ended pools (cleaned automatically after winners get their prices)
SmartPool[] poolsDone;

//History (contains all the pools since the deploy)
SmartPool[] poolsHistory;

//Current rand seed (it changes a lot so it's pretty hard to know its value when the winner is picked)
uint randSeed;

//Constructor (only owner)
function PoolManager(address wal) public
{
owner = msg.sender;
wallet = wal;

randSeed = 0;
}

//Called frequently by other functions to keep the seed moving
function updateSeed() private
{
randSeed += (uint(block.blockhash(block.number - 1)));
}

//Create a new pool (only owner can do this)
function addPool(uint ticketPrice, uint ticketCount, uint duration) public
{
require(msg.sender == owner);
require(ticketPrice >= ticketPriceMultiple && ticketPrice % ticketPriceMultiple == 0);

//Deploy a new pool
pools.push(new SmartPool(ticketPrice, ticketCount, duration));
}

//Accessors (public)

//Get Active Pools
function getPoolCount() public constant returns(uint)
{
return pools.length;
}
function getPool(uint index) public constant returns(address)
{
require(index < pools.length);
return pools[index];
}

//Get Ended Pools
function getPoolDoneCount() public constant returns(uint)
{
return poolsDone.length;
}
function getPoolDone(uint index) public constant returns(address)
{
require(index < poolsDone.length);
return poolsDone[index];
}

//Get History
function getPoolHistoryCount() public constant returns(uint)
{
return poolsHistory.length;
}
function getPoolHistory(uint index) public constant returns(address)
{
require(index < poolsHistory.length);
return poolsHistory[index];
}

//Buy tickets for a pool (public)
function buyTicket(uint poolIndex, uint ticketCount, address websiteFeeAddr) public payable
{
require(poolIndex < pools.length);
require(ticketCount > 0);

//Get pool and check state
SmartPool pool = pools[poolIndex];
pool.checkEnd();
require (!pool.isEnded());

//Adjust ticketCount according to available tickets
uint availableCount = pool.getAvailableTicketCount();
if (ticketCount > availableCount)
ticketCount = availableCount;

//Get amount required and check msg.value
uint amountRequired = ticketCount * pool.getTicketPrice();
require(msg.value >= amountRequired);

//If too much value sent, we send it back to player
uint amountLeft = msg.value - amountRequired;

//if no websiteFeeAddr given, the wallet get the fee
if (websiteFeeAddr == address(0))
websiteFeeAddr = wallet;

//Compute fee
uint feeAmount = amountRequired / feeDivider;

addFee(websiteFeeAddr, feeAmount);
addFee(wallet, feeAmount);

//Add player to the pool with the amount minus the fees (1% + 1% = 2%)
pool.addPlayer(msg.sender, ticketCount, amountRequired - 2 * feeAmount);

//Send back amountLeft to player if too much value sent
if (amountLeft > 0 && !msg.sender.send(amountLeft))
{
addFee(wallet, amountLeft); // if it fails, we take it as a fee..
}

updateSeed();
}

//Check pools end. (called by our console each 10 minutes, or can be called by anybody)
function checkPoolsEnd() public
{
for (uint i = 0; i < pools.length; i++)
{
//Check each pool and restart the ended ones
checkPoolEnd(i);
}
}

//Check end of a pool and restart it if it's ended (public)
function checkPoolEnd(uint i) public
{
require(i < pools.length);

//Check end (if not triggered yet)
SmartPool pool = pools[i];
if (!pool.isEnded())
pool.checkEnd();

if (!pool.isEnded())
{
return; // not ended yet
}

updateSeed();

//Store pool done and restart a pool to replace it
poolsDone.push(pool);
pools[i] = new SmartPool(pool.getTicketPrice(), pool.getTicketCount(), pool.getDurationS());
}

//Check pools done. (called by our console, or can be called by anybody)
function checkPoolsDone() public
{
for (uint i = 0; i < poolsDone.length; i++)
{
checkPoolDone(i);
}
}

//Check end of one pool
function checkPoolDone(uint i) public
{
require(i < poolsDone.length);

SmartPool pool = poolsDone[i];
if (pool.isTerminated())
return; // already terminated

if (!pool.canTerminate())
return; // we need to wait a bit more before random occurs, so the seed has changed enough (60 minutes before ended and terminated states)

updateSeed();

//Terminate (pick a winner) and store pool done
pool.terminate(randSeed);
}

//Send money of the pool to the winner (public)
function sendPoolMoney(uint i) public
{
require(i < poolsDone.length);

SmartPool pool = poolsDone[i];
require (pool.isTerminated()); // we need a winner picked

require(!pool.isMoneySent()); // money not sent

uint amount = pool.getCurrAmount();
address winner = pool.getWinner();
pool.onMoneySent();
if (amount > 0 && !winner.send(amount)) // the winner can't get his money (should not happen)
{
addFee(wallet, amount);
}

//Pool goes into history array
poolsHistory.push(pool);
}

//Clear pools done array (called once a week by our console, or can be called by anybody)
function clearPoolsDone() public
{
//Make sure all pools are terminated with no money left
for (uint i = 0; i < poolsDone.length; i++)
{
if (!poolsDone[i].isMoneySent())
return;
}

//"Clear" poolsDone array (just reset the length, instances will be override)
poolsDone.length = 0;
}

//Get current fee value
function getFeeValue(address a) public constant returns (uint)
{
if (a == address(0))
a = msg.sender;
return fees[a];
}

//Send fee to address (public, with a min amount required)
function getMyFee(address a) public
{
if (a == address(0))
a = msg.sender;
uint amount = fees[a];
require (amount > 0);

fees[a] = 0;

if (a == wallet)
{
WalletContract walletContract = WalletContract(a);
walletContract.payMe.value(amount)();
}
else if (!a.send(amount))
addFee(wallet, amount); // the fee can't be sent (hacking attempt?), so we take it... :-p
}

//Add fee (private)
function addFee(address a, uint fee) private
{
if (fees[a] == 0)
fees[a] = fee;
else
fees[a] += fee; // we don't check for overflow, if you're billionaire in fees, call getMyFee sometimes :-)
}
}