Skip to content
Merged
23 changes: 20 additions & 3 deletions common/VotingUtils.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions common/globalTruffleConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <http://truffleframework.com/docs/advanced/configuration>
Expand Down
1 change: 1 addition & 0 deletions core/scripts/cli/textStyle.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const style = {
// Colors
instruction: chalkPipe("bgRed"),
success: chalkPipe("bgGreen"),
warning: chalkPipe("bgYellow"),
help: chalkPipe("bgCyan"),

// Links
Expand Down
6 changes: 5 additions & 1 deletion core/scripts/cli/vote.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
13 changes: 12 additions & 1 deletion core/scripts/cli/voting/commitVotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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(
Expand All @@ -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)}`);
});
Expand Down
65 changes: 36 additions & 29 deletions core/scripts/cli/voting/displayStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`);
Expand Down
6 changes: 6 additions & 0 deletions core/scripts/cli/voting/getResolvedVotesByRoundId.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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", {
Expand Down
6 changes: 6 additions & 0 deletions core/scripts/cli/voting/getRewardsByRoundId.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 },
Expand Down
13 changes: 11 additions & 2 deletions core/scripts/cli/voting/retrieveRewards.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
Expand All @@ -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" });
Expand Down Expand Up @@ -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.`);
}
};

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down