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 Failed();
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.1";
address payable public contractOwner;
// key is npo address, value is ratio
mapping(address => uint8) public npoRatios;
uint8 sumRatio;
address payable[] public npoAddresses;
uint8 public npoLength;
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;
uint256 public contributedAmount;
// 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);
npoLength = uint8(_npoAddresses.length);
for (uint8 i = 0; i < npoLength; 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;
contributedAmount += 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);
} else {
emit Failed();
}
}
}
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() public 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);
}
}
}
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);
}
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;
}
}
{
"compilationTarget": {
"CharityChallenge.sol": "CharityChallenge"
},
"evmVersion": "byzantium",
"libraries": {},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[],"name":"safetyHatchClaim","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"npoAddresses","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"hasChallengeAccomplished","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"question","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"finalize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"claim","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"npoLength","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"npoRatios","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_npo","type":"address"}],"name":"getExpectedDonationAmount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"arbitrator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_donorAddress","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"timeout","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"donorBalances","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"challengeSafetyHatchTime1","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"marketAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"checkRealitio","outputs":[{"name":"happened","type":"bool"},{"name":"errored","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"questionId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isEventFinalized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"unlockOnNo","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"challengeEndTime","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"donorCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"contractOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"challengeSafetyHatchTime2","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"contributedAmount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"makerFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"VERSION","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_contractOwner","type":"address"},{"name":"_npoAddresses","type":"address[]"},{"name":"_ratios","type":"uint8[]"},{"name":"_marketAddress","type":"address"},{"name":"_question","type":"string"},{"name":"_arbitrator","type":"address"},{"name":"_timeout","type":"uint256"},{"name":"_challengeEndTime","type":"uint256"},{"name":"_makerFee","type":"uint256"},{"name":"_unlockOnNo","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"npo","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Donated","type":"event"},{"anonymous":false,"inputs":[],"name":"Failed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"maker","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Fee","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"claimer","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"claimer","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"SafetyHatchClaimed","type":"event"}]