Merge pull request #29 from 67P/ethers

Use ethers.js instead of the old web3
This commit was merged in pull request #29.
This commit is contained in:
2018-04-04 14:20:55 +02:00
committed by GitHub
9 changed files with 776 additions and 725 deletions
+3 -3
View File
@@ -18,9 +18,9 @@ export default Component.extend({
inProgress: false, inProgress: false,
isValidAddress: function() { isValidAddress: function() {
return this.get('kredits.web3') // TODO: add proper address validation
.isAddress(this.get('newContributor.address')); return this.get('newContributor.address') !== '';
}.property('kredits.web3', 'newContributor.address'), }.property('kredits.ethProvider', 'newContributor.address'),
isValidName: function() { isValidName: function() {
return isPresent(this.get('newContributor.name')); return isPresent(this.get('newContributor.name'));
+2 -1
View File
@@ -18,7 +18,8 @@ export default Component.extend({
inProgress: false, inProgress: false,
isValidRecipient: computed('proposal.recipientAddress', function() { isValidRecipient: computed('proposal.recipientAddress', function() {
return this.get('kredits.web3').isAddress(this.get('proposal.recipientAddress')); // TODO: add proper address validation
return this.get('proposal.recipientAddress') !== '';
}), }),
isValidAmount: computed('proposal.amount', function() { isValidAmount: computed('proposal.amount', function() {
+25 -41
View File
@@ -12,7 +12,7 @@ export default Ember.Controller.extend({
kredits: service(), kredits: service(),
contractInteractionEnabled: computed.alias('kredits.web3Provided'), contractInteractionEnabled: computed.alias('kredits.hasAccounts'),
proposalsOpen: function() { proposalsOpen: function() {
let proposals = this.get('model.proposals') let proposals = this.get('model.proposals')
@@ -47,78 +47,62 @@ export default Ember.Controller.extend({
contributorsSorting: ['balance:desc'], contributorsSorting: ['balance:desc'],
contributorsSorted: Ember.computed.sort('contributorsWithKredits', 'contributorsSorting'), contributorsSorted: Ember.computed.sort('contributorsWithKredits', 'contributorsSorting'),
watchContractEvents: function() { init() {
this._super(...arguments);
this.get('kredits.kreditsContract') this.get('kredits.kreditsContract')
.then((contract) => { .then((contract) => {
let events = contract.get('content').allEvents(); contract.onproposalvoted = this._handleProposalVoted.bind(this);
events.watch((error, data) => { contract.onproposalcreated = this._handleProposalCreated.bind(this);
Ember.Logger.debug('[index] Received contract event', data); contract.onproposalexecuted = this._handleProposalExecuted.bind(this);
// TODO: transfer on the token contract
switch (data.event) {
case 'ProposalCreated':
this._handleProposalCreated(data);
break;
case 'ProposalExecuted':
this._handleProposalExecuted(data);
break;
case 'ProposalVoted':
this._handleProposalVoted(data);
break;
case 'Transfer':
this._handleTransfer(data);
break;
}
});
}); });
}.on('init'), },
_handleProposalCreated(data) { _handleProposalCreated(proposalId, creatorAddress, recipientAddress, amount) {
if (Ember.isPresent(this.get('model.proposals') if (Ember.isPresent(this.get('model.proposals')
.findBy('id', data.args.id.toNumber()))) { .findBy('id', proposalId.toNumber()))) {
Ember.Logger.debug('[index] proposal exists, not adding from event'); Ember.Logger.debug('[index] proposal exists, not adding from event');
return false; return false;
} }
let proposal = Proposal.create({ let proposal = Proposal.create({
id: data.args.id.toNumber(), id: proposalId.toNumber(),
creatorAddress: data.args.creator, creatorAddress: creatorAddress,
recipientAddress: data.args.recipient, recipientAddress: recipientAddress,
recipientName: null, recipientName: null,
votesCount: 0, votesCount: 0,
votesNeeded: 2, votesNeeded: 2,
amount: data.args.amount.toNumber(), amount: amount.toNumber(),
executed: false, executed: false
url: data.args.url,
ipfsHash: data.args.ipfsHash
}); });
this.get('model.proposals').pushObject(proposal); this.get('model.proposals').pushObject(proposal);
}, },
_handleProposalExecuted(data) { _handleProposalExecuted(proposalId, recipientId, amount) {
if (this.get('model.proposals') if (this.get('model.proposals')
.findBy('id', data.args.id.toNumber()) .findBy('id', recipientId.toNumber())
.get('executed')) { .get('executed')) {
Ember.Logger.debug('[index] proposal already executed, not adding from event'); Ember.Logger.debug('[index] proposal already executed, not adding from event');
return false; return false;
} }
this.get('model.proposals') this.get('model.proposals')
.findBy('id', data.args.id.toNumber()) .findBy('id', recipientId.toNumber())
.setProperties({ .setProperties({
'executed': true, 'executed': true,
'votesCount': 2 // TODO use real count 'votesCount': 2 // TODO use real count
}); });
this.get('model.contributors') this.get('model.contributors')
.findBy('address', data.args.recipient) .findBy('id', recipientId)
.incrementProperty('balance', data.args.amount.toNumber()); .incrementProperty('balance', amount.toNumber());
}, },
_handleProposalVoted(data) { _handleProposalVoted(proposalId, voter, totalVotes) {
this.get('model.proposals') this.get('model.proposals')
.findBy('id', data.args.id.toNumber()) .findBy('id', proposalId.toNumber())
.incrementProperty('votesCount', 1); .setProperties({ 'votesCount': totalVotes });
}, },
_handleTransfer(data) { _handleTransfer(data) {
@@ -134,8 +118,8 @@ export default Ember.Controller.extend({
actions: { actions: {
confirmProposal(proposalId) { confirmProposal(proposalId) {
this.get('kredits').vote(proposalId).then(transactionId => { this.get('kredits').vote(proposalId).then(transaction => {
window.confirm('Vote submitted to Ethereum blockhain: '+transactionId); window.confirm('Vote submitted to Ethereum blockhain: '+transaction.hash);
}); });
} }
+7 -10
View File
@@ -6,16 +6,13 @@ export default Ember.Route.extend({
beforeModel(transition) { beforeModel(transition) {
const kredits = this.get('kredits'); const kredits = this.get('kredits');
return kredits.initEthProvider().then(() => {
if (kredits.get('web3') && kredits.get('web3Provided')) { if (kredits.get('accountNeedsUnlock')) {
kredits.get('web3').eth.getAccounts((error, accounts) => { if (confirm('It looks like you have an Ethereum wallet available. Please unlock your account.')) {
if (error || accounts.length === 0) { transition.retry();
if (confirm('It looks like you have an Ethereum wallet available. Please unlock your account.')) {
transition.retry();
}
} }
}); }
} });
}, },
model() { model() {
@@ -24,7 +21,7 @@ export default Ember.Route.extend({
let kredits = this.get('kredits'); let kredits = this.get('kredits');
let totalSupply = kredits.get('tokenContract') let totalSupply = kredits.get('tokenContract')
.then((contract) => contract.invoke('totalSupply')); .then((contract) => contract.totalSupply());
return Ember.RSVP.hash({ return Ember.RSVP.hash({
contributors: kredits.getContributors(), contributors: kredits.getContributors(),
+65 -88
View File
@@ -1,4 +1,4 @@
import Web3 from 'npm:web3'; import ethers from 'npm:ethers';
import bs58 from 'npm:bs58'; import bs58 from 'npm:bs58';
import NpmBuffer from 'npm:buffer'; import NpmBuffer from 'npm:buffer';
@@ -24,92 +24,77 @@ const {
const Buffer = NpmBuffer.Buffer; 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: injectService(), ipfs: injectService(),
web3Instance: null, ethProvider: null,
web3Provided: false, // Web3 provided (using Mist Browser, Metamask et al.) currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded.
web3: function() { // this is called called in the routes beforeModel(). So it is initialized before everything else
if (this.get('web3Instance')) { // and we can rely on the ethProvider and the potential currentUserAccounts to be available
return this.get('web3Instance'); initEthProvider: function() {
} return new Ember.RSVP.Promise((resolve) => {
let ethProvider;
let networkId;
if (typeof window.web3 !== 'undefined') {
debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask');
networkId = parseInt(window.web3.version.network);
ethProvider = new ethers.providers.Web3Provider(window.web3.currentProvider, {chainId: networkId});
ethProvider.listAccounts().then((accounts) => {
this.set('currentUserAccounts', accounts);
this.set('ethProvider', ethProvider);
resolve(ethProvider);
});
} else {
debug('[kredits] Creating new instance from npm module class');
let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl;
networkId = parseInt(config.contractMetadata.networkId);
ethProvider = new ethers.providers.JsonRpcProvider(providerUrl, {chainId: networkId});
this.set('ethProvider', ethProvider);
resolve(ethProvider);
}
window.ethProvider = ethProvider;
});
},
let web3Instance; accountNeedsUnlock: computed('currentUserAccounts', function() {
return this.get('currentUserAccounts') && Ember.isEmpty(this.get('currentUserAccounts'));
if (typeof window.web3 !== 'undefined') {
debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask');
web3Instance = new Web3(window.web3.currentProvider);
this.set('web3Provided', true);
} else {
debug('[kredits] Creating new instance from npm module class');
let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl;
let provider = new Web3.providers.HttpProvider(providerUrl);
web3Instance = new Web3(provider);
}
this.set('web3Instance', web3Instance);
window.web3 = web3Instance;
return web3Instance;
}.property('web3Instance'),
currentUserAccounts: function() {
return (this.get('web3Provided') && this.get('web3').eth.accounts) || [];
}.property('web3Provided', 'web3'),
registryContract: computed('web3', function() {
let networkId = this.get('web3').version.network;
let contract = this.get('web3')
.eth
.contract(abis['Registry'])
.at(addresses['Registry'][networkId]);
return RSVP.resolve(contractProxy(contract));
}), }),
contributorsContract: computed('web3', function() { hasAccounts: computed('currentUserAccounts', function() {
return !Ember.isEmpty(this.get('currentUserAccounts'));
}),
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;
}),
contributorsContract: computed('ethProvider', function() {
return this.contractFor('Contributors'); return this.contractFor('Contributors');
}), }),
kreditsContract: computed('web3', function() { kreditsContract: computed('ethProvider', function() {
return this.contractFor('Operator'); return this.contractFor('Operator');
}), }),
tokenContract: computed('web3', function() { tokenContract: computed('ethProvider', function() {
return this.contractFor('Token'); return this.contractFor('Token');
}), }),
contractFor(name) { contractFor(name) {
return this.get('registryContract') return this.get('registryContract').functions.getProxyFor(name)
.then((contract) => contract.invoke('getProxyFor', name))
.then((address) => { .then((address) => {
debug('[kredits] get contract', name, address); debug('[kredits] get contract', name, address);
let contract = this.get('web3') return new ethers.Contract(address, abis[name], this.get('ethProvider').getSigner());
.eth
.contract(abis[name])
.at(address);
return contractProxy(contract);
}); });
}, },
getContributorData(id) { getContributorData(id) {
return this.get('contributorsContract') return this.get('contributorsContract')
.then((contract) => contract.invoke('contributors', id)) .then((contract) => contract.contributors(id))
.then((data) => { .then((data) => {
debug('[kredits] contributor', data); debug('[kredits] contributor', data);
@@ -117,12 +102,11 @@ export default Service.extend({
let isCurrentUser = this.get('currentUserAccounts').includes(address); let isCurrentUser = this.get('currentUserAccounts').includes(address);
let profileHash = this.getMultihashFromBytes32({ let profileHash = this.getMultihashFromBytes32({
digest, digest,
hashFunction: hashFunction.toNumber(), hashFunction: hashFunction,
size: size.toNumber() size: size
}); });
return this.get('tokenContract') return this.get('tokenContract')
.then((contract) => contract.invoke('balanceOf', address)) .then((contract) => contract.balanceOf(address))
.then((balance) => { .then((balance) => {
balance = balance.toNumber(); balance = balance.toNumber();
@@ -144,7 +128,7 @@ export default Service.extend({
getContributors() { getContributors() {
return this.get('contributorsContract') return this.get('contributorsContract')
.then((contract) => contract.invoke('contributorsCount')) .then((contract) => contract.contributorsCount())
.then(contributorsCount => { .then(contributorsCount => {
debug('[kredits] contributorsCount:', contributorsCount.toNumber()); debug('[kredits] contributorsCount:', contributorsCount.toNumber());
let contributors = []; let contributors = [];
@@ -159,24 +143,19 @@ export default Service.extend({
getProposalData(i) { getProposalData(i) {
return this.get('kreditsContract') return this.get('kreditsContract')
.then((contract) => contract.invoke('proposals', i)) .then((contract) => contract.proposals(i))
.then(p => { .then(p => {
let contributionIpfsHash = this.getMultihashFromBytes32({ digest: p.ipfsHash, hashFunction: p.hashFunction, size: p.hashSize });
let ipfsHash = this.getMultihashFromBytes32({
digest: p[6],
hashFunction: p[7].toNumber(),
size: p[8].toNumber()
});
let proposal = Proposal.create({ let proposal = Proposal.create({
id : i, id : i,
creatorAddress : p[0], creatorAddress : p.creator,
recipientId : p[1].toNumber(), recipientId : p.recipientId.toNumber(),
votesCount : p[2].toNumber(), votesCount : p.votesCount.toNumber(),
votesNeeded : p[3].toNumber(), votesNeeded : p.votesNeeded.toNumber(),
amount : p[4].toNumber(), amount : p.amount.toNumber(),
executed : p[5], executed : p.executed,
ipfsHash : ipfsHash ipfsHash : contributionIpfsHash
}); });
if (proposal.get('ipfsHash')) { if (proposal.get('ipfsHash')) {
@@ -193,7 +172,7 @@ export default Service.extend({
getProposals() { getProposals() {
return this.get('kreditsContract') return this.get('kreditsContract')
.then((contract) => contract.invoke('proposalsCount')) .then((contract) => contract.proposalsCount())
.then(proposalsCount => { .then(proposalsCount => {
let proposals = []; let proposals = [];
@@ -209,7 +188,7 @@ export default Service.extend({
debug('[kredits] vote for', proposalId); debug('[kredits] vote for', proposalId);
return this.get('kreditsContract') return this.get('kreditsContract')
.then((contract) => contract.invoke('vote', proposalId)) .then((contract) => contract.vote(proposalId))
.then((data) => { .then((data) => {
debug('[kredits] vote response', data); debug('[kredits] vote response', data);
return data; return data;
@@ -252,7 +231,7 @@ export default Service.extend({
.then(profileHash => { .then(profileHash => {
contributor.setProperties({ contributor.setProperties({
profileHash: profileHash, profileHash: profileHash,
kredits: 0, balance: 0,
isCurrentUser: this.get('currentUserAccounts').includes(contributor.address) isCurrentUser: this.get('currentUserAccounts').includes(contributor.address)
}); });
@@ -262,8 +241,7 @@ export default Service.extend({
return this.get('kreditsContract') return this.get('kreditsContract')
.then((contract) => { .then((contract) => {
return contract.invoke( return contract.addContributor(
'addContributor',
contributor.address, contributor.address,
digest, digest,
hashFunction, hashFunction,
@@ -290,8 +268,7 @@ export default Service.extend({
.then(ipfsHash => { .then(ipfsHash => {
return this.get('kreditsContract') return this.get('kreditsContract')
.then((contract) => { .then((contract) => {
return contract.invoke( return contract.addProposal(
'addProposal',
recipientAddress, recipientAddress,
amount, amount,
url, url,
+1 -1
View File
@@ -43,7 +43,7 @@ module.exports = function(environment) {
} }
}; };
ENV.contractMetadata['networkId'] = "17"; ENV.contractMetadata['networkId'] = "42";
if (process.env.OPERATOR_CONTRACT_ADDR) { if (process.env.OPERATOR_CONTRACT_ADDR) {
ENV.contractMetadata['Operator'] = { ENV.contractMetadata['Operator'] = {
+663 -577
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -55,12 +55,12 @@
"ember-parachute": "0.1.0", "ember-parachute": "0.1.0",
"ember-resolver": "^2.0.3", "ember-resolver": "^2.0.3",
"ember-truth-helpers": "1.3.0", "ember-truth-helpers": "1.3.0",
"ethers": "^3.0.8",
"ipfs-api": "^19.0.0", "ipfs-api": "^19.0.0",
"kosmos-schemas": "^1.1.2", "kosmos-schemas": "^1.1.2",
"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"
"web3": "^0.18.2"
}, },
"engines": { "engines": {
"node": ">= 0.12.0" "node": ">= 0.12.0"
@@ -70,5 +70,6 @@
"paths": [ "paths": [
"lib/contracts" "lib/contracts"
] ]
} },
"dependencies": {}
} }
+6 -1
View File
@@ -28,7 +28,12 @@ let addFixtures = function(controller) {
}; };
test('doesn\'t contain people with 0 balance', function(assert) { test('doesn\'t contain people with 0 balance', function(assert) {
let controller = this.subject(); // 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() { }
});
addFixtures(controller); addFixtures(controller);
let contributorsSorted = controller.get('contributorsSorted'); let contributorsSorted = controller.get('contributorsSorted');