Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add celocli releasegold:admin-revoke command #8749

Merged
merged 18 commits into from
Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions packages/cli/src/commands/governance/vote.test.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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]])
Expand Down
169 changes: 169 additions & 0 deletions packages/cli/src/commands/releasegold/admin-revoke.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
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 Approve from '../governance/approve'
import GovernanceUpvote from '../governance/upvote'
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))
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)
})

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 = '10'

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', () => {
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', '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 and upvotes', async () => {
const isVotingBefore = await governance.isVoting(contractAddress)
expect(isVotingBefore).toBeTruthy()
await AdminRevoke.run(['--contract', contractAddress, '--yesreally'])
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()
// })
})
})
})
})
143 changes: 143 additions & 0 deletions packages/cli/src/commands/releasegold/admin-revoke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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() {
const { flags: _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) {
// rotate vote signers
let voteSigner = await accounts.getVoteSigner(this.contractAddress)
if (voteSigner !== this.contractAddress) {
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(voteSigner, 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, { from: voteSigner }, [
'ValidatorGroupPendingVoteRevoked',
'ValidatorGroupActiveVoteRevoked',
])
}
}

const governance = await this.kit.contracts.getGovernance()
const isGovernanceVoting = await governance.isVoting(this.contractAddress)

// handle governance votes
if (isGovernanceVoting) {
const isUpvoting = await governance.isUpvoting(this.contractAddress)
if (isUpvoting) {
await displaySendTx(
'governance: revokeUpvote',
await governance.revokeUpvote(this.contractAddress),
{ from: voteSigner },
'ProposalUpvoteRevoked'
)
}

const isVotingReferendum = await governance.isVotingReferendum(this.contractAddress)
if (isVotingReferendum) {
await displaySendTx(
'governance: revokeVotes',
governance.revokeVotes(),
{ from: voteSigner },
'ProposalVoteRevoked'
)
}
}

await displaySendTx(
'releasegold: unlockAllGold',
await this.releaseGoldWrapper.unlockAllGold(),
undefined,
'GoldUnlocked'
)
}

// rescue any cUSD balance
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'
)
}

// attempt to refund and finalize, surface pending withdrawals
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))
}
}
}
4 changes: 1 addition & 3 deletions packages/cli/src/commands/releasegold/authorize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
4 changes: 1 addition & 3 deletions packages/cli/src/commands/releasegold/locked-gold.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ testWithGanache('releasegold:refund-and-finalize cmd', (web3: Web3) => {
let kit: ContractKit

beforeEach(async () => {
const contractCanValidate = false
contractAddress = await getContractFromEvent(
'ReleaseGoldInstanceCreated(address,address)',
web3,
contractCanValidate
{ index: 1 } // revocable = true
)
kit = newKitFromWeb3(web3)
})
Expand Down
Loading