1pragma solidity >=0.0; 2import "../Oracles/Oracle.sol"; 3import "../Tokens/Token.sol"; 4import "../Utils/Math.sol"; 5 6 7/// @title Ultimate oracle contract - Allows to swap oracle result for ultimate oracle result 8/// @author Stefan George - <stefan@gnosis.pm> 9contract UltimateOracle is Oracle { 10 using Math for *; 11 12 /* 13 * Events 14 */ 15 event ForwardedOracleOutcomeAssignment(int outcome); 16 event OutcomeChallenge(address indexed sender, int outcome); 17 event OutcomeVote(address indexed sender, int outcome, uint amount); 18 event Withdrawal(address indexed sender, uint amount); 19 20 /* 21 * Storage 22 */ 23 Oracle public forwardedOracle; 24 Token public collateralToken; 25 uint8 public spreadMultiplier; 26 uint public challengePeriod; 27 uint public challengeAmount; 28 uint public frontRunnerPeriod; 29 30 int public forwardedOutcome; 31 uint public forwardedOutcomeSetTimestamp; 32 int public frontRunner; 33 uint public frontRunnerSetTimestamp; 34 35 uint public totalAmount; 36 mapping (int => uint) public totalOutcomeAmounts; 37 mapping (address => mapping (int => uint)) public outcomeAmounts; 38 39 /* 40 * Public functions 41 */ 42 /// @dev Constructor sets ultimate oracle properties 43 /// @param _forwardedOracle Oracle address 44 /// @param _collateralToken Collateral token address 45 /// @param _spreadMultiplier Defines the spread as a multiple of the money bet on other outcomes 46 /// @param _challengePeriod Time to challenge oracle outcome 47 /// @param _challengeAmount Amount to challenge the outcome 48 /// @param _frontRunnerPeriod Time to overbid the front-runner 49 constructor( 50 Oracle _forwardedOracle, 51 Token _collateralToken, 52 uint8 _spreadMultiplier, 53 uint _challengePeriod, 54 uint _challengeAmount, 55 uint _frontRunnerPeriod 56 ) 57 { 58 // Validate inputs 59 require( address(_forwardedOracle) != address(0) 60 && address(_collateralToken) != address(0) 61 && _spreadMultiplier >= 2 62 && _challengePeriod > 0 63 && _challengeAmount > 0 64 && _frontRunnerPeriod > 0); 65 forwardedOracle = _forwardedOracle; 66 collateralToken = _collateralToken; 67 spreadMultiplier = _spreadMultiplier; 68 challengePeriod = _challengePeriod; 69 challengeAmount = _challengeAmount; 70 frontRunnerPeriod = _frontRunnerPeriod; 71 } 72 73 /// @dev Allows to set oracle outcome 74 function setForwardedOutcome() 75 public 76 { 77 // There was no challenge and the outcome was not set yet in the ultimate oracle but in the forwarded oracle 78 require( !isChallenged() 79 && forwardedOutcomeSetTimestamp == 0 80 && forwardedOracle.isOutcomeSet()); 81 forwardedOutcome = forwardedOracle.getOutcome(); 82 forwardedOutcomeSetTimestamp = block.timestamp; 83 emit ForwardedOracleOutcomeAssignment(forwardedOutcome); 84 } 85 86 /// @dev Allows to challenge the oracle outcome 87 /// @param _outcome Outcome to bid on 88 function challengeOutcome(int _outcome) 89 public 90 { 91 // There was no challenge yet or the challenge period expired 92 require( !isChallenged() 93 && !isChallengePeriodOver() 94 && collateralToken.transferFrom(msg.sender, address(this), challengeAmount)); 95 outcomeAmounts[msg.sender][_outcome] = challengeAmount; 96 totalOutcomeAmounts[_outcome] = challengeAmount; 97 totalAmount = challengeAmount; 98 frontRunner = _outcome; 99 frontRunnerSetTimestamp = block.timestamp; 100 emit OutcomeChallenge(msg.sender, _outcome); 101 } 102 103 /// @dev Allows to challenge the oracle outcome 104 /// @param _outcome Outcome to bid on 105 /// @param amount Amount to bid 106 function voteForOutcome(int _outcome, uint amount) 107 public 108 { 109 uint maxAmount = (totalAmount - totalOutcomeAmounts[_outcome]).mul(spreadMultiplier); 110 if (amount > maxAmount) 111 amount = maxAmount; 112 // Outcome is challenged and front runner period is not over yet and tokens can be transferred 113 require( isChallenged() 114 && !isFrontRunnerPeriodOver() 115 && collateralToken.transferFrom(msg.sender, address(this), amount)); 116 outcomeAmounts[msg.sender][_outcome] = outcomeAmounts[msg.sender][_outcome].add(amount); 117 totalOutcomeAmounts[_outcome] = totalOutcomeAmounts[_outcome].add(amount); 118 totalAmount = totalAmount.add(amount); 119 if (_outcome != frontRunner && totalOutcomeAmounts[_outcome] > totalOutcomeAmounts[frontRunner]) 120 { 121 frontRunner = _outcome; 122 frontRunnerSetTimestamp = block.timestamp; 123 } 124 emit OutcomeVote(msg.sender, _outcome, amount); 125 } 126 127 /// @dev Withdraws winnings for user 128 /// @return amount Winnings 129 function withdraw() 130 public 131 returns (uint amount) 132 { 133 // Outcome was challenged and ultimate outcome decided 134 require(isFrontRunnerPeriodOver()); 135 amount = totalAmount.mul(outcomeAmounts[msg.sender][frontRunner]) / totalOutcomeAmounts[frontRunner]; 136 outcomeAmounts[msg.sender][frontRunner] = 0; 137 // Transfer earnings to contributor 138 require(collateralToken.transfer(msg.sender, amount)); 139 emit Withdrawal(msg.sender, amount); 140 } 141 142 /// @dev Checks if time to challenge the outcome is over 143 /// @return Is challenge period over? 144 function isChallengePeriodOver() 145 public 146 view 147 returns (bool) 148 { 149 return forwardedOutcomeSetTimestamp != 0 && (block.timestamp).sub(forwardedOutcomeSetTimestamp) > challengePeriod; 150 } 151 152 /// @dev Checks if time to overbid the front runner is over 153 /// @return Is front runner period over? 154 function isFrontRunnerPeriodOver() 155 public 156 view 157 returns (bool) 158 { 159 return frontRunnerSetTimestamp != 0 && (block.timestamp).sub(frontRunnerSetTimestamp) > frontRunnerPeriod; 160 } 161 162 /// @dev Checks if outcome was challenged 163 /// @return Is challenged? 164 function isChallenged() 165 public 166 view 167 returns (bool) 168 { 169 return frontRunnerSetTimestamp != 0; 170 } 171 172 /// @dev Returns if winning outcome is set 173 /// @return Is outcome set? 174 function isOutcomeSet() 175 public 176 override 177 view 178 returns (bool) 179 { 180 return isChallengePeriodOver() && !isChallenged() 181 || isFrontRunnerPeriodOver(); 182 } 183 184 /// @dev Returns winning outcome 185 /// @return Outcome 186 function getOutcome() 187 public 188 override 189 view 190 returns (int) 191 { 192 if (isFrontRunnerPeriodOver()) 193 return frontRunner; 194 return forwardedOutcome; 195 } 196} 197