Created
July 30, 2017 19:26
-
-
Save killerstorm/c0cb0e51cabd6fdc782a705cc311756d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.8; | |
contract PredictionClub { | |
struct OutcomeState { | |
uint nOutcomeTokens; | |
uint nBetTokens; | |
uint cumBetTokens; | |
uint withdrawQueueBetTokens; | |
mapping (address => uint) balances; | |
// slow withdrawal: | |
mapping (address => uint) withdrawThreshold; // at what point it's possible | |
mapping (address => uint) withdrawAmount; // amount in bet tokens | |
} | |
struct EventState { | |
uint nOutcomes; | |
uint eventIdx; | |
OutcomeState[] outcomes; | |
} | |
mapping (bytes32 => EventState) events; | |
struct EventDescription { | |
bytes32 id; | |
string description; | |
bytes32[] outcomes; | |
bytes32[] inheritEventIDs; | |
uint8[] inheritOutcomeIdxs; | |
} | |
EventDescription[] public eventDescriptions; // mostly unnecessary | |
event EventRegistered(bytes32 indexed id, string description, bytes32[] outcomes, bytes32[] inheritEventIDs, uint8[] inheritOutcomeIdxs); | |
event BoughtOutcomeTokens(address indexed who, bytes32 indexed eventID, uint8 outcomeIdx, uint betTokens); | |
event ConvertedOutcomeTokens(address indexed who, bytes32 indexed eventID, uint8 fromOutcomeIdx, uint8 toOutcomeIdx, uint betTokens); | |
event ConvertedEventTokens(address indexed who, bytes32 indexed fromEventID, | |
bytes32 indexed toEventID, | |
uint8 fromOutcomeIdx, uint8 toOutcomeIdx, uint betTokens); | |
event Withdrawn(address indexed who, uint amount, bool slow); | |
mapping (address => uint) betTokenBalances; | |
uint public totalBetTokens; | |
uint public totalEther; // or just use this.balance? | |
function registerEvent (string description, bytes32[] outcomes, bytes32[] inheritEventIDs, uint8[] inheritOutcomeIdxs) { | |
bytes32 id = keccak256(description); | |
if (events[id].nOutcomes > 0) throw; // event already exists | |
if (outcomes.length > 200) throw; // 200 ought to be enough for anybody | |
if (inheritEventIDs.length > 200) throw; | |
if (inheritEventIDs.length != inheritOutcomeIdxs.length) throw; | |
eventDescriptions.push(EventDescription(id, description, outcomes, inheritEventIDs, inheritOutcomeIdxs)); | |
EventState es = events[id]; | |
es.nOutcomes = outcomes.length; | |
es.eventIdx = eventDescriptions.length - 1; | |
for (var i = 0; i < outcomes.length; ++i) { | |
es.outcomes.push(OutcomeState(0, 0, 0, 0)); | |
} | |
EventRegistered(id, description, outcomes, inheritEventIDs, inheritOutcomeIdxs); | |
} | |
function _sellOutcomeTokens (OutcomeState storage os, address _address, uint amount) internal | |
returns (uint betTokens) | |
{ | |
if (amount > os.balances[_address]) throw; | |
betTokens = (amount * os.nBetTokens) / os.nOutcomeTokens; | |
os.balances[_address] -= amount; | |
os.nOutcomeTokens -= amount; | |
os.nBetTokens -= betTokens; | |
} | |
function _buyOutcomeTokens (OutcomeState storage os, address _address, uint betTokens, uint8 feePrc, bool track) internal | |
returns (uint amount) | |
{ | |
if (os.nBetTokens == 0) { | |
amount = betTokens; | |
} else { | |
if (feePrc == 0) { | |
amount = (os.nOutcomeTokens * betTokens) / os.nBetTokens; | |
} else { | |
uint fee = (feePrc * betTokens) / 100; | |
// what the formula below means: | |
// fee is added to the outcome's bet tokens pool, and after that | |
// purchase is made at fair price | |
amount = (os.nOutcomeTokens * (betTokens - fee)) / (os.nBetTokens + fee); | |
} | |
} | |
os.balances[_address] += amount; | |
os.nOutcomeTokens += amount; | |
os.nBetTokens += betTokens; | |
if (track) os.cumBetTokens += betTokens; | |
} | |
function convertOutcomeTokens (bytes32 eventID, uint8 fromOutcomeIdx, uint8 toOutcomeIdx, uint amount) { | |
EventState es = events[eventID]; | |
if (es.nOutcomes == 0 || | |
fromOutcomeIdx >= es.nOutcomes || | |
toOutcomeIdx >= es.nOutcomes) | |
throw; | |
OutcomeState fromOutcome = es.outcomes[fromOutcomeIdx]; | |
OutcomeState toOutcome = es.outcomes[toOutcomeIdx]; | |
uint gotBetTokens = _sellOutcomeTokens(fromOutcome, msg.sender, amount); | |
_buyOutcomeTokens(toOutcome, msg.sender, gotBetTokens, 8, true); | |
ConvertedOutcomeTokens(msg.sender, eventID, fromOutcomeIdx, toOutcomeIdx, gotBetTokens); | |
} | |
function buyOutcomeTokens (bytes32 eventID, uint8 outcomeIdx) payable | |
{ | |
OutcomeState os = getOutcomeState(eventID, outcomeIdx); | |
uint betTokens = _buyBetTokens(); | |
_buyOutcomeTokens(os, msg.sender, betTokens, 1, true); | |
BoughtOutcomeTokens(msg.sender, eventID, outcomeIdx, betTokens); | |
} | |
function enqueueSlowWithdrawal (bytes32 eventID, uint8 outcomeIdx, uint amount) { | |
OutcomeState os = getOutcomeState(eventID, outcomeIdx); | |
uint betTokens = _sellOutcomeTokens(os, msg.sender, amount); | |
os.withdrawQueueBetTokens += betTokens; | |
os.withdrawThreshold[msg.sender] = (os.cumBetTokens + os.withdrawQueueBetTokens); | |
os.withdrawAmount[msg.sender] += betTokens; | |
} | |
function getOutcomeState(bytes32 eventID, uint8 outcomeIdx) | |
internal returns (OutcomeState storage os) { | |
EventState es = events[eventID]; | |
if (es.nOutcomes == 0 || outcomeIdx >= es.nOutcomes) throw; | |
os = es.outcomes[outcomeIdx]; | |
} | |
function completeSlowWithdrawal (bytes32 eventID, uint8 outcomeIdx) { | |
OutcomeState os = getOutcomeState(eventID, outcomeIdx); | |
uint withdrawThreshold = os.withdrawThreshold[msg.sender]; | |
if ((withdrawThreshold > 0) && (withdrawThreshold < os.cumBetTokens)) { | |
uint betTokens = os.withdrawAmount[msg.sender]; | |
os.withdrawThreshold[msg.sender] = 0; | |
os.withdrawAmount[msg.sender] = 0; | |
os.withdrawQueueBetTokens -= betTokens; | |
uint etherValue = (betTokens * totalEther) / totalBetTokens; | |
if (etherValue > totalEther) throw; // dafuq | |
totalEther -= etherValue; | |
totalBetTokens -= betTokens; | |
if (!msg.sender.send(etherValue)) throw; | |
Withdrawn(msg.sender, etherValue, true); | |
} else throw; | |
} | |
function cancelSlowWithdrawal (bytes32 eventID, uint8 outcomeIdx) { | |
OutcomeState os = getOutcomeState(eventID, outcomeIdx); | |
uint betTokens = os.withdrawAmount[msg.sender]; | |
if (betTokens > 0) { | |
os.withdrawThreshold[msg.sender] = 0; | |
os.withdrawAmount[msg.sender] = 0; | |
_buyOutcomeTokens(os, msg.sender, betTokens, 0, false); | |
} | |
} | |
function canCompleteSlowWithdrawal (address who, bytes32 eventID, uint8 outcomeIdx) constant returns (bool) { | |
OutcomeState os = getOutcomeState(eventID, outcomeIdx); | |
uint withdrawThreshold = os.withdrawThreshold[msg.sender]; | |
if ((withdrawThreshold > 0) && (withdrawThreshold < os.cumBetTokens)) { | |
return true; | |
} else | |
return false; | |
} | |
function withdrawOutcomeTokens (bytes32 eventID, uint8 outcomeIdx, uint amount) { | |
OutcomeState os = getOutcomeState(eventID, outcomeIdx); | |
uint betTokens = _sellOutcomeTokens(os, msg.sender, amount); | |
uint etherValue = (betTokens * totalEther * 90) / totalBetTokens / 100; | |
if (etherValue > totalEther) throw; // dafuq | |
totalEther -= etherValue; | |
totalBetTokens -= betTokens; | |
if (!msg.sender.send(etherValue)) throw; | |
Withdrawn(msg.sender, etherValue, false); | |
} | |
function _buyBetTokens() internal returns (uint betTokens) { | |
betTokens = msg.value; // when contract is empty price is 1 | |
if (totalBetTokens > 0) { | |
betTokens = (msg.value * totalBetTokens) / totalEther; | |
} | |
totalBetTokens += betTokens; | |
totalEther += msg.value; | |
} | |
function convertEventTokens (bytes32 fromEventID, uint8 fromOutcomeIdx, bytes32 toEventID, uint8 toOutcomeIdx, | |
uint amount) | |
{ | |
EventState fromES = events[fromEventID]; | |
EventState toES = events[toEventID]; | |
if (fromES.nOutcomes == 0 || fromOutcomeIdx >= fromES.nOutcomes) throw; | |
if (toES.nOutcomes == 0 || toOutcomeIdx >= toES.nOutcomes) throw; | |
EventDescription toEventDescr = eventDescriptions[toES.eventIdx]; | |
OutcomeState fromOutcome = fromES.outcomes[fromOutcomeIdx]; | |
OutcomeState toOutcome = toES.outcomes[toOutcomeIdx]; | |
bool found = false; | |
for (uint8 i = 0; i < toEventDescr.inheritEventIDs.length; ++i) { | |
if ((toEventDescr.inheritEventIDs[i] == fromEventID) | |
&& (toEventDescr.inheritOutcomeIdxs[i] == fromOutcomeIdx)) { | |
found = true; | |
break; | |
} | |
} | |
if (!found) throw; | |
uint gotBetTokens = _sellOutcomeTokens(fromOutcome, msg.sender, amount); | |
_buyOutcomeTokens(toOutcome, msg.sender, gotBetTokens, 0, true); | |
ConvertedEventTokens(msg.sender, fromEventID, | |
toEventID, fromOutcomeIdx, toOutcomeIdx, gotBetTokens); | |
} | |
function getEventOutcomes(uint eventIdx) constant returns (bytes32[] outcomes) { | |
outcomes = eventDescriptions[eventIdx].outcomes; | |
} | |
function getInherits(uint eventIdx) constant returns (bytes32[] inheritEventIDs, uint8[] inheritOutcomeIdxs) { | |
inheritEventIDs = eventDescriptions[eventIdx].inheritEventIDs; | |
inheritOutcomeIdxs = eventDescriptions[eventIdx].inheritOutcomeIdxs; | |
} | |
function getBalance(address addr, bytes32 eventID, uint8 outcomeIdx) | |
constant returns (uint balance) { | |
OutcomeState outcome = getOutcomeState(eventID, outcomeIdx); | |
balance = outcome.balances[addr]; | |
} | |
function getEthBalance(address addr, bytes32 eventID, uint8 outcomeIdx) | |
constant returns (uint balance, uint tokens) { | |
OutcomeState outcome = getOutcomeState(eventID, outcomeIdx); | |
tokens = outcome.balances[addr]; | |
balance = (tokens * totalEther) / totalBetTokens; | |
} | |
function getOutcomeTotalEther(bytes32 eventID, uint8 outcomeIdx) | |
constant returns (uint total) | |
{ | |
OutcomeState outcome = getOutcomeState(eventID, outcomeIdx); | |
total = (outcome.nBetTokens * totalEther) / totalBetTokens; | |
} | |
function getEventIndex(bytes32 eventID) constant returns (uint) { | |
EventState es = events[eventID]; | |
if (es.nOutcomes > 0) { | |
return es.eventIdx; | |
} else { | |
throw; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment