pragma solidity ^0.4.18;
contract AccessControl {
/// @dev The addresses of the accounts (or contracts) that can execute actions within each roles
address public ceoAddress;
address public cooAddress;
/// @dev Keeps track whether the contract is paused. When that is true, most actions are blocked
bool public paused = false;
/// @dev The AccessControl constructor sets the original C roles of the contract to the sender account
function AccessControl() public {
ceoAddress = msg.sender;
cooAddress = msg.sender;
}
/// @dev Access modifier for CEO-only functionality
modifier onlyCEO() {
require(msg.sender == ceoAddress);
_;
}
/// @dev Access modifier for COO-only functionality
modifier onlyCOO() {
require(msg.sender == cooAddress);
_;
}
/// @dev Access modifier for any CLevel functionality
modifier onlyCLevel() {
require(msg.sender == ceoAddress || msg.sender == cooAddress);
_;
}
/// @dev Assigns a new address to act as the CEO. Only available to the current CEO
/// @param _newCEO The address of the new CEO
function setCEO(address _newCEO) public onlyCEO {
require(_newCEO != address(0));
ceoAddress = _newCEO;
}
/// @dev Assigns a new address to act as the COO. Only available to the current CEO
/// @param _newCOO The address of the new COO
function setCOO(address _newCOO) public onlyCEO {
require(_newCOO != address(0));
cooAddress = _newCOO;
}
/// @dev Modifier to allow actions only when the contract IS NOT paused
modifier whenNotPaused() {
require(!paused);
_;
}
/// @dev Modifier to allow actions only when the contract IS paused
modifier whenPaused {
require(paused);
_;
}
/// @dev Pause the smart contract. Only can be called by the CEO
function pause() public onlyCEO whenNotPaused {
paused = true;
}
/// @dev Unpauses the smart contract. Only can be called by the CEO
function unpause() public onlyCEO whenPaused {
paused = false;
}
}
contract RacingClubPresale is AccessControl {
using SafeMath for uint256;
// Max number of cars (includes sales and gifts)
uint256 public constant MAX_CARS = 999;
// Max number of cars to gift (includes unicorns)
uint256 public constant MAX_CARS_TO_GIFT = 99;
// Max number of unicorn cars to gift
uint256 public constant MAX_UNICORNS_TO_GIFT = 9;
// End date for the presale. No purchases can be made after this date.
// Thursday, May 10, 2018 11:59:59 PM
uint256 public constant PRESALE_END_TIMESTAMP = 1525996799;
// Price limits to decrease the appreciation rate
uint256 private constant PRICE_LIMIT_1 = 0.1 ether;
// Appreciation steps for each price limit
uint256 private constant APPRECIATION_STEP_1 = 0.0005 ether;
uint256 private constant APPRECIATION_STEP_2 = 0.0001 ether;
// Max count which can be bought with one transaction
uint256 private constant MAX_ORDER = 5;
// 0 - 9 valid Id's for cars
uint256 private constant CAR_MODELS = 10;
// The special car (the most rarest one) which can't be picked even with MAX_ORDER
uint256 public constant UNICORN_ID = 0;
// Maps any number from 0 - 255 to 0 - 9 car Id
uint256[] private PROBABILITY_MAP = [4, 18, 32, 46, 81, 116, 151, 186, 221, 256];
// Step by which the price should be changed
uint256 public appreciationStep = APPRECIATION_STEP_1;
// Current price of the car. The price appreciation is happening with each new sale.
uint256 public currentPrice = 0.001 ether;
// Overall cars count
uint256 public carsCount;
// Overall gifted cars count
uint256 public carsGifted;
// Gifted unicorn cars count
uint256 public unicornsGifted;
// A mapping from addresses to the carIds
mapping (address => uint256[]) private ownerToCars;
// A mapping from addresses to the upgrade packages
mapping (address => uint256) private ownerToUpgradePackages;
// Events
event CarsPurchased(address indexed _owner, uint256[] _carIds, bool _upgradePackage, uint256 _pricePayed);
event CarGifted(address indexed _receiver, uint256 _carId, bool _upgradePackage);
// Buy a car. The cars are unique within the order.
// If order count is 5 then one car can be preselected.
function purchaseCars(uint256 _carsToBuy, uint256 _pickedId, bool _upgradePackage) public payable whenNotPaused {
require(now < PRESALE_END_TIMESTAMP);
require(_carsToBuy > 0 && _carsToBuy <= MAX_ORDER);
require(carsCount + _carsToBuy <= MAX_CARS);
uint256 priceToPay = calculatePrice(_carsToBuy, _upgradePackage);
require(msg.value >= priceToPay);
// return excess ether
uint256 excess = msg.value.sub(priceToPay);
if (excess > 0) {
msg.sender.transfer(excess);
}
// initialize an array for the new cars
uint256[] memory randomCars = new uint256[](_carsToBuy);
// shows from which point the randomCars array should be filled
uint256 startFrom = 0;
// for MAX_ORDERs the first item is user picked
if (_carsToBuy == MAX_ORDER) {
require(_pickedId < CAR_MODELS);
require(_pickedId != UNICORN_ID);
randomCars[0] = _pickedId;
startFrom = 1;
}
fillRandomCars(randomCars, startFrom);
// add new cars to the owner's list
for (uint256 i = 0; i < randomCars.length; i++) {
ownerToCars[msg.sender].push(randomCars[i]);
}
// increment upgrade packages
if (_upgradePackage) {
ownerToUpgradePackages[msg.sender] += _carsToBuy;
}
CarsPurchased(msg.sender, randomCars, _upgradePackage, priceToPay);
carsCount += _carsToBuy;
currentPrice += _carsToBuy * appreciationStep;
// update this once per purchase
// to save the gas and to simplify the calculations
updateAppreciationStep();
}
// MAX_CARS_TO_GIFT amout of cars are dedicated for gifts
function giftCar(address _receiver, uint256 _carId, bool _upgradePackage) public onlyCLevel {
// NOTE
// Some promo results will be calculated after the presale,
// so there is no need to check for the PRESALE_END_TIMESTAMP.
require(_carId < CAR_MODELS);
require(_receiver != address(0));
// check limits
require(carsCount < MAX_CARS);
require(carsGifted < MAX_CARS_TO_GIFT);
if (_carId == UNICORN_ID) {
require(unicornsGifted < MAX_UNICORNS_TO_GIFT);
}
ownerToCars[_receiver].push(_carId);
if (_upgradePackage) {
ownerToUpgradePackages[_receiver] += 1;
}
CarGifted(_receiver, _carId, _upgradePackage);
carsCount += 1;
carsGifted += 1;
if (_carId == UNICORN_ID) {
unicornsGifted += 1;
}
currentPrice += appreciationStep;
updateAppreciationStep();
}
function calculatePrice(uint256 _carsToBuy, bool _upgradePackage) private view returns (uint256) {
// Arithmetic Sequence
// A(n) = A(0) + (n - 1) * D
uint256 lastPrice = currentPrice + (_carsToBuy - 1) * appreciationStep;
// Sum of the First n Terms of an Arithmetic Sequence
// S(n) = n * (a(1) + a(n)) / 2
uint256 priceToPay = _carsToBuy * (currentPrice + lastPrice) / 2;
// add an extra amount for the upgrade package
if (_upgradePackage) {
if (_carsToBuy < 3) {
priceToPay = priceToPay * 120 / 100; // 20% extra
} else if (_carsToBuy < 5) {
priceToPay = priceToPay * 115 / 100; // 15% extra
} else {
priceToPay = priceToPay * 110 / 100; // 10% extra
}
}
return priceToPay;
}
// Fill unique random cars into _randomCars starting from _startFrom
// as some slots may be already filled
function fillRandomCars(uint256[] _randomCars, uint256 _startFrom) private view {
// All random cars for the current purchase are generated from this 32 bytes.
// All purchases within a same block will get different car combinations
// as current price is changed at the end of the purchase.
//
// We don't need super secure random algorithm as it's just presale
// and if someone can time the block and grab the desired car we are just happy for him / her
bytes32 rand32 = keccak256(currentPrice, now);
uint256 randIndex = 0;
uint256 carId;
for (uint256 i = _startFrom; i < _randomCars.length; i++) {
do {
// the max number for one purchase is limited to 5
// 32 tries are more than enough to generate 5 unique numbers
require(randIndex < 32);
carId = generateCarId(uint8(rand32[randIndex]));
randIndex++;
} while(alreadyContains(_randomCars, carId, i));
_randomCars[i] = carId;
}
}
// Generate a car ID from the given serial number (0 - 255)
function generateCarId(uint256 _serialNumber) private view returns (uint256) {
for (uint256 i = 0; i < PROBABILITY_MAP.length; i++) {
if (_serialNumber < PROBABILITY_MAP[i]) {
return i;
}
}
// we should not reach to this point
assert(false);
}
// Check if the given value is already in the list.
// By default all items are 0 so _to is used explicitly to validate 0 values.
function alreadyContains(uint256[] _list, uint256 _value, uint256 _to) private pure returns (bool) {
for (uint256 i = 0; i < _to; i++) {
if (_list[i] == _value) {
return true;
}
}
return false;
}
function updateAppreciationStep() private {
// this method is called once per purcahse
// so use 'greater than' not to miss the limit
if (currentPrice > PRICE_LIMIT_1) {
// don't update if there is no change
if (appreciationStep != APPRECIATION_STEP_2) {
appreciationStep = APPRECIATION_STEP_2;
}
}
}
function carCountOf(address _owner) public view returns (uint256 _carCount) {
return ownerToCars[_owner].length;
}
function carOfByIndex(address _owner, uint256 _index) public view returns (uint256 _carId) {
return ownerToCars[_owner][_index];
}
function carsOf(address _owner) public view returns (uint256[] _carIds) {
return ownerToCars[_owner];
}
function upgradePackageCountOf(address _owner) public view returns (uint256 _upgradePackageCount) {
return ownerToUpgradePackages[_owner];
}
function allOf(address _owner) public view returns (uint256[] _carIds, uint256 _upgradePackageCount) {
return (ownerToCars[_owner], ownerToUpgradePackages[_owner]);
}
function getStats() public view returns (uint256 _carsCount, uint256 _carsGifted, uint256 _unicornsGifted, uint256 _currentPrice, uint256 _appreciationStep) {
return (carsCount, carsGifted, unicornsGifted, currentPrice, appreciationStep);
}
function withdrawBalance(address _to, uint256 _amount) public onlyCEO {
if (_amount == 0) {
_amount = address(this).balance;
}
if (_to == address(0)) {
ceoAddress.transfer(_amount);
} else {
_to.transfer(_amount);
}
}
// Raffle
// max count of raffle participants
uint256 public raffleLimit = 50;
// list of raffle participants
address[] private raffleList;
// Events
event Raffle2Registered(address indexed _iuser, address _user);
event Raffle3Registered(address _user);
function isInRaffle(address _address) public view returns (bool) {
for (uint256 i = 0; i < raffleList.length; i++) {
if (raffleList[i] == _address) {
return true;
}
}
return false;
}
function getRaffleStats() public view returns (address[], uint256) {
return (raffleList, raffleLimit);
}
function drawRaffle(uint256 _carId) public onlyCLevel {
bytes32 rand32 = keccak256(now, raffleList.length);
uint256 winner = uint(rand32) % raffleList.length;
giftCar(raffleList[winner], _carId, true);
}
function resetRaffle() public onlyCLevel {
delete raffleList;
}
function setRaffleLimit(uint256 _limit) public onlyCLevel {
raffleLimit = _limit;
}
// Raffle v1
function registerForRaffle() public {
require(raffleList.length < raffleLimit);
require(!isInRaffle(msg.sender));
raffleList.push(msg.sender);
}
// Raffle v2
function registerForRaffle2() public {
Raffle2Registered(msg.sender, msg.sender);
}
// Raffle v3
function registerForRaffle3() public payable {
Raffle3Registered(msg.sender);
}
}
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;
}
}
{
"compilationTarget": {
"RacingClubPresale.sol": "RacingClubPresale"
},
"libraries": {},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"constant":true,"inputs":[],"name":"ceoAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"withdrawBalance","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getRaffleStats","outputs":[{"name":"","type":"address[]"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"raffleLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"upgradePackageCountOf","outputs":[{"name":"_upgradePackageCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"unicornsGifted","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newCEO","type":"address"}],"name":"setCEO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_index","type":"uint256"}],"name":"carOfByIndex","outputs":[{"name":"_carId","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":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"registerForRaffle2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"carCountOf","outputs":[{"name":"_carCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"allOf","outputs":[{"name":"_carIds","type":"uint256[]"},{"name":"_upgradePackageCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appreciationStep","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resetRaffle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_address","type":"address"}],"name":"isInRaffle","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"registerForRaffle3","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_carId","type":"uint256"}],"name":"drawRaffle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PRESALE_END_TIMESTAMP","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"currentPrice","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"registerForRaffle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_CARS_TO_GIFT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cooAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"carsOf","outputs":[{"name":"_carIds","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"carsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStats","outputs":[{"name":"_carsCount","type":"uint256"},{"name":"_carsGifted","type":"uint256"},{"name":"_unicornsGifted","type":"uint256"},{"name":"_currentPrice","type":"uint256"},{"name":"_appreciationStep","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_carsToBuy","type":"uint256"},{"name":"_pickedId","type":"uint256"},{"name":"_upgradePackage","type":"bool"}],"name":"purchaseCars","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"carsGifted","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_limit","type":"uint256"}],"name":"setRaffleLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"UNICORN_ID","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MAX_UNICORNS_TO_GIFT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MAX_CARS","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_receiver","type":"address"},{"name":"_carId","type":"uint256"},{"name":"_upgradePackage","type":"bool"}],"name":"giftCar","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":false,"name":"_carIds","type":"uint256[]"},{"indexed":false,"name":"_upgradePackage","type":"bool"},{"indexed":false,"name":"_pricePayed","type":"uint256"}],"name":"CarsPurchased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_receiver","type":"address"},{"indexed":false,"name":"_carId","type":"uint256"},{"indexed":false,"name":"_upgradePackage","type":"bool"}],"name":"CarGifted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_iuser","type":"address"},{"indexed":false,"name":"_user","type":"address"}],"name":"Raffle2Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"Raffle3Registered","type":"event"}]