solidity/test/compilationTests/gnosis/Oracles/UltimateOracle.sol
2020-07-07 12:16:18 +02:00

197 lines
6.7 KiB
Solidity

pragma solidity >=0.0;
import "../Oracles/Oracle.sol";
import "../Tokens/Token.sol";
import "../Utils/Math.sol";
/// @title Ultimate oracle contract - Allows to swap oracle result for ultimate oracle result
/// @author Stefan George - <stefan@gnosis.pm>
contract UltimateOracle is Oracle {
using Math for *;
/*
* Events
*/
event ForwardedOracleOutcomeAssignment(int outcome);
event OutcomeChallenge(address indexed sender, int outcome);
event OutcomeVote(address indexed sender, int outcome, uint amount);
event Withdrawal(address indexed sender, uint amount);
/*
* Storage
*/
Oracle public forwardedOracle;
Token public collateralToken;
uint8 public spreadMultiplier;
uint public challengePeriod;
uint public challengeAmount;
uint public frontRunnerPeriod;
int public forwardedOutcome;
uint public forwardedOutcomeSetTimestamp;
int public frontRunner;
uint public frontRunnerSetTimestamp;
uint public totalAmount;
mapping (int => uint) public totalOutcomeAmounts;
mapping (address => mapping (int => uint)) public outcomeAmounts;
/*
* Public functions
*/
/// @dev Constructor sets ultimate oracle properties
/// @param _forwardedOracle Oracle address
/// @param _collateralToken Collateral token address
/// @param _spreadMultiplier Defines the spread as a multiple of the money bet on other outcomes
/// @param _challengePeriod Time to challenge oracle outcome
/// @param _challengeAmount Amount to challenge the outcome
/// @param _frontRunnerPeriod Time to overbid the front-runner
constructor(
Oracle _forwardedOracle,
Token _collateralToken,
uint8 _spreadMultiplier,
uint _challengePeriod,
uint _challengeAmount,
uint _frontRunnerPeriod
)
{
// Validate inputs
require( address(_forwardedOracle) != address(0)
&& address(_collateralToken) != address(0)
&& _spreadMultiplier >= 2
&& _challengePeriod > 0
&& _challengeAmount > 0
&& _frontRunnerPeriod > 0);
forwardedOracle = _forwardedOracle;
collateralToken = _collateralToken;
spreadMultiplier = _spreadMultiplier;
challengePeriod = _challengePeriod;
challengeAmount = _challengeAmount;
frontRunnerPeriod = _frontRunnerPeriod;
}
/// @dev Allows to set oracle outcome
function setForwardedOutcome()
public
{
// There was no challenge and the outcome was not set yet in the ultimate oracle but in the forwarded oracle
require( !isChallenged()
&& forwardedOutcomeSetTimestamp == 0
&& forwardedOracle.isOutcomeSet());
forwardedOutcome = forwardedOracle.getOutcome();
forwardedOutcomeSetTimestamp = block.timestamp;
emit ForwardedOracleOutcomeAssignment(forwardedOutcome);
}
/// @dev Allows to challenge the oracle outcome
/// @param _outcome Outcome to bid on
function challengeOutcome(int _outcome)
public
{
// There was no challenge yet or the challenge period expired
require( !isChallenged()
&& !isChallengePeriodOver()
&& collateralToken.transferFrom(msg.sender, address(this), challengeAmount));
outcomeAmounts[msg.sender][_outcome] = challengeAmount;
totalOutcomeAmounts[_outcome] = challengeAmount;
totalAmount = challengeAmount;
frontRunner = _outcome;
frontRunnerSetTimestamp = block.timestamp;
emit OutcomeChallenge(msg.sender, _outcome);
}
/// @dev Allows to challenge the oracle outcome
/// @param _outcome Outcome to bid on
/// @param amount Amount to bid
function voteForOutcome(int _outcome, uint amount)
public
{
uint maxAmount = (totalAmount - totalOutcomeAmounts[_outcome]).mul(spreadMultiplier);
if (amount > maxAmount)
amount = maxAmount;
// Outcome is challenged and front runner period is not over yet and tokens can be transferred
require( isChallenged()
&& !isFrontRunnerPeriodOver()
&& collateralToken.transferFrom(msg.sender, address(this), amount));
outcomeAmounts[msg.sender][_outcome] = outcomeAmounts[msg.sender][_outcome].add(amount);
totalOutcomeAmounts[_outcome] = totalOutcomeAmounts[_outcome].add(amount);
totalAmount = totalAmount.add(amount);
if (_outcome != frontRunner && totalOutcomeAmounts[_outcome] > totalOutcomeAmounts[frontRunner])
{
frontRunner = _outcome;
frontRunnerSetTimestamp = block.timestamp;
}
emit OutcomeVote(msg.sender, _outcome, amount);
}
/// @dev Withdraws winnings for user
/// @return amount Winnings
function withdraw()
public
returns (uint amount)
{
// Outcome was challenged and ultimate outcome decided
require(isFrontRunnerPeriodOver());
amount = totalAmount.mul(outcomeAmounts[msg.sender][frontRunner]) / totalOutcomeAmounts[frontRunner];
outcomeAmounts[msg.sender][frontRunner] = 0;
// Transfer earnings to contributor
require(collateralToken.transfer(msg.sender, amount));
emit Withdrawal(msg.sender, amount);
}
/// @dev Checks if time to challenge the outcome is over
/// @return Is challenge period over?
function isChallengePeriodOver()
public
view
returns (bool)
{
return forwardedOutcomeSetTimestamp != 0 && (block.timestamp).sub(forwardedOutcomeSetTimestamp) > challengePeriod;
}
/// @dev Checks if time to overbid the front runner is over
/// @return Is front runner period over?
function isFrontRunnerPeriodOver()
public
view
returns (bool)
{
return frontRunnerSetTimestamp != 0 && (block.timestamp).sub(frontRunnerSetTimestamp) > frontRunnerPeriod;
}
/// @dev Checks if outcome was challenged
/// @return Is challenged?
function isChallenged()
public
view
returns (bool)
{
return frontRunnerSetTimestamp != 0;
}
/// @dev Returns if winning outcome is set
/// @return Is outcome set?
function isOutcomeSet()
public
override
view
returns (bool)
{
return isChallengePeriodOver() && !isChallenged()
|| isFrontRunnerPeriodOver();
}
/// @dev Returns winning outcome
/// @return Outcome
function getOutcome()
public
override
view
returns (int)
{
if (isFrontRunnerPeriodOver())
return frontRunner;
return forwardedOutcome;
}
}