134 lines
4.5 KiB
Solidity
134 lines
4.5 KiB
Solidity
pragma solidity ^0.4.24;
|
|
|
|
import "@aragon/os/contracts/apps/AragonApp.sol";
|
|
import "@aragon/os/contracts/kernel/IKernel.sol";
|
|
|
|
interface IContributor {
|
|
function getContributorAddressById(uint32 contributorId) public view returns (address);
|
|
function getContributorIdByAddress(address contributorAccount) public view returns (uint32);
|
|
function exists(uint32 contributorId) public view returns (bool);
|
|
}
|
|
|
|
interface IContribution {
|
|
function add(uint32 amount, uint32 contributorId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public;
|
|
}
|
|
|
|
contract Proposal is AragonApp {
|
|
|
|
bytes32 public constant ADD_PROPOSAL_ROLE = keccak256("ADD_PROPOSAL_ROLE");
|
|
bytes32 public constant VOTE_PROPOSAL_ROLE = keccak256("VOTE_PROPOSAL_ROLE");
|
|
|
|
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
|
|
// ensure alphabetic order
|
|
enum Apps { Contribution, Contributor, Proposal, Token }
|
|
bytes32[4] public appIds;
|
|
|
|
struct Proposal {
|
|
address creatorAccount;
|
|
uint32 contributorId;
|
|
uint16 votesCount;
|
|
uint16 votesNeeded;
|
|
uint32 amount;
|
|
bool executed;
|
|
bytes32 hashDigest;
|
|
uint8 hashFunction;
|
|
uint8 hashSize;
|
|
uint32[] voterIds;
|
|
mapping (uint32 => bool) votes;
|
|
bool exists;
|
|
}
|
|
|
|
mapping(uint32 => Proposal) public proposals;
|
|
uint32 public proposalsCount;
|
|
|
|
event ProposalCreated(uint32 id, address creatorAccount, uint32 contributorId, uint32 amount);
|
|
|
|
event ProposalVoted(uint32 id, uint32 voterId, uint16 totalVotes);
|
|
event ProposalExecuted(uint32 id, uint32 contributorId, uint32 amount);
|
|
|
|
function initialize(bytes32[4] _appIds) public onlyInit {
|
|
appIds = _appIds;
|
|
initialized();
|
|
}
|
|
|
|
function getContributorContract() public view returns (address) {
|
|
return IKernel(kernel()).getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[uint8(Apps.Contributor)]);
|
|
}
|
|
|
|
function getContributionContract() public view returns (address) {
|
|
return IKernel(kernel()).getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[uint8(Apps.Contribution)]);
|
|
}
|
|
|
|
function addProposal(uint32 contributorId, uint32 amount, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(ADD_PROPOSAL_ROLE) {
|
|
require(IContributor(getContributorContract()).exists(contributorId), 'CONTRIBUTOR_NOT_FOUND');
|
|
|
|
uint32 proposalId = proposalsCount + 1;
|
|
uint16 _votesNeeded = 1; //contributorsContract().coreContributorsCount() / 100 * 75;
|
|
|
|
Proposal storage p = proposals[proposalId];
|
|
p.creatorAccount = msg.sender;
|
|
p.contributorId = contributorId;
|
|
p.amount = amount;
|
|
p.hashDigest = hashDigest;
|
|
p.hashFunction = hashFunction;
|
|
p.hashSize = hashSize;
|
|
p.votesCount = 0;
|
|
p.votesNeeded = _votesNeeded;
|
|
p.exists = true;
|
|
|
|
proposalsCount++;
|
|
emit ProposalCreated(proposalId, msg.sender, p.contributorId, p.amount);
|
|
}
|
|
|
|
function getProposal(uint32 proposalId) public view returns (uint32 id, address creatorAccount, uint32 contributorId, uint16 votesCount, uint16 votesNeeded, uint32 amount, bool executed, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint32[] voterIds, bool exists) {
|
|
id = proposalId;
|
|
Proposal storage p = proposals[id];
|
|
return (
|
|
id,
|
|
p.creatorAccount,
|
|
p.contributorId,
|
|
p.votesCount,
|
|
p.votesNeeded,
|
|
p.amount,
|
|
p.executed,
|
|
p.hashDigest,
|
|
p.hashFunction,
|
|
p.hashSize,
|
|
p.voterIds,
|
|
p.exists
|
|
);
|
|
}
|
|
|
|
function vote(uint32 proposalId) public isInitialized auth(VOTE_PROPOSAL_ROLE) {
|
|
Proposal storage p = proposals[proposalId];
|
|
require(!p.executed, 'ALREADY_EXECUTED');
|
|
uint32 voterId = IContributor(getContributorContract()).getContributorIdByAddress(msg.sender);
|
|
require(p.votes[voterId] != true, 'ALREADY_VOTED');
|
|
p.voterIds.push(voterId);
|
|
p.votes[voterId] = true;
|
|
|
|
p.votesCount++;
|
|
if (p.votesCount >= p.votesNeeded) {
|
|
executeProposal(proposalId);
|
|
}
|
|
emit ProposalVoted(proposalId, voterId, p.votesCount);
|
|
}
|
|
|
|
function batchVote(uint32[] _proposalIds) public isInitialized auth(VOTE_PROPOSAL_ROLE) {
|
|
for (uint32 i = 0; i < _proposalIds.length; i++) {
|
|
vote(_proposalIds[i]);
|
|
}
|
|
}
|
|
|
|
function executeProposal(uint32 proposalId) private {
|
|
Proposal storage p = proposals[proposalId];
|
|
require(!p.executed, 'ALREADY_EXECUTED');
|
|
require(p.votesCount >= p.votesNeeded, 'MISSING_VOTES');
|
|
|
|
p.executed = true;
|
|
IContribution(getContributionContract()).add(p.amount, p.contributorId, p.hashDigest, p.hashFunction, p.hashSize);
|
|
emit ProposalExecuted(proposalId, p.contributorId, p.amount);
|
|
}
|
|
|
|
}
|