Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
bepro-js/contracts/PredictionMarket.sol
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
944 lines (759 sloc)
29.8 KB
This file contains 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.6.0; | |
pragma experimental ABIEncoderV2; | |
import "./RealitioERC20.sol"; | |
// openzeppelin imports | |
import "@openzeppelin/contracts/math/SafeMath.sol"; | |
library CeilDiv { | |
// calculates ceil(x/y) | |
function ceildiv(uint256 x, uint256 y) internal pure returns (uint256) { | |
if (x > 0) return ((x - 1) / y) + 1; | |
return x / y; | |
} | |
} | |
/// @title Market Contract Factory | |
contract PredictionMarket { | |
using SafeMath for uint256; | |
using CeilDiv for uint256; | |
// ------ Events ------ | |
event MarketCreated(address indexed user, uint256 indexed marketId, uint256 outcomes, string question, string image); | |
event MarketActionTx( | |
address indexed user, | |
MarketAction indexed action, | |
uint256 indexed marketId, | |
uint256 outcomeId, | |
uint256 shares, | |
uint256 value, | |
uint256 timestamp | |
); | |
event MarketOutcomePrice(uint256 indexed marketId, uint256 indexed outcomeId, uint256 value, uint256 timestamp); | |
event MarketLiquidity( | |
uint256 indexed marketId, | |
uint256 value, // total liquidity | |
uint256 price, // value of one liquidity share; max: 1 (50-50 situation) | |
uint256 timestamp | |
); | |
event MarketResolved(address indexed user, uint256 indexed marketId, uint256 outcomeId, uint256 timestamp); | |
// ------ Events End ------ | |
uint256 public constant MAX_UINT_256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935; | |
uint256 public constant ONE = 10**18; | |
enum MarketState { | |
open, | |
closed, | |
resolved | |
} | |
enum MarketAction { | |
buy, | |
sell, | |
addLiquidity, | |
removeLiquidity, | |
claimWinnings, | |
claimLiquidity, | |
claimFees, | |
claimVoided | |
} | |
struct Market { | |
// market details | |
uint256 closesAtTimestamp; | |
uint256 balance; // total stake | |
uint256 liquidity; // stake held | |
uint256 sharesAvailable; // shares held (all outcomes) | |
mapping(address => uint256) liquidityShares; | |
mapping(address => bool) liquidityClaims; // wether user has claimed liquidity earnings | |
MarketState state; // resolution variables | |
MarketResolution resolution; // fees | |
MarketFees fees; | |
// market outcomes | |
uint256[] outcomeIds; | |
mapping(uint256 => MarketOutcome) outcomes; | |
} | |
struct MarketFees { | |
uint256 value; // fee % taken from every transaction | |
uint256 poolWeight; // internal var used to ensure pro-rate fee distribution | |
mapping(address => uint256) claimed; | |
} | |
struct MarketResolution { | |
bool resolved; | |
uint256 outcomeId; | |
bytes32 questionId; // realitio questionId | |
} | |
struct MarketOutcome { | |
uint256 marketId; | |
uint256 id; | |
Shares shares; | |
} | |
struct Shares { | |
uint256 total; // number of shares | |
uint256 available; // available shares | |
mapping(address => uint256) holders; | |
mapping(address => bool) claims; // wether user has claimed winnings | |
mapping(address => bool) voidedClaims; // wether user has claimed voided market shares | |
} | |
uint256[] marketIds; | |
mapping(uint256 => Market) markets; | |
uint256 public marketIndex; | |
// governance | |
uint256 public fee; // fee % taken from every transaction | |
// realitio configs | |
address public realitioAddress; | |
uint256 public realitioTimeout; | |
// market creation | |
IERC20 public token; // token used for rewards / market creation | |
uint256 public requiredBalance; // required balance for market creation | |
// ------ Modifiers ------ | |
modifier isMarket(uint256 marketId) { | |
require(marketId < marketIndex, "Market not found"); | |
_; | |
} | |
modifier timeTransitions(uint256 marketId) { | |
if (now > markets[marketId].closesAtTimestamp && markets[marketId].state == MarketState.open) { | |
nextState(marketId); | |
} | |
_; | |
} | |
modifier atState(uint256 marketId, MarketState state) { | |
require(markets[marketId].state == state, "Market in incorrect state"); | |
_; | |
} | |
modifier notAtState(uint256 marketId, MarketState state) { | |
require(markets[marketId].state != state, "Market in incorrect state"); | |
_; | |
} | |
modifier transitionNext(uint256 marketId) { | |
_; | |
nextState(marketId); | |
} | |
modifier mustHoldRequiredBalance() { | |
require(token.balanceOf(msg.sender) >= requiredBalance, "msg.sender must hold minimum erc20 balance"); | |
_; | |
} | |
// ------ Modifiers End ------ | |
/// @dev protocol is immutable and has no ownership | |
constructor( | |
uint256 _fee, | |
IERC20 _token, | |
uint256 _requiredBalance, | |
address _realitioAddress, | |
uint256 _realitioTimeout | |
) public { | |
require(_realitioAddress != address(0), "_realitioAddress is address 0"); | |
require(_realitioTimeout > 0, "timeout must be positive"); | |
fee = _fee; | |
token = _token; | |
requiredBalance = _requiredBalance; | |
realitioAddress = _realitioAddress; | |
realitioTimeout = _realitioTimeout; | |
} | |
// ------ Core Functions ------ | |
/// @dev Creates a market, initializes the outcome shares pool and submits a question in Realitio | |
function createMarket( | |
string calldata question, | |
string calldata image, | |
uint256 closesAt, | |
address arbitrator, | |
uint256 outcomes | |
) external payable mustHoldRequiredBalance returns (uint256) { | |
uint256 marketId = marketIndex; | |
marketIds.push(marketId); | |
Market storage market = markets[marketId]; | |
require(msg.value > 0, "stake needs to be > 0"); | |
require(closesAt > now, "market must resolve after the current date"); | |
require(arbitrator != address(0), "invalid arbitrator address"); | |
// v1 - only binary markets | |
require(outcomes == 2, "number of outcomes has to be 2"); | |
market.closesAtTimestamp = closesAt; | |
market.state = MarketState.open; | |
market.fees.value = fee; | |
// setting intial value to an integer that does not map to any outcomeId | |
market.resolution.outcomeId = MAX_UINT_256; | |
// creating market outcomes | |
for (uint256 i = 0; i < outcomes; i++) { | |
market.outcomeIds.push(i); | |
MarketOutcome storage outcome = market.outcomes[i]; | |
outcome.marketId = marketId; | |
outcome.id = i; | |
} | |
// creating question in realitio | |
RealitioERC20 realitio = RealitioERC20(realitioAddress); | |
market.resolution.questionId = realitio.askQuestionERC20( | |
2, | |
question, | |
arbitrator, | |
uint32(realitioTimeout), | |
uint32(closesAt), | |
0, | |
0 | |
); | |
addLiquidity(marketId, msg.value); | |
// emiting initial price events | |
emitMarketOutcomePriceEvents(marketId); | |
emit MarketCreated(msg.sender, marketId, outcomes, question, image); | |
// incrementing market array index | |
marketIndex = marketIndex + 1; | |
return marketId; | |
} | |
/// @dev Calculates the number of shares bought with "amount" balance | |
function calcBuyAmount( | |
uint256 amount, | |
uint256 marketId, | |
uint256 outcomeId | |
) public view returns (uint256) { | |
Market storage market = markets[marketId]; | |
uint256[] memory outcomesShares = getMarketOutcomesShares(marketId); | |
uint256 amountMinusFees = amount.sub(amount.mul(market.fees.value) / ONE); | |
uint256 buyTokenPoolBalance = outcomesShares[outcomeId]; | |
uint256 endingOutcomeBalance = buyTokenPoolBalance.mul(ONE); | |
for (uint256 i = 0; i < outcomesShares.length; i++) { | |
if (i != outcomeId) { | |
uint256 outcomeShares = outcomesShares[i]; | |
endingOutcomeBalance = endingOutcomeBalance.mul(outcomeShares).ceildiv(outcomeShares.add(amountMinusFees)); | |
} | |
} | |
require(endingOutcomeBalance > 0, "must have non-zero balances"); | |
return buyTokenPoolBalance.add(amountMinusFees).sub(endingOutcomeBalance.ceildiv(ONE)); | |
} | |
/// @dev Calculates the number of shares needed to be sold in order to receive "amount" in balance | |
function calcSellAmount( | |
uint256 amount, | |
uint256 marketId, | |
uint256 outcomeId | |
) public view returns (uint256 outcomeTokenSellAmount) { | |
Market storage market = markets[marketId]; | |
uint256[] memory outcomesShares = getMarketOutcomesShares(marketId); | |
uint256 amountPlusFees = amount.mul(ONE) / ONE.sub(market.fees.value); | |
uint256 sellTokenPoolBalance = outcomesShares[outcomeId]; | |
uint256 endingOutcomeBalance = sellTokenPoolBalance.mul(ONE); | |
for (uint256 i = 0; i < outcomesShares.length; i++) { | |
if (i != outcomeId) { | |
uint256 outcomeShares = outcomesShares[i]; | |
endingOutcomeBalance = endingOutcomeBalance.mul(outcomeShares).ceildiv(outcomeShares.sub(amountPlusFees)); | |
} | |
} | |
require(endingOutcomeBalance > 0, "must have non-zero balances"); | |
return amountPlusFees.add(endingOutcomeBalance.ceildiv(ONE)).sub(sellTokenPoolBalance); | |
} | |
/// @dev Buy shares of a market outcome | |
function buy( | |
uint256 marketId, | |
uint256 outcomeId, | |
uint256 minOutcomeSharesToBuy | |
) external payable timeTransitions(marketId) atState(marketId, MarketState.open) { | |
Market storage market = markets[marketId]; | |
uint256 value = msg.value; | |
uint256 shares = calcBuyAmount(value, marketId, outcomeId); | |
require(shares >= minOutcomeSharesToBuy, "minimum buy amount not reached"); | |
require(shares > 0, "shares amount is 0"); | |
// subtracting fee from transaction value | |
uint256 feeAmount = value.mul(market.fees.value) / ONE; | |
market.fees.poolWeight = market.fees.poolWeight.add(feeAmount); | |
uint256 valueMinusFees = value.sub(feeAmount); | |
MarketOutcome storage outcome = market.outcomes[outcomeId]; | |
// Funding market shares with received funds | |
addSharesToMarket(marketId, valueMinusFees); | |
require(outcome.shares.available >= shares, "outcome shares pool balance is too low"); | |
transferOutcomeSharesfromPool(msg.sender, marketId, outcomeId, shares); | |
emit MarketActionTx(msg.sender, MarketAction.buy, marketId, outcomeId, shares, value, now); | |
emitMarketOutcomePriceEvents(marketId); | |
} | |
/// @dev Sell shares of a market outcome | |
function sell( | |
uint256 marketId, | |
uint256 outcomeId, | |
uint256 value, | |
uint256 maxOutcomeSharesToSell | |
) external payable timeTransitions(marketId) atState(marketId, MarketState.open) { | |
Market storage market = markets[marketId]; | |
MarketOutcome storage outcome = market.outcomes[outcomeId]; | |
uint256 shares = calcSellAmount(value, marketId, outcomeId); | |
require(shares <= maxOutcomeSharesToSell, "maximum sell amount exceeded"); | |
require(shares > 0, "shares amount is 0"); | |
require(outcome.shares.holders[msg.sender] >= shares, "user does not have enough balance"); | |
transferOutcomeSharesToPool(msg.sender, marketId, outcomeId, shares); | |
// adding fee to transaction value | |
uint256 feeAmount = value.mul(market.fees.value) / (ONE.sub(fee)); | |
market.fees.poolWeight = market.fees.poolWeight.add(feeAmount); | |
uint256 valuePlusFees = value.add(feeAmount); | |
require(market.balance >= valuePlusFees, "market does not have enough balance"); | |
// Rebalancing market shares | |
removeSharesFromMarket(marketId, valuePlusFees); | |
// Transferring funds to user | |
msg.sender.transfer(value); | |
emit MarketActionTx(msg.sender, MarketAction.sell, marketId, outcomeId, shares, value, now); | |
emitMarketOutcomePriceEvents(marketId); | |
} | |
/// @dev Adds liquidity to a market - external | |
function addLiquidity(uint256 marketId) | |
external | |
payable | |
timeTransitions(marketId) | |
atState(marketId, MarketState.open) | |
{ | |
addLiquidity(marketId, msg.value); | |
} | |
/// @dev Private function, used by addLiquidity and CreateMarket | |
function addLiquidity(uint256 marketId, uint256 value) | |
private | |
timeTransitions(marketId) | |
atState(marketId, MarketState.open) | |
{ | |
Market storage market = markets[marketId]; | |
require(value > 0, "stake has to be greater than 0."); | |
uint256 liquidityAmount; | |
uint256[] memory outcomesShares = getMarketOutcomesShares(marketId); | |
uint256[] memory sendBackAmounts = new uint256[](outcomesShares.length); | |
uint256 poolWeight = 0; | |
if (market.liquidity > 0) { | |
// part of the liquidity is exchanged for outcome shares if market is not balanced | |
for (uint256 i = 0; i < outcomesShares.length; i++) { | |
uint256 outcomeShares = outcomesShares[i]; | |
if (poolWeight < outcomeShares) poolWeight = outcomeShares; | |
} | |
for (uint256 i = 0; i < outcomesShares.length; i++) { | |
uint256 remaining = value.mul(outcomesShares[i]) / poolWeight; | |
sendBackAmounts[i] = value.sub(remaining); | |
} | |
liquidityAmount = value.mul(market.liquidity) / poolWeight; | |
// re-balancing fees pool | |
rebalanceFeesPool(marketId, liquidityAmount, MarketAction.addLiquidity); | |
} else { | |
// funding market with no liquidity | |
liquidityAmount = value; | |
} | |
// funding market | |
market.liquidity = market.liquidity.add(liquidityAmount); | |
market.liquidityShares[msg.sender] = market.liquidityShares[msg.sender].add(liquidityAmount); | |
addSharesToMarket(marketId, value); | |
// transform sendBackAmounts to array of amounts added | |
for (uint256 i = 0; i < sendBackAmounts.length; i++) { | |
if (sendBackAmounts[i] > 0) { | |
uint256 marketShares = market.sharesAvailable; | |
uint256 outcomeShares = market.outcomes[i].shares.available; | |
transferOutcomeSharesfromPool(msg.sender, marketId, i, sendBackAmounts[i]); | |
emit MarketActionTx( | |
msg.sender, | |
MarketAction.buy, | |
marketId, | |
i, | |
sendBackAmounts[i], | |
(marketShares.sub(outcomeShares)).mul(sendBackAmounts[i]).div(market.sharesAvailable), // price * shares | |
now | |
); | |
} | |
} | |
uint256 liquidityPrice = getMarketLiquidityPrice(marketId); | |
uint256 liquidityValue = liquidityPrice.mul(liquidityAmount) / ONE; | |
emit MarketActionTx(msg.sender, MarketAction.addLiquidity, marketId, 0, liquidityAmount, liquidityValue, now); | |
emit MarketLiquidity(marketId, market.liquidity, liquidityPrice, now); | |
} | |
/// @dev Removes liquidity to a market - external | |
function removeLiquidity(uint256 marketId, uint256 shares) | |
external | |
payable | |
timeTransitions(marketId) | |
atState(marketId, MarketState.open) | |
{ | |
Market storage market = markets[marketId]; | |
require(market.liquidityShares[msg.sender] >= shares, "user does not have enough balance"); | |
// claiming any pending fees | |
claimFees(marketId); | |
// re-balancing fees pool | |
rebalanceFeesPool(marketId, shares, MarketAction.removeLiquidity); | |
uint256[] memory outcomesShares = getMarketOutcomesShares(marketId); | |
uint256[] memory sendAmounts = new uint256[](outcomesShares.length); | |
uint256 poolWeight = MAX_UINT_256; | |
// part of the liquidity is exchanged for outcome shares if market is not balanced | |
for (uint256 i = 0; i < outcomesShares.length; i++) { | |
uint256 outcomeShares = outcomesShares[i]; | |
if (poolWeight > outcomeShares) poolWeight = outcomeShares; | |
} | |
uint256 liquidityAmount = shares.mul(poolWeight).div(market.liquidity); | |
for (uint256 i = 0; i < outcomesShares.length; i++) { | |
sendAmounts[i] = outcomesShares[i].mul(shares) / market.liquidity; | |
sendAmounts[i] = sendAmounts[i].sub(liquidityAmount); | |
} | |
// removing liquidity from market | |
removeSharesFromMarket(marketId, liquidityAmount); | |
market.liquidity = market.liquidity.sub(shares); | |
// removing liquidity tokens from market creator | |
market.liquidityShares[msg.sender] = market.liquidityShares[msg.sender].sub(shares); | |
for (uint256 i = 0; i < outcomesShares.length; i++) { | |
if (sendAmounts[i] > 0) { | |
uint256 marketShares = market.sharesAvailable; | |
uint256 outcomeShares = market.outcomes[i].shares.available; | |
transferOutcomeSharesfromPool(msg.sender, marketId, i, sendAmounts[i]); | |
emit MarketActionTx( | |
msg.sender, | |
MarketAction.buy, | |
marketId, | |
i, | |
sendAmounts[i], | |
(marketShares.sub(outcomeShares)).mul(sendAmounts[i]).div(market.sharesAvailable), // price * shares | |
now | |
); | |
} | |
} | |
// transferring user funds from liquidity removed | |
msg.sender.transfer(liquidityAmount); | |
emit MarketActionTx(msg.sender, MarketAction.removeLiquidity, marketId, 0, shares, liquidityAmount, now); | |
emit MarketLiquidity(marketId, market.liquidity, getMarketLiquidityPrice(marketId), now); | |
} | |
/// @dev Fetches winning outcome from Realitio and resolves the market | |
function resolveMarketOutcome(uint256 marketId) | |
external | |
timeTransitions(marketId) | |
atState(marketId, MarketState.closed) | |
transitionNext(marketId) | |
returns (uint256) | |
{ | |
Market storage market = markets[marketId]; | |
RealitioERC20 realitio = RealitioERC20(realitioAddress); | |
// will fail if question is not finalized | |
uint256 outcomeId = uint256(realitio.resultFor(market.resolution.questionId)); | |
market.resolution.outcomeId = outcomeId; | |
emit MarketResolved(msg.sender, marketId, outcomeId, now); | |
emitMarketOutcomePriceEvents(marketId); | |
return market.resolution.outcomeId; | |
} | |
/// @dev Allows holders of resolved outcome shares to claim earnings. | |
function claimWinnings(uint256 marketId) external atState(marketId, MarketState.resolved) { | |
Market storage market = markets[marketId]; | |
MarketOutcome storage resolvedOutcome = market.outcomes[market.resolution.outcomeId]; | |
require(resolvedOutcome.shares.holders[msg.sender] > 0, "user does not hold resolved outcome shares"); | |
require(resolvedOutcome.shares.claims[msg.sender] == false, "user already claimed resolved outcome winnings"); | |
// 1 share => price = 1 | |
uint256 value = resolvedOutcome.shares.holders[msg.sender]; | |
// assuring market has enough funds | |
require(market.balance >= value, "Market does not have enough balance"); | |
market.balance = market.balance.sub(value); | |
resolvedOutcome.shares.claims[msg.sender] = true; | |
emit MarketActionTx( | |
msg.sender, | |
MarketAction.claimWinnings, | |
marketId, | |
market.resolution.outcomeId, | |
resolvedOutcome.shares.holders[msg.sender], | |
value, | |
now | |
); | |
msg.sender.transfer(value); | |
} | |
/// @dev Allows holders of voided outcome shares to claim balance back. | |
function claimVoidedOutcomeShares(uint256 marketId, uint256 outcomeId) | |
external | |
atState(marketId, MarketState.resolved) | |
{ | |
Market storage market = markets[marketId]; | |
MarketOutcome storage outcome = market.outcomes[outcomeId]; | |
require(outcome.shares.holders[msg.sender] > 0, "user does not hold outcome shares"); | |
require(outcome.shares.voidedClaims[msg.sender] == false, "user already claimed outcome shares"); | |
// voided market - shares are valued at last market price | |
uint256 price = getMarketOutcomePrice(marketId, outcomeId); | |
uint256 value = price.mul(outcome.shares.holders[msg.sender]).div(ONE); | |
// assuring market has enough funds | |
require(market.balance >= value, "Market does not have enough balance"); | |
market.balance = market.balance.sub(value); | |
outcome.shares.voidedClaims[msg.sender] = true; | |
emit MarketActionTx( | |
msg.sender, | |
MarketAction.claimVoided, | |
marketId, | |
outcomeId, | |
outcome.shares.holders[msg.sender], | |
value, | |
now | |
); | |
msg.sender.transfer(value); | |
} | |
/// @dev Allows liquidity providers to claim earnings from liquidity providing. | |
function claimLiquidity(uint256 marketId) external atState(marketId, MarketState.resolved) { | |
Market storage market = markets[marketId]; | |
// claiming any pending fees | |
claimFees(marketId); | |
require(market.liquidityShares[msg.sender] > 0, "user does not hold liquidity shares"); | |
require(market.liquidityClaims[msg.sender] == false, "user already claimed liquidity winnings"); | |
// value = total resolved outcome pool shares * pool share (%) | |
uint256 liquidityPrice = getMarketLiquidityPrice(marketId); | |
uint256 value = liquidityPrice.mul(market.liquidityShares[msg.sender]) / ONE; | |
// assuring market has enough funds | |
require(market.balance >= value, "Market does not have enough balance"); | |
market.balance = market.balance.sub(value); | |
market.liquidityClaims[msg.sender] = true; | |
emit MarketActionTx( | |
msg.sender, | |
MarketAction.claimLiquidity, | |
marketId, | |
0, | |
market.liquidityShares[msg.sender], | |
value, | |
now | |
); | |
msg.sender.transfer(value); | |
} | |
/// @dev Allows liquidity providers to claim their fees share from fees pool | |
function claimFees(uint256 marketId) public payable { | |
Market storage market = markets[marketId]; | |
uint256 claimableFees = getUserClaimableFees(marketId, msg.sender); | |
if (claimableFees > 0) { | |
market.fees.claimed[msg.sender] = market.fees.claimed[msg.sender].add(claimableFees); | |
msg.sender.transfer(claimableFees); | |
} | |
emit MarketActionTx( | |
msg.sender, | |
MarketAction.claimFees, | |
marketId, | |
0, | |
market.liquidityShares[msg.sender], | |
claimableFees, | |
now | |
); | |
} | |
/// @dev Rebalances the fees pool. Needed in every AddLiquidity / RemoveLiquidity call | |
function rebalanceFeesPool( | |
uint256 marketId, | |
uint256 liquidityShares, | |
MarketAction action | |
) private returns (uint256) { | |
Market storage market = markets[marketId]; | |
uint256 poolWeight = liquidityShares.mul(market.fees.poolWeight).div(market.liquidity); | |
if (action == MarketAction.addLiquidity) { | |
market.fees.poolWeight = market.fees.poolWeight.add(poolWeight); | |
market.fees.claimed[msg.sender] = market.fees.claimed[msg.sender].add(poolWeight); | |
} else { | |
market.fees.poolWeight = market.fees.poolWeight.sub(poolWeight); | |
market.fees.claimed[msg.sender] = market.fees.claimed[msg.sender].sub(poolWeight); | |
} | |
} | |
/// @dev Transitions market to next state | |
function nextState(uint256 marketId) private { | |
Market storage market = markets[marketId]; | |
market.state = MarketState(uint256(market.state) + 1); | |
} | |
/// @dev Emits a outcome price event for every outcome | |
function emitMarketOutcomePriceEvents(uint256 marketId) private { | |
Market storage market = markets[marketId]; | |
for (uint256 i = 0; i < market.outcomeIds.length; i++) { | |
emit MarketOutcomePrice(marketId, i, getMarketOutcomePrice(marketId, i), now); | |
} | |
// liquidity shares also change value | |
emit MarketLiquidity(marketId, market.liquidity, getMarketLiquidityPrice(marketId), now); | |
} | |
/// @dev Adds outcome shares to shares pool | |
function addSharesToMarket(uint256 marketId, uint256 shares) private { | |
Market storage market = markets[marketId]; | |
for (uint256 i = 0; i < market.outcomeIds.length; i++) { | |
MarketOutcome storage outcome = market.outcomes[i]; | |
outcome.shares.available = outcome.shares.available.add(shares); | |
outcome.shares.total = outcome.shares.total.add(shares); | |
// only adding to market total shares, the available remains | |
market.sharesAvailable = market.sharesAvailable.add(shares); | |
} | |
market.balance = market.balance.add(shares); | |
} | |
/// @dev Removes outcome shares from shares pool | |
function removeSharesFromMarket(uint256 marketId, uint256 shares) private { | |
Market storage market = markets[marketId]; | |
for (uint256 i = 0; i < market.outcomeIds.length; i++) { | |
MarketOutcome storage outcome = market.outcomes[i]; | |
outcome.shares.available = outcome.shares.available.sub(shares); | |
outcome.shares.total = outcome.shares.total.sub(shares); | |
// only subtracting from market total shares, the available remains | |
market.sharesAvailable = market.sharesAvailable.sub(shares); | |
} | |
market.balance = market.balance.sub(shares); | |
} | |
/// @dev Transfer outcome shares from pool to user balance | |
function transferOutcomeSharesfromPool( | |
address user, | |
uint256 marketId, | |
uint256 outcomeId, | |
uint256 shares | |
) private { | |
Market storage market = markets[marketId]; | |
MarketOutcome storage outcome = market.outcomes[outcomeId]; | |
// transfering shares from shares pool to user | |
outcome.shares.holders[user] = outcome.shares.holders[user].add(shares); | |
outcome.shares.available = outcome.shares.available.sub(shares); | |
market.sharesAvailable = market.sharesAvailable.sub(shares); | |
} | |
/// @dev Transfer outcome shares from user balance back to pool | |
function transferOutcomeSharesToPool( | |
address user, | |
uint256 marketId, | |
uint256 outcomeId, | |
uint256 shares | |
) private { | |
Market storage market = markets[marketId]; | |
MarketOutcome storage outcome = market.outcomes[outcomeId]; | |
// adding shares back to pool | |
outcome.shares.holders[user] = outcome.shares.holders[user].sub(shares); | |
outcome.shares.available = outcome.shares.available.add(shares); | |
market.sharesAvailable = market.sharesAvailable.add(shares); | |
} | |
// ------ Core Functions End ------ | |
// ------ Getters ------ | |
function getUserMarketShares(uint256 marketId, address user) | |
external | |
view | |
returns ( | |
uint256, | |
uint256, | |
uint256 | |
) | |
{ | |
Market storage market = markets[marketId]; | |
return ( | |
market.liquidityShares[user], | |
market.outcomes[0].shares.holders[user], | |
market.outcomes[1].shares.holders[user] | |
); | |
} | |
function getUserClaimStatus(uint256 marketId, address user) | |
external | |
view | |
returns ( | |
bool, | |
bool, | |
bool, | |
bool, | |
uint256 | |
) | |
{ | |
Market storage market = markets[marketId]; | |
// market still not resolved | |
if (market.state != MarketState.resolved) { | |
return (false, false, false, false, getUserClaimableFees(marketId, user)); | |
} | |
MarketOutcome storage outcome = market.outcomes[market.resolution.outcomeId]; | |
return ( | |
outcome.shares.holders[user] > 0, | |
outcome.shares.claims[user], | |
market.liquidityShares[user] > 0, | |
market.liquidityClaims[user], | |
getUserClaimableFees(marketId, user) | |
); | |
} | |
function getUserLiquidityPoolShare(uint256 marketId, address user) external view returns (uint256) { | |
Market storage market = markets[marketId]; | |
return market.liquidityShares[user].mul(ONE).div(market.liquidity); | |
} | |
function getUserClaimableFees(uint256 marketId, address user) public view returns (uint256) { | |
Market storage market = markets[marketId]; | |
uint256 rawAmount = market.fees.poolWeight.mul(market.liquidityShares[user]).div(market.liquidity); | |
// No fees left to claim | |
if (market.fees.claimed[user] > rawAmount) return 0; | |
return rawAmount.sub(market.fees.claimed[user]); | |
} | |
function getMarkets() external view returns (uint256[] memory) { | |
return marketIds; | |
} | |
function getMarketData(uint256 marketId) | |
external | |
view | |
returns ( | |
MarketState, | |
uint256, | |
uint256, | |
uint256, | |
uint256, | |
int256 | |
) | |
{ | |
Market storage market = markets[marketId]; | |
return ( | |
market.state, | |
market.closesAtTimestamp, | |
market.liquidity, | |
market.balance, | |
market.sharesAvailable, | |
getMarketResolvedOutcome(marketId) | |
); | |
} | |
function getMarketAltData(uint256 marketId) | |
external | |
view | |
returns ( | |
uint256, | |
bytes32, | |
uint256 | |
) | |
{ | |
Market storage market = markets[marketId]; | |
return (market.fees.value, market.resolution.questionId, uint256(market.resolution.questionId)); | |
} | |
function getMarketQuestion(uint256 marketId) external view returns (bytes32) { | |
Market storage market = markets[marketId]; | |
return (market.resolution.questionId); | |
} | |
function getMarketPrices(uint256 marketId) | |
external | |
view | |
returns ( | |
uint256, | |
uint256, | |
uint256 | |
) | |
{ | |
return (getMarketLiquidityPrice(marketId), getMarketOutcomePrice(marketId, 0), getMarketOutcomePrice(marketId, 1)); | |
} | |
function getMarketLiquidityPrice(uint256 marketId) public view returns (uint256) { | |
Market storage market = markets[marketId]; | |
if (market.state == MarketState.resolved && !isMarketVoided(marketId)) { | |
// resolved market, price is either 0 or 1 | |
// final liquidity price = outcome shares / liquidity shares | |
return market.outcomes[market.resolution.outcomeId].shares.available.mul(ONE).div(market.liquidity); | |
} | |
// liquidity price = # liquidity shares / # outcome shares * # outcomes | |
return market.liquidity.mul(ONE * market.outcomeIds.length).div(market.sharesAvailable); | |
} | |
function getMarketResolvedOutcome(uint256 marketId) public view returns (int256) { | |
Market storage market = markets[marketId]; | |
// returning -1 if market still not resolved | |
if (market.state != MarketState.resolved) { | |
return -1; | |
} | |
return int256(market.resolution.outcomeId); | |
} | |
function isMarketVoided(uint256 marketId) public view returns (bool) { | |
Market storage market = markets[marketId]; | |
// market still not resolved, still in valid state | |
if (market.state != MarketState.resolved) { | |
return false; | |
} | |
// resolved market id does not match any of the market ids | |
return market.resolution.outcomeId >= market.outcomeIds.length; | |
} | |
// ------ Outcome Getters ------ | |
function getMarketOutcomeIds(uint256 marketId) external view returns (uint256[] memory) { | |
Market storage market = markets[marketId]; | |
return market.outcomeIds; | |
} | |
function getMarketOutcomePrice(uint256 marketId, uint256 outcomeId) public view returns (uint256) { | |
Market storage market = markets[marketId]; | |
MarketOutcome storage outcome = market.outcomes[outcomeId]; | |
if (market.state == MarketState.resolved && !isMarketVoided(marketId)) { | |
// resolved market, price is either 0 or 1 | |
return outcomeId == market.resolution.outcomeId ? ONE : 0; | |
} | |
return (market.sharesAvailable.sub(outcome.shares.available)).mul(ONE).div(market.sharesAvailable); | |
} | |
function getMarketOutcomeData(uint256 marketId, uint256 outcomeId) | |
external | |
view | |
returns ( | |
uint256, | |
uint256, | |
uint256 | |
) | |
{ | |
Market storage market = markets[marketId]; | |
MarketOutcome storage outcome = market.outcomes[outcomeId]; | |
return (getMarketOutcomePrice(marketId, outcomeId), outcome.shares.available, outcome.shares.total); | |
} | |
function getMarketOutcomesShares(uint256 marketId) private view returns (uint256[] memory) { | |
Market storage market = markets[marketId]; | |
uint256[] memory shares = new uint256[](market.outcomeIds.length); | |
for (uint256 i = 0; i < market.outcomeIds.length; i++) { | |
shares[i] = market.outcomes[i].shares.available; | |
} | |
return shares; | |
} | |
} |