Skip to content

Commit

Permalink
Expose and test validator set precompiles (#874)
Browse files Browse the repository at this point in the history
  • Loading branch information
m-chrzan authored and ashishb committed Sep 16, 2019
1 parent 627872e commit ec7fbe6
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 9 deletions.
189 changes: 182 additions & 7 deletions packages/celotool/src/e2e-tests/governance_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { assert } from 'chai'
import Web3 from 'web3'
import { strip0x } from '../lib/utils'
import {
assertRevert,
erc20Abi,
getContext,
getContractAddress,
getEnode,
getHooks,
importGenesis,
initAndStartGeth,
sleep,
Expand Down Expand Up @@ -167,6 +168,39 @@ const validatorsAbi = [
stateMutability: 'nonpayable',
type: 'function',
},
{
constant: true,
inputs: [
{
name: 'index',
type: 'uint256',
},
],
name: 'validatorAddressFromCurrentSet',
outputs: [
{
name: '',
type: 'address',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'numberValidatorsInCurrentSet',
outputs: [
{
name: '',
type: 'uint256',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
]

describe('governance tests', () => {
Expand All @@ -181,21 +215,21 @@ describe('governance tests', () => {
],
}

const hooks: any = getHooks(gethConfig)
const context: any = getContext(gethConfig)
let web3: any
let lockedGold: any
let validators: any
let goldToken: any

before(async function(this: any) {
this.timeout(0)
await hooks.before()
await context.hooks.before()
})

after(hooks.after)
after(context.hooks.after)

const restart = async () => {
await hooks.restart()
await context.hooks.restart()
web3 = new Web3('http://localhost:8545')
lockedGold = new web3.eth.Contract(lockedGoldAbi, await getContractAddress('LockedGoldProxy'))
goldToken = new web3.eth.Contract(erc20Abi, await getContractAddress('GoldTokenProxy'))
Expand Down Expand Up @@ -289,6 +323,147 @@ describe('governance tests', () => {
return tx.send({ from: account, ...txOptions, gas })
}

describe('Validators.numberValidatorsInCurrentSet()', () => {
before(async function() {
this.timeout(0)
await restart()
validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
})

it('should return the validator set size', async () => {
const numberValidators = await validators.methods.numberValidatorsInCurrentSet().call()

assert.equal(numberValidators, 5)
})

describe('after the validator set changes', () => {
before(async function() {
this.timeout(0)
await restart()
const [groupAddress, groupPrivateKey] = await getValidatorGroupKeys()
const epoch = 10

const groupInstance = {
name: 'validatorGroup',
validating: false,
syncmode: 'full',
port: 30325,
wsport: 8567,
privateKey: groupPrivateKey.slice(2),
peers: [await getEnode(8545)],
}
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
const groupWeb3 = new Web3('ws://localhost:8567')
validators = new groupWeb3.eth.Contract(
validatorsAbi,
await getContractAddress('ValidatorsProxy')
)
// Give the node time to sync.
await sleep(15)
const members = await getValidatorGroupMembers()
await removeMember(groupWeb3, groupAddress, members[0])
await sleep(epoch * 2)
})

it('should return the reduced validator set size', async () => {
const numberValidators = await validators.methods.numberValidatorsInCurrentSet().call()

assert.equal(numberValidators, 4)
})
})
})

describe('Validators.validatorAddressFromCurrentSet()', () => {
before(async function() {
this.timeout(0)
await restart()
validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
})

it('should return the first validator', async () => {
const resultAddress = await validators.methods.validatorAddressFromCurrentSet(0).call()

assert.equal(strip0x(resultAddress), context.validators[0].address)
})

it('should return the third validator', async () => {
const resultAddress = await validators.methods.validatorAddressFromCurrentSet(2).call()

assert.equal(strip0x(resultAddress), context.validators[2].address)
})

it('should return the fifth validator', async () => {
const resultAddress = await validators.methods.validatorAddressFromCurrentSet(4).call()

assert.equal(strip0x(resultAddress), context.validators[4].address)
})

it('should revert when asked for an out of bounds validator', async function(this: any) {
this.timeout(0) // Disable test timeout
await assertRevert(
validators.methods.validatorAddressFromCurrentSet(5).send({
from: `0x${context.validators[0].address}`,
})
)
})

describe('after the validator set changes', () => {
before(async function() {
this.timeout(0)
await restart()
const [groupAddress, groupPrivateKey] = await getValidatorGroupKeys()
const epoch = 10

const groupInstance = {
name: 'validatorGroup',
validating: false,
syncmode: 'full',
port: 30325,
wsport: 8567,
privateKey: groupPrivateKey.slice(2),
peers: [await getEnode(8545)],
}
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
const groupWeb3 = new Web3('ws://localhost:8567')
validators = new groupWeb3.eth.Contract(
validatorsAbi,
await getContractAddress('ValidatorsProxy')
)
// Give the node time to sync.
await sleep(15)
const members = await getValidatorGroupMembers()
await removeMember(groupWeb3, groupAddress, members[0])
await sleep(epoch * 2)

validators = new web3.eth.Contract(
validatorsAbi,
await getContractAddress('ValidatorsProxy')
)
})

it('should return the second validator in the first place', async () => {
const resultAddress = await validators.methods.validatorAddressFromCurrentSet(0).call()

assert.equal(strip0x(resultAddress), context.validators[1].address)
})

it('should return the last validator in the fourth place', async () => {
const resultAddress = await validators.methods.validatorAddressFromCurrentSet(3).call()

assert.equal(strip0x(resultAddress), context.validators[4].address)
})

it('should revert when asked for an out of bounds validator', async function(this: any) {
this.timeout(0)
await assertRevert(
validators.methods.validatorAddressFromCurrentSet(4).send({
from: `0x${context.validators[0].address}`,
})
)
})
})
})

describe('when the validator set is changing', () => {
const epoch = 10
const expectedEpochMembership = new Map()
Expand All @@ -306,7 +481,7 @@ describe('governance tests', () => {
privateKey: groupPrivateKey.slice(2),
peers: [await getEnode(8545)],
}
await initAndStartGeth(hooks.gethBinaryPath, groupInstance)
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
const groupWeb3 = new Web3('ws://localhost:8567')
validators = new groupWeb3.eth.Contract(
validatorsAbi,
Expand Down Expand Up @@ -377,7 +552,7 @@ describe('governance tests', () => {
rpcport: 8567,
privateKey: 'f2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d',
}
await initAndStartGeth(hooks.gethBinaryPath, delegateInstance)
await initAndStartGeth(context.hooks.gethBinaryPath, delegateInstance)
// Note that we don't need to create an account or make a commitment as this has already been
// done in the migration.
await delegateRewards(account, delegate)
Expand Down
11 changes: 9 additions & 2 deletions packages/celotool/src/e2e-tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ export async function initAndStartGeth(gethBinaryPath: string, instance: GethIns
return startGeth(gethBinaryPath, instance)
}

export function getHooks(gethConfig: GethTestConfig) {
export function getContext(gethConfig: GethTestConfig) {
const mnemonic =
'jazz ripple brown cloth door bridge pen danger deer thumb cable prepare negative library vast'
const validatorInstances = gethConfig.instances.filter((x: any) => x.validating)
Expand Down Expand Up @@ -449,7 +449,14 @@ export function getHooks(gethConfig: GethTestConfig) {

const after = () => killGeth()

return { before, after, restart, gethBinaryPath }
return {
validators,
hooks: { before, after, restart, gethBinaryPath },
}
}

export function getHooks(gethConfig: GethTestConfig) {
return getContext(gethConfig).hooks
}

export async function assertRevert(promise: any, errorMessage: string = '') {
Expand Down
42 changes: 42 additions & 0 deletions packages/protocol/contracts/governance/Validators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
using SafeMath for uint256;
using BytesLib for bytes;

// Address of the getValidator precompiled contract
address constant public GET_VALIDATOR_ADDRESS = address(0xfa);

// TODO(asa): These strings should be modifiable
struct ValidatorGroup {
string identifier;
Expand Down Expand Up @@ -543,6 +546,45 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return true;
}

function validatorAddressFromCurrentSet(uint256 index) external view returns (address) {
address validatorAddress;
assembly {
let newCallDataPosition := mload(0x40)
mstore(newCallDataPosition, index)
let success := staticcall(
5000,
0xfa,
newCallDataPosition,
32,
0,
0
)
returndatacopy(add(newCallDataPosition, 64), 0, 32)
validatorAddress := mload(add(newCallDataPosition, 64))
}

return validatorAddress;
}

function numberValidatorsInCurrentSet() external view returns (uint256) {
uint256 numberValidators;
assembly {
let success := staticcall(
5000,
0xf9,
0,
0,
0,
0
)
let returnData := mload(0x40)
returndatacopy(returnData, 0, 32)
numberValidators := mload(returnData)
}

return numberValidators;
}

/**
* @notice Returns validator information.
* @param account The account that registered the validator.
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/truffle.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const networks = {
...defaultConfig,
from: DEVELOPMENT_FROM,
gasPrice: 0,
gas: 7000000,
defaultBalance: 1000000,
mnemonic: 'concert load couple harbor equip island argue ramp clarify fence smart topic',
},
Expand Down

0 comments on commit ec7fbe6

Please sign in to comment.