From 7e04cef964473e6ff6bf639487f3e3bd204c8e3c Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Mon, 9 Apr 2018 02:09:07 +0200 Subject: [PATCH 01/11] Extract kredits module --- app/controllers/index.js | 14 +- app/lib/kredits/contracts/base.js | 37 +++ app/lib/kredits/contracts/contributor.js | 72 +++++ app/lib/kredits/contracts/index.js | 9 + app/lib/kredits/contracts/operator.js | 72 +++++ app/lib/kredits/contracts/token.js | 4 + app/lib/kredits/index.js | 11 +- app/lib/kredits/kredits.js | 77 +++++ app/lib/kredits/serializers/contribution.js | 2 +- app/lib/kredits/serializers/contributor.js | 2 +- app/routes/application.js | 2 +- app/routes/index.js | 3 +- app/services/ipfs.js | 34 --- app/services/kredits.js | 322 ++++---------------- app/templates/index.hbs | 2 +- 15 files changed, 343 insertions(+), 320 deletions(-) create mode 100644 app/lib/kredits/contracts/base.js create mode 100644 app/lib/kredits/contracts/contributor.js create mode 100644 app/lib/kredits/contracts/index.js create mode 100644 app/lib/kredits/contracts/operator.js create mode 100644 app/lib/kredits/contracts/token.js create mode 100644 app/lib/kredits/kredits.js delete mode 100644 app/services/ipfs.js diff --git a/app/controllers/index.js b/app/controllers/index.js index fce8ada..4f18593 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -8,13 +8,11 @@ export default Controller.extend({ init() { this._super(...arguments); - this.get('kredits.kreditsContract') - .then((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 - }); + 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('model.contributors'), @@ -50,7 +48,7 @@ export default Controller.extend({ return; } - proposal = this.get('kredits').getProposalById(proposalId); + proposal = this.get('kredits.kredits').Operator.getById(proposalId); this.get('proposals').pushObject(proposal); }, diff --git a/app/lib/kredits/contracts/base.js b/app/lib/kredits/contracts/base.js new file mode 100644 index 0000000..ffafeaf --- /dev/null +++ b/app/lib/kredits/contracts/base.js @@ -0,0 +1,37 @@ +import Kredits from '../kredits'; +import { fromBytes32 } from '../utils/multihash'; + +export default class Base { + constructor(contract) { + this.contract = contract; + } + + get functions() { + return this.contract.functions; + } + + // TODO: move into utils + fetchAndMergeIpfsData(data, Serializer) { + let ipfsHash = data.ipfsHash; + + if (!ipfsHash) { + return data; + } + + return Kredits.ipfs + .cat(ipfsHash) + .then(Serializer.deserialize) + .then((attributes) => { + return Object.assign({}, data, attributes); + }); + } + + reassembleIpfsHash(data) { + let { ipfsHash: digest, hashFunction, hashSize } = data; + data.ipfsHash = fromBytes32({ digest, hashFunction, hashSize }); + delete data.hashFunction; + delete data.hashSize; + + return data; + } +} diff --git a/app/lib/kredits/contracts/contributor.js b/app/lib/kredits/contracts/contributor.js new file mode 100644 index 0000000..6f4c790 --- /dev/null +++ b/app/lib/kredits/contracts/contributor.js @@ -0,0 +1,72 @@ +import ethers from 'npm:ethers'; +import RSVP from 'rsvp'; + +import Kredits from '../kredits'; +import ContributorSerializer from '../serializers/contributor'; +import { toBytes32 } from '../utils/multihash'; + +import Base from './base'; + +export default class Contributor extends Base { + all() { + return this.contract.functions.contributorsCount() + .then((count) => { + count = count.toNumber(); + let contributors = []; + + for (let id = 1; id <= count; id++) { + contributors.push(this.getById(id)); + } + + return RSVP.all(contributors); + }); + } + + getById(id) { + id = ethers.utils.bigNumberify(id); + + return this.contract.functions.getContributorById(id) + .then(this.reassembleIpfsHash) + .then((data) => { + // TODO: remove as soon as the contract provides the id + data.id = id; + // TODO: rename address to account + data.address = data.account; + + return data; + }) + // Fetch IPFS data if available + .then((data) => { + return this.fetchAndMergeIpfsData(data, ContributorSerializer); + }); + } + + add(attributes) { + console.log(attributes); + + let json = ContributorSerializer.serialize(attributes); + // TODO: validate against schema + + return Kredits.ipfs + .add(new Kredits.ipfs.Buffer(json)) + .then((res) => res[0].hash) + .then((ipfsHash) => { + Object.assign(attributes, toBytes32(ipfsHash)); + return attributes; + }) + .then((attributes) => { + let { address, digest, hashFunction, hashSize, isCore } = attributes; + + let contributor = [ + address, + digest, + hashFunction, + hashSize, + isCore, + ]; + + console.log('[kredits] addContributor', ...contributor); + return this.contract.functions.addContributor(...contributor); + }); + } +} diff --git a/app/lib/kredits/contracts/index.js b/app/lib/kredits/contracts/index.js new file mode 100644 index 0000000..4ef4162 --- /dev/null +++ b/app/lib/kredits/contracts/index.js @@ -0,0 +1,9 @@ +import Contributor from './contributor'; +import Operator from './operator'; +import Token from './token'; + +export default { + Contributors: Contributor, + Operator, + Token +}; diff --git a/app/lib/kredits/contracts/operator.js b/app/lib/kredits/contracts/operator.js new file mode 100644 index 0000000..1bff044 --- /dev/null +++ b/app/lib/kredits/contracts/operator.js @@ -0,0 +1,72 @@ +import ethers from 'npm:ethers'; +import RSVP from 'rsvp'; + +import Kredits from '../kredits'; +import ContributionSerializer from '../serializers/contribution'; +import { toBytes32 } from '../utils/multihash'; + +import Base from './base'; + +export default class Operator extends Base { + all() { + return this.contract.functions.proposalsCount() + .then((count) => { + count = count.toNumber(); + let proposals = []; + + for (let id = 0; id < count; id++) { + proposals.push(this.getById(id)); + } + + return RSVP.all(proposals); + }); + } + + getById(id) { + id = ethers.utils.bigNumberify(id); + + return this.contract.functions.proposals(id) + .then(this.reassembleIpfsHash) + .then((data) => { + // TODO: remove as soon as the contract provides the id + data.id = id; + // TODO: rename creatorAddress to creator + data.creatorAddress = data.creator; + + return data; + }) + // Fetch IPFS data if available + .then((data) => { + return this.fetchAndMergeIpfsData(data, ContributionSerializer); + }); + } + + addProposal(attributes) { + console.log(attributes); + + let json = ContributionSerializer.serialize(attributes); + // TODO: validate against schema + + return Kredits.ipfs + .add(new Kredits.ipfs.Buffer(json)) + .then((res) => res[0].hash) + .then((ipfsHash) => { + Object.assign(attributes, toBytes32(ipfsHash)); + return attributes; + }) + .then((attributes) => { + let { recipientId, amount, digest, hashFunction, hashSize } = attributes; + + let proposal = [ + recipientId, + amount, + digest, + hashFunction, + hashSize, + ]; + + console.log('[kredits] addProposal', ...proposal); + return this.contract.functions.addProposal(...proposal); + }); + } +} diff --git a/app/lib/kredits/contracts/token.js b/app/lib/kredits/contracts/token.js new file mode 100644 index 0000000..42c97ee --- /dev/null +++ b/app/lib/kredits/contracts/token.js @@ -0,0 +1,4 @@ +import Base from './base'; + +export default class Token extends Base { +} diff --git a/app/lib/kredits/index.js b/app/lib/kredits/index.js index 7042bcf..eeb0011 100644 --- a/app/lib/kredits/index.js +++ b/app/lib/kredits/index.js @@ -1,10 +1 @@ -import ContributionSerializer from './serializers/contribution'; -import ContributorSerializer from './serializers/contributor'; -import { fromBytes32, toBytes32 } from './utils/multihash'; - -export { - ContributionSerializer, - ContributorSerializer, - fromBytes32, - toBytes32 -}; +export { default } from './kredits'; diff --git a/app/lib/kredits/kredits.js b/app/lib/kredits/kredits.js new file mode 100644 index 0000000..1531a0b --- /dev/null +++ b/app/lib/kredits/kredits.js @@ -0,0 +1,77 @@ +import ethers from 'npm:ethers'; +import ipfsAPI from 'npm:ipfs-api'; +import RSVP from 'rsvp'; + +import abis from 'contracts/abis'; +import addresses from 'contracts/addresses'; + +import contracts from './contracts'; + +// Helpers +function capitalize(word) { + let [first, ...rest] = word; + return `${first.toUpperCase()}${rest.join('')}`; +} + +export default class Kredits { + constructor(provider, signer, addresses) { + this.provider = provider; + this.signer = signer; + + // Initialize our registry contract + this.addresses = addresses; + this.contracts = {}; + } + + static setup(provider, signer, ipfsConfig) { + this.ipfsConfig = ipfsConfig; + this.ipfs = ipfsAPI(ipfsConfig); + + let registryContract = this.initRegistryContract(provider); + + let addresses = Object.keys(contracts).reduce((mem, name) => { + let contractName = capitalize(name); + mem[contractName] = registryContract.functions.getProxyFor(contractName); + return mem; + }, {}); + + return RSVP.hash(addresses) + .then((addresses) => { + return new Kredits(provider, signer, addresses); + }); + } + + static initRegistryContract(provider) { + let address = addresses['Registry'][provider.chainId]; + let abi = abis['Registry']; + console.log('Initialize registry contract:', address, abi, provider); + return new ethers.Contract(address, abi, provider); + } + + get Contributor() { + // TODO: rename to contributor + return this.contractFor('contributors'); + } + + get Operator() { + return this.contractFor('operator'); + } + + get Token() { + return this.contractFor('token'); + } + + // Should be private + contractFor(name) { + if (this.contracts[name]) { + return this.contracts[name]; + } + + let contractName = capitalize(name); + let address = this.addresses[contractName]; + let contract = new ethers.Contract(address, abis[contractName], this.signer); + this.contracts[name] = new contracts[contractName](contract); + + return this.contracts[name]; + } +} diff --git a/app/lib/kredits/serializers/contribution.js b/app/lib/kredits/serializers/contribution.js index 0802eea..2bed268 100644 --- a/app/lib/kredits/serializers/contribution.js +++ b/app/lib/kredits/serializers/contribution.js @@ -18,7 +18,7 @@ export default class Contributor { description, details, url, - } = JSON.parse(serialized); + } = JSON.parse(serialized.toString('utf8')); return { kind, diff --git a/app/lib/kredits/serializers/contributor.js b/app/lib/kredits/serializers/contributor.js index 80df182..5967903 100644 --- a/app/lib/kredits/serializers/contributor.js +++ b/app/lib/kredits/serializers/contributor.js @@ -18,7 +18,7 @@ export default class Contributor { kind, url, accounts, - } = JSON.parse(serialized); + } = JSON.parse(serialized.toString('utf8')); let github_username, github_uid, wiki_username; let github = accounts.find((a) => a.site === 'github.com'); diff --git a/app/routes/application.js b/app/routes/application.js index 764b2eb..9371c64 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -7,7 +7,7 @@ export default Route.extend({ beforeModel(transition) { const kredits = this.get('kredits'); - return kredits.initEthProvider().then(() => { + return kredits.setup().then(() => { if (kredits.get('accountNeedsUnlock')) { if (confirm('It looks like you have an Ethereum wallet available. Please unlock your account.')) { transition.retry(); diff --git a/app/routes/index.js b/app/routes/index.js index d032132..437e4f3 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -9,8 +9,7 @@ export default Ember.Route.extend({ newContributor.set('kind', 'person'); let kredits = this.get('kredits'); - let totalSupply = kredits.get('tokenContract') - .then((contract) => contract.totalSupply()); + let totalSupply = kredits.get('kredits').Token.functions.totalSupply(); return Ember.RSVP.hash({ contributors: kredits.getContributors(), diff --git a/app/services/ipfs.js b/app/services/ipfs.js deleted file mode 100644 index 0f4606f..0000000 --- a/app/services/ipfs.js +++ /dev/null @@ -1,34 +0,0 @@ -import Ember from 'ember'; -import ipfsAPI from 'npm:ipfs-api'; -import config from 'kredits-web/config/environment'; - -export default Ember.Service.extend({ - - ipfsInstance: null, - - ipfs: function() { - if (this.get('ipfsInstance')) { - return this.get('ipfsInstance'); - } - let ipfs = ipfsAPI(config.ipfs); - this.set('ipfsInstance', ipfs); - return ipfs; - }.property('ipfsInstance'), - - storeFile(content) { - let ipfs = this.get('ipfs'); - return ipfs.add(new ipfs.Buffer(content)).then(res => { - Ember.Logger.debug('[ipfs] stored content in IPFS', content, res[0].hash); - return res[0].hash; - }); - }, - - getFile(hash) { - return this.get('ipfs').cat(hash).then(res => { - return res.toString('utf8'); - }, err => { - Ember.Logger.warn('[ipfs] error trying to fetch file', hash, err); - }); - } - -}); diff --git a/app/services/kredits.js b/app/services/kredits.js index e0a8334..8f4f477 100644 --- a/app/services/kredits.js +++ b/app/services/kredits.js @@ -1,36 +1,21 @@ import ethers from 'npm:ethers'; - +import Kredits from 'kredits-web/lib/kredits'; import RSVP from 'rsvp'; import Ember from 'ember'; import Service from 'ember-service'; -import injectService from 'ember-service/inject'; import computed, { alias } from 'ember-computed'; import { isEmpty, isPresent } from 'ember-utils'; import config from 'kredits-web/config/environment'; -import abis from 'contracts/abis'; -import addresses from 'contracts/addresses'; -import { - ContributionSerializer, - ContributorSerializer, - fromBytes32, - toBytes32 -} from 'kredits-web/lib/kredits'; - const { getOwner, Logger: { - debug, - error + debug } } = Ember; - export default Service.extend({ - - ipfs: injectService(), - ethProvider: null, currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded. currentUser: null, @@ -48,7 +33,7 @@ export default Service.extend({ // this is called called in the routes beforeModel(). So it is initialized before everything else // and we can rely on the ethProvider and the potential currentUserAccounts to be available initEthProvider: function() { - return new Ember.RSVP.Promise((resolve) => { + return new RSVP.Promise((resolve) => { let ethProvider; let networkId; if (typeof window.web3 !== 'undefined') { @@ -58,14 +43,7 @@ export default Service.extend({ ethProvider.listAccounts().then((accounts) => { this.set('currentUserAccounts', accounts); this.set('ethProvider', ethProvider); - if (accounts.length > 0) { - this.get('getCurrentUser').then((contributorData) => { - this.set('currentUser', contributorData); - resolve(ethProvider); - }); - } else { - resolve(ethProvider); - } + resolve(ethProvider); }); } else { debug('[kredits] Creating new instance from npm module class'); @@ -79,36 +57,24 @@ export default Service.extend({ }); }, - registryContract: computed('ethProvider', function() { - let networkId = this.get('ethProvider').chainId; - let registry = new ethers.Contract( - addresses['Registry'][networkId], - abis['Registry'], - this.get('ethProvider') - ); - return registry; - }), + setup() { + return this.initEthProvider().then((ethProvider) => { + let signer = ethProvider.getSigner(); + return Kredits.setup(ethProvider, signer, config.ipfs).then((kredits) => { + this.set('kredits', kredits); - contributorsContract: computed('ethProvider', function() { - return this.contractFor('Contributors'); - }), - - kreditsContract: computed('ethProvider', function() { - return this.contractFor('Operator'); - }), - - tokenContract: computed('ethProvider', function() { - return this.contractFor('Token'); - }), - - contractFor(name) { - return this.get('registryContract').functions.getProxyFor(name) - .then((address) => { - debug('[kredits] get contract', name, address); - return new ethers.Contract(address, abis[name], this.get('ethProvider').getSigner()); - }); + // TODO: Cleanup + if (this.get('currentUserAccounts').length > 0) { + this.get('getCurrentUser').then((contributorData) => { + this.set('currentUser', contributorData); + }); + } + return kredits; + }); + }); }, + // TODO: Only assign valid attributes buildModel(name, attributes) { debug('[kredits] build', name, attributes); let model = getOwner(this).lookup(`model:${name}`); @@ -122,237 +88,69 @@ export default Service.extend({ return model; }, - getContributorById(id) { - id = ethers.utils.bigNumberify(id); - - return this.get('contributorsContract') - .then((contract) => contract.getContributorById(id)) - .then(this.reassembleIpfsHash) - // Set basic data - .then(({ account: address, balance, ipfsHash, isCore }) => { - let isCurrentUser = this.get('currentUserAccounts').includes(address); - - return { - id: id, - address, - balance: balance.toNumber(), - ipfsHash, - isCore, - isCurrentUser, - }; - }) - // Fetch IPFS data if available - .then((data) => { - return this.fetchAndMergeIpfsData(data, ContributorSerializer); - }) - .then((attributes) => { - return this.buildModel('contributor', attributes); - }); - }, - - getContributors() { - return this.get('contributorsContract') - .then((contract) => contract.contributorsCount()) - .then((count) => { - count = count.toNumber(); - debug('[kredits] contributors count:', count); - let contributors = []; - - for(var id = 1; id <= count; id++) { - contributors.push(this.getContributorById(id)); - } - - return RSVP.all(contributors); - }); - }, - - reassembleIpfsHash(data) { - let { ipfsHash: digest, hashFunction, hashSize } = data; - data.ipfsHash = fromBytes32({ digest, hashFunction, hashSize }); - delete data.hashFunction; - delete data.hashSize; - - return data; - }, - - /** - * Loads the contributor's profile data from IPFS and returns the attributes - * - * @method - * @public - */ - fetchAndMergeIpfsData(data, Serializer) { - let ipfsHash = data.ipfsHash; - - if (!ipfsHash) { - return data; - } - - return this.get('ipfs') - .getFile(ipfsHash) - .then(Serializer.deserialize) - .then((attributes) => { - debug('[kredits] fetched ipfs data:', attributes); - return Object.assign({}, data, attributes); - }) - .catch((err) => { - error('[kredits] error trying to fetch', ipfsHash, err); - }); - }, - - getProposalById(id) { - id = ethers.utils.bigNumberify(id); - - return this.get('kreditsContract') - .then((contract) => contract.proposals(id)) - .then(this.reassembleIpfsHash) - // Set basic data - .then(({ - creator: creatorAddress, - recipientId, - votesCount, - votesNeeded, - amount, - executed, - ipfsHash, - }) => { - return { - id: id, - creatorAddress, - recipientId: recipientId.toString(), - votesCount: votesCount.toNumber(), - votesNeeded: votesNeeded.toNumber(), - amount: amount.toNumber(), - executed, - ipfsHash - }; - }) - // Fetch IPFS data if available - .then((data) => { - return this.fetchAndMergeIpfsData(data, ContributionSerializer); - }) - .then((attributes) => { - return this.buildModel('proposal', attributes); - }); - }, - - getProposals() { - return this.get('kreditsContract') - .then((contract) => contract.proposalsCount()) - .then((count) => { - count = count.toNumber(); - debug('[kredits] proposals count:', count); - let proposals = []; - - for(var i = 0; i < count; i++) { - proposals.push(this.getProposalById(i)); - } - - return RSVP.all(proposals); - }); - }, - - vote(proposalId) { - debug('[kredits] vote for', proposalId); - - return this.get('kreditsContract') - .then((contract) => contract.vote(proposalId)) - .then((data) => { - debug('[kredits] vote response', data); - return data; - }); - }, - - // TODO: extract common logic to module addContributor(attributes) { debug('[kredits] add contributor', attributes); - let json = ContributorSerializer.serialize(attributes); - // TODO: validate against schema - - return this.get('ipfs') - .storeFile(json) - // Set ipfsHash - .then((ipfsHash) => { - attributes.ipfsHash = ipfsHash; - return attributes; - }) - .then((attributes) => { - return this.get('contributorsContract') - .then((contract) => { - let { address, isCore, ipfsHash } = attributes; - let { digest, hashFunction, hashSize } = toBytes32(ipfsHash); - - let contributor = [ - address, - digest, - hashFunction, - hashSize, - isCore, - ]; - debug('[kredits] addContributor', ...contributor); - return contract.addContributor(...contributor); - }); - }) + return this.get('kredits').Contributor.add(attributes) .then((data) => { debug('[kredits] add contributor response', data); return this.buildModel('contributor', attributes); }); }, + getContributors() { + return this.get('kredits').Contributor.all() + .then((contributors) => { + return contributors.map((contributor) => { + return this.buildModel('contributor', contributor); + }); + }); + }, + addProposal(attributes) { debug('[kredits] add proposal', attributes); - let json = ContributionSerializer.serialize(attributes); - // TODO: validate against schema - - return this.get('ipfs') - .storeFile(json) - // Set ipfsHash - .then((ipfsHash) => { - delete attributes.contributorIpfsHash; - attributes.ipfsHash = ipfsHash; - return attributes; - }) - .then((attributes) => { - return this.get('kreditsContract') - .then((contract) => { - let { recipientId, amount, ipfsHash } = attributes; - let { digest, hashFunction, hashSize } = toBytes32(ipfsHash); - - let proposal = [ - recipientId, - amount, - digest, - hashFunction, - hashSize, - ]; - debug('[kredits] addProposal', ...proposal); - return contract.addProposal(...proposal); - }); - }) + return this.get('kredits').Operator.addProposal(attributes) .then((data) => { debug('[kredits] add proposal response', data); return this.buildModel('proposal', attributes); }); }, + getProposals() { + return this.get('kredits').Operator.all() + .then((proposals) => { + return proposals.map((proposal) => { + return this.buildModel('proposal', proposal); + }); + }); + }, + + vote(proposalId) { + debug('[kredits] vote for', proposalId); + + return this.get('kredits').Operator.functions.vote(proposalId) + .then((data) => { + debug('[kredits] vote response', data); + return data; + }); + }, + + // TODO: Cleanup getCurrentUser: computed('ethProvider', function() { if (isEmpty(this.get('currentUserAccounts'))) { return RSVP.resolve(); } - return this.get('contributorsContract') - .then((contract) => { - return contract.getContributorIdByAddress(this.get('currentUserAccounts.firstObject')) - .then((id) => { - id = id.toNumber(); - // check if the user is a contributor or not - if (id === 0) { - return RSVP.resolve(); - } else { - return this.getContributorById(id); - } - }); + return this.get('kredits').Contributor + .functions.getContributorIdByAddress(this.get('currentUserAccounts.firstObject')) + .then((id) => { + id = id.toNumber(); + // check if the user is a contributor or not + if (id === 0) { + return RSVP.resolve(); + } else { + return this.get('kredits').Contributor.getById(id); + } }); }) }); diff --git a/app/templates/index.hbs b/app/templates/index.hbs index cf22e2a..5ad6e8b 100644 --- a/app/templates/index.hbs +++ b/app/templates/index.hbs @@ -68,7 +68,7 @@
- {{#if currentUser.isCore}} + {{#if kredits.currentUser.isCore}} {{add-contributor contributors=model.contributors save=(action 'save')}} {{else}} Only core team members can add new contributors. Please ask someone to set you up. From e527099b007750b08ddcdf973e51e0c9be45bdce Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Mon, 9 Apr 2018 02:26:03 +0200 Subject: [PATCH 02/11] Fix tests --- app/controllers/index.js | 2 +- tests/unit/controllers/index-test.js | 10 +++++----- .../lib/kredits/serializers/contribution-test.js | 2 +- .../unit/lib/kredits/serializers/contributor-test.js | 2 +- tests/unit/services/ipfs-test.js | 12 ------------ 5 files changed, 8 insertions(+), 20 deletions(-) delete mode 100644 tests/unit/services/ipfs-test.js diff --git a/app/controllers/index.js b/app/controllers/index.js index 4f18593..387e259 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -17,7 +17,7 @@ export default Controller.extend({ contributors: alias('model.contributors'), contributorsWithKredits: filter('contributors', function(contributor) { - return contributor.get('balance') !== 0; + return contributor.get('balance').toString() !== "0"; }), contributorsSorting: ['balance:desc'], contributorsSorted: sort('contributorsWithKredits', 'contributorsSorting'), diff --git a/tests/unit/controllers/index-test.js b/tests/unit/controllers/index-test.js index 45c2465..a0bdd28 100644 --- a/tests/unit/controllers/index-test.js +++ b/tests/unit/controllers/index-test.js @@ -8,7 +8,7 @@ const { } = Ember; moduleFor('controller:index', 'Unit | Controller | index', { - needs: ['service:ipfs', 'service:kredits'] + needs: ['service:kredits'] }); let addFixtures = function(controller) { @@ -18,10 +18,10 @@ let addFixtures = function(controller) { }); [ - { github_username: "neo", github_uid: "318", balance: 10000 }, - { github_username: "morpheus", github_uid: "843", balance: 15000 }, - { github_username: "trinity", github_uid: "123", balance: 5000 }, - { github_username: "mouse", github_uid: "696", balance: 0 } + { github_username: "neo", github_uid: "318", balance: "10000" }, + { github_username: "morpheus", github_uid: "843", balance: "15000" }, + { github_username: "trinity", github_uid: "123", balance: "5000" }, + { github_username: "mouse", github_uid: "696", balance: "0" } ].forEach(fixture => { controller.get('model.contributors').push(Contributor.create(fixture)); }); diff --git a/tests/unit/lib/kredits/serializers/contribution-test.js b/tests/unit/lib/kredits/serializers/contribution-test.js index 7d0b02a..e0f5cf1 100644 --- a/tests/unit/lib/kredits/serializers/contribution-test.js +++ b/tests/unit/lib/kredits/serializers/contribution-test.js @@ -1,7 +1,7 @@ import { module, test } from 'ember-qunit'; import schemas from 'npm:kosmos-schemas'; import tv4 from 'npm:tv4'; -import { ContributionSerializer } from 'kredits-web/lib/kredits'; +import ContributionSerializer from 'kredits-web/lib/kredits/serializers/contribution'; module('Serializers contribution'); diff --git a/tests/unit/lib/kredits/serializers/contributor-test.js b/tests/unit/lib/kredits/serializers/contributor-test.js index 9c08650..cd17b75 100644 --- a/tests/unit/lib/kredits/serializers/contributor-test.js +++ b/tests/unit/lib/kredits/serializers/contributor-test.js @@ -1,7 +1,7 @@ import { module, test } from 'ember-qunit'; import schemas from 'npm:kosmos-schemas'; import tv4 from 'npm:tv4'; -import { ContributorSerializer } from 'kredits-web/lib/kredits'; +import ContributorSerializer from 'kredits-web/lib/kredits/serializers/contributor'; module('Serializers contributor'); diff --git a/tests/unit/services/ipfs-test.js b/tests/unit/services/ipfs-test.js deleted file mode 100644 index 3a10cca..0000000 --- a/tests/unit/services/ipfs-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { moduleFor, test } from 'ember-qunit'; - -moduleFor('service:ipfs', 'Unit | Service | ipfs', { - // Specify the other units that are required for this test. - // needs: ['service:foo'] -}); - -// Replace this with your real tests. -test('it exists', function(assert) { - let service = this.subject(); - assert.ok(service); -}); From 41a04d6b53c7f214e6a46cacba56d59972b543da Mon Sep 17 00:00:00 2001 From: bumi Date: Mon, 9 Apr 2018 14:52:39 +0200 Subject: [PATCH 03/11] Refactor IPFS hash handling This now uses the multihashes dependency (which is an ipfs dependency) to decode/encode the ipfs hashes. --- app/lib/kredits/contracts/base.js | 22 +++++++++++----------- app/lib/kredits/contracts/contributor.js | 3 +-- app/lib/kredits/contracts/operator.js | 19 ++++++++----------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/app/lib/kredits/contracts/base.js b/app/lib/kredits/contracts/base.js index ffafeaf..6ac690f 100644 --- a/app/lib/kredits/contracts/base.js +++ b/app/lib/kredits/contracts/base.js @@ -1,5 +1,5 @@ import Kredits from '../kredits'; -import { fromBytes32 } from '../utils/multihash'; +import multihashes from 'npm:multihashes'; export default class Base { constructor(contract) { @@ -12,11 +12,11 @@ export default class Base { // TODO: move into utils fetchAndMergeIpfsData(data, Serializer) { - let ipfsHash = data.ipfsHash; - - if (!ipfsHash) { + if (!data.hashSize || data.hashSize === 0) { return data; } + let digest = Kredits.ipfs.Buffer.from(data.ipfsHash.slice(2), 'hex'); + let ipfsHash = multihashes.encode(digest, data.hashFunction, data.hashSize); return Kredits.ipfs .cat(ipfsHash) @@ -26,12 +26,12 @@ export default class Base { }); } - reassembleIpfsHash(data) { - let { ipfsHash: digest, hashFunction, hashSize } = data; - data.ipfsHash = fromBytes32({ digest, hashFunction, hashSize }); - delete data.hashFunction; - delete data.hashSize; - - return data; + decodeIpfsHash(ipfsHash) { + let multihash = multihashes.decode(multihashes.fromB58String(ipfsHash)); + return { + ipfsHash: '0x' + multihashes.toHexString(multihash.digest), + hashSize: multihash.length, + hashFunction: multihash.code + }; } } diff --git a/app/lib/kredits/contracts/contributor.js b/app/lib/kredits/contracts/contributor.js index 6f4c790..9340eff 100644 --- a/app/lib/kredits/contracts/contributor.js +++ b/app/lib/kredits/contracts/contributor.js @@ -26,7 +26,6 @@ export default class Contributor extends Base { id = ethers.utils.bigNumberify(id); return this.contract.functions.getContributorById(id) - .then(this.reassembleIpfsHash) .then((data) => { // TODO: remove as soon as the contract provides the id data.id = id; @@ -51,7 +50,7 @@ export default class Contributor extends Base { .add(new Kredits.ipfs.Buffer(json)) .then((res) => res[0].hash) .then((ipfsHash) => { - Object.assign(attributes, toBytes32(ipfsHash)); + Object.assign(attributes, this.decodeIpfsHash(ipfsHash)); return attributes; }) .then((attributes) => { diff --git a/app/lib/kredits/contracts/operator.js b/app/lib/kredits/contracts/operator.js index 1bff044..e0e860c 100644 --- a/app/lib/kredits/contracts/operator.js +++ b/app/lib/kredits/contracts/operator.js @@ -1,5 +1,6 @@ import ethers from 'npm:ethers'; import RSVP from 'rsvp'; +import multihashes from 'npm:multihashes'; import Kredits from '../kredits'; import ContributionSerializer from '../serializers/contribution'; @@ -26,7 +27,6 @@ export default class Operator extends Base { id = ethers.utils.bigNumberify(id); return this.contract.functions.proposals(id) - .then(this.reassembleIpfsHash) .then((data) => { // TODO: remove as soon as the contract provides the id data.id = id; @@ -51,20 +51,17 @@ export default class Operator extends Base { .add(new Kredits.ipfs.Buffer(json)) .then((res) => res[0].hash) .then((ipfsHash) => { - Object.assign(attributes, toBytes32(ipfsHash)); + Object.assign(attributes, this.decodeIpfsHash(ipfsHash)); return attributes; }) - .then((attributes) => { - let { recipientId, amount, digest, hashFunction, hashSize } = attributes; - + .then((attr) => { let proposal = [ - recipientId, - amount, - digest, - hashFunction, - hashSize, + parseInt(attr.recipientId), + parseInt(attr.amount), + attr.ipfsHash, + attr.hashFunction, + attr.hashSize, ]; - console.log('[kredits] addProposal', ...proposal); return this.contract.functions.addProposal(...proposal); }); From b9fa0e2d603e5d2c170927d9ef3a136f28161461 Mon Sep 17 00:00:00 2001 From: bumi Date: Mon, 9 Apr 2018 14:58:25 +0200 Subject: [PATCH 04/11] typo --- app/components/add-proposal/component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/add-proposal/component.js b/app/components/add-proposal/component.js index e605fd6..aba9926 100644 --- a/app/components/add-proposal/component.js +++ b/app/components/add-proposal/component.js @@ -53,7 +53,7 @@ export default Component.extend({ saved.then(() => { this.reset(); window.scroll(0,0); - window.alert('Contributor added.'); + window.alert('Proposal added.'); }); } } From ce8fef79dc5fd8f4552101b8ee73c433cce562e1 Mon Sep 17 00:00:00 2001 From: bumi Date: Mon, 9 Apr 2018 14:58:50 +0200 Subject: [PATCH 05/11] Search proposal contributor by stringified ID Kredits returns default web3/ethers.js values which are BigNumbers. Internally we use Strings. We need to do this somewhere centrally though. --- 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 387e259..6d0195c 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('recipientId').toString()); proposal.set('contributor', contributor); From e9f0638ca24a062d549141f867ec17e840ecef87 Mon Sep 17 00:00:00 2001 From: bumi Date: Mon, 9 Apr 2018 15:05:13 +0200 Subject: [PATCH 06/11] Unify Contributor/Proposal saving --- app/lib/kredits/contracts/contributor.js | 14 ++++++-------- app/lib/kredits/contracts/operator.js | 5 +++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/lib/kredits/contracts/contributor.js b/app/lib/kredits/contracts/contributor.js index 9340eff..5504eee 100644 --- a/app/lib/kredits/contracts/contributor.js +++ b/app/lib/kredits/contracts/contributor.js @@ -53,15 +53,13 @@ export default class Contributor extends Base { Object.assign(attributes, this.decodeIpfsHash(ipfsHash)); return attributes; }) - .then((attributes) => { - let { address, digest, hashFunction, hashSize, isCore } = attributes; - + .then((attr) => { let contributor = [ - address, - digest, - hashFunction, - hashSize, - isCore, + attr.address, + attr.ipfsHash, + attr.hashFunction, + attr.hashSize, + attr.isCore, ]; console.log('[kredits] addContributor', ...contributor); diff --git a/app/lib/kredits/contracts/operator.js b/app/lib/kredits/contracts/operator.js index e0e860c..0adf525 100644 --- a/app/lib/kredits/contracts/operator.js +++ b/app/lib/kredits/contracts/operator.js @@ -56,12 +56,13 @@ export default class Operator extends Base { }) .then((attr) => { let proposal = [ - parseInt(attr.recipientId), - parseInt(attr.amount), + attr.recipientId, + attr.amount, attr.ipfsHash, attr.hashFunction, attr.hashSize, ]; + console.log('[kredits] addProposal', ...proposal); return this.contract.functions.addProposal(...proposal); }); From 0e330a6529206dae954901ce35536b4dfa4ae00f Mon Sep 17 00:00:00 2001 From: bumi Date: Mon, 9 Apr 2018 15:15:33 +0200 Subject: [PATCH 07/11] Cleanup --- app/lib/kredits/contracts/contributor.js | 1 - app/lib/kredits/contracts/operator.js | 2 -- app/lib/kredits/utils/multihash.js | 33 ------------------------ 3 files changed, 36 deletions(-) delete mode 100644 app/lib/kredits/utils/multihash.js diff --git a/app/lib/kredits/contracts/contributor.js b/app/lib/kredits/contracts/contributor.js index 5504eee..65cad84 100644 --- a/app/lib/kredits/contracts/contributor.js +++ b/app/lib/kredits/contracts/contributor.js @@ -3,7 +3,6 @@ import RSVP from 'rsvp'; import Kredits from '../kredits'; import ContributorSerializer from '../serializers/contributor'; -import { toBytes32 } from '../utils/multihash'; import Base from './base'; diff --git a/app/lib/kredits/contracts/operator.js b/app/lib/kredits/contracts/operator.js index 0adf525..841c421 100644 --- a/app/lib/kredits/contracts/operator.js +++ b/app/lib/kredits/contracts/operator.js @@ -1,10 +1,8 @@ import ethers from 'npm:ethers'; import RSVP from 'rsvp'; -import multihashes from 'npm:multihashes'; import Kredits from '../kredits'; import ContributionSerializer from '../serializers/contribution'; -import { toBytes32 } from '../utils/multihash'; import Base from './base'; diff --git a/app/lib/kredits/utils/multihash.js b/app/lib/kredits/utils/multihash.js deleted file mode 100644 index f49e3e0..0000000 --- a/app/lib/kredits/utils/multihash.js +++ /dev/null @@ -1,33 +0,0 @@ -import bs58 from 'npm:bs58'; -import NpmBuffer from 'npm:buffer'; -const Buffer = NpmBuffer.Buffer; - -function fromBytes32({ digest, hashFunction, hashSize }) { - if (hashSize === 0) { - return; - } - - const hashBytes = Buffer.from(digest.slice(2), 'hex'); - const multiHashBytes = new (hashBytes.constructor)(2 + hashBytes.length); - - multiHashBytes[0] = hashFunction; - multiHashBytes[1] = hashSize; - multiHashBytes.set(hashBytes, 2); - - return bs58.encode(multiHashBytes); -} - -function toBytes32(string) { - const decoded = bs58.decode(string); - - return { - digest: `0x${decoded.slice(2).toString('hex')}`, - hashFunction: decoded[0], - hashSize: decoded[1], - }; -} - -export { - fromBytes32, - toBytes32 -}; From 1a5d33b1f0bbb76e43e49763e1eb9999fa820cc6 Mon Sep 17 00:00:00 2001 From: bumi Date: Mon, 9 Apr 2018 19:28:49 +0200 Subject: [PATCH 08/11] Move IPFS handling in utils class --- app/lib/kredits/contracts/base.js | 27 ------------ app/lib/kredits/contracts/contributor.js | 27 +++++------- app/lib/kredits/contracts/operator.js | 27 +++++------- app/lib/kredits/kredits.js | 4 +- app/lib/kredits/utils/ipfs.js | 55 ++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 63 deletions(-) create mode 100644 app/lib/kredits/utils/ipfs.js diff --git a/app/lib/kredits/contracts/base.js b/app/lib/kredits/contracts/base.js index 6ac690f..a7a35aa 100644 --- a/app/lib/kredits/contracts/base.js +++ b/app/lib/kredits/contracts/base.js @@ -1,6 +1,3 @@ -import Kredits from '../kredits'; -import multihashes from 'npm:multihashes'; - export default class Base { constructor(contract) { this.contract = contract; @@ -10,28 +7,4 @@ export default class Base { return this.contract.functions; } - // TODO: move into utils - fetchAndMergeIpfsData(data, Serializer) { - if (!data.hashSize || data.hashSize === 0) { - return data; - } - let digest = Kredits.ipfs.Buffer.from(data.ipfsHash.slice(2), 'hex'); - let ipfsHash = multihashes.encode(digest, data.hashFunction, data.hashSize); - - return Kredits.ipfs - .cat(ipfsHash) - .then(Serializer.deserialize) - .then((attributes) => { - return Object.assign({}, data, attributes); - }); - } - - decodeIpfsHash(ipfsHash) { - let multihash = multihashes.decode(multihashes.fromB58String(ipfsHash)); - return { - ipfsHash: '0x' + multihashes.toHexString(multihash.digest), - hashSize: multihash.length, - hashFunction: multihash.code - }; - } } diff --git a/app/lib/kredits/contracts/contributor.js b/app/lib/kredits/contracts/contributor.js index 65cad84..4e859ff 100644 --- a/app/lib/kredits/contracts/contributor.js +++ b/app/lib/kredits/contracts/contributor.js @@ -35,30 +35,23 @@ export default class Contributor extends Base { }) // Fetch IPFS data if available .then((data) => { - return this.fetchAndMergeIpfsData(data, ContributorSerializer); + return Kredits.ipfs.catAndMerge(data, ContributorSerializer.deserialize); }); } - add(attributes) { - console.log(attributes); - - let json = ContributorSerializer.serialize(attributes); + add(contributorAttr) { + let json = ContributorSerializer.serialize(contributorAttr); // TODO: validate against schema return Kredits.ipfs - .add(new Kredits.ipfs.Buffer(json)) - .then((res) => res[0].hash) - .then((ipfsHash) => { - Object.assign(attributes, this.decodeIpfsHash(ipfsHash)); - return attributes; - }) - .then((attr) => { + .add(json) + .then((ipfsHashAttr) => { let contributor = [ - attr.address, - attr.ipfsHash, - attr.hashFunction, - attr.hashSize, - attr.isCore, + contributorAttr.address, + ipfsHashAttr.ipfsHash, + ipfsHashAttr.hashFunction, + ipfsHashAttr.hashSize, + contributorAttr.isCore, ]; console.log('[kredits] addContributor', ...contributor); diff --git a/app/lib/kredits/contracts/operator.js b/app/lib/kredits/contracts/operator.js index 841c421..c62eb9c 100644 --- a/app/lib/kredits/contracts/operator.js +++ b/app/lib/kredits/contracts/operator.js @@ -35,30 +35,23 @@ export default class Operator extends Base { }) // Fetch IPFS data if available .then((data) => { - return this.fetchAndMergeIpfsData(data, ContributionSerializer); + return Kredits.ipfs.catAndMerge(data, ContributionSerializer.deserialize); }); } - addProposal(attributes) { - console.log(attributes); - - let json = ContributionSerializer.serialize(attributes); + addProposal(proposalAttr) { + let json = ContributionSerializer.serialize(proposalAttr); // TODO: validate against schema return Kredits.ipfs - .add(new Kredits.ipfs.Buffer(json)) - .then((res) => res[0].hash) - .then((ipfsHash) => { - Object.assign(attributes, this.decodeIpfsHash(ipfsHash)); - return attributes; - }) - .then((attr) => { + .add(json) + .then((ipfsHashAttr) => { let proposal = [ - attr.recipientId, - attr.amount, - attr.ipfsHash, - attr.hashFunction, - attr.hashSize, + proposalAttr.recipientId, + proposalAttr.amount, + ipfsHashAttr.ipfsHash, + ipfsHashAttr.hashFunction, + ipfsHashAttr.hashSize, ]; console.log('[kredits] addProposal', ...proposal); diff --git a/app/lib/kredits/kredits.js b/app/lib/kredits/kredits.js index 1531a0b..b087371 100644 --- a/app/lib/kredits/kredits.js +++ b/app/lib/kredits/kredits.js @@ -1,11 +1,11 @@ import ethers from 'npm:ethers'; -import ipfsAPI from 'npm:ipfs-api'; import RSVP from 'rsvp'; import abis from 'contracts/abis'; import addresses from 'contracts/addresses'; import contracts from './contracts'; +import IPFS from './utils/ipfs'; // Helpers function capitalize(word) { @@ -25,7 +25,7 @@ export default class Kredits { static setup(provider, signer, ipfsConfig) { this.ipfsConfig = ipfsConfig; - this.ipfs = ipfsAPI(ipfsConfig); + this.ipfs = new IPFS(ipfsConfig); let registryContract = this.initRegistryContract(provider); diff --git a/app/lib/kredits/utils/ipfs.js b/app/lib/kredits/utils/ipfs.js new file mode 100644 index 0000000..9485c12 --- /dev/null +++ b/app/lib/kredits/utils/ipfs.js @@ -0,0 +1,55 @@ +import ipfsAPI from 'npm:ipfs-api'; +import multihashes from 'npm:multihashes'; + +export default class IPFS { + + constructor(config) { + this._ipfsAPI = ipfsAPI(config); + this._config = config; + } + + catAndMerge(data, deserializer) { + // if no hash details are found simply return the data; nothing to merge + if (!data.hashSize || data.hashSize === 0) { + return data; + } + return this.cat(data) + .then(deserializer) + .then((attributes) => { + return Object.assign({}, data, attributes); + }); + } + + add(data) { + return this._ipfsAPI + .add(new this._ipfsAPI.Buffer(data)) + .then((res) => { + return this.decodeHash(res[0].hash); + }); + } + + cat(hashData) { + let ipfsHash = hashData; // default - if it is a string + if (hashData.hasOwnProperty('hashSize')) { + ipfsHash = this.encodeHash(hashData); + } + return this._ipfsAPI.cat(ipfsHash); + } + + decodeHash(ipfsHash) { + let multihash = multihashes.decode(multihashes.fromB58String(ipfsHash)); + return { + ipfsHash: '0x' + multihashes.toHexString(multihash.digest), + hashSize: multihash.length, + hashFunction: multihash.code, + sourceHash: ipfsHash + }; + } + + encodeHash(hashData) { + let digest = this._ipfsAPI.Buffer.from(hashData.ipfsHash.slice(2), 'hex'); + return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize); + } + + +} From 32846194b6ba8bf1712ae3bcc0fd7419f1db0e5e Mon Sep 17 00:00:00 2001 From: bumi Date: Mon, 9 Apr 2018 20:31:34 +0200 Subject: [PATCH 09/11] Handle bignumber conversions The kredits module always returns raw data from the contract. For uint values these are bignumbers. To handle those in the ember app we need to convert them to string or to number. --- app/controllers/index.js | 4 ++-- app/models/contributor.js | 5 +++-- app/models/proposal.js | 11 ++++++----- app/utils/cps/bignumber.js | 13 +++++++++++++ 4 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 app/utils/cps/bignumber.js diff --git a/app/controllers/index.js b/app/controllers/index.js index 6d0195c..5b0727e 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -17,7 +17,7 @@ export default Controller.extend({ contributors: alias('model.contributors'), contributorsWithKredits: filter('contributors', function(contributor) { - return contributor.get('balance').toString() !== "0"; + return contributor.get('balance') !== 0; }), contributorsSorting: ['balance:desc'], contributorsSorted: sort('contributorsWithKredits', 'contributorsSorting'), @@ -68,7 +68,7 @@ export default Controller.extend({ this.get('contributors') .findBy('id', recipientId.toString()) - .incrementProperty('balance', amount.toNumber()); + .incrementProperty('balance', amount); }, _handleProposalVoted(proposalId, voter, totalVotes) { diff --git a/app/models/contributor.js b/app/models/contributor.js index 1c3801a..127f44f 100644 --- a/app/models/contributor.js +++ b/app/models/contributor.js @@ -1,12 +1,13 @@ import computed from 'ember-computed'; import EmberObject from 'ember-object'; +import bignumber from 'kredits-web/utils/cps/bignumber'; export default EmberObject.extend({ // Contract - id: null, + id: bignumber('idRaw', 'toString'), // TODO: Should we rename it to account like in the contract? address: null, - balance: 0, + balance: bignumber('balanceRaw', 'toNumber'), isCore: false, ipfsHash: null, diff --git a/app/models/proposal.js b/app/models/proposal.js index c52d162..88594e4 100644 --- a/app/models/proposal.js +++ b/app/models/proposal.js @@ -1,14 +1,15 @@ import EmberObject from 'ember-object'; import { alias } from 'ember-computed'; +import bignumber from 'kredits-web/utils/cps/bignumber'; export default EmberObject.extend({ // Contract - id: null, + id: bignumber('idRaw', 'toString'), creatorAddress: null, - recipientId: null, - amount: null, - votesCount: null, - votesNeeded: null, + recipientId: bignumber('recipientIdRaw', 'toString'), + amount: bignumber('amountRaw', 'toNumber'), + votesCount: bignumber('votesCountRaw', 'toNumber'), + votesNeeded: bignumber('votesNeededRaw', 'toNumber'), executed: null, ipfsHash: null, diff --git a/app/utils/cps/bignumber.js b/app/utils/cps/bignumber.js new file mode 100644 index 0000000..701eed1 --- /dev/null +++ b/app/utils/cps/bignumber.js @@ -0,0 +1,13 @@ +import computed from 'ember-computed'; + +export default function(dependentKey, converterMethod) { + return computed(dependentKey, { + get () { + return this.get(dependentKey)[converterMethod](); + }, + set (key, value) { + this.set(dependentKey, value); + return value[converterMethod](); + } + }); +} From c373c901a2abb51b83e8b9f4aa3c370c7500e84c Mon Sep 17 00:00:00 2001 From: bumi Date: Mon, 9 Apr 2018 20:54:07 +0200 Subject: [PATCH 10/11] Fix test to work with the bignumber depencency --- tests/unit/controllers/index-test.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/unit/controllers/index-test.js b/tests/unit/controllers/index-test.js index a0bdd28..0bce69e 100644 --- a/tests/unit/controllers/index-test.js +++ b/tests/unit/controllers/index-test.js @@ -18,11 +18,14 @@ let addFixtures = function(controller) { }); [ - { github_username: "neo", github_uid: "318", balance: "10000" }, - { github_username: "morpheus", github_uid: "843", balance: "15000" }, - { github_username: "trinity", github_uid: "123", balance: "5000" }, - { github_username: "mouse", github_uid: "696", balance: "0" } + { github_username: "neo", github_uid: "318", balance: 10000 }, + { github_username: "morpheus", github_uid: "843", balance: 15000 }, + { 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)); }); }; From 4a711788f652b8f293ae7f8f2774b6f03a996692 Mon Sep 17 00:00:00 2001 From: bumi Date: Tue, 10 Apr 2018 15:27:38 +0200 Subject: [PATCH 11/11] cleanup --- app/controllers/index.js | 2 +- app/lib/kredits/contracts/contributor.js | 6 +++--- app/lib/kredits/contracts/operator.js | 6 +++--- app/lib/kredits/utils/ipfs.js | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/controllers/index.js b/app/controllers/index.js index 5b0727e..1f5ca43 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').toString()); + .findBy('id', proposal.get('recipientId')); proposal.set('contributor', contributor); diff --git a/app/lib/kredits/contracts/contributor.js b/app/lib/kredits/contracts/contributor.js index 4e859ff..3e8ab1e 100644 --- a/app/lib/kredits/contracts/contributor.js +++ b/app/lib/kredits/contracts/contributor.js @@ -8,7 +8,7 @@ import Base from './base'; export default class Contributor extends Base { all() { - return this.contract.functions.contributorsCount() + return this.functions.contributorsCount() .then((count) => { count = count.toNumber(); let contributors = []; @@ -24,7 +24,7 @@ export default class Contributor extends Base { getById(id) { id = ethers.utils.bigNumberify(id); - return this.contract.functions.getContributorById(id) + return this.functions.getContributorById(id) .then((data) => { // TODO: remove as soon as the contract provides the id data.id = id; @@ -55,7 +55,7 @@ export default class Contributor extends Base { ]; console.log('[kredits] addContributor', ...contributor); - return this.contract.functions.addContributor(...contributor); + return this.functions.addContributor(...contributor); }); } } diff --git a/app/lib/kredits/contracts/operator.js b/app/lib/kredits/contracts/operator.js index c62eb9c..5a98943 100644 --- a/app/lib/kredits/contracts/operator.js +++ b/app/lib/kredits/contracts/operator.js @@ -8,7 +8,7 @@ import Base from './base'; export default class Operator extends Base { all() { - return this.contract.functions.proposalsCount() + return this.functions.proposalsCount() .then((count) => { count = count.toNumber(); let proposals = []; @@ -24,7 +24,7 @@ export default class Operator extends Base { getById(id) { id = ethers.utils.bigNumberify(id); - return this.contract.functions.proposals(id) + return this.functions.proposals(id) .then((data) => { // TODO: remove as soon as the contract provides the id data.id = id; @@ -55,7 +55,7 @@ export default class Operator extends Base { ]; console.log('[kredits] addProposal', ...proposal); - return this.contract.functions.addProposal(...proposal); + return this.functions.addProposal(...proposal); }); } } diff --git a/app/lib/kredits/utils/ipfs.js b/app/lib/kredits/utils/ipfs.js index 9485c12..5a2a7e4 100644 --- a/app/lib/kredits/utils/ipfs.js +++ b/app/lib/kredits/utils/ipfs.js @@ -8,13 +8,13 @@ export default class IPFS { this._config = config; } - catAndMerge(data, deserializer) { + catAndMerge(data, deserialize) { // if no hash details are found simply return the data; nothing to merge if (!data.hashSize || data.hashSize === 0) { return data; } return this.cat(data) - .then(deserializer) + .then(deserialize) .then((attributes) => { return Object.assign({}, data, attributes); });