Compare commits

...

25 Commits

Author SHA1 Message Date
db2e12c750 3.0.1 2018-05-03 13:20:06 +02:00
7dc75a19b5 Clean your room, kiddo. 2018-05-03 12:40:24 +02:00
926913da50 Update description 2018-05-03 12:40:16 +02:00
a045702937 Use normal Markdown extension
Hardly anyone uses `.mdown`, so let's use what everyone knows and looks
for.
2018-05-03 12:32:36 +02:00
b13b93122e 3.0.0 2018-05-03 12:04:10 +02:00
c23e7f2df8 Start with old npm package version 2018-05-03 12:03:19 +02:00
7260544838 Merge pull request #41 from 67P/feature/improve-scripts
Improve helper scripts
2018-04-29 09:43:22 +02:00
da2f951bdb Merge pull request #42 from 67P/feature/filter-contributors-by-account
Add filter and find by account function to contributors
2018-04-26 14:27:26 +00:00
017073018f Use more readable Array.every method instead of reduce 2018-04-26 15:35:32 +02:00
fe1fa2e881 Allow filter and find for contributors by account data 2018-04-26 14:51:18 +02:00
c367c9cf6b Merge pull request #44 from 67P/add-contributors-alias
Add Contributors alias for Contributor
2018-04-26 11:52:23 +00:00
52643da096 typo 2018-04-26 13:52:09 +02:00
ad5fe3ae77 Add Contributors alias for Contributor
Because the contract is named `Contributors` we alias to `Contributor` which will become the new contract name if we manage to change it.
2018-04-26 12:41:51 +02:00
418785059c Merge pull request #40 from 67P/feature/constructor-options
Constructor configuration options
2018-04-26 10:32:12 +00:00
89261d039a Fix contributor id lookup for proposal creation script 2018-04-26 02:42:31 +02:00
6949a5940a Merge pull request #26 from 67P/features/batch-voting
Add batch voting for proposals
2018-04-24 12:40:15 +00:00
ce5f5fb8d2 Merge branch 'master' into features/batch-voting 2018-04-24 12:39:16 +00:00
2e8d00bc2c Add a filter by account function to contributors
This allows to filter contributors by the account entries.
For example:

```js

Contributor.filterByAccount({site: 'github.com'}); // returns all
contributors with github account
Contributor.filterByAccount({site: 'github.com', username: 'bumi'});
// returns bumi

```
2018-04-23 16:29:44 +02:00
b1345b53f3 Add repl.js helper script
This is similar to the cli.js helper but only provides an initialized
`kredits` instance.
The cli.js is for executing contract functions
2018-04-23 16:24:40 +02:00
847f5a251c Allow funding accounts with the seeds script 2018-04-23 13:43:35 +02:00
62a5cefd1a Update readme to reflect helper script changes 2018-04-23 13:20:57 +02:00
6378df7075 cleanup 2018-04-23 12:11:26 +02:00
bd2af6ed72 Smarter cli script
It now allows you to list available functions and allows to call
functions on the wrapper or on the contract directly.
2018-04-23 12:10:20 +02:00
85032353ca Use kredits library in add-proposal script 2018-04-23 12:06:04 +02:00
e2e9cd5c3b Add batch voting for proposals
The batchVote function accepts an array of proposal ids and votes for
every one.
Normally arrays without fix length are problematic and gas usage can not be
estimated really well. So we need to see how that works or what other
pattern could be used.
2018-04-15 18:15:21 +02:00
14 changed files with 325 additions and 199 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
build
node_modules
.ganache-db
.ganache-db
.tm_properties

181
README.md Normal file
View File

@@ -0,0 +1,181 @@
# Kredits Contracts
This repository contains the Solidity smart contracts and JavaScript API
wrapper for [Kosmos Kredits](https://wiki.kosmos.org/Kredits).
It uses the [Truffle framework](http://truffleframework.com/) for some things.
## Development
### Installation
$ npm install
### Requirements
All requirements are defined in `package.json`.
Those can be installed globally for convenience:
* [truffle framework](http://truffleframework.com): `npm install -g truffle`
* [ganache](http://truffleframework.com/ganache): `npm install -g ganache-cli`
We use following solidity contract libraries:
* [Open Zeppelin](https://github.com/OpenZeppelin/zeppelin-solidity)
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.
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:
$ npm run ganache (which is: ganache-cli -p 7545 -i 100 --db=./.ganache-db -m kredits)
### Truffle console
Truffle comes with a simple REPL to interact with the Smart Contracts. Have a
look at the [documentation
here](http://truffleframework.com/docs/getting_started/console)
NOTE: There are promisses, have a look at the examples:
```javascript
Token.deployed().then(function(token) {
token.totalSupply.call().then(function(value) {
console.log(value.toString());
})
});
```
Also please be aware of the differences between web3.js 0.2x.x and
[1.x.x](https://web3js.readthedocs.io/en/1.0/) - [web3
repo](https://github.com/ethereum/web3.js/)
## Contract Deployment
Truffle uses migration scripts to deploy contract to various networks. Have a
look at the `migrations` folder for those. The Ethereum nodes for the
different networks need to be configured in `truffle.js`.
Run the truffle migration scripts:
$ truffle deploy
$ truffle deploy --network=<network config from truffle.js>
Truffle keeps track of already executed migration scripts. To reset the
migration use the `--reset` option
$ truffle migrate --reset
Migration scripts can also be run from within `truffle console` or `truffle
develop`
To initially bootstrap a local development chain in ganache you can use the
bootstrap script:
$ npm run bootstrap
## Helper scripts
`scripts/` contains some helper scripts to interact with the contracts from the
CLI. _At some point these should be moved into a real nice CLI._
To run these scripts use `truffle exec`. For example: `truffle exec
scripts/add-proposal.js`.
### cli.js
Call any function on any contract:
$ truffle exec scripts/cli.js
### repl.js
Similar to cli.js but only provides a REPL with an initialized `kredits`
instance.
$ truffle exec scripts/repl.js
### add-contributor.js
Adds a new core contributor, creates a proposal for the new contributor and
votes for that one.
$ truffle exec scripts/add-contributor.js
### add-proposal.js
Adds a new proposal for an existing contributor
$ truffle exec scripts/add-proposal.js
### send-funds.js
Sends funds to an address. Helpful in development mode to for example fund a
metamask account.
$ truffle exec scripts/send-funds.js
### seeds.js
Run seeds defined in `config/seeds.js`.
$ truffle exec scripts/seeds.js
or
$ npm run seeds
## Upgradeable contracts
Some of the contracts use upgradability ideas from
[zeppelinos](https://github.com/zeppelinos/labs) (see `contracts/upgradable`).
The basic idea is to have a Registry contract that knows about the current
implementations and a Proxy contract that uses `delegatecall` to call the
current implementation. That means the Proxy contract holds the storage and
the address of that one does not change but the actuall implemenation is
managed through the Registry.
To deploy a new version a new contract is deployed then the version is
registered (`addVersion()`) in the Registry and on the Proxy contract is
"upgraded" (`upgrade()`) to the new version.
The Registry knows about all the different contracts and implementations.
Versions are stored as uint and automatically incremented for every added
implementation.
### Example
Deployment is best done using the truffle deployer.
1. Setup
1. Deploy the Registry
2. Deploy the contract
3. Register the contract at the Registry:
`registry.addVersion('Token', Token.address)`
4. Create the Proxy:
`registry.createProxy('Token', 1)`
2. Update
1. Deploy a new Version of the contract
2. Register the new version at the Registry:
`registry.addVersion('Token', NewToken.address)`
3. Set the new implementation address on the Proxy contract:
`registry.upgrade('Token', 2)`
## Known Issues
When resetting ganache Metamask might have an invalid transaction nonce and
transactions get rejected. Nonces in Ethereum must be incrementing and have no
gap.
To solve this reset the metamask account (Account -> Settings -> Reset Account)

View File

@@ -1,153 +0,0 @@
# Kredits
This repository contains all the contracts for Kredits as a [truffle framework](http://truffleframework.com/) project.
## Development
### Installation
$ npm install
### Requirements
All requirements are defined in `package.json`.
Those can be installed globally for convenience:
* [truffle framework](http://truffleframework.com): `npm install -g truffle`
* [ganache](http://truffleframework.com/ganache): `npm install -g ganache-cli`
We use following solidity contract libraries:
* [Open Zeppelin](https://github.com/OpenZeppelin/zeppelin-solidity)
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.
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:
$ npm run ganache (which is: ganache-cli -p 7545 -i 100 --db=./.ganache-db -m kredits)
### Truffle console
Truffle comes with a simple REPL to interact with the Smart Contracts. Have a look at the [documentation here](http://truffleframework.com/docs/getting_started/console)
NOTE: There are promisses, have a look at the examples:
```javascript
Token.deployed().then(function(token) {
token.totalSupply.call().then(function(value) {
console.log(value.toString());
})
});
```
Also please be aware of the differences between web3.js 0.2x.x and [1.x.x](https://web3js.readthedocs.io/en/1.0/) - [web3 repo](https://github.com/ethereum/web3.js/)
## Contract Deployment
Truffle uses migration scripts to deploy contract to various networks. Have a look at the `migrations` folder for those.
The Ethereum nodes for the different networks need to be configured in `truffle.js`.
Run the truffle migration scripts:
$ truffle deploy
$ truffle deploy --network=<network config from truffle.js>
Truffle keeps track of already executed migration scripts. To reset the migration use the `--reset` option
$ truffle migrate --reset
Migration scripts can also be run from within `truffle console` or `truffle develop`
To initially bootstrap a local development chain in ganache you can use the bootstrap script:
$ npm run bootstrap (= `truffle migrate --reset && truffle exec scripts/seeds.js && npm run build-json`)
## Helper scripts
`scripts/` contains some helper scripts to interact with the contracts from the CLI.
At some point these should be moved into a real nice CLI.
To run these scripts use `truffle exec`. For example: `truffle exec scripts/add-proposal.js`
### cli.js
Call any function on any contract:
$ truffle exec scripts/cli.js <Contract Name> <Function> [<optional> <arguments>]
For example:
$ truffle exec scripts/cli.js Operator proposalsCount
Please note that the contract name and the function are case sensitive.
### add-contributor.js
Adds a new core contributor, creates a proposal for the new contributor and votes for that one.
$ truffle exec scripts/add-contributor.js <ethereum address> [<profile IPFS hash>]
### add-proposal.js
Adds a new proposal for an existing contributor
$ truffle exec scripts/add-proposal.js <ethereum address> [<proposal IPFS hash>]
### send-funds.js
Sends funds to an address. Helpful in development mode to for example fund a metamask account.
$ truffle exec scripts/send-funds.js <ethereum address>
### seeds.js
Run seeds defined in `config/seeds.js`.
$ truffle exec scripts/seeds.js
or
$ npm run seeds
## Upgradeable contracts
Some of the contracts use upgradability ideas from [zeppelinos](https://github.com/zeppelinos/labs) (see `contracts/upgradable`).
The basic idea is to have a Registry contract that knows about the current implementations and a Proxy contract that uses `delegatecall` to call the current implementation.
That means the Proxy contract holds the storage and the address of that one does not change but the actuall implemenation is managed through the Registry.
To deploy a new version a new contract is deployed then the version is registered (`addVersion()`) in the Registry and on the Proxy contract is "upgraded" (`upgrade()`) to the new version.
The Registry knows about all the different contracts and implementations. Versions are stored as uint and automatically incremented for every added implementation.
### Example:
Deployment is best done using the [truffle deployer]()
1. Setup
1. deploy the Registry
2. deploy the contract
3. register the contract at the Registry:
`registry.addVersion('Token', Token.address)`
4. create the Proxy:
`registry.createProxy('Token', 1)`
2. Update
1. deploy a new Version of the contract
2. register the new version at the Registry:
`registry.addVersion('Token', NewToken.address)`
3. set the new implementation address on the Proxy contract:
`registry.upgrade('Token', 2)`
## Known Issues
When resetting ganache Metamask might have an invalid transaction nonce and transactions get rejected.
Nonces in Ethereum must be incrementing and have no gap.
To solve this reset the metamask account (Account -> Settings -> Reset Account)

View File

@@ -6,4 +6,8 @@ let contractCalls = [
['Operator', 'addProposal', [{contributorId: 3, amount: 100, kind: 'code', description: 'hacks on kredits', url: '' }, {gasLimit: 350000}]],
['Operator', 'vote', ['1', {gasLimit: 250000}]]
];
module.exports = { contractCalls };
let funds = [
'0x7e8f313c56f809188313aa274fa67ee58c31515d',
'0xa502eb4021f3b9ab62f75b57a94e1cfbf81fd827'
];
module.exports = { contractCalls, funds };

View File

@@ -110,7 +110,14 @@ contract Operator is Upgradeable {
ProposalVoted(proposalId, voterId, p.votesCount);
}
function batchVote(uint256[] _proposalIds) public coreOnly {
for (uint256 i = 0; i < _proposalIds.length; i++) {
vote(_proposalIds[i]);
}
}
function executeProposal(uint proposalId) private {
var p = proposals[proposalId];
require(!p.executed);
require(p.votesCount >= p.votesNeeded);

View File

@@ -34,6 +34,30 @@ class Contributor extends Base {
});
}
filterByAccount(search) {
return this._byAccount(search, 'filter');
}
findByAccount(search) {
return this._byAccount(search, 'find');
}
_byAccount(search, method = 'filter') {
return this.all().then((contributors) => {
const searchEntries = Object.entries(search);
return contributors[method]((contributor) => {
if (!contributor.accounts) { return false; }
return contributor.accounts.find((account) => {
return searchEntries.every((item) => {
let [ key, value ] = item;
return account[key] === value;
});
});
});
});
}
add(contributorAttr, callOptions = {}) {
let json = ContributorSerializer.serialize(contributorAttr);
// TODO: validate against schema

View File

@@ -29,7 +29,7 @@ class Kredits {
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.addresses = addresses || { Registry: RegistryAddress[this.provider.chainId.toString()] }; // chainID must be a string
this.abis = abis || ABIS;
this.ipfs = new IPFS(ipfsConfig);
this.contracts = {};
@@ -63,6 +63,11 @@ class Kredits {
return this.contractFor('contributors');
}
get Contributors() {
console.log('Contributors is deprecated use Contributor instead');
return this.Contributor;
}
get Operator() {
return this.contractFor('operator');
}

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "kredits-contracts",
"version": "1.0.0",
"version": "3.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "kredits-contracts",
"version": "1.0.0",
"version": "3.0.1",
"description": "Ethereum contracts and npm wrapper for Kredits",
"main": "./lib/kredits.js",
"directories": {

View File

@@ -1,6 +1,5 @@
const Registry = artifacts.require('./Registry.sol');
const promptly = require('promptly');
const bs58 = require('bs58');
const ethers = require('ethers');
const Kredits = require('../lib/kredits');
@@ -37,7 +36,7 @@ module.exports = function(callback) {
console.log("\nAdding contributor:");
console.log(contributorAttributes);
kredits.Contributor.add(contributorAttributes, {gasLimit: 250000}).then((result) => {
kredits.Contributor.add(contributorAttributes, { gasLimit: 250000 }).then((result) => {
console.log("\n\nResult:");
console.log(result);
callback();

View File

@@ -1,38 +1,46 @@
const Registry = artifacts.require('./Registry.sol');
const Operator = artifacts.require('./Operator.sol');
const Contributors = artifacts.require('./Contributors.sol');
const promptly = require('promptly');
const 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],
};
}
const ethers = require('ethers');
const Kredits = require('../lib/kredits');
module.exports = function(callback) {
Registry.deployed().then(async (registry) => {
var operatorAddress = await registry.getProxyFor('Operator');
var contributorsAddress = await registry.getProxyFor('Contributors');
const networkId = parseInt(web3.version.network);
const provider = new ethers.providers.Web3Provider(
web3.currentProvider, { chainId: networkId }
);
const kredits = await Kredits.setup(provider, provider.getSigner());
var operator = await Operator.at(operatorAddress);
var contributors = await Contributors.at(contributorsAddress);
console.log(`Using operator at: ${kredits.Operator.contract.address}`);
let recipientAddress = await promptly.prompt('Contributor address: ');
let ipfsHash = await promptly.prompt('IPFS hash (blank for default): ', { default: 'QmQNA1hhVyL1Vm6HiRxXe9xmc6LUMBDyiNMVgsjThtyevs' });
let contributor = await promptly.prompt('Contributor (address or id): ');
let contributorId;
if (contributor.length < 5) {
contributorId = contributor;
} else {
contributorId = await kredits.Contributor.functions.getContributorIdByAddress(contributor);
}
console.log(`Creating a proposal for contributor ID #${contributorId}`);
let multihash = getBytes32FromMultiash(ipfsHash);
let contributionAttributes = {
contributorId,
amount: await promptly.prompt('Amount: '),
description: await promptly.prompt('Description: '),
kind: await promptly.prompt('Kind: ', { default: 'dev' }),
url: await promptly.prompt('URL: ', { default: '' })
}
let contributorId = await contributors.getContributorIdByAddress(recipientAddress);
console.log("\nAdding proposal:");
console.log(contributionAttributes);
let result = await operator.addProposal(contributorId.toNumber(), 23, multihash.digest, multihash.hashFunction, multihash.size);
console.log('Proposal added, tx: ', result.tx);
callback();
kredits.Operator.addProposal(contributionAttributes, { gasLimit: 300000 }).then((result) => {
console.log("\n\nResult:");
console.log(result);
callback();
}).catch((error) => {
console.log('Failed to create proposal');
console.log(error);
});
});
}

View File

@@ -7,30 +7,43 @@ const Kredits = require('../lib/kredits');
module.exports = function(callback) {
const Registry = artifacts.require('./Registry.sol');
Registry.deployed().then(async (registry) => {
let contractName = await promptly.prompt('Contract Name: ');
let method = await promptly.prompt('Function: ');
let argumentInput = await promptly.prompt('Arguments (comma separated): ', { default: '' });
let args = [];
if (argumentInput !== '') {
args = argumentInput.split(',').map(a => a.trim());
}
const networkId = parseInt(web3.version.network);
const provider = new ethers.providers.Web3Provider(
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)}`);
let contractName = await promptly.prompt('Contract Name: ');
const contractWrapper = kredits[contractName];
if (!contract[method]) {
let method;
method = await promptly.prompt('Function (? for available functions): ');
while (method === '?') {
console.log(`Contract functions: ${JSON.stringify(Object.keys(contractWrapper.functions))}`);
console.log(`\nWrapper functions: ${JSON.stringify(Object.getOwnPropertyNames(Object.getPrototypeOf(contractWrapper)))}`);
console.log("\n");
method = await promptly.prompt('Function: ');
}
if (!contractWrapper[method] && !contractWrapper.functions[method]) {
callback(new Error(`Method ${method} is not defined on ${contractName}`));
return;
}
let argumentInput = await promptly.prompt('Arguments (comma separated): ', { default: '' });
let args = [];
if (argumentInput !== '') {
args = argumentInput.split(',').map(a => a.trim());
}
console.log(`Using ${contractName} at ${contractWrapper.contract.address}`);
console.log(`Calling ${method} with ${JSON.stringify(args)}`);
contract[method](...args).then((result) => {
let func;
if (contractWrapper[method]) {
func = contractWrapper[method];
} else {
func = contractWrapper.functions[method];
}
func.apply(contractWrapper, args).then((result) => {
console.log("\nResult:");
console.log(result);
@@ -38,7 +51,7 @@ module.exports = function(callback) {
console.log(`defined variables: result, ${contractName}, kredis`);
let r = REPL.start();
r.context.result = result;
r.context[contractName] = contract;
r.context[contractName] = contractWrapper;
r.context.kredits = kredits;
r.on('exit', () => {

25
scripts/repl.js Normal file
View File

@@ -0,0 +1,25 @@
const REPL = require('repl');
const promptly = require('promptly');
const ethers = require('ethers');
const Kredits = require('../lib/kredits');
module.exports = function(callback) {
const Registry = artifacts.require('./Registry.sol');
Registry.deployed().then(async (registry) => {
const networkId = parseInt(web3.version.network);
const provider = new ethers.providers.Web3Provider(
web3.currentProvider, { chainId: networkId }
);
const kredits = await Kredits.setup(provider, provider.getSigner());
console.log(`defined variables: kredits, web3`);
let r = REPL.start();
r.context.kredits = kredits;
r.context.web3 = web3;
r.on('exit', () => {
console.log('Bye');
callback();
});
});
}

View File

@@ -17,6 +17,18 @@ module.exports = function(callback) {
);
const kredits = await Kredits.setup(provider, provider.getSigner());
let fundingAmount = 2;
each(seeds.funds, (address, next) => {
console.log(`funding ${address} with 2 ETH`);
web3.eth.sendTransaction({
to: address,
value: web3.toWei(fundingAmount),
from: web3.eth.accounts[0]
},
(result) => { next(); }
)
});
each(seeds.contractCalls, (call, next) => {
let [contractName, method, args] = call;
let contractWrapper = kredits[contractName];