pragma solidity ^0.4.4; contract Etherdate { uint constant startingPrice = 20 finney; string constant startingMessage = "Nothing to see here..."; uint constant dummyCoinID = 0; // There are 366 coins (1-indexed so that 0 can be used as a non-assignment flag): // day | id // 1/1 | 1 // ... // 12/31 | 365 // 2/29 | 366 (leap day) mapping(uint => address) public coinToOwner; mapping(uint => string) public coinToMessage; mapping(uint => uint) public coinToPrice; mapping(address => uint) _pendingWithdrawals; // sorted (lowest-highest) array of top 10 coin IDs by price uint[10] _top10Coins; // May not need this address public creator; modifier onlyCreator() { require(msg.sender == creator); _; } function Etherdate() public { creator = msg.sender; } function buy(uint id, string message) public payable returns (bool) { require(id >= 1 && id <= 366); var (owner, prevMessage, price) = _getCoinData(id); if (msg.value >= price) { var (fee, payment) = _extractFee(msg.value); _pendingWithdrawals[creator] += fee; _pendingWithdrawals[owner] += payment; _assignCoin(id, msg.sender, message, _determineNewPrice(msg.value)); _updateTop10Coins(id); return true; } else { return false; } } function _assignCoin(uint id, address owner, string message, uint newPrice) private { coinToOwner[id] = owner; coinToMessage[id] = message; coinToPrice[id] = newPrice; } // Extract fee to be paid to contract creator. Fee is defined entirely in this contract! Can be changed in // future versions function _extractFee(uint amountPaid) private pure returns (uint, uint) { uint fee = amountPaid / 100; return (fee, amountPaid - fee); } // 20 -> 320 f = 2x // 320 -> 1620 f = 1.5x // 1620 -> ... f = 1.1x function _determineNewPrice(uint amountPaid) private pure returns (uint) { if (amountPaid < 320 finney) { return amountPaid * 2; } else if (amountPaid < 1620 finney) { return (amountPaid * 3) / 2; } else { return (amountPaid * 11) / 10; } } // Do an insertion sort into the list and then unshift elements 'behind it' function _updateTop10Coins(uint newCoinId) private { _removeExistingFromTop10(newCoinId); _insertNewCoinTop10(newCoinId); } function _insertNewCoinTop10(uint newCoinId) private { uint newPrice = coinToPrice[newCoinId]; // get insertion index uint8 i = 0; while (i < 10 && (_top10Coins[i] == dummyCoinID || newPrice >= coinToPrice[_top10Coins[i]])) { i++; } // don't need to insert if doesn't belong in top 10 if (i > 0) { uint8 insertionIndex = i - 1; uint idToInsert = newCoinId; uint tmp; while (idToInsert != dummyCoinID) { tmp = _top10Coins[insertionIndex]; _top10Coins[insertionIndex] = idToInsert; // Don't do shifting logic if we're at the beginning of the list! if (insertionIndex == 0) { break; } insertionIndex--; idToInsert = tmp; } } } // Remove all existing instances of new coin in the top 10 and shift list accordingly // i.e. if newCoinId = 1 and top10List = [0,...,3,1,2] -> [0,...,3,2] function _removeExistingFromTop10(uint newCoinId) private { uint newCoinIdx = 10; // only top 10, so this can never be an issue... May want to hardcode in top 10-ness for (uint i = 0; i < 10; i++) { if (_top10Coins[i] == newCoinId) { newCoinIdx = i; } } if (newCoinIdx < 10) { _top10Coins[newCoinIdx] = dummyCoinID; uint dummyIdx = newCoinIdx; // Right shift dummy coins while (dummyIdx > 0) { _top10Coins[dummyIdx] = _top10Coins[dummyIdx - 1]; _top10Coins[dummyIdx - 1] = dummyCoinID; dummyIdx--; } } } // NOTE: Maybe this should return a 'nil'-type value function getCoinData(uint id) public view returns (address, string, uint) { return _getCoinData(id); } function _getCoinData(uint id) private view returns (address, string, uint) { address owner; string memory message; uint price; if (coinToPrice[id] == dummyCoinID) { owner = creator; message = startingMessage; price = startingPrice; } else { owner = coinToOwner[id]; message = coinToMessage[id]; price = coinToPrice[id]; } return (owner, message, price); } function getTop10Coins() public view returns (uint[10]) { return _top10Coins; } // Withdraw split out to avoid re-entrancey if buy fails on send function withdraw() public { uint amount = _pendingWithdrawals[msg.sender]; if (amount > 0) { _pendingWithdrawals[msg.sender] = 0; // zero out withdrawal first to protect against re-entrancy msg.sender.transfer(amount); } } function getPendingWithdrawal() public view returns (uint) { return _pendingWithdrawals[msg.sender]; } // NOTE: For retrieving withdrawals in case a new generation is necessary function getPendingWithdrawal(address user) public view onlyCreator returns (uint) { return _pendingWithdrawals[user]; } }