const BridgeValidators = artifacts.require('RewardableValidators.sol') const EternalStorageProxy = artifacts.require('EternalStorageProxy.sol') const { expect } = require('chai') const { ERROR_MSG, ZERO_ADDRESS, F_ADDRESS, BN } = require('./setup') const { expectEventInLogs, createAccounts, deployProxy } = require('./helpers/helpers') const MAX_GAS = 8000000 const MAX_VALIDATORS = 50 const ZERO = new BN(0) contract('RewardableValidators', async accounts => { let bridgeValidators const owner = accounts[0] beforeEach(async () => { bridgeValidators = await deployProxy(BridgeValidators) }) describe('#initialize', async () => { it('sets values', async () => { expect(await bridgeValidators.owner()).to.be.equal(ZERO_ADDRESS) expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal(ZERO) expect(await bridgeValidators.isValidator(accounts[0])).to.be.equal(false) expect(await bridgeValidators.isValidator(accounts[1])).to.be.equal(false) expect(await bridgeValidators.isInitialized()).to.be.equal(false) expect(await bridgeValidators.requiredSignatures()).to.be.bignumber.equal(ZERO) expect(await bridgeValidators.deployedAtBlock()).to.be.bignumber.equal(ZERO) await bridgeValidators .initialize(3, accounts.slice(0, 3), accounts.slice(3, 5), accounts[2]) .should.be.rejectedWith(ERROR_MSG) await bridgeValidators.initialize(1, [accounts[0]], [ZERO_ADDRESS], accounts[1]).should.be.rejectedWith(ERROR_MSG) await bridgeValidators.initialize(1, [ZERO_ADDRESS], [accounts[0]], accounts[1]).should.be.rejectedWith(ERROR_MSG) await bridgeValidators.initialize(1, [F_ADDRESS], [accounts[0]], accounts[1]).should.be.rejectedWith(ERROR_MSG) await bridgeValidators .initialize(2, accounts.slice(0, 2), accounts.slice(2, 4), accounts[2], { from: accounts[1] }) .should.be.rejectedWith(ERROR_MSG) await bridgeValidators.initialize(2, accounts.slice(0, 2), accounts.slice(2, 4), accounts[2]).should.be.fulfilled await bridgeValidators .initialize(2, accounts.slice(0, 2), accounts.slice(2, 4), accounts[2]) .should.be.rejectedWith(ERROR_MSG) expect(await bridgeValidators.isInitialized()).to.be.equal(true) expect(await bridgeValidators.requiredSignatures()).to.be.bignumber.equal('2') expect(await bridgeValidators.isValidator(accounts[0])).to.be.equal(true) expect(await bridgeValidators.isValidator(accounts[1])).to.be.equal(true) expect(await bridgeValidators.owner()).to.be.equal(accounts[2]) expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal('2') expect(await bridgeValidators.deployedAtBlock()).to.be.bignumber.above(ZERO) const { major, minor, patch } = await bridgeValidators.getBridgeValidatorsInterfacesVersion() expect(major).to.be.bignumber.gte(ZERO) expect(minor).to.be.bignumber.gte(ZERO) expect(patch).to.be.bignumber.gte(ZERO) }) it('should fail if exceed amount of validators', async () => { // Given const validators = createAccounts(web3, MAX_VALIDATORS + 1) // When await bridgeValidators .initialize(MAX_VALIDATORS - 1, validators, validators, accounts[2]) .should.be.rejectedWith(ERROR_MSG) }) it('should be able to operate with max allowed number of validators', async () => { // Given const validators = createAccounts(web3, MAX_VALIDATORS) // When const { receipt } = await bridgeValidators.initialize(MAX_VALIDATORS - 1, validators, validators, accounts[2]) .should.be.fulfilled expect(receipt.gasUsed).to.be.lte(MAX_GAS) expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal(`${MAX_VALIDATORS}`) // removing last validator from list (the highest gas consumption) await bridgeValidators.removeValidator(validators[MAX_VALIDATORS - 1], { from: accounts[2] }).should.be.fulfilled expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal(`${MAX_VALIDATORS - 1}`) }) }) describe('#addValidator', async () => { const owner = accounts[2] const validators = [accounts[0], accounts[1]] const rewards = accounts.slice(2, 4) const requiredSignatures = 2 beforeEach(async () => { await bridgeValidators.initialize(requiredSignatures, validators, rewards, owner).should.be.fulfilled expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal('2') }) it('adds validator', async () => { const newValidator = accounts[4] const newReward = accounts[5] false.should.be.equal(await bridgeValidators.isValidator(newValidator)) await bridgeValidators .addRewardableValidator(newValidator, newReward, { from: validators[0] }) .should.be.rejectedWith(ERROR_MSG) const { logs } = await bridgeValidators.addRewardableValidator(newValidator, newReward, { from: owner }).should.be.fulfilled expect(await bridgeValidators.isValidator(newValidator)).to.be.equal(true) expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal('3') expectEventInLogs(logs, 'ValidatorAdded', { validator: newValidator }) const rewardAddress = await bridgeValidators.getValidatorRewardAddress(newValidator) expect(rewardAddress).to.be.equal(newReward) }) it('cannot add already existing validator', async () => { true.should.be.equal(await bridgeValidators.isValidator(validators[0])) await bridgeValidators .addRewardableValidator(validators[0], rewards[0], { from: owner }) .should.be.rejectedWith(ERROR_MSG) expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal('2') }) it(`cannot add 0xf as validator address`, async () => { // Given await bridgeValidators .addRewardableValidator(F_ADDRESS, rewards[0], { from: owner }) .should.be.rejectedWith(ERROR_MSG) }) it(`cannot add 0x0 as validator address`, async () => { await bridgeValidators .addRewardableValidator(ZERO_ADDRESS, rewards[0], { from: owner }) .should.be.rejectedWith(ERROR_MSG) }) it(`cannot add 0x0 as reward address`, async () => { await bridgeValidators .addRewardableValidator(accounts[4], ZERO_ADDRESS, { from: owner }) .should.be.rejectedWith(ERROR_MSG) }) }) describe('#removeValidator', async () => { const owner = accounts[2] const validators = [accounts[0], accounts[1], accounts[3]] const rewards = accounts.slice(4, 7) const requiredSignatures = 2 beforeEach(async () => { await bridgeValidators.initialize(requiredSignatures, validators, rewards, owner).should.be.fulfilled expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal('3') }) it('removes validator', async () => { const toRemove = validators[0] expect(await bridgeValidators.isValidator(toRemove)).to.be.equal(true) await bridgeValidators.removeValidator(toRemove, { from: validators[0] }).should.be.rejectedWith(ERROR_MSG) const { logs } = await bridgeValidators.removeValidator(toRemove, { from: owner }).should.be.fulfilled expect(await bridgeValidators.isValidator(toRemove)).to.be.equal(false) expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal('2') expectEventInLogs(logs, 'ValidatorRemoved', { validator: toRemove }) }) it('cannot remove if it will break requiredSignatures', async () => { const toRemove = validators[0] const toRemove2 = validators[1] true.should.be.equal(await bridgeValidators.isValidator(toRemove)) true.should.be.equal(await bridgeValidators.isValidator(toRemove)) await bridgeValidators.removeValidator(toRemove, { from: owner }).should.be.fulfilled await bridgeValidators.removeValidator(toRemove2, { from: owner }).should.be.rejectedWith(ERROR_MSG) false.should.be.equal(await bridgeValidators.isValidator(toRemove)) true.should.be.equal(await bridgeValidators.isValidator(toRemove2)) expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal('2') }) it('cannot remove non-existent validator', async () => { false.should.be.equal(await bridgeValidators.isValidator(accounts[4])) await bridgeValidators.removeValidator(accounts[4], { from: owner }).should.be.rejectedWith(ERROR_MSG) await bridgeValidators.removeValidator(ZERO_ADDRESS, { from: owner }).should.be.rejectedWith(ERROR_MSG) expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal('3') }) }) describe('#setRequiredSignatures', async () => { const owner = accounts[2] const validators = [accounts[0], accounts[1], accounts[3]] const rewards = accounts.slice(4, 7) const requiredSignatures = '2' beforeEach(async () => { await bridgeValidators.initialize(requiredSignatures, validators, rewards, owner).should.be.fulfilled expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal('3') }) it('sets req signatures', async () => { const newReqSig = '3' expect(await bridgeValidators.requiredSignatures()).to.be.bignumber.equal(requiredSignatures) await bridgeValidators.setRequiredSignatures(newReqSig, { from: validators[0] }).should.be.rejectedWith(ERROR_MSG) await bridgeValidators.setRequiredSignatures(newReqSig, { from: owner }).should.be.fulfilled expect(await bridgeValidators.requiredSignatures()).to.be.bignumber.equal(newReqSig) }) it('cannot set more than validators count', async () => { const newReqSig = '4' expect(await bridgeValidators.requiredSignatures()).to.be.bignumber.equal(requiredSignatures) await bridgeValidators.setRequiredSignatures(newReqSig, { from: owner }).should.be.rejectedWith(ERROR_MSG) expect(await bridgeValidators.requiredSignatures()).to.be.bignumber.equal(requiredSignatures) }) }) describe('#upgradable', async () => { it('can be upgraded via upgradeToAndCall', async () => { const impl = await BridgeValidators.new() const requiredSignatures = '2' const validators = [accounts[0], accounts[1]] const rewards = accounts.slice(3, 5) const owner = accounts[2] const data = impl.contract.methods.initialize(requiredSignatures, validators, rewards, owner).encodeABI() const proxy = await EternalStorageProxy.at(bridgeValidators.address) await proxy.upgradeToAndCall('2', impl.address, data, { from: accounts[1] }).should.be.rejectedWith(ERROR_MSG) await proxy.upgradeToAndCall('2', impl.address, data).should.be.fulfilled true.should.be.equal(await bridgeValidators.isInitialized()) expect(await bridgeValidators.requiredSignatures()).to.be.bignumber.equal(requiredSignatures) true.should.be.equal(await bridgeValidators.isValidator(validators[0])) true.should.be.equal(await bridgeValidators.isValidator(validators[1])) owner.should.be.equal(await bridgeValidators.owner()) expect(await bridgeValidators.validatorCount()).to.be.bignumber.equal(validators.length.toString()) }) }) describe('#single list remove', () => { it(`should remove ${accounts[0]} - without Proxy`, async () => { // Given const { initialize, isInitialized, removeValidator } = bridgeValidators await initialize(1, accounts.slice(0, 2), accounts.slice(2, 4), owner).should.be.fulfilled true.should.be.equal(await isInitialized()) // When const { logs } = await removeValidator(accounts[0], { from: owner }).should.be.fulfilled // Then expectEventInLogs(logs, 'ValidatorRemoved', { validator: accounts[0] }) }) it(`Removed validator should return zero address on nextValidator`, async () => { // Given const { initialize, isInitialized, removeValidator, getNextValidator } = bridgeValidators await initialize(1, accounts.slice(0, 2), accounts.slice(2, 4), owner).should.be.fulfilled true.should.be.equal(await isInitialized()) const initialNextValidator = await getNextValidator(accounts[0]) // When const { logs } = await removeValidator(accounts[0], { from: owner }).should.be.fulfilled // Then expectEventInLogs(logs, 'ValidatorRemoved', { validator: accounts[0] }) const updatedNextValidator = await getNextValidator(accounts[0]) initialNextValidator.should.be.equals(accounts[1]) updatedNextValidator.should.be.equals(ZERO_ADDRESS) }) accounts.slice(0, 5).forEach(validator => { it(`should remove ${validator} - with Proxy`, async () => { // Given const { initialize, isInitialized, removeValidator } = bridgeValidators await initialize(1, accounts.slice(0, 5), accounts.slice(5, 10), owner, { from: accounts[1] }).should.be.rejectedWith(ERROR_MSG) await initialize(1, accounts.slice(0, 5), accounts.slice(5, 10), owner).should.be.fulfilled true.should.be.equal(await isInitialized()) // When const { logs } = await removeValidator(validator, { from: owner }).should.be.fulfilled // Then expectEventInLogs(logs, 'ValidatorRemoved', { validator }) }) }) }) describe('#reward address', () => { it(`reward address is properly assigned`, async () => { // Given const { initialize, isInitialized, getValidatorRewardAddress } = bridgeValidators await initialize(1, accounts.slice(0, 5), accounts.slice(5, 10), owner).should.be.fulfilled // When expect(await isInitialized()).to.be.equal(true) // Then for (let i = 0; i < accounts.slice(0, 5).length; i++) { const validator = accounts[i] expect(await getValidatorRewardAddress(validator)).to.be.equal(accounts[i + 5]) } }) }) describe('#Validators list', () => { it('should return validators list', async () => { // Given const validators = accounts.slice(0, 5) const { initialize, validatorList } = bridgeValidators // When await initialize(1, validators, accounts.slice(5, 10), owner).should.be.fulfilled // Then expect(await validatorList()).to.be.eql(validators) }) }) })