Compare commits

...

80 Commits

Author SHA1 Message Date
2af9476a3f Merge branch 'master' into fix/ipfs-config
# Conflicts:
#	lib/utils/ipfs.js
#	lib/utils/preflight.js
2019-04-24 20:33:15 +02:00
d643e5842c Merge pull request #97 from 67P/chore/linter
Add linter and travis.yml
2019-04-24 18:28:32 +00:00
a87d5326bf Oh my package-lock 2019-04-24 20:17:03 +02:00
c9fed46054 Add contract test linting 2019-04-24 19:56:37 +02:00
6f2097ed46 Fix regex 2019-04-24 19:39:30 +02:00
94d342ce63 Replace console.log by deprecate function 2019-04-24 19:31:26 +02:00
f984dec95a Style fixes 2019-04-24 19:31:04 +02:00
145b3ea766 Merge branch 'master' into chore/linter
# Conflicts:
#	lib/contracts/contribution.js
#	lib/contracts/contributor.js
#	lib/contracts/proposal.js
#	lib/kredits.js
#	lib/serializers/contributor.js
#	yarn.lock
2019-04-24 19:12:18 +02:00
a80dfee5c5 Merge pull request #116 from 67P/deployments/balance-update
Release/deployment notes for latest contract updates on Rinkeby
2019-04-24 17:03:48 +01:00
4649de24c8 Deploy notes for latest contributor and contribution deployment 2019-04-24 17:32:58 +02:00
8539b56a48 5.3.0 2019-04-24 14:22:53 +01:00
f59c37827a Merge pull request #108 from 67P/feature/kredits-formatter
Add kredits-formatter and additionally format balance
2019-04-24 14:20:44 +01:00
58db57ee45 Merge branch 'master' into feature/kredits-formatter 2019-04-24 13:10:48 +00:00
b64a7ca299 Merge pull request #113 from 67P/feature/gitea_site
Add Gitea site/username to serializer and seeds
2019-04-24 12:58:44 +00:00
6f09ca8d13 Merge pull request #114 from 67P/chore/scripts
Improve/fix scripts
2019-04-24 12:57:43 +00:00
6f53c8097e Wrap anything that could break in the try/catch block 2019-04-24 14:55:35 +02:00
6b2ac15f56 Improve error handling for list-contributors script 2019-04-24 13:46:27 +01:00
359989f235 Fix function name for total kredits issued/earned 2019-04-24 13:45:56 +01:00
5894f6323b Add Gitea username to contributor seeds 2019-04-24 13:43:46 +01:00
09b78e1e8f Add Gitea site/username to contributor serializer 2019-04-23 20:28:56 +01:00
48c8f6b9b3 Merge branch 'master' into feature/kredits-formatter 2019-04-23 12:56:55 +00:00
542ebaf3f3 5.2.0 2019-04-23 13:52:46 +01:00
d82ffba75e Update ABI 2019-04-23 13:52:37 +01:00
bdb2cee0c4 Merge pull request #110 from 67P/feature/contribution-balances
Add contract calls for total contribution balances
2019-04-23 13:47:50 +01:00
cd45ce260f Merge pull request #112 from 67P/feature/update-contributor-profile
Update contributor profile
2019-04-20 09:22:31 +00:00
3a97983540 Merge pull request #111 from 67P/feature/contributor-schema-validation
Add Contributor validation against JSON schema
2019-04-20 08:47:49 +01:00
9714926e11 Add function to update contributor profile to wrapper
The function updates the contributor profile,
adds the new profile to IPFS and does the contract call to update
the profile hash
2019-04-20 02:17:38 +02:00
59614201b5 Ups... 2019-04-20 02:15:53 +02:00
e591742e40 Add Contributor validation against JSON schema
based on the Contribution validation.
2019-04-20 01:10:46 +02:00
791190f5e7 Cleanup promise call 2019-04-20 00:30:20 +02:00
24b66daf2a Update ABI 2019-04-19 14:16:40 +02:00
ad034d7712 Naming is hard 2019-04-19 14:15:30 +02:00
375d8f3275 Cleanup 2019-04-19 13:34:39 +02:00
80dc787971 Merge pull request #109 from 67P/dev/list_contributors_error_handling
Handle errors in contributor list script
2019-04-19 11:22:38 +00:00
7967dc26f2 Update ABIs 2019-04-19 13:20:25 +02:00
c248725cc1 Add balances to getContributorById call 2019-04-19 13:19:55 +02:00
59135bf312 Add contract calls for total contribution balances
Those methods return the total kredits amounts of contribtions.
2019-04-19 13:06:36 +02:00
1d771c43e8 Handle errors in contributor list script 2019-04-19 12:02:28 +01:00
3cb94fb660 Add kredits-formatter and additionally format balance
This adds a `balanceInt` value to the contributor data that has the
token balance formatted as full Kredits. (similar to Ether)
2019-04-19 11:57:35 +02:00
7a6b117876 Cleanup preflight 2019-04-18 00:41:21 +02:00
f945b378e2 Add ipfs.config getters 2019-04-18 00:41:05 +02:00
5820d71b2c Merge pull request #96 from 67P/feature/pagination
Add pagination for .all methods
2019-04-17 13:53:17 +00:00
4771c0c8a0 Merge branch 'master' into feature/pagination 2019-04-17 13:52:53 +00:00
2a1ae117e8 5.1.1 2019-04-16 12:50:07 +01:00
8abc7ba77f Merge pull request #104 from 67P/chore/update-kosmos-schema
Update kosmos-schema dependency
2019-04-16 11:19:03 +00:00
f696721918 Readme: add note about locally running out of funds 2019-04-16 13:17:54 +02:00
00b58dec66 Update kosmos-schema dependency 2019-04-16 13:16:05 +02:00
560315cbca Merge pull request #102 from 67P/chore/add-yarn-support
Add yarn lock files
2019-04-15 15:54:38 +00:00
24933f31a7 Add yarn lock files 2019-04-15 17:34:48 +02:00
b13bf6e8b0 Readme: add note about locally running out of funds 2019-04-15 15:59:54 +02:00
8dcad88372 Merge pull request #99 from 67P/update-seeds
Update seeds
2019-04-15 10:39:10 +00:00
dc6d2716aa Update seeds 2019-04-15 12:33:51 +02:00
80ad9db630 5.1.0 2019-04-13 12:41:49 +01:00
6f97c905d6 Add count property and DRY 2019-04-13 13:05:47 +02:00
1b5f7ff95d Merge pull request #93 from 67P/deployments/weltempfaenger-release
Production release to rinkeby
2019-04-13 11:07:58 +01:00
b5b12e22ce Readd solhint:default 2019-04-13 01:53:52 +02:00
fe811cdb12 Use solhint:recommended 2019-04-13 01:17:38 +02:00
42df6fe310 Fix solhint invocation 2019-04-13 01:10:40 +02:00
c8805be054 Switch to yarn 2019-04-13 00:19:52 +02:00
764f63fc8c Trigger build 2019-04-13 00:14:23 +02:00
f73ccedf42 Activate linting on travis 2019-04-13 00:10:21 +02:00
a9093c1c40 Fix linter errors
# Conflicts:
#	lib/contracts/contribution.js
#	lib/contracts/contributor.js
#	lib/contracts/proposal.js
#	lib/kredits.js
#	lib/utils/pagination.js
2019-04-13 00:08:12 +02:00
e405644b1d Add eslint and solhint 2019-04-13 00:03:16 +02:00
f6189bf910 Add pagination for .all methods
I removed rsvp as a dependency as we only use Promise.all
2019-04-12 21:21:19 +02:00
b6c06c289c Merge pull request #95 from 67P/cosmetics
Cosmetic cleanups
2019-04-12 18:55:59 +00:00
f405e39c04 Merge pull request #94 from 67P/bugfix/schema_changes
Refactor contribution serializer and validation
2019-04-12 18:54:56 +00:00
1dbf3b5742 Cosmetic cleanups 2019-04-12 20:16:24 +02:00
952b5153fd Validate proposed contributions 2019-04-11 13:46:42 +02:00
d953141f52 Refactor contribution serializer and validation 2019-04-11 13:05:40 +02:00
aa57d7c70b Adjust seeds for new requirements 2019-04-11 11:02:15 +02:00
4c0bb879e8 Show IPFS hash in list script output 2019-04-11 11:01:45 +02:00
9c5a517fb9 5.0.0 2019-04-11 08:45:49 +02:00
b00ddda312 Merge pull request #92 from 67P/feature/contribution_schema
Validate contribution docs against schema
2019-04-11 08:40:38 +02:00
130e2a7797 Validate contribution docs against schema
Currently requires an open PR branch for the schemas, which is adding
date and time for contributions.

refs #30
2019-04-10 18:37:02 +02:00
27e5190e29 Production release to rinkeby
Deployed all apps and created a new Kredits DAO
2019-04-10 18:28:24 +02:00
b9c171884b Merge pull request #91 from 67P/feature/store-balance-as-wei
Store kredits tokens as wei with 18 decimals
2019-04-10 15:51:36 +02:00
4d2e0ea84b Update Token symbol 2019-04-10 15:50:33 +02:00
26d12ba239 Store kredits tokens as wei with 18 decimals
This stores the tokens with 18 decimals as most other tokens and as
assumed by a potential future token standard EIP 777.
2019-04-10 13:59:00 +02:00
1aae62e139 Merge pull request #88 from 67P/bugfix/confirmed-contributions
Fix confirmed contribution check
2019-04-09 22:36:37 +02:00
f21c4cf9de Fix confirmed contribution check
If the blocknumber is higher or equal the contribution.confirmedAt block
number then the contribution is confirmed and can be claimed.
2019-04-09 22:30:17 +02:00
56 changed files with 43109 additions and 378 deletions

32
.eslintrc.js Normal file
View File

@@ -0,0 +1,32 @@
module.exports = {
'env': {
'browser': true,
'es6': true,
'node': true
},
'extends': 'eslint:recommended',
'globals': {
'Atomics': 'readonly',
'SharedArrayBuffer': 'readonly'
},
'parserOptions': {
'ecmaVersion': 2018,
'sourceType': 'module'
},
'rules': {
'comma-dangle': ['error', {
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'never',
exports: 'never',
functions: 'ignore',
}],
'eol-last': ['error', 'always'],
semi: ['error', 'always'],
'space-before-function-paren': ['error', {
anonymous: 'never',
named: 'always',
asyncArrow: 'always',
}],
}
}

2
.gitignore vendored
View File

@@ -4,3 +4,5 @@ node_modules
**/node_modules **/node_modules
.ganache-db .ganache-db
.tm_properties .tm_properties
yarn-error.log
.DS_Store

9
.solhint.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": [
"solhint:default",
"solhint:recommended"
],
"rules": {
"indent": "2"
}
}

26
.travis.yml Normal file
View File

@@ -0,0 +1,26 @@
---
language: node_js
node_js:
- "11"
sudo: false
dist: xenial
cache:
yarn: true
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash
- export PATH=$HOME/.yarn/bin:$PATH
install:
- yarn install --no-lockfile --non-interactive
script:
- yarn lint:wrapper
- yarn lint:contract-tests
# - yarn lint:contracts
branches:
only:
- master

View File

@@ -36,7 +36,7 @@ development ganache.
$ npm run devchain (or aragon devchain --port 7545) $ npm run devchain (or aragon devchain --port 7545)
To clear/reset the chain use: To clear/reset the chain use (e.g. if you run out of funds on your devchain)
$ npm run devchain -- --reset (or aragon devchain --port 7545 --reset) $ npm run devchain -- --reset (or aragon devchain --port 7545 --reset)
@@ -79,9 +79,9 @@ Kredits DAO independently.
![](docs/kredits-diagram.png) ![](docs/kredits-diagram.png)
A DAO can be deployed using the `scripts/deploy-kit.js` script or with the `npm A DAO can be deployed using the `scripts/deploy-kit.js` script or with the
run deploy:dao` command. This deploys a new Kredits DAO, installs the latest `npm run deploy:dao` command. This deploys a new Kredits DAO, installs the
app versions and sets the required permissions. latest app versions and sets the required permissions.
See each app in `/apps/*` for details. See each app in `/apps/*` for details.
@@ -115,11 +115,11 @@ Script to add a new entries to the contracts using the JS wrapper
$ truffle exec scripts/add-{contributor, contribution, proposal}.js $ truffle exec scripts/add-{contributor, contribution, proposal}.js
### list-{contributor, contribution, proposal}.js ### list-{contributors, contributions, proposals}.js
List contract entries List contract entries
$ truffle exec scripts/list-{contributor, contribution, proposal}.js $ truffle exec scripts/list-{contributors, contributions, proposals}.js
### send-funds.js ### send-funds.js
@@ -152,7 +152,7 @@ Deploys a new KreditsKit that allows to create a new DAO
or or
$ npm run deploy:kit $ npm run deploy:kit
`ENS` address is required as environment variable. `ENS` address is required as environment variable.
`DAO_FACTORY` can optionally be set as environment variable. (see aragon) `DAO_FACTORY` can optionally be set as environment variable. (see aragon)
### new-dao.js ### new-dao.js
@@ -174,6 +174,26 @@ Runs `npm install` for each app and publishes a new version.
or or
$ npm run deploy:apps $ npm run deploy:apps
## Deployment
### Apps deployment
To deploy a new app version run:
$ aragon apm publish major --environment=NETWORK_TO_DEPLOY
### KreditsKit
deploy the KreditsKit as Kit to create new DAOs
$ truffle exec scripts/deploy-kit.js --network=NETWORK_TO_DEPLOY
### Creating a new DAO
make sure all apps and the KreditsKit are deployed, then create a new DAO:
$ truffle exec scripts/new-dao.js --network=NETWORK_TO_DEPLOY
## ACL / Permissions ## ACL / Permissions
## Upgradeable contracts ## Upgradeable contracts

12
apps/.eslintrc.js Normal file
View File

@@ -0,0 +1,12 @@
module.exports = {
'globals': {
contract: true,
describe: true,
it: true,
},
rules: {
'no-unused-vars': ['error', {
'argsIgnorePattern': '^_',
}],
}
}

View File

@@ -118,6 +118,26 @@ contract Contribution is AragonApp {
// Custom functions // Custom functions
// //
function totalKreditsEarned(bool confirmedOnly) public view returns (uint256 count) {
for (uint32 i = 1; i <= contributionsCount; i++) {
ContributionData memory c = contributions[i];
if (block.number >= c.confirmedAtBlock || !confirmedOnly) {
count += c.amount; // should use safemath
}
}
}
function totalKreditsEarnedByContributor(uint32 contributorId, bool confirmedOnly) public view returns (uint256 count) {
uint256 tokenBalance = ownedContributions[contributorId].length;
for (uint256 i = 0; i < tokenBalance; i++) {
uint32 cId = ownedContributions[contributorId][i];
ContributionData memory c = contributions[cId];
if (block.number >= c.confirmedAtBlock || !confirmedOnly) {
count += c.amount; // should use safemath
}
}
}
function getContribution(uint32 contributionId) public view returns (uint32 id, uint32 contributorId, uint32 amount, bool claimed, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint256 confirmedAtBlock, bool exists, bool vetoed) { function getContribution(uint32 contributionId) public view returns (uint32 id, uint32 contributorId, uint32 amount, bool claimed, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint256 confirmedAtBlock, bool exists, bool vetoed) {
id = contributionId; id = contributionId;
ContributionData storage c = contributions[id]; ContributionData storage c = contributions[id];

View File

@@ -11,10 +11,10 @@
"start": "npm run start:aragon:ipfs", "start": "npm run start:aragon:ipfs",
"start:aragon:ipfs": "aragon run", "start:aragon:ipfs": "aragon run",
"start:aragon:http": "aragon run --http localhost:8001 --http-served-from ./dist", "start:aragon:http": "aragon run --http localhost:8001 --http-served-from ./dist",
"start:app": "npm run sync-assets && npm run build:script -- --no-minify && parcel serve app/index.html -p 8001 --out-dir dist/ --no-cache", "start:app": "",
"test": "aragon contracts test", "test": "aragon contracts test",
"compile": "aragon contracts compile", "compile": "aragon contracts compile",
"sync-assets": "copy-aragon-ui-assets -n aragon-ui ./dist", "sync-assets": "",
"build:app": "", "build:app": "",
"build:script": "", "build:script": "",
"build": "", "build": "",

View File

@@ -1,5 +1,5 @@
const CounterApp = artifacts.require('Contribution.sol') // const Contribution = artifacts.require('Contribution.sol');
contract('Contribution', (accounts) => { contract('Contribution', (_accounts) => {
it('should be tested') it('should be tested');
}) });

8145
apps/contribution/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,10 @@ import "@aragon/os/contracts/kernel/IKernel.sol";
interface ITokenBalance { interface ITokenBalance {
function balanceOf(address contributorAccount) public view returns (uint256); function balanceOf(address contributorAccount) public view returns (uint256);
} }
interface IContributionBalance {
function totalKreditsEarnedByContributor(uint32 contributorId, bool confirmedOnly) public view returns (uint256 count);
function balanceOf(address owner) public view returns (uint256);
}
contract Contributor is AragonApp { contract Contributor is AragonApp {
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb; bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
@@ -43,6 +47,12 @@ contract Contributor is AragonApp {
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[uint8(Apps.Token)]); return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[uint8(Apps.Token)]);
} }
function getContributionContract() public view returns (address) {
IKernel k = IKernel(kernel());
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[uint8(Apps.Contribution)]);
}
function coreContributorsCount() view public returns (uint32) { function coreContributorsCount() view public returns (uint32) {
uint32 count = 0; uint32 count = 0;
for (uint32 i = 1; i <= contributorsCount; i++) { for (uint32 i = 1; i <= contributorsCount; i++) {
@@ -118,7 +128,7 @@ contract Contributor is AragonApp {
return contributors[id]; return contributors[id];
} }
function getContributorById(uint32 _id) public view returns (uint32 id, address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, bool isCore, uint256 balance, bool exists ) { function getContributorById(uint32 _id) public view returns (uint32 id, address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, bool isCore, uint256 balance, uint256 totalKreditsEarned, uint256 contributionsCount, bool exists ) {
id = _id; id = _id;
Contributor storage c = contributors[_id]; Contributor storage c = contributors[_id];
account = c.account; account = c.account;
@@ -128,6 +138,9 @@ contract Contributor is AragonApp {
isCore = isCoreTeam(id); isCore = isCoreTeam(id);
address token = getTokenContract(); address token = getTokenContract();
balance = ITokenBalance(token).balanceOf(c.account); balance = ITokenBalance(token).balanceOf(c.account);
address contribution = getContributionContract();
totalKreditsEarned = IContributionBalance(contribution).totalKreditsEarnedByContributor(_id, true);
contributionsCount = IContributionBalance(contribution).balanceOf(c.account);
exists = c.exists; exists = c.exists;
} }

View File

@@ -1,5 +1,5 @@
const CounterApp = artifacts.require('CounterApp.sol') // const Contributor = artifacts.require('Contributor.sol');
contract('CounterApp', (accounts) => { contract('Contributor', (_accounts) => {
it('should be tested') it('should be tested');
}) });

8145
apps/contributor/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
{ {
"name": "Proposal", "name": "Proposal",
"description": "Kredits proposal app" "description": "Kredits Proposal app"
} }

View File

@@ -0,0 +1,5 @@
// const Proposal = artifacts.require('Proposal.sol');
contract('Proposal', (_accounts) => {
it('should be tested');
});

8145
apps/proposal/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -14,11 +14,15 @@ contract Token is ERC20Token, AragonApp {
function initialize(bytes32[4] _appIds) public onlyInit { function initialize(bytes32[4] _appIds) public onlyInit {
appIds = _appIds; appIds = _appIds;
name = 'Kredits';
symbol = '₭S';
decimals = 18;
initialized(); initialized();
} }
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) public isInitialized auth(MINT_TOKEN_ROLE) { function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) public isInitialized auth(MINT_TOKEN_ROLE) {
_mint(contributorAccount, amount); uint256 amountInWei = amount.mul(1 ether);
_mint(contributorAccount, amountInWei);
emit LogMint(contributorAccount, amount, contributionId); emit LogMint(contributorAccount, amount, contributionId);
} }

View File

@@ -1,4 +1,4 @@
{ {
"name": "Token", "name": "Token",
"description": "Kredits token app" "description": "Kredits Token app"
} }

View File

@@ -1,5 +1,5 @@
const CounterApp = artifacts.require('CounterApp.sol') // const Token = artifacts.require('Token.sol');
contract('CounterApp', (accounts) => { contract('Token', (_accounts) => {
it('should be tested') it('should be tested');
}) });

8145
apps/token/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,48 @@
const contractCalls = [ const contractCalls = [
['Contributor', 'add', [{ account: '0x7e8f313c56f809188313aa274fa67ee58c31515d', name: 'bumi', isCore: true, kind: 'person', url: '', github_username: 'bumi', github_uid: 318, wiki_username: 'bumi' }, { gasLimit: 200000 }]], ['Contributor', 'add', [{
['Contributor', 'add', [{ account: '0x49575f3DD9a0d60aE661BC992f72D837A77f05Bc', name: 'raucao', isCore: true, kind: 'person', url: '', github_username: 'skddc', github_uid: 842, wiki_username: 'raucau' }, { gasLimit: 200000 }]], account: '0x7e8f313c56f809188313aa274fa67ee58c31515d',
['Proposal', 'addProposal', [{ contributorId: 1, amount: 500, kind: 'code', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]], name: 'bumi',
['Proposal', 'addProposal', [{ contributorId: 2, amount: 500, kind: 'code', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]], kind: 'person',
['Proposal', 'addProposal', [{ contributorId: 2, amount: 500, kind: 'code', description: '[67P/kredits-contracts] Hacked on kredits', url: '' }, { gasLimit: 350000 }]], url: '',
github_username: 'bumi',
github_uid: 318,
gitea_username: 'bumi',
wiki_username: 'Bumi'
}, { gasLimit: 200000 }]],
['Contributor', 'add', [{
account: '0x49575f3DD9a0d60aE661BC992f72D837A77f05Bc',
name: 'raucao',
kind: 'person',
url: '',
github_username: 'skddc',
github_uid: 842,
gitea_username: 'raucao',
wiki_username: 'Basti'
}, { gasLimit: 200000 }]],
['Contributor', 'add', [{
account: '0xF722709ECC3B05c19d02E82a2a4A4021B8F48C62',
name: 'Manuel',
kind: 'person',
url: '',
github_username: 'fsmanuel',
github_uid: 54812,
wiki_username: 'Manuel'
}, { gasLimit: 200000 }]],
['Proposal', 'addProposal', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-09', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]],
['Proposal', 'addProposal', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-10', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]],
['Proposal', 'addProposal', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Hacked on kredits', url: '' }, { gasLimit: 350000 }]],
['Proposal', 'vote', [1, { gasLimit: 550000 }]], ['Proposal', 'vote', [1, { gasLimit: 550000 }]],
['Contribution', 'addContribution', [{ contributorId: 1, amount: 5000, kind: 'dev', description: '[67P/kredits-contracts] Introduce contribution token', url: '' }, { gasLimit: 350000 }]], ['Contribution', 'addContribution', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-11', amount: 5000, kind: 'dev', description: '[67P/kredits-contracts] Introduce contribution token', url: '' }, { gasLimit: 350000 }]],
['Contribution', 'addContribution', [{ contributorId: 2, amount: 1500, kind: 'dev', description: '[67P/kredits-web] Reviewed stuff', url: '' }, { gasLimit: 350000 }]], ['Contribution', 'addContribution', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 1500, kind: 'dev', description: '[67P/kredits-web] Reviewed stuff', url: '' }, { gasLimit: 350000 }]],
['Contribution', 'claim', [1, { gasLimit: 300000 }]] ['Contribution', 'claim', [1, { gasLimit: 300000 }]]
]; ];
const funds = [ const funds = [
'0x7e8f313c56f809188313aa274fa67ee58c31515d', '0x7e8f313c56f809188313aa274fa67ee58c31515d',
'0xa502eb4021f3b9ab62f75b57a94e1cfbf81fd827' '0xa502eb4021f3b9ab62f75b57a94e1cfbf81fd827'
]; ];
module.exports = { contractCalls, funds }; module.exports = { contractCalls, funds };

View File

@@ -1,5 +1,22 @@
# Contribution deployments # Contribution deployments
aragon apm publish major --environment=rinkeby"
## 2019-04-24 update balances
✔ Successfully published kredits-contribution.open.aragonpm.eth v6.0.0:
Contract address: 0x2c083EEA83fd3a99C93759D97D0317A43261c758
Content (ipfs): QmULpSqz7BgTFmDu8AL7YZZEz525xkcEzf3dPKtbRdUtFs
Transaction hash: 0x8b01c4c00162e918659d267a2beaf33b578e2aaf9f427f1aa9a43029333c5cd7
## 2019-04-10 - Weltempfänger release
✔ Successfully published kredits-contribution.open.aragonpm.eth v5.0.0:
Contract address: 0xe0f7dB486321b917e3A986Bdb2F2b9d51BA98fa9
Content (ipfs): QmU3XEBb4f5jU8MFFEpwaa95C1mhc82UeYLRWLrKsvcQNw
Transaction hash: 0xd736ff5f79f8142be3fad1a50580fb40aa468838da397f8630285fd91a445af3
## 2019-04-04 ## 2019-04-04
✔ Successfully published kredits-contribution.open.aragonpm.eth v4.0.0: ✔ Successfully published kredits-contribution.open.aragonpm.eth v4.0.0:

View File

@@ -1,5 +1,23 @@
# Contributor deployments # Contributor deployments
aragon apm publish major --environment=rinkeby
## 2019-04-29 update balances
✔ Successfully published kredits-contributor.open.aragonpm.eth v5.0.0:
Contract address: 0xadefa3b66b68a127Fe38bEa1813b844EE69CFD86
Content (ipfs): QmeygbQgoj2McLWzo9hJayLWuBZqFaK4HTpa5qLeQdkn5K
Transaction hash: 0x4237a9636f6e4a8190e0d5bcfa85a452da097bf654a173a88e0e1de3d078f08d
## 2019-04-10 - Weltempfänger release
✔ Successfully published kredits-contributor.open.aragonpm.eth v4.0.0:
Contract address: 0x08a6D4D915FCAA5524F05F5F715a6C17cB6eeA6B
Content (ipfs): QmR62PWwe1EzommfkhJDYcTvHoZjbXuv9dTG6vCn5dWCsb
Transaction hash: 0xd5317c9e207a413485c55ec3046b09d467d978443680304737a6d7d3db0c90e1
## 2019-04-04 ## 2019-04-04
✔ Successfully published kredits-contributor.open.aragonpm.eth v3.0.0: ✔ Successfully published kredits-contributor.open.aragonpm.eth v3.0.0:

View File

@@ -1,5 +1,24 @@
# Kredits deployment # Kredits deployment
## 2019-04-24 upgrade contributor and contribution
aragon dao upgrade 0xc34edf7d11b7f8433d597f0bb0697acdff55ef14 kredits-contributor.open.aragonpm.eth --environment=rinkeby
eth-provider | Invalid provider preset/location: "local"
✔ Fetching kredits-contributor.open.aragonpm.eth@latest
✔ Upgrading app
✔ Successfully executed: "Set the resolving address of 'kredits-contributor.open.aragonpm.eth' in namespace 'App code' to 0xadefa3b66b68a127Fe38bEa1813b844EE69CFD86"
aragon dao upgrade 0xc34edf7d11b7f8433d597f0bb0697acdff55ef14 kredits-contribution.open.aragonpm.eth --environment=rinkeby
✔ Fetching kredits-contribution.open.aragonpm.eth@latest
✔ Upgrading app
✔ Successfully executed: "Set the resolving address of 'kredits-contribution.open.aragonpm.eth' in namespace 'App code' to 0x2c083EEA83fd3a99C93759D97D0317A43261c758"
## 2019-04-10 - Weltempfänger release
Using KreditsKit at: 0x76e069b47b79442657eaf0555a32c6b16fa1b8b4
Created new DAO at: 0xc34edf7d11b7f8433d597f0bb0697acdff55ef14
## 2019-04-04 ## 2019-04-04
Using KreditsKit at: 0x76e069b47b79442657eaf0555a32c6b16fa1b8b4 Using KreditsKit at: 0x76e069b47b79442657eaf0555a32c6b16fa1b8b4

View File

@@ -1,5 +1,13 @@
# Proposal deployments # Proposal deployments
## 2019-04-10 - Weltempfänger release
✔ Successfully published kredits-proposal.open.aragonpm.eth v5.0.0:
Contract address: 0x4ce5b0286483c66b861e5599a199054687434552
Content (ipfs): QmNYXEcmvKTGxYiob7WUf85oZhdmFDCuGiA6TsRrDE9TYb
Transaction hash: 0x0482b58a1ba87d494c6391026399d0ac41b45384330d916f3f99ba70e501584b
## 2019-04-04 ## 2019-04-04
✔ Successfully published kredits-proposal.open.aragonpm.eth v4.0.0: ✔ Successfully published kredits-proposal.open.aragonpm.eth v4.0.0:

View File

@@ -1,5 +1,13 @@
# Token deployments # Token deployments
## 2019-04-10 - Weltempfänger release
✔ Successfully published kredits-token.open.aragonpm.eth v4.0.0:
Contract address: 0x05E0C2bbdA8e5BeE22AC1E20C1457dA4de63aE26
Content (ipfs): QmUuYLRMRNZcundUk2pxVaSjMNvtB7hzdf3eyoaUNqDPow
Transaction hash: 0x98c28b5ca645904d56eb83c4783682f018c0fcee015b3a3d5fa8bd609223fb89
## 2019-04-04 ## 2019-04-04
✔ Successfully published kredits-token.open.aragonpm.eth v3.0.0: ✔ Successfully published kredits-token.open.aragonpm.eth v3.0.0:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,3 @@
{ {
"4": "0x76e069b47b79442657eaf0555a32c6b16fa1b8b4", "4": "0x76e069b47b79442657eaf0555a32c6b16fa1b8b4"
"41787949": "0xa35aacdfccac54d3d96e0d29050c773b251c2c83"
} }

View File

@@ -1,4 +1,3 @@
{ {
"4": "0xcd75458fbc4aa2231252d5b21f1391fd031e5cb2", "4": "0xc34edf7d11b7f8433d597f0bb0697acdff55ef14"
"41787949": "0x183af3950364390a266edff2a0e7c4c2f95c0691"
} }

View File

@@ -2,11 +2,15 @@ const Base = require('./base');
const EthersUtils = require('ethers').utils; const EthersUtils = require('ethers').utils;
class Acl extends Base { class Acl extends Base {
hasPermission (fromAddress, contractAddress, roleID, params = null) {
hasPermission(fromAddress, contractAddress, roleID, params = null) {
let roleHash = EthersUtils.keccak256(EthersUtils.toUtf8Bytes(roleID)); let roleHash = EthersUtils.keccak256(EthersUtils.toUtf8Bytes(roleID));
console.log(roleHash)
return this.functions.hasPermission(fromAddress, contractAddress, roleHash, params); return this.functions.hasPermission(
fromAddress,
contractAddress,
roleHash,
params
);
} }
} }

View File

@@ -1,23 +1,24 @@
class Base { class Base {
constructor(contract) { constructor (contract) {
this.contract = contract; this.contract = contract;
} }
get functions() { get functions () {
return this.contract.functions; return this.contract.functions;
} }
get ipfs() { get ipfs () {
if (!this._ipfsAPI) { throw new Error('IPFS API not configured; please set an ipfs instance'); } if (!this._ipfsAPI) { throw new Error('IPFS API not configured; please set an ipfs instance'); }
return this._ipfsAPI; return this._ipfsAPI;
} }
set ipfs(ipfsAPI) { set ipfs (ipfsAPI) {
this._ipfsAPI = ipfsAPI; this._ipfsAPI = ipfsAPI;
} }
on(type, callback) { on (type, callback) {
return this.contract.on(type, callback); return this.contract.on(type, callback);
} }
} }
module.exports = Base; module.exports = Base;

View File

@@ -1,37 +1,24 @@
const ethers = require('ethers'); const Record = require('./record');
const ContributionSerializer = require('../serializers/contribution'); const ContributionSerializer = require('../serializers/contribution');
const Base = require('./base');
class Contribution extends Base { class Contribution extends Record {
all() { get count () {
return this.functions.contributionsCount() return this.functions.contributionsCount();
.then(async (count) => {
let contributions = [];
for (let id = 1; id <= count; id++) {
const contribution = await this.getById(id)
contributions.push(contribution);
}
return contributions;
});
} }
getById(id) { getById (id) {
return this.functions.getContribution(id) return this.functions.getContribution(id)
.then(data => { .then(data => {
return this.ipfs.catAndMerge(data, ContributionSerializer.deserialize); return this.ipfs.catAndMerge(data, ContributionSerializer.deserialize);
}); });
} }
getByContributorId(contributorId) { getByContributorId (contributorId) {
return this.functions.getContributorAddressById(contributorId) return this.functions.getContributorAddressById(contributorId)
.then(address => this.getByContributorAddress(address)); .then(address => this.getByContributorAddress(address));
} }
getByContributorAddress(address) { getByContributorAddress (address) {
return this.functions.balanceOf(address) return this.functions.balanceOf(address)
.then(async (balance) => { .then(async (balance) => {
const count = balance.toNumber(); const count = balance.toNumber();
@@ -47,13 +34,17 @@ class Contribution extends Base {
}); });
} }
addContribution(contributionAttr, callOptions = {}) { async addContribution (contributionAttr, callOptions = {}) {
let json = ContributionSerializer.serialize(contributionAttr); const contribution = new ContributionSerializer(contributionAttr);
// TODO: validate against schema
try { await contribution.validate(); }
catch (error) { return Promise.reject(error); }
const jsonStr = contribution.serialize();
return this.ipfs return this.ipfs
.add(json) .add(jsonStr)
.then((ipfsHashAttr) => { .then(ipfsHashAttr => {
let contribution = [ let contribution = [
contributionAttr.amount, contributionAttr.amount,
contributionAttr.contributorId, contributionAttr.contributorId,

View File

@@ -1,40 +1,29 @@
const ethers = require('ethers'); const Record = require('./record');
const RSVP = require('rsvp');
const ContributorSerializer = require('../serializers/contributor'); const ContributorSerializer = require('../serializers/contributor');
const Base = require('./base'); const formatKredits = require('../utils/format-kredits');
class Contributor extends Base { class Contributor extends Record {
all() { get count () {
return this.functions.contributorsCount() return this.functions.contributorsCount();
.then(count => {
let contributors = [];
for (let id = 1; id <= count; id++) {
contributors.push(this.getById(id));
}
return RSVP.all(contributors);
});
} }
getById(id) { getById (id) {
return this.functions.getContributorById(id) return this.functions.getContributorById(id)
// Fetch IPFS data if available .then(data => {
.then((data) => { data.balanceInt = formatKredits(data.balance);
return this.ipfs.catAndMerge(data, ContributorSerializer.deserialize); return this.ipfs.catAndMerge(data, ContributorSerializer.deserialize);
}); });
} }
filterByAccount(search) { filterByAccount (search) {
return this._byAccount(search, 'filter'); return this._byAccount(search, 'filter');
} }
findByAccount(search) { findByAccount (search) {
return this._byAccount(search, 'find'); return this._byAccount(search, 'find');
} }
_byAccount(search, method = 'filter') { _byAccount (search, method = 'filter') {
return this.all().then((contributors) => { return this.all().then((contributors) => {
const searchEntries = Object.entries(search); const searchEntries = Object.entries(search);
@@ -50,12 +39,16 @@ class Contributor extends Base {
}); });
} }
add(contributorAttr, callOptions = {}) { async add (contributorAttr, callOptions = {}) {
let json = ContributorSerializer.serialize(contributorAttr); let contributor = new ContributorSerializer(contributorAttr);
// TODO: validate against schema
try { await contributor.validate(); }
catch (error) { return Promise.reject(error); }
const jsonStr = contributor.serialize();
return this.ipfs return this.ipfs
.add(json) .add(jsonStr)
.then((ipfsHashAttr) => { .then((ipfsHashAttr) => {
let contributor = [ let contributor = [
contributorAttr.account, contributorAttr.account,
@@ -67,6 +60,30 @@ class Contributor extends Base {
return this.functions.addContributor(...contributor, callOptions); return this.functions.addContributor(...contributor, callOptions);
}); });
} }
updateProfile (contributorId, updateAttr, callOptions = {}) {
return this.getById(contributorId).then(async (contributor) => {
let updatedContributorAttr = Object.assign(contributor, updateAttr);
let updatedContributor = new ContributorSerializer(updatedContributorAttr);
try { await updatedContributor.validate(); }
catch (error) { return Promise.reject(error); }
const jsonStr = updatedContributor.serialize();
return this.ipfs
.add(jsonStr)
.then(ipfsHashAttr => {
return this.functions.updateContributorProfileHash(
contributorId,
ipfsHashAttr.hashDigest,
ipfsHashAttr.hashFunction,
ipfsHashAttr.hashSize,
callOptions
);
});
});
}
} }
module.exports = Contributor; module.exports = Contributor;

View File

@@ -4,5 +4,5 @@ module.exports = {
Proposal: require('./proposal'), Proposal: require('./proposal'),
Token: require('./token'), Token: require('./token'),
Kernel: require('./kernel'), Kernel: require('./kernel'),
Acl: require('./acl') Acl: require('./acl'),
}; };

View File

@@ -4,19 +4,19 @@ const Base = require('./base');
const KERNEL_APP_ADDR_NAMESPACE = '0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb'; const KERNEL_APP_ADDR_NAMESPACE = '0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb';
class Kernel extends Base { class Kernel extends Base {
constructor(contract) { constructor (contract) {
super(contract); super(contract);
this.apm = 'aragonpm.eth'; // can be overwritten if needed this.apm = 'aragonpm.eth'; // can be overwritten if needed
} }
getApp(appName) { getApp (appName) {
if (appName === 'Acl') { if (appName === 'Acl') {
return this.functions.acl(); return this.functions.acl();
} }
return this.functions.getApp(KERNEL_APP_ADDR_NAMESPACE, this.appNamehash(appName)); return this.functions.getApp(KERNEL_APP_ADDR_NAMESPACE, this.appNamehash(appName));
} }
appNamehash(appName) { appNamehash (appName) {
return namehash(`kredits-${appName.toLowerCase()}.${this.apm}`); return namehash(`kredits-${appName.toLowerCase()}.${this.apm}`);
} }
} }

View File

@@ -1,36 +1,28 @@
const ethers = require('ethers'); const Record = require('./record');
const RSVP = require('rsvp');
const ContributionSerializer = require('../serializers/contribution'); const ContributionSerializer = require('../serializers/contribution');
const Base = require('./base');
class Proposal extends Base { class Proposal extends Record {
all() { get count () {
return this.functions.proposalsCount() return this.functions.proposalsCount();
.then(count => {
let proposals = [];
for (let id = 1; id <= count; id++) {
proposals.push(this.getById(id));
}
return RSVP.all(proposals);
});
} }
getById(id) { getById (id) {
return this.functions.getProposal(id) return this.functions.getProposal(id)
.then(data => { .then(data => {
return this.ipfs.catAndMerge(data, ContributionSerializer.deserialize); return this.ipfs.catAndMerge(data, ContributionSerializer.deserialize);
}); });
} }
addProposal(proposalAttr, callOptions = {}) { async addProposal (proposalAttr, callOptions = {}) {
let json = ContributionSerializer.serialize(proposalAttr); const contribution = new ContributionSerializer(proposalAttr);
// TODO: validate against schema
try { await contribution.validate(); }
catch (error) { return Promise.reject(error); }
const jsonStr = contribution.serialize();
return this.ipfs return this.ipfs
.add(json) .add(jsonStr)
.then((ipfsHashAttr) => { .then((ipfsHashAttr) => {
let proposal = [ let proposal = [
proposalAttr.contributorId, proposalAttr.contributorId,
@@ -45,4 +37,4 @@ class Proposal extends Base {
} }
} }
module.exports = Proposal module.exports = Proposal;

14
lib/contracts/record.js Normal file
View File

@@ -0,0 +1,14 @@
const Base = require('./base');
const paged = require('../utils/pagination');
class Record extends Base {
all (options = {}) {
return this.count
.then((count) => {
let records = paged(count, options).map((id) => this.getById(id));
return Promise.all(records);
});
}
}
module.exports = Record;

View File

@@ -4,4 +4,3 @@ class Token extends Base {
} }
module.exports = Token; module.exports = Token;

View File

@@ -1,7 +1,7 @@
const ethers = require('ethers'); const ethers = require('ethers');
const RSVP = require('rsvp');
const Preflight = require('./utils/preflight'); const Preflight = require('./utils/preflight');
const deprecate = require('./utils/deprecate');
const ABIS = { const ABIS = {
Contributor: require('./abis/Contributor.json'), Contributor: require('./abis/Contributor.json'),
@@ -9,29 +9,29 @@ const ABIS = {
Token: require('./abis/Token.json'), Token: require('./abis/Token.json'),
Proposal: require('./abis/Proposal.json'), Proposal: require('./abis/Proposal.json'),
Kernel: require('./abis/Kernel.json'), Kernel: require('./abis/Kernel.json'),
Acl: require('./abis/ACL.json') Acl: require('./abis/ACL.json'),
}; };
const APP_CONTRACTS = [ const APP_CONTRACTS = [
'Contributor', 'Contributor',
'Contribution', 'Contribution',
'Token', 'Token',
'Proposal', 'Proposal',
'Acl' 'Acl',
]; ];
const DaoAddresses = require('./addresses/dao.json'); const DaoAddresses = require('./addresses/dao.json');
const Contracts = require('./contracts'); const Contracts = require('./contracts');
const IPFS = require('./utils/ipfs') const IPFS = require('./utils/ipfs');
// Helpers // Helpers
function capitalize(word) { function capitalize (word) {
let [first, ...rest] = word; let [first, ...rest] = word;
return `${first.toUpperCase()}${rest.join('')}`; return `${first.toUpperCase()}${rest.join('')}`;
} }
class Kredits { class Kredits {
constructor(provider, signer, options = {}) { constructor (provider, signer, options = {}) {
let { addresses, abis, ipfsConfig } = options; let { addresses, abis, ipfsConfig } = options;
this.provider = provider; this.provider = provider;
@@ -41,32 +41,35 @@ class Kredits {
this.abis = abis || ABIS; this.abis = abis || ABIS;
this.ipfs = new IPFS(ipfsConfig); this.ipfs = new IPFS(ipfsConfig);
this.contracts = {}; this.contracts = {};
this.networkId = null;
} }
init(names) { init (names) {
let contractsToLoad = names || APP_CONTRACTS; let contractsToLoad = names || APP_CONTRACTS;
return this.provider.getNetwork().then(network => { return this.provider.getNetwork().then(network => {
this.addresses['Kernel'] = this.addresses['Kernel'] || DaoAddresses[network.chainId.toString()]; this.networkId = network.chainId.toString();
this.addresses['Kernel'] = this.addresses['Kernel'] || DaoAddresses[this.networkId];
let addressPromises = contractsToLoad.map((contractName) => { let addressPromises = contractsToLoad.map((contractName) => {
return this.Kernel.getApp(contractName).then((address) => { return this.Kernel.getApp(contractName).then((address) => {
this.addresses[contractName] = address; this.addresses[contractName] = address;
}).catch((error) => { }).catch((error) => {
console.log(error);
throw new Error(`Failed to get address for ${contractName} from DAO at ${this.Kernel.contract.address} throw new Error(`Failed to get address for ${contractName} from DAO at ${this.Kernel.contract.address}
- ${error.message}` - ${error.message}`
); );
}); });
}); });
return RSVP.all(addressPromises).then(() => { return this });
return Promise.all(addressPromises).then(() => { return this; });
}); });
} }
static setup(provider, signer, ipfsConfig = null) { static setup (provider, signer, ipfsConfig = null) {
console.log('Kredits.setup() is deprecated use new Kredits().init() instead'); deprecate('Kredits.setup() is deprecated use new Kredits().init() instead');
return new Kredits(provider, signer, { ipfsConfig: ipfsConfig }).init(); return new Kredits(provider, signer, { ipfsConfig: ipfsConfig }).init();
} }
get Kernel() { get Kernel () {
let k = this.contractFor('Kernel'); let k = this.contractFor('Kernel');
// in case we want to use a special apm (e.g. development vs. production) // in case we want to use a special apm (e.g. development vs. production)
if (this.options.apm) { if (this.options.apm) {
@@ -75,37 +78,37 @@ class Kredits {
return k; return k;
} }
get Contributor() { get Contributor () {
return this.contractFor('Contributor'); return this.contractFor('Contributor');
} }
get Contributors() { get Contributors () {
console.log('Contributors is deprecated use Contributor instead'); deprecate('Contributors is deprecated use Contributor instead');
return this.Contributor; return this.Contributor;
} }
get Proposal() { get Proposal () {
return this.contractFor('Proposal'); return this.contractFor('Proposal');
} }
get Operator() { get Operator () {
return this.Proposal; return this.Proposal;
} }
get Token() { get Token () {
return this.contractFor('Token'); return this.contractFor('Token');
} }
get Contribution() { get Contribution () {
return this.contractFor('Contribution'); return this.contractFor('Contribution');
} }
get Acl() { get Acl () {
return this.contractFor('Acl'); return this.contractFor('Acl');
} }
// Should be private // Should be private
contractFor(name) { contractFor (name) {
if (this.contracts[name]) { if (this.contracts[name]) {
return this.contracts[name]; return this.contracts[name];
} }
@@ -125,7 +128,7 @@ class Kredits {
return this.contracts[name]; return this.contracts[name];
} }
preflightChecks() { preflightChecks () {
return new Preflight(this).check(); return new Preflight(this).check();
} }
} }

View File

@@ -1,19 +1,75 @@
const schemas = require('kosmos-schemas');
const validator = require('../utils/validator');
/** /**
* Handle serialization for JSON-LD object of the contribution, according to * Serialization and validation for JSON-LD document of the contribution.
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contribution.json
* *
* @class * @class
* @public * @public
*/ */
class Contribution { class Contribution {
/**
* Deserialize JSON to object constructor (attrs) {
* Object.keys(attrs).forEach(a => this[a] = attrs[a]);
* @method }
* @public
*/ /**
static deserialize(serialized) { * Serialize object to JSON
*
* @public
*/
serialize () {
let { let {
contributorIpfsHash,
date,
time,
kind,
description,
url,
details,
} = this;
let data = {
'@context': 'https://schema.kosmos.org',
'@type': 'Contribution',
'contributor': {
'ipfs': contributorIpfsHash,
},
date,
time,
kind,
description,
'details': details || {},
};
if (url) {
data['url'] = url;
}
// Write it pretty to ipfs
return JSON.stringify(data, null, 2);
}
/**
* Validate serialized data against schema
*
* @public
*/
validate () {
const serialized = JSON.parse(this.serialize());
const valid = validator.validate(serialized, schemas['contribution']);
return valid ? Promise.resolve() : Promise.reject(validator.error);
}
/**
* Deserialize JSON to object
*
* @public
*/
static deserialize (serialized) {
let {
date,
time,
kind, kind,
description, description,
details, details,
@@ -21,6 +77,8 @@ class Contribution {
} = JSON.parse(serialized.toString('utf8')); } = JSON.parse(serialized.toString('utf8'));
return { return {
date,
time,
kind, kind,
description, description,
details, details,
@@ -29,39 +87,6 @@ class Contribution {
}; };
} }
/**
* 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; module.exports = Contribution;

View File

@@ -1,3 +1,5 @@
const schemas = require('kosmos-schemas');
const validator = require('../utils/validator');
/** /**
* Handle serialization for JSON-LD object of the contributor, according to * Handle serialization for JSON-LD object of the contributor, according to
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contributor.json * https://github.com/67P/kosmos-schemas/blob/master/schemas/contributor.json
@@ -6,13 +8,87 @@
* @public * @public
*/ */
class Contributor { class Contributor {
constructor (attrs) {
Object.keys(attrs).forEach(a => this[a] = attrs[a]);
}
/** /**
* Serialize object to JSON
*
* @method
* @public
*/
serialize () {
let {
name,
kind,
url,
github_uid,
github_username,
gitea_username,
wiki_username,
} = this;
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 (gitea_username) {
data.accounts.push({
'site': 'gitea.kosmos.org',
'username': gitea_username,
'url': `https://gitea.kosmos.org/${gitea_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);
}
/**
* Validate serialized data against schema
*
* @public
*/
validate () {
const serialized = JSON.parse(this.serialize());
const valid = validator.validate(serialized, schemas['contributor']);
return valid ? Promise.resolve() : Promise.reject(validator.error);
}
/**
* Deserialize JSON to object * Deserialize JSON to object
* *
* @method * @method
* @public * @public
*/ */
static deserialize(serialized) { static deserialize (serialized) {
let { let {
name, name,
kind, kind,
@@ -20,13 +96,17 @@ class Contributor {
accounts, accounts,
} = JSON.parse(serialized.toString('utf8')); } = JSON.parse(serialized.toString('utf8'));
let github_username, github_uid, wiki_username; let github_username, github_uid, gitea_username, wiki_username;
let github = accounts.find((a) => a.site === 'github.com'); let github = accounts.find(a => a.site === 'github.com');
let wiki = accounts.find((a) => a.site === 'wiki.kosmos.org'); let gitea = accounts.find(a => a.site === 'gitea.kosmos.org');
let wiki = accounts.find(a => a.site === 'wiki.kosmos.org');
if (github) { if (github) {
(({ username: github_username, uid: github_uid} = github)); (({ username: github_username, uid: github_uid} = github));
} }
if (gitea) {
(({ username: gitea_username } = gitea));
}
if (wiki) { if (wiki) {
(({ username: wiki_username } = wiki)); (({ username: wiki_username } = wiki));
} }
@@ -38,59 +118,12 @@ class Contributor {
accounts, accounts,
github_uid, github_uid,
github_username, github_username,
gitea_username,
wiki_username, wiki_username,
ipfsData: serialized, 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; module.exports = Contributor;

5
lib/utils/deprecate.js Normal file
View File

@@ -0,0 +1,5 @@
/*eslint no-console: ["error", { allow: ["warn"] }] */
module.exports = function deprecate (msg) {
console.warn(msg);
};

View File

@@ -0,0 +1,10 @@
const ethersUtils = require('ethers').utils;
module.exports = function(value, options = {}) {
let etherValue = ethersUtils.formatEther(value);
if (options.asFloat) {
return parseFloat(etherValue);
} else {
return parseInt(etherValue);
}
};

View File

@@ -2,8 +2,7 @@ const ipfsClient = require('ipfs-http-client');
const multihashes = require('multihashes'); const multihashes = require('multihashes');
class IPFS { class IPFS {
constructor (config) {
constructor(config) {
if (!config) { if (!config) {
config = { host: 'localhost', port: '5001', protocol: 'http' }; config = { host: 'localhost', port: '5001', protocol: 'http' };
} }
@@ -11,7 +10,15 @@ class IPFS {
this._config = config; this._config = config;
} }
catAndMerge(data, deserialize) { get config () {
return this._config;
}
get peerId () {
return this._ipfsAPI.id();
}
catAndMerge (data, deserialize) {
// if no hash details are found simply return the data; nothing to merge // if no hash details are found simply return the data; nothing to merge
if (!data.hashSize || data.hashSize === 0) { if (!data.hashSize || data.hashSize === 0) {
return data; return data;
@@ -26,7 +33,7 @@ class IPFS {
}); });
} }
add(data) { add (data) {
return this._ipfsAPI return this._ipfsAPI
.add(ipfsClient.Buffer.from(data)) .add(ipfsClient.Buffer.from(data))
.then((res) => { .then((res) => {
@@ -34,7 +41,7 @@ class IPFS {
}); });
} }
cat(hashData) { cat (hashData) {
let ipfsHash = hashData; // default - if it is a string let ipfsHash = hashData; // default - if it is a string
if (hashData.hasOwnProperty('hashSize')) { if (hashData.hasOwnProperty('hashSize')) {
ipfsHash = this.encodeHash(hashData); ipfsHash = this.encodeHash(hashData);
@@ -42,21 +49,20 @@ class IPFS {
return this._ipfsAPI.cat(ipfsHash); return this._ipfsAPI.cat(ipfsHash);
} }
decodeHash(ipfsHash) { decodeHash (ipfsHash) {
let multihash = multihashes.decode(multihashes.fromB58String(ipfsHash)); let multihash = multihashes.decode(multihashes.fromB58String(ipfsHash));
return { return {
hashDigest: '0x' + multihashes.toHexString(multihash.digest), hashDigest: '0x' + multihashes.toHexString(multihash.digest),
hashSize: multihash.length, hashSize: multihash.length,
hashFunction: multihash.code, hashFunction: multihash.code,
ipfsHash: ipfsHash ipfsHash: ipfsHash,
}; };
} }
encodeHash(hashData) { encodeHash (hashData) {
let digest = ipfsClient.Buffer.from(hashData.hashDigest.slice(2), 'hex'); let digest = ipfsClient.Buffer.from(hashData.hashDigest.slice(2), 'hex');
return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize); return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize);
} }
} }
module.exports = IPFS; module.exports = IPFS;

46
lib/utils/pagination.js Normal file
View File

@@ -0,0 +1,46 @@
function pageNumber (number, size, recordCount) {
let numberOfPages = Math.ceil(recordCount / size);
number = parseInt(number) || 1;
// Ensure page number is in range
number = number < 1 ? 1 : number;
number = number > numberOfPages ? numberOfPages : number;
return number;
}
function buildIds (order, number, size, recordCount) {
let offset = size * (number - 1);
let start;
let mapFunction;
if (order === 'asc') {
start = 1 + offset;
mapFunction = (_, i) => start + i;
} else {
start = recordCount - offset;
mapFunction = (_, i) => start - i;
}
// Ensure size is in range
let end = offset + size;
if (end > recordCount) {
let diff = end - recordCount;
size = size - diff;
}
return Array.from({ length: size }, mapFunction);
}
module.exports = function paged (recordCount, options = {}) {
let { order, page } = options;
order = order || 'desc';
page = page || {};
let size = parseInt(page.size) || 25;
let number = pageNumber(page.number, size, recordCount);
return buildIds(order, number, size, recordCount);
};

View File

@@ -1,25 +1,31 @@
class Preflight { class Preflight {
constructor(kredits) { constructor (kredits) {
this.kredits = kredits; this.kredits = kredits;
} }
check() { check () {
return this.kredits.ipfs._ipfsAPI.id() return this.kredits.ipfs.peerId()
.catch((error) => { .catch((error) => {
throw new Error(`IPFS node not available; config: ${JSON.stringify(this.kredits.ipfs.config)} - ${error.message}`); const ipfsConfig = JSON.stringify(this.kredits.ipfs.config);
throw new Error(`IPFS node not available; config: ${ipfsConfig} - ${error.message}`);
}) })
.then(() => { .then(() => {
let promises = Object.keys(this.kredits.contracts).map((name) => { let promises = Object.keys(this.kredits.contracts).map((name) => {
let contractWrapper = this.kredits.contracts[name]; let address = this.kredits.contracts[name].contract.address;
return this.kredits.provider.getCode(contractWrapper.contract.address).then((code) => {
// TODO: I think this throws the error: Error: contract not deployed
// I guess we don't need that if check anymore...
return this.kredits.provider.getCode(address).then((code) => {
// not sure if we always get the same return value if the code is not available // not sure if we always get the same return value if the code is not available
// so checking if it is < 5 long // so checking if it is < 5 long
if (code === '0x00' || code.length < 5) { if (code === '0x00' || code.length < 5) {
throw new Error(`Contract for: ${name} not found at ${contractWrapper.contract.address} on network ${this.kredits.provider.chainId}`); throw new Error(`Contract for: ${name} not found at ${address} on network ${this.kredits.networkId}`);
} }
return true; return true;
}); });
}); });
return Promise.all(promises); return Promise.all(promises);
}); });
} }

15
lib/utils/validator.js Normal file
View File

@@ -0,0 +1,15 @@
const tv4 = require('tv4');
const validator = tv4.freshApi();
validator.addFormat({
'date': function(value) {
const dateRegexp = /^[0-9]{4,}-[0-9]{2}-[0-9]{2}$/;
return dateRegexp.test(value) ? null : 'A valid ISO 8601 full-date string is expected';
},
'time': function(value) {
const timeRegexp = /^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([+|-]([01][0-9]|2[0-3]):[0-5][0-9]))$/;
return timeRegexp.test(value) ? null : 'A valid ISO 8601 full-time string is expected';
},
});
module.exports = validator;

1023
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "kredits-contracts", "name": "kredits-contracts",
"version": "4.0.2", "version": "5.3.0",
"description": "Ethereum contracts and npm wrapper for Kredits", "description": "Ethereum contracts and npm wrapper for Kredits",
"main": "./lib/kredits.js", "main": "./lib/kredits.js",
"directories": { "directories": {
@@ -20,6 +20,9 @@
"deploy:apps": "./scripts/every-app.sh \"aragon apm publish major\"", "deploy:apps": "./scripts/every-app.sh \"aragon apm publish major\"",
"devchain": "aragon devchain --port 7545", "devchain": "aragon devchain --port 7545",
"dao:address": "truffle exec scripts/current-address.js", "dao:address": "truffle exec scripts/current-address.js",
"lint:contracts": "solhint \"contracts/**/*.sol\" \"apps/*/contracts/**/*.sol\"",
"lint:contract-tests": "eslint apps/*/test",
"lint:wrapper": "eslint lib/",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
@@ -37,15 +40,21 @@
"@aragon/kits-base": "^1.0.0", "@aragon/kits-base": "^1.0.0",
"@aragon/os": "^4.1.0", "@aragon/os": "^4.1.0",
"async-each-series": "^1.1.0", "async-each-series": "^1.1.0",
"eslint": "^5.16.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.1.1",
"eth-provider": "^0.2.2", "eth-provider": "^0.2.2",
"openzeppelin-solidity": "^2.2.0", "openzeppelin-solidity": "^2.2.0",
"promptly": "^3.0.3", "promptly": "^3.0.3",
"solc": "^0.4.25" "solc": "^0.4.25",
"solhint": "^2.0.0"
}, },
"dependencies": { "dependencies": {
"ethers": "^4.0.27", "ethers": "^4.0.27",
"ipfs-http-client": "^30.1.1", "ipfs-http-client": "^30.1.1",
"rsvp": "^4.8.2" "kosmos-schemas": "^2.0.0",
"tv4": "^1.3.0"
}, },
"keywords": [ "keywords": [
"kosmos", "kosmos",

View File

@@ -1,4 +1,5 @@
const promptly = require('promptly'); const promptly = require('promptly');
const { inspect } = require('util');
const initKredits = require('./helpers/init_kredits.js'); const initKredits = require('./helpers/init_kredits.js');
@@ -26,23 +27,32 @@ module.exports = async function(callback) {
console.log(`Creating a contribution for contributor account ${contributorAccount} ID: ${contributorId}`); console.log(`Creating a contribution for contributor account ${contributorAccount} ID: ${contributorId}`);
[ dateNow, timeNow ] = (new Date()).toISOString().split('T');
let contributionAttributes = { let contributionAttributes = {
contributorId, contributorId,
date: dateNow,
time: timeNow,
amount: await promptly.prompt('Amount: '), amount: await promptly.prompt('Amount: '),
description: await promptly.prompt('Description: '), description: await promptly.prompt('Description: '),
kind: await promptly.prompt('Kind: ', { default: 'dev' }), kind: await promptly.prompt('Kind: ', { default: 'dev' }),
url: await promptly.prompt('URL: ', { default: '' }) url: await promptly.prompt('URL: ', { default: '' })
} }
const contributorData = await kredits.Contributor.getById(contributorId);
contributionAttributes.contributorIpfsHash = contributorData.ipfsHash;
console.log("\nAdding contribution:"); console.log("\nAdding contribution:");
console.log(contributionAttributes); console.log(contributionAttributes);
kredits.Contribution.addContribution(contributionAttributes, { gasLimit: 300000 }).then((result) => { kredits.Contribution.addContribution(contributionAttributes, { gasLimit: 300000 })
console.log("\n\nResult:"); .then(result => {
console.log(result); console.log("\n\nResult:");
callback(); console.log(result);
}).catch((error) => { callback();
console.log('Failed to create contribution'); })
callback(error); .catch(error => {
}); console.log('Failed to create contribution');
callback(inspect(error));
});
} }

View File

@@ -26,7 +26,7 @@ module.exports = async function(callback) {
kind: await prompt('Kind (default person): ', {default: 'person'}), kind: await prompt('Kind (default person): ', {default: 'person'}),
url: await prompt('URL: '), url: await prompt('URL: '),
github_username: await prompt('GitHub username: '), github_username: await prompt('GitHub username: '),
github_uid: await prompt('GitHub UID: '), github_uid: parseInt(await prompt('GitHub UID: ')),
wiki_username: await prompt('Wiki username: '), wiki_username: await prompt('Wiki username: '),
}; };

View File

@@ -1,4 +1,5 @@
const promptly = require('promptly'); const promptly = require('promptly');
const { inspect } = require('util');
const initKredits = require('./helpers/init_kredits.js'); const initKredits = require('./helpers/init_kredits.js');
@@ -25,23 +26,31 @@ module.exports = async function(callback) {
} }
console.log(`Creating a proposal for contributor ID #${contributorId} account: ${contributorAccount}`); console.log(`Creating a proposal for contributor ID #${contributorId} account: ${contributorAccount}`);
[ dateNow, timeNow ] = (new Date()).toISOString().split('T');
let contributionAttributes = { let contributionAttributes = {
contributorId, contributorId,
date: dateNow,
time: timeNow,
amount: await promptly.prompt('Amount: '), amount: await promptly.prompt('Amount: '),
description: await promptly.prompt('Description: '), description: await promptly.prompt('Description: '),
kind: await promptly.prompt('Kind: ', { default: 'dev' }), kind: await promptly.prompt('Kind: ', { default: 'dev' }),
url: await promptly.prompt('URL: ', { default: '' }) url: await promptly.prompt('URL: ', { default: '' })
} }
const contributorData = await kredits.Contributor.getById(contributorId);
contributionAttributes.contributorIpfsHash = contributorData.ipfsHash;
console.log("\nAdding proposal:"); console.log("\nAdding proposal:");
console.log(contributionAttributes); console.log(contributionAttributes);
kredits.Proposal.addProposal(contributionAttributes, { gasLimit: 300000 }).then((result) => { kredits.Proposal.addProposal(contributionAttributes, { gasLimit: 300000 })
console.log("\n\nResult:"); .then((result) => {
console.log(result); console.log("\n\nResult:");
callback(); console.log(result);
}).catch((error) => { callback();
console.log('Failed to create proposal'); }).catch((error) => {
callback(error); console.log('Failed to create proposal');
}); callback(inspect(error));
});
} }

View File

@@ -15,28 +15,33 @@ module.exports = async function(callback) {
console.log(`Using Contribution at: ${kredits.Contribution.contract.address}`); console.log(`Using Contribution at: ${kredits.Contribution.contract.address}`);
const table = new Table({ const table = new Table({
head: ['ID', 'Contributor ID', 'Description', 'Amount', 'Confirmed?', 'Vetoed?', 'Claimed?'] head: ['ID', 'Contributor ID', 'Description', 'Amount', 'Confirmed?', 'Vetoed?', 'Claimed?', 'IPFS']
}) })
try { try {
let blockNumber = await kredits.provider.getBlockNumber(); let blockNumber = await kredits.provider.getBlockNumber();
let contributions = await kredits.Contribution.all() let contributions = await kredits.Contribution.all();
console.log(`Current block number: ${blockNumber}`);
contributions.forEach((c) => { contributions.forEach((c) => {
const confirmed = !!(c.claimAtBlock < blockNumber) const confirmed = c.confirmedAtBlock <= blockNumber;
table.push([ table.push([
c.id.toString(), c.id.toString(),
c.contributorId, c.contributorId,
`${c.description}`, `${c.description}`,
c.amount.toString(), c.amount.toString(),
confirmed, `${confirmed} (${c.confirmedAtBlock})`,
c.vetoed, c.vetoed,
c.claimed, c.claimed,
c.ipfsHash
]) ])
}) });
console.log(table.toString()) console.log(table.toString());
let totalKreditsEarned = await kredits.Contribution.functions.totalKreditsEarned(true);
console.log(`Total confirmed kredits: ${totalKreditsEarned}`);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }

View File

@@ -1,5 +1,6 @@
const promptly = require('promptly'); const promptly = require('promptly');
const Table = require('cli-table'); const Table = require('cli-table');
const ethers = require('ethers');
const initKredits = require('./helpers/init_kredits.js'); const initKredits = require('./helpers/init_kredits.js');
@@ -14,23 +15,31 @@ module.exports = async function(callback) {
console.log(`Using Contributor at: ${kredits.Contributor.contract.address}`); console.log(`Using Contributor at: ${kredits.Contributor.contract.address}`);
const table = new Table({ const table = new Table({
head: ['ID', 'Account', 'Core?', 'Name', 'Balance'] head: ['ID', 'Account', 'Name', 'Core?', 'Balance', 'Kredits earned', 'Contributions count', 'IPFS']
}) })
let contributors = await kredits.Contributor.all() try {
const contributors = await kredits.Contributor.all()
contributors.forEach((c) => {
table.push([
c.id.toString(),
c.account,
`${c.name}`,
c.isCore,
c.balanceInt.toString(),
c.totalKreditsEarned.toString(),
c.contributionsCount.toString(),
c.ipfsHash
])
})
console.log(table.toString())
} catch(e) {
callback(e);
return;
}
contributors.forEach((c) => {
table.push([
c.id.toString(),
c.account,
c.isCore,
`${c.name}`,
c.balance.toString()
])
})
console.log(table.toString())
callback() callback()
} }

8836
yarn.lock Normal file

File diff suppressed because it is too large Load Diff