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
14 changes: 14 additions & 0 deletions l1-contracts/src/core/slashing/SlashingProposer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ contract SlashingProposer is IEmpire, EmpireBase {
address public immutable INSTANCE;
ISlasher public immutable SLASHER;

/**
* @notice Constructor for the SlashingProposer contract.
*
* @param _instance - The specific rollup that the proposer will be used for
* @param _slasher - The entity that can slash on the _instance
* The SlashingProposer `address(this)` should be able to use the slasher for this contract to
* make sense.
* @param _slashingQuorum The number of signals needed in a round for a slash to pass.
* @param _roundSize The number of signals that can be cast in a round.
* @param _lifetimeInRounds - A deadline for when the passing proposal must have been executed.
* @param _executionDelayInRounds - A delay for how quickly a passing proposal can be executed.
* When used together with a `_slasher` that has VETO functionality this is the time
* that the vetoer have to act.
*/
constructor(
address _instance,
ISlasher _slasher,
Expand Down
12 changes: 8 additions & 4 deletions l1-contracts/src/governance/GSE.sol
Original file line number Diff line number Diff line change
Expand Up @@ -745,18 +745,22 @@ contract GSE is IGSE, GSECore {

uint32 ts = Timestamp.unwrap(_timestamp).toUint32();

// The effective size of the set will be the size of the instance attesters, plus the size of the bonus attesters
// if the instance is the latest rollup. This will effectively work as one long list with [...instance, ...bonus]
uint256 storeSize = instanceStore.attesters.lengthAtTimestamp(ts);
uint256 canonicalSize = isLatestRollup ? bonusStore.attesters.lengthAtTimestamp(ts) : 0;
uint256 totalSize = storeSize + canonicalSize;

// When this is called from `getAttestersAtTime`, _indices.length is one more than the effective attester count.
// And the returned array will be:
// [rollup attester 1, rollup attester 2, ..., rollup attester N, bonus attester 1, bonus attester 2, ..., bonus
// attester M]
// We loop through the indices, and for each index we get the attester from the instance or bonus instance
// depending on value in the collective list [...instance, ...bonus]
for (uint256 i = 0; i < _indices.length; i++) {
uint256 index = _indices[i];
require(index < totalSize, Errors.GSE__OutOfBounds(index, totalSize));

// since we have ensured that the index is not out of bounds, we can use the unsafe function in
// `AddressSnapshotLib` to fetch if. We use the `recent` variant as we expect the attesters to
// mainly be from recent history when fetched during tx execution.

if (index < storeSize) {
attesters[i] = instanceStore.attesters.unsafeGetRecentAddressFromIndexAtTimestamp(index, ts);
} else if (isLatestRollup) {
Expand Down
35 changes: 28 additions & 7 deletions l1-contracts/src/governance/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ contract Governance is IGovernance {
/**
* @dev Modifier to ensure that the caller is the governance contract itself.
*
* Protects functions that allow the governance contract to modify its own state, via a proposal.
* The caller will only be the governance itself if executed via a proposal.
*/
modifier onlySelf() {
require(msg.sender == address(this), Errors.Governance__CallerNotSelf(msg.sender, address(this)));
Expand Down Expand Up @@ -271,7 +271,8 @@ contract Governance is IGovernance {
/**
* @notice Update the governance proposer.
* @dev The governance proposer is the address that is allowed to use `propose`.
* only callable by the governance contract itself.
*
* @dev only callable by the governance contract itself.
*
* @dev causes all proposals proposed by the previous governance proposer to be `Droppable`.
*
Expand Down Expand Up @@ -313,8 +314,8 @@ contract Governance is IGovernance {
* the beneficiary must be allowed to hold power in the governance contract, according to `depositControl`.
*
* It is worth pointing out that someone could attempt to spam the deposit function, and increase the cost to vote
* as a result of creating many checkpoints. In reality though, at ethereum's current block time of 12s, it would
* take ~36 years of continuous spamming to increase the cost to vote by ~66K gas, so we accept this risk.
* as a result of creating many checkpoints. In reality though, as the checkpoints are using time as a key it would
* take ~36 years of continuous spamming to increase the cost to vote by ~66K gas with 12 second block times.
*
* @param _beneficiary The beneficiary to increase the power of.
* @param _amount The amount of funds to deposit, which is converted to power 1:1.
Expand Down Expand Up @@ -426,7 +427,9 @@ contract Governance is IGovernance {
ProposalState state = getProposalState(_proposalId);
require(state == ProposalState.Active, Errors.Governance__ProposalNotActive());

// Compute the power at the time where we became active
// Compute the power at the time the proposals goes from pending to active.
// This is the last second before active, and NOT the first second active, because it would then be possible to
// alter the power while the proposal is active since all txs in a block have the same timestamp.
uint256 userPower = users[msg.sender].valueAt(proposals[_proposalId].pendingThrough());

Ballot storage userBallot = ballots[_proposalId][msg.sender];
Expand Down Expand Up @@ -566,10 +569,26 @@ contract Governance is IGovernance {
return configuration;
}

/**
* @notice Get a proposal by its id.
*
* @dev Will return default values (0) for non-existing proposals
*
* @param _proposalId The id of the proposal to get.
* @return The proposal.
*/
function getProposal(uint256 _proposalId) external view override(IGovernance) returns (Proposal memory) {
return proposals[_proposalId];
}

/**
* @notice Get a withdrawal by its id.
*
* @dev Will return default values (0) for non-existing withdrawals
*
* @param _withdrawalId The id of the withdrawal to get.
* @return The withdrawal.
*/
function getWithdrawal(uint256 _withdrawalId) external view override(IGovernance) returns (Withdrawal memory) {
return withdrawals[_withdrawalId];
}
Expand Down Expand Up @@ -689,8 +708,10 @@ contract Governance is IGovernance {
/**
* @dev create a new proposal. In it we store:
*
* - a copy of the current configuration, because it can be updated on the Governance contract
* - the summed ballot to avoid recomputing it when determining the proposal state
* - a copy of the current governance configuration, effectively "freezing" the config for the proposal.
* This is done to ensure that in progress proposals that alter the delays etc won't take effect on existing
* proposals.
* - the summed ballots
* - the proposer, which can be:
* - the current governanceProposer (which can be updated on the Governance contract), if created via `propose`
* - the governance contract itself, if created via `proposeWithLock`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ library AddressSnapshotLib {
* @notice Removes a validator from the set
* @param _self The storage reference to the set
* @param _index The index of the validator to remove
* @param _address The address to remove
* @return bool True if the validator was removed, reverts otherwise
*/
function _remove(SnapshottedAddressSet storage _self, uint224 _index, address _address) internal returns (bool) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ library ConfigurationLib {
uint256 internal constant REQUIRED_YEA_MARGIN_UPPER = 1e18;

uint256 internal constant VOTES_LOWER = 1;
uint256 internal constant LOCK_AMOUNT_LOWER = 2;

Timestamp internal constant TIME_LOWER = Timestamp.wrap(60);
Timestamp internal constant TIME_UPPER = Timestamp.wrap(30 * 24 * 3600);
Expand Down Expand Up @@ -44,7 +45,9 @@ library ConfigurationLib {
);

require(_self.minimumVotes >= VOTES_LOWER, Errors.Governance__ConfigurationLib__InvalidMinimumVotes());
require(_self.proposeConfig.lockAmount >= VOTES_LOWER, Errors.Governance__ConfigurationLib__LockAmountTooSmall());
require(
_self.proposeConfig.lockAmount >= LOCK_AMOUNT_LOWER, Errors.Governance__ConfigurationLib__LockAmountTooSmall()
);

// Beyond checking the bounds like this, it might be useful to ensure that the value is larger than the withdrawal
// delay
Expand Down
105 changes: 105 additions & 0 deletions l1-contracts/src/governance/libraries/DepositDelegationLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ library DepositDelegationLib {
event DelegateChanged(address indexed attester, address oldDelegatee, address newDelegatee);
event DelegateVotesChanged(address indexed delegatee, uint256 oldValue, uint256 newValue);

/**
* @notice Increase the balance of an `_attester` on `_instance` by `_amount`,
* increases the voting power of the delegatee equally.
*
* @param _self The DepositAndDelegationAccounting struct to modify in storage
* @param _instance The instance that the attester is on
* @param _attester The attester to increase the balance of
* @param _amount The amount to increase by
*/
function increaseBalance(
DepositAndDelegationAccounting storage _self,
address _instance,
Expand All @@ -61,6 +70,15 @@ library DepositDelegationLib {
_self.supply.add(_amount);
}

/**
* @notice Decrease the balance of an `_attester` on `_instance` by `_amount`,
* decrease the voting power of the delegatee equally
*
* @param _self The DepositAndDelegationAccounting struct to modify in storage
* @param _instance The instance that the attester is on
* @param _attester The attester to decrease the balance of
* @param _amount The amount to decrease by
*/
function decreaseBalance(
DepositAndDelegationAccounting storage _self,
address _instance,
Expand Down Expand Up @@ -111,6 +129,14 @@ library DepositDelegationLib {
_self.votingAccounts[_delegatee].powerUsed[_proposalId] += _amount;
}

/**
* @notice Delegate the voting power of an `_attester` on a specific `_instance` to a `_delegatee`
*
* @param _self The DepositAndDelegationAccounting struct to modify in storage
* @param _instance The instance the attester is on
* @param _attester The attester to delegate the voting power of
* @param _delegatee The delegatee to delegate the voting power to
*/
function delegate(
DepositAndDelegationAccounting storage _self,
address _instance,
Expand All @@ -127,10 +153,28 @@ library DepositDelegationLib {
moveVotingPower(_self, oldDelegate, _delegatee, getBalanceOf(_self, _instance, _attester));
}

/**
* @notice Convenience function to remove delegation from `_attester` at `_instance`
*
* @dev Similar as calling `delegate` with `_delegatee = address(0)`
*
* @param _self The DepositAndDelegationAccounting struct to modify in storage
* @param _instance The instance that the attester is on
* @param _attester The attester to undelegate the voting power of
*/
function undelegate(DepositAndDelegationAccounting storage _self, address _instance, address _attester) internal {
delegate(_self, _instance, _attester, address(0));
}

/**
* @notice Get the balance of an `_attester` on `_instance`
*
* @param _self The DepositAndDelegationAccounting struct to read from
* @param _instance The instance that the attester is on
* @param _attester The attester to get the balance of
*
* @return The balance of the attester
*/
function getBalanceOf(DepositAndDelegationAccounting storage _self, address _instance, address _attester)
internal
view
Expand All @@ -139,14 +183,38 @@ library DepositDelegationLib {
return _self.ledgers[_instance].positions[_attester].balance;
}

/**
* @notice Get the supply of an `_instance`
*
* @param _self The DepositAndDelegationAccounting struct to read from
* @param _instance The instance to get the supply of
*
* @return The supply of the instance
*/
function getSupplyOf(DepositAndDelegationAccounting storage _self, address _instance) internal view returns (uint256) {
return _self.ledgers[_instance].supply.valueNow();
}

/**
* @notice Get the total supply of all instances
*
* @param _self The DepositAndDelegationAccounting struct to read from
*
* @return The total supply of all instances
*/
function getSupply(DepositAndDelegationAccounting storage _self) internal view returns (uint256) {
return _self.supply.valueNow();
}

/**
* @notice Get the delegatee of an `_attester` on `_instance`
*
* @param _self The DepositAndDelegationAccounting struct to read from
* @param _instance The instance that the attester is on
* @param _attester The attester to get the delegatee of
*
* @return The delegatee of the attester
*/
function getDelegatee(DepositAndDelegationAccounting storage _self, address _instance, address _attester)
internal
view
Expand All @@ -155,6 +223,14 @@ library DepositDelegationLib {
return _self.ledgers[_instance].positions[_attester].delegatee;
}

/**
* @notice Get the voting power of a `_delegatee`
*
* @param _self The DepositAndDelegationAccounting struct to read from
* @param _delegatee The delegatee to get the voting power of
*
* @return The voting power of the delegatee
*/
function getVotingPower(DepositAndDelegationAccounting storage _self, address _delegatee)
internal
view
Expand All @@ -163,6 +239,15 @@ library DepositDelegationLib {
return _self.votingAccounts[_delegatee].votingPower.valueNow();
}

/**
* @notice Get the voting power of a `_delegatee` at a specific `_timestamp`
*
* @param _self The DepositAndDelegationAccounting struct to read from
* @param _delegatee The delegatee to get the voting power of
* @param _timestamp The timestamp to get the voting power at
*
* @return The voting power of the delegatee at the specific `_timestamp`
*/
function getVotingPowerAt(DepositAndDelegationAccounting storage _self, address _delegatee, Timestamp _timestamp)
internal
view
Expand All @@ -171,6 +256,15 @@ library DepositDelegationLib {
return _self.votingAccounts[_delegatee].votingPower.valueAt(_timestamp);
}

/**
* @notice Get the power used by a `_delegatee` on a specific `_proposalId`
*
* @param _self The DepositAndDelegationAccounting struct to read from
* @param _delegatee The delegatee to get the power used by
* @param _proposalId The proposal to get the power used on
*
* @return The voting power used by the `_delegatee` at `_proposalId`
*/
function getPowerUsed(DepositAndDelegationAccounting storage _self, address _delegatee, uint256 _proposalId)
internal
view
Expand All @@ -179,6 +273,17 @@ library DepositDelegationLib {
return _self.votingAccounts[_delegatee].powerUsed[_proposalId];
}

/**
* @notice Move `_amount` of voting power from the delegatee of `_from` to the delegatee of `_to`
*
* @dev If the `_from` is `address(0)` the decrease is skipped, and it is effectively a mint
* @dev If the `_to` is `address(0)` the increase is skipped, and it is effectively a burn
*
* @param _self The DepositAndDelegationAccounting struct to modify in storage
* @param _from The address to move the voting power from
* @param _to The address to move the voting power to
* @param _amount The amount of voting power to move
*/
function moveVotingPower(DepositAndDelegationAccounting storage _self, address _from, address _to, uint256 _amount)
private
{
Expand Down
13 changes: 9 additions & 4 deletions l1-contracts/src/governance/proposer/EmpireBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ abstract contract EmpireBase is EIP712, IEmpire {
uint256 public immutable QUORUM_SIZE;
// The number of slots per round.
uint256 public immutable ROUND_SIZE;
// The number of rounds that a round winner may be submitted for.
// The number of rounds that a round winner may be submitted for, after it have passed.
uint256 public immutable LIFETIME_IN_ROUNDS;
// The number of rounds that must elapse before a round winner may be submitted.
uint256 public immutable EXECUTION_DELAY_IN_ROUNDS;
Expand Down Expand Up @@ -198,6 +198,10 @@ abstract contract EmpireBase is EIP712, IEmpire {

CompressedRoundAccounting storage round = rounds[instance][_roundNumber];
require(!round.executed, Errors.GovernanceProposer__PayloadAlreadySubmitted(_roundNumber));

// If the payload with the most signals is address(0) there are nothing to execute and it is a no-op.
// This will be the case if no signals have been cast during a round, or if people have simple signalled
// for nothing to happen (the same as not signalling).
require(
round.payloadWithMostSignals != IPayload(address(0)), Errors.GovernanceProposer__PayloadCannotBeAddressZero()
);
Expand Down Expand Up @@ -276,18 +280,19 @@ abstract contract EmpireBase is EIP712, IEmpire {
address instance = getInstance();
require(instance.code.length > 0, Errors.GovernanceProposer__InstanceHaveNoCode(instance));

IEmperor sequencerSelection = IEmperor(instance);
Slot currentSlot = sequencerSelection.getCurrentSlot();
IEmperor selection = IEmperor(instance);
Slot currentSlot = selection.getCurrentSlot();

uint256 roundNumber = computeRound(currentSlot);

CompressedRoundAccounting storage round = rounds[instance][roundNumber];

// Ensure that time have progressed since the last slot. If not, the current proposer might send multiple signals
require(
currentSlot > round.lastSignalSlot.decompress(), Errors.GovernanceProposer__SignalAlreadyCastForSlot(currentSlot)
);

address signaler = sequencerSelection.getCurrentProposer();
address signaler = selection.getCurrentProposer();

if (_sig.isEmpty()) {
require(msg.sender == signaler, Errors.GovernanceProposer__OnlyProposerCanSignal(msg.sender, signaler));
Expand Down
Loading
Loading