diff --git a/common/VotingUtils.js b/common/VotingUtils.js index 0e1e35b305..6ee433fb36 100644 --- a/common/VotingUtils.js +++ b/common/VotingUtils.js @@ -1,7 +1,14 @@ -const { decryptMessage, encryptMessage, deriveKeyPairFromSignatureTruffle } = require("./Crypto"); +const { + decryptMessage, + encryptMessage, + deriveKeyPairFromSignatureTruffle, + deriveKeyPairFromSignatureMetamask +} = require("./Crypto"); const { getKeyGenMessage, computeTopicHash } = require("./EncryptionHelper"); const { BATCH_MAX_COMMITS, BATCH_MAX_RETRIEVALS, BATCH_MAX_REVEALS } = require("./Constants"); +const argv = require("minimist")(process.argv.slice()); + /** * Generate a salt and use it to encrypt a committed vote in response to a price request * Return committed vote details to the voter @@ -17,7 +24,12 @@ const constructCommitment = async (request, roundId, web3, price, account) => { const hash = web3.utils.soliditySha3(priceWei, salt); const vote = { price: priceWei, salt }; - const { publicKey } = await deriveKeyPairFromSignatureTruffle(web3, getKeyGenMessage(roundId), account); + let publicKey; + if (argv.network === "metamask") { + publicKey = (await deriveKeyPairFromSignatureMetamask(web3, getKeyGenMessage(roundId), account)).publicKey; + } else { + publicKey = (await deriveKeyPairFromSignatureTruffle(web3, getKeyGenMessage(roundId), account)).publicKey; + } const encryptedVote = await encryptMessage(publicKey, JSON.stringify(vote)); return { @@ -42,7 +54,12 @@ const constructReveal = async (request, roundId, web3, account, votingContract) const topicHash = computeTopicHash(request, roundId); const encryptedCommit = await votingContract.getMessage(account, topicHash, { from: account }); - const { privateKey } = await deriveKeyPairFromSignatureTruffle(web3, getKeyGenMessage(roundId), account); + let privateKey; + if (argv.network === "metamask") { + privateKey = (await deriveKeyPairFromSignatureMetamask(web3, getKeyGenMessage(roundId), account)).privateKey; + } else { + privateKey = (await deriveKeyPairFromSignatureTruffle(web3, getKeyGenMessage(roundId), account)).privateKey; + } const vote = JSON.parse(await decryptMessage(privateKey, encryptedCommit)); return { diff --git a/common/globalTruffleConfig.js b/common/globalTruffleConfig.js index ed7c463757..d42022b228 100644 --- a/common/globalTruffleConfig.js +++ b/common/globalTruffleConfig.js @@ -123,8 +123,8 @@ addLocalNetwork(networks, "test"); // Coverage requires specific parameters to allow very high cost transactions. addLocalNetwork(networks, "coverage", { port: 8545, network_id: 1234 }); -// MetaMask truffle provider -addLocalNetwork(networks, "metamask", { provider: new MetaMaskTruffleProvider() }); +// MetaMask truffle provider requires a longer timeout so that user has time to point web browser with metamask to localhost:3333 +addLocalNetwork(networks, "metamask", { provider: new MetaMaskTruffleProvider(), networkCheckTimeout: 500000 }); module.exports = { // See diff --git a/core/scripts/cli/textStyle.js b/core/scripts/cli/textStyle.js index 77f44a3057..a7b6d95ada 100644 --- a/core/scripts/cli/textStyle.js +++ b/core/scripts/cli/textStyle.js @@ -8,6 +8,7 @@ const style = { // Colors instruction: chalkPipe("bgRed"), success: chalkPipe("bgGreen"), + warning: chalkPipe("bgYellow"), help: chalkPipe("bgCyan"), // Links diff --git a/core/scripts/cli/vote.js b/core/scripts/cli/vote.js index d66780f4f1..179e60d84d 100644 --- a/core/scripts/cli/vote.js +++ b/core/scripts/cli/vote.js @@ -77,7 +77,11 @@ const votingMenu = async function(web3, artifacts) { ACTIONS.reveal )}: Prompts user to select batch of votes to reveal. Only possible during the Reveal phase.` ); - console.log(`${style.help(ACTIONS.rewards)}: Prompts user to select resolved votes to retrieve rewards for.`); + console.log( + `${style.help( + ACTIONS.rewards + )}: Prompts user to select resolved votes to retrieve rewards for. This might not work perfectly if you are using a Metamask provider.` + ); console.groupEnd(); break; diff --git a/core/scripts/cli/voting/commitVotes.js b/core/scripts/cli/voting/commitVotes.js index a1d6859512..1bc3faf828 100644 --- a/core/scripts/cli/voting/commitVotes.js +++ b/core/scripts/cli/voting/commitVotes.js @@ -4,6 +4,7 @@ const getDefaultAccount = require("../wallet/getDefaultAccount"); const filterRequests = require("./filterRequestsByRound"); const { VotePhasesEnum } = require("../../../../common/Enums"); const { constructCommitment, batchCommitVotes } = require("../../../../common/VotingUtils"); +const networkUtils = require("../../../../common/PublicNetworks"); /** * This prompts the user twice from the command line interface: @@ -79,6 +80,16 @@ const commitVotes = async (web3, voting, designatedVoting) => { const { successes, batches } = await batchCommitVotes(newCommitments, voting, account); style.spinnerWritingContracts.stop(); + // Construct etherscan link based on network + const networkId = web3.networkId; + let url; + if (networkUtils[networkId]) { + url = `${networkUtils[networkId].etherscan}/tx/`; + } else { + // No URL for localhost, just show transaction ID + url = ""; + } + // Print results console.log( style.success( @@ -89,7 +100,7 @@ const commitVotes = async (web3, voting, designatedVoting) => { ); console.group(style.success(`Receipts:`)); successes.forEach(committedVote => { - console.log(`- transaction: ${style.link(`https://etherscan.io/tx/${committedVote.txnHash}`)}`); + console.log(`- transaction: ${style.link(`${url}${committedVote.txnHash}`)}`); console.log(` - salt: ${committedVote.salt}`); console.log(` - voted price: ${web3.utils.fromWei(committedVote.price)}`); }); diff --git a/core/scripts/cli/voting/displayStatus.js b/core/scripts/cli/voting/displayStatus.js index 0454b909fe..2e51a02a9f 100644 --- a/core/scripts/cli/voting/displayStatus.js +++ b/core/scripts/cli/voting/displayStatus.js @@ -26,13 +26,12 @@ const displayVoteStatus = async (web3, voting, designatedVoting) => { const roundPhase = (await voting.getVotePhase()).toString(); // TODO: #901 Can't access Voting.rounds in latest deployed Contract https://etherscan.io/address/0xfe3c4f1ec9f5df918d42ef7ed3fba81cc0086c5f#readContract // const roundStats = await voting.rounds(roundId); - const currentTime = await voting.getCurrentTime(); // If the user is using the two key contract, then the account is the designated voting contract's address const account = designatedVoting ? designatedVoting.address : await getDefaultAccount(web3); const filteredRequests = await filterRequests(pendingRequests, account, roundId, roundPhase, voting); - const rewards = await getAvailableRewards(web3, voting, account); const resolvedPrices = await getResolvedPrices(web3, voting, account); + const rewards = await getAvailableRewards(web3, voting, account); style.spinnerReadingContracts.stop(); // TODO: #901 Can't access Voting.rounds in latest deployed Contract https://etherscan.io/address/0xfe3c4f1ec9f5df918d42ef7ed3fba81cc0086c5f#readContract @@ -87,36 +86,44 @@ const displayVoteStatus = async (web3, voting, designatedVoting) => { } // Display rewards to be retrieved in a table - console.log(`${style.success(`- Voting Rewards Available`)}:`); - if (rewards.roundIds.length > 0) { - const reducer = (accumulator, currentValue) => accumulator.concat(currentValue); - const rewardsTable = Object.values(rewards.rewardsByRoundId) - .reduce(reducer) - .map(reward => { - return { - round_id: reward.roundId, - name: reward.name, - reward_tokens: web3.utils.fromWei(reward.potentialRewards) - }; - }); - console.table(rewardsTable); + if (rewards) { + console.log(`${style.success(`- Voting Rewards Available`)}:`); + if (rewards.roundIds.length > 0) { + const reducer = (accumulator, currentValue) => accumulator.concat(currentValue); + const rewardsTable = Object.values(rewards.rewardsByRoundId) + .reduce(reducer) + .map(reward => { + return { + round_id: reward.roundId, + name: reward.name, + reward_tokens: web3.utils.fromWei(reward.potentialRewards) + }; + }); + console.table(rewardsTable); + } + } else { + console.log(`${style.warning(`- Cannot display available voting rewards for Metamask users`)}`); } // Display resolved prices that voter voted on - console.log(`${style.success(`- Resolved Prices of Votes Participated In`)}:`); - if (Object.keys(resolvedPrices).length > 0) { - const reducer = (accumulator, currentValue) => accumulator.concat(currentValue); - const resolvedPricesTable = Object.values(resolvedPrices) - .reduce(reducer) - .map(resolution => { - return { - round_id: resolution.roundId, - identifier: resolution.identifier, - time: resolution.time, - price: resolution.price - }; - }); - console.table(resolvedPricesTable); + if (resolvedPrices) { + console.log(`${style.success(`- Resolved Prices of Votes Participated In`)}:`); + if (Object.keys(resolvedPrices).length > 0) { + const reducer = (accumulator, currentValue) => accumulator.concat(currentValue); + const resolvedPricesTable = Object.values(resolvedPrices) + .reduce(reducer) + .map(resolution => { + return { + round_id: resolution.roundId, + identifier: resolution.identifier, + time: resolution.time, + price: resolution.price + }; + }); + console.table(resolvedPricesTable); + } + } else { + console.log(`${style.warning(`- Cannot display past vote results for Metamask users`)}`); } console.log(`\n`); diff --git a/core/scripts/cli/voting/getResolvedVotesByRoundId.js b/core/scripts/cli/voting/getResolvedVotesByRoundId.js index ba887f944e..9717b6f6c4 100644 --- a/core/scripts/cli/voting/getResolvedVotesByRoundId.js +++ b/core/scripts/cli/voting/getResolvedVotesByRoundId.js @@ -1,4 +1,5 @@ const style = require("../textStyle"); +const argv = require("minimist")(process.argv.slice()); /** * Return the list of votes (that the voter has participated in) that have successfully resolved a price @@ -9,6 +10,11 @@ const style = require("../textStyle"); * @param {* String} account Etheruem account of voter */ const getResolvedVotesByRound = async (web3, votingContract, account) => { + // TODO(#901): MetaMask provider sometimes has trouble reading past events + if (argv.network === "metamask") { + return; + } + // First check list of votes revealed by user to determine which // price requests a user has voted on const revealedVotes = await votingContract.getPastEvents("VoteRevealed", { diff --git a/core/scripts/cli/voting/getRewardsByRoundId.js b/core/scripts/cli/voting/getRewardsByRoundId.js index b86eb5680a..c77e19f1b2 100644 --- a/core/scripts/cli/voting/getRewardsByRoundId.js +++ b/core/scripts/cli/voting/getRewardsByRoundId.js @@ -1,4 +1,5 @@ const style = require("../textStyle"); +const argv = require("minimist")(process.argv.slice()); /** * Given a list of revealed votes for a user, return @@ -18,6 +19,11 @@ const style = require("../textStyle"); * @param {* String} account Etheruem account of voter */ const getRewardsByRound = async (web3, votingContract, account) => { + // TODO(#901): MetaMask provider sometimes has trouble reading past events + if (argv.network === "metamask") { + return; + } + // All rewards available must be tied to formerly revealed votes const revealedVotes = await votingContract.getPastEvents("VoteRevealed", { filter: { voter: account }, diff --git a/core/scripts/cli/voting/retrieveRewards.js b/core/scripts/cli/voting/retrieveRewards.js index f48fc69454..8e995e193e 100644 --- a/core/scripts/cli/voting/retrieveRewards.js +++ b/core/scripts/cli/voting/retrieveRewards.js @@ -3,6 +3,7 @@ const style = require("../textStyle"); const { batchRetrieveRewards } = require("../../../../common/VotingUtils"); const getAvailableRewards = require("./getRewardsByRoundId"); const inquirer = require("inquirer"); +const argv = require("minimist")(process.argv.slice()); /** * This prompts the user to select from a list of round ID's that have one or more rewards that can be retrieved. @@ -12,6 +13,14 @@ const inquirer = require("inquirer"); * @param {* Object} voting deployed Voting.sol contract instance */ const retrieveRewards = async (web3, voting, designatedVoting) => { + // TODO(#901): MetaMask provider sometimes has trouble reading past events + if (argv.network === "metamask") { + console.log( + `Sorry, we currently do not support retrieving rewards for Metamask users! Try again with another web3 provider.` + ); + return; + } + style.spinnerReadingContracts.start(); // If the user is using the two key contract, then the account is the designated voting contract's address const account = designatedVoting ? designatedVoting.address : await getDefaultAccount(web3); @@ -21,7 +30,7 @@ const retrieveRewards = async (web3, voting, designatedVoting) => { if (roundIds.length > 0) { console.group( `${style.instruction( - `\nPlease select which round ID of resolved price requests you would like to retrieve rewards for` + `\nPlease select which round ID of resolved price requests you would like to retrieve rewards for:` )}` ); roundIds.push({ name: "back" }); @@ -57,7 +66,7 @@ const retrieveRewards = async (web3, voting, designatedVoting) => { console.log(`\n`); console.groupEnd(); } else { - console.log(`You have no rewards to retrieve`); + console.log(`You have no rewards to retrieve.`); } }; diff --git a/package.json b/package.json index c7914ede9a..e72e873c1c 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "minimist": "^1.2.0", "moment": "^2.24.0", "node-fetch": "^2.3.0", - "node-metamask": "^1.1.2", + "node-metamask": "github:UMAprotocol/node-metamask", "ora": "^4.0.3", "require-context": "^1.1.0", "secp256k1": "^3.7.1",