From 4c0bb879e82bbadd39bf02cf3b487e2785de7647 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Thu, 11 Apr 2019 11:01:45 +0200 Subject: [PATCH 1/4] Show IPFS hash in list script output --- scripts/list-contributions.js | 3 ++- scripts/list-contributors.js | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/list-contributions.js b/scripts/list-contributions.js index 50d9fe3..0f92ae4 100644 --- a/scripts/list-contributions.js +++ b/scripts/list-contributions.js @@ -15,7 +15,7 @@ module.exports = async function(callback) { console.log(`Using Contribution at: ${kredits.Contribution.contract.address}`); const table = new Table({ - head: ['ID', 'Contributor ID', 'Description', 'Amount', 'Confirmed?', 'Vetoed?', 'Claimed?'] + head: ['ID', 'Contributor ID', 'Description', 'Amount', 'Confirmed?', 'Vetoed?', 'Claimed?', 'IPFS'] }) try { @@ -33,6 +33,7 @@ module.exports = async function(callback) { confirmed, c.vetoed, c.claimed, + c.ipfsHash ]) }); diff --git a/scripts/list-contributors.js b/scripts/list-contributors.js index 14e602c..6ac7dc5 100644 --- a/scripts/list-contributors.js +++ b/scripts/list-contributors.js @@ -17,7 +17,7 @@ module.exports = async function(callback) { const table = new Table({ - head: ['ID', 'Account', 'Core?', 'Name', 'Balance'] + head: ['ID', 'Account', 'Name', 'Core?', 'Balance', 'IPFS'] }) let contributors = await kredits.Contributor.all() @@ -26,9 +26,10 @@ module.exports = async function(callback) { table.push([ c.id.toString(), c.account, - c.isCore, `${c.name}`, - ethers.utils.formatEther(c.balance) + c.isCore, + ethers.utils.formatEther(c.balance), + c.ipfsHash ]) }) console.log(table.toString()) From aa57d7c70b3acc1e0d3522a71161573f0f73ae15 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Thu, 11 Apr 2019 11:02:15 +0200 Subject: [PATCH 2/4] Adjust seeds for new requirements --- config/seeds.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/seeds.js b/config/seeds.js index fde92f9..0d519b7 100644 --- a/config/seeds.js +++ b/config/seeds.js @@ -1,12 +1,12 @@ 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', [{ account: '0x49575f3DD9a0d60aE661BC992f72D837A77f05Bc', name: 'raucao', isCore: true, kind: 'person', url: '', github_username: 'skddc', github_uid: 842, wiki_username: 'raucau' }, { gasLimit: 200000 }]], + ['Contributor', 'add', [{ account: '0x7e8f313c56f809188313aa274fa67ee58c31515d', name: 'bumi', kind: 'person', url: '', github_username: 'bumi', github_uid: 318, wiki_username: 'bumi' }, { gasLimit: 200000 }]], + ['Contributor', 'add', [{ account: '0x49575f3DD9a0d60aE661BC992f72D837A77f05Bc', name: 'raucao', kind: 'person', url: '', github_username: 'skddc', github_uid: 842, wiki_username: 'raucau' }, { gasLimit: 200000 }]], ['Proposal', 'addProposal', [{ contributorId: 1, amount: 500, kind: 'code', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]], ['Proposal', 'addProposal', [{ contributorId: 2, amount: 500, kind: 'code', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]], ['Proposal', 'addProposal', [{ contributorId: 2, amount: 500, kind: 'code', description: '[67P/kredits-contracts] Hacked on kredits', url: '' }, { gasLimit: 350000 }]], ['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: 2, amount: 1500, kind: 'dev', description: '[67P/kredits-web] Reviewed stuff', 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, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 1500, kind: 'dev', description: '[67P/kredits-web] Reviewed stuff', url: '' }, { gasLimit: 350000 }]], ['Contribution', 'claim', [1, { gasLimit: 300000 }]] ]; const funds = [ From d953141f52d515dbcba4216c5eab9111c58467a2 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Thu, 11 Apr 2019 12:23:41 +0200 Subject: [PATCH 3/4] Refactor contribution serializer and validation --- lib/contracts/contribution.js | 26 +++------- lib/serializers/contribution.js | 85 ++++++++++++++++++++------------- lib/utils/validator.js | 15 ++++++ 3 files changed, 72 insertions(+), 54 deletions(-) create mode 100644 lib/utils/validator.js diff --git a/lib/contracts/contribution.js b/lib/contracts/contribution.js index 6398458..e49591a 100644 --- a/lib/contracts/contribution.js +++ b/lib/contracts/contribution.js @@ -1,20 +1,5 @@ const ethers = require('ethers'); -const schemas = require('kosmos-schemas'); -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"; - } -}) - const ContributionSerializer = require('../serializers/contribution'); const Base = require('./base'); @@ -62,12 +47,13 @@ class Contribution extends Base { }); } - addContribution(contributionAttr, callOptions = {}) { - let jsonStr = ContributionSerializer.serialize(contributionAttr); + async addContribution(contributionAttr, callOptions = {}) { + const contribution = new ContributionSerializer(contributionAttr); - // Validate JSON document against schema - let result = validator.validate(JSON.parse(jsonStr), schemas['contribution']); - if (!result) { return Promise.reject(validator.error); } + try { await contribution.validate(); } + catch (error) { return Promise.reject(error); } + + const jsonStr = contribution.serialize(); return this.ipfs .add(jsonStr) diff --git a/lib/serializers/contribution.js b/lib/serializers/contribution.js index 6cdc279..8ed8dee 100644 --- a/lib/serializers/contribution.js +++ b/lib/serializers/contribution.js @@ -1,45 +1,24 @@ +const schemas = require('kosmos-schemas'); +const validator = require('../utils/validator'); + /** - * Handle serialization for JSON-LD object of the contribution, according to - * https://github.com/67P/kosmos-schemas/blob/master/schemas/contribution.json + * Serialization and validation for JSON-LD document of the contribution. * * @class * @public */ class Contribution { - /** - * Deserialize JSON to object - * - * @method - * @public - */ - static deserialize(serialized) { - let { - date, - time, - kind, - description, - details, - url, - } = JSON.parse(serialized.toString('utf8')); - return { - date, - time, - kind, - description, - details, - url, - ipfsData: serialized, - }; + constructor(attrs) { + Object.keys(attrs).forEach(a => this[a] = attrs[a]); } - /** - * Serialize object to JSON - * - * @method - * @public - */ - static serialize(deserialized) { + /** + * Serialize object to JSON + * + * @public + */ + serialize () { let { contributorIpfsHash, date, @@ -48,7 +27,7 @@ class Contribution { description, url, details - } = deserialized; + } = this; let data = { "@context": "https://schema.kosmos.org", @@ -70,6 +49,44 @@ class Contribution { // 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, + description, + details, + url, + } = JSON.parse(serialized.toString('utf8')); + + return { + date, + time, + kind, + description, + details, + url, + ipfsData: serialized, + }; + } + } module.exports = Contribution; diff --git a/lib/utils/validator.js b/lib/utils/validator.js new file mode 100644 index 0000000..1dde313 --- /dev/null +++ b/lib/utils/validator.js @@ -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; From 952b5153fdbce4408fa58a2d94778d786c9a53c8 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Thu, 11 Apr 2019 13:23:08 +0200 Subject: [PATCH 4/4] Validate proposed contributions --- config/seeds.js | 8 +++++--- lib/contracts/proposal.js | 12 ++++++++---- scripts/add-proposal.js | 25 +++++++++++++++++-------- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/config/seeds.js b/config/seeds.js index 0d519b7..6547537 100644 --- a/config/seeds.js +++ b/config/seeds.js @@ -1,16 +1,18 @@ const contractCalls = [ ['Contributor', 'add', [{ account: '0x7e8f313c56f809188313aa274fa67ee58c31515d', name: 'bumi', kind: 'person', url: '', github_username: 'bumi', github_uid: 318, wiki_username: 'bumi' }, { gasLimit: 200000 }]], ['Contributor', 'add', [{ account: '0x49575f3DD9a0d60aE661BC992f72D837A77f05Bc', name: 'raucao', kind: 'person', url: '', github_username: 'skddc', github_uid: 842, wiki_username: 'raucau' }, { gasLimit: 200000 }]], - ['Proposal', 'addProposal', [{ contributorId: 1, amount: 500, kind: 'code', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]], - ['Proposal', 'addProposal', [{ contributorId: 2, amount: 500, kind: 'code', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]], - ['Proposal', 'addProposal', [{ contributorId: 2, amount: 500, kind: 'code', description: '[67P/kredits-contracts] Hacked on kredits', url: '' }, { gasLimit: 350000 }]], + ['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 }]], ['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, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 1500, kind: 'dev', description: '[67P/kredits-web] Reviewed stuff', url: '' }, { gasLimit: 350000 }]], ['Contribution', 'claim', [1, { gasLimit: 300000 }]] ]; + const funds = [ '0x7e8f313c56f809188313aa274fa67ee58c31515d', '0xa502eb4021f3b9ab62f75b57a94e1cfbf81fd827' ]; + module.exports = { contractCalls, funds }; diff --git a/lib/contracts/proposal.js b/lib/contracts/proposal.js index 896b72f..5266144 100644 --- a/lib/contracts/proposal.js +++ b/lib/contracts/proposal.js @@ -25,12 +25,16 @@ class Proposal extends Base { }); } - addProposal(proposalAttr, callOptions = {}) { - let json = ContributionSerializer.serialize(proposalAttr); - // TODO: validate against schema + async addProposal(proposalAttr, callOptions = {}) { + const contribution = new ContributionSerializer(proposalAttr); + + try { await contribution.validate(); } + catch (error) { return Promise.reject(error); } + + const jsonStr = contribution.serialize(); return this.ipfs - .add(json) + .add(jsonStr) .then((ipfsHashAttr) => { let proposal = [ proposalAttr.contributorId, diff --git a/scripts/add-proposal.js b/scripts/add-proposal.js index 935b461..c99fecf 100644 --- a/scripts/add-proposal.js +++ b/scripts/add-proposal.js @@ -1,4 +1,5 @@ const promptly = require('promptly'); +const { inspect } = require('util'); 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}`); + [ dateNow, timeNow ] = (new Date()).toISOString().split('T'); + let contributionAttributes = { contributorId, + date: dateNow, + time: timeNow, amount: await promptly.prompt('Amount: '), description: await promptly.prompt('Description: '), kind: await promptly.prompt('Kind: ', { default: 'dev' }), url: await promptly.prompt('URL: ', { default: '' }) } + const contributorData = await kredits.Contributor.getById(contributorId); + contributionAttributes.contributorIpfsHash = contributorData.ipfsHash; + console.log("\nAdding proposal:"); console.log(contributionAttributes); - kredits.Proposal.addProposal(contributionAttributes, { gasLimit: 300000 }).then((result) => { - console.log("\n\nResult:"); - console.log(result); - callback(); - }).catch((error) => { - console.log('Failed to create proposal'); - callback(error); - }); + kredits.Proposal.addProposal(contributionAttributes, { gasLimit: 300000 }) + .then((result) => { + console.log("\n\nResult:"); + console.log(result); + callback(); + }).catch((error) => { + console.log('Failed to create proposal'); + callback(inspect(error)); + }); }