zcash-grant-system/contract/contracts/PrivateFund.sol

202 lines
7.4 KiB
Solidity

pragma solidity ^0.4.24;
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
contract PrivateFund {
using SafeMath for uint256;
struct Milestone {
uint amount;
bool openRequest;
bool paid;
}
struct BoardMember {
bool[] milestoneApprovals;
address refundAddress;
// TODO - refactor not to waste space like this;
bool exists;
}
event Transfered(address payee, uint weiAmount);
event Deposited(address indexed payee, uint256 weiAmount);
event Withdrawn(address indexed payee, uint256 weiAmount);
uint public amountRaised;
uint public raiseGoal;
uint public quorum;
address public beneficiary;
address public funder;
address[] public trustees;
bool unanimityForRefunds;
mapping(address => BoardMember) public boardMembers;
address[] public boardMembersList;
// constructor ensures that all values combined equal raiseGoal
Milestone[] public milestones;
constructor(
uint _raiseGoal,
address _beneficiary,
address[] _trustees,
uint _quorum,
address[] _boardMembers,
uint[] _milestones,
address _funder,
bool _unanimityForRefunds)
public {
require(_raiseGoal >= 1 ether, "Raise goal is smaller than 1 ether");
require(_milestones.length >= 1, "Milestones must be at least 1");
require(_quorum <= _boardMembers.length, "quorum is larger than total number of boardMembers");
require(_quorum >= 1, "quorum must be at least 1");
// TODO - require minimum milestone voting period
// ensure that cumalative milestone payouts equal raiseGoalAmount
uint milestoneTotal = 0;
for (uint i = 0; i < _milestones.length; i++) {
uint milestoneAmount = _milestones[i];
require(milestoneAmount > 0, "Milestone amount must be greater than 0");
milestoneTotal = milestoneTotal.add(milestoneAmount);
milestones.push(Milestone({
amount: milestoneAmount,
openRequest: false,
paid: false
}));
}
require(milestoneTotal == _raiseGoal, "Milestone total must equal raise goal");
boardMembersList = _boardMembers;
for (uint e = 0; e < boardMembersList.length; e++) {
address boardMemberAddress = boardMembersList[e];
boardMembers[boardMemberAddress] = BoardMember({
milestoneApprovals: new bool[](milestones.length),
refundAddress: 0,
exists: true
});
}
quorum = _quorum;
raiseGoal = _raiseGoal;
beneficiary = _beneficiary;
trustees = _trustees;
funder = _funder;
unanimityForRefunds = _unanimityForRefunds;
amountRaised = 0;
}
function contribute() public payable {
require(msg.sender == funder, "Sender must be funder");
require(amountRaised.add(msg.value) == raiseGoal, "Contribution must be exactly raise goal");
amountRaised = msg.value;
emit Deposited(msg.sender, msg.value);
}
function requestMilestonePayout (uint index) public onlyTrustee onlyRaised {
bool milestoneAlreadyPaid = milestones[index].paid;
// prevent requesting paid milestones
require(!milestoneAlreadyPaid, "Milestone already paid");
int lowestIndexPaid = -1;
for (uint i = 0; i < milestones.length; i++) {
if (milestones[i].paid) {
lowestIndexPaid = int(i);
}
}
require(index == uint(lowestIndexPaid + 1), "Milestone request must be for first unpaid milestone");
// begin grace period for contributors to vote no on milestone payout
require(!milestones[index].openRequest, "Milestone must not have already been requested");
milestones[index].openRequest = true;
}
function voteMilestonePayout(uint index, bool vote) public onlyBoardMember onlyRaised {
bool existingMilestoneVote = boardMembers[msg.sender].milestoneApprovals[index];
require(existingMilestoneVote != vote, "Vote value must be different than existing vote state");
require(milestones[index].openRequest, "Milestone voting must be open");
boardMembers[msg.sender].milestoneApprovals[index] = vote;
}
function payMilestonePayout(uint index) public onlyRaised {
bool quorumReached = isQuorumReachedForMilestonePayout(index);
bool milestoneAlreadyPaid = milestones[index].paid;
if (quorumReached && !milestoneAlreadyPaid) {
milestones[index].paid = true;
milestones[index].openRequest = false;
fundTransfer(beneficiary, milestones[index].amount);
// TODO trigger self-destruct with any un-spent funds (since funds could have been force sent at any point)
} else {
revert("required conditions were not satisfied");
}
}
function voteRefundAddress(address refundAddress) public onlyBoardMember onlyRaised {
boardMembers[msg.sender].refundAddress = refundAddress;
}
function refund(address refundAddress) public onlyBoardMember onlyRaised {
require(isConsensusReachedForRefund(refundAddress), "Unanimity is not reached to refund to given address");
selfdestruct(refundAddress);
}
function fundTransfer(address etherReceiver, uint256 amount) private {
etherReceiver.transfer(amount);
emit Transfered(etherReceiver, amount);
}
function isConsensusReachedForRefund(address refundAddress) public view onlyRaised returns (bool) {
uint yesVotes = 0;
for (uint i = 0; i < boardMembersList.length; i++) {
address boardMemberAddress = boardMembersList[i];
address boardMemberRefundAddressSelection = boardMembers[boardMemberAddress].refundAddress;
if (boardMemberRefundAddressSelection == refundAddress) {
yesVotes += 1;
}
}
if (unanimityForRefunds) {
return yesVotes == boardMembersList.length;
} else {
return yesVotes >= quorum;
}
}
function isQuorumReachedForMilestonePayout(uint milestoneIndex) public view onlyRaised returns (bool) {
uint yesVotes = 0;
for (uint i = 0; i < boardMembersList.length; i++) {
address boardMemberAddress = boardMembersList[i];
bool boardMemberVote = boardMembers[boardMemberAddress].milestoneApprovals[milestoneIndex];
if (boardMemberVote) {
yesVotes += 1;
}
}
return yesVotes >= quorum;
}
function isCallerTrustee() public view returns (bool) {
for (uint i = 0; i < trustees.length; i++) {
if (msg.sender == trustees[i]) {
return true;
}
}
return false;
}
function getBoardMemberMilestoneVote(address boardMemberAddress, uint milestoneIndex) public view returns (bool) {
return boardMembers[boardMemberAddress].milestoneApprovals[milestoneIndex];
}
modifier onlyRaised() {
require(raiseGoal == amountRaised, "Proposal is not funded");
_;
}
modifier onlyBoardMember() {
require(boardMembers[msg.sender].exists, "Caller is not a board member");
_;
}
modifier onlyTrustee() {
require(isCallerTrustee(), "Caller is not a trustee");
_;
}
}