Skip to content

Commit

Permalink
feat: slasher
Browse files Browse the repository at this point in the history
  • Loading branch information
LHerskind committed Dec 14, 2024
1 parent 8699f8a commit 2409751
Show file tree
Hide file tree
Showing 63 changed files with 1,663 additions and 159 deletions.
8 changes: 6 additions & 2 deletions l1-contracts/src/core/Leonidas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ contract Leonidas is Staking, TimeFns, ILeonidas {
LeonidasStorage private leonidasStore;

constructor(
address _ares,
IERC20 _stakingAsset,
uint256 _minimumStake,
uint256 _slashingQuorum,
uint256 _roundSize,
uint256 _slotDuration,
uint256 _epochDuration,
uint256 _targetCommitteeSize
) Staking(_ares, _stakingAsset, _minimumStake) TimeFns(_slotDuration, _epochDuration) {
)
Staking(_stakingAsset, _minimumStake, _slashingQuorum, _roundSize)
TimeFns(_slotDuration, _epochDuration)
{
GENESIS_TIME = Timestamp.wrap(block.timestamp);
SLOT_DURATION = _slotDuration;
EPOCH_DURATION = _epochDuration;
Expand Down
12 changes: 8 additions & 4 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ struct Config {
uint256 targetCommitteeSize;
uint256 aztecEpochProofClaimWindowInL2Slots;
uint256 minimumStake;
uint256 slashingQuorum;
uint256 slashingRoundSize;
}

/**
Expand Down Expand Up @@ -108,15 +110,15 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, Leonidas, IRollup, ITes
)
Ownable(_ares)
Leonidas(
_ares,
_stakingAsset,
_config.minimumStake,
_config.slashingQuorum,
_config.slashingRoundSize,
_config.aztecSlotDuration,
_config.aztecEpochDuration,
_config.targetCommitteeSize
)
{
rollupStore.epochProofVerifier = new MockVerifier();
FEE_JUICE_PORTAL = _fpcJuicePortal;
REWARD_DISTRIBUTOR = _rewardDistributor;
ASSET = _fpcJuicePortal.UNDERLYING();
Expand All @@ -125,14 +127,16 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, Leonidas, IRollup, ITes
);
INBOX = IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT)));
OUTBOX = IOutbox(address(new Outbox(address(this))));
rollupStore.vkTreeRoot = _vkTreeRoot;
rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot;
VERSION = 1;
L1_BLOCK_AT_GENESIS = block.number;
CLAIM_DURATION_IN_L2_SLOTS = _config.aztecEpochProofClaimWindowInL2Slots;

IS_FOUNDRY_TEST = VM_ADDRESS.code.length > 0;

rollupStore.epochProofVerifier = new MockVerifier();
rollupStore.vkTreeRoot = _vkTreeRoot;
rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot;

// Genesis block
rollupStore.blocks[0] = BlockLog({
feeHeader: FeeHeader({
Expand Down
9 changes: 9 additions & 0 deletions l1-contracts/src/core/interfaces/ISlasher.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;

import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";

interface ISlasher {
function slash(IPayload _payload) external returns (bool);
}
37 changes: 37 additions & 0 deletions l1-contracts/src/core/staking/Slasher.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;

import {ISlasher} from "@aztec/core/interfaces/ISlasher.sol";
import {SlashingProposer} from "@aztec/core/staking/SlashingProposer.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";

contract Slasher is ISlasher {
SlashingProposer public immutable PROPOSER;

event SlashFailed(address target, bytes data, bytes returnData);

error Slasher__CallerNotProposer(address caller, address proposer); // 0x44c1f74f

constructor(uint256 _n, uint256 _m) {
PROPOSER = new SlashingProposer(msg.sender, this, _n, _m);
}

function slash(IPayload _payload) external override(ISlasher) returns (bool) {
require(
msg.sender == address(PROPOSER), Slasher__CallerNotProposer(msg.sender, address(PROPOSER))
);

IPayload.Action[] memory actions = _payload.getActions();

for (uint256 i = 0; i < actions.length; i++) {
// Allow failure of individual calls but emit the failure!
(bool success, bytes memory returnData) = actions[i].target.call(actions[i].data);
if (!success) {
emit SlashFailed(actions[i].target, actions[i].data, returnData);
}
}

return true;
}
}
35 changes: 35 additions & 0 deletions l1-contracts/src/core/staking/SlashingProposer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;

import {ISlasher} from "@aztec/core/interfaces/ISlasher.sol";
import {IGovernanceProposer} from "@aztec/governance/interfaces/IGovernanceProposer.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
import {EmpireBase} from "@aztec/governance/proposer/EmpireBase.sol";

/**
* @notice A SlashingProposer implementation following the empire model
*/
contract SlashingProposer is IGovernanceProposer, EmpireBase {
address public immutable INSTANCE;
ISlasher public immutable SLASHER;

constructor(address _instance, ISlasher _slasher, uint256 _slashingQuorum, uint256 _roundSize)
EmpireBase(_slashingQuorum, _roundSize)
{
INSTANCE = _instance;
SLASHER = _slasher;
}

function getExecutor() public view override(EmpireBase, IGovernanceProposer) returns (address) {
return address(SLASHER);
}

function getInstance() public view override(EmpireBase, IGovernanceProposer) returns (address) {
return INSTANCE;
}

function _execute(IPayload _proposal) internal override(EmpireBase) returns (bool) {
return SLASHER.slash(_proposal);
}
}
16 changes: 12 additions & 4 deletions l1-contracts/src/core/staking/Staking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "@aztec/core/interfaces/IStaking.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {Timestamp} from "@aztec/core/libraries/TimeMath.sol";
import {Slasher} from "@aztec/core/staking/Slasher.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";
import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol";
Expand All @@ -23,14 +24,19 @@ contract Staking is IStaking {
// Constant pulled out of the ass
Timestamp public constant EXIT_DELAY = Timestamp.wrap(60 * 60 * 24);

address public immutable SLASHER;
Slasher public immutable SLASHER;
IERC20 public immutable STAKING_ASSET;
uint256 public immutable MINIMUM_STAKE;

StakingStorage internal stakingStore;

constructor(address _slasher, IERC20 _stakingAsset, uint256 _minimumStake) {
SLASHER = _slasher;
constructor(
IERC20 _stakingAsset,
uint256 _minimumStake,
uint256 _slashingQuorum,
uint256 _roundSize
) {
SLASHER = new Slasher(_slashingQuorum, _roundSize);
STAKING_ASSET = _stakingAsset;
MINIMUM_STAKE = _minimumStake;
}
Expand All @@ -57,7 +63,9 @@ contract Staking is IStaking {
}

function slash(address _attester, uint256 _amount) external override(IStaking) {
require(msg.sender == SLASHER, Errors.Staking__NotSlasher(SLASHER, msg.sender));
require(
msg.sender == address(SLASHER), Errors.Staking__NotSlasher(address(SLASHER), msg.sender)
);

ValidatorInfo storage validator = stakingStore.info[_attester];
require(validator.status != Status.NONE, Errors.Staking__NoOneToSlash(_attester));
Expand Down
2 changes: 1 addition & 1 deletion l1-contracts/src/governance/CoinIssuer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract CoinIssuer is ICoinIssuer, Ownable {
*/
function mint(address _to, uint256 _amount) external override(ICoinIssuer) onlyOwner {
uint256 maxMint = mintAvailable();
require(_amount <= maxMint, Errors.CoinIssuer__InssuficientMintAvailable(maxMint, _amount));
require(_amount <= maxMint, Errors.CoinIssuer__InsufficientMintAvailable(maxMint, _amount));
timeOfLastMint = block.timestamp;
ASSET.mint(_to, _amount);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
pragma solidity >=0.8.27;

import {Slot} from "@aztec/core/libraries/TimeMath.sol";
import {IGovernance} from "@aztec/governance/interfaces/IGovernance.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";

interface IGovernanceProposer {
event VoteCast(IPayload indexed proposal, uint256 indexed round, address indexed voter);
event ProposalPushed(IPayload indexed proposal, uint256 indexed round);

function vote(IPayload _proposa) external returns (bool);
function vote(IPayload _proposal) external returns (bool);
function pushProposal(uint256 _roundNumber) external returns (bool);
function yeaCount(address _instance, uint256 _round, IPayload _proposal)
external
view
returns (uint256);
function computeRound(Slot _slot) external view returns (uint256);
function getGovernance() external view returns (IGovernance);
function getInstance() external view returns (address);
function getExecutor() external view returns (address);
}
18 changes: 9 additions & 9 deletions l1-contracts/src/governance/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,20 @@ library Errors {
error Governance__ProposalLib__ZeroYeaVotesNeeded();
error Governance__ProposalLib__MoreYeaVoteThanExistNeeded();

error GovernanceProposer__CanOnlyPushProposalInPast(); // 0x49fdf611"
error GovernanceProposer__FailedToPropose(IPayload proposal); // 0x6ca2a2ed
error GovernanceProposer__InstanceHaveNoCode(address instance); // 0x20a3b441
error GovernanceProposer__InsufficientVotes(); // 0xba1e05ef
error GovernanceProposer__CanOnlyPushProposalInPast(); // 0x84a5b5ae
error GovernanceProposer__FailedToPropose(IPayload proposal); // 0x8d94fbfc
error GovernanceProposer__InstanceHaveNoCode(address instance); // 0x5fa92625
error GovernanceProposer__InsufficientVotes(uint256 votesCast, uint256 votesNeeded); // 0xd4ad89c2
error GovernanceProposer__InvalidNAndMValues(uint256 n, uint256 m); // 0x520d9704
error GovernanceProposer__NCannotBeLargerTHanM(uint256 n, uint256 m); // 0x2fdfc063
error GovernanceProposer__OnlyProposerCanVote(address caller, address proposer); // 0xba27df38
error GovernanceProposer__ProposalAlreadyExecuted(uint256 roundNumber); // 0x7aeacb17
error GovernanceProposer__ProposalCannotBeAddressZero(); // 0xdb3e4b6e
error GovernanceProposer__ProposalHaveNoCode(IPayload proposal); // 0xdce0615b
error GovernanceProposer__ProposalTooOld(uint256 roundNumber, uint256 currentRoundNumber); //0x02283b1a
error GovernanceProposer__VoteAlreadyCastForSlot(Slot slot); //0xc2201452
error GovernanceProposer__ProposalCannotBeAddressZero(); // 0x16ac1942
error GovernanceProposer__ProposalHaveNoCode(IPayload proposal); // 0xb69440a1
error GovernanceProposer__ProposalTooOld(uint256 roundNumber, uint256 currentRoundNumber); // 0xc3d7aa4f
error GovernanceProposer__VoteAlreadyCastForSlot(Slot slot); // 0x3a6150ca

error CoinIssuer__InssuficientMintAvailable(uint256 available, uint256 needed); // 0xf268b931
error CoinIssuer__InsufficientMintAvailable(uint256 available, uint256 needed); // 0xa1cc8799

error Registry__RollupAlreadyRegistered(address rollup); // 0x3c34eabf
error Registry__RollupNotRegistered(address rollup); // 0xa1fee4cf
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ pragma solidity >=0.8.27;

import {ILeonidas} from "@aztec/core/interfaces/ILeonidas.sol";
import {Slot, SlotLib} from "@aztec/core/libraries/TimeMath.sol";
import {IGovernance} from "@aztec/governance/interfaces/IGovernance.sol";
import {IGovernanceProposer} from "@aztec/governance/interfaces/IGovernanceProposer.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol";
import {Errors} from "@aztec/governance/libraries/Errors.sol";

/**
Expand All @@ -17,7 +15,7 @@ import {Errors} from "@aztec/governance/libraries/Errors.sol";
* This also means that the implementation here will need to be "updated" if
* the interfaces of the sequencer selection changes, for example going optimistic.
*/
contract GovernanceProposer is IGovernanceProposer {
abstract contract EmpireBase is IGovernanceProposer {
using SlotLib for Slot;

struct RoundAccounting {
Expand All @@ -29,14 +27,12 @@ contract GovernanceProposer is IGovernanceProposer {

uint256 public constant LIFETIME_IN_ROUNDS = 5;

IRegistry public immutable REGISTRY;
uint256 public immutable N;
uint256 public immutable M;

mapping(address instance => mapping(uint256 roundNumber => RoundAccounting)) public rounds;

constructor(IRegistry _registry, uint256 _n, uint256 _m) {
REGISTRY = _registry;
constructor(uint256 _n, uint256 _m) {
N = _n;
M = _m;

Expand All @@ -57,11 +53,12 @@ contract GovernanceProposer is IGovernanceProposer {
* @return True if executed successfully, false otherwise
*/
function vote(IPayload _proposal) external override(IGovernanceProposer) returns (bool) {
require(
// For now, skipping this as the check is not really needed but there were not full agreement
/*require(
address(_proposal).code.length > 0, Errors.GovernanceProposer__ProposalHaveNoCode(_proposal)
);
);*/

address instance = REGISTRY.getRollup();
address instance = getInstance();
require(instance.code.length > 0, Errors.GovernanceProposer__InstanceHaveNoCode(instance));

ILeonidas selection = ILeonidas(instance);
Expand Down Expand Up @@ -102,7 +99,7 @@ contract GovernanceProposer is IGovernanceProposer {
*/
function pushProposal(uint256 _roundNumber) external override(IGovernanceProposer) returns (bool) {
// Need to ensure that the round is not active.
address instance = REGISTRY.getRollup();
address instance = getInstance();
require(instance.code.length > 0, Errors.GovernanceProposer__InstanceHaveNoCode(instance));

ILeonidas selection = ILeonidas(instance);
Expand All @@ -120,16 +117,14 @@ contract GovernanceProposer is IGovernanceProposer {
require(
round.leader != IPayload(address(0)), Errors.GovernanceProposer__ProposalCannotBeAddressZero()
);
require(round.yeaCount[round.leader] >= N, Errors.GovernanceProposer__InsufficientVotes());
uint256 votesCast = round.yeaCount[round.leader];
require(votesCast >= N, Errors.GovernanceProposer__InsufficientVotes(votesCast, N));

round.executed = true;

emit ProposalPushed(round.leader, _roundNumber);

require(
getGovernance().propose(round.leader),
Errors.GovernanceProposer__FailedToPropose(round.leader)
);
require(_execute(round.leader), Errors.GovernanceProposer__FailedToPropose(round.leader));
return true;
}

Expand Down Expand Up @@ -162,7 +157,8 @@ contract GovernanceProposer is IGovernanceProposer {
return _slot.unwrap() / M;
}

function getGovernance() public view override(IGovernanceProposer) returns (IGovernance) {
return IGovernance(REGISTRY.getGovernance());
}
// Virtual functions
function getInstance() public view virtual override(IGovernanceProposer) returns (address);
function getExecutor() public view virtual override(IGovernanceProposer) returns (address);
function _execute(IPayload _proposal) internal virtual returns (bool);
}
36 changes: 36 additions & 0 deletions l1-contracts/src/governance/proposer/GovernanceProposer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;

import {IGovernance} from "@aztec/governance/interfaces/IGovernance.sol";
import {IGovernanceProposer} from "@aztec/governance/interfaces/IGovernanceProposer.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol";
import {EmpireBase} from "./EmpireBase.sol";

/**
* @notice A GovernanceProposer implementation following the empire model
* Beware that while governance generally do not care about the implementation
* this implementation will since it is dependent on the sequencer selection.
* This also means that the implementation here will need to be "updated" if
* the interfaces of the sequencer selection changes, for example going optimistic.
*/
contract GovernanceProposer is IGovernanceProposer, EmpireBase {
IRegistry public immutable REGISTRY;

constructor(IRegistry _registry, uint256 _n, uint256 _m) EmpireBase(_n, _m) {
REGISTRY = _registry;
}

function getExecutor() public view override(EmpireBase, IGovernanceProposer) returns (address) {
return REGISTRY.getGovernance();
}

function getInstance() public view override(EmpireBase, IGovernanceProposer) returns (address) {
return REGISTRY.getRollup();
}

function _execute(IPayload _proposal) internal override(EmpireBase) returns (bool) {
return IGovernance(getExecutor()).propose(_proposal);
}
}
Loading

0 comments on commit 2409751

Please sign in to comment.