Skip to content

Commit

Permalink
Adding slashing multiplier to validators.sol and reward calculation (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasege authored and celo-ci-bot-user committed Dec 17, 2019
1 parent c82468d commit 3fbb017
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 21 deletions.
2 changes: 1 addition & 1 deletion packages/dev-utils/src/ganache-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export async function startGanache(datadir: string, opts: { verbose?: boolean }
network_id: 1101,
db_path: datadir,
mnemonic: MNEMONIC,
gasLimit: 10000000,
gasLimit: 15000000,
allowUnlimitedContractSize: true,
})

Expand Down
16 changes: 13 additions & 3 deletions packages/protocol/contracts/governance/Election.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";

import "./interfaces/IElection.sol";
import "./interfaces/IValidators.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/linkedlists/AddressSortedLinkedList.sol";
Expand Down Expand Up @@ -430,8 +431,9 @@ contract Election is
uint256 totalEpochRewards,
uint256[] calldata uptimes
) external view returns (uint256) {
IValidators validators = getValidators();
// The group must meet the balance requirements for their voters to receive epoch rewards.
if (!getValidators().meetsAccountLockedGoldRequirements(group) || votes.active.total <= 0) {
if (!validators.meetsAccountLockedGoldRequirements(group) || votes.active.total <= 0) {
return 0;
}

Expand All @@ -440,10 +442,18 @@ contract Election is
votes.active.total
);
FixidityLib.Fraction memory score = FixidityLib.wrap(
getValidators().calculateGroupEpochScore(uptimes)
validators.calculateGroupEpochScore(uptimes)
);
FixidityLib.Fraction memory slashingMultiplier = FixidityLib.wrap(
validators.getValidatorGroupSlashingMultiplier(group)
);
return
FixidityLib.newFixed(totalEpochRewards).multiply(votePortion).multiply(score).fromFixed();
FixidityLib
.newFixed(totalEpochRewards)
.multiply(votePortion)
.multiply(score)
.multiply(slashingMultiplier)
.fromFixed();
}

/**
Expand Down
97 changes: 83 additions & 14 deletions packages/protocol/contracts/governance/Validators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ contract Validators is
FixidityLib.Fraction commission;
// sizeHistory[i] contains the last time the group contained i members.
uint256[] sizeHistory;
SlashingInfo slashInfo;
}

// Stores the epoch number at which a validator joined a particular group.
Expand All @@ -81,6 +82,11 @@ contract Validators is
uint256 lastRemovedFromGroupTimestamp;
}

struct SlashingInfo {
FixidityLib.Fraction multiplier;
uint256 lastSlashed;
}

struct PublicKeys {
bytes ecdsa;
bytes bls;
Expand Down Expand Up @@ -108,6 +114,7 @@ contract Validators is
ValidatorScoreParameters private validatorScoreParameters;
uint256 public membershipHistoryLength;
uint256 public maxGroupSize;
uint256 public slashingMultiplierResetPeriod;

event MaxGroupSizeSet(uint256 size);
event ValidatorEpochPaymentSet(uint256 value);
Expand Down Expand Up @@ -162,6 +169,7 @@ contract Validators is
uint256 validatorScoreExponent,
uint256 validatorScoreAdjustmentSpeed,
uint256 _membershipHistoryLength,
uint256 _slashingMultiplierResetPeriod,
uint256 _maxGroupSize
) external initializer {
_transferOwnership(msg.sender);
Expand All @@ -171,6 +179,7 @@ contract Validators is
setValidatorScoreParameters(validatorScoreExponent, validatorScoreAdjustmentSpeed);
setMaxGroupSize(_maxGroupSize);
setMembershipHistoryLength(_membershipHistoryLength);
setSlashingMultiplierResetPeriod(_slashingMultiplierResetPeriod);
}

/**
Expand Down Expand Up @@ -282,7 +291,7 @@ contract Validators is
bytes calldata blsPublicKey,
bytes calldata blsPop
) external nonReentrant returns (bool) {
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
uint256 lockedGoldBalance = getLockedGold().getAccountTotalLockedGold(account);
require(lockedGoldBalance >= validatorLockedGoldRequirements.value);
Expand Down Expand Up @@ -454,7 +463,7 @@ contract Validators is
* @dev Fails if the validator has been a member of a group too recently.
*/
function deregisterValidator(uint256 index) external nonReentrant returns (bool) {
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(isValidator(account));

// Require that the validator has not been a member of a validator group for
Expand Down Expand Up @@ -482,7 +491,7 @@ contract Validators is
* @dev De-affiliates with the previously affiliated group if present.
*/
function affiliate(address group) external nonReentrant returns (bool) {
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(isValidator(account) && isValidatorGroup(group));
require(meetsAccountLockedGoldRequirements(account));
require(meetsAccountLockedGoldRequirements(group));
Expand All @@ -501,7 +510,7 @@ contract Validators is
* @dev Fails if the account is not a validator with non-zero affiliation.
*/
function deaffiliate() external nonReentrant returns (bool) {
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(isValidator(account));
Validator storage validator = validators[account];
require(validator.affiliation != address(0));
Expand All @@ -521,7 +530,7 @@ contract Validators is
external
returns (bool)
{
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(isValidator(account));
Validator storage validator = validators[account];
_updateBlsPublicKey(validator, account, blsPublicKey, blsPop);
Expand Down Expand Up @@ -603,13 +612,14 @@ contract Validators is
*/
function registerValidatorGroup(uint256 commission) external nonReentrant returns (bool) {
require(commission <= FixidityLib.fixed1().unwrap(), "Commission can't be greater than 100%");
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
uint256 lockedGoldBalance = getLockedGold().getAccountTotalLockedGold(account);
require(lockedGoldBalance >= groupLockedGoldRequirements.value);
ValidatorGroup storage group = groups[account];
group.exists = true;
group.commission = FixidityLib.wrap(commission);
group.slashInfo = SlashingInfo(FixidityLib.fixed1(), 0);
registeredGroups.push(account);
emit ValidatorGroupRegistered(account, commission);
return true;
Expand All @@ -623,7 +633,7 @@ contract Validators is
* @dev Fails if the group has had members too recently.
*/
function deregisterValidatorGroup(uint256 index) external nonReentrant returns (bool) {
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
// Only Validator Groups that have never had members or have been empty for at least
// `groupLockedGoldRequirements.duration` seconds can be deregistered.
require(isValidatorGroup(account) && groups[account].members.numElements == 0);
Expand All @@ -645,7 +655,7 @@ contract Validators is
* @dev Fails if the group has zero members.
*/
function addMember(address validator) external nonReentrant returns (bool) {
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(groups[account].members.numElements > 0);
return _addMember(account, validator, address(0), address(0));
}
Expand All @@ -664,7 +674,7 @@ contract Validators is
nonReentrant
returns (bool)
{
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(groups[account].members.numElements == 0);
return _addMember(account, validator, lesser, greater);
}
Expand Down Expand Up @@ -707,7 +717,7 @@ contract Validators is
* @dev Fails if `validator` is not a member of the account's group.
*/
function removeMember(address validator) external nonReentrant returns (bool) {
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(isValidatorGroup(account) && isValidator(validator), "is not group and validator");
return _removeMember(account, validator);
}
Expand All @@ -727,7 +737,7 @@ contract Validators is
nonReentrant
returns (bool)
{
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
ValidatorGroup storage group = groups[account];
require(group.members.contains(validator));
Expand All @@ -743,7 +753,7 @@ contract Validators is
* @return True upon success.
*/
function updateCommission(uint256 commission) external returns (bool) {
address account = getAccounts().signerToAccount(msg.sender);
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
require(commission <= FixidityLib.fixed1().unwrap(), "Commission can't be greater than 100%");
Expand Down Expand Up @@ -837,11 +847,17 @@ contract Validators is
function getValidatorGroup(address account)
external
view
returns (address[] memory, uint256, uint256[] memory)
returns (address[] memory, uint256, uint256[] memory, uint256, uint256)
{
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
return (group.members.getKeys(), group.commission.unwrap(), group.sizeHistory);
return (
group.members.getKeys(),
group.commission.unwrap(),
group.sizeHistory,
group.slashInfo.multiplier.unwrap(),
group.slashInfo.lastSlashed
);
}

/**
Expand Down Expand Up @@ -1131,4 +1147,57 @@ contract Validators is
}
}
}

/**
* @notice Sets the slashingMultiplierRestPeriod property if called by owner.
* @param value New reset period for slashing multiplier.
*/
function setSlashingMultiplierResetPeriod(uint256 value) public nonReentrant onlyOwner {
slashingMultiplierResetPeriod = value;
}

/**
* @notice Resets a group's slashing multiplier if it has been >= the reset period since
* the last time the group was slashed.
*/
function resetSlashingMultiplier() external nonReentrant {
address account = getAccounts().validatorSignerToAccount(msg.sender);
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
require(
now >= group.slashInfo.lastSlashed.add(slashingMultiplierResetPeriod),
"`resetSlashingMultiplier` called before resetPeriod expired"
);
group.slashInfo.multiplier = FixidityLib.fixed1();
}

bytes32[] canHalveSlashingMultiplier = [
DOWNTIME_SLASHER_REGISTRY_ID,
DOUBLE_SIGNING_SLASHER_REGISTRY_ID
];

/**
* @notice Halves the group's slashing multiplier.
* @param account The group being slashed.
*/
function halveSlashingMultiplier(address account)
external
nonReentrant
onlyRegisteredContracts(canHalveSlashingMultiplier)
{
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
group.slashInfo.multiplier = FixidityLib.wrap(group.slashInfo.multiplier.unwrap().div(2));
group.slashInfo.lastSlashed = now;
}

/**
* @notice Getter for a group's slashing multiplier.
* @param account The group to fetch slashing multiplier for.
*/
function getValidatorGroupSlashingMultiplier(address account) external view returns (uint256) {
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
return group.slashInfo.multiplier.unwrap();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ interface IValidators {
function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool);
function isValidator(address) external view returns (bool);
function calculateGroupEpochScore(uint256[] calldata uptimes) external view returns (uint256);
function getValidatorGroupSlashingMultiplier(address) external view returns (uint256);
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,8 @@ contract MockValidators is IValidators {
}
return numMembers;
}

function getValidatorGroupSlashingMultiplier(address) external view returns (uint256) {
return FIXED1_UINT;
}
}
1 change: 1 addition & 0 deletions packages/protocol/migrations/12_validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const initializeArgs = async (): Promise<any[]> => {
config.validators.validatorScoreParameters.exponent,
toFixed(config.validators.validatorScoreParameters.adjustmentSpeed).toFixed(),
config.validators.membershipHistoryLength,
config.validators.slashingPenaltyResetPeriod,
config.validators.maxGroupSize,
]
}
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/migrationsConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const DefaultConfig = {
},
membershipHistoryLength: 60,
maxGroupSize: '5',
slashingPenaltyResetPeriod: 60 * 60 * 24 * 30, // 30 Days

// We register a number of C-Labs groups to contain an initial set of validators to run the network.
validatorKeys: [],
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/runTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ async function startGanache() {
network_id: network.network_id,
mnemonic: network.mnemonic,
gasPrice: network.gasPrice,
gasLimit: 10000000,
gasLimit: 15000000,
allowUnlimitedContractSize: true,
})

Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/scripts/devchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as yargs from 'yargs'

const MNEMONIC = 'concert load couple harbor equip island argue ramp clarify fence smart topic'

const gasLimit = 10000000
const gasLimit = 15000000

const ProtocolRoot = path.normalize(path.join(__dirname, '../'))

Expand Down
Loading

0 comments on commit 3fbb017

Please sign in to comment.