Skip to content

Commit

Permalink
Allow a specified address to disable/enable rewards distribution (#1828)
Browse files Browse the repository at this point in the history
  • Loading branch information
m-chrzan authored and celo-ci-bot-user committed Dec 5, 2019
1 parent a330768 commit 81d644c
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 70 deletions.
171 changes: 104 additions & 67 deletions packages/celotool/src/e2e-tests/governance_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,74 @@ describe('governance tests', () => {
assertAlmostEqual(currentBalance.minus(previousBalance), expected)
}

const waitForBlock = async (blockNumber: number) => {
// const epoch = new BigNumber(await validators.methods.getEpochSize().call()).toNumber()
let currentBlock: number
do {
currentBlock = await web3.eth.getBlockNumber()
await sleep(0.1)
} while (currentBlock < blockNumber)
}

const waitForEpochTransition = async (epoch: number) => {
// const epoch = new BigNumber(await validators.methods.getEpochSize().call()).toNumber()
let blockNumber: number
do {
blockNumber = await web3.eth.getBlockNumber()
await sleep(0.1)
} while (blockNumber % epoch !== 1)
}

const assertTargetVotingYieldChanged = async (blockNumber: number, expected: BigNumber) => {
const currentTarget = new BigNumber(
(await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber))[0]
)
const previousTarget = new BigNumber(
(await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber - 1))[0]
)
const max = new BigNumber(
(await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber))[1]
)
const expectedTarget = previousTarget.plus(expected)
if (expectedTarget.isGreaterThanOrEqualTo(max)) {
assert.equal(currentTarget.toFixed(), max.toFixed())
} else if (expectedTarget.isLessThanOrEqualTo(0)) {
assert.isTrue(currentTarget.isZero())
} else {
const difference = currentTarget.minus(previousTarget)
// Assert equal to 9 decimal places due to rounding errors.
assert.equal(
fromFixed(difference)
.dp(9)
.toFixed(),
fromFixed(expected)
.dp(9)
.toFixed()
)
}
}

const assertTargetVotingYieldUnchanged = async (blockNumber: number) => {
await assertTargetVotingYieldChanged(blockNumber, new BigNumber(0))
}

const getLastEpochBlock = (blockNumber: number, epoch: number) => {
const epochNumber = Math.floor((blockNumber - 1) / epoch)
return epochNumber * epoch
}

const assertGoldTokenTotalSupplyUnchanged = async (blockNumber: number) => {
await assertGoldTokenTotalSupplyChanged(blockNumber, new BigNumber(0))
}

const assertGoldTokenTotalSupplyChanged = async (blockNumber: number, expected: BigNumber) => {
const currentSupply = new BigNumber(await goldToken.methods.totalSupply().call({}, blockNumber))
const previousSupply = new BigNumber(
await goldToken.methods.totalSupply().call({}, blockNumber - 1)
)
assertAlmostEqual(currentSupply.minus(previousSupply), expected)
}

describe('when the validator set is changing', () => {
let epoch: number
const blockNumbers: number[] = []
Expand Down Expand Up @@ -281,16 +349,9 @@ describe('governance tests', () => {
assert.equal(epoch, 10)

// Wait for an epoch transition so we can activate our vote.
let blockNumber: number
do {
blockNumber = await web3.eth.getBlockNumber()
await sleep(0.1)
} while (blockNumber % epoch !== 1)
await waitForEpochTransition(epoch)
// Wait for an extra epoch transition to ensure everyone is connected to one another.
do {
blockNumber = await web3.eth.getBlockNumber()
await sleep(0.1)
} while (blockNumber % epoch !== 1)
await waitForEpochTransition(epoch)

// Prepare for member swapping.
const groupWeb3 = new Web3('ws://localhost:8555')
Expand Down Expand Up @@ -357,14 +418,9 @@ describe('governance tests', () => {
)
}

const getLastEpochBlock = (blockNumber: number) => {
const epochNumber = Math.floor((blockNumber - 1) / epoch)
return epochNumber * epoch
}

it('should always return a validator set size equal to the number of group members at the end of the last epoch', async () => {
for (const blockNumber of blockNumbers) {
const lastEpochBlock = getLastEpochBlock(blockNumber)
const lastEpochBlock = getLastEpochBlock(blockNumber, epoch)
const validatorSetSize = await election.methods
.numberValidatorsInCurrentSet()
.call({}, blockNumber)
Expand All @@ -376,7 +432,7 @@ describe('governance tests', () => {
it('should always return a validator set equal to the signing keys of the group members at the end of the last epoch', async function(this: any) {
this.timeout(0)
for (const blockNumber of blockNumbers) {
const lastEpochBlock = getLastEpochBlock(blockNumber)
const lastEpochBlock = getLastEpochBlock(blockNumber, epoch)
const memberAccounts = await getValidatorGroupMembers(lastEpochBlock)
const memberSigners = await Promise.all(
memberAccounts.map((v: string) => getValidatorSigner(v, lastEpochBlock))
Expand All @@ -391,7 +447,7 @@ describe('governance tests', () => {
it('should block propose in a round robin fashion', async () => {
let roundRobinOrder: string[] = []
for (const blockNumber of blockNumbers) {
const lastEpochBlock = getLastEpochBlock(blockNumber)
const lastEpochBlock = getLastEpochBlock(blockNumber, epoch)
// Fetch the round robin order if it hasn't already been set for this epoch.
if (roundRobinOrder.length === 0 || blockNumber === lastEpochBlock + 1) {
const validatorSet = await getValidatorSetSignersAtBlock(blockNumber)
Expand Down Expand Up @@ -548,19 +604,6 @@ describe('governance tests', () => {
return new BigNumber(gpm).times(new BigNumber(gas))
}

const assertGoldTokenTotalSupplyChanged = async (
blockNumber: number,
expected: BigNumber
) => {
const currentSupply = new BigNumber(
await goldToken.methods.totalSupply().call({}, blockNumber)
)
const previousSupply = new BigNumber(
await goldToken.methods.totalSupply().call({}, blockNumber - 1)
)
assertAlmostEqual(currentSupply.minus(previousSupply), expected)
}

const assertLockedGoldBalanceChanged = async (blockNumber: number, expected: BigNumber) => {
await assertBalanceChanged(lockedGold.options.address, blockNumber, expected, goldToken)
}
Expand All @@ -573,10 +616,6 @@ describe('governance tests', () => {
await assertVotesChanged(blockNumber, new BigNumber(0))
}

const assertGoldTokenTotalSupplyUnchanged = async (blockNumber: number) => {
await assertGoldTokenTotalSupplyChanged(blockNumber, new BigNumber(0))
}

const assertLockedGoldBalanceUnchanged = async (blockNumber: number) => {
await assertLockedGoldBalanceChanged(blockNumber, new BigNumber(0))
}
Expand Down Expand Up @@ -641,39 +680,6 @@ describe('governance tests', () => {
})

it('should update the target voting yield', async () => {
const assertTargetVotingYieldChanged = async (blockNumber: number, expected: BigNumber) => {
const currentTarget = new BigNumber(
(await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber))[0]
)
const previousTarget = new BigNumber(
(await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber - 1))[0]
)
const max = new BigNumber(
(await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber))[1]
)
const expectedTarget = previousTarget.plus(expected)
if (expectedTarget.isGreaterThanOrEqualTo(max)) {
assert.equal(currentTarget.toFixed(), max.toFixed())
} else if (expectedTarget.isLessThanOrEqualTo(0)) {
assert.isTrue(currentTarget.isZero())
} else {
const difference = currentTarget.minus(previousTarget)
// Assert equal to 9 decimal places due to rounding errors.
assert.equal(
fromFixed(difference)
.dp(9)
.toFixed(),
fromFixed(expected)
.dp(9)
.toFixed()
)
}
}

const assertTargetVotingYieldUnchanged = async (blockNumber: number) => {
await assertTargetVotingYieldChanged(blockNumber, new BigNumber(0))
}

for (const blockNumber of blockNumbers) {
if (isLastBlockOfEpoch(blockNumber, epoch)) {
// We use the voting gold fraction from before the rewards are granted.
Expand All @@ -698,6 +704,37 @@ describe('governance tests', () => {
})
})

describe('when rewards distribution is frozen', () => {
before(restart)

let epoch: number
let blockFrozen: number
let latestBlock: number

before(async function(this: any) {
this.timeout(0)
const validator = (await kit.web3.eth.getAccounts())[0]
await kit.web3.eth.personal.unlockAccount(validator, '', 1000000)
await epochRewards.methods.freeze().send({ from: validator })
blockFrozen = await web3.eth.getBlockNumber()
epoch = new BigNumber(await validators.methods.getEpochSize().call()).toNumber()
await waitForBlock(blockFrozen + epoch * 2)
latestBlock = await web3.eth.getBlockNumber()
})

it('should not update the target voing yield', async () => {
for (let blockNumber = blockFrozen; blockNumber < latestBlock; blockNumber++) {
await assertTargetVotingYieldUnchanged(blockNumber)
}
})

it('should not mint new Celo Gold', async () => {
for (let blockNumber = blockFrozen; blockNumber < latestBlock; blockNumber++) {
await assertGoldTokenTotalSupplyUnchanged(blockNumber)
}
})
})

describe('after the gold token smart contract is registered', () => {
let goldGenesisSupply = new BigNumber(0)
beforeEach(async function(this: any) {
Expand Down
15 changes: 13 additions & 2 deletions packages/protocol/contracts/governance/EpochRewards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.5.3;
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";

import "../baklava/Freezable.sol";
import "../common/FixidityLib.sol";
import "../common/Initializable.sol";
import "../common/UsingRegistry.sol";
Expand All @@ -11,7 +12,7 @@ import "../common/UsingPrecompiles.sol";
/**
* @title Contract for calculating epoch rewards.
*/
contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry {
contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry, Freezable {
using FixidityLib for FixidityLib.Fraction;
using SafeMath for uint256;

Expand Down Expand Up @@ -82,6 +83,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
*/
function initialize(
address registryAddress,
address _freezer,
uint256 targetVotingYieldInitial,
uint256 targetVotingYieldMax,
uint256 targetVotingYieldAdjustmentFactor,
Expand All @@ -92,6 +94,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
uint256 _targetValidatorEpochPayment
) external initializer {
_transferOwnership(msg.sender);
setFreezer(_freezer);
setRegistry(registryAddress);
setTargetVotingYieldParameters(targetVotingYieldMax, targetVotingYieldAdjustmentFactor);
setRewardsMultiplierParameters(
Expand Down Expand Up @@ -127,6 +130,10 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
);
}

function setFreezer(address freezer) public onlyOwner {
_setFreezer(freezer);
}

/**
* @notice Sets the target voting Gold fraction.
* @param value The percentage of floating Gold voting to target.
Expand Down Expand Up @@ -362,7 +369,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
* voting Gold fraction.
* @dev Only called directly by the protocol.
*/
function updateTargetVotingYield() external {
function updateTargetVotingYield() external onlyWhenNotFrozen {
require(msg.sender == address(0));
_updateTargetVotingYield();
}
Expand All @@ -372,6 +379,10 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
* @return The per validator epoch payment and the total rewards to voters.
*/
function calculateTargetEpochPaymentAndRewards() external view returns (uint256, uint256) {
if (frozen) {
return (0, 0);
}

uint256 targetEpochRewards = getTargetEpochRewards();
uint256 targetTotalEpochPaymentsInGold = getTargetTotalEpochPaymentsInGold();
uint256 targetGoldSupplyIncrease = targetEpochRewards.add(targetTotalEpochPaymentsInGold);
Expand Down
5 changes: 4 additions & 1 deletion packages/protocol/migrations/14_epoch_rewards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils'
import { config } from '@celo/protocol/migrationsConfig'
import { toFixed } from '@celo/utils/lib/fixidity'
import { EpochRewardsInstance } from 'types'
const truffle = require('@celo/protocol/truffle-config.js')

const initializeArgs = async (): Promise<any[]> => {
const initializeArgs = async (networkName: string): Promise<any[]> => {
const network: any = truffle.networks[networkName]
return [
config.registry.predeployedProxyAddress,
network.from,
toFixed(config.epochRewards.targetVotingYieldParameters.initial).toFixed(),
toFixed(config.epochRewards.targetVotingYieldParameters.max).toFixed(),
toFixed(config.epochRewards.targetVotingYieldParameters.adjustmentFactor).toFixed(),
Expand Down
17 changes: 17 additions & 0 deletions packages/protocol/test/governance/epochrewards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ contract('EpochRewards', (accounts: string[]) => {

await epochRewards.initialize(
registry.address,
accounts[0],
targetVotingYieldParams.initial,
targetVotingYieldParams.max,
targetVotingYieldParams.adjustmentFactor,
Expand Down Expand Up @@ -130,6 +131,7 @@ contract('EpochRewards', (accounts: string[]) => {
await assertRevert(
epochRewards.initialize(
registry.address,
accounts[0],
targetVotingYieldParams.initial,
targetVotingYieldParams.max,
targetVotingYieldParams.adjustmentFactor,
Expand Down Expand Up @@ -578,4 +580,19 @@ contract('EpochRewards', (accounts: string[]) => {
})
})
})

describe('when the contract is frozen', () => {
beforeEach(async () => {
await epochRewards.freeze()
})

it('should make calculateTargetEpochPaymentAndRewards return zeroes', async () => {
const [
validatorPayment,
voterRewards,
] = await epochRewards.calculateTargetEpochPaymentAndRewards()
assertEqualBN(validatorPayment, 0)
assertEqualBN(voterRewards, 0)
})
})
})

0 comments on commit 81d644c

Please sign in to comment.