From e1f19a1832120a8f65d4deb74b122e295fa47f37 Mon Sep 17 00:00:00 2001 From: bumi Date: Sun, 15 Apr 2018 16:45:09 +0200 Subject: [PATCH 01/12] Fail smarter with better error handling This should give more insights in case of an error during loading data from Ethereum and IPFS. --- app/lib/kredits/kredits.js | 45 +++++++++++++++++++++++++++-------- app/lib/kredits/utils/ipfs.js | 1 - app/routes/application.js | 2 ++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/app/lib/kredits/kredits.js b/app/lib/kredits/kredits.js index b087371..7001a9a 100644 --- a/app/lib/kredits/kredits.js +++ b/app/lib/kredits/kredits.js @@ -27,22 +27,44 @@ export default class Kredits { this.ipfsConfig = ipfsConfig; this.ipfs = new IPFS(ipfsConfig); - let registryContract = this.initRegistryContract(provider); + return this.ipfs._ipfsAPI.id().catch((error) => { + throw new Error(`IPFS node not available; config: ${JSON.stringify(ipfsConfig)} - ${error.message}`); + }).then(() => { - let addresses = Object.keys(contracts).reduce((mem, name) => { - let contractName = capitalize(name); - mem[contractName] = registryContract.functions.getProxyFor(contractName); - return mem; - }, {}); + let registryContract = this.initRegistryContract(provider); - return RSVP.hash(addresses) - .then((addresses) => { - return new Kredits(provider, signer, addresses); - }); + let addresses = Object.keys(contracts).reduce((mem, name) => { + let contractName = capitalize(name); + mem[contractName] = registryContract.functions.getProxyFor(contractName).catch((error) => { + throw new Error(`Failed to get address for ${contractName} from registry at ${registryContract.address} + - correct registry? does it have version entry? - ${error.message}` + ); + }); + return mem; + }, {}); + + return RSVP.hash(addresses) + .then((addresses) => { + return new Kredits(provider, signer, addresses); + }); + }); } static initRegistryContract(provider) { let address = addresses['Registry'][provider.chainId]; + if (!address) { + throw new Error(`Registry address not found; invalid network? + requested network: ${provider.chainId} + supported networks: ${Object.keys(addresses['Registry'])} + `); + } + provider.getCode(address).then((code) => { + // not sure if we always get the same return value of the code is not available + // that's why checking if it is < 5 long + if (code === '0x00' || code.length < 5) { + throw new Error(`Registry not found at ${address} on network ${provider.chainId}`); + } + }); let abi = abis['Registry']; console.log('Initialize registry contract:', address, abi, provider); return new ethers.Contract(address, abi, provider); @@ -69,6 +91,9 @@ export default class Kredits { let contractName = capitalize(name); let address = this.addresses[contractName]; + if (!address || !abis[contractName]) { + throw new Error('Address or ABI not found for ' + contractName); + } let contract = new ethers.Contract(address, abis[contractName], this.signer); this.contracts[name] = new contracts[contractName](contract); diff --git a/app/lib/kredits/utils/ipfs.js b/app/lib/kredits/utils/ipfs.js index 5a2a7e4..bc6ccda 100644 --- a/app/lib/kredits/utils/ipfs.js +++ b/app/lib/kredits/utils/ipfs.js @@ -51,5 +51,4 @@ export default class IPFS { return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize); } - } diff --git a/app/routes/application.js b/app/routes/application.js index 9371c64..602cdbd 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -13,6 +13,8 @@ export default Route.extend({ transition.retry(); } } + }).catch((error) => { + console.log('Error initializing Kredits', error); }); }, }); -- 2.50.1 From 7e6c6a037d3e0ce73034cc56ee1c8e5d735ee6f5 Mon Sep 17 00:00:00 2001 From: bumi Date: Sun, 15 Apr 2018 18:02:52 +0200 Subject: [PATCH 02/12] code style. use interpolation syntax --- app/lib/kredits/kredits.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/kredits/kredits.js b/app/lib/kredits/kredits.js index 7001a9a..46a893f 100644 --- a/app/lib/kredits/kredits.js +++ b/app/lib/kredits/kredits.js @@ -92,7 +92,7 @@ export default class Kredits { let contractName = capitalize(name); let address = this.addresses[contractName]; if (!address || !abis[contractName]) { - throw new Error('Address or ABI not found for ' + contractName); + throw new Error(`Address or ABI not found for ${contractName}`); } let contract = new ethers.Contract(address, abis[contractName], this.signer); this.contracts[name] = new contracts[contractName](contract); -- 2.50.1 From 399ee27af975ed71720bebb510e32798d3840a8d Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Sun, 15 Apr 2018 16:35:31 +0000 Subject: [PATCH 03/12] Revert "Adjust for new naming conventions" --- app/components/add-contributor/component.js | 8 ++++---- app/components/add-contributor/template.hbs | 6 +++--- app/components/contributor-list/template.hbs | 2 +- app/controllers/index.js | 6 ++++-- app/lib/kredits/contracts/contributor.js | 10 +++++++++- app/lib/kredits/contracts/operator.js | 10 +++++++++- app/models/contributor.js | 3 ++- app/models/proposal.js | 2 +- app/services/kredits.js | 5 +++++ app/templates/index.hbs | 2 +- 10 files changed, 39 insertions(+), 15 deletions(-) diff --git a/app/components/add-contributor/component.js b/app/components/add-contributor/component.js index 254ec95..13c83da 100644 --- a/app/components/add-contributor/component.js +++ b/app/components/add-contributor/component.js @@ -9,7 +9,7 @@ export default Component.extend({ // Default attributes used by reset attributes: { - account: null, + address: null, name: null, kind: 'person', url: null, @@ -24,9 +24,9 @@ export default Component.extend({ this.reset(); }, - isValidAccount: computed('kredits.ethProvider', 'account', function() { + isValidAddress: computed('kredits.ethProvider', 'address', function() { // TODO: add proper address validation - return this.get('account') !== ''; + return this.get('address') !== ''; }), isValidName: isPresent('name'), isValidURL: isPresent('url'), @@ -34,7 +34,7 @@ export default Component.extend({ isValidGithubUsername: isPresent('github_username'), isValidWikiUsername: isPresent('wiki_username'), isValid: and( - 'isValidAccount', + 'isValidAddress', 'isValidName', 'isValidGithubUID' ), diff --git a/app/components/add-contributor/template.hbs b/app/components/add-contributor/template.hbs index c47fd92..ded7d47 100644 --- a/app/components/add-contributor/template.hbs +++ b/app/components/add-contributor/template.hbs @@ -9,11 +9,11 @@

- {{input name="account" + {{input name="address" type="text" placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4" - value=account - class=(if isValidAccount 'valid' '')}} + value=address + class=(if isValidAddress 'valid' '')}}

+

diff --git a/app/controllers/index.js b/app/controllers/index.js index a82343a..1233651 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -26,7 +26,7 @@ export default Controller.extend({ return this.get('model.proposals') .map((proposal) => { let contributor = this.get('contributors') - .findBy('id', proposal.get('recipientId')); + .findBy('id', proposal.get('contributorId')); proposal.set('contributor', contributor); @@ -51,9 +51,9 @@ export default Controller.extend({ this.get('proposals').pushObject(proposal); }, - _handleProposalExecuted(proposalId, recipientId, amount) { + _handleProposalExecuted(proposalId, contributorId, amount) { let proposal = this.get('proposals') - .findBy('id', proposalId.toString()); + .findBy('id', proposalId); if (proposal.get('isExecuted')) { Ember.Logger.debug('[index] proposal already executed, not adding from event'); @@ -65,13 +65,13 @@ export default Controller.extend({ }); this.get('contributors') - .findBy('id', recipientId.toString()) + .findBy('id', contributorId) .incrementProperty('balance', amount); }, _handleProposalVoted(proposalId, voter, totalVotes) { this.get('proposals') - .findBy('id', proposalId.toString()) + .findBy('id', proposalId) .setProperties({ 'votesCount': totalVotes }); }, diff --git a/app/controllers/proposals/new.js b/app/controllers/proposals/new.js index dbea973..2b2f869 100644 --- a/app/controllers/proposals/new.js +++ b/app/controllers/proposals/new.js @@ -11,7 +11,7 @@ export default Controller.extend({ actions: { save(proposal) { // contributorIpfsHash is needed for the proposal ipfs data. I'm not happy to do this here but I think to load all the contributors in addProposal again is a bit too much. I hope we can refactor it later. - let contributor = this.get('contributors').findBy('id', proposal.recipientId); + let contributor = this.get('contributors').findBy('id', proposal.contributorId); proposal.contributorIpfsHash = contributor.get('ipfsHash'); return this.get('kredits').addProposal(proposal) diff --git a/app/lib/kredits/contracts/operator.js b/app/lib/kredits/contracts/operator.js index cd37573..accf785 100644 --- a/app/lib/kredits/contracts/operator.js +++ b/app/lib/kredits/contracts/operator.js @@ -39,7 +39,7 @@ export default class Operator extends Base { .add(json) .then((ipfsHashAttr) => { let proposal = [ - proposalAttr.recipientId, + proposalAttr.contributorId, proposalAttr.amount, ipfsHashAttr.ipfsHash, ipfsHashAttr.hashFunction, diff --git a/app/models/proposal.js b/app/models/proposal.js index b193414..4022ec4 100644 --- a/app/models/proposal.js +++ b/app/models/proposal.js @@ -6,7 +6,7 @@ export default EmberObject.extend({ // Contract id: bignumber('idRaw', 'toString'), creatorAccount: null, - recipientId: bignumber('recipientIdRaw', 'toString'), + contributorId: bignumber('contributorIdRaw', 'toString'), amount: bignumber('amountRaw', 'toNumber'), votesCount: bignumber('votesCountRaw', 'toNumber'), votesNeeded: bignumber('votesNeededRaw', 'toNumber'), diff --git a/app/utils/cps/bignumber.js b/app/utils/cps/bignumber.js index 701eed1..f86cab2 100644 --- a/app/utils/cps/bignumber.js +++ b/app/utils/cps/bignumber.js @@ -1,4 +1,5 @@ import computed from 'ember-computed'; +import ethers from 'npm:ethers'; export default function(dependentKey, converterMethod) { return computed(dependentKey, { @@ -6,6 +7,7 @@ export default function(dependentKey, converterMethod) { return this.get(dependentKey)[converterMethod](); }, set (key, value) { + value = ethers.utils.bigNumberify(value); this.set(dependentKey, value); return value[converterMethod](); } -- 2.50.1 From 99bbcda709f343c349ddd765e4cf76b817197435 Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Sun, 15 Apr 2018 19:02:57 +0200 Subject: [PATCH 05/12] Move contributors + proposals to service With this change we have all the contributors + proposals all the time in one place and can update them via events. --- app/controllers/index.js | 14 ++------------ app/controllers/proposals/new.js | 4 ++-- app/models/proposal.js | 10 ++++++++-- app/routes/application.js | 4 ++++ app/routes/index.js | 22 ---------------------- app/routes/proposals/new.js | 15 --------------- app/services/kredits.js | 17 +++++++++++++++++ app/templates/index.hbs | 4 ++-- tests/unit/controllers/index-test.js | 2 +- tests/unit/routes/index-test.js | 11 ----------- tests/unit/routes/proposals/new-test.js | 11 ----------- 11 files changed, 36 insertions(+), 78 deletions(-) delete mode 100644 app/routes/index.js delete mode 100644 app/routes/proposals/new.js delete mode 100644 tests/unit/routes/index-test.js delete mode 100644 tests/unit/routes/proposals/new-test.js diff --git a/app/controllers/index.js b/app/controllers/index.js index 1f5ca43..ab1549d 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -15,24 +15,14 @@ export default Controller.extend({ // TODO: transfer on the token contract }, - contributors: alias('model.contributors'), + contributors: alias('kredits.contributors'), contributorsWithKredits: filter('contributors', function(contributor) { return contributor.get('balance') !== 0; }), contributorsSorting: ['balance:desc'], contributorsSorted: sort('contributorsWithKredits', 'contributorsSorting'), - proposals: computed('model.proposals.[]', 'contributors.[]', function() { - return this.get('model.proposals') - .map((proposal) => { - let contributor = this.get('contributors') - .findBy('id', proposal.get('recipientId')); - - proposal.set('contributor', contributor); - - return proposal; - }); - }), + proposals: alias('kredits.proposals'), proposalsOpen: filterBy('proposals', 'isExecuted', false), proposalsClosed: filterBy('proposals', 'isExecuted', true), proposalsSorting: ['id:desc'], diff --git a/app/controllers/proposals/new.js b/app/controllers/proposals/new.js index dbea973..bc1ce67 100644 --- a/app/controllers/proposals/new.js +++ b/app/controllers/proposals/new.js @@ -1,11 +1,11 @@ import Controller from 'ember-controller'; -import { filterBy } from 'ember-computed'; +import { alias, filterBy } from 'ember-computed'; import injectService from 'ember-service/inject'; export default Controller.extend({ kredits: injectService(), - contributors: [], + contributors: alias('kredits.contributors'), minedContributors: filterBy('contributors', 'id'), actions: { diff --git a/app/models/proposal.js b/app/models/proposal.js index 88594e4..7bd376c 100644 --- a/app/models/proposal.js +++ b/app/models/proposal.js @@ -1,8 +1,11 @@ import EmberObject from 'ember-object'; -import { alias } from 'ember-computed'; +import computed, { alias } from 'ember-computed'; +import injectService from 'ember-service/inject'; import bignumber from 'kredits-web/utils/cps/bignumber'; export default EmberObject.extend({ + kredits: injectService(), + // Contract id: bignumber('idRaw', 'toString'), creatorAddress: null, @@ -24,5 +27,8 @@ export default EmberObject.extend({ ipfsData: '', // Relationships - contributor: null, + // TODO: Optimize it. We don't need to find the contributor every time we add a new one + contributor: computed('recipientId', 'kredits.contributors.[]', function() { + return this.get('kredits.contributors').findBy('id', this.get('recipientId')); + }) }); diff --git a/app/routes/application.js b/app/routes/application.js index 9371c64..3119aad 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -15,4 +15,8 @@ export default Route.extend({ } }); }, + + afterModel() { + return this.get('kredits').loadContributorsAndProposals(); + } }); diff --git a/app/routes/index.js b/app/routes/index.js deleted file mode 100644 index 437e4f3..0000000 --- a/app/routes/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Route.extend({ - - kredits: Ember.inject.service(), - - model() { - let newContributor = Ember.getOwner(this).lookup('model:contributor'); - newContributor.set('kind', 'person'); - - let kredits = this.get('kredits'); - let totalSupply = kredits.get('kredits').Token.functions.totalSupply(); - - return Ember.RSVP.hash({ - contributors: kredits.getContributors(), - proposals: kredits.getProposals(), - totalSupply, - newContributor: newContributor, - }); - } - -}); diff --git a/app/routes/proposals/new.js b/app/routes/proposals/new.js deleted file mode 100644 index 657e6b2..0000000 --- a/app/routes/proposals/new.js +++ /dev/null @@ -1,15 +0,0 @@ -import injectService from 'ember-service/inject'; -import Route from 'ember-route'; - -export default Route.extend({ - kredits: injectService(), - - setupController(controller) { - this._super(...arguments); - - this.get('kredits').getContributors() - .then((contributors) => { - controller.set('contributors', contributors); - }); - } -}); diff --git a/app/services/kredits.js b/app/services/kredits.js index 8f4f477..cc25378 100644 --- a/app/services/kredits.js +++ b/app/services/kredits.js @@ -74,6 +74,23 @@ export default Service.extend({ }); }, + totalSupply: computed(function() { + return this.get('kredits').Token.functions.totalSupply(); + }), + + contributors: [], + proposals: [], + + loadContributorsAndProposals() { + return RSVP.hash({ + contributors: this.getContributors(), + proposals: this.getProposals(), + }).then(({ contributors, proposals }) => { + this.set('contributors', contributors); + this.set('proposals', proposals); + }); + }, + // TODO: Only assign valid attributes buildModel(name, attributes) { debug('[kredits] build', name, attributes); diff --git a/app/templates/index.hbs b/app/templates/index.hbs index 5ad6e8b..2386410 100644 --- a/app/templates/index.hbs +++ b/app/templates/index.hbs @@ -18,7 +18,7 @@ {{contributor-list contributors=contributorsSorted}}

- {{model.totalSupply}} kredits issued and distributed among + {{await kredits.totalSupply}} kredits issued and distributed among {{contributorsWithKredits.length}} contributors.

@@ -69,7 +69,7 @@
{{#if kredits.currentUser.isCore}} - {{add-contributor contributors=model.contributors save=(action 'save')}} + {{add-contributor contributors=contributors save=(action 'save')}} {{else}} Only core team members can add new contributors. Please ask someone to set you up. {{/if}} diff --git a/tests/unit/controllers/index-test.js b/tests/unit/controllers/index-test.js index 0bce69e..fec0ad3 100644 --- a/tests/unit/controllers/index-test.js +++ b/tests/unit/controllers/index-test.js @@ -26,7 +26,7 @@ let addFixtures = function(controller) { // we expect a bignumer but I don't want to add the bignumber dependency here... so this is some hack to return an object that looks good enough for the test let fakeBignumber = function(balance) { return { toNumber: function() { return balance; } }; }; fixture.balance = fakeBignumber(fixture.balance); - controller.get('model.contributors').push(Contributor.create(fixture)); + controller.get('kredits.contributors').push(Contributor.create(fixture)); }); }; diff --git a/tests/unit/routes/index-test.js b/tests/unit/routes/index-test.js deleted file mode 100644 index 5d0f50d..0000000 --- a/tests/unit/routes/index-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { moduleFor, test } from 'ember-qunit'; - -moduleFor('route:index', 'Unit | Route | index', { - // Specify the other units that are required for this test. - // needs: ['controller:foo'] -}); - -test('it exists', function(assert) { - let route = this.subject(); - assert.ok(route); -}); diff --git a/tests/unit/routes/proposals/new-test.js b/tests/unit/routes/proposals/new-test.js deleted file mode 100644 index ad8366e..0000000 --- a/tests/unit/routes/proposals/new-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { moduleFor, test } from 'ember-qunit'; - -moduleFor('route:proposals/new', 'Unit | Route | proposals/new', { - // Specify the other units that are required for this test. - // needs: ['controller:foo'] -}); - -test('it exists', function(assert) { - let route = this.subject(); - assert.ok(route); -}); -- 2.50.1 From e5f69a5e9ae4ff4f554e867209ce0036fb6aaba1 Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Sun, 15 Apr 2018 19:09:16 +0200 Subject: [PATCH 06/12] Remove unused computed --- app/controllers/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/index.js b/app/controllers/index.js index ab1549d..aadce1b 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -1,6 +1,6 @@ import Ember from 'ember'; import Controller from 'ember-controller'; -import computed, { alias, filter, filterBy, sort } from 'ember-computed'; +import { alias, filter, filterBy, sort } from 'ember-computed'; import injectService from 'ember-service/inject'; export default Controller.extend({ -- 2.50.1 From 62e195d186880743b883768dc52092c2d333658d Mon Sep 17 00:00:00 2001 From: bumi Date: Sun, 15 Apr 2018 19:56:03 +0200 Subject: [PATCH 07/12] remove fake bignumber from tests We make everything bignumber-y now so that is no longer needed --- tests/unit/controllers/index-test.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/controllers/index-test.js b/tests/unit/controllers/index-test.js index 0bce69e..76f11f1 100644 --- a/tests/unit/controllers/index-test.js +++ b/tests/unit/controllers/index-test.js @@ -23,9 +23,6 @@ let addFixtures = function(controller) { { github_username: "trinity", github_uid: "123", balance: 5000 }, { github_username: "mouse", github_uid: "696", balance: 0 } ].forEach(fixture => { - // we expect a bignumer but I don't want to add the bignumber dependency here... so this is some hack to return an object that looks good enough for the test - let fakeBignumber = function(balance) { return { toNumber: function() { return balance; } }; }; - fixture.balance = fakeBignumber(fixture.balance); controller.get('model.contributors').push(Contributor.create(fixture)); }); }; -- 2.50.1 From d425ed6b9ecd1101845c261b79c3a7d6122d5e6d Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Sun, 15 Apr 2018 20:36:21 +0200 Subject: [PATCH 08/12] Move contract event handling into service --- app/controllers/index.js | 57 ---------------------- app/lib/kredits/contracts/base.js | 7 +++ app/services/kredits.js | 73 +++++++++++++++++++++++++++- tests/unit/controllers/index-test.js | 7 +-- 4 files changed, 80 insertions(+), 64 deletions(-) diff --git a/app/controllers/index.js b/app/controllers/index.js index 764a620..6e60243 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -6,15 +6,6 @@ import injectService from 'ember-service/inject'; export default Controller.extend({ kredits: injectService(), - init() { - this._super(...arguments); - let contract = this.get('kredits.kredits').Operator.contract; - contract.onproposalvoted = this._handleProposalVoted.bind(this); - contract.onproposalcreated = this._handleProposalCreated.bind(this); - contract.onproposalexecuted = this._handleProposalExecuted.bind(this); - // TODO: transfer on the token contract - }, - contributors: alias('kredits.contributors'), contributorsWithKredits: filter('contributors', function(contributor) { return contributor.get('balance') !== 0; @@ -29,54 +20,6 @@ export default Controller.extend({ proposalsClosedSorted: sort('proposalsClosed', 'proposalsSorting'), proposalsOpenSorted: sort('proposalsOpen', 'proposalsSorting'), - _handleProposalCreated(proposalId) { - // TODO: check if proposalId is already a string - let proposal = this.get('proposals') - .findBy('id', proposalId.toString()); - if (proposal) { - Ember.Logger.debug('[index] proposal exists, not adding from event'); - return; - } - - proposal = this.get('kredits.kredits').Operator.getById(proposalId); - this.get('proposals').pushObject(proposal); - }, - - _handleProposalExecuted(proposalId, contributorId, amount) { - let proposal = this.get('proposals') - .findBy('id', proposalId); - - if (proposal.get('isExecuted')) { - Ember.Logger.debug('[index] proposal already executed, not adding from event'); - return; - } - - proposal.setProperties({ - 'executed': true, - }); - - this.get('contributors') - .findBy('id', contributorId) - .incrementProperty('balance', amount); - }, - - _handleProposalVoted(proposalId, voter, totalVotes) { - this.get('proposals') - .findBy('id', proposalId) - .setProperties({ 'votesCount': totalVotes }); - }, - - _handleTransfer(from, to, value) { - value = value.toNumber(); - this.get('contributors') - .findBy('address', from) - .decrementProperty('balance', value); - this.get('contributors') - .findBy('address', to) - .incrementProperty('balance', value); - }, - - actions: { confirmProposal(proposalId) { this.get('kredits').vote(proposalId).then(transaction => { diff --git a/app/lib/kredits/contracts/base.js b/app/lib/kredits/contracts/base.js index a7a35aa..c54ca22 100644 --- a/app/lib/kredits/contracts/base.js +++ b/app/lib/kredits/contracts/base.js @@ -7,4 +7,11 @@ export default class Base { return this.contract.functions; } + on(type, callback) { + let eventMethod = `on${type.toLowerCase()}`; + // Don't use this.contract.events here. Seems to be a bug in ethers.js + this.contract[eventMethod] = callback; + + return this; + } } diff --git a/app/services/kredits.js b/app/services/kredits.js index cc25378..5100470 100644 --- a/app/services/kredits.js +++ b/app/services/kredits.js @@ -88,6 +88,22 @@ export default Service.extend({ }).then(({ contributors, proposals }) => { this.set('contributors', contributors); this.set('proposals', proposals); + }).then(() => { + this.get('kredits').Operator + .on('ProposalCreated', (proposalId) => { + this.handleProposalCreated(proposalId); + }) + .on('ProposalVoted', (proposalId, voter, totalVotes) => { + this.handleProposalVoted(proposalId, totalVotes); + }) + .on('ProposalExecuted', (proposalId, contributorId, amount) => { + this.handleProposalExecuted(proposalId, contributorId, amount); + }); + + this.get('kredits').Token + .on('Transfer', (from, to, value) => { + this.handleTransfer(from, to, value); + }); }); }, @@ -169,5 +185,60 @@ export default Service.extend({ return this.get('kredits').Contributor.getById(id); } }); - }) + }), + + findProposalById(proposalId) { + return this.get('proposals').findBy('id', proposalId.toString()); + }, + + // Contract events + handleProposalCreated(proposalId) { + let proposal = this.findProposalById(proposalId); + + if (proposal) { + debug('[events] proposal exists, not adding from event'); + return; + } + + this.get('kredits').Operator.getById(proposalId) + .then((proposal) => { + this.get('proposals').pushObject(proposal); + }); + }, + + // TODO: We may want to reload that proposal to show the voter as voted + handleProposalVoted(proposalId, totalVotes) { + let proposal = this.findProposalById(proposalId); + + if (proposal) { + proposal.set('votesCount', totalVotes); + } + }, + + handleProposalExecuted(proposalId, contributorId, amount) { + let proposal = this.findProposalById(proposalId); + + if (proposal.get('isExecuted')) { + debug('[events] proposal already executed, not adding from event'); + return; + } + + proposal.set('executed', true); + + this.get('contributors') + .findBy('id', contributorId.toString()) + .incrementProperty('balance', amount); + }, + + handleTransfer(from, to, value) { + value = value.toNumber(); + + this.get('contributors') + .findBy('address', from) + .decrementProperty('balance', value); + + this.get('contributors') + .findBy('address', to) + .incrementProperty('balance', value); + }, }); diff --git a/tests/unit/controllers/index-test.js b/tests/unit/controllers/index-test.js index 827d256..bbc749f 100644 --- a/tests/unit/controllers/index-test.js +++ b/tests/unit/controllers/index-test.js @@ -23,12 +23,7 @@ let addFixtures = function(controller) { }; test('doesn\'t contain people with 0 balance', function(assert) { - // This is a bit strange... we do not want the controller to call the init function defined in the controller that - // initializes the event handlers on the contracts. Main reason is that we do not have proper contracts in test mode. - // this seems to work. but probably kills some controller stuff, which is fine in this test - let controller = this.subject({ - init: function() { } - }); + let controller = this.subject(); addFixtures(controller); let contributorsSorted = controller.get('contributorsSorted'); -- 2.50.1 From 89d6b920b1ee65008d41ae6775b10588d4647c10 Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Sun, 15 Apr 2018 20:38:24 +0200 Subject: [PATCH 09/12] Remove unused Ember import --- app/controllers/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/index.js b/app/controllers/index.js index 6e60243..3fb693f 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -1,4 +1,3 @@ -import Ember from 'ember'; import Controller from 'ember-controller'; import { alias, filter, filterBy, sort } from 'ember-computed'; import injectService from 'ember-service/inject'; -- 2.50.1 From a04ab09ed611b4ea259c5117b0f368d1e7624f6f Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Sun, 15 Apr 2018 20:49:17 +0200 Subject: [PATCH 10/12] Move event handler assignment into method --- app/routes/application.js | 5 ++++- app/services/kredits.js | 41 +++++++++++++++++++-------------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/app/routes/application.js b/app/routes/application.js index 8a6a815..f136ff8 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -19,6 +19,9 @@ export default Route.extend({ }, afterModel() { - return this.get('kredits').loadContributorsAndProposals(); + return this.get('kredits').loadContributorsAndProposals() + .then(() => { + this.get('kredits').addContractEventHandlers(); + }); } }); diff --git a/app/services/kredits.js b/app/services/kredits.js index 5100470..470c1ff 100644 --- a/app/services/kredits.js +++ b/app/services/kredits.js @@ -88,35 +88,34 @@ export default Service.extend({ }).then(({ contributors, proposals }) => { this.set('contributors', contributors); this.set('proposals', proposals); - }).then(() => { - this.get('kredits').Operator - .on('ProposalCreated', (proposalId) => { - this.handleProposalCreated(proposalId); - }) - .on('ProposalVoted', (proposalId, voter, totalVotes) => { - this.handleProposalVoted(proposalId, totalVotes); - }) - .on('ProposalExecuted', (proposalId, contributorId, amount) => { - this.handleProposalExecuted(proposalId, contributorId, amount); - }); - - this.get('kredits').Token - .on('Transfer', (from, to, value) => { - this.handleTransfer(from, to, value); - }); }); }, + addContractEventHandlers() { + // Operator events + this.get('kredits').Operator + .on('ProposalCreated', (proposalId) => { + this.handleProposalCreated(proposalId); + }) + .on('ProposalVoted', (proposalId, voter, totalVotes) => { + this.handleProposalVoted(proposalId, totalVotes); + }) + .on('ProposalExecuted', (proposalId, contributorId, amount) => { + this.handleProposalExecuted(proposalId, contributorId, amount); + }); + + // Token events + this.get('kredits').Token + .on('Transfer', (from, to, value) => { + this.handleTransfer(from, to, value); + }); + }, + // TODO: Only assign valid attributes buildModel(name, attributes) { debug('[kredits] build', name, attributes); let model = getOwner(this).lookup(`model:${name}`); - // coerce id to string - if (attributes.id) { - attributes.id = attributes.id.toString(); - } - model.setProperties(attributes); return model; }, -- 2.50.1 From 3839eb2be4e382b9aa2064e631ff178edd23dac3 Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Sun, 15 Apr 2018 21:33:37 +0200 Subject: [PATCH 11/12] Fixes and cleanup --- app/services/kredits.js | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/app/services/kredits.js b/app/services/kredits.js index 470c1ff..91cfa3f 100644 --- a/app/services/kredits.js +++ b/app/services/kredits.js @@ -86,31 +86,11 @@ export default Service.extend({ contributors: this.getContributors(), proposals: this.getProposals(), }).then(({ contributors, proposals }) => { - this.set('contributors', contributors); - this.set('proposals', proposals); + this.get('contributors').pushObjects(contributors); + this.get('proposals').pushObjects(proposals); }); }, - addContractEventHandlers() { - // Operator events - this.get('kredits').Operator - .on('ProposalCreated', (proposalId) => { - this.handleProposalCreated(proposalId); - }) - .on('ProposalVoted', (proposalId, voter, totalVotes) => { - this.handleProposalVoted(proposalId, totalVotes); - }) - .on('ProposalExecuted', (proposalId, contributorId, amount) => { - this.handleProposalExecuted(proposalId, contributorId, amount); - }); - - // Token events - this.get('kredits').Token - .on('Transfer', (from, to, value) => { - this.handleTransfer(from, to, value); - }); - }, - // TODO: Only assign valid attributes buildModel(name, attributes) { debug('[kredits] build', name, attributes); @@ -191,6 +171,18 @@ export default Service.extend({ }, // Contract events + addContractEventHandlers() { + // Operator events + this.get('kredits').Operator + .on('ProposalCreated', this.handleProposalCreated.bind(this)) + .on('ProposalVoted', this.handleProposalVoted.bind(this)) + .on('ProposalExecuted', this.handleProposalExecuted.bind(this)); + + // Token events + this.get('kredits').Token + .on('Transfer', this.handleTransfer.bind(this)); + }, + handleProposalCreated(proposalId) { let proposal = this.findProposalById(proposalId); @@ -201,12 +193,13 @@ export default Service.extend({ this.get('kredits').Operator.getById(proposalId) .then((proposal) => { + proposal = this.buildModel('proposal', proposal); this.get('proposals').pushObject(proposal); }); }, // TODO: We may want to reload that proposal to show the voter as voted - handleProposalVoted(proposalId, totalVotes) { + handleProposalVoted(proposalId, voterId, totalVotes) { let proposal = this.findProposalById(proposalId); if (proposal) { @@ -226,7 +219,7 @@ export default Service.extend({ this.get('contributors') .findBy('id', contributorId.toString()) - .incrementProperty('balance', amount); + .incrementProperty('balance', amount.toNumber()); }, handleTransfer(from, to, value) { -- 2.50.1 From 1761ffbd3b5fa0a28c1001b9143454aaff4e3483 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Mon, 16 Apr 2018 12:49:49 +0200 Subject: [PATCH 12/12] Update README Improve description and instructions for running against a local development chain/node and IPFS node. --- README.md | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 43c6aa9..73106b6 100644 --- a/README.md +++ b/README.md @@ -44,36 +44,50 @@ Make use of the many generators for code, try `ember help generate` for more det ### Deploying -Specify what it takes to deploy your app. +_(You need collaborator permissions on the 5apps Deploy project.)_ +`npm run deploy` ## Working with locally deployed contracts -For development you should checkout [truffle-kredits](https://github.com/67P/truffle-kredits). -See the [README](https://github.com/67P/truffle-kredits/#readme) how to setup `truffle-kredits`. +The smart contracts and their JavaScript wrapper library are developed in the +[truffle-kredits](https://github.com/67P/truffle-kredits) repo/package. -### Basic idea: +You can run `kredits-web` on your machine, against a local, simulated Ethereum +network, provided e.g. by [ganache](http://truffleframework.com/ganache/) or +[ganache-cli](https://github.com/trufflesuite/ganache-cli). -To run the app locally with a locally deployed contract most likely in a ethereum simulator like [ganache](http://truffleframework.com/ganache/) or [ganache-cli](https://github.com/trufflesuite/ganache-cli) -[truffle-kredits](https://github.com/67P/truffle-kredits) holds all the tools to start and setup that local simulation network (and generally to deply the contracts) +[truffle-kredits](https://github.com/67P/truffle-kredits) holds all the tools +to start and set up such a simulated network, as well as to deploy smart +contracts to it. -Get familiar with truffle and truffle-kredits, but these are the basic steps to get up and running: +These are the basic steps to get up and running: -1. truffle-kredits (get the local ethereum node running) - * setup (clone and npm install) truffle-kredits +#### 1. IPFS + +Run a local IPFS deamon in offline mode. + + * Make sure CORS headers are configured. See [IPFS](#ipfs) for more info. + * `ipfs daemon --offline` + +#### 2. truffle-kredits + +Get your local Ethereum development node running. + + * Clone [truffle-kredits](https://github.com/67P/truffle-kredits) + * `npm install` * `npm run ganache` - which is basically: `ganache-cli -p 7545 -i 100` (we use the non-default port for local networks and a fixed network id) * `npm run bootstrap` - bootstrap runs fresh migrations, adds some seed data and writes the address/abi information to JSON that will be used by kredits-web - * `npm link` - link the truffle-kredits dependency to kredits-web + * `npm link` - make the `truffle-kredits` module linkable as `kredits-contracts` on your machine -2. IPFS (run a local ipfs deamon in offline mode) - * make sure the ipfs cors header are configured - See [IPFS](#ipfs) for more configurations - * `ipfs daemon --offline` +#### 3. kredits-web -3. kredits-web - * `npm link kredits-contracts` - link the local truffle-kredits package (attention: the naming! we need to make the new truffle-kredits the official kredits-contracts package) - * `ember serve` +With IPFS and Ethereum/ganache running, you can now start this Ember app. -## IPFS + * `npm link kredits-contracts` - link the local `truffle-kredits` package (it will become `kredits-contracts` soon) + * `npm start` + +#### IPFS Install IPFS with your favorite package manager and run -- 2.50.1