Update kredits service

This commit is contained in:
2018-04-01 00:24:51 +02:00
parent 020a1c5682
commit f12740dfa8
7 changed files with 27457 additions and 9447 deletions
+1
View File
@@ -15,3 +15,4 @@
/libpeerconnection.log /libpeerconnection.log
npm-debug.log* npm-debug.log*
testem.log testem.log
.tm_properties
+4 -3
View File
@@ -48,9 +48,9 @@ export default Ember.Controller.extend({
contributorsSorted: Ember.computed.sort('contributorsWithKredits', 'contributorsSorting'), contributorsSorted: Ember.computed.sort('contributorsWithKredits', 'contributorsSorting'),
watchContractEvents: function() { watchContractEvents: function() {
let events = this.get('kredits.kreditsContract') this.get('kredits.kreditsContract')
.allEvents(/* [additionalFilterObject], */); .then((contract) => contract.invoke('allEvents'))
.then((events) => {
events.watch((error, data) => { events.watch((error, data) => {
Ember.Logger.debug('[index] Received contract event', data); Ember.Logger.debug('[index] Received contract event', data);
@@ -69,6 +69,7 @@ export default Ember.Controller.extend({
break; break;
} }
}); });
});
}.on('init'), }.on('init'),
_handleProposalCreated(data) { _handleProposalCreated(data) {
+7 -4
View File
@@ -31,7 +31,12 @@ export default Ember.Object.extend({
* @public * @public
*/ */
loadProfile(ipfs) { loadProfile(ipfs) {
let promise = new Ember.RSVP.Promise((resolve, reject) => { return new Ember.RSVP.Promise((resolve, reject) => {
if (!this.get('profileHash')) {
resolve(this);
return;
}
ipfs.getFile(this.get('profileHash')).then(content => { ipfs.getFile(this.get('profileHash')).then(content => {
let profileJSON = JSON.parse(content); let profileJSON = JSON.parse(content);
let profile = Ember.Object.create(profileJSON); let profile = Ember.Object.create(profileJSON);
@@ -58,14 +63,12 @@ export default Ember.Object.extend({
} }
Ember.Logger.debug('[contributor] loaded contributor profile', profile); Ember.Logger.debug('[contributor] loaded contributor profile', profile);
resolve(); resolve(this);
}).catch((err) => { }).catch((err) => {
Ember.Logger.error('[contributor] error trying to load contributor profile', this.get('profileHash'), err); Ember.Logger.error('[contributor] error trying to load contributor profile', this.get('profileHash'), err);
reject(err); reject(err);
}); });
}); });
return promise;
}, },
/** /**
+3 -1
View File
@@ -21,11 +21,13 @@ export default Ember.Route.extend({
model() { model() {
let kredits = this.get('kredits'); let kredits = this.get('kredits');
let totalSupply = kredits.get('tokenContract')
.then((contract) => contract.invoke('totalSupply'));
return Ember.RSVP.hash({ return Ember.RSVP.hash({
contributors: kredits.getContributors(), contributors: kredits.getContributors(),
totalSupply: kredits.getValueFromContract('tokenContract', 'totalSupply'),
proposals: kredits.getProposals(), proposals: kredits.getProposals(),
totalSupply,
newContributor: Contributor.create({ kind: 'person' }) newContributor: Contributor.create({ kind: 'person' })
}); });
} }
+178 -111
View File
@@ -1,23 +1,44 @@
import Ember from 'ember';
import Web3 from 'npm:web3'; import Web3 from 'npm:web3';
import bs58 from 'npm:bs58';
import NpmBuffer from 'npm:buffer';
import RSVP from 'rsvp';
import Ember from 'ember';
import Service from 'ember-service';
import injectService from 'ember-service/inject';
import computed from 'ember-computed';
import config from 'kredits-web/config/environment'; import config from 'kredits-web/config/environment';
import Contributor from 'kredits-web/models/contributor'; import Contributor from 'kredits-web/models/contributor';
import Proposal from 'kredits-web/models/proposal'; import Proposal from 'kredits-web/models/proposal';
import kreditsContracts from 'npm:kredits-contracts';
import Kredits from 'npm:kredits-contracts/operator'; import abis from 'contracts/abis';
import uuid from 'npm:uuid'; import addresses from 'contracts/addresses';
const { const {
Service, Logger: {
isPresent, debug,
inject: { warn
service
} }
} = Ember; } = Ember;
const Buffer = NpmBuffer.Buffer;
function contractProxy(object) {
let proxy = Ember.ObjectProxy.extend({
invoke(contractMethod, ...args) {
debug('[kredits] invoke', contractMethod, ...args);
let contract = this.get('content');
return RSVP.denodeify(contract[contractMethod])(...args);
}
});
return proxy.create({ content: object });
}
export default Service.extend({ export default Service.extend({
ipfs: service(), ipfs: injectService(),
web3Instance: null, web3Instance: null,
web3Provided: false, // Web3 provided (using Mist Browser, Metamask et al.) web3Provided: false, // Web3 provided (using Mist Browser, Metamask et al.)
@@ -30,11 +51,11 @@ export default Service.extend({
let web3Instance; let web3Instance;
if (typeof window.web3 !== 'undefined') { if (typeof window.web3 !== 'undefined') {
Ember.Logger.debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask'); debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask');
web3Instance = window.web3; web3Instance = window.web3;
this.set('web3Provided', true); this.set('web3Provided', true);
} else { } else {
Ember.Logger.debug('[kredits] Creating new instance from npm module class'); debug('[kredits] Creating new instance from npm module class');
let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl; let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl;
let provider = new Web3.providers.HttpProvider(providerUrl); let provider = new Web3.providers.HttpProvider(providerUrl);
web3Instance = new Web3(provider); web3Instance = new Web3(provider);
@@ -50,93 +71,95 @@ export default Service.extend({
return (this.get('web3Provided') && this.get('web3').eth.accounts) || []; return (this.get('web3Provided') && this.get('web3').eth.accounts) || [];
}.property('web3Provided', 'web3'), }.property('web3Provided', 'web3'),
initializeKreditsContract() { registryContract: computed('web3', function() {
let contract = null; let networkId = this.get('web3').version.network;
let contract = this.get('web3')
.eth
.contract(abis['Registry'])
.at(addresses['Registry'][networkId]);
if (isPresent(config.contractMetadata)) { return RSVP.resolve(contractProxy(contract));
if (localStorage.getItem('config:networkId')) { }),
config.contractMetadata['networkId'] = localStorage.getItem('config:networkId');
}
contract = new Kredits(this.get('web3'), config.contractMetadata['Operator']);
} else {
contract = new Kredits(this.get('web3'));
}
return contract; contributorsContract: computed('web3', function() {
}, return this.contractFor('Contributors');
}),
kreditsContract: function() { kreditsContract: computed('web3', function() {
if (this.get('kreditsContractInstance')) { return this.contractFor('Operator');
return this.get('kreditsContractInstance'); }),
}
let contract = this.initializeKreditsContract(); tokenContract: computed('web3', function() {
return this.contractFor('Token');
}),
this.set('kreditsContractInstance', contract); contractFor(name) {
return this.get('registryContract')
.then((contract) => contract.invoke('getProxyFor', name))
.then((address) => {
debug('[kredits] get contract', name, address);
let contract = this.get('web3')
.eth
.contract(abis[name])
.at(address);
return contract; return contractProxy(contract);
}.property('kreditsContractInstance', 'web3'),
tokenContract: function() {
if (this.get('tokenContractInstance')) {
return this.get('tokenContractInstance');
}
let contract = kreditsContracts(this.get('web3'), config.contractMetadata)['Token'];
this.set('tokenContractInstance', contract);
window.Token = contract;
return contract;
}.property('tokenContractInstance', 'web3'),
getValueFromContract(contract, contractMethod, ...args) {
Ember.Logger.debug('[kredits] read from contract', contract);
return new Ember.RSVP.Promise((resolve, reject) => {
this.get(contract)[contractMethod](...args, (err, data) => {
if (err) { reject(err); return; }
resolve(data);
});
}); });
}, },
getContributorData(id) { getContributorData(id) {
let kredits = this.get('kreditsContract'); return this.get('contributorsContract')
.then((contract) => contract.invoke('contributors', id))
.then(contributorData => {
debug('[kredits] contributor', contributorData);
let promise = new Ember.RSVP.Promise((resolve, reject) => { let [ address, digest, hashFunction, size, isCore ] = contributorData;
kredits.getContributor(id).then(contributorData => {
Ember.Logger.debug('[kredits] contributor', contributorData);
let contributor = Contributor.create(contributorData);
contributor.set('isCurrentUser', this.get('currentUserAccounts').includes(contributor.address));
this.getValueFromContract('tokenContract', 'balanceOf', contributor.address).then(balance => { let profileHash = this.getMultihashFromBytes32({
contributor.set('kredits', balance.toNumber()); digest,
hashFunction: hashFunction.toNumber(),
contributor.loadProfile(this.get('ipfs')).then( size: size.toNumber()
() => resolve(contributor),
err => reject(err)
);
});
}).catch(err => reject(err));
}); });
return promise; let isCurrentUser = this.get('currentUserAccounts').includes(address);
return this.get('tokenContract')
.then((contract) => contract.invoke('balanceOf', address))
.then(balance => {
let contributor = Contributor.create({
id,
address,
profileHash,
isCore,
isCurrentUser,
kredits: balance.toNumber()
});
// TODO: move ipfs into model
return contributor.loadProfile(this.get('ipfs'));
});
});
}, },
getContributors() { getContributors() {
return this.get('kreditsContract').contributorsCount().then(contributorsCount => { return this.get('contributorsContract')
Ember.Logger.debug('[kredits] contributorsCount:', contributorsCount.toNumber()); .then((contract) => contract.invoke('contributorsCount'))
.then(contributorsCount => {
debug('[kredits] contributorsCount:', contributorsCount.toNumber());
let contributors = []; let contributors = [];
for(var id = 1; id <= contributorsCount.toNumber(); id++) { for(var id = 1; id <= contributorsCount.toNumber(); id++) {
contributors.push(this.getContributorData(id)); contributors.push(this.getContributorData(id));
} }
return Ember.RSVP.all(contributors); return RSVP.all(contributors);
}); });
}, },
getProposalData(i) { getProposalData(i) {
let promise = new Ember.RSVP.Promise((resolve, reject) => { return this.get('kreditsContract')
this.get('kreditsContract').proposals(i).then(p => { .then((contract) => contract.invoke('proposals', i))
.then(p => {
let proposal = Proposal.create({ let proposal = Proposal.create({
id : i, id : i,
creatorAddress : p[0], creatorAddress : p[0],
@@ -149,84 +172,128 @@ export default Service.extend({
}); });
if (proposal.get('ipfsHash')) { if (proposal.get('ipfsHash')) {
proposal.loadContribution(this.get('ipfs')).then( // TODO: move ipfs into model
() => resolve(proposal), return proposal
err => reject(err) .loadContribution(this.get('ipfs'))
); .then(() => { return proposal; });
} else { } else {
Ember.Logger.warn('[kredits] proposal from blockchain is missing IPFS hash', proposal); warn('[kredits] proposal from blockchain is missing IPFS hash', proposal);
resolve(proposal); return proposal;
} }
}).catch(err => reject(err));
}); });
return promise;
}, },
getProposals() { getProposals() {
return this.get('kreditsContract').proposalsCount().then(proposalsCount => { return this.get('kreditsContract')
.then((contract) => contract.invoke('proposalsCount'))
.then(proposalsCount => {
let proposals = []; let proposals = [];
for(var i = 0; i < proposalsCount.toNumber(); i++) { for(var i = 0; i < proposalsCount.toNumber(); i++) {
proposals.push(this.getProposalData(i)); proposals.push(this.getProposalData(i));
} }
return Ember.RSVP.all(proposals); return RSVP.all(proposals);
}); });
}, },
vote(proposalId) { vote(proposalId) {
Ember.Logger.debug('[kredits] vote for', proposalId); debug('[kredits] vote for', proposalId);
return new Ember.RSVP.Promise((resolve, reject) => {
this.get('kreditsContract').vote(proposalId, (err, data) => { return this.get('kreditsContract')
if (err) { reject(err); return; } .then((contract) => contract.invoke('vote', proposalId))
Ember.Logger.debug('[kredits] vote response', data); .then((data) => {
resolve(data); debug('[kredits] vote response', data);
}); return data;
}); });
}, },
addContributor(contributor) { // TODO: move into utils
Ember.Logger.debug('[kredits] add contributor', contributor); getMultihashFromBytes32(multihash) {
const { digest, hashFunction, size } = multihash;
if (size === 0) {
return;
}
const hashBytes = Buffer.from(digest.slice(2), 'hex');
const multiHashBytes = new (hashBytes.constructor)(2 + hashBytes.length);
multiHashBytes[0] = hashFunction; //contributorData[2];
multiHashBytes[1] = size; //contributorData[3];
multiHashBytes.set(hashBytes, 2);
return bs58.encode(multiHashBytes);
},
getBytes32FromMultihash(multihash) {
const decoded = bs58.decode(multihash);
return {
digest: `0x${decoded.slice(2).toString('hex')}`,
hashFunction: decoded[0],
size: decoded[1],
};
},
addContributor(contributor) {
debug('[kredits] add contributor', contributor);
return this.get('ipfs')
.storeFile(contributor.serialize())
.then(profileHash => {
contributor.setProperties({ contributor.setProperties({
profileHash: profileHash,
kredits: 0, kredits: 0,
isCurrentUser: this.get('currentUserAccounts').includes(contributor.address) isCurrentUser: this.get('currentUserAccounts').includes(contributor.address)
}); });
let id = uuid.v4(); let {
digest, hashFunction, size
} = this.getBytes32FromMultihash(profileHash);
return new Ember.RSVP.Promise((resolve, reject) => { return this.get('kreditsContract')
this.get('ipfs').storeFile(contributor.serialize()).then(ipfsHash => { .then((contract) => {
contributor.set('ipfsHash', ipfsHash); return contract.invoke(
this.get('kreditsContract').addContributor(contributor.address, contributor.name, contributor.ipfsHash, contributor.isCore, id, (err, data) => { 'addContributor',
if (err) { reject(err); return; } contributor.address,
Ember.Logger.debug('[kredits] add contributor response', data); digest,
resolve(contributor); hashFunction,
}); size,
contributor.isCore
);
})
.then((data) => {
debug('[kredits] add contributor response', data);
return contributor;
}); });
}); });
}, },
addProposal(proposal) { addProposal(proposal) {
return new Ember.RSVP.Promise((resolve, reject) => {
const { const {
recipientAddress, recipientAddress,
amount, amount,
url url
} = proposal.getProperties('recipientAddress', 'amount', 'url'); } = proposal.getProperties('recipientAddress', 'amount', 'url');
this.get('ipfs').storeFile(proposal.serializeContribution()).then(ipfsHash => { return this.get('ipfs')
this.get('kreditsContract').addProposal(recipientAddress, amount, url, ipfsHash, (err, data) => { .storeFile(proposal.serializeContribution())
if (err) { reject(err); return; } .then(ipfsHash => {
Ember.Logger.debug('[kredits] add proposal response', data); return this.get('kreditsContract')
resolve(); .then((contract) => {
}); return contract.invoke(
'addProposal',
recipientAddress,
amount,
url,
ipfsHash
);
})
.then((data) => {
debug('[kredits] add proposal response', data);
return data;
}); });
}); });
}, },
logOperatorContract: function() {
Ember.Logger.debug('[kredits] operatorContract', this.get('kreditsContract'));
}.on('init')
}); });
+26968 -9033
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -63,7 +63,8 @@
"kredits-contracts": "github:67P/truffle-kredits#master", "kredits-contracts": "github:67P/truffle-kredits#master",
"loader.js": "^4.0.10", "loader.js": "^4.0.10",
"tv4": "^1.3.0", "tv4": "^1.3.0",
"uuid": "^3.0.1", "bs58": "^4.0.1",
"buffer": "^5.1.0",
"web3": "^0.18.2" "web3": "^0.18.2"
}, },
"engines": { "engines": {