From 1b784ef91df5fd2f7a2d003430597c6d910ce0c0 Mon Sep 17 00:00:00 2001 From: yorhodes Date: Mon, 4 Oct 2021 16:10:31 -0700 Subject: [PATCH 01/17] Simplify release gold wrapper (closure over account == contract address) --- .../contractkit/src/wrappers/ReleaseGold.ts | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts b/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts index c1bf5cc38cb..df7aff5cddc 100644 --- a/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts +++ b/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts @@ -1,8 +1,10 @@ +import { concurrentMap } from '@celo/base' import { findAddressIndex } from '@celo/base/lib/address' import { Signature } from '@celo/base/lib/signatureUtils' import { Address, CeloTransactionObject, toTransactionObject } from '@celo/connect' import { hashMessageWithPrefix, signedMessageToPublicKey } from '@celo/utils/lib/signatureUtils' import BigNumber from 'bignumber.js' +import { flatten } from 'fp-ts/lib/Array' import { ReleaseGold } from '../generated/ReleaseGold' import { BaseWrapper, @@ -575,6 +577,7 @@ export class ReleaseGoldWrapper extends BaseWrapper { /** * Revokes pending votes + * @deprecated prefer revokePendingVotes * @param account The account to revoke from. * @param validatorGroup The group to revoke the vote for. * @param value The amount of gold to revoke. @@ -598,10 +601,19 @@ export class ReleaseGoldWrapper extends BaseWrapper { ) } + /** + * Revokes pending votes + * @param validatorGroup The group to revoke the vote for. + * @param value The amount of gold to revoke. + */ + revokePendingVotes = (group: Address, value: BigNumber) => + this.revokePending(this.address, group, value) + /** * Revokes active votes + * @deprecated Prefer revokeActiveVotes * @param account The account to revoke from. - * @param validatorGroup The group to revoke the vote for. + * @param group The group to revoke the vote for. * @param value The amount of gold to revoke. */ async revokeActive( @@ -623,6 +635,21 @@ export class ReleaseGoldWrapper extends BaseWrapper { ) } + /** + * Revokes active votes + * @param group The group to revoke the vote for. + * @param value The amount of gold to revoke. + */ + revokeActiveVotes = (group: Address, value: BigNumber) => + this.revokeActive(this.address, group, value) + + /** + * Revokes value from pending/active aggregate + * @deprecated prefer revokeValueFromVotes + * @param account The account to revoke from. + * @param group The group to revoke the vote for. + * @param value The amount of gold to revoke. + */ async revoke( account: Address, group: Address, @@ -644,4 +671,37 @@ export class ReleaseGoldWrapper extends BaseWrapper { } return txos } + + /** + * Revokes value from pending/active aggregate + * @param group The group to revoke the vote for. + * @param value The amount of gold to revoke. + */ + revokeValueFromVotes = (group: Address, value: BigNumber) => + this.revoke(this.address, group, value) + + revokeAllVotesForGroup = async (group: Address) => { + const txos = [] + const electionContract = await this.kit.contracts.getElection() + const { pending, active } = await electionContract.getVotesForGroupByAccount( + this.address, + group + ) + if (pending.isGreaterThan(0)) { + const revokePendingTx = await this.revokePendingVotes(group, pending) + txos.push(revokePendingTx) + } + if (active.isGreaterThan(0)) { + const revokeActiveTx = await this.revokeActiveVotes(group, active) + txos.push(revokeActiveTx) + } + return txos + } + + revokeAllVotesForAllGroups = async () => { + const electionContract = await this.kit.contracts.getElection() + const groups = await electionContract.getGroupsVotedForByAccount(this.address) + const txoMatrix = await concurrentMap(4, groups, (group) => this.revokeAllVotesForGroup(group)) + return flatten(txoMatrix) + } } From 7a136abb124e11dc960c4adb2ce42a7c92ec125a Mon Sep 17 00:00:00 2001 From: yorhodes Date: Mon, 4 Oct 2021 16:11:08 -0700 Subject: [PATCH 02/17] Add --all flags to releasegold:revoke-votes --- .../src/commands/releasegold/revoke-votes.ts | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/commands/releasegold/revoke-votes.ts b/packages/cli/src/commands/releasegold/revoke-votes.ts index c9cc1dcd623..d3aed95beb1 100644 --- a/packages/cli/src/commands/releasegold/revoke-votes.ts +++ b/packages/cli/src/commands/releasegold/revoke-votes.ts @@ -1,3 +1,4 @@ +import { CeloTransactionObject } from '@celo/connect' import { flags } from '@oclif/command' import BigNumber from 'bignumber.js' import { newCheckBuilder } from '../../utils/checks' @@ -12,34 +13,62 @@ export default class RevokeVotes extends ReleaseGoldBaseCommand { static flags = { ...ReleaseGoldBaseCommand.flags, group: Flags.address({ - required: true, + required: false, + exclusive: ['allGroups'], description: 'Address of the group to revoke votes from', }), - votes: flags.string({ required: true, description: 'The number of votes to revoke' }), + votes: flags.string({ + required: false, + exclusive: ['allVotes', 'allGroups'], + description: 'The number of votes to revoke', + }), + allVotes: flags.boolean({ + required: false, + exclusive: ['votes'], + description: 'Revoke all votes', + }), + allGroups: flags.boolean({ + required: false, + exclusive: ['group', 'votes'], + description: 'Revoke all votes from all groups', + }), } static examples = [ 'revoke-votes --contract 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --group 0x5409ED021D9299bf6814279A6A1411A7e866A631 --votes 100', + 'revoke-votes --contract 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --allVotes --allGroups', ] async run() { // tslint:disable-next-line const { flags } = this.parse(RevokeVotes) + await newCheckBuilder(this).isAccount(this.releaseGoldWrapper.address).runChecks() + const isRevoked = await this.releaseGoldWrapper.isRevoked() const beneficiary = await this.releaseGoldWrapper.getBeneficiary() const releaseOwner = await this.releaseGoldWrapper.getReleaseOwner() - const votes = new BigNumber(flags.votes) - await newCheckBuilder(this).isAccount(this.releaseGoldWrapper.address).runChecks() this.kit.defaultAccount = isRevoked ? releaseOwner : beneficiary - const txos = await this.releaseGoldWrapper.revoke( - this.releaseGoldWrapper.address, - flags.group, - votes - ) + + let txos: Array> + if (flags.allVotes && flags.allGroups) { + txos = await this.releaseGoldWrapper.revokeAllVotesForAllGroups() + } else if (flags.allVotes && flags.group) { + txos = await this.releaseGoldWrapper.revokeAllVotesForGroup(flags.group) + } else if (flags.votes && flags.group) { + txos = await this.releaseGoldWrapper.revokeValueFromVotes( + flags.group, + new BigNumber(flags.votes) + ) + } else { + throw new Error( + 'Must provide --votes amount and --group address or --allVotes --allGroups flags' + ) + } + for (const txo of txos) { - await displaySendTx('revoke', txo) + await displaySendTx('revokeVotes', txo) } } } From bca3ca6c17aa413f422e83ef9e645d0a998428d0 Mon Sep 17 00:00:00 2001 From: yorhodes Date: Mon, 4 Oct 2021 16:22:37 -0700 Subject: [PATCH 03/17] Add bytecode verification to cli releasegold base --- packages/cli/src/utils/release-gold-base.ts | 15 ++++++++------- packages/cli/tsconfig.json | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/utils/release-gold-base.ts b/packages/cli/src/utils/release-gold-base.ts index 9921d8c2aa7..600ca7cce5f 100644 --- a/packages/cli/src/utils/release-gold-base.ts +++ b/packages/cli/src/utils/release-gold-base.ts @@ -1,5 +1,6 @@ import { newReleaseGold } from '@celo/contractkit/lib/generated/ReleaseGold' import { ReleaseGoldWrapper } from '@celo/contractkit/lib/wrappers/ReleaseGold' +import ReleaseGoldArtifact from '@celo/protocol/build/core-contracts.v5/contracts/ReleaseGold.json' import { ParserOutput } from '@oclif/parser/lib/parse' import { BaseCommand } from '../base' import { Flags } from './command' @@ -31,16 +32,16 @@ export abstract class ReleaseGoldBaseCommand extends BaseCommand { async init() { await super.init() if (!this._releaseGoldWrapper) { + const runtimeBytecode = await this.web3.eth.getCode(this.contractAddress) + if (runtimeBytecode !== ReleaseGoldArtifact.deployedBytecode) { + throw new Error( + `Contract at ${this.contractAddress} does not match expected ReleaseGold bytecode` + ) + } this._releaseGoldWrapper = new ReleaseGoldWrapper( this.kit, - newReleaseGold(this.kit.connection.web3, this.contractAddress as string) + newReleaseGold(this.kit.connection.web3, this.contractAddress) ) - // Call arbitrary release gold fn to verify `contractAddress` is a releasegold contract. - try { - await this._releaseGoldWrapper.getBeneficiary() - } catch (err) { - this.error(`Does the provided address point to release gold contract? ${err}`) - } } } } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index b40c8057222..6af7123508d 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -4,7 +4,8 @@ "rootDir": "src", "outDir": "lib", "esModuleInterop": true, - "target": "es6" + "target": "es6", + "resolveJsonModule": true }, "include": ["src", "../contractkit/types", "types/hw-transport-node-hid.d.ts"], "references": [{ "path": "../sdk/utils" }, { "path": "../sdk/contractkit" }] From bb114ce7f4b810ee54b91b5a0841c688be8a8b3b Mon Sep 17 00:00:00 2001 From: yorhodes Date: Tue, 5 Oct 2021 11:35:03 -0700 Subject: [PATCH 04/17] Add more wrapper utilities --- .../sdk/contractkit/src/wrappers/Election.ts | 6 ++++++ .../sdk/contractkit/src/wrappers/Governance.ts | 16 ++++++++++++++++ .../sdk/contractkit/src/wrappers/ReleaseGold.ts | 6 ++++++ 3 files changed, 28 insertions(+) diff --git a/packages/sdk/contractkit/src/wrappers/Election.ts b/packages/sdk/contractkit/src/wrappers/Election.ts index f4b22918e0a..461d524389a 100644 --- a/packages/sdk/contractkit/src/wrappers/Election.ts +++ b/packages/sdk/contractkit/src/wrappers/Election.ts @@ -252,6 +252,12 @@ export class ElectionWrapper extends BaseWrapper { return { address: account, votes } } + getTotalVotesByAccount = proxyCall( + this.contract.methods.getTotalVotesByAccount, + undefined, + valueToBigNumber + ) + /** * Returns whether or not the account has any pending votes. * @param account The address of the account casting votes. diff --git a/packages/sdk/contractkit/src/wrappers/Governance.ts b/packages/sdk/contractkit/src/wrappers/Governance.ts index 3e75ea0e25c..a75327fce71 100644 --- a/packages/sdk/contractkit/src/wrappers/Governance.ts +++ b/packages/sdk/contractkit/src/wrappers/Governance.ts @@ -529,6 +529,15 @@ export class GovernanceWrapper extends BaseWrapper { }) ) + async isUpvoting(upvoter: Address) { + const upvote = await this.getUpvoteRecord(upvoter) + return ( + !upvote.proposalID.isZero() && + (await this.isQueued(upvote.proposalID)) && + !(await this.isQueuedProposalExpired(upvote.proposalID)) + ) + } + /** * Returns the corresponding vote record * @param voter Address of voter @@ -623,6 +632,11 @@ export class GovernanceWrapper extends BaseWrapper { return voteRecords.filter((record) => record != null) as VoteRecord[] } + async isVotingReferendum(voter: Address) { + const records = await this.getVoteRecords(voter) + return records.length !== 0 + } + /* * Returns information pertaining to a voter in governance. */ @@ -786,6 +800,8 @@ export class GovernanceWrapper extends BaseWrapper { ) } + revokeVotes = proxySend(this.kit, this.contract.methods.revokeVotes) + /** * Returns `voter`'s vote choice on a given proposal. * @param proposalID Governance proposal UUID diff --git a/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts b/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts index df7aff5cddc..687bae82b53 100644 --- a/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts +++ b/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts @@ -279,6 +279,12 @@ export class ReleaseGoldWrapper extends BaseWrapper { this.contract.methods.revoke ) + /** + * Revoke a vesting CELO schedule from the contract's beneficiary. + * @return A CeloTransactionObject + */ + revokeBeneficiary = this.revokeReleasing + /** * Refund `refundAddress` and `beneficiary` after the ReleaseGold schedule has been revoked. * @return A CeloTransactionObject From 0dceebd98931a7ac0f20aa44e29b9317c609f3c2 Mon Sep 17 00:00:00 2001 From: yorhodes Date: Tue, 5 Oct 2021 12:01:24 -0700 Subject: [PATCH 05/17] Add releasegold:admin-revoke for idempotent revocation progress --- .../src/commands/releasegold/admin-revoke.ts | 147 ++++++++++++++++++ packages/cli/src/utils/cli.ts | 8 +- .../contractkit/src/wrappers/ReleaseGold.ts | 6 + 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 packages/cli/src/commands/releasegold/admin-revoke.ts diff --git a/packages/cli/src/commands/releasegold/admin-revoke.ts b/packages/cli/src/commands/releasegold/admin-revoke.ts new file mode 100644 index 00000000000..7279bf123a2 --- /dev/null +++ b/packages/cli/src/commands/releasegold/admin-revoke.ts @@ -0,0 +1,147 @@ +import { generateKeys, generateMnemonic } from '@celo/utils/lib/account' +import { flags } from '@oclif/command' +import prompts from 'prompts' +import { displaySendTx, printValueMap } from '../../utils/cli' +import { ReleaseGoldBaseCommand } from '../../utils/release-gold-base' + +export default class AdminRevoke extends ReleaseGoldBaseCommand { + static hidden = true + + static description = 'Take all possible steps to revoke given contract instance.' + + static flags = { + ...ReleaseGoldBaseCommand.flags, + yesreally: flags.boolean({ description: 'Override interactive prompt to confirm revocation' }), + } + + static args = [] + + static examples = ['admin-revoke --contract 0x5409ED021D9299bf6814279A6A1411A7e866A631'] + + async run() { + // tslint:disable-next-line + const { flags } = this.parse(AdminRevoke) + + if (!flags.yesreally) { + const response = await prompts({ + type: 'confirm', + name: 'confirmation', + message: 'Are you sure you want to revoke this contract? (y/n)', + }) + + if (!response.confirmation) { + console.info('Aborting due to user response') + process.exit(0) + } + } + + this.kit.defaultAccount = await this.releaseGoldWrapper.getReleaseOwner() + + const isRevoked = await this.releaseGoldWrapper.isRevoked() + if (!isRevoked) { + await displaySendTx( + 'releasegold: revokeBeneficiary', + this.releaseGoldWrapper.revokeBeneficiary(), + undefined, + 'ReleaseScheduleRevoked' + ) + } + + const accounts = await this.kit.contracts.getAccounts() + const isAccount = await accounts.isAccount(this.contractAddress) + if (isAccount) { + const election = await this.kit.contracts.getElection() + const electionVotes = await election.getTotalVotesByAccount(this.contractAddress) + const isElectionVoting = electionVotes.isGreaterThan(0) + + const governance = await this.kit.contracts.getGovernance() + const isGovernanceVoting = await governance.isVoting(this.contractAddress) + + if (isElectionVoting || isGovernanceVoting) { + // rotate vote signers + const voteSigner = await accounts.getVoteSigner(this.contractAddress) + if (voteSigner !== this.contractAddress) { + const keys = await generateKeys(await generateMnemonic()) + const pop = await accounts.generateProofOfKeyPossessionLocally( + this.contractAddress, + keys.address, + keys.privateKey + ) + await displaySendTx( + 'accounts: rotateVoteSigner', + await this.releaseGoldWrapper.authorizeVoteSigner(keys.address, pop), + undefined, + 'VoteSignerAuthorized' + ) + } + + // handle governance votes + if (isGovernanceVoting) { + const isUpvoting = await governance.isUpvoting(this.contractAddress) + if (isUpvoting) { + await displaySendTx( + 'governance: revokeUpvote', + await governance.revokeUpvote(this.contractAddress), + undefined, + 'ProposalUpvoteRevoked' + ) + } + + const isVotingReferendum = await governance.isVotingReferendum(this.contractAddress) + if (isVotingReferendum) { + await displaySendTx( + 'governance: revokeVotes', + governance.revokeVotes(), + undefined, + 'ProposalVoteRevoked' + ) + } + } + + // handle election votes + if (isElectionVoting) { + const txos = await this.releaseGoldWrapper.revokeAllVotesForAllGroups() + for (const txo of txos) { + await displaySendTx('election: revokeVotes', txo, undefined, [ + 'ValidatorGroupPendingVoteRevoked', + 'ValidatorGroupActiveVoteRevoked', + ]) + } + } + } + + await displaySendTx( + 'releasegold: unlockAllGold', + await this.releaseGoldWrapper.unlockAllGold(), + undefined, + 'GoldUnlocked' + ) + } + + const stabletoken = await this.kit.contracts.getStableToken() + const cusdBalance = await stabletoken.balanceOf(this.contractAddress) + if (cusdBalance.isGreaterThan(0)) { + await displaySendTx( + 'releasegold: rescueCUSD', + this.releaseGoldWrapper.transfer(this.kit.defaultAccount, cusdBalance), + undefined, + 'Transfer' + ) + } + + const remainingLockedGold = await this.releaseGoldWrapper.getRemainingLockedBalance() + if (remainingLockedGold.isZero()) { + await displaySendTx( + 'releasegold: refundAndFinalize', + this.releaseGoldWrapper.refundAndFinalize(), + undefined, + 'ReleaseGoldInstanceDestroyed' + ) + } else { + console.log('Some gold is still locked, printing pending withdrawals...') + const lockedGold = await this.kit.contracts.getLockedGold() + const pendingWithdrawals = await lockedGold.getPendingWithdrawals(this.contractAddress) + pendingWithdrawals.forEach((w) => printValueMap(w)) + } + } +} diff --git a/packages/cli/src/utils/cli.ts b/packages/cli/src/utils/cli.ts index 33634158acb..22515a574de 100644 --- a/packages/cli/src/utils/cli.ts +++ b/packages/cli/src/utils/cli.ts @@ -17,7 +17,7 @@ export async function displaySendTx( name: string, txObj: CeloTransactionObject, tx?: Omit, - displayEventName?: string + displayEventName?: string | string[] ) { cli.action.start(`Sending Transaction: ${name}`) try { @@ -33,7 +33,11 @@ export async function displaySendTx( if (displayEventName && txReceipt.events) { Object.entries(txReceipt.events) - .filter(([eventName]) => eventName === displayEventName) + .filter( + ([eventName]) => + (typeof displayEventName === 'string' && eventName === displayEventName) || + displayEventName.includes(eventName) + ) .forEach(([eventName, log]) => { const { params } = parseDecodedParams((log as EventLog).returnValues) console.log(chalk.magenta.bold(`${eventName}:`)) diff --git a/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts b/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts index 687bae82b53..98398cf166f 100644 --- a/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts +++ b/packages/sdk/contractkit/src/wrappers/ReleaseGold.ts @@ -320,6 +320,12 @@ export class ReleaseGoldWrapper extends BaseWrapper { tupleParser(valueToString) ) + async unlockAllGold() { + const lockedGold = await this.kit.contracts.getLockedGold() + const amount = await lockedGold.getAccountTotalLockedGold(this.address) + return this.unlockGold(amount) + } + /** * Relocks gold in the ReleaseGold instance that has been unlocked but not withdrawn. * @param index The index of the pending withdrawal to relock from. From de3c19dd2ae57cc7c0d18faf916d89903caf5ae9 Mon Sep 17 00:00:00 2001 From: yorhodes Date: Tue, 5 Oct 2021 12:06:27 -0700 Subject: [PATCH 06/17] Remove tslint disable --- packages/cli/src/commands/releasegold/admin-revoke.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/releasegold/admin-revoke.ts b/packages/cli/src/commands/releasegold/admin-revoke.ts index 7279bf123a2..bbb08edd016 100644 --- a/packages/cli/src/commands/releasegold/admin-revoke.ts +++ b/packages/cli/src/commands/releasegold/admin-revoke.ts @@ -19,10 +19,9 @@ export default class AdminRevoke extends ReleaseGoldBaseCommand { static examples = ['admin-revoke --contract 0x5409ED021D9299bf6814279A6A1411A7e866A631'] async run() { - // tslint:disable-next-line - const { flags } = this.parse(AdminRevoke) + const { flags: _flags } = this.parse(AdminRevoke) - if (!flags.yesreally) { + if (!_flags.yesreally) { const response = await prompts({ type: 'confirm', name: 'confirmation', From fbf1517a8a7f6cf04d27224f8737930ceda1e94b Mon Sep 17 00:00:00 2001 From: yorhodes Date: Tue, 5 Oct 2021 12:07:48 -0700 Subject: [PATCH 07/17] Add helpful comments --- packages/cli/src/commands/releasegold/admin-revoke.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cli/src/commands/releasegold/admin-revoke.ts b/packages/cli/src/commands/releasegold/admin-revoke.ts index bbb08edd016..da962d865dd 100644 --- a/packages/cli/src/commands/releasegold/admin-revoke.ts +++ b/packages/cli/src/commands/releasegold/admin-revoke.ts @@ -117,6 +117,7 @@ export default class AdminRevoke extends ReleaseGoldBaseCommand { ) } + // rescue any cUSD balance const stabletoken = await this.kit.contracts.getStableToken() const cusdBalance = await stabletoken.balanceOf(this.contractAddress) if (cusdBalance.isGreaterThan(0)) { @@ -128,6 +129,7 @@ export default class AdminRevoke extends ReleaseGoldBaseCommand { ) } + // attempt to refund and finalize, surface pending withdrawals const remainingLockedGold = await this.releaseGoldWrapper.getRemainingLockedBalance() if (remainingLockedGold.isZero()) { await displaySendTx( From 40a5e590c98af02a2b17718dfeb5a7c7142c8e70 Mon Sep 17 00:00:00 2001 From: yorhodes Date: Tue, 5 Oct 2021 15:25:56 -0700 Subject: [PATCH 08/17] Temporarily disable bytecode verification --- packages/cli/src/utils/release-gold-base.ts | 26 ++++++++++++++------- packages/cli/tsconfig.json | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/utils/release-gold-base.ts b/packages/cli/src/utils/release-gold-base.ts index 600ca7cce5f..7b022e2adeb 100644 --- a/packages/cli/src/utils/release-gold-base.ts +++ b/packages/cli/src/utils/release-gold-base.ts @@ -1,7 +1,9 @@ import { newReleaseGold } from '@celo/contractkit/lib/generated/ReleaseGold' import { ReleaseGoldWrapper } from '@celo/contractkit/lib/wrappers/ReleaseGold' -import ReleaseGoldArtifact from '@celo/protocol/build/core-contracts.v5/contracts/ReleaseGold.json' import { ParserOutput } from '@oclif/parser/lib/parse' +// import { flags } from '@oclif/command' +// import { readJsonSync } from 'fs-extra' +// import ReleaseGoldArtifactfrom '../../ReleaseGold.json' import { BaseCommand } from '../base' import { Flags } from './command' @@ -9,6 +11,8 @@ export abstract class ReleaseGoldBaseCommand extends BaseCommand { static flags = { ...BaseCommand.flags, contract: Flags.address({ required: true, description: 'Address of the ReleaseGold Contract' }), + // verifyBytecode: flags.boolean({ required: false, default: false, description: 'Verify bytecode of contract'}), + // verifyStorage: flags.string({ required: false, description: 'Path to json where grant is specified'}), } private _contractAddress: string | null = null @@ -17,7 +21,7 @@ export abstract class ReleaseGoldBaseCommand extends BaseCommand { get contractAddress() { if (!this._contractAddress) { const res: ParserOutput = this.parse() - this._contractAddress = String(res.flags.contract) + this._contractAddress = res.flags.contract as string } return this._contractAddress } @@ -32,16 +36,22 @@ export abstract class ReleaseGoldBaseCommand extends BaseCommand { async init() { await super.init() if (!this._releaseGoldWrapper) { - const runtimeBytecode = await this.web3.eth.getCode(this.contractAddress) - if (runtimeBytecode !== ReleaseGoldArtifact.deployedBytecode) { - throw new Error( - `Contract at ${this.contractAddress} does not match expected ReleaseGold bytecode` - ) - } + // const res: ParserOutput = this.parse() + // if (res.flags.verifyBytecode as boolean) { + // const runtimeBytecode = await this.web3.eth.getCode(this.contractAddress) + // if (runtimeBytecode !== ReleaseGoldArtifact.deployedBytecode) { + // throw new Error( + // `Contract at ${this.contractAddress} does not match expected ReleaseGold bytecode` + // ) + // } + // } this._releaseGoldWrapper = new ReleaseGoldWrapper( this.kit, newReleaseGold(this.kit.connection.web3, this.contractAddress) ) + // if (res.flags.verifyStorage) { + // const grant = readJsonSync(res.flags.verifyStorage, {}) + // } } } } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 6af7123508d..c231fb9ff65 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -7,6 +7,6 @@ "target": "es6", "resolveJsonModule": true }, - "include": ["src", "../contractkit/types", "types/hw-transport-node-hid.d.ts"], + "include": ["src/**/*", "../contractkit/types", "types/hw-transport-node-hid.d.ts", ], "references": [{ "path": "../sdk/utils" }, { "path": "../sdk/contractkit" }] } From d21f1e632ba3ff19a8d595946ce4949bc7041e5f Mon Sep 17 00:00:00 2001 From: yorhodes Date: Tue, 5 Oct 2021 15:59:13 -0700 Subject: [PATCH 09/17] Update docs --- .../docs/command-line-interface/releasegold.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/docs/command-line-interface/releasegold.md b/packages/docs/command-line-interface/releasegold.md index 7e591b03c00..e255cc67d86 100644 --- a/packages/docs/command-line-interface/releasegold.md +++ b/packages/docs/command-line-interface/releasegold.md @@ -181,20 +181,27 @@ USAGE $ celocli releasegold:revoke-votes OPTIONS + --allGroups Revoke all votes from all + groups + + --allVotes Revoke all votes + --contract=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the ReleaseGold Contract --globalHelp View all available global flags - --group=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the group - to revoke votes from + --group=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d Address of the group to revoke + votes from - --votes=votes (required) The number of votes - to revoke + --votes=votes The number of votes to revoke -EXAMPLE +EXAMPLES revoke-votes --contract 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --group 0x5409ED021D9299bf6814279A6A1411A7e866A631 --votes 100 + + revoke-votes --contract 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --allVotes + --allGroups ``` _See code: [src/commands/releasegold/revoke-votes.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/releasegold/revoke-votes.ts)_ From 54d18575fc7ef0058d070b596fb6c2522203d7d0 Mon Sep 17 00:00:00 2001 From: yorhodes Date: Thu, 7 Oct 2021 17:04:00 -0700 Subject: [PATCH 10/17] Revert unused changes --- packages/cli/src/utils/release-gold-base.ts | 27 ++++++--------------- packages/cli/tsconfig.json | 5 ++-- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/packages/cli/src/utils/release-gold-base.ts b/packages/cli/src/utils/release-gold-base.ts index 7b022e2adeb..9921d8c2aa7 100644 --- a/packages/cli/src/utils/release-gold-base.ts +++ b/packages/cli/src/utils/release-gold-base.ts @@ -1,9 +1,6 @@ import { newReleaseGold } from '@celo/contractkit/lib/generated/ReleaseGold' import { ReleaseGoldWrapper } from '@celo/contractkit/lib/wrappers/ReleaseGold' import { ParserOutput } from '@oclif/parser/lib/parse' -// import { flags } from '@oclif/command' -// import { readJsonSync } from 'fs-extra' -// import ReleaseGoldArtifactfrom '../../ReleaseGold.json' import { BaseCommand } from '../base' import { Flags } from './command' @@ -11,8 +8,6 @@ export abstract class ReleaseGoldBaseCommand extends BaseCommand { static flags = { ...BaseCommand.flags, contract: Flags.address({ required: true, description: 'Address of the ReleaseGold Contract' }), - // verifyBytecode: flags.boolean({ required: false, default: false, description: 'Verify bytecode of contract'}), - // verifyStorage: flags.string({ required: false, description: 'Path to json where grant is specified'}), } private _contractAddress: string | null = null @@ -21,7 +16,7 @@ export abstract class ReleaseGoldBaseCommand extends BaseCommand { get contractAddress() { if (!this._contractAddress) { const res: ParserOutput = this.parse() - this._contractAddress = res.flags.contract as string + this._contractAddress = String(res.flags.contract) } return this._contractAddress } @@ -36,22 +31,16 @@ export abstract class ReleaseGoldBaseCommand extends BaseCommand { async init() { await super.init() if (!this._releaseGoldWrapper) { - // const res: ParserOutput = this.parse() - // if (res.flags.verifyBytecode as boolean) { - // const runtimeBytecode = await this.web3.eth.getCode(this.contractAddress) - // if (runtimeBytecode !== ReleaseGoldArtifact.deployedBytecode) { - // throw new Error( - // `Contract at ${this.contractAddress} does not match expected ReleaseGold bytecode` - // ) - // } - // } this._releaseGoldWrapper = new ReleaseGoldWrapper( this.kit, - newReleaseGold(this.kit.connection.web3, this.contractAddress) + newReleaseGold(this.kit.connection.web3, this.contractAddress as string) ) - // if (res.flags.verifyStorage) { - // const grant = readJsonSync(res.flags.verifyStorage, {}) - // } + // Call arbitrary release gold fn to verify `contractAddress` is a releasegold contract. + try { + await this._releaseGoldWrapper.getBeneficiary() + } catch (err) { + this.error(`Does the provided address point to release gold contract? ${err}`) + } } } } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index c231fb9ff65..b40c8057222 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -4,9 +4,8 @@ "rootDir": "src", "outDir": "lib", "esModuleInterop": true, - "target": "es6", - "resolveJsonModule": true + "target": "es6" }, - "include": ["src/**/*", "../contractkit/types", "types/hw-transport-node-hid.d.ts", ], + "include": ["src", "../contractkit/types", "types/hw-transport-node-hid.d.ts"], "references": [{ "path": "../sdk/utils" }, { "path": "../sdk/contractkit" }] } From 6da774fa0a518256c3219fd54e362336acc691fe Mon Sep 17 00:00:00 2001 From: yorhodes Date: Mon, 11 Oct 2021 16:29:55 -0700 Subject: [PATCH 11/17] Refactor getContractFromEvent --- .../cli/src/commands/governance/vote.test.ts | 7 +-- .../commands/releasegold/authorize.test.ts | 4 +- .../commands/releasegold/locked-gold.test.ts | 4 +- .../releasegold/refund-and-finalize.test.ts | 4 +- .../releasegold/set-beneficiary.test.ts | 3 +- .../releasegold/transfer-dollars.test.ts | 3 +- .../src/commands/releasegold/withdraw.test.ts | 4 +- packages/dev-utils/src/ganache-test.ts | 59 +++++++------------ 8 files changed, 30 insertions(+), 58 deletions(-) diff --git a/packages/cli/src/commands/governance/vote.test.ts b/packages/cli/src/commands/governance/vote.test.ts index 43039459a5c..df0485bb705 100644 --- a/packages/cli/src/commands/governance/vote.test.ts +++ b/packages/cli/src/commands/governance/vote.test.ts @@ -1,8 +1,7 @@ import { Address } from '@celo/connect' import { newKitFromWeb3 } from '@celo/contractkit' -import { GovernanceWrapper, Proposal } from '@celo/contractkit/lib/wrappers/Governance' +import { GovernanceWrapper } from '@celo/contractkit/lib/wrappers/Governance' import { NetworkConfig, testWithGanache, timeTravel } from '@celo/dev-utils/lib/ganache-test' -import { ProposalBuilder } from '@celo/governance' import BigNumber from 'bignumber.js' import Web3 from 'web3' import Register from '../account/register' @@ -27,10 +26,8 @@ testWithGanache('governance:vote cmd', (web3: Web3) => { accounts = await web3.eth.getAccounts() kit.defaultAccount = accounts[0] governance = await kit.contracts.getGovernance() - let proposal: Proposal - proposal = await new ProposalBuilder(kit).build() await governance - .propose(proposal, 'URL') + .propose([], 'URL') .sendAndWaitForReceipt({ from: accounts[0], value: minDeposit }) await timeTravel(expConfig.dequeueFrequency, web3) await Dequeue.run(['--from', accounts[0]]) diff --git a/packages/cli/src/commands/releasegold/authorize.test.ts b/packages/cli/src/commands/releasegold/authorize.test.ts index b48a3e2d73c..1abb1b506e0 100644 --- a/packages/cli/src/commands/releasegold/authorize.test.ts +++ b/packages/cli/src/commands/releasegold/authorize.test.ts @@ -14,11 +14,9 @@ testWithGanache('releasegold:authorize cmd', (web3: Web3) => { let kit: any beforeEach(async () => { - const contractCanValidate = true contractAddress = await getContractFromEvent( 'ReleaseGoldInstanceCreated(address,address)', - web3, - contractCanValidate + web3 ) kit = newKitFromWeb3(web3) await CreateAccount.run(['--contract', contractAddress]) diff --git a/packages/cli/src/commands/releasegold/locked-gold.test.ts b/packages/cli/src/commands/releasegold/locked-gold.test.ts index d3b3d269942..39716479741 100644 --- a/packages/cli/src/commands/releasegold/locked-gold.test.ts +++ b/packages/cli/src/commands/releasegold/locked-gold.test.ts @@ -11,11 +11,9 @@ testWithGanache('releasegold:locked-gold cmd', (web3: Web3) => { let kit: any beforeEach(async () => { - const contractCanValdiate = true contractAddress = await getContractFromEvent( 'ReleaseGoldInstanceCreated(address,address)', - web3, - contractCanValdiate + web3 ) kit = newKitFromWeb3(web3) await CreateAccount.run(['--contract', contractAddress]) diff --git a/packages/cli/src/commands/releasegold/refund-and-finalize.test.ts b/packages/cli/src/commands/releasegold/refund-and-finalize.test.ts index fe81618e3bc..1d3ff0b54d2 100644 --- a/packages/cli/src/commands/releasegold/refund-and-finalize.test.ts +++ b/packages/cli/src/commands/releasegold/refund-and-finalize.test.ts @@ -14,11 +14,9 @@ testWithGanache('releasegold:refund-and-finalize cmd', (web3: Web3) => { let kit: ContractKit beforeEach(async () => { - const contractCanValidate = false contractAddress = await getContractFromEvent( 'ReleaseGoldInstanceCreated(address,address)', - web3, - contractCanValidate + web3 ) kit = newKitFromWeb3(web3) }) diff --git a/packages/cli/src/commands/releasegold/set-beneficiary.test.ts b/packages/cli/src/commands/releasegold/set-beneficiary.test.ts index 3ac66c65380..0ac3c24d52f 100644 --- a/packages/cli/src/commands/releasegold/set-beneficiary.test.ts +++ b/packages/cli/src/commands/releasegold/set-beneficiary.test.ts @@ -22,11 +22,10 @@ testWithGanache('releasegold:set-beneficiary cmd', (web3: Web3) => { releaseOwner = accounts[0] newBeneficiary = accounts[2] otherAccount = accounts[3] - const contractCanValidate = false contractAddress = await getContractFromEvent( 'ReleaseGoldInstanceCreated(address,address)', web3, - contractCanValidate + { index: 1 } // canValidate = false ) kit = newKitFromWeb3(web3) releaseGoldWrapper = new ReleaseGoldWrapper(kit, newReleaseGold(web3, contractAddress)) diff --git a/packages/cli/src/commands/releasegold/transfer-dollars.test.ts b/packages/cli/src/commands/releasegold/transfer-dollars.test.ts index 7ea297bf7fc..e066ce34371 100644 --- a/packages/cli/src/commands/releasegold/transfer-dollars.test.ts +++ b/packages/cli/src/commands/releasegold/transfer-dollars.test.ts @@ -17,11 +17,10 @@ testWithGanache('releasegold:transfer-dollars cmd', (web3: Web3) => { let kit: ContractKit beforeEach(async () => { - const contractCanValidate = false contractAddress = await getContractFromEvent( 'ReleaseGoldInstanceCreated(address,address)', web3, - contractCanValidate + { index: 1 } // canValidate = false ) kit = newKitFromWeb3(web3) accounts = await web3.eth.getAccounts() diff --git a/packages/cli/src/commands/releasegold/withdraw.test.ts b/packages/cli/src/commands/releasegold/withdraw.test.ts index 9d71c067d7d..e8512eece96 100644 --- a/packages/cli/src/commands/releasegold/withdraw.test.ts +++ b/packages/cli/src/commands/releasegold/withdraw.test.ts @@ -16,11 +16,9 @@ testWithGanache('releasegold:withdraw cmd', (web3: Web3) => { let kit: ContractKit beforeEach(async () => { - const contractCanValidate = true contractAddress = await getContractFromEvent( 'ReleaseGoldInstanceCreated(address,address)', - web3, - contractCanValidate + web3 ) kit = newKitFromWeb3(web3) await CreateAccount.run(['--contract', contractAddress]) diff --git a/packages/dev-utils/src/ganache-test.ts b/packages/dev-utils/src/ganache-test.ts index 225546deb08..2722d043f2b 100644 --- a/packages/dev-utils/src/ganache-test.ts +++ b/packages/dev-utils/src/ganache-test.ts @@ -82,47 +82,32 @@ export function testWithGanache(name: string, fn: (web3: Web3) => void) { /** * Gets a contract address by parsing blocks and matching event signatures against the given event. - * `canValidate` actually controls whether we grab the first or second contract associated with - * the given `eventSignature`. This is to allow for deployment of two contracts with distinct - * setup parameters for testing. */ export async function getContractFromEvent( eventSignature: string, web3: Web3, - canValidate: boolean + filter?: { + expectedData?: string + index?: number + } ): Promise { - let currBlockNumber = await web3.eth.getBlockNumber() - let currBlock: any - let contractAddress: any - const target = web3.utils.sha3(eventSignature) - let found = false - while (true) { - currBlock = await web3.eth.getBlock(currBlockNumber) - for (const tx of currBlock.transactions) { - const txFull = await web3.eth.getTransactionReceipt(tx) - if (txFull.logs) { - for (const log of txFull.logs) { - if (log.topics) { - for (const topic of log.topics) { - if (topic === target) { - if (canValidate && !found) { - found = true - } else { - contractAddress = log.address - } - } - } - } - } - } - } - currBlockNumber-- - if (contractAddress !== undefined) { - break - } - if (currBlockNumber < 0) { - throw Error('Error: ReleaseGoldInstance could not be found') - } + const logs = await web3.eth.getPastLogs({ + topics: [web3.utils.sha3(eventSignature)], + fromBlock: 'earliest', + toBlock: 'latest', + }) + if (logs.length === 0) { + throw Error(`Error: contract could not be found matching signature ${eventSignature}`) + } + const logIndex = filter?.index ?? 0 + if (!filter?.expectedData) { + return logs[logIndex].address + } + const filteredLogs = logs.filter((log) => log.data === filter.expectedData) + if (filteredLogs.length === 0) { + throw Error( + `Error: contract could not be found matching signature ${eventSignature} with data ${filter.expectedData}` + ) } - return contractAddress + return filteredLogs[logIndex ?? 0].address } From 6f3fe91e41c4dd261f2849e5283163f197413b76 Mon Sep 17 00:00:00 2001 From: yorhodes Date: Mon, 11 Oct 2021 16:30:33 -0700 Subject: [PATCH 12/17] Add celocli releasegold:admin-revoke tests --- .../commands/releasegold/admin-revoke.test.ts | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 packages/cli/src/commands/releasegold/admin-revoke.test.ts diff --git a/packages/cli/src/commands/releasegold/admin-revoke.test.ts b/packages/cli/src/commands/releasegold/admin-revoke.test.ts new file mode 100644 index 00000000000..11c3c1f4c44 --- /dev/null +++ b/packages/cli/src/commands/releasegold/admin-revoke.test.ts @@ -0,0 +1,137 @@ +import { serializeSignature } from '@celo/base/lib/signatureUtils' +import { ContractKit, newKitFromWeb3 } from '@celo/contractkit' +import { newReleaseGold } from '@celo/contractkit/lib/generated/ReleaseGold' +import { AccountsWrapper } from '@celo/contractkit/lib/wrappers/Accounts' +import { GovernanceWrapper } from '@celo/contractkit/lib/wrappers/Governance' +import { ReleaseGoldWrapper } from '@celo/contractkit/lib/wrappers/ReleaseGold' +import { + getContractFromEvent, + NetworkConfig, + testWithGanache, + timeTravel, +} from '@celo/dev-utils/lib/ganache-test' +import Web3 from 'web3' +// import ElectionVote from '../election/vote' +import Approve from '../governance/approve' +import GovernanceVote from '../governance/vote' +import AdminRevoke from './admin-revoke' +import Authorize from './authorize' +import CreateAccount from './create-account' +import LockedGold from './locked-gold' + +process.env.NO_SYNCCHECK = 'true' + +testWithGanache('releasegold:admin-revoke cmd', (web3: Web3) => { + let kit: ContractKit + let contractAddress: string + let releaseGoldWrapper: ReleaseGoldWrapper + let accounts: string[] + + beforeEach(async () => { + contractAddress = await getContractFromEvent( + 'ReleaseGoldInstanceCreated(address,address)', + web3, + { index: 1 } // revocable: true + ) + kit = newKitFromWeb3(web3) + releaseGoldWrapper = new ReleaseGoldWrapper(kit, newReleaseGold(web3, contractAddress)) + const amt = await releaseGoldWrapper.getCurrentReleasedTotalAmount() + console.log('released', amt) + accounts = await web3.eth.getAccounts() + }) + + test('will revoke', async () => { + await AdminRevoke.run(['--contract', contractAddress, '--yesreally']) + const revokedContract = await getContractFromEvent( + 'ReleaseScheduleRevoked(uint256,uint256)', + web3 + ) + expect(revokedContract).toBe(contractAddress) + }) + + describe('#when account exists with locked gold', () => { + const value = '1' + + beforeEach(async () => { + await CreateAccount.run(['--contract', contractAddress]) + await LockedGold.run([ + '--contract', + contractAddress, + '--action', + 'lock', + '--value', + value, + '--yes', + ]) + }) + + test('will unlock all gold', async () => { + await AdminRevoke.run(['--contract', contractAddress, '--yesreally']) + const lockedGold = await kit.contracts.getLockedGold() + const lockedAmount = await lockedGold.getAccountTotalLockedGold(releaseGoldWrapper.address) + expect(lockedAmount.isZero()).toBeTruthy() + }) + + describe('#when account has authorized a vote signer', () => { + let voteSigner: string + let accountsWrapper: AccountsWrapper + + beforeEach(async () => { + voteSigner = accounts[2] + accountsWrapper = await kit.contracts.getAccounts() + const pop = await accountsWrapper.generateProofOfKeyPossession(contractAddress, voteSigner) + await Authorize.run([ + '--contract', + contractAddress, + '--role', + 'vote', + '--signer', + voteSigner, + '--signature', + serializeSignature(pop), + ]) + }) + + test('will rotate vote signer', async () => { + await AdminRevoke.run(['--contract', contractAddress, '--yesreally']) + const newVoteSigner = await accountsWrapper.getVoteSigner(contractAddress) + expect(newVoteSigner).not.toEqual(voteSigner) + }) + + describe('#when account has voted', () => { + const proposalID = '1' + let governance: GovernanceWrapper + + beforeEach(async () => { + // from vote.test.ts + const expConfig = NetworkConfig.governance + const minDeposit = web3.utils.toWei(expConfig.minDeposit.toString(), 'ether') + governance = await kit.contracts.getGovernance() + await governance + .propose([], 'URL') + .sendAndWaitForReceipt({ from: accounts[0], value: minDeposit }) + await timeTravel(expConfig.dequeueFrequency, web3) + await Approve.run(['--from', accounts[0], '--proposalID', proposalID, '--useMultiSig']) + await GovernanceVote.run([ + '--from', + voteSigner, + '--proposalID', + proposalID, + '--value', + 'Yes', + ]) + + // const election = await kit.contracts.getElection() + // election. + // await ElectionVote.run(['--from', voteSigner, '--value', value, '--for', ]) + }) + + test('will revoke governance votes', async () => { + await AdminRevoke.run(['--contract', contractAddress, '--yesreally']) + const isVoting = await governance.isVoting(contractAddress) + expect(isVoting).toBeFalsy() + }) + }) + }) + }) +}) From c36b24a58b2343675b8935050562bc5716f417e0 Mon Sep 17 00:00:00 2001 From: yorhodes Date: Mon, 11 Oct 2021 16:35:38 -0700 Subject: [PATCH 13/17] Always rotate vote signer if one exists --- .../src/commands/releasegold/admin-revoke.ts | 88 +++++++++---------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/packages/cli/src/commands/releasegold/admin-revoke.ts b/packages/cli/src/commands/releasegold/admin-revoke.ts index da962d865dd..fff5bcfe2be 100644 --- a/packages/cli/src/commands/releasegold/admin-revoke.ts +++ b/packages/cli/src/commands/releasegold/admin-revoke.ts @@ -49,63 +49,61 @@ export default class AdminRevoke extends ReleaseGoldBaseCommand { const accounts = await this.kit.contracts.getAccounts() const isAccount = await accounts.isAccount(this.contractAddress) if (isAccount) { + // rotate vote signers + const voteSigner = await accounts.getVoteSigner(this.contractAddress) + if (voteSigner !== this.contractAddress) { + const keys = await generateKeys(await generateMnemonic()) + const pop = await accounts.generateProofOfKeyPossessionLocally( + this.contractAddress, + keys.address, + keys.privateKey + ) + await displaySendTx( + 'accounts: rotateVoteSigner', + await this.releaseGoldWrapper.authorizeVoteSigner(keys.address, pop), + undefined, + 'VoteSignerAuthorized' + ) + } + const election = await this.kit.contracts.getElection() const electionVotes = await election.getTotalVotesByAccount(this.contractAddress) const isElectionVoting = electionVotes.isGreaterThan(0) + // handle election votes + if (isElectionVoting) { + const txos = await this.releaseGoldWrapper.revokeAllVotesForAllGroups() + for (const txo of txos) { + await displaySendTx('election: revokeVotes', txo, undefined, [ + 'ValidatorGroupPendingVoteRevoked', + 'ValidatorGroupActiveVoteRevoked', + ]) + } + } + const governance = await this.kit.contracts.getGovernance() const isGovernanceVoting = await governance.isVoting(this.contractAddress) - if (isElectionVoting || isGovernanceVoting) { - // rotate vote signers - const voteSigner = await accounts.getVoteSigner(this.contractAddress) - if (voteSigner !== this.contractAddress) { - const keys = await generateKeys(await generateMnemonic()) - const pop = await accounts.generateProofOfKeyPossessionLocally( - this.contractAddress, - keys.address, - keys.privateKey - ) + // handle governance votes + if (isGovernanceVoting) { + const isUpvoting = await governance.isUpvoting(this.contractAddress) + if (isUpvoting) { await displaySendTx( - 'accounts: rotateVoteSigner', - await this.releaseGoldWrapper.authorizeVoteSigner(keys.address, pop), + 'governance: revokeUpvote', + await governance.revokeUpvote(this.contractAddress), undefined, - 'VoteSignerAuthorized' + 'ProposalUpvoteRevoked' ) } - // handle governance votes - if (isGovernanceVoting) { - const isUpvoting = await governance.isUpvoting(this.contractAddress) - if (isUpvoting) { - await displaySendTx( - 'governance: revokeUpvote', - await governance.revokeUpvote(this.contractAddress), - undefined, - 'ProposalUpvoteRevoked' - ) - } - - const isVotingReferendum = await governance.isVotingReferendum(this.contractAddress) - if (isVotingReferendum) { - await displaySendTx( - 'governance: revokeVotes', - governance.revokeVotes(), - undefined, - 'ProposalVoteRevoked' - ) - } - } - - // handle election votes - if (isElectionVoting) { - const txos = await this.releaseGoldWrapper.revokeAllVotesForAllGroups() - for (const txo of txos) { - await displaySendTx('election: revokeVotes', txo, undefined, [ - 'ValidatorGroupPendingVoteRevoked', - 'ValidatorGroupActiveVoteRevoked', - ]) - } + const isVotingReferendum = await governance.isVotingReferendum(this.contractAddress) + if (isVotingReferendum) { + await displaySendTx( + 'governance: revokeVotes', + governance.revokeVotes(), + undefined, + 'ProposalVoteRevoked' + ) } } From 6b6769bab8b78f0dd4ab8555d3696b74e3787375 Mon Sep 17 00:00:00 2001 From: yorhodes Date: Tue, 12 Oct 2021 11:24:54 -0700 Subject: [PATCH 14/17] Fix tests --- .../protocol/scripts/truffle/releaseGoldExampleConfigs.json | 2 +- packages/sdk/utils/src/signatureUtils.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/protocol/scripts/truffle/releaseGoldExampleConfigs.json b/packages/protocol/scripts/truffle/releaseGoldExampleConfigs.json index 9abcff822be..a1ed128782c 100644 --- a/packages/protocol/scripts/truffle/releaseGoldExampleConfigs.json +++ b/packages/protocol/scripts/truffle/releaseGoldExampleConfigs.json @@ -21,7 +21,7 @@ "releaseCliffTime": 0, "numReleasePeriods": 1, "releasePeriod": 100000000, - "amountReleasedPerPeriod": 2, + "amountReleasedPerPeriod": 12, "revocable": true, "beneficiary": "0x6Ecbe1DB9EF729CBe972C83Fb886247691Fb6beb", "releaseOwner": "0x5409ED021D9299bf6814279A6A1411A7e866A631", diff --git a/packages/sdk/utils/src/signatureUtils.ts b/packages/sdk/utils/src/signatureUtils.ts index 27d5485218c..93f27050e08 100644 --- a/packages/sdk/utils/src/signatureUtils.ts +++ b/packages/sdk/utils/src/signatureUtils.ts @@ -1,6 +1,6 @@ import { NativeSigner, serializeSignature, Signer } from '@celo/base/lib/signatureUtils' import * as Web3Utils from 'web3-utils' -import { eqAddress, privateKeyToAddress } from './address' +import { ensureLeading0x, eqAddress, privateKeyToAddress } from './address' import { EIP712TypedData, generateTypedDataHash } from './sign-typed-data-utils' // Exports moved to @celo/base, forwarding them @@ -82,7 +82,7 @@ export function signMessage(message: string, privateKey: string, address: string } export function signMessageWithoutPrefix(messageHash: string, privateKey: string, address: string) { - const publicKey = ethjsutil.privateToPublic(ethjsutil.toBuffer(privateKey)) + const publicKey = ethjsutil.privateToPublic(ethjsutil.toBuffer(ensureLeading0x(privateKey))) const derivedAddress: string = ethjsutil.bufferToHex(ethjsutil.pubToAddress(publicKey)) if (derivedAddress.toLowerCase() !== address.toLowerCase()) { throw new Error('Provided private key does not match address of intended signer') From 4e77a4fb3aae745e50ee6cf4863e68a6fc9a0b6b Mon Sep 17 00:00:00 2001 From: yorhodes Date: Tue, 12 Oct 2021 12:31:30 -0700 Subject: [PATCH 15/17] Add more tests and cleanup --- packages/cli/package.json | 2 +- .../commands/releasegold/admin-revoke.test.ts | 74 +++++++++++++------ .../src/commands/releasegold/admin-revoke.ts | 21 +++--- packages/sdk/utils/src/signatureUtils.ts | 8 +- 4 files changed, 69 insertions(+), 36 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index f55720bcd91..c6d4eb1b59b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -28,7 +28,7 @@ "generate:shrinkwrap": "yarn run npm install --production && yarn run npm shrinkwrap", "check:shrinkwrap": "yarn run npm install --production && yarn run npm shrinkwrap && ./scripts/check_shrinkwrap_dirty.sh", "prepack": "yarn run build && oclif-dev manifest && oclif-dev readme && yarn run check:shrinkwrap", - "test:reset": "yarn --cwd ../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../dev-utils/src/migration-override.json --upto 25 --release_gold_contracts scripts/truffle/releaseGoldExampleConfigs.json", + "test:reset": "yarn --cwd ../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../dev-utils/src/migration-override.json --upto 26 --release_gold_contracts scripts/truffle/releaseGoldExampleConfigs.json", "test:livechain": "yarn --cwd ../protocol devchain run-tar .tmp/devchain.tar.gz", "test": "TZ=UTC jest --runInBand" }, diff --git a/packages/cli/src/commands/releasegold/admin-revoke.test.ts b/packages/cli/src/commands/releasegold/admin-revoke.test.ts index 11c3c1f4c44..67232160a36 100644 --- a/packages/cli/src/commands/releasegold/admin-revoke.test.ts +++ b/packages/cli/src/commands/releasegold/admin-revoke.test.ts @@ -11,8 +11,8 @@ import { timeTravel, } from '@celo/dev-utils/lib/ganache-test' import Web3 from 'web3' -// import ElectionVote from '../election/vote' import Approve from '../governance/approve' +import GovernanceUpvote from '../governance/upvote' import GovernanceVote from '../governance/vote' import AdminRevoke from './admin-revoke' import Authorize from './authorize' @@ -35,8 +35,6 @@ testWithGanache('releasegold:admin-revoke cmd', (web3: Web3) => { ) kit = newKitFromWeb3(web3) releaseGoldWrapper = new ReleaseGoldWrapper(kit, newReleaseGold(web3, contractAddress)) - const amt = await releaseGoldWrapper.getCurrentReleasedTotalAmount() - console.log('released', amt) accounts = await web3.eth.getAccounts() }) @@ -49,8 +47,27 @@ testWithGanache('releasegold:admin-revoke cmd', (web3: Web3) => { expect(revokedContract).toBe(contractAddress) }) + test('will rescue all cUSD balance', async () => { + const stableToken = await kit.contracts.getStableToken() + await stableToken.transfer(contractAddress, 100).send({ + from: accounts[0], + }) + await AdminRevoke.run(['--contract', contractAddress, '--yesreally']) + const balance = await stableToken.balanceOf(contractAddress) + expect(balance.isZero()).toBeTruthy() + }) + + test('will refund and finalize', async () => { + await AdminRevoke.run(['--contract', contractAddress, '--yesreally']) + const destroyedContract = await getContractFromEvent( + 'ReleaseGoldInstanceDestroyed(address,address)', + web3 + ) + expect(destroyedContract).toBe(contractAddress) + }) + describe('#when account exists with locked gold', () => { - const value = '1' + const value = '10' beforeEach(async () => { await CreateAccount.run(['--contract', contractAddress]) @@ -99,7 +116,6 @@ testWithGanache('releasegold:admin-revoke cmd', (web3: Web3) => { }) describe('#when account has voted', () => { - const proposalID = '1' let governance: GovernanceWrapper beforeEach(async () => { @@ -111,26 +127,42 @@ testWithGanache('releasegold:admin-revoke cmd', (web3: Web3) => { .propose([], 'URL') .sendAndWaitForReceipt({ from: accounts[0], value: minDeposit }) await timeTravel(expConfig.dequeueFrequency, web3) - await Approve.run(['--from', accounts[0], '--proposalID', proposalID, '--useMultiSig']) - await GovernanceVote.run([ - '--from', - voteSigner, - '--proposalID', - proposalID, - '--value', - 'Yes', - ]) - - // const election = await kit.contracts.getElection() - // election. - // await ElectionVote.run(['--from', voteSigner, '--value', value, '--for', ]) + await Approve.run(['--from', accounts[0], '--proposalID', '1', '--useMultiSig']) + await timeTravel(expConfig.approvalStageDuration, web3) + await GovernanceVote.run(['--from', voteSigner, '--proposalID', '1', '--value', 'Yes']) + await governance + .propose([], 'URL') + .sendAndWaitForReceipt({ from: accounts[0], value: minDeposit }) + await GovernanceUpvote.run(['--from', voteSigner, '--proposalID', '2']) + + // const validators = await kit.contracts.getValidators() + // const groups = await validators.getRegisteredValidatorGroupsAddresses() + // await ElectionVote.run([ + // '--from', + // voteSigner, + // '--for', + // groups[0], + // '--value', + // value + // ]) }) - test('will revoke governance votes', async () => { + test('will revoke governance votes and upvotes', async () => { + const isVotingBefore = await governance.isVoting(contractAddress) + expect(isVotingBefore).toBeTruthy() await AdminRevoke.run(['--contract', contractAddress, '--yesreally']) - const isVoting = await governance.isVoting(contractAddress) - expect(isVoting).toBeFalsy() + const isVotingAfter = await governance.isVoting(contractAddress) + expect(isVotingAfter).toBeFalsy() }) + + // test.only('will revoke election votes', async () => { + // const election = await kit.contracts.getElection() + // const votesBefore = await election.getTotalVotesByAccount(contractAddress) + // expect(votesBefore.isZero).toBeFalsy() + // await AdminRevoke.run(['--contract', contractAddress, '--yesreally']) + // const votesAfter = await election.getTotalVotesByAccount(contractAddress) + // expect(votesAfter.isZero()).toBeTruthy() + // }) }) }) }) diff --git a/packages/cli/src/commands/releasegold/admin-revoke.ts b/packages/cli/src/commands/releasegold/admin-revoke.ts index fff5bcfe2be..b2e317b8388 100644 --- a/packages/cli/src/commands/releasegold/admin-revoke.ts +++ b/packages/cli/src/commands/releasegold/admin-revoke.ts @@ -1,4 +1,3 @@ -import { generateKeys, generateMnemonic } from '@celo/utils/lib/account' import { flags } from '@oclif/command' import prompts from 'prompts' import { displaySendTx, printValueMap } from '../../utils/cli' @@ -50,17 +49,15 @@ export default class AdminRevoke extends ReleaseGoldBaseCommand { const isAccount = await accounts.isAccount(this.contractAddress) if (isAccount) { // rotate vote signers - const voteSigner = await accounts.getVoteSigner(this.contractAddress) + let voteSigner = await accounts.getVoteSigner(this.contractAddress) if (voteSigner !== this.contractAddress) { - const keys = await generateKeys(await generateMnemonic()) - const pop = await accounts.generateProofOfKeyPossessionLocally( - this.contractAddress, - keys.address, - keys.privateKey - ) + const password = 'bad_password' + voteSigner = await this.web3.eth.personal.newAccount(password) + await this.web3.eth.personal.unlockAccount(voteSigner, password, 1000) + const pop = await accounts.generateProofOfKeyPossession(this.contractAddress, voteSigner) await displaySendTx( 'accounts: rotateVoteSigner', - await this.releaseGoldWrapper.authorizeVoteSigner(keys.address, pop), + await this.releaseGoldWrapper.authorizeVoteSigner(voteSigner, pop), undefined, 'VoteSignerAuthorized' ) @@ -74,7 +71,7 @@ export default class AdminRevoke extends ReleaseGoldBaseCommand { if (isElectionVoting) { const txos = await this.releaseGoldWrapper.revokeAllVotesForAllGroups() for (const txo of txos) { - await displaySendTx('election: revokeVotes', txo, undefined, [ + await displaySendTx('election: revokeVotes', txo, { from: voteSigner }, [ 'ValidatorGroupPendingVoteRevoked', 'ValidatorGroupActiveVoteRevoked', ]) @@ -91,7 +88,7 @@ export default class AdminRevoke extends ReleaseGoldBaseCommand { await displaySendTx( 'governance: revokeUpvote', await governance.revokeUpvote(this.contractAddress), - undefined, + { from: voteSigner }, 'ProposalUpvoteRevoked' ) } @@ -101,7 +98,7 @@ export default class AdminRevoke extends ReleaseGoldBaseCommand { await displaySendTx( 'governance: revokeVotes', governance.revokeVotes(), - undefined, + { from: voteSigner }, 'ProposalVoteRevoked' ) } diff --git a/packages/sdk/utils/src/signatureUtils.ts b/packages/sdk/utils/src/signatureUtils.ts index 93f27050e08..b23e4463523 100644 --- a/packages/sdk/utils/src/signatureUtils.ts +++ b/packages/sdk/utils/src/signatureUtils.ts @@ -78,11 +78,15 @@ export function signedMessageToPublicKey(message: string, v: number, r: string, } export function signMessage(message: string, privateKey: string, address: string) { - return signMessageWithoutPrefix(hashMessageWithPrefix(message), privateKey, address) + return signMessageWithoutPrefix( + hashMessageWithPrefix(message), + ensureLeading0x(privateKey), + address + ) } export function signMessageWithoutPrefix(messageHash: string, privateKey: string, address: string) { - const publicKey = ethjsutil.privateToPublic(ethjsutil.toBuffer(ensureLeading0x(privateKey))) + const publicKey = ethjsutil.privateToPublic(ethjsutil.toBuffer(privateKey)) const derivedAddress: string = ethjsutil.bufferToHex(ethjsutil.pubToAddress(publicKey)) if (derivedAddress.toLowerCase() !== address.toLowerCase()) { throw new Error('Provided private key does not match address of intended signer') From 5b6ad091820ce81bab3a7315c0d17ecdb5a30387 Mon Sep 17 00:00:00 2001 From: yorhodes Date: Tue, 12 Oct 2021 12:33:27 -0700 Subject: [PATCH 16/17] Revert elect validators migration change --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index c6d4eb1b59b..f55720bcd91 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -28,7 +28,7 @@ "generate:shrinkwrap": "yarn run npm install --production && yarn run npm shrinkwrap", "check:shrinkwrap": "yarn run npm install --production && yarn run npm shrinkwrap && ./scripts/check_shrinkwrap_dirty.sh", "prepack": "yarn run build && oclif-dev manifest && oclif-dev readme && yarn run check:shrinkwrap", - "test:reset": "yarn --cwd ../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../dev-utils/src/migration-override.json --upto 26 --release_gold_contracts scripts/truffle/releaseGoldExampleConfigs.json", + "test:reset": "yarn --cwd ../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../dev-utils/src/migration-override.json --upto 25 --release_gold_contracts scripts/truffle/releaseGoldExampleConfigs.json", "test:livechain": "yarn --cwd ../protocol devchain run-tar .tmp/devchain.tar.gz", "test": "TZ=UTC jest --runInBand" }, From 4603ab7aa8d92cc7c069f219edf3b58443474a7c Mon Sep 17 00:00:00 2001 From: yorhodes Date: Tue, 12 Oct 2021 12:38:12 -0700 Subject: [PATCH 17/17] Fix getContractFromEvent usage --- .../cli/src/commands/releasegold/refund-and-finalize.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/releasegold/refund-and-finalize.test.ts b/packages/cli/src/commands/releasegold/refund-and-finalize.test.ts index 1d3ff0b54d2..8ea732859a0 100644 --- a/packages/cli/src/commands/releasegold/refund-and-finalize.test.ts +++ b/packages/cli/src/commands/releasegold/refund-and-finalize.test.ts @@ -16,7 +16,8 @@ testWithGanache('releasegold:refund-and-finalize cmd', (web3: Web3) => { beforeEach(async () => { contractAddress = await getContractFromEvent( 'ReleaseGoldInstanceCreated(address,address)', - web3 + web3, + { index: 1 } // revocable = true ) kit = newKitFromWeb3(web3) })