diff --git a/contracts/Contribution.sol b/contracts/Contribution.sol index da4de63..919b37e 100644 --- a/contracts/Contribution.sol +++ b/contracts/Contribution.sol @@ -1,34 +1,49 @@ -pragma solidity ^0.4.18; +pragma solidity ^0.4.19; import "zeppelin-solidity/contracts/token/ERC721/ERC721Token.sol"; import './upgradeable/Upgradeable.sol'; // ToDo: only load interfaces import './Token.sol'; +import './Contributors.sol'; contract Contribution is Upgradeable, ERC721Token { - struct Contribution { + struct ContributionData { address contributor; uint amount; - bool issued; - uint proposalId; - string url; - uint256 claimAfterBlock; + bool claimed; + bytes32 hashDigest; + uint8 hashFunction; + uint8 hashSize; + string tokenMetadataURL; + uint claimAfterBlock; bool exists; } string internal name_; string internal symbol_; - mapping(uint256 => string) contributionURIs; - mapping(uint256 => address) contributionOwner; mapping(address => uint256[]) ownedContributions; - mapping(uint256 => Contribution) public contributions; + mapping(uint256 => ContributionData) public contributions; uint256 public contributionsCount; event ContributionAdded(uint256 id, address indexed contributor, uint256 amount); + event ContributionClaimed(uint256 id, address indexed contributor, uint256 amount); + + modifier coreOnly() { + require(contributorsContract().addressIsCore(msg.sender)); + _; + } + modifier contributorOnly() { + require(contributorsContract().addressExists(msg.sender)); + _; + } + + function contributorsContract() view public returns (Contributors) { + return Contributors(registry.getProxyFor('Contributors')); + } function tokenContract() view public returns (Token) { return Token(registry.getProxyFor('Token')); @@ -42,11 +57,6 @@ contract Contribution is Upgradeable, ERC721Token { return symbol_; } - function contributionURI(uint256 contributionId) public view returns (string) { - require(exists(contributionId)); - return contributions[contributionId].url; - } - function ownerOf(uint256 contributionId) public view returns (address) { require(exists(contributionId)); return contributions[contributionId].contributor; @@ -56,14 +66,36 @@ contract Contribution is Upgradeable, ERC721Token { return ownedContributions[contributor].length; } - function add(uint256 amount, uint256 proposalId, address contributor, uint256 blocksToWait, string url) public { + function tokenOfOwnerByIndex(address contributor, uint index) public view returns (uint) { + return ownedContributions[contributor][index]; + } + + function tokenMetadata(uint contributionId) public view returns (string) { + return contributions[contributionId].tokenMetadataURL; + } + + function getContribution(uint contributionId) public view returns (uint256 id, address contributor, uint256 amount, bool claimed, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint claimAfterBlock, bool exists) { + id = contributionId; + ContributionData storage c = contributions[id]; + return ( + id, + c.contributor, + c.amount, + c.claimed, + c.hashDigest, + c.hashFunction, + c.hashSize, + c.claimAfterBlock, + c.exists + ); + } + + function add(uint256 amount, address contributor, uint256 blocksToWait) public coreOnly { uint contributionId = contributionsCount + 1; - var c = contributions[contributionId]; + ContributionData storage c = contributions[contributionId]; c.exists = true; c.amount = amount; - c.issued = false; - c.proposalId = proposalId; - c.url = url; + c.claimed = false; c.contributor = contributor; c.claimAfterBlock = block.number + blocksToWait; @@ -71,17 +103,19 @@ contract Contribution is Upgradeable, ERC721Token { contributionOwner[contributionId] = contributor; ownedContributions[contributor].push(contributionId); - + ContributionAdded(contributionId, contributor, amount); } function claim(uint256 contributionId) public { - var c = contributions[contributionId]; + ContributionData storage c = contributions[contributionId]; require(c.exists); - require(!c.issued); + require(!c.claimed); require(block.number > c.claimAfterBlock); + c.claimed = true; tokenContract().mintFor(c.contributor, c.amount, contributionId); - c.issued = true; + + ContributionClaimed(contributionId, c.contributor, c.amount); } function exists(uint256 contributionId) view public returns (bool) { diff --git a/contracts/Operator.sol b/contracts/Operator.sol index fb6ec2e..d4b1542 100644 --- a/contracts/Operator.sol +++ b/contracts/Operator.sol @@ -121,12 +121,11 @@ contract Operator is Upgradeable { } function executeProposal(uint proposalId) private { - var p = proposals[proposalId]; require(!p.executed); require(p.votesCount >= p.votesNeeded); address recipientAddress = contributorsContract().getContributorAddressById(p.contributorId); - contributionContract().add(p.amount, proposalId, recipientAddress, 0, ''); + contributionContract().add(p.amount, recipientAddress, 0); p.executed = true; ProposalExecuted(proposalId, p.contributorId, p.amount); } diff --git a/lib/abis/Contribution.json b/lib/abis/Contribution.json new file mode 100644 index 0000000..64b488c --- /dev/null +++ b/lib/abis/Contribution.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"contributions","outputs":[{"name":"contributor","type":"address"},{"name":"amount","type":"uint256"},{"name":"claimed","type":"bool"},{"name":"hashDigest","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"tokenMetadataURL","type":"string"},{"name":"claimAfterBlock","type":"uint256"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"approvedFor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"contributionsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"tokensOf","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"implementation","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_proxiedContractName","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"takeOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"sender","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":true,"name":"contributor","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ContributionAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":true,"name":"contributor","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ContributionClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_approved","type":"address"},{"indexed":false,"name":"_tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"constant":true,"inputs":[],"name":"contributorsContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tokenContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"contributionId","type":"uint256"}],"name":"ownerOf","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"contributor","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"contributor","type":"address"},{"name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"contributionId","type":"uint256"}],"name":"tokenMetadata","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"contributionId","type":"uint256"}],"name":"getContribution","outputs":[{"name":"id","type":"uint256"},{"name":"contributor","type":"address"},{"name":"amount","type":"uint256"},{"name":"claimed","type":"bool"},{"name":"hashDigest","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"claimAfterBlock","type":"uint256"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"},{"name":"contributor","type":"address"},{"name":"blocksToWait","type":"uint256"}],"name":"add","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"contributionId","type":"uint256"}],"name":"claim","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"contributionId","type":"uint256"}],"name":"exists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abis/Operator.json b/lib/abis/Operator.json index f099710..fe21bd3 100644 --- a/lib/abis/Operator.json +++ b/lib/abis/Operator.json @@ -1 +1 @@ -[{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"proposals","outputs":[{"name":"creatorAccount","type":"address"},{"name":"contributorId","type":"uint256"},{"name":"votesCount","type":"uint256"},{"name":"votesNeeded","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"executed","type":"bool"},{"name":"ipfsHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"proposalsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"implementation","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_proxiedContractName","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"sender","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"creatorAccount","type":"address"},{"indexed":false,"name":"contributorId","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ProposalCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"voterId","type":"uint256"},{"indexed":false,"name":"totalVotes","type":"uint256"}],"name":"ProposalVoted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"contributorId","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ProposalExecuted","type":"event"},{"constant":true,"inputs":[],"name":"contributorsContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tokenContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"contributorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"coreContributorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"contributorId","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"ipfsHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"}],"name":"addProposal","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"uint256"}],"name":"getProposal","outputs":[{"name":"id","type":"uint256"},{"name":"creatorAccount","type":"address"},{"name":"contributorId","type":"uint256"},{"name":"votesCount","type":"uint256"},{"name":"votesNeeded","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"executed","type":"bool"},{"name":"ipfsHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"voterIds","type":"uint256[]"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"uint256"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_proposalIds","type":"uint256[]"}],"name":"batchVote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"proposals","outputs":[{"name":"creatorAccount","type":"address"},{"name":"contributorId","type":"uint256"},{"name":"votesCount","type":"uint256"},{"name":"votesNeeded","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"executed","type":"bool"},{"name":"ipfsHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"proposalsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"implementation","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_proxiedContractName","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"sender","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"creatorAccount","type":"address"},{"indexed":false,"name":"contributorId","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ProposalCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"voterId","type":"uint256"},{"indexed":false,"name":"totalVotes","type":"uint256"}],"name":"ProposalVoted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"contributorId","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ProposalExecuted","type":"event"},{"constant":true,"inputs":[],"name":"contributorsContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tokenContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"contributionContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"contributorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"coreContributorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"contributorId","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"ipfsHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"}],"name":"addProposal","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"uint256"}],"name":"getProposal","outputs":[{"name":"id","type":"uint256"},{"name":"creatorAccount","type":"address"},{"name":"contributorId","type":"uint256"},{"name":"votesCount","type":"uint256"},{"name":"votesNeeded","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"executed","type":"bool"},{"name":"ipfsHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"voterIds","type":"uint256[]"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"uint256"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_proposalIds","type":"uint256[]"}],"name":"batchVote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/contracts/contribution.js b/lib/contracts/contribution.js new file mode 100644 index 0000000..c7f933d --- /dev/null +++ b/lib/contracts/contribution.js @@ -0,0 +1,51 @@ +const ethers = require('ethers'); +const RSVP = require('rsvp'); + +const ContributionSerializer = require('../serializers/contribution'); +const Base = require('./base'); + +class Contribution extends Base { + all() { + return this.functions.contributionsCount() + .then((count) => { + count = count.toNumber(); + let contributions = []; + + for (let id = 1; id <= count; id++) { + contributions.push(this.getById(id)); + } + + return RSVP.all(contributions); + }); + } + + getById(id) { + id = ethers.utils.bigNumberify(id); + + return this.functions.getContribution(id) + .then((data) => { + return this.ipfs.catAndMerge(data, ContributionSerializer.deserialize); + }); + + } + + getByContributor(contributor) { + return this.functions.balanceOf(contributor) + then((balance) => { + count = balance.toNumber(); + + let contributions = []; + + for (let index = 0; index <= count; index++) { + this.functions.tokenOfOwnerByIndex(contributor, index) + .then((id) => { + contributions.push(this.getById(id)); + }); + } + + return RSVP.all(contributions); + }); + } +} + +module.exports = Contribution; diff --git a/lib/contracts/index.js b/lib/contracts/index.js index a4a1792..ff3dc15 100644 --- a/lib/contracts/index.js +++ b/lib/contracts/index.js @@ -1,5 +1,6 @@ module.exports = { Contributors: require('./contributor'), + Contribution: require('./contribution'), Operator: require('./operator'), Token: require('./token'), Registry: require('./registry') diff --git a/lib/kredits.js b/lib/kredits.js index 9300179..6d17aca 100644 --- a/lib/kredits.js +++ b/lib/kredits.js @@ -5,6 +5,7 @@ const Preflight = require('./utils/preflight'); const ABIS = { Contributors: require('./abis/Contributors.json'), + Contribution: require('./abis/Contribution.json'), Operator: require('./abis/Operator.json'), Registry: require('./abis/Registry.json'), Token: require('./abis/Token.json') @@ -76,6 +77,10 @@ class Kredits { return this.contractFor('token'); } + get Contribution() { + return this.contractFor('contribution'); + } + // Should be private contractFor(name) { if (this.contracts[name]) { diff --git a/scripts/build-json.js b/scripts/build-json.js index db00827..0fb057b 100644 --- a/scripts/build-json.js +++ b/scripts/build-json.js @@ -8,6 +8,7 @@ const addressesPath = path.join(libPath, 'addresses'); const files = [ 'Contributors', + 'Contribution', 'Operator', 'Registry', 'Token'