1pragma solidity >=0.0;
2import "../Oracles/Oracle.sol";
3import "../Events/EventFactory.sol";
4import "../Markets/MarketFactory.sol";
5
6
7/// @title Futarchy oracle contract - Allows to create an oracle based on market behaviour
8/// @author Stefan George - <stefan@gnosis.pm>
9contract FutarchyOracle is Oracle {
10    using Math for *;
11
12    /*
13     *  Events
14     */
15    event FutarchyFunding(uint funding);
16    event FutarchyClosing();
17    event OutcomeAssignment(uint winningMarketIndex);
18
19    /*
20     *  Constants
21     */
22    uint8 public constant LONG = 1;
23
24    /*
25     *  Storage
26     */
27    address creator;
28    Market[] public markets;
29    CategoricalEvent public categoricalEvent;
30    uint public deadline;
31    uint public winningMarketIndex;
32    bool public isSet;
33
34    /*
35     *  Modifiers
36     */
37    modifier isCreator () {
38        // Only creator is allowed to proceed
39        require(msg.sender == creator);
40        _;
41    }
42
43    /*
44     *  Public functions
45     */
46    /// @dev Constructor creates events and markets for futarchy oracle
47    /// @param _creator Oracle creator
48    /// @param eventFactory Event factory contract
49    /// @param collateralToken Tokens used as collateral in exchange for outcome tokens
50    /// @param oracle Oracle contract used to resolve the event
51    /// @param outcomeCount Number of event outcomes
52    /// @param lowerBound Lower bound for event outcome
53    /// @param upperBound Lower bound for event outcome
54    /// @param marketFactory Market factory contract
55    /// @param marketMaker Market maker contract
56    /// @param fee Market fee
57    /// @param _deadline Decision deadline
58    constructor(
59        address _creator,
60        EventFactory eventFactory,
61        Token collateralToken,
62        Oracle oracle,
63        uint8 outcomeCount,
64        int lowerBound,
65        int upperBound,
66        MarketFactory marketFactory,
67        MarketMaker marketMaker,
68        uint24 fee,
69        uint _deadline
70    )
71    {
72        // Deadline is in the future
73        require(_deadline > block.timestamp);
74        // Create decision event
75        categoricalEvent = eventFactory.createCategoricalEvent(collateralToken, this, outcomeCount);
76        // Create outcome events
77        for (uint8 i = 0; i < categoricalEvent.getOutcomeCount(); i++) {
78            ScalarEvent scalarEvent = eventFactory.createScalarEvent(
79                categoricalEvent.outcomeTokens(i),
80                oracle,
81                lowerBound,
82                upperBound
83            );
84            markets.push(marketFactory.createMarket(scalarEvent, marketMaker, fee));
85        }
86        creator = _creator;
87        deadline = _deadline;
88    }
89
90    /// @dev Funds all markets with equal amount of funding
91    /// @param funding Amount of funding
92    function fund(uint funding)
93        public
94        isCreator
95    {
96        // Buy all outcomes
97        require(   categoricalEvent.collateralToken().transferFrom(creator, address(this), funding)
98                && categoricalEvent.collateralToken().approve(address(categoricalEvent), funding));
99        categoricalEvent.buyAllOutcomes(funding);
100        // Fund each market with outcome tokens from categorical event
101        for (uint8 i = 0; i < markets.length; i++) {
102            Market market = markets[i];
103            // Approve funding for market
104            require(market.eventContract().collateralToken().approve(address(market), funding));
105            market.fund(funding);
106        }
107        emit FutarchyFunding(funding);
108    }
109
110    /// @dev Closes market for winning outcome and redeems winnings and sends all collateral tokens to creator
111    function close()
112        public
113        isCreator
114    {
115        // Winning outcome has to be set
116        Market market = markets[uint(getOutcome())];
117        require(categoricalEvent.isOutcomeSet() && market.eventContract().isOutcomeSet());
118        // Close market and transfer all outcome tokens from winning outcome to this contract
119        market.close();
120        market.eventContract().redeemWinnings();
121        market.withdrawFees();
122        // Redeem collateral token for winning outcome tokens and transfer collateral tokens to creator
123        categoricalEvent.redeemWinnings();
124        require(categoricalEvent.collateralToken().transfer(creator, categoricalEvent.collateralToken().balanceOf(address(this))));
125        emit FutarchyClosing();
126    }
127
128    /// @dev Allows to set the oracle outcome based on the market with largest long position
129    function setOutcome()
130        public
131    {
132        // Outcome is not set yet and deadline has passed
133        require(!isSet && deadline <= block.timestamp);
134        // Find market with highest marginal price for long outcome tokens
135        uint highestMarginalPrice = markets[0].marketMaker().calcMarginalPrice(markets[0], LONG);
136        uint highestIndex = 0;
137        for (uint8 i = 1; i < markets.length; i++) {
138            uint marginalPrice = markets[i].marketMaker().calcMarginalPrice(markets[i], LONG);
139            if (marginalPrice > highestMarginalPrice) {
140                highestMarginalPrice = marginalPrice;
141                highestIndex = i;
142            }
143        }
144        winningMarketIndex = highestIndex;
145        isSet = true;
146        emit OutcomeAssignment(winningMarketIndex);
147    }
148
149    /// @dev Returns if winning outcome is set
150    /// @return Is outcome set?
151    function isOutcomeSet()
152        public
153        override
154        view
155        returns (bool)
156    {
157        return isSet;
158    }
159
160    /// @dev Returns winning outcome
161    /// @return Outcome
162    function getOutcome()
163        public
164        override
165        view
166        returns (int)
167    {
168        return int(winningMarketIndex);
169    }
170}
171