[WIP] Add Contribution contract #55

Merged
bumi merged 5 commits from contribution-contract into master 2019-04-02 19:36:38 +00:00
10 changed files with 205 additions and 4 deletions

126
contracts/Contribution.sol Normal file
View File

@ -0,0 +1,126 @@
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 ContributionData {
address contributor;
uint amount;
bool claimed;
bytes32 hashDigest;
uint8 hashFunction;
uint8 hashSize;
string tokenMetadataURL;
uint claimAfterBlock;
bool exists;
}
string internal name_;
string internal symbol_;
mapping(uint256 => address) contributionOwner;
mapping(address => uint256[]) ownedContributions;
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'));
}
function name() external view returns (string) {
return name_;
}
function symbol() external view returns (string) {
return symbol_;
}
function ownerOf(uint256 contributionId) public view returns (address) {
require(exists(contributionId));
return contributions[contributionId].contributor;
}
function balanceOf(address contributor) public view returns (uint) {
return ownedContributions[contributor].length;
}
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;
ContributionData storage c = contributions[contributionId];
c.exists = true;
c.amount = amount;
c.claimed = false;
c.contributor = contributor;
c.claimAfterBlock = block.number + blocksToWait;
contributionsCount++;
contributionOwner[contributionId] = contributor;
ownedContributions[contributor].push(contributionId);
ContributionAdded(contributionId, contributor, amount);
}
function claim(uint256 contributionId) public {
ContributionData storage c = contributions[contributionId];
require(c.exists);
require(!c.claimed);
require(block.number > c.claimAfterBlock);
c.claimed = true;
tokenContract().mintFor(c.contributor, c.amount, contributionId);
ContributionClaimed(contributionId, c.contributor, c.amount);
}
function exists(uint256 contributionId) view public returns (bool) {
return contributions[contributionId].exists;
}
}

View File

@ -3,6 +3,7 @@ pragma solidity ^0.4.18;
// ToDo: only load interfaces // ToDo: only load interfaces
import './Token.sol'; import './Token.sol';
import './Contributors.sol'; import './Contributors.sol';
import './Contribution.sol';
contract Operator is Upgradeable { contract Operator is Upgradeable {
@ -47,6 +48,9 @@ contract Operator is Upgradeable {
function tokenContract() view public returns (Token) { function tokenContract() view public returns (Token) {
return Token(registry.getProxyFor('Token')); return Token(registry.getProxyFor('Token'));
} }
function contributionContract() view public returns (Contribution) {
return Contribution(registry.getProxyFor('Contribution'));
}
function contributorsCount() view public returns (uint) { function contributorsCount() view public returns (uint) {
return contributorsContract().contributorsCount(); return contributorsContract().contributorsCount();
@ -117,12 +121,11 @@ contract Operator is Upgradeable {
} }
function executeProposal(uint proposalId) private { function executeProposal(uint proposalId) private {
var p = proposals[proposalId]; var p = proposals[proposalId];
require(!p.executed); require(!p.executed);
require(p.votesCount >= p.votesNeeded); require(p.votesCount >= p.votesNeeded);
address recipientAddress = contributorsContract().getContributorAddressById(p.contributorId); address recipientAddress = contributorsContract().getContributorAddressById(p.contributorId);
tokenContract().mintFor(recipientAddress, p.amount, proposalId); contributionContract().add(p.amount, recipientAddress, 0);
p.executed = true; p.executed = true;
ProposalExecuted(proposalId, p.contributorId, p.amount); ProposalExecuted(proposalId, p.contributorId, p.amount);
} }

View File

@ -17,7 +17,7 @@ contract Token is Upgradeable, BasicToken {
decimals = 18; decimals = 18;
} }
function mintFor(address contributorAccount, uint256 amount, uint proposalId) onlyRegistryContractFor('Operator') public { function mintFor(address contributorAccount, uint256 amount, uint proposalId) onlyRegistryContractFor('Contribution') public {
totalSupply_ = totalSupply_.add(amount); totalSupply_ = totalSupply_.add(amount);
balances[contributorAccount] = balances[contributorAccount].add(amount); balances[contributorAccount] = balances[contributorAccount].add(amount);

File diff suppressed because one or more lines are too long

View File

@ -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"}] [{"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"}]

View File

@ -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;

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
Contributors: require('./contributor'), Contributors: require('./contributor'),
Contribution: require('./contribution'),
Operator: require('./operator'), Operator: require('./operator'),
Token: require('./token'), Token: require('./token'),
Registry: require('./registry') Registry: require('./registry')

View File

@ -5,6 +5,7 @@ const Preflight = require('./utils/preflight');
const ABIS = { const ABIS = {
Contributors: require('./abis/Contributors.json'), Contributors: require('./abis/Contributors.json'),
Contribution: require('./abis/Contribution.json'),
Operator: require('./abis/Operator.json'), Operator: require('./abis/Operator.json'),
Registry: require('./abis/Registry.json'), Registry: require('./abis/Registry.json'),
Token: require('./abis/Token.json') Token: require('./abis/Token.json')
@ -76,6 +77,10 @@ class Kredits {
return this.contractFor('token'); return this.contractFor('token');
} }
get Contribution() {
return this.contractFor('contribution');
}
// Should be private // Should be private
contractFor(name) { contractFor(name) {
if (this.contracts[name]) { if (this.contracts[name]) {

View File

@ -0,0 +1,13 @@
var Registry = artifacts.require('./Registry.sol');
var Contribution = artifacts.require('./Contribution.sol');
module.exports = function(deployer) {
deployer.deploy(Contribution).then(function(contribution) {
console.log('Registry address: ', Registry.address);
console.log('Contribution address: ', Contribution.address);
Registry.deployed().then(function(registry) {
registry.addVersion('Contribution', Contribution.address);
registry.createProxy('Contribution', 1);
});
});
};

View File

@ -8,6 +8,7 @@ const addressesPath = path.join(libPath, 'addresses');
const files = [ const files = [
'Contributors', 'Contributors',
'Contribution',
'Operator', 'Operator',
'Registry', 'Registry',
'Token' 'Token'