Skip to content
This repository has been archived by the owner on Sep 3, 2019. It is now read-only.

Commit

Permalink
Merge pull request #23 from bodhiproject/calc-winnings-fix
Browse files Browse the repository at this point in the history
Calc winnings fix
  • Loading branch information
dwalintukan committed Jun 4, 2019
2 parents cd0edba + bdad8f6 commit 77faf0b
Show file tree
Hide file tree
Showing 7 changed files with 1,301 additions and 899 deletions.
13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
language: node_js

node_js:
- "9"

cache: npm

install:
- npm install -g [email protected]
- npm install

script:
- npm run test
4 changes: 2 additions & 2 deletions contracts/event/EventFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ contract EventFactory is NRC223Receiver {
uint amount;
}

uint16 private constant VERSION = 3;
uint16 private constant VERSION = 5;

address private _configManager;
address private _bodhiTokenAddress;
Expand Down Expand Up @@ -54,7 +54,7 @@ contract EventFactory is NRC223Receiver {
bytes memory funcHash = data.sliceBytes(0, 4);
bytes memory params = data.sliceBytes(4, data.length - 4);
bytes32 encodedFunc = keccak256(abi.encodePacked(funcHash));
if (encodedFunc == keccak256(abi.encodePacked(hex"2b2601bf"))) {
if (encodedFunc == keccak256(abi.encodePacked(hex"662edd20"))) {
handleCreateMultipleResultsEvent(from, value, params);
} else {
revert("Unhandled function in tokenFallback");
Expand Down
233 changes: 150 additions & 83 deletions contracts/event/MultipleResultsEvent.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,16 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
using ByteUtils for bytes32;
using SafeMath for uint;

/// @dev Represents all the bets of a result.
struct ResultBalance {
uint total;
mapping(address => uint) bets;
}

/// @dev Represents the aggregated bets/votes of a round.
struct EventRound {
bool finished;
uint8 lastResultIndex;
uint8 resultIndex;
uint consensusThreshold;
uint arbitrationEndTime;
ResultBalance[4] balances;
}

uint16 private constant VERSION = 3;
uint16 private constant VERSION = 5;
uint8 private constant INVALID_RESULT_INDEX = 255;

uint8 private _numOfResults;
Expand All @@ -49,7 +42,11 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
uint private _thresholdPercentIncrease;
uint private _arbitrationRewardPercentage;
uint private _totalBets;
uint[4] private _resultTotals;
uint[4] private _betRoundTotals;
uint[4] private _voteRoundsTotals;
uint[4] private _currentVotingRoundTotals;
mapping(address => uint[4]) private _betRoundUserTotals;
mapping(address => uint[4]) private _voteRoundsUserTotals;
mapping(uint8 => EventRound) private _eventRounds;
mapping(address => bool) private _didWithdraw;

Expand Down Expand Up @@ -188,7 +185,7 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
external
{
require(msg.sender == _bodhiTokenAddress, "Only NBOT is accepted");
require(data.length >= 4, "Data is not long enough.");
require(data.length >= 4, "Data is not long enough");

bytes memory betFunc = hex"885ab66d";
bytes memory setResultFunc = hex"a6b4218b";
Expand Down Expand Up @@ -243,10 +240,10 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
}

/// @notice Calculates the tokens returned based on the sender's participation.
/// @param better Address to calculate winnings for.
/// @return Amount of bet and vote tokens won.
/// @param player Address to calculate winnings for.
/// @return Amount of tokens that will be returned.
function calculateWinnings(
address better)
address player)
public
view
returns (uint)
Expand All @@ -257,56 +254,13 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
return 0;
}

// Calculate bet round losers' total
uint betRoundLosersTotal;
for (uint8 i = 0; i < _numOfResults; i++) {
if (i != _currentResultIndex) {
betRoundLosersTotal =
betRoundLosersTotal.add(_eventRounds[0].balances[i].total);
}
}

// Subtract arbitration reward from bet round losers' total
uint arbitrationReward =
uint(_arbitrationRewardPercentage).mul(betRoundLosersTotal).div(100);
betRoundLosersTotal = betRoundLosersTotal.sub(arbitrationReward);

// Calculate all vote rounds totals
uint voteRoundsWinnersTotal;
uint voteRoundsLosersTotal;
for (uint8 i = 1; i <= _currentRound; i++) {
for (uint8 j = 0; j < _numOfResults; j++) {
uint total = _eventRounds[i].balances[j].total;
if (j == _currentResultIndex) {
voteRoundsWinnersTotal = voteRoundsWinnersTotal.add(total);
} else {
voteRoundsLosersTotal = voteRoundsLosersTotal.add(total);
}
}
}

// Calculate all rounds totals
uint allRoundsWinnersTotal = _resultTotals[_currentResultIndex];
uint allRoundsLosersTotal = betRoundLosersTotal.add(voteRoundsLosersTotal);

// Calculate user's winning bets
uint allRoundsUserBets;
uint voteRoundsUserBets;
for (uint8 i = 0; i <= _currentRound; i++) {
uint bets = _eventRounds[i].balances[_currentResultIndex].bets[better];
allRoundsUserBets = allRoundsUserBets.add(bets);
if (i > 0) {
voteRoundsUserBets = voteRoundsUserBets.add(bets);
}
// Bets are returned if the currentResultIndex is Invalid
if (_currentResultIndex == 0) {
return calculateInvalidWinnings(player);
}

// Calculate users portion of all rounds losers total
uint winningAmt = allRoundsUserBets.mul(allRoundsLosersTotal)
.div(allRoundsWinnersTotal).add(allRoundsUserBets);
uint arbitrationRewardAmt = voteRoundsUserBets.mul(arbitrationReward)
.div(voteRoundsWinnersTotal);
winningAmt = winningAmt.add(arbitrationRewardAmt);
return winningAmt;
// Otherwise calculate winnings for a non-Invalid result
return calculateNormalWinnings(player);
}

function version() public pure returns (uint16) {
Expand Down Expand Up @@ -411,12 +365,10 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
require(value > 0, "Bet amount should be > 0");

// Update balances
_eventRounds[0].balances[resultIndex].total =
_eventRounds[0].balances[resultIndex].total.add(value);
_eventRounds[0].balances[resultIndex].bets[from] =
_eventRounds[0].balances[resultIndex].bets[from].add(value);
_resultTotals[resultIndex] = _resultTotals[resultIndex].add(value);
_totalBets = _totalBets.add(value);
_betRoundTotals[resultIndex] = _betRoundTotals[resultIndex].add(value);
_betRoundUserTotals[from][resultIndex] =
_betRoundUserTotals[from][resultIndex].add(value);

// Emit events
emit BetPlaced(address(this), from, resultIndex, value, _currentRound);
Expand Down Expand Up @@ -454,12 +406,10 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
_currentRound = _currentRound + 1;

// Update balances
_eventRounds[1].balances[resultIndex].total =
_eventRounds[1].balances[resultIndex].total.add(value);
_eventRounds[1].balances[resultIndex].bets[from] =
_eventRounds[1].balances[resultIndex].bets[from].add(value);
_resultTotals[resultIndex] = _resultTotals[resultIndex].add(value);
_totalBets = _totalBets.add(value);
_voteRoundsTotals[resultIndex] = _voteRoundsTotals[resultIndex].add(value);
_voteRoundsUserTotals[from][resultIndex] =
_voteRoundsUserTotals[from][resultIndex].add(value);

// Init DecentralizedOracle round
uint nextThreshold = getNextThreshold(_eventRounds[0].consensusThreshold);
Expand Down Expand Up @@ -495,33 +445,48 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
"Cannot vote on the last result index");
require(value > 0, "Vote amount should be > 0");

// Calculate diff if over threshold
uint adjustedValue = value;
uint refund;
if (_currentVotingRoundTotals[resultIndex].add(value) >
_eventRounds[_currentRound].consensusThreshold) {
adjustedValue = _eventRounds[_currentRound].consensusThreshold
.sub(_currentVotingRoundTotals[resultIndex]);
refund = _currentVotingRoundTotals[resultIndex]
.add(value)
.sub(_eventRounds[_currentRound].consensusThreshold);
}

// Update balances
_eventRounds[_currentRound].balances[resultIndex].total =
_eventRounds[_currentRound].balances[resultIndex].total.add(value);
_eventRounds[_currentRound].balances[resultIndex].bets[from] =
_eventRounds[_currentRound].balances[resultIndex].bets[from].add(value);
_resultTotals[resultIndex] = _resultTotals[resultIndex].add(value);
_totalBets = _totalBets.add(value);
_totalBets = _totalBets.add(adjustedValue);
_voteRoundsTotals[resultIndex] =
_voteRoundsTotals[resultIndex].add(adjustedValue);
_voteRoundsUserTotals[from][resultIndex] =
_voteRoundsUserTotals[from][resultIndex].add(adjustedValue);
_currentVotingRoundTotals[resultIndex] =
_currentVotingRoundTotals[resultIndex].add(adjustedValue);

// Emit events
emit VotePlaced(address(this), from, resultIndex, value, _currentRound);
emit VotePlaced(address(this), from, resultIndex, adjustedValue,
_currentRound);

// If voted over the threshold, create a new DecentralizedOracle round
uint resultVotes = _eventRounds[_currentRound].balances[resultIndex].total;
uint threshold = _eventRounds[_currentRound].consensusThreshold;
if (resultVotes >= threshold) {
voteSetResult(from, resultIndex, value);
if (_currentVotingRoundTotals[resultIndex] ==
_eventRounds[_currentRound].consensusThreshold) {
voteSetResult(from, resultIndex, adjustedValue, refund);
}
}

/// @dev Result got voted over the threshold so start a new DecentralizedOracle round.
/// @param from Address who is voted over the threshold.
/// @param resultIndex Index of result that was voted over the threshold.
/// @param value Amount of tokens used to vote.
/// @param refund Amount to refund for voting over the threshold.
function voteSetResult(
address from,
uint8 resultIndex,
uint value)
uint value,
uint refund)
private
{
// Calculate next consensus threshold
Expand All @@ -535,6 +500,9 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
_currentResultIndex = resultIndex;
_currentRound = _currentRound + 1;

// Clear current voting round totals
delete _currentVotingRoundTotals;

// Init next DecentralizedOracle round
uint arbitrationEndTime = block.timestamp.add(_arbitrationLength);
initEventRound(
Expand All @@ -543,6 +511,11 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
nextThreshold,
arbitrationEndTime);

// Refund difference over threshold
if (refund > 0) {
INRC223(_bodhiTokenAddress).transfer(from, refund);
}

// Emit events
emit VoteResultSet(address(this), from, resultIndex, value,
previousRound, nextThreshold, arbitrationEndTime);
Expand All @@ -562,4 +535,98 @@ contract MultipleResultsEvent is NRC223Receiver, Ownable {
uint increment = _thresholdPercentIncrease.mul(currentThreshold).div(100);
return currentThreshold.add(increment);
}

/// @notice Calculates the tokens returned for a non-Invalid final result.
/// @param player Address to calculate winnings for.
/// @return Amount of tokens that will be returned.
function calculateNormalWinnings(
address player)
private
view
returns (uint)
{
// Calculate user's winning bets
uint myWinningBets = _betRoundUserTotals[player][_currentResultIndex];
uint myWinningVotes = _voteRoundsUserTotals[player][_currentResultIndex];

// Calculate bet and vote rounds losing totals
uint betRoundLosersTotal;
uint voteRoundsLosersTotal;
for (uint8 i = 0; i < _numOfResults; i++) {
if (i != _currentResultIndex) {
betRoundLosersTotal = betRoundLosersTotal.add(_betRoundTotals[i]);
voteRoundsLosersTotal =
voteRoundsLosersTotal.add(_voteRoundsTotals[i]);
}
}

// Calculate user's winning amount for bet round
uint maxPercent = 100;
uint betRoundWinningAmt;
if (myWinningBets > 0 && _betRoundTotals[_currentResultIndex] > 0) {
uint percentage = maxPercent.sub(_arbitrationRewardPercentage);
betRoundWinningAmt =
betRoundLosersTotal
.mul(percentage)
.div(maxPercent)
.mul(myWinningBets)
.div(_betRoundTotals[_currentResultIndex]);
}

// Calculate user's winning amount for vote rounds
uint voteRoundsWinningAmt;
if (myWinningVotes > 0 && _voteRoundsTotals[_currentResultIndex] > 0) {
voteRoundsWinningAmt =
betRoundLosersTotal
.mul(_arbitrationRewardPercentage)
.div(maxPercent)
.add(voteRoundsLosersTotal)
.mul(myWinningVotes)
.div(_voteRoundsTotals[_currentResultIndex]);
}

return myWinningBets
.add(myWinningVotes)
.add(betRoundWinningAmt)
.add(voteRoundsWinningAmt);
}

/// @notice Calculates the tokens returned for an Invalid final result.
/// @param player Address to calculate winnings for.
/// @return Amount of tokens that will be returned.
function calculateInvalidWinnings(
address player)
private
view
returns (uint)
{
// Calculate user's winning bets
uint myWinningBets = _betRoundUserTotals[player][_currentResultIndex];
uint myWinningVotes = _voteRoundsUserTotals[player][_currentResultIndex];

// Calculate user's losing bets and vote rounds losing totals
uint myLosingBets;
uint voteRoundsLosersTotal;
for (uint8 i = 0; i < _numOfResults; i++) {
if (i != _currentResultIndex) {
myLosingBets = myLosingBets.add(_betRoundUserTotals[player][i]);
voteRoundsLosersTotal =
voteRoundsLosersTotal.add(_voteRoundsTotals[i]);
}
}

// Calculate user's winning amount for vote rounds
uint voteRoundsWinningAmt;
if (myWinningVotes > 0 && _voteRoundsTotals[_currentResultIndex] > 0) {
voteRoundsWinningAmt =
voteRoundsLosersTotal
.mul(myWinningVotes)
.div(_voteRoundsTotals[_currentResultIndex]);
}

return myWinningBets
.add(myLosingBets)
.add(myWinningVotes)
.add(voteRoundsWinningAmt);
}
}
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"devDependencies": {
"sol-assert": "^1.1.1",
"sol-time-machine": "^1.0.0"
"sol-time-machine": "^1.1.0"
},
"scripts": {
"test": "truffle test"
Expand Down
Loading

0 comments on commit 77faf0b

Please sign in to comment.