Skip to content
91 changes: 51 additions & 40 deletions packages/celotool/src/e2e-tests/governance_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
import { bitIsSet, parseBlockExtraData } from '@celo/utils/lib/istanbul'
import BigNumber from 'bignumber.js'
import { assert } from 'chai'
import path from 'path'
import Web3 from 'web3'
import { connectBipartiteClique, connectPeers, importGenesis, initAndStartGeth } from '../lib/geth'
import { AccountType, generateAddress, generatePrivateKey } from '../lib/generate_utils'
import { connectBipartiteClique, connectPeers, initAndStartGeth } from '../lib/geth'
import { GethInstanceConfig } from '../lib/interfaces/geth-instance-config'
import { GethRunConfig } from '../lib/interfaces/geth-run-config'
import {
assertAlmostEqual,
getHooks,
mnemonic,
sleep,
waitForAnnounceToStabilize,
waitForBlock,
waitForEpochTransition,
waitToFinishInstanceSyncing,
Expand All @@ -28,6 +30,14 @@ interface MemberSwapper {
const TMP_PATH = '/tmp/e2e'
const verbose = false
const carbonOffsettingPartnerAddress = '0x1234567812345678123456781234567812345678'
const validatorAddress = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95'
// The tests calculate some expected values based on the rewards multiplier from the block
// before an epoch block. However, the actual rewards multiplier used for epoch rewards is
// calculated inside the epoch block. Since the multiplier depends on the timestamp, this means
// the expected values will never exactly match the actual values, so we need some tolerance.
// This constant defines the tolerance as a fraction of the expected value.
// values. We use 10^-6, so they have to be match to (nearly) 6 significant figures
const tolerance = new BigNumber(10).pow(new BigNumber(-6))

async function newMemberSwapper(kit: ContractKit, members: string[]): Promise<MemberSwapper> {
let index = 0
Expand Down Expand Up @@ -165,15 +175,15 @@ async function calculateUptime(
// TODO(asa): Test independent rotation of ecdsa, bls keys.
describe('governance tests', () => {
const gethConfig: GethRunConfig = {
migrate: true,
runPath: TMP_PATH,
verbosity: 4,
migrateTo: 25,
verbosity: 3,
useMycelo: true,
networkId: 1101,
network: 'local',
genesisConfig: {
churritoBlock: 0,
donutBlock: 0,
epoch: 10,
},
instances: [
// Validators 0 and 1 are swapped in and out of the group.
Expand Down Expand Up @@ -214,11 +224,6 @@ describe('governance tests', () => {
rpcport: 8553,
},
],
migrationOverrides: {
epochRewards: {
carbonOffsettingPartner: carbonOffsettingPartnerAddress,
},
},
}

const hooks: any = getHooks(gethConfig)
Expand All @@ -229,7 +234,6 @@ describe('governance tests', () => {
let sortedOracles: any
let epochRewards: any
let goldToken: any
let registry: any
let reserve: any
let validators: any
let accounts: any
Expand All @@ -250,16 +254,28 @@ describe('governance tests', () => {
await hooks.restart()
web3 = new Web3('http://localhost:8545')
kit = newKitFromWeb3(web3)
// TODO(mcortesi): magic sleep. without it unlockAccount sometimes fails
await sleep(2)
// Assuming empty password
await kit.connection.web3.eth.personal.unlockAccount(validatorAddress, '', 1000000)

goldToken = await kit._web3Contracts.getGoldToken()
stableToken = await kit._web3Contracts.getStableToken()
sortedOracles = await kit._web3Contracts.getSortedOracles()
validators = await kit._web3Contracts.getValidators()
registry = await kit._web3Contracts.getRegistry()
reserve = await kit._web3Contracts.getReserve()
election = await kit._web3Contracts.getElection()
epochRewards = await kit._web3Contracts.getEpochRewards()
accounts = await kit._web3Contracts.getAccounts()

await waitForBlock(web3, 1)
await waitForAnnounceToStabilize(web3)

const er = await kit._web3Contracts.getEpochRewards()
const fraction = await er.methods.getCarbonOffsettingFraction().call()
await er.methods
.setCarbonOffsettingFund(carbonOffsettingPartnerAddress, fraction)
.send({ from: validatorAddress })
}

const getValidatorGroupMembers = async (blockNumber?: number) => {
Expand Down Expand Up @@ -288,6 +304,12 @@ describe('governance tests', () => {

const getValidatorGroupPrivateKey = async () => {
const [groupAddress] = await validators.methods.getRegisteredValidatorGroups().call()
// If we're using mycelo, we can just generate the validator group key directly
const myceloAddress = generateAddress(mnemonic, AccountType.VALIDATOR_GROUP, 0)
if (myceloAddress === groupAddress) {
return '0x' + generatePrivateKey(mnemonic, AccountType.VALIDATOR_GROUP, 0)
}
// Otherwise, the validator group key is encoded in its name (see 25_elect_validators.ts)
const name = await accounts.methods.getName(groupAddress).call()
const encryptedKeystore64 = name.split(' ')[1]
const encryptedKeystore = JSON.parse(Buffer.from(encryptedKeystore64, 'base64').toString())
Expand Down Expand Up @@ -315,7 +337,8 @@ describe('governance tests', () => {
)
assert.isFalse(currentBalance.isNaN())
assert.isFalse(previousBalance.isNaN())
assertAlmostEqual(currentBalance.minus(previousBalance), expected)
const margin = expected.times(tolerance)
assertAlmostEqual(currentBalance.minus(previousBalance), expected, margin)
}

const assertTargetVotingYieldChanged = async (blockNumber: number, expected: BigNumber) => {
Expand Down Expand Up @@ -407,7 +430,18 @@ describe('governance tests', () => {
const groupKit = newKitFromWeb3(groupWeb3)

const group: string = (await groupWeb3.eth.getAccounts())[0]
// Send some funds to the group, so it can afford fees
await (
await kit.sendTransaction({
from: validatorAddress,
to: group,
value: Web3.utils.toWei('1', 'ether'),
})
).waitReceipt()

// groupKit uses a different node than kit does, so wait a second in case kit's node
// got the new block before groupKit's node did.
await sleep(1)
const txos = await (await groupKit.contracts.getElection()).activate(group)
for (const txo of txos) {
await txo.sendAndWaitForReceipt({ from: group })
Expand All @@ -416,6 +450,8 @@ describe('governance tests', () => {
validators = await groupKit._web3Contracts.getValidators()
const membersToSwap = [validatorAccounts[0], validatorAccounts[1]]
const memberSwapper = await newMemberSwapper(groupKit, membersToSwap)
// The memberSwapper makes a change when it's created, so we wait for epoch change so it takes effect
await waitForEpochTransition(web3, epoch)

const handled: any = {}

Expand Down Expand Up @@ -659,7 +695,8 @@ describe('governance tests', () => {
const previousVotes = new BigNumber(
await election.methods.getTotalVotesForGroup(group).call({}, blockNumber - 1)
)
assertAlmostEqual(currentVotes.minus(previousVotes), expected)
const margin = expected.times(tolerance)
assertAlmostEqual(currentVotes.minus(previousVotes), expected, margin)
}

// Returns the gas fee base for a given block, which is distributed to the governance contract.
Expand Down Expand Up @@ -1073,30 +1110,4 @@ describe('governance tests', () => {
}
})
})

describe('after the gold token smart contract is registered', () => {
let goldGenesisSupply = new BigNumber(0)
beforeEach(async function (this: any) {
this.timeout(0) // Disable test timeout
await restart()
const genesis = await importGenesis(path.join(gethConfig.runPath, 'genesis.json'))
Object.keys(genesis.alloc).forEach((address) => {
goldGenesisSupply = goldGenesisSupply.plus(genesis.alloc[address].balance)
})
})

it('should initialize the Celo Gold total supply correctly', async function (this: any) {
const events = await registry.getPastEvents('RegistryUpdated', { fromBlock: 0 })
let blockNumber = 0
for (const e of events) {
if (e.returnValues.identifier === 'GoldToken') {
blockNumber = e.blockNumber
break
}
}
assert.isAtLeast(blockNumber, 1)
const goldTotalSupply = await goldToken.methods.totalSupply().call({}, blockNumber)
assert.equal(goldTotalSupply, goldGenesisSupply.toFixed())
})
})
})
2 changes: 1 addition & 1 deletion packages/celotool/src/e2e-tests/replica_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const verbose = false

describe('replica swap tests', () => {
const gethConfig: GethRunConfig = {
migrate: false,
useMycelo: true,
runPath: TMP_PATH,
verbosity: 4,
networkId: 1101,
Expand Down
25 changes: 10 additions & 15 deletions packages/celotool/src/e2e-tests/slashing_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ContractKit, newKitFromWeb3 } from '@celo/contractkit'
import { ensureLeading0x } from '@celo/utils/lib/address'
import BigNumber from 'bignumber.js'
import { assert } from 'chai'
import * as _ from 'lodash'
import * as rlp from 'rlp'
import Web3 from 'web3'
import { GethRunConfig } from '../lib/interfaces/geth-run-config'
Expand Down Expand Up @@ -92,11 +93,11 @@ async function generateValidIntervalArrays(
}

describe('slashing tests', function (this: any) {
const gethConfigDown: GethRunConfig = {
const gethConfig: GethRunConfig = {
network: 'local',
networkId: 1101,
runPath: TMP_PATH,
migrate: true,
useMycelo: true,
genesisConfig: {
churritoBlock: 0,
donutBlock: 0,
Expand Down Expand Up @@ -130,34 +131,28 @@ describe('slashing tests', function (this: any) {
port: 30309,
rpcport: 8551,
},
],
}

const gethConfig: GethRunConfig = {
network: 'local',
networkId: 1101,
runPath: TMP_PATH,
migrate: true,
instances: gethConfigDown.instances.concat([
// Validator 4 will be down in the downtime test
{
name: 'validator4',
validating: true,
syncmode: 'full',
port: 30311,
rpcport: 8553,
},
]),
],
}

// Do a shallow copy so that the instance objects are the the same (even after the init step fills private keys, etc.)
const gethConfigDown = _.clone(gethConfig)
// Exclude the last validator to simulate it being down
gethConfigDown.instances = gethConfig.instances.slice(0, gethConfig.instances.length - 1)

const hooks: any = getHooks(gethConfig)
const hooksDown: any = getHooks(gethConfigDown)
let web3: Web3
let kit: ContractKit

before(async function (this: any) {
this.timeout(0)
// Comment out the following line after a test run for a quick rerun.
await hooks.before()
})

Expand Down Expand Up @@ -230,14 +225,14 @@ describe('slashing tests', function (this: any) {
this.timeout(0) // Disable test timeout
const slasher = await kit._web3Contracts.getDowntimeSlasher()
const slashableDowntime = new BigNumber(await slasher.methods.slashableDowntime().call())
await waitForBlock(web3, 1)
const blockNumber = await kit.connection.getBlockNumber()
await waitForBlock(web3, blockNumber + slashableDowntime.toNumber() + 2 * safeMarginBlocks)

// Store this block for testing double signing
doubleSigningBlock = await kit.connection.getBlock(blockNumber + 2 * safeMarginBlocks)

const signer = await slasher.methods.validatorSignerAddressFromSet(4, blockNumber).call()

const validator = (await kit.connection.getAccounts())[0]
await kit.connection.web3.eth.personal.unlockAccount(validator, '', 1000000)
const lockedGold = await kit.contracts.getLockedGold()
Expand Down
Loading