Skip to content
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
2 changes: 1 addition & 1 deletion packages/cli/src/commands/lockedgold/unlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class Unlock extends BaseCommand {

await newCheckBuilder(this, res.flags.from)
.isAccount(res.flags.from)
.hasEnoughNonvotingLockedGold(value)
.hasEnoughLockedGoldToUnlock(value)
.runChecks()

await displaySendTx('unlock', lockedgold.unlock(value))
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/commands/validator/deregister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export default class ValidatorDeregister extends BaseCommand {
.isSignerOrAccount()
.canSignValidatorTxs()
.signerAccountIsValidator()
.isNotValidatorGroupMember()
.validatorDeregisterDurationPassed()
.runChecks()

const validator = await validators.signerToAccount(res.flags.from)
Expand Down
25 changes: 25 additions & 0 deletions packages/cli/src/commands/validator/register.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,29 @@ testWithGanache('validator:register', (web3: Web3) => {
'cdb77255037eb68897cd487fdd85388cbda448f617f874449d4b11588b0b7ad8ddc20d9bb450b513bb35664ea3923900',
])
})

test('fails if validator already registered', async () => {
await ValidatorRegister.run([
'--from',
account,
'--ecdsaKey',
ecdsaPublicKey,
'--blsKey',
'4fa3f67fc913878b068d1fa1cdddc54913d3bf988dbe5a36a20fa888f20d4894c408a6773f3d7bde11154f2a3076b700d345a42fd25a0e5e83f4db5586ac7979ac2053cd95d8f2efd3e959571ceccaa743e02cf4be3f5d7aaddb0b06fc9aff00',
'--blsSignature',
'cdb77255037eb68897cd487fdd85388cbda448f617f874449d4b11588b0b7ad8ddc20d9bb450b513bb35664ea3923900',
])
await expect(
ValidatorRegister.run([
'--from',
account,
'--ecdsaKey',
ecdsaPublicKey,
'--blsKey',
'4fa3f67fc913878b068d1fa1cdddc54913d3bf988dbe5a36a20fa888f20d4894c408a6773f3d7bde11154f2a3076b700d345a42fd25a0e5e83f4db5586ac7979ac2053cd95d8f2efd3e959571ceccaa743e02cf4be3f5d7aaddb0b06fc9aff00',
'--blsSignature',
'cdb77255037eb68897cd487fdd85388cbda448f617f874449d4b11588b0b7ad8ddc20d9bb450b513bb35664ea3923900',
])
).rejects.toThrow("Some checks didn't pass!")
})
})
2 changes: 2 additions & 0 deletions packages/cli/src/commands/validator/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export default class ValidatorRegister extends BaseCommand {
await newCheckBuilder(this, res.flags.from)
.isSignerOrAccount()
.canSignValidatorTxs()
.isNotValidator()
.isNotValidatorGroup()
.signerMeetsValidatorBalanceRequirements()
.runChecks()

Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/commands/validatorgroup/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export default class ValidatorGroupRegister extends BaseCommand {
.addCheck('Commission is in range [0,1]', () => commission.gte(0) && commission.lte(1))
.isSignerOrAccount()
.canSignValidatorTxs()
.isNotValidator()
.isNotValidatorGroup()
.signerMeetsValidatorGroupBalanceRequirements()
.runChecks()

Expand Down
59 changes: 56 additions & 3 deletions packages/cli/src/utils/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,21 @@ class CheckBuilder {
}

withLockedGold<A>(
f: (lockedGold: LockedGoldWrapper, signer: Address, account: Address) => A
f: (
lockedGold: LockedGoldWrapper,
signer: Address,
account: Address,
validators: ValidatorsWrapper
) => A
): () => Promise<Resolve<A>> {
return async () => {
const lockedGold = await this.kit.contracts.getLockedGold()
const validators = await this.kit.contracts.getValidators()
if (this.signer) {
const account = await validators.signerToAccount(this.signer)
return f(lockedGold, this.signer, account) as Resolve<A>
return f(lockedGold, this.signer, account, validators) as Resolve<A>
} else {
return f(lockedGold, '', '') as Resolve<A>
return f(lockedGold, '', '', validators) as Resolve<A>
}
}
}
Expand Down Expand Up @@ -183,6 +188,18 @@ class CheckBuilder {
this.withValidators((v) => v.isValidatorGroup(account))
)

isNotValidator = () =>
this.addCheck(
`${this.signer!} is not a registered Validator`,
this.withValidators((v, _signer, account) => negate(v.isValidator(account)))
)

isNotValidatorGroup = () =>
this.addCheck(
`${this.signer!} is not a registered ValidatorGroup`,
this.withValidators((v, _signer, account) => negate(v.isValidatorGroup(account)))
)

signerMeetsValidatorBalanceRequirements = () =>
this.addCheck(
`Signer's account has enough locked gold for registration`,
Expand Down Expand Up @@ -278,6 +295,42 @@ class CheckBuilder {
)
}

hasEnoughLockedGoldToUnlock = (value: BigNumber) => {
const valueInEth = this.kit.web3.utils.fromWei(value.toFixed(), 'ether')
return this.addCheck(
`Account has at least ${valueInEth} non-voting Locked Gold over requirement`,
this.withLockedGold(async (l, _signer, account, v) =>
value
.plus(await v.getAccountLockedGoldRequirement(account))
.isLessThanOrEqualTo(await l.getAccountNonvotingLockedGold(account))
)
)
}

isNotValidatorGroupMember = () => {
return this.addCheck(
`Account isn't a member of a validator group`,
this.withValidators(async (v, _signer, account) => {
const { affiliation } = await v.getValidator(account)
const { members } = await v.getValidatorGroup(affiliation!)
return !members.includes(account)
})
)
}

validatorDeregisterDurationPassed = () => {
return this.addCheck(
`Enough time has passed since the account was removed from a validator group`,
this.withValidators(async (v, _signer, account) => {
const { lastRemovedFromGroupTimestamp } = await v.getValidatorMembershipHistoryExtraData(
account
)
const { duration } = await v.getValidatorLockedGoldRequirements()
return duration.toNumber() + lastRemovedFromGroupTimestamp < Date.now()
})
)
}

async runChecks() {
console.log(`Running Checks:`)
let allPassed = true
Expand Down
18 changes: 18 additions & 0 deletions packages/contractkit/src/wrappers/Validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export interface GroupMembership {
group: Address
}

export interface MembershipHistoryExtraData {
lastRemovedFromGroupTimestamp: number
tail: number
}

/**
* Contract for voting for validators and managing validator groups.
*/
Expand Down Expand Up @@ -265,6 +270,19 @@ export class ValidatorsWrapper extends BaseWrapper<Validators> {
zip((epoch, group): GroupMembership => ({ epoch: valueToInt(epoch), group }), res[0], res[1])
)

/**
* Returns extra data from the Validator's group membership history
* @param validator The validator whose membership history to return.
* @return The group membership history of a validator.
*/
getValidatorMembershipHistoryExtraData: (
validator: Address
) => Promise<MembershipHistoryExtraData> = proxyCall(
this.contract.methods.getMembershipHistory,
undefined,
(res) => ({ lastRemovedFromGroupTimestamp: valueToInt(res[2]), tail: valueToInt(res[3]) })
)

/** Get the size (amount of members) of a ValidatorGroup */
getValidatorGroupSize: (group: Address) => Promise<number> = proxyCall(
this.contract.methods.getGroupNumMembers,
Expand Down