Skip to content

Instantly share code, notes, and snippets.

@killerstorm
Created July 30, 2017 19:26
Show Gist options
  • Save killerstorm/c0cb0e51cabd6fdc782a705cc311756d to your computer and use it in GitHub Desktop.
Save killerstorm/c0cb0e51cabd6fdc782a705cc311756d to your computer and use it in GitHub Desktop.
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