Skip to content

Commit

Permalink
feat: staking integration (#10403)
Browse files Browse the repository at this point in the history
  • Loading branch information
LHerskind authored Dec 5, 2024
1 parent c643a54 commit ecd6c4f
Show file tree
Hide file tree
Showing 59 changed files with 613 additions and 400 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/devnet-deploys.yml
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ jobs:
echo "TF_VAR_INBOX_CONTRACT_ADDRESS=$(extract inboxAddress)" >>$GITHUB_ENV
echo "TF_VAR_OUTBOX_CONTRACT_ADDRESS=$(extract outboxAddress)" >>$GITHUB_ENV
echo "TF_VAR_FEE_JUICE_CONTRACT_ADDRESS=$(extract feeJuiceAddress)" >>$GITHUB_ENV
echo "TF_VAR_STAKING_ASSET_CONTRACT_ADDRESS=$(extract stakingAssetAddress)" >>$GITHUB_ENV
echo "TF_VAR_FEE_JUICE_PORTAL_CONTRACT_ADDRESS=$(extract feeJuicePortalAddress)" >>$GITHUB_ENV
- name: Apply l1-contracts Terraform
Expand Down Expand Up @@ -678,6 +679,7 @@ jobs:
aws s3 cp ${{ env.CONTRACT_S3_BUCKET }}/${{ env.DEPLOY_TAG }}/basic_contracts.json ./basic_contracts.json
echo "TF_VAR_FEE_JUICE_CONTRACT_ADDRESS=$(jq -r '.feeJuiceAddress' ./l1_contracts.json)" >>$GITHUB_ENV
echo "TF_VAR_STAKING_ASSET_CONTRACT_ADDRESS=$(jq -r '.stakingAssetAddress' ./l1_contracts.json)" >>$GITHUB_ENV
echo "TF_VAR_DEV_COIN_CONTRACT_ADDRESS=$(jq -r '.devCoinL1' ./basic_contracts.json)" >>$GITHUB_ENV
- name: Deploy Faucet
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/sepolia-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ jobs:
echo "TF_VAR_OUTBOX_CONTRACT_ADDRESS=$(extract outboxAddress)" >>$GITHUB_ENV
echo "TF_VAR_AVAILABILITY_ORACLE_CONTRACT_ADDRESS=$(extract availabilityOracleAddress)" >>$GITHUB_ENV
echo "TF_VAR_FEE_JUICE_CONTRACT_ADDRESS=$(extract feeJuiceAddress)" >>$GITHUB_ENV
echo "TF_VAR_STAKING_ASSET_CONTRACT_ADDRESS=$(extract stakingAssetAddress)" >>$GITHUB_ENV
echo "TF_VAR_FEE_JUICE_PORTAL_CONTRACT_ADDRESS=$(extract feeJuicePortalAddress)" >>$GITHUB_ENV
- name: Apply l1-contracts Terraform
Expand Down
155 changes: 70 additions & 85 deletions l1-contracts/src/core/Leonidas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ pragma solidity >=0.8.27;
import {ILeonidas, EpochData, LeonidasStorage} from "@aztec/core/interfaces/ILeonidas.sol";
import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol";
import {DataStructures} from "@aztec/core/libraries/DataStructures.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {LeonidasLib} from "@aztec/core/libraries/LeonidasLib/LeonidasLib.sol";
import {
Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeFns
} from "@aztec/core/libraries/TimeMath.sol";
import {Ownable} from "@oz/access/Ownable.sol";
import {Staking} from "@aztec/core/staking/Staking.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol";

/**
Expand All @@ -19,16 +21,13 @@ import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol";
* He define the structure needed for committee and leader selection and provides logic for validating that
* the block and its "evidence" follows his rules.
*
* @dev Leonidas is depending on Ares to add/remove warriors to/from his army competently.
*
* @dev Leonidas have one thing in mind, he provide a reference of the LOGIC going on for the spartan selection.
* He is not concerned about gas costs, he is a king, he just throw gas in the air like no-one cares.
* It will be the duty of his successor (Pleistarchus) to optimize the costs with same functionality.
*
*/
contract Leonidas is Ownable, TimeFns, ILeonidas {
contract Leonidas is Staking, TimeFns, ILeonidas {
using EnumerableSet for EnumerableSet.AddressSet;
using LeonidasLib for LeonidasStorage;

using SlotLib for Slot;
using EpochLib for Epoch;
Expand All @@ -40,50 +39,22 @@ contract Leonidas is Ownable, TimeFns, ILeonidas {
// The time that the contract was deployed
Timestamp public immutable GENESIS_TIME;

LeonidasStorage private store;
LeonidasStorage private leonidasStore;

constructor(
address _ares,
IERC20 _stakingAsset,
uint256 _minimumStake,
uint256 _slotDuration,
uint256 _epochDuration,
uint256 _targetCommitteeSize
) Ownable(_ares) TimeFns(_slotDuration, _epochDuration) {
) Staking(_ares, _stakingAsset, _minimumStake) TimeFns(_slotDuration, _epochDuration) {
GENESIS_TIME = Timestamp.wrap(block.timestamp);
SLOT_DURATION = _slotDuration;
EPOCH_DURATION = _epochDuration;
TARGET_COMMITTEE_SIZE = _targetCommitteeSize;
}

/**
* @notice Adds a validator to the validator set
*
* @dev Only ARES can add validators
*
* @dev Will setup the epoch if needed BEFORE adding the validator.
* This means that the validator will effectively be added to the NEXT epoch.
*
* @param _validator - The validator to add
*/
function addValidator(address _validator) external override(ILeonidas) onlyOwner {
setupEpoch();
_addValidator(_validator);
}

/**
* @notice Removes a validator from the validator set
*
* @dev Only ARES can add validators
*
* @dev Will setup the epoch if needed BEFORE removing the validator.
* This means that the validator will effectively be removed from the NEXT epoch.
*
* @param _validator - The validator to remove
*/
function removeValidator(address _validator) external override(ILeonidas) onlyOwner {
setupEpoch();
store.validatorSet.remove(_validator);
}

/**
* @notice Get the validator set for a given epoch
*
Expand All @@ -99,26 +70,40 @@ contract Leonidas is Ownable, TimeFns, ILeonidas {
override(ILeonidas)
returns (address[] memory)
{
return store.epochs[_epoch].committee;
return leonidasStore.epochs[_epoch].committee;
}

/**
* @notice Get the validator set for the current epoch
* @return The validator set for the current epoch
*/
function getCurrentEpochCommittee() external view override(ILeonidas) returns (address[] memory) {
return store.getCommitteeAt(getCurrentEpoch(), TARGET_COMMITTEE_SIZE);
return LeonidasLib.getCommitteeAt(
leonidasStore, stakingStore, getCurrentEpoch(), TARGET_COMMITTEE_SIZE
);
}

/**
* @notice Get the validator set
*
* @dev Consider removing this to replace with a `size` and individual getter.
*
* @return The validator set
*/
function getValidators() external view override(ILeonidas) returns (address[] memory) {
return store.validatorSet.values();
function deposit(address _attester, address _proposer, address _withdrawer, uint256 _amount)
public
override(Staking)
{
setupEpoch();
require(
_attester != address(0) && _proposer != address(0),
Errors.Leonidas__InvalidDeposit(_attester, _proposer)
);
super.deposit(_attester, _proposer, _withdrawer, _amount);
}

function initiateWithdraw(address _attester, address _recipient)
public
override(Staking)
returns (bool)
{
// @note The attester might be chosen for the epoch, so the delay must be long enough
// to allow for that.
setupEpoch();
return super.initiateWithdraw(_attester, _recipient);
}

/**
Expand All @@ -127,49 +112,31 @@ contract Leonidas is Ownable, TimeFns, ILeonidas {
* - Set the seed for the epoch
* - Update the last seed
*
* @dev Since this is a reference optimising for simplicity, we store the actual validator set in the epoch structure.
* @dev Since this is a reference optimising for simplicity, we leonidasStore the actual validator set in the epoch structure.
* This is very heavy on gas, so start crying because the gas here will melt the poles
* https://i.giphy.com/U1aN4HTfJ2SmgB2BBK.webp
*/
function setupEpoch() public override(ILeonidas) {
Epoch epochNumber = getCurrentEpoch();
EpochData storage epoch = store.epochs[epochNumber];
EpochData storage epoch = leonidasStore.epochs[epochNumber];

if (epoch.sampleSeed == 0) {
epoch.sampleSeed = store.getSampleSeed(epochNumber);
epoch.nextSeed = store.lastSeed = _computeNextSeed(epochNumber);

epoch.committee = store.sampleValidators(epoch.sampleSeed, TARGET_COMMITTEE_SIZE);
epoch.sampleSeed = LeonidasLib.getSampleSeed(leonidasStore, epochNumber);
epoch.nextSeed = leonidasStore.lastSeed = _computeNextSeed(epochNumber);
epoch.committee =
LeonidasLib.sampleValidators(stakingStore, epoch.sampleSeed, TARGET_COMMITTEE_SIZE);
}
}

/**
* @notice Get the number of validators in the validator set
* @notice Get the attester set
*
* @return The number of validators in the validator set
*/
function getValidatorCount() public view override(ILeonidas) returns (uint256) {
return store.validatorSet.length();
}

/**
* @notice Get the number of validators in the validator set
*
* @return The number of validators in the validator set
*/
function getValidatorAt(uint256 _index) public view override(ILeonidas) returns (address) {
return store.validatorSet.at(_index);
}

/**
* @notice Checks if an address is in the validator set
*
* @param _validator - The address to check
* @dev Consider removing this to replace with a `size` and individual getter.
*
* @return True if the address is in the validator set, false otherwise
* @return The validator set
*/
function isValidator(address _validator) public view override(ILeonidas) returns (bool) {
return store.validatorSet.contains(_validator);
function getAttesters() public view override(ILeonidas) returns (address[] memory) {
return stakingStore.attesters.values();
}

/**
Expand Down Expand Up @@ -241,7 +208,9 @@ contract Leonidas is Ownable, TimeFns, ILeonidas {
function getProposerAt(Timestamp _ts) public view override(ILeonidas) returns (address) {
Slot slot = getSlotAt(_ts);
Epoch epochNumber = getEpochAtSlot(slot);
return store.getProposerAt(slot, epochNumber, TARGET_COMMITTEE_SIZE);
return LeonidasLib.getProposerAt(
leonidasStore, stakingStore, slot, epochNumber, TARGET_COMMITTEE_SIZE
);
}

/**
Expand Down Expand Up @@ -277,12 +246,19 @@ contract Leonidas is Ownable, TimeFns, ILeonidas {
return Epoch.wrap(_slotNumber.unwrap() / EPOCH_DURATION);
}

/**
* @notice Adds a validator to the set WITHOUT setting up the epoch
* @param _validator - The validator to add
*/
function _addValidator(address _validator) internal {
store.validatorSet.add(_validator);
// Can be used to add validators without setting up the epoch, useful for the initial set.
function _cheat__Deposit(
address _attester,
address _proposer,
address _withdrawer,
uint256 _amount
) internal {
require(
_attester != address(0) && _proposer != address(0),
Errors.Leonidas__InvalidDeposit(_attester, _proposer)
);

super.deposit(_attester, _proposer, _withdrawer, _amount);
}

/**
Expand All @@ -308,7 +284,16 @@ contract Leonidas is Ownable, TimeFns, ILeonidas {
DataStructures.ExecutionFlags memory _flags
) internal view {
Epoch epochNumber = getEpochAtSlot(_slot);
store.validateLeonidas(_slot, epochNumber, _signatures, _digest, _flags, TARGET_COMMITTEE_SIZE);
LeonidasLib.validateLeonidas(
leonidasStore,
stakingStore,
_slot,
epochNumber,
_signatures,
_digest,
_flags,
TARGET_COMMITTEE_SIZE
);
}

/**
Expand Down
21 changes: 17 additions & 4 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEsc
import {
IRollup,
ITestRollup,
CheatDepositArgs,
FeeHeader,
ManaBaseFeeComponents,
BlockLog,
Expand Down Expand Up @@ -40,6 +41,7 @@ import {Outbox} from "@aztec/core/messagebridge/Outbox.sol";
import {ProofCommitmentEscrow} from "@aztec/core/ProofCommitmentEscrow.sol";
import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol";
import {MockVerifier} from "@aztec/mock/MockVerifier.sol";
import {Ownable} from "@oz/access/Ownable.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {EIP712} from "@oz/utils/cryptography/EIP712.sol";
import {Vm} from "forge-std/Vm.sol";
Expand All @@ -49,6 +51,7 @@ struct Config {
uint256 aztecEpochDuration;
uint256 targetCommitteeSize;
uint256 aztecEpochProofClaimWindowInL2Slots;
uint256 minimumStake;
}

/**
Expand All @@ -57,7 +60,7 @@ struct Config {
* @notice Rollup contract that is concerned about readability and velocity of development
* not giving a damn about gas costs.
*/
contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, Leonidas, IRollup, ITestRollup {
using SlotLib for Slot;
using EpochLib for Epoch;
using ProposeLib for ProposeArgs;
Expand Down Expand Up @@ -97,14 +100,17 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
constructor(
IFeeJuicePortal _fpcJuicePortal,
IRewardDistributor _rewardDistributor,
IERC20 _stakingAsset,
bytes32 _vkTreeRoot,
bytes32 _protocolContractTreeRoot,
address _ares,
address[] memory _validators,
Config memory _config
)
Ownable(_ares)
Leonidas(
_ares,
_stakingAsset,
_config.minimumStake,
_config.aztecSlotDuration,
_config.aztecEpochDuration,
_config.targetCommitteeSize
Expand Down Expand Up @@ -145,8 +151,15 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
post: L1FeeData({baseFee: block.basefee, blobFee: _getBlobBaseFee()}),
slotOfChange: LIFETIME
});
for (uint256 i = 0; i < _validators.length; i++) {
_addValidator(_validators[i]);
}

function cheat__InitialiseValidatorSet(CheatDepositArgs[] memory _args)
external
override(ITestRollup)
onlyOwner
{
for (uint256 i = 0; i < _args.length; i++) {
_cheat__Deposit(_args[i].attester, _args[i].proposer, _args[i].withdrawer, _args[i].amount);
}
setupEpoch();
}
Expand Down
Loading

0 comments on commit ecd6c4f

Please sign in to comment.