Merge branch 'master' into features/batch-voting

This commit is contained in:
bumi 2018-04-24 12:39:16 +00:00 committed by GitHub
commit ce5f5fb8d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 914 additions and 504 deletions

0
.ganache-db/.gitkeep Normal file
View File

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
build build
node_modules node_modules
.ganache-db

View File

@ -22,11 +22,18 @@ We use following solidity contract libraries:
For local development it is recommended to use [ganache-cli](https://github.com/trufflesuite/ganache-cli) (or the [ganache GUI](http://truffleframework.com/ganache/) to run a local development chain. For local development it is recommended to use [ganache-cli](https://github.com/trufflesuite/ganache-cli) (or the [ganache GUI](http://truffleframework.com/ganache/) to run a local development chain.
Using the ganache simulator no full Ethereum node is required. Using the ganache simulator no full Ethereum node is required.
We default to the port 7545 for development to not get in conflict with the default Ethereum RPC port. Have a look at `ganache-cli` for more configuration options. We default to:
* port 7545 for development to not get in conflict with the default Ethereum RPC port.
* network ID 100 to stay on the same network id
* store ganache data in .ganache-db to presist the chain data across restarts
* use a fixed Mnemonic code to get the same accounts across restarts
Have a look at `ganache-cli` for more configuration options.
Run your ganache simulator before using Kredits locally: Run your ganache simulator before using Kredits locally:
$ ganache-cli -p 7545 $ npm run ganache (which is: ganache-cli -p 7545 -i 100 --db=./.ganache-db -m kredits)
### Truffle console ### Truffle console

View File

@ -1,65 +1,9 @@
let contractCalls = { let contractCalls = [
Contributors: { ['Contributor', 'add', [{ account: '0x7e8f313c56f809188313aa274fa67ee58c31515d', name: 'bumi', isCore: true, kind: 'preson', url: '', github_username: 'bumi', github_uid: 318, wiki_username: 'bumi' }, {gasLimit: 200000}]],
addContributor: [ ['Contributor', 'add', [{ account: '0xa502eb4021f3b9ab62f75b57a94e1cfbf81fd827', name: 'raucau', isCore: true, kind: 'person', url: '', github_username: 'skddc', github_uid: 842, wiki_username: 'raucau' }, {gasLimit: 200000}]],
// make sure to use an IPFS hash of one of the objects in ipfsContent ['Operator', 'addProposal', [{ contributorId: 2, amount: 42, kind: 'code', description: 'runs the seeds', url: '' }, {gasLimit: 350000}]],
['0x24dd2aedd8a9fe52ac071b3a23b2fc8f225c185e', '0x272bbfc66166f26cae9c9b96b7f9590e095f02edf342ac2dd71e1667a12116ca', 18, 32, true], // QmQyZJT9uikzDYTZLhhyVZ5ReZVCoMucYzyvDokDJsijhj ['Operator', 'addProposal', [{ contributorId: 3, amount: 23, kind: 'code', description: 'runs the seeds', url: '' }, {gasLimit: 350000}]],
['0xa502eb4021f3b9ab62f75b57a94e1cfbf81fd827', '0x9569ed44826286597982e40bbdff919c6b7752e29d13250efca452644e6b4b25', 18, 32, true] // QmYPu8zvtfDy18ZqHCviVxnKtxycw5UTJLJyk9oAEjWfnL ['Operator', 'addProposal', [{contributorId: 3, amount: 100, kind: 'code', description: 'hacks on kredits', url: '' }, {gasLimit: 350000}]],
] ['Operator', 'vote', ['1', {gasLimit: 250000}]]
}, ];
Operator: { module.exports = { contractCalls };
addProposal: [
[2, 23, '0x1e1a168d736fc825213144973a8fd5b3cc9f37ad821a8b3d9c3488034bbf69d8', 18, 32], // QmQNA1hhVyL1Vm6HiRxXe9xmc6LUMBDyiNMVgsjThtyevs"
[3, 42, '0x1e1a168d736fc825213144973a8fd5b3cc9f37ad821a8b3d9c3488034bbf69d8', 18, 32], // QmQNA1hhVyL1Vm6HiRxXe9xmc6LUMBDyiNMVgsjThtyevs"
[3, 100, '0x1e1a168d736fc825213144973a8fd5b3cc9f37ad821a8b3d9c3488034bbf69d8', 18, 32] // QmQNA1hhVyL1Vm6HiRxXe9xmc6LUMBDyiNMVgsjThtyevs"
],
vote: [
[1]
]
}
};
let ipfsContent = [
{
"@context": "https://schema.kosmos.org",
"@type": "Contributor",
"kind": "person",
"name": "Râu Cao",
"url": "https://sebastian.kip.pe",
"accounts": [
{
"site": "github.com",
"username": "skddc",
"uid": 842,
"url": "https://github.com/skddc/"
},
]
},
{
"@context": "https://schema.kosmos.org",
"@type": "Contributor",
"kind": "person",
"name": "Bumi",
"url": "https://michaelbumann.com",
"accounts": [
{
"site": "github.com",
"username": "bumi",
"uid": 318,
"url": "https://github.com/bumi/"
},
]
},
{
"@context": "https://schema.kosmos.org",
"@type": "Contribution",
"contributor": {
"ipfs": "QmQ2ZZS2bXgneQfKtVTVxe6dV7pcJuXnTeZJQtoVUFsAtJ"
},
"kind": "dev",
"description": "hacking hacking on kredits",
"url": "https://github.com/67P/kredits-web/pull/11",
"details": {}
}
]
module.exports = { contractCalls, ipfsContent };

View File

@ -49,7 +49,7 @@ contract Contributors is Upgradeable {
return count; return count;
} }
function updateContributorAddress(uint id, address oldAccount, address newAccount) public onlyCoreOrOperator { function updateContributorAccount(uint id, address oldAccount, address newAccount) public onlyCoreOrOperator {
contributorIds[oldAccount] = 0; contributorIds[oldAccount] = 0;
contributorIds[newAccount] = id; contributorIds[newAccount] = id;
contributors[id].account = newAccount; contributors[id].account = newAccount;

View File

@ -7,8 +7,8 @@ import './Contributors.sol';
contract Operator is Upgradeable { contract Operator is Upgradeable {
struct Proposal { struct Proposal {
address creator; address creatorAccount;
uint recipientId; uint contributorId;
uint votesCount; uint votesCount;
uint votesNeeded; uint votesNeeded;
uint256 amount; uint256 amount;
@ -24,9 +24,9 @@ contract Operator is Upgradeable {
mapping(uint256 => Proposal) public proposals; mapping(uint256 => Proposal) public proposals;
uint256 public proposalsCount; uint256 public proposalsCount;
event ProposalCreated(uint256 id, address creator, uint recipient, uint256 amount); event ProposalCreated(uint256 id, address creatorAccount, uint256 contributorId, uint256 amount);
event ProposalVoted(uint256 id, address voter, uint256 totalVotes); event ProposalVoted(uint256 id, uint256 voterId, uint256 totalVotes);
event ProposalExecuted(uint256 id, uint recipient, uint256 amount); event ProposalExecuted(uint256 id, uint256 contributorId, uint256 amount);
modifier coreOnly() { modifier coreOnly() {
require(contributorsContract().addressIsCore(msg.sender)); require(contributorsContract().addressIsCore(msg.sender));
@ -55,48 +55,34 @@ contract Operator is Upgradeable {
return contributorsContract().coreContributorsCount(); return contributorsContract().coreContributorsCount();
} }
function addContributor(address _address, bytes32 _ipfsHash, uint8 _hashFunction, uint8 _hashSize, bool _isCore) public coreOnly { function addProposal(uint contributorId, uint256 amount, bytes32 ipfsHash, uint8 hashFunction, uint8 hashSize) public {
contributorsContract().addContributor(_address, _ipfsHash, _hashFunction, _hashSize, _isCore); require(contributorsContract().exists(contributorId));
}
function updateContributorIpfsHash(uint _id, bytes32 _ipfsHash, uint8 _hashFunction, uint8 _hashSize) public coreOnly { uint256 proposalId = proposalsCount + 1;
contributorsContract().updateContributorIpfsHash(_id, _ipfsHash, _hashFunction, _hashSize); uint256 _votesNeeded = contributorsContract().coreContributorsCount() / 100 * 75;
}
function getContributor(uint _id) view public returns (address account, bytes32 ipfsHash, uint8 hashFunction, uint8 hashSize, bool isCore) {
bool exists;
(account, ipfsHash, hashFunction, hashSize, isCore, exists) = contributorsContract().contributors(_id);
require(exists);
}
function addProposal(uint _recipient, uint256 _amount, bytes32 _ipfsHash, uint8 _hashFunction, uint8 _hashSize) public returns (uint256 proposalId) {
require(contributorsContract().exists(_recipient));
proposalId = proposalsCount + 1;
uint _votesNeeded = contributorsContract().coreContributorsCount() / 100 * 75;
var p = proposals[proposalId]; var p = proposals[proposalId];
p.creator = msg.sender; p.creatorAccount = msg.sender;
p.recipientId = _recipient; p.contributorId = contributorId;
p.amount = _amount; p.amount = amount;
p.ipfsHash = _ipfsHash; p.ipfsHash = ipfsHash;
p.hashFunction = _hashFunction; p.hashFunction = hashFunction;
p.hashSize = _hashSize; p.hashSize = hashSize;
p.votesCount = 0; p.votesCount = 0;
p.votesNeeded = _votesNeeded; p.votesNeeded = _votesNeeded;
p.exists = true; p.exists = true;
proposalsCount++; proposalsCount++;
ProposalCreated(proposalId, msg.sender, p.recipientId, p.amount); ProposalCreated(proposalId, msg.sender, p.contributorId, p.amount);
} }
function getProposal(uint _proposalId) public view returns (address creator, uint256 recipientId, uint256 votesCount, uint256 votesNeeded, uint256 amount, bool executed, bytes32 ipfsHash, uint8 hashFunction, uint8 hashSize, uint256[] voterIds, bool exists) { function getProposal(uint proposalId) public view returns (uint256 id, address creatorAccount, uint256 contributorId, uint256 votesCount, uint256 votesNeeded, uint256 amount, bool executed, bytes32 ipfsHash, uint8 hashFunction, uint8 hashSize, uint256[] voterIds, bool exists) {
Proposal storage p = proposals[_proposalId]; id = proposalId;
Proposal storage p = proposals[id];
return ( return (
p.creator, id,
p.recipientId, p.creatorAccount,
p.contributorId,
p.votesCount, p.votesCount,
p.votesNeeded, p.votesNeeded,
p.amount, p.amount,
@ -109,22 +95,19 @@ contract Operator is Upgradeable {
); );
} }
function vote(uint256 _proposalId) public coreOnly returns (uint _pId, bool _executed) { function vote(uint256 proposalId) public coreOnly {
var p = proposals[_proposalId]; var p = proposals[proposalId];
require(!p.executed); require(!p.executed);
uint256 contributorId = contributorsContract().getContributorIdByAddress(msg.sender); uint256 voterId = contributorsContract().getContributorIdByAddress(msg.sender);
require(p.votes[contributorId] != true); require(p.votes[voterId] != true);
p.voterIds.push(contributorId); p.voterIds.push(voterId);
p.votes[contributorId] = true; p.votes[voterId] = true;
p.votesCount++; p.votesCount++;
_executed = false;
_pId = _proposalId;
if (p.votesCount >= p.votesNeeded) { if (p.votesCount >= p.votesNeeded) {
executeProposal(_proposalId); executeProposal(proposalId);
_executed = true;
} }
ProposalVoted(_pId, msg.sender, p.votesCount); ProposalVoted(proposalId, voterId, p.votesCount);
} }
function batchVote(uint256[] _proposalIds) public coreOnly { function batchVote(uint256[] _proposalIds) public coreOnly {
@ -133,15 +116,15 @@ contract Operator is Upgradeable {
} }
} }
function executeProposal(uint proposalId) private returns (bool) { 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.recipientId); address recipientAddress = contributorsContract().getContributorAddressById(p.contributorId);
tokenContract().mintFor(recipientAddress, p.amount, proposalId); tokenContract().mintFor(recipientAddress, p.amount, proposalId);
p.executed = true; p.executed = true;
ProposalExecuted(proposalId, p.recipientId, p.amount); ProposalExecuted(proposalId, p.contributorId, p.amount);
return true;
} }
} }

View File

@ -17,12 +17,11 @@ contract Token is Upgradeable, BasicToken {
decimals = 18; decimals = 18;
} }
function mintFor(address _recipient, uint256 _amount, uint _proposalId) onlyRegistryContractFor('Operator') public returns (bool success) { function mintFor(address contributorAccount, uint256 amount, uint proposalId) onlyRegistryContractFor('Operator') public {
totalSupply_ = totalSupply_.add(_amount); totalSupply_ = totalSupply_.add(amount);
balances[_recipient] = balances[_recipient].add(_amount); balances[contributorAccount] = balances[contributorAccount].add(amount);
LogMint(_recipient, _amount, _proposalId); LogMint(contributorAccount, amount, proposalId);
return true;
} }
} }

View File

@ -1 +1 @@
[{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"contributors","outputs":[{"name":"account","type":"address"},{"name":"profileHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"isCore","type":"bool"},{"name":"exists","type":"bool"}],"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":true,"inputs":[],"name":"contributorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"contributorIds","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"oldProfileHash","type":"bytes32"},{"indexed":false,"name":"newProfileHash","type":"bytes32"}],"name":"ContributorProfileUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"oldAddress","type":"address"},{"indexed":false,"name":"newAddress","type":"address"}],"name":"ContributorAddressUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"_address","type":"address"}],"name":"ContributorAdded","type":"event"},{"constant":false,"inputs":[{"name":"sender","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"coreContributorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_oldAddress","type":"address"},{"name":"_newAddress","type":"address"}],"name":"updateContributorAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_hashFunction","type":"uint8"},{"name":"_hashSize","type":"uint8"},{"name":"_profileHash","type":"bytes32"}],"name":"updateContributorProfileHash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"},{"name":"_hashFunction","type":"uint8"},{"name":"_hashSize","type":"uint8"},{"name":"_profileHash","type":"bytes32"},{"name":"isCore","type":"bool"}],"name":"addContributor","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"isCore","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"exists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_address","type":"address"}],"name":"addressIsCore","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_address","type":"address"}],"name":"addressExists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_address","type":"address"}],"name":"getContributorIdByAddress","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"getContributorAddressById","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"getContributorById","outputs":[{"name":"account","type":"address"},{"name":"profileHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"isCore","type":"bool"},{"name":"exists","type":"bool"},{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] [{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"contributors","outputs":[{"name":"account","type":"address"},{"name":"ipfsHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"isCore","type":"bool"},{"name":"exists","type":"bool"}],"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":true,"inputs":[],"name":"contributorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"contributorIds","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"oldIpfsHash","type":"bytes32"},{"indexed":false,"name":"newIpfsHash","type":"bytes32"}],"name":"ContributorProfileUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"oldAccount","type":"address"},{"indexed":false,"name":"newAccount","type":"address"}],"name":"ContributorAccountUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"account","type":"address"}],"name":"ContributorAdded","type":"event"},{"constant":false,"inputs":[{"name":"sender","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"coreContributorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"oldAccount","type":"address"},{"name":"newAccount","type":"address"}],"name":"updateContributorAccount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"ipfsHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"}],"name":"updateContributorIpfsHash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"account","type":"address"},{"name":"ipfsHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"isCore","type":"bool"}],"name":"addContributor","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint256"}],"name":"isCore","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint256"}],"name":"exists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"addressIsCore","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"addressExists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getContributorIdByAddress","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint256"}],"name":"getContributorAddressById","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"getContributorById","outputs":[{"name":"id","type":"uint256"},{"name":"account","type":"address"},{"name":"ipfsHash","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"isCore","type":"bool"},{"name":"balance","type":"uint256"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}]

View File

@ -1 +1 @@
[{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"proposals","outputs":[{"name":"creator","type":"address"},{"name":"recipientId","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":"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":"creator","type":"address"},{"indexed":false,"name":"recipient","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ProposalCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"voter","type":"address"},{"indexed":false,"name":"totalVotes","type":"uint256"}],"name":"ProposalVoted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"recipient","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":"_address","type":"address"},{"name":"_profileHash","type":"bytes32"},{"name":"_hashFunction","type":"uint8"},{"name":"_hashSize","type":"uint8"},{"name":"_isCore","type":"bool"}],"name":"addContributor","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_profileHash","type":"bytes32"},{"name":"_hashFunction","type":"uint8"},{"name":"_hashSize","type":"uint8"}],"name":"updateContributorProfileHash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"getContributor","outputs":[{"name":"account","type":"address"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"profileHash","type":"bytes32"},{"name":"isCore","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"proposalsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"uint256"},{"name":"_amount","type":"uint256"},{"name":"_ipfsHash","type":"bytes32"},{"name":"_hashFunction","type":"uint8"},{"name":"_hashSize","type":"uint8"}],"name":"addProposal","outputs":[{"name":"proposalId","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_proposalId","type":"uint256"}],"name":"vote","outputs":[{"name":"_pId","type":"uint256"},{"name":"_executed","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_proposalId","type":"uint256"}],"name":"hasVotedFor","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","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":"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"}]

View File

@ -1 +1 @@
[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"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":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"recipient","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"proposalId","type":"uint256"}],"name":"LogMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":false,"inputs":[{"name":"sender","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"},{"name":"_proposalId","type":"uint256"}],"name":"mintFor","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}] [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"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":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"recipient","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"proposalId","type":"uint256"}],"name":"LogMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":false,"inputs":[{"name":"sender","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"contributorAccount","type":"address"},{"name":"amount","type":"uint256"},{"name":"proposalId","type":"uint256"}],"name":"mintFor","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]

View File

@ -1 +1 @@
{"100":"0x34262a9471ede40e94497d110652dcf0f26fa0db"} {"42":"0x205fe1b3dac678b594c5f0535e7d158e38591f93","100":"0xa7fc9b1f678c41396b53904f94f50a42ff44d826"}

View File

@ -1 +1 @@
{"100":"0x901f3e99a170619857b354c101aa97244885a39e"} {"42":"0x9fd66ee78a5ebe86006f12b37ff59c63f9caa15b","100":"0x95d3bd7d136bb0b7ac9988097e964236f8a9976e"}

View File

@ -1 +1 @@
{"100":"0x9447a29373cf3ce0edf41804820c2a9da0ef4fd9"} {"42":"0xc270e6ea4fe303df9f1a3d4a132ac425264082e7","100":"0x7458dea485d9d8301e3ce43e8a1ec1456be5ba83"}

View File

@ -1 +1 @@
{"100":"0x8ecf9fa8a1c50179cef966d53aaee3d2a382c932"} {"42":"0xf71ccf7ab48044ef9ae0b5e6983dbd3266b78b36","100":"0x3fc29fbe40c2d0ca78c7e81342f00226650fe2ad"}

27
lib/contracts/base.js Normal file
View File

@ -0,0 +1,27 @@
class Base {
constructor(contract) {
this.contract = contract;
}
get functions() {
return this.contract.functions;
}
get ipfs() {
if (!this._ipfsAPI) { throw new Error('IPFS API not configured; please set an ipfs instance'); }
return this._ipfsAPI;
}
set ipfs(ipfsAPI) {
this._ipfsAPI = ipfsAPI;
}
on(type, callback) {
let eventMethod = `on${type.toLowerCase()}`;
// Don't use this.contract.events here. Seems to be a bug in ethers.js
this.contract[eventMethod] = callback;
return this;
}
}
module.exports = Base;

View File

@ -0,0 +1,57 @@
const ethers = require('ethers');
const RSVP = require('rsvp');
const ContributorSerializer = require('../serializers/contributor');
const Base = require('./base');
class Contributor extends Base {
all() {
return this.functions.contributorsCount()
.then((count) => {
count = count.toNumber();
let contributors = [];
for (let id = 1; id <= count; id++) {
contributors.push(this.getById(id));
}
return RSVP.all(contributors);
});
}
getById(id) {
id = ethers.utils.bigNumberify(id);
return this.functions.getContributorById(id)
.then((data) => {
// TODO: remove when naming updated on the contract
data.hashDigest = data.ipfsHash;
return data;
})
// Fetch IPFS data if available
.then((data) => {
return this.ipfs.catAndMerge(data, ContributorSerializer.deserialize);
});
}
add(contributorAttr, callOptions = {}) {
let json = ContributorSerializer.serialize(contributorAttr);
// TODO: validate against schema
return this.ipfs
.add(json)
.then((ipfsHashAttr) => {
let contributor = [
contributorAttr.account,
ipfsHashAttr.hashDigest,
ipfsHashAttr.hashFunction,
ipfsHashAttr.hashSize,
contributorAttr.isCore,
];
return this.functions.addContributor(...contributor, callOptions);
});
}
}
module.exports = Contributor;

6
lib/contracts/index.js Normal file
View File

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

57
lib/contracts/operator.js Normal file
View File

@ -0,0 +1,57 @@
const ethers = require('ethers');
const RSVP = require('rsvp');
const ContributionSerializer = require('../serializers/contribution');
const Base = require('./base');
class Operator extends Base {
all() {
return this.functions.proposalsCount()
.then((count) => {
count = count.toNumber();
let proposals = [];
for (let id = 1; id <= count; id++) {
proposals.push(this.getById(id));
}
return RSVP.all(proposals);
});
}
getById(id) {
id = ethers.utils.bigNumberify(id);
return this.functions.getProposal(id)
.then((data) => {
// TODO: remove when naming updated on the contract
data.hashDigest = data.ipfsHash;
return data;
})
// Fetch IPFS data if available
.then((data) => {
return this.ipfs.catAndMerge(data, ContributionSerializer.deserialize);
});
}
addProposal(proposalAttr, callOptions = {}) {
let json = ContributionSerializer.serialize(proposalAttr);
// TODO: validate against schema
return this.ipfs
.add(json)
.then((ipfsHashAttr) => {
let proposal = [
proposalAttr.contributorId,
proposalAttr.amount,
ipfsHashAttr.hashDigest,
ipfsHashAttr.hashFunction,
ipfsHashAttr.hashSize,
];
return this.functions.addProposal(...proposal, callOptions);
});
}
}
module.exports = Operator;

View File

@ -0,0 +1,6 @@
const Base = require('./base');
class Registry extends Base {
}
module.exports = Registry;

7
lib/contracts/token.js Normal file
View File

@ -0,0 +1,7 @@
const Base = require('./base');
class Token extends Base {
}
module.exports = Token;

View File

@ -1 +0,0 @@
module.exports = ["Contributors","Operator","Registry","Token"];

103
lib/kredits.js Normal file
View File

@ -0,0 +1,103 @@
const ethers = require('ethers');
const RSVP = require('rsvp');
const Healthcheck = require('./utils/healthcheck');
const ABIS = {
Contributors: require('./abis/Contributors.json'),
Operator: require('./abis/Operator.json'),
Registry: require('./abis/Registry.json'),
Token: require('./abis/Token.json')
};
const RegistryAddress = require('./addresses/Registry.json');
const Contracts = require('./contracts');
const IPFS = require('./utils/ipfs')
// Helpers
function capitalize(word) {
let [first, ...rest] = word;
return `${first.toUpperCase()}${rest.join('')}`;
}
class Kredits {
constructor(provider, signer, addresses) {
this.provider = provider;
this.signer = signer;
// by default we only need the registry address.
// the rest is loaded from there in the init() function
this.addresses = addresses || {Registry: RegistryAddress[this.provider.chainId.toString()]}; // chaiID must be a string
this.abis = ABIS;
this.contracts = {};
this.ipfs = new IPFS();
}
init(names) {
let contractsToLoad = names || Object.keys(ABIS);
let addressPromises = contractsToLoad.map((contractName) => {
return this.Registry.functions.getProxyFor(contractName).then((address) => {
this.addresses[contractName] = address;
}).catch((error) => {
throw new Error(`Failed to get address for ${contractName} from registry at ${this.Registry.contract.address}
- correct registry? does it have version entry? - ${error.message}`
);
});
});
return RSVP.all(addressPromises).then(() => { return this });
}
static setup(provider, signer, ipfsConfig = null) {
console.log('Kredits.setup() is deprecated use new Kredits().init() instead');
let ipfs = new IPFS(ipfsConfig);
return new Kredits(provider, signer).init().then((kredits) => {
kredits.ipfs = ipfs;
return kredits;
});
}
get Registry() {
return this.contractFor('registry');
}
get Contributor() {
// TODO: rename to contributor
return this.contractFor('contributors');
}
get Operator() {
return this.contractFor('operator');
}
get Token() {
return this.contractFor('token');
}
// Should be private
contractFor(name) {
if (this.contracts[name]) {
return this.contracts[name];
}
const contractName = capitalize(name);
const address = this.addresses[contractName];
const abi = this.abis[contractName];
if (!address || !abi) {
throw new Error(`Address or ABI not found for ${contractName}`);
}
let signerOrProvider = this.signer || this.provider;
let contract = new ethers.Contract(address, abi, signerOrProvider);
this.contracts[name] = new Contracts[contractName](contract);
this.contracts[name].ipfs = this.ipfs;
return this.contracts[name];
}
healthcheck() {
return new Healthcheck(this).check();
}
}
module.exports = Kredits;

View File

@ -0,0 +1,67 @@
/**
* Handle serialization for JSON-LD object of the contribution, according to
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contribution.json
*
* @class
* @public
*/
class Contribution {
/**
* Deserialize JSON to object
*
* @method
* @public
*/
static deserialize(serialized) {
let {
kind,
description,
details,
url,
} = JSON.parse(serialized.toString('utf8'));
return {
kind,
description,
details,
url,
ipfsData: serialized,
};
}
/**
* Serialize object to JSON
*
* @method
* @public
*/
static serialize(deserialized) {
let {
contributorIpfsHash,
kind,
description,
url,
details
} = deserialized;
let data = {
"@context": "https://schema.kosmos.org",
"@type": "Contribution",
"contributor": {
"ipfs": contributorIpfsHash
},
kind,
description,
"details": details || {}
};
if (url) {
data["url"] = url;
}
// Write it pretty to ipfs
return JSON.stringify(data, null, 2);
}
}
module.exports = Contribution;

View File

@ -0,0 +1,96 @@
/**
* Handle serialization for JSON-LD object of the contributor, according to
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contributor.json
*
* @class
* @public
*/
class Contributor {
/**
* Deserialize JSON to object
*
* @method
* @public
*/
static deserialize(serialized) {
let {
name,
kind,
url,
accounts,
} = JSON.parse(serialized.toString('utf8'));
let github_username, github_uid, wiki_username;
let github = accounts.find((a) => a.site === 'github.com');
let wiki = accounts.find((a) => a.site === 'wiki.kosmos.org');
if (github) {
(({ username: github_username, uid: github_uid} = github));
}
if (wiki) {
(({ username: wiki_username } = wiki));
}
return {
name,
kind,
url,
accounts,
github_uid,
github_username,
wiki_username,
ipfsData: serialized,
};
}
/**
* Serialize object to JSON
*
* @method
* @public
*/
static serialize(deserialized) {
let {
name,
kind,
url,
github_uid,
github_username,
wiki_username,
} = deserialized;
let data = {
"@context": "https://schema.kosmos.org",
"@type": "Contributor",
kind,
name,
"accounts": []
};
if (url) {
data["url"] = url;
}
if (github_uid) {
data.accounts.push({
"site": "github.com",
"uid": github_uid,
"username": github_username,
"url": `https://github.com/${github_username}`
});
}
if (wiki_username) {
data.accounts.push({
"site": "wiki.kosmos.org",
"username": wiki_username,
"url": `https://wiki.kosmos.org/User:${wiki_username}`
});
}
// Write it pretty to ipfs
return JSON.stringify(data, null, 2);
}
}
module.exports = Contributor;

28
lib/utils/healthcheck.js Normal file
View File

@ -0,0 +1,28 @@
class Healthcheck {
constructor(kredits) {
this.kredits = kredits;
}
check() {
return this.kredits.ipfs._ipfsAPI.id()
.catch((error) => {
throw new Error(`IPFS node not available; config: ${JSON.stringify(this.kredits.ipfs.config)} - ${error.message}`);
})
.then(() => {
let promises = Object.keys(this.kredits.contracts).map((name) => {
let contractWrapper = this.kredits.contracts[name];
return this.kredits.provider.getCode(contractWrapper.contract.address).then((code) => {
// not sure if we always get the same return value if the code is not available
// so checking if it is < 5 long
if (code === '0x00' || code.length < 5) {
throw new Error(`Contract for: ${name} not found at ${contractWrapper.contract.address} on network ${this.kredits.provider.chainId}`);
}
return true;
});
});
return Promise.all(promises);
});
}
}
module.exports = Healthcheck;

62
lib/utils/ipfs.js Normal file
View File

@ -0,0 +1,62 @@
const ipfsAPI = require('ipfs-api');
const multihashes = require('multihashes');
class IPFS {
constructor(config) {
if (!config) {
config = {host: 'localhost', port: '5001', protocol: 'http'};
}
this._ipfsAPI = ipfsAPI(config);
this._config = config;
}
catAndMerge(data, deserialize) {
// if no hash details are found simply return the data; nothing to merge
if (!data.hashSize || data.hashSize === 0) {
return data;
}
// merge ipfsHash (encoded from hashDigest, hashSize, hashFunction)
data.ipfsHash = multihashes.toB58String(this.encodeHash(data));
return this.cat(data.ipfsHash)
.then(deserialize)
.then((attributes) => {
return Object.assign({}, data, attributes);
});
}
add(data) {
return this._ipfsAPI
.add(new this._ipfsAPI.Buffer(data))
.then((res) => {
return this.decodeHash(res[0].hash);
});
}
cat(hashData) {
let ipfsHash = hashData; // default - if it is a string
if (hashData.hasOwnProperty('hashSize')) {
ipfsHash = this.encodeHash(hashData);
}
return this._ipfsAPI.cat(ipfsHash);
}
decodeHash(ipfsHash) {
let multihash = multihashes.decode(multihashes.fromB58String(ipfsHash));
return {
hashDigest: '0x' + multihashes.toHexString(multihash.digest),
hashSize: multihash.length,
hashFunction: multihash.code,
ipfsHash: ipfsHash
};
}
encodeHash(hashData) {
let digest = this._ipfsAPI.Buffer.from(hashData.hashDigest.slice(2), 'hex');
return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize);
}
}
module.exports = IPFS;

443
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,14 +2,14 @@
"name": "kredits-contracts", "name": "kredits-contracts",
"version": "1.0.0", "version": "1.0.0",
"description": "Ethereum contracts and npm wrapper for Kredits", "description": "Ethereum contracts and npm wrapper for Kredits",
"main": "index.js", "main": "./lib/kredits.js",
"directories": { "directories": {
"test": "test" "test": "test"
}, },
"scripts": { "scripts": {
"build-json": "truffle compile && node ./scripts/build-json.js", "build-json": "truffle compile && node ./scripts/build-json.js",
"bootstrap": "truffle migrate --reset && truffle exec scripts/seeds.js && npm run build-json", "bootstrap": "truffle migrate --reset && npm run build-json && truffle exec scripts/seeds.js",
"ganache": "ganache-cli -p 7545 -i 100", "ganache": "ganache-cli -p 7545 -i 100 --db=./.ganache-db -m kredits",
"dev": "truffle migrate && npm run build-json", "dev": "truffle migrate && npm run build-json",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
@ -24,9 +24,15 @@
}, },
"homepage": "https://github.com/67P/truffle-kredits#readme", "homepage": "https://github.com/67P/truffle-kredits#readme",
"devDependencies": { "devDependencies": {
"async-each-series": "^1.1.0",
"ganache-cli": "^6.0.3", "ganache-cli": "^6.0.3",
"ipfs-api": "^19.0.0", "promptly": "^3.0.3",
"truffle": "^4.1.3", "truffle": "^4.1.3",
"zeppelin-solidity": "^1.7.0" "zeppelin-solidity": "^1.7.0"
},
"dependencies": {
"ethers": "3.0.15",
"ipfs-api": "^19.0.0",
"rsvp": "^4.8.2"
} }
} }

View File

@ -1,47 +1,50 @@
const Registry = artifacts.require('./Registry.sol'); const Registry = artifacts.require('./Registry.sol');
const Operator = artifacts.require('./Operator.sol'); const promptly = require('promptly');
const Contributors = artifacts.require('./Contributors.sol'); const bs58 = require('bs58');
var bs58 = require('bs58'); const ethers = require('ethers');
const Kredits = require('../lib/kredits');
function getBytes32FromMultiash(multihash) { async function prompt(message, options) {
const decoded = bs58.decode(multihash); if (!options) {
options = {default: ''}
return { }
digest: `0x${decoded.slice(2).toString('hex')}`, return await promptly.prompt(message, options);
hashFunction: decoded[0],
size: decoded[1],
};
} }
module.exports = function(callback) { module.exports = function(callback) {
Registry.deployed().then(async (registry) => { Registry.deployed().then(async (registry) => {
var operatorAddress = await registry.getProxyFor('Operator');
var contributorsAddress = await registry.getProxyFor('Contributors');
var operator = await Operator.at(operatorAddress); const networkId = parseInt(web3.version.network);
var contributors = await Contributors.at(contributorsAddress); const provider = new ethers.providers.Web3Provider(
web3.currentProvider, { chainId: networkId }
);
const kredits = await Kredits.setup(provider, provider.getSigner());
let contributorToAddAddress = process.argv[4]; console.log(`Using contributors at: ${kredits.Contributor.contract.address}`);
if(!contributorToAddAddress) {
console.log('please provide an address');
proxess.exit();
}
let ipfsHash = process.argv[5] || 'QmQyZJT9uikzDYTZLhhyVZ5ReZVCoMucYzyvDokDJsijhj';
let contributorMultihash = getBytes32FromMultiash(ipfsHash);
let isCore = true;
let contributorResult = await contributors.addContributor(contributorToAddAddress, contributorMultihash.digest, contributorMultihash.hashFunction, contributorMultihash.size, isCore);
console.log('Contributor added, tx: ', contributorResult.tx);
let contributorId = await contributors.getContributorIdByAddress(contributorToAddAddress); let contributorAttributes = {
let proposalMultihash = getBytes32FromMultiash('QmQNA1hhVyL1Vm6HiRxXe9xmc6LUMBDyiNMVgsjThtyevs'); account: await prompt('Contributor address: ', {}),
let proposalResult = await operator.addProposal(contributorId, 23, proposalMultihash.digest, proposalMultihash.hashFunction, proposalMultihash.size); name: await prompt('Name: '),
console.log('Proposal added, tx: ', proposalResult.tx); isCore: await prompt('core? y/n') === 'y',
kind: await prompt('Kind (default person): ', {default: 'person'}),
url: await prompt('URL: '),
github_username: await prompt('GitHub username: '),
github_uid: await prompt('GitHub UID: '),
wiki_username: await prompt('Wiki username: '),
};
let proposalId = await operator.proposalsCount(); console.log("\nAdding contributor:");
let votingResult = await operator.vote(proposalId.toNumber()-1); console.log(contributorAttributes);
console.log('Voted for proposal', proposalId.toString(), votingResult.tx);
kredits.Contributor.add(contributorAttributes, {gasLimit: 250000}).then((result) => {
console.log("\n\nResult:");
console.log(result);
callback();
}).catch((error) => {
console.log('Failed to create contributor');
callback(error);
});
callback();
}); });
} }

View File

@ -1,8 +1,9 @@
const Registry = artifacts.require('./Registry.sol'); const Registry = artifacts.require('./Registry.sol');
const Operator = artifacts.require('./Operator.sol'); const Operator = artifacts.require('./Operator.sol');
const Contributors = artifacts.require('./Contributors.sol'); const Contributors = artifacts.require('./Contributors.sol');
const promptly = require('promptly');
var bs58 = require('bs58'); const bs58 = require('bs58');
function getBytes32FromMultiash(multihash) { function getBytes32FromMultiash(multihash) {
const decoded = bs58.decode(multihash); const decoded = bs58.decode(multihash);
@ -22,12 +23,9 @@ module.exports = function(callback) {
var operator = await Operator.at(operatorAddress); var operator = await Operator.at(operatorAddress);
var contributors = await Contributors.at(contributorsAddress); var contributors = await Contributors.at(contributorsAddress);
let recipientAddress = process.argv[4]; let recipientAddress = await promptly.prompt('Contributor address: ');
if(!recipientAddress) { let ipfsHash = await promptly.prompt('IPFS hash (blank for default): ', { default: 'QmQNA1hhVyL1Vm6HiRxXe9xmc6LUMBDyiNMVgsjThtyevs' });
console.log('please provide an address');
process.exit();
}
let ipfsHash = process.argv[5] || 'QmQNA1hhVyL1Vm6HiRxXe9xmc6LUMBDyiNMVgsjThtyevs';
let multihash = getBytes32FromMultiash(ipfsHash); let multihash = getBytes32FromMultiash(ipfsHash);
let contributorId = await contributors.getContributorIdByAddress(recipientAddress); let contributorId = await contributors.getContributorIdByAddress(recipientAddress);

View File

@ -18,14 +18,16 @@ files.forEach((fileName) => {
let abiFile = path.join(abisPath, `${fileName}.json`); let abiFile = path.join(abisPath, `${fileName}.json`);
fs.writeFileSync(abiFile, JSON.stringify(file.abi)); fs.writeFileSync(abiFile, JSON.stringify(file.abi));
let addresseFile = path.join(addressesPath, `${fileName}.json`); if (fileName === 'Registry') {
let addresses = Object.keys(file.networks) let addresseFile = path.join(addressesPath, `${fileName}.json`);
.reduce((addresses, key) => { let content = fs.readFileSync(addresseFile);
addresses[key] = file.networks[key].address; let addresses = Object.keys(file.networks)
return addresses; .reduce((addresses, key) => {
}, {}); addresses[key] = file.networks[key].address;
fs.writeFileSync(addresseFile, JSON.stringify(addresses)); return addresses;
}, JSON.parse(content));
let indexFile = path.join(libPath, 'index.js'); fs.writeFileSync(addresseFile, JSON.stringify(addresses));
fs.writeFileSync(indexFile, `module.exports = ${JSON.stringify(files)};`); }
}); });
console.log("Don't forget to reaload the JSON files from your application; i.e. restart kredits-web");

View File

@ -1,39 +1,45 @@
const REPL = require('repl'); const REPL = require('repl');
const promptly = require('promptly');
const ethers = require('ethers');
const Kredits = require('../lib/kredits');
module.exports = function(callback) { module.exports = function(callback) {
const Registry = artifacts.require('./Registry.sol'); const Registry = artifacts.require('./Registry.sol');
Registry.deployed().then(async (registry) => { Registry.deployed().then(async (registry) => {
let contractName = process.argv[4]; let contractName = await promptly.prompt('Contract Name: ');
let method = process.argv[5]; let method = await promptly.prompt('Function: ');
let args = process.argv.slice(6); let argumentInput = await promptly.prompt('Arguments (comma separated): ', { default: '' });
let args = [];
if (!contractName) { if (argumentInput !== '') {
console.log("Usage:"); args = argumentInput.split(',').map(a => a.trim());
console.log(" truffle exec scripts/cli.js <Contract name> <method to call> [<optional> <arguments>]");
callback();
return;
} }
let contractAddress = await registry.getProxyFor(contractName); const networkId = parseInt(web3.version.network);
console.log(`Using ${contractName} at ${contractAddress}`); const provider = new ethers.providers.Web3Provider(
let contract = await artifacts.require(`./${contractName}`).at(contractAddress); web3.currentProvider, { chainId: networkId }
);
const kredits = await Kredits.setup(provider, provider.getSigner());
const contract = kredits[contractName].contract;
console.log(`Using ${contractName} at ${contract.address}`);
console.log(`Calling ${method} with ${JSON.stringify(args)}`);
if (!contract[method]) { if (!contract[method]) {
callback(new Error(`Method ${method} is not defined on ${contractName}`)); callback(new Error(`Method ${method} is not defined on ${contractName}`));
return; return;
} }
console.log(`Calling ${method} with ${JSON.stringify(args)}`);
contract[method](...args).then((result) => { contract[method](...args).then((result) => {
console.log("\nResult:"); console.log("\nResult:");
console.log(result); console.log(result);
console.log("\nStartig a REPL. (type .exit to exit)"); console.log("\nStartig a REPL. (type .exit to exit)");
console.log(`defined variables: result, ${contractName}, web3`); console.log(`defined variables: result, ${contractName}, kredis`);
let r = REPL.start(); let r = REPL.start();
r.context.result = result; r.context.result = result;
r.context[contractName] = contract; r.context[contractName] = contract;
r.context.web3 = web3; r.context.kredits = kredits;
r.on('exit', () => { r.on('exit', () => {
console.log('Bye'); console.log('Bye');

View File

@ -1,35 +0,0 @@
var bs58 = require('bs58');
function getBytes32FromMultiash(multihash) {
const decoded = bs58.decode(multihash);
return {
digest: `0x${decoded.slice(2).toString('hex')}`,
hashFunction: decoded[0],
size: decoded[1],
};
}
function getMultihashFromBytes32(multihash) {
const { digest, hashFunction, size } = multihash;
if (size === 0) return null;
// cut off leading "0x"
const hashBytes = Buffer.from(digest.slice(2), 'hex');
// prepend hashFunction and digest size
//const multihashBytes = new (hashBytes.constructor)(2 + hashBytes.length);
const multihashBytes = new Buffer(2 + hashBytes.length);
console.log(hashBytes.constructor);
multihashBytes[0] = hashFunction;
multihashBytes[1] = size;
multihashBytes.set(hashBytes, 2);
return bs58.encode(multihashBytes);
}
var m = getBytes32FromMultiash(process.argv[2]);
console.log(m)

View File

@ -1,34 +1,39 @@
const path = require('path'); const path = require('path');
const seeds = require(path.join(__dirname, '..', '/config/seeds.js')); const seeds = require(path.join(__dirname, '..', '/config/seeds.js'));
const IPFS = require('ipfs-api');
var ipfs = IPFS({host: 'localhost', port: '5001', protocol: 'http'}) const ethers = require('ethers');
const Kredits = require('../lib/kredits');
const each = require('async-each-series');
module.exports = function(callback) { module.exports = function(callback) {
const Registry = artifacts.require('./Registry.sol'); const Registry = artifacts.require('./Registry.sol');
const contracts = {}; Registry.deployed().then(async (registry) => {
Registry.deployed().then((registry) => {
Object.keys(seeds.contractCalls).forEach(async (contract) => {
var address = await registry.getProxyFor(contract);
console.log(`Using ${contract} at ${address}`);
contracts[contract] = await artifacts.require(contract).at(address);
Object.keys(seeds.contractCalls[contract]).forEach((method) => { const networkId = parseInt(web3.version.network);
seeds.contractCalls[contract][method].forEach((args) => { const provider = new ethers.providers.Web3Provider(
console.log(`[Sending] ${contract}.#${method}(${JSON.stringify(args)})`); web3.currentProvider, { chainId: networkId }
contracts[contract][method](...args).then((result) => { );
console.log(`[Result] ${contract}.${method}(${JSON.stringify(args)}) => ${result.tx}`); const kredits = await Kredits.setup(provider, provider.getSigner());
});
}); each(seeds.contractCalls, (call, next) => {
let [contractName, method, args] = call;
let contractWrapper = kredits[contractName];
let func;
if (contractWrapper[method]) {
func = contractWrapper[method];
} else {
func = contractWrapper.functions[method];
}
func.apply(contractWrapper, args).then((result) => {
console.log(`[OK] kredits.${contractName}.${method}(${JSON.stringify(args)}) => ${result.hash}`);
next();
}).catch((error) => {
console.log(`[FAILD] kredits.${contractName}.${method}(${JSON.stringify(args)})`);
callback(error)
}); });
}); }, () => { console.log("\nDone!") });
}); });
seeds.ipfsContent.forEach((content) => {
ipfs.add(new ipfs.Buffer(JSON.stringify(content))).then((result) => { console.log(`[IPFS] added ${result[0].hash}`) });
});
callback();
} }

View File

@ -1,12 +1,12 @@
const promptly = require('promptly');
module.exports = async function(callback) {
let recipient = await promptly.prompt('Recipient address: ');
let amount = await promptly.prompt('Amount: ', {default: '1'});
amount = parseInt(amount);
console.log(`sending ${amount} ETH from ${web3.eth.accounts[0]} to ${recipient}`);
module.exports = function(callback) {
let recipient = process.argv[4];
if (!recipient) {
console.log('Please provide a recipient address');
process.exit();
}
let amount = parseInt(process.argv[5]) || 1;
console.log(recipient);
web3.eth.sendTransaction({to: recipient, value: web3.toWei(amount), from: web3.eth.accounts[0]}, console.log); web3.eth.sendTransaction({to: recipient, value: web3.toWei(amount), from: web3.eth.accounts[0]}, console.log);
callback(); callback();

View File

@ -5,6 +5,11 @@ module.exports = {
host: "127.0.0.1", host: "127.0.0.1",
port: 7545, port: 7545,
network_id: "*" // Match any network id network_id: "*" // Match any network id
},
kovan: {
host: "127.0.0.1",
port: 8545,
network_id: "42"
} }
} }
}; };