// File: CharityChallenge.sol



pragma solidity ^0.5.2;

import "./IRealityCheck.sol";
import "./SafeMath.sol";

contract CharityChallenge {
    using SafeMath for uint256;
    using SafeMath for uint8;

    event Received(address indexed sender, uint256 value);

    event Donated(address indexed npo, uint256 value);

    event Fee(address indexed maker, uint256 value);

    event Claimed(address indexed claimer, uint256 value);

    event SafetyHatchClaimed(address indexed claimer, uint256 value);

    string public constant VERSION = "0.4.0";

    address payable public contractOwner;

    // key is npo address, value is ratio
    mapping(address => uint8) public npoRatios;

    uint8 sumRatio;

    address payable[] public npoAddresses;

    address public marketAddress;

    bool public unlockOnNo;

    IRealityCheck realityCheck;

    string public question;

    address public arbitrator;

    uint256 public timeout;

    bytes32 public questionId;

    uint256 public challengeEndTime;

    // For a fee of 10.5%, pass 1050
    uint256 public makerFee;

    uint256 public challengeSafetyHatchTime1;

    uint256 public challengeSafetyHatchTime2;

    // Valid outcomes are 'YES', 'NO' and 'INVALID'
    bool public isEventFinalized;

    // hasChallengeAccomplished will be set to true if we got the expected
    // result that allow to unlock the funds.
    bool public hasChallengeAccomplished;

    bool private safetyHatchClaimSucceeded;

    mapping(address => uint256) public donorBalances;

    uint256 public donorCount;

    // We use a divider of 10000 instead of 100 to have more granularity for
    // the maker fee
    uint256 constant feeDivider = 10000;

    bool private mReentrancyLock = false;
    modifier nonReentrant() {
        require(!mReentrancyLock);
        mReentrancyLock = true;
        _;
        mReentrancyLock = false;
    }

    constructor(
        address payable _contractOwner,
        address payable[] memory _npoAddresses,
        uint8[] memory _ratios,
        address _marketAddress,
        string memory _question,
        address _arbitrator,
        uint256 _timeout,
        uint256 _challengeEndTime,
        uint256 _makerFee,
        bool _unlockOnNo
    ) public
    {
        require(_npoAddresses.length == _ratios.length);
        require(makerFee < feeDivider);
        uint length = _npoAddresses.length;
        for (uint i = 0; i < length; i++) {
            address payable npo = _npoAddresses[i];
            npoAddresses.push(npo);
            require(_ratios[i] > 0, "Ratio must be a positive number");
            npoRatios[npo] = _ratios[i];
            sumRatio += _ratios[i];
        }
        contractOwner = _contractOwner;
        marketAddress = _marketAddress;
        realityCheck = IRealityCheck(_marketAddress);
        question = _question;
        arbitrator = _arbitrator;
        timeout = _timeout;
        challengeEndTime = _challengeEndTime;
        makerFee = _makerFee;
        questionId = realityCheck.askQuestion(0, question, arbitrator, uint32(timeout), uint32(challengeEndTime), 0);
        unlockOnNo = _unlockOnNo;
        challengeSafetyHatchTime1 = challengeEndTime + 26 weeks;
        challengeSafetyHatchTime2 = challengeSafetyHatchTime1 + 52 weeks;
        isEventFinalized = false;
        hasChallengeAccomplished = false;
    }

    function() external payable {
        require(now <= challengeEndTime);
        require(msg.value > 0);
        if (donorBalances[msg.sender] == 0 && msg.value > 0) {
            donorCount++;
        }
        donorBalances[msg.sender] += msg.value;
        emit Received(msg.sender, msg.value);
    }

    function balanceOf(address _donorAddress) public view returns (uint256) {
        if (safetyHatchClaimSucceeded) {
            return 0;
        }
        return donorBalances[_donorAddress];
    }

    function finalize() nonReentrant external {
        require(now > challengeEndTime);
        require(now <= challengeSafetyHatchTime1);
        require(!isEventFinalized);
        doFinalize();
    }

    function doFinalize() private {
        bool hasError;
        (hasChallengeAccomplished, hasError) = checkRealitio();
        if (!hasError) {
            isEventFinalized = true;
            if (hasChallengeAccomplished) {
                uint length = npoAddresses.length;
                if (makerFee > 0) {
                    uint256 amount = address(this).balance.mul(makerFee).div(feeDivider);
                    contractOwner.transfer(amount);
                    emit Fee(contractOwner, amount);
                }
                for (uint i = 0; i < length - 1; i++) {
                    address payable npo = npoAddresses[i];
                    uint8 ratio = npoRatios[npo];
                    uint256 amount = address(this).balance.mul(ratio).div(sumRatio);
                    npo.transfer(amount);
                    emit Donated(npo, amount);
                }
                // Don't want to keep any amount in the contract
                uint256 amount = address(this).balance;
                address payable npo = npoAddresses[length - 1];
                npo.transfer(amount);
                emit Donated(npo, amount);
            }
        }
    }

    function getExpectedDonationAmount(address payable _npo) view external returns (uint256) {
        require(npoRatios[_npo] > 0);
        uint256 amountForNPO = address(this).balance.sub(address(this).balance.mul(makerFee).div(feeDivider));
        uint8 ratio = npoRatios[_npo];
        return amountForNPO.mul(ratio).div(sumRatio);
    }

    function claim() nonReentrant external {
        require(now > challengeEndTime);
        require(isEventFinalized || now > challengeSafetyHatchTime1);
        require(!hasChallengeAccomplished || now > challengeSafetyHatchTime1);
        require(balanceOf(msg.sender) > 0);

        uint256 claimedAmount = balanceOf(msg.sender);
        donorBalances[msg.sender] = 0;
        msg.sender.transfer(claimedAmount);
        emit Claimed(msg.sender, claimedAmount);
    }

    function safetyHatchClaim() external {
        require(now > challengeSafetyHatchTime2);
        require(msg.sender == contractOwner);

        uint totalContractBalance = address(this).balance;
        safetyHatchClaimSucceeded = true;
        contractOwner.transfer(address(this).balance);
        emit SafetyHatchClaimed(contractOwner, totalContractBalance);
    }

    function checkRealitio() private view returns (bool happened, bool errored) {
        if (realityCheck.isFinalized(questionId)) {
            bytes32 answer = realityCheck.getFinalAnswer(questionId);
            if (answer == 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) {
                // Treat 'invalid' outcome as 'no'
                // because 'invalid' is one of the valid outcomes
                return (false, false);
            } else {
                if (unlockOnNo) {
                    return (answer == 0x0000000000000000000000000000000000000000000000000000000000000000, false);
                }
                return (answer == 0x0000000000000000000000000000000000000000000000000000000000000001, false);
            }
        } else {
            return (false, true);
        }
    }
}

// File: IRealityCheck.sol



pragma solidity ^0.5.2;

// RealityCheck API used to interract with realit.io, we only need to describe the
// functions we'll be using.
// cf https://raw.githubusercontent.com/realitio/realitio-dapp/master/truffle/contracts/RealityCheck.sol
interface IRealityCheck {
    function askQuestion(
        uint256 template_id, string calldata question,
        address arbitrator, uint32 timeout, uint32 opening_ts, uint256 nonce) external returns (bytes32);
    function isFinalized(bytes32 question_id) external view returns (bool);
    function getFinalAnswer(bytes32 question_id) external view returns (bytes32);
    function getOpeningTS(bytes32 question_id) external view returns(uint32);
}

// File: SafeMath.sol



pragma solidity ^0.5.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, "SafeMath: division by zero");
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0, "SafeMath: modulo by zero");
        return a % b;
    }
}