Feature Tip: Add private address tag to any address under My Name Tag !
Source Code
Overview
ETH Balance
0 ETH
Eth Value
$0.00
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
There are no matching entriesUpdate your filters to view other transactions | |||||||||
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
TGEPayload
Compiler Version
v0.8.30+commit.73712a01
Optimization Enabled:
Yes with 200 runs
Other Settings:
prague EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.30;
import {IRegistry as IATPRegistry} from "@atp/Registry.sol";
import {IRollupCore} from "@aztec/core/interfaces/IRollup.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
import {IDateGatedRelayer} from "@aztec/periphery/interfaces/IDateGatedRelayer.sol";
import {Ownable2Step} from "@oz/access/Ownable2Step.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {StakingRegistry} from "src/staking-registry/StakingRegistry.sol";
import {ATPWithdrawableAndClaimableStakerV2, IRegistry} from "src/tge/ATPWithdrawableAndClaimableStakerV2.sol";
import {GovernanceAcceleratedLock} from "src/uniswap-periphery/GovernanceAcceleratedLock.sol";
import {IVirtualLBPStrategyBasic} from "src/uniswap-periphery/IVirtualLBPStrategyBasic.sol";
contract TGEPayload is IPayload {
IATPRegistry public constant ATP_REGISTRY = IATPRegistry(0x63841bAD6B35b6419e15cA9bBBbDf446D4dC3dde);
IVirtualLBPStrategyBasic public constant VIRTUAL_LBP_STRATEGY =
IVirtualLBPStrategyBasic(0xd53006d1e3110fD319a79AEEc4c527a0d265E080);
IRegistry public constant ROLLUP_REGISTRY = IRegistry(0x35b22e09Ee0390539439E24f06Da43D83f90e298);
IERC20 public constant AZTEC_TOKEN = IERC20(0xA27EC0006e59f245217Ff08CD52A7E8b169E62D2);
StakingRegistry public constant STAKING_REGISTRY = StakingRegistry(0x042dF8f42790d6943F41C25C2132400fd727f452);
address public constant DATE_GATED_RELAYER_SHORT = 0x7d6DECF157E1329A20c4596eAf78D387E896aa4e;
address public constant ROLLUP = 0x603bb2c05D474794ea97805e8De69bCcFb3bCA12;
// Jan 1, 2026 00:00:00 CET (Dec 31, 2025 23:00:00 UTC) - a Thursday
uint256 public constant JAN_1_2026_CET = 1767222000;
// Day of week constants (Monday = 0, Sunday = 6)
uint256 internal constant MONDAY = 0;
uint256 internal constant TUESDAY = 1;
uint256 internal constant WEDNESDAY = 2;
uint256 internal constant THURSDAY = 3;
uint256 internal constant FRIDAY = 4;
uint256 internal constant SATURDAY = 5;
uint256 internal constant SUNDAY = 6;
// Configurable business hours (CET)
uint256 public constant START_OF_WORKDAY = 8 hours;
uint256 public constant END_OF_WORKDAY = 15 hours;
uint256 public constant START_DAY = TUESDAY;
uint256 public constant END_DAY = THURSDAY;
ATPWithdrawableAndClaimableStakerV2 public immutable STAKER;
error OutsideBusinessHours(uint256 secondsSinceMidnightCET, uint256 dayOfWeek);
modifier inBusinessHours() {
// Constraint: Since it opens up for trading, and will be aligned with centralized exchanges,
// execution should only happen during configured business hours and days (in CET)
uint256 timeSinceReference = block.timestamp - JAN_1_2026_CET;
uint256 secondsSinceMidnight = timeSinceReference % 1 days;
// Calculate day of week with Monday=0, Sunday=6
// Jan 1, 2026 was a Thursday (day 3 in Monday=0), so we add 3
uint256 daysSinceReference = timeSinceReference / 1 days;
uint256 dayOfWeek = (daysSinceReference + 3) % 7;
require(
secondsSinceMidnight >= START_OF_WORKDAY && secondsSinceMidnight < END_OF_WORKDAY && dayOfWeek >= START_DAY
&& dayOfWeek <= END_DAY,
OutsideBusinessHours(secondsSinceMidnight, dayOfWeek)
);
_;
}
constructor() {
STAKER =
new ATPWithdrawableAndClaimableStakerV2(AZTEC_TOKEN, ROLLUP_REGISTRY, STAKING_REGISTRY, block.timestamp);
}
function getActions() external view override(IPayload) inBusinessHours returns (IPayload.Action[] memory) {
// [ ] 0. Accelerate the lock
// [ ] 1. Accept the ownership
// [ ] 2. Set the unlock start time
// [ ] 3. Register the staker
// [ ] 4. Approve the migration (allow trading)
// [ ] 5. Make rewards claimable
IPayload.Action[] memory actions = new IPayload.Action[](6);
actions[0] = IPayload.Action({
target: DATE_GATED_RELAYER_SHORT,
data: abi.encodeWithSelector(GovernanceAcceleratedLock.accelerateLock.selector)
});
actions[1] = IPayload.Action({
target: DATE_GATED_RELAYER_SHORT,
data: abi.encodeWithSelector(
IDateGatedRelayer.relay.selector,
address(ATP_REGISTRY),
abi.encodeWithSelector(Ownable2Step.acceptOwnership.selector)
)
});
actions[2] = IPayload.Action({
target: DATE_GATED_RELAYER_SHORT,
data: abi.encodeWithSelector(
IDateGatedRelayer.relay.selector,
address(ATP_REGISTRY),
abi.encodeWithSelector(IATPRegistry.setUnlockStartTime.selector, block.timestamp - 365 days)
)
});
actions[3] = IPayload.Action({
target: DATE_GATED_RELAYER_SHORT,
data: abi.encodeWithSelector(
IDateGatedRelayer.relay.selector,
address(ATP_REGISTRY),
abi.encodeWithSelector(IATPRegistry.registerStakerImplementation.selector, address(STAKER))
)
});
actions[4] = IPayload.Action({
target: DATE_GATED_RELAYER_SHORT,
data: abi.encodeWithSelector(
IDateGatedRelayer.relay.selector,
address(VIRTUAL_LBP_STRATEGY),
abi.encodeWithSelector(IVirtualLBPStrategyBasic.approveMigration.selector)
)
});
actions[5] = IPayload.Action({
target: ROLLUP, data: abi.encodeWithSelector(IRollupCore.setRewardsClaimable.selector, true)
});
return actions;
}
function getURI() external pure override(IPayload) returns (string memory) {
return "https://github.com/AztecProtocol/ignition-contracts/";
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {Ownable2Step, Ownable} from "@oz/access/Ownable2Step.sol";
import {UUPSUpgradeable, ERC1967Utils} from "@oz/proxy/utils/UUPSUpgradeable.sol";
import {LockParams} from "./libraries/LockLib.sol";
import {BaseStaker} from "./staker/BaseStaker.sol";
type MilestoneId is uint96;
type StakerVersion is uint256;
enum MilestoneStatus {
Pending,
Failed,
Succeeded
}
interface IRegistry {
event UpdatedRevoker(address revoker);
event UpdatedRevokerOperator(address revokerOperator);
event UpdatedExecuteAllowedAt(uint256 executeAllowedAt);
event UpdatedUnlockStartTime(uint256 unlockStartTime);
event StakerRegistered(StakerVersion version, address implementation);
event MilestoneAdded(MilestoneId milestoneId);
event MilestoneStatusUpdated(MilestoneId milestoneId, MilestoneStatus status);
error InvalidExecuteAllowedAt(uint256 newExecuteAllowedAt, uint256 currentExecuteAllowedAt);
error InvalidUnlockStartTime(uint256 newUnlockStartTime, uint256 currentUnlockStartTime);
error InvalidUnlockDuration();
error InvalidUnlockCliffDuration();
error InvalidStakerImplementation(address implementation);
error UnRegisteredStaker(StakerVersion version);
error InvalidMilestoneId(MilestoneId milestoneId);
error InvalidMilestoneStatus(MilestoneId milestoneId);
function setRevoker(address _revoker) external;
function setRevokerOperator(address _revokerOperator) external;
function setExecuteAllowedAt(uint256 _executeAllowedAt) external;
function setUnlockStartTime(uint256 _unlockStartTime) external;
function registerStakerImplementation(address _implementation) external;
function addMilestone() external returns (MilestoneId);
function setMilestoneStatus(MilestoneId _milestoneId, MilestoneStatus _status) external;
function getRevoker() external view returns (address);
function getRevokerOperator() external view returns (address);
function getExecuteAllowedAt() external view returns (uint256);
function getUnlockStartTime() external view returns (uint256);
function getGlobalLockParams() external view returns (LockParams memory);
function getStakerImplementation(StakerVersion _version) external view returns (address);
function getNextStakerVersion() external view returns (StakerVersion);
function getMilestoneStatus(MilestoneId _milestoneId) external view returns (MilestoneStatus);
function getNextMilestoneId() external view returns (MilestoneId);
}
contract Registry is Ownable2Step, IRegistry {
uint256 internal immutable UNLOCK_CLIFF_DURATION;
uint256 internal immutable UNLOCK_LOCK_DURATION;
// @note An initial value set to be the unix timestamp of 1st of January 2027
uint256 internal unlockStartTime = 1798761600;
uint256 internal executeAllowedAt = 1798761600;
address internal revoker;
address internal revokerOperator;
StakerVersion internal nextStakerVersion;
mapping(StakerVersion version => address implementation) internal stakerImplementations;
MilestoneId internal nextMilestoneId;
mapping(MilestoneId milestoneId => MilestoneStatus status) internal milestones;
constructor(address __owner, uint256 _unlockCliffDuration, uint256 _unlockLockDuration) Ownable(__owner) {
require(_unlockLockDuration > 0, InvalidUnlockDuration());
require(_unlockLockDuration >= _unlockCliffDuration, InvalidUnlockCliffDuration());
UNLOCK_CLIFF_DURATION = _unlockCliffDuration;
UNLOCK_LOCK_DURATION = _unlockLockDuration;
// @note Register the base staker implementation
stakerImplementations[StakerVersion.wrap(0)] = address(new BaseStaker());
nextStakerVersion = StakerVersion.wrap(1);
}
/**
* @notice Add a new milestone
*
* @dev Only callable by the owner
*
* @return The milestone id
*/
function addMilestone() external override(IRegistry) onlyOwner returns (MilestoneId) {
MilestoneId milestoneId = nextMilestoneId;
nextMilestoneId = MilestoneId.wrap(MilestoneId.unwrap(nextMilestoneId) + 1);
milestones[milestoneId] = MilestoneStatus.Pending; // To be explicit
emit MilestoneAdded(milestoneId);
return milestoneId;
}
function setMilestoneStatus(MilestoneId _milestoneId, MilestoneStatus _status)
external
override(IRegistry)
onlyOwner
{
require(getMilestoneStatus(_milestoneId) == MilestoneStatus.Pending, InvalidMilestoneStatus(_milestoneId));
require(_status != MilestoneStatus.Pending, InvalidMilestoneStatus(_milestoneId));
milestones[_milestoneId] = _status;
emit MilestoneStatusUpdated(_milestoneId, _status);
}
/**
* @notice Register a new staker implementation
*
* @dev Only callable by the owner
*
* @param _implementation The address of the staker implementation
*/
function registerStakerImplementation(address _implementation) external override(IRegistry) onlyOwner {
require(
UUPSUpgradeable(_implementation).proxiableUUID() == ERC1967Utils.IMPLEMENTATION_SLOT,
InvalidStakerImplementation(_implementation)
);
StakerVersion version = nextStakerVersion;
nextStakerVersion = StakerVersion.wrap(StakerVersion.unwrap(nextStakerVersion) + 1);
stakerImplementations[version] = _implementation;
emit StakerRegistered(version, _implementation);
}
/**
* @notice Set the revoker address
*
* @dev Only callable by the owner
*
* @param _revoker The address of the revoker
*/
function setRevoker(address _revoker) external override(IRegistry) onlyOwner {
revoker = _revoker;
emit UpdatedRevoker(_revoker);
}
function setRevokerOperator(address _revokerOperator) external override(IRegistry) onlyOwner {
revokerOperator = _revokerOperator;
emit UpdatedRevokerOperator(_revokerOperator);
}
/**
* @notice Set the execute allowed at timestamp
* Can only be decreased to avoid unintentional updates and give some guarantees to LATP beneficiaries
*
* @dev Only callable by the owner
*
* @param _executeAllowedAt The timestamp of when the execute is allowed
*/
function setExecuteAllowedAt(uint256 _executeAllowedAt) external override(IRegistry) onlyOwner {
require(_executeAllowedAt < executeAllowedAt, InvalidExecuteAllowedAt(_executeAllowedAt, executeAllowedAt));
executeAllowedAt = _executeAllowedAt;
emit UpdatedExecuteAllowedAt(_executeAllowedAt);
}
/**
* @notice Set the unlock start time
* Can only be decreased to avoid unintentional updates and give some guarantees to LATP beneficiaries
*
* @dev Only callable by the owner
*
* @param _unlockStartTime The timestamp of when the unlock starts
*/
function setUnlockStartTime(uint256 _unlockStartTime) external override(IRegistry) onlyOwner {
require(_unlockStartTime < unlockStartTime, InvalidUnlockStartTime(_unlockStartTime, unlockStartTime));
unlockStartTime = _unlockStartTime;
emit UpdatedUnlockStartTime(_unlockStartTime);
}
/**
* @notice Get the revoker address
*
* @return The address of the revoker
*/
function getRevoker() external view override(IRegistry) returns (address) {
return revoker;
}
function getRevokerOperator() external view override(IRegistry) returns (address) {
return revokerOperator;
}
/**
* @notice Get the execute allowed at timestamp
*
* @return The timestamp of when the execute is allowed
*/
function getExecuteAllowedAt() external view override(IRegistry) returns (uint256) {
return executeAllowedAt;
}
/**
* @notice Get the unlock start time
*
* @return The timestamp of when the unlock starts
*/
function getUnlockStartTime() external view override(IRegistry) returns (uint256) {
return unlockStartTime;
}
/**
* @notice Get the lock params for the global unlocking schedule
*
* @return The global lock params
*/
function getGlobalLockParams() external view override(IRegistry) returns (LockParams memory) {
return LockParams({
startTime: unlockStartTime, cliffDuration: UNLOCK_CLIFF_DURATION, lockDuration: UNLOCK_LOCK_DURATION
});
}
/**
* @notice Get the implementation for a given staker version
*
* @param _version The version of the staker
*
* @return The implementation for the given staker version
*/
function getStakerImplementation(StakerVersion _version) external view override(IRegistry) returns (address) {
require(StakerVersion.unwrap(_version) < StakerVersion.unwrap(nextStakerVersion), UnRegisteredStaker(_version));
return stakerImplementations[_version];
}
/**
* @notice Get the next staker version
*
* @return The next staker version
*/
function getNextStakerVersion() external view override(IRegistry) returns (StakerVersion) {
return nextStakerVersion;
}
function getNextMilestoneId() external view override(IRegistry) returns (MilestoneId) {
return nextMilestoneId;
}
function getMilestoneStatus(MilestoneId _milestoneId) public view override(IRegistry) returns (MilestoneStatus) {
require(
MilestoneId.unwrap(_milestoneId) < MilestoneId.unwrap(nextMilestoneId), InvalidMilestoneId(_milestoneId)
);
return milestones[_milestoneId];
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol";
import {SlasherFlavor} from "@aztec/core/interfaces/ISlasher.sol";
import {IVerifier} from "@aztec/core/interfaces/IVerifier.sol";
import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol";
import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol";
import {BlockLog, CompressedTempBlockLog} from "@aztec/core/libraries/compressed-data/BlockLog.sol";
import {StakingQueueConfig} from "@aztec/core/libraries/compressed-data/StakingQueueConfig.sol";
import {CompressedChainTips, ChainTips} from "@aztec/core/libraries/compressed-data/Tips.sol";
import {CommitteeAttestations} from "@aztec/core/libraries/rollup/AttestationLib.sol";
import {FeeHeader, L1FeeData, ManaBaseFeeComponents} from "@aztec/core/libraries/rollup/FeeLib.sol";
import {FeeAssetPerEthE9, EthValue, FeeAssetValue} from "@aztec/core/libraries/rollup/FeeLib.sol";
import {ProposedHeader} from "@aztec/core/libraries/rollup/ProposedHeaderLib.sol";
import {ProposeArgs} from "@aztec/core/libraries/rollup/ProposeLib.sol";
import {RewardConfig} from "@aztec/core/libraries/rollup/RewardLib.sol";
import {RewardBoostConfig} from "@aztec/core/reward-boost/RewardBooster.sol";
import {IHaveVersion} from "@aztec/governance/interfaces/IRegistry.sol";
import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol";
import {Signature} from "@aztec/shared/libraries/SignatureLib.sol";
import {Timestamp, Slot, Epoch} from "@aztec/shared/libraries/TimeMath.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
struct PublicInputArgs {
bytes32 previousArchive;
bytes32 endArchive;
address proverId;
}
struct SubmitEpochRootProofArgs {
uint256 start; // inclusive
uint256 end; // inclusive
PublicInputArgs args;
bytes32[] fees;
CommitteeAttestations attestations; // attestations for the last block in epoch
bytes blobInputs;
bytes proof;
}
/**
* @notice Struct for storing flags for block header validation
* @param ignoreDA - True will ignore DA check, otherwise checks
*/
struct BlockHeaderValidationFlags {
bool ignoreDA;
}
struct GenesisState {
bytes32 vkTreeRoot;
bytes32 protocolContractTreeRoot;
bytes32 genesisArchiveRoot;
}
struct RollupConfigInput {
uint256 aztecSlotDuration;
uint256 aztecEpochDuration;
uint256 targetCommitteeSize;
uint256 lagInEpochs;
uint256 aztecProofSubmissionEpochs;
uint256 slashingQuorum;
uint256 slashingRoundSize;
uint256 slashingLifetimeInRounds;
uint256 slashingExecutionDelayInRounds;
uint256[3] slashAmounts;
uint256 slashingOffsetInRounds;
SlasherFlavor slasherFlavor;
address slashingVetoer;
uint256 slashingDisableDuration;
uint256 manaTarget;
uint256 exitDelaySeconds;
uint32 version;
EthValue provingCostPerMana;
RewardConfig rewardConfig;
RewardBoostConfig rewardBoostConfig;
StakingQueueConfig stakingQueueConfig;
uint256 localEjectionThreshold;
Timestamp earliestRewardsClaimableTimestamp;
}
struct RollupConfig {
bytes32 vkTreeRoot;
bytes32 protocolContractTreeRoot;
uint32 version;
IERC20 feeAsset;
IFeeJuicePortal feeAssetPortal;
IVerifier epochProofVerifier;
IInbox inbox;
IOutbox outbox;
}
struct RollupStore {
CompressedChainTips tips; // put first such that the struct slot structure is easy to follow for cheatcodes
mapping(uint256 blockNumber => bytes32 archive) archives;
// The following represents a circular buffer. Key is `blockNumber % size`.
mapping(uint256 circularIndex => CompressedTempBlockLog temp) tempBlockLogs;
RollupConfig config;
}
interface IRollupCore {
event L2BlockProposed(uint256 indexed blockNumber, bytes32 indexed archive, bytes32[] versionedBlobHashes);
event L2ProofVerified(uint256 indexed blockNumber, address indexed proverId);
event BlockInvalidated(uint256 indexed blockNumber);
event RewardConfigUpdated(RewardConfig rewardConfig);
event ManaTargetUpdated(uint256 indexed manaTarget);
event PrunedPending(uint256 provenBlockNumber, uint256 pendingBlockNumber);
event RewardsClaimableUpdated(bool isRewardsClaimable);
function setRewardsClaimable(bool _isRewardsClaimable) external;
function claimSequencerRewards(address _recipient) external returns (uint256);
function claimProverRewards(address _recipient, Epoch[] memory _epochs) external returns (uint256);
function prune() external;
function updateL1GasFeeOracle() external;
function setProvingCostPerMana(EthValue _provingCostPerMana) external;
function propose(
ProposeArgs calldata _args,
CommitteeAttestations memory _attestations,
address[] memory _signers,
Signature memory _attestationsAndSignersSignature,
bytes calldata _blobInput
) external;
function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external;
function invalidateBadAttestation(
uint256 _blockNumber,
CommitteeAttestations memory _attestations,
address[] memory _committee,
uint256 _invalidIndex
) external;
function invalidateInsufficientAttestations(
uint256 _blockNumber,
CommitteeAttestations memory _attestations,
address[] memory _committee
) external;
function setRewardConfig(RewardConfig memory _config) external;
function updateManaTarget(uint256 _manaTarget) external;
// solhint-disable-next-line func-name-mixedcase
function L1_BLOCK_AT_GENESIS() external view returns (uint256);
}
interface IRollup is IRollupCore, IHaveVersion {
function validateHeaderWithAttestations(
ProposedHeader calldata _header,
CommitteeAttestations memory _attestations,
address[] memory _signers,
Signature memory _attestationsAndSignersSignature,
bytes32 _digest,
bytes32 _blobsHash,
BlockHeaderValidationFlags memory _flags
) external;
function canProposeAtTime(Timestamp _ts, bytes32 _archive, address _who) external returns (Slot, uint256);
function getTips() external view returns (ChainTips memory);
function status(uint256 _myHeaderBlockNumber)
external
view
returns (
uint256 provenBlockNumber,
bytes32 provenArchive,
uint256 pendingBlockNumber,
bytes32 pendingArchive,
bytes32 archiveOfMyBlock,
Epoch provenEpochNumber
);
function getEpochProofPublicInputs(
uint256 _start,
uint256 _end,
PublicInputArgs calldata _args,
bytes32[] calldata _fees,
bytes calldata _blobPublicInputs
) external view returns (bytes32[] memory);
function validateBlobs(bytes calldata _blobsInputs) external view returns (bytes32[] memory, bytes32, bytes[] memory);
function getManaBaseFeeComponentsAt(Timestamp _timestamp, bool _inFeeAsset)
external
view
returns (ManaBaseFeeComponents memory);
function getManaBaseFeeAt(Timestamp _timestamp, bool _inFeeAsset) external view returns (uint256);
function getL1FeesAt(Timestamp _timestamp) external view returns (L1FeeData memory);
function getFeeAssetPerEth() external view returns (FeeAssetPerEthE9);
function getEpochForBlock(uint256 _blockNumber) external view returns (Epoch);
function canPruneAtTime(Timestamp _ts) external view returns (bool);
function archive() external view returns (bytes32);
function archiveAt(uint256 _blockNumber) external view returns (bytes32);
function getProvenBlockNumber() external view returns (uint256);
function getPendingBlockNumber() external view returns (uint256);
function getBlock(uint256 _blockNumber) external view returns (BlockLog memory);
function getFeeHeader(uint256 _blockNumber) external view returns (FeeHeader memory);
function getBlobCommitmentsHash(uint256 _blockNumber) external view returns (bytes32);
function getCurrentBlobCommitmentsHash() external view returns (bytes32);
function getSharesFor(address _prover) external view returns (uint256);
function getSequencerRewards(address _sequencer) external view returns (uint256);
function getCollectiveProverRewardsForEpoch(Epoch _epoch) external view returns (uint256);
function getSpecificProverRewardsForEpoch(Epoch _epoch, address _prover) external view returns (uint256);
function getHasSubmitted(Epoch _epoch, uint256 _length, address _prover) external view returns (bool);
function getHasClaimed(address _prover, Epoch _epoch) external view returns (bool);
function getProofSubmissionEpochs() external view returns (uint256);
function getManaTarget() external view returns (uint256);
function getManaLimit() external view returns (uint256);
function getProvingCostPerManaInEth() external view returns (EthValue);
function getProvingCostPerManaInFeeAsset() external view returns (FeeAssetValue);
function getFeeAsset() external view returns (IERC20);
function getFeeAssetPortal() external view returns (IFeeJuicePortal);
function getRewardDistributor() external view returns (IRewardDistributor);
function getBurnAddress() external view returns (address);
function getInbox() external view returns (IInbox);
function getOutbox() external view returns (IOutbox);
function getRewardConfig() external view returns (RewardConfig memory);
function getBlockReward() external view returns (uint256);
function getEarliestRewardsClaimableTimestamp() external view returns (Timestamp);
function isRewardsClaimable() external view returns (bool);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
interface IPayload {
struct Action {
address target;
bytes data;
}
/**
* @notice A URI that can be used to refer to where a non-coder human readable description
* of the payload can be found.
*
* @dev Not used in the contracts, so could be any string really
*
* @return - Ideally a useful URI for the payload description
*/
function getURI() external view returns (string memory);
function getActions() external view returns (Action[] memory);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Aztec Labs.
pragma solidity >=0.8.27;
interface IDateGatedRelayer {
function relay(address target, bytes calldata data) external returns (bytes memory);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.20;
import {Ownable} from "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* This extension of the {Ownable} contract includes a two-step mechanism to transfer
* ownership, where the new owner must call {acceptOwnership} in order to replace the
* old one. This can help prevent common mistakes, such as transfers of ownership to
* incorrect accounts, or to contracts that are unable to interact with the
* permission system.
*
* The initial owner is specified at deployment time in the constructor for `Ownable`. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*
* Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";
import {SplitV2Lib} from "@splits/libraries/SplitV2.sol";
import {PullSplitFactory} from "@splits/splitters/pull/PullSplitFactory.sol";
import {Constants} from "src/constants.sol";
import {IRegistry} from "src/staking/rollup-system-interfaces/IRegistry.sol";
import {IStaking} from "src/staking/rollup-system-interfaces/IStaking.sol";
import {BN254Lib} from "./libs/BN254.sol";
import {QueueLib, Queue} from "./libs/QueueLib.sol";
interface IStakingRegistry {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Structs */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
struct ProviderConfiguration {
/// @notice The address of the provider admin
address providerAdmin;
/// @notice The take rate for the provider
uint16 providerTakeRate;
/// @notice The address of the provider rewards recipient
address providerRewardsRecipient;
}
struct KeyStore {
/// @notice The address of the attester
address attester;
/// @notice - The BLS public key - BN254 G1
BN254Lib.G1Point publicKeyG1;
/// @notice - The BLS public key - BN254 G2
BN254Lib.G2Point publicKeyG2;
/// @notice - The BLS proofOfPossession - required to prevent rogue key attacks
BN254Lib.G1Point proofOfPossession;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Events */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
event ProviderRegistered(
uint256 indexed providerIdentifier, address indexed providerAdmin, uint16 indexed providerTakeRate
);
event ProviderAdminUpdateInitiated(uint256 indexed providerIdentifier, address indexed newAdmin);
event ProviderAdminUpdated(uint256 indexed providerIdentifier, address indexed newAdmin);
event ProviderTakeRateUpdated(uint256 indexed providerIdentifier, uint16 newTakeRate);
event ProviderRewardsRecipientUpdated(uint256 indexed providerIdentifier, address indexed newRewardsRecipient);
event ProviderQueueDripped(uint256 indexed providerIdentifier, address indexed attester);
event AttestersAddedToProvider(uint256 indexed providerIdentifier, address[] attesters);
event StakedWithProvider(
uint256 indexed providerIdentifier,
address indexed rollupAddress,
address indexed attester,
address coinbaseSplitContractAddress,
address stakerImplementation
);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Errors */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
error StakingRegistry__ZeroAddress();
error StakingRegistry__InvalidProviderIdentifier(uint256 _providerIdentifier);
error StakingRegistry__NotProviderAdmin();
error StakingRegistry__UpdatedProviderAdminToSameAddress();
error StakingRegistry__UpdatedProviderTakeRateToSameValue();
error StakingRegistry__NotPendingProviderAdmin();
error StakingRegistry__InvalidTakeRate(uint256 _takeRate);
error StakingRegistry__UnexpectedTakeRate(uint256 _expectedTakeRate, uint256 _gotTakeRate);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Functions */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function stake(
/// The provider identifier to stake with
uint256 _providerIdentifier,
/// The rollup version to stake to
uint256 _rollupVersion,
/// The withdrawal address for the created validator
address _withdrawalAddress,
/// The expected provider take rate
uint16 _expectedProviderTakeRate,
/// The address that will receive the rewards
address _userRewardsRecipient,
/// Whether to move the validator to the latest rollup
bool _moveWithLatestRollup
) external;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Provider Management Functions */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function registerProvider(address _providerAdmin, uint16 _providerTakeRate, address _providerRewardsRecipient)
external
returns (uint256);
function addKeysToProvider(uint256 _providerIdentifier, KeyStore[] calldata _keyStores) external;
function updateProviderAdmin(uint256 _providerIdentifier, address _newAdmin) external;
function acceptProviderAdmin(uint256 _providerIdentifier) external;
function updateProviderRewardsRecipient(uint256 _providerIdentifier, address _newRewardsRecipient) external;
function updateProviderTakeRate(uint256 _providerIdentifier, uint16 _newTakeRate) external;
function dripProviderQueue(uint256 _providerIdentifier, uint256 _numberOfKeysToDrip) external;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Provider Queue Getters */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function getProviderQueueLength(uint256 _providerIdentifier) external view returns (uint256);
function getFirstIndexInQueue(uint256 _providerIdentifier) external view returns (uint128);
function getLastIndexInQueue(uint256 _providerIdentifier) external view returns (uint128);
function getValueAtIndexInQueue(uint256 _providerIdentifier, uint128 _index) external view returns (KeyStore memory);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* View Functions */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function getActivationThreshold(uint256 _rollupVersion) external view returns (uint256);
}
/**
* @title Staking Registry
* @author Aztec-Labs
* @notice This contract is used to register staking providers and their associated keypairs
*
* Description:
* - The staking registry allows operators to list keypairs that they will run on behalf of other users.
* - The operators are expected to have all of the keys that they list running on a validator ready to go.
*/
contract StakingRegistry is IStakingRegistry {
using QueueLib for Queue;
using SafeERC20 for IERC20;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Immutables */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
PullSplitFactory public immutable PULL_SPLIT_FACTORY;
IERC20 public immutable STAKING_ASSET;
IRegistry public immutable ROLLUP_REGISTRY;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Storage */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
mapping(uint256 providerIdentifier => ProviderConfiguration providerConfiguration) public providerConfigurations;
mapping(uint256 providerIdentifier => Queue attesterKeys) public providerQueues;
/// @dev The next provider identifier to use - incremented upon each registration
uint256 public nextProviderIdentifier = 1;
/// @dev The provider admin waiting to accept the provider admin role
mapping(uint256 providerIdentifier => address providerAdmin) public pendingProviderAdmins;
constructor(IERC20 _stakingAsset, address _pullSplitFactory, IRegistry _rollupRegistry) {
STAKING_ASSET = _stakingAsset;
PULL_SPLIT_FACTORY = PullSplitFactory(_pullSplitFactory);
ROLLUP_REGISTRY = _rollupRegistry;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Functions */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/**
* @notice stake with a provider
*
* Steps:
* - Retrieve a keystore from the provider queue.
* - Deposit into the rollup with the user's withdrawal address.
* - Create a split contract for the user and the provider that the provider will set as the coinbase for the validator
* in order to split the rewards between them at a known take rate.
*
* - Note: The user must trust that the provider running their node will set the correct coinbase on their behalf. If you do not trust your
* provider to do this, do not stake with them.
*
* @param _providerIdentifier - The identifier of the provider to use
* @param _rollupVersion - The rollup version to stake to
* @param _withdrawalAddress - The address that will control withdrawing the validator
* @param _expectedProviderTakeRate - The expected provider take rate
* @param _userRewardsRecipient - The address that will receive the user's reward split
* @param _moveWithLatestRollup - Whether to move the validator to the latest rollup
*/
function stake(
uint256 _providerIdentifier,
uint256 _rollupVersion,
address _withdrawalAddress,
uint16 _expectedProviderTakeRate,
address _userRewardsRecipient,
bool _moveWithLatestRollup
) external override(IStakingRegistry) {
ProviderConfiguration memory providerConfiguration = providerConfigurations[_providerIdentifier];
// Or require providerIdentifier < nextProviderIdentifier
require(
providerConfiguration.providerAdmin != address(0),
StakingRegistry__InvalidProviderIdentifier(_providerIdentifier)
);
require(_withdrawalAddress != address(0), StakingRegistry__ZeroAddress());
require(_userRewardsRecipient != address(0), StakingRegistry__ZeroAddress());
// If the provider take rate has changed inbetween the time the transaction was submitted and the time it was executed, we revert
require(
_expectedProviderTakeRate == providerConfiguration.providerTakeRate,
StakingRegistry__UnexpectedTakeRate(_expectedProviderTakeRate, providerConfiguration.providerTakeRate)
);
address rollupAddress = ROLLUP_REGISTRY.getRollup(_rollupVersion);
require(rollupAddress != address(0), StakingRegistry__ZeroAddress()); // Sanity check - it should never be zero
// Revertable conditions
// - provider has no keys - QueueIsEmpty()
KeyStore memory keyStore = providerQueues[_providerIdentifier].dequeue();
// This can be read from the registry!
// Ext call - Revertable conditions
// msg.sender does not have enough funds
// msg.sender has not approved enough funds
uint256 activationThreshold = IStaking(rollupAddress).getActivationThreshold();
STAKING_ASSET.safeTransferFrom(msg.sender, address(this), activationThreshold);
// Ext call
STAKING_ASSET.approve(rollupAddress, activationThreshold);
// Place the validator into the entry queue
// Revertable conditions:
// - attester or withdrawal address is the zero address
// - attester is currently exiting
//
// Async revertable conditions: flushEntryQueue
// - recoverable: the deposit amount will be returned to the _withdrawalAddress if the deposit fails
IStaking(rollupAddress)
.deposit(
keyStore.attester,
_withdrawalAddress,
keyStore.publicKeyG1,
keyStore.publicKeyG2,
keyStore.proofOfPossession,
_moveWithLatestRollup
);
// Create the splitting contract
// User take rate is BIPS (10_000) - provider take rate
// Provider take is constrained to be less than BIPS
SplitV2Lib.Split memory splitInstance;
{
uint256 providerTakeRate = providerConfiguration.providerTakeRate;
uint256 totalAllocation = Constants.BIPS;
uint256 userTakeRate = totalAllocation - providerTakeRate;
address providerRewardsRecipient = providerConfiguration.providerRewardsRecipient;
// Set the address that will receive the rewards
address[] memory recipients = new address[](2);
recipients[0] = providerRewardsRecipient;
recipients[1] = _userRewardsRecipient;
uint256[] memory allocations = new uint256[](2);
allocations[0] = providerTakeRate;
allocations[1] = userTakeRate;
splitInstance = SplitV2Lib.Split({
recipients: recipients,
allocations: allocations,
totalAllocation: totalAllocation,
distributionIncentive: 0
});
}
address split = PULL_SPLIT_FACTORY.createSplit(
splitInstance,
address(0), // owner - 0 to make the split immutable
address(this) // creator - only put in a log - no special permissions
);
emit StakedWithProvider(_providerIdentifier, rollupAddress, keyStore.attester, split, msg.sender);
}
/**
* @notice Register a new staking provider
*
* @param _providerAdmin The address of the provider admin
* @param _providerTakeRate The take rate for the provider
* @param _providerRewardsRecipient The address that will receive the provider's rewards
*
* @dev Provider identifier's are auto-incremented and assigned to the provider
*/
function registerProvider(address _providerAdmin, uint16 _providerTakeRate, address _providerRewardsRecipient)
external
override(IStakingRegistry)
returns (uint256)
{
require(_providerAdmin != address(0), StakingRegistry__ZeroAddress());
require(_providerRewardsRecipient != address(0), StakingRegistry__ZeroAddress());
require(_providerTakeRate <= Constants.BIPS, StakingRegistry__InvalidTakeRate(_providerTakeRate));
// Assign and increment the provider identifier
uint256 providerIdentifier = nextProviderIdentifier;
nextProviderIdentifier++;
ProviderConfiguration memory providerConfiguration = ProviderConfiguration({
providerAdmin: _providerAdmin,
providerTakeRate: _providerTakeRate,
providerRewardsRecipient: _providerRewardsRecipient
});
// Set the provider admin, queue, and take rate
providerConfigurations[providerIdentifier] = providerConfiguration;
providerQueues[providerIdentifier].init();
emit ProviderRegistered(providerIdentifier, _providerAdmin, _providerTakeRate);
return providerIdentifier;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Provider Queue Management Functions */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/**
* @notice Add a set of keys to a provider
*
* @param _providerIdentifier The identifier of the provider
* @param _keyStores The key stores to add
*/
function addKeysToProvider(uint256 _providerIdentifier, KeyStore[] calldata _keyStores)
external
override(IStakingRegistry)
{
ProviderConfiguration memory providerConfiguration = providerConfigurations[_providerIdentifier];
require(msg.sender == providerConfiguration.providerAdmin, StakingRegistry__NotProviderAdmin());
Queue storage providerQueue = providerQueues[_providerIdentifier];
address[] memory attesters = new address[](_keyStores.length); // just for logging
for (uint256 i; i < _keyStores.length; ++i) {
providerQueue.enqueue(_keyStores[i]);
attesters[i] = _keyStores[i].attester;
}
emit AttestersAddedToProvider(_providerIdentifier, attesters);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Provider Admin Functions */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/**
* @notice Update the admin of a provider
*
* @param _providerIdentifier The identifier of the provider
* @param _newAdmin The new admin address
*/
function updateProviderAdmin(uint256 _providerIdentifier, address _newAdmin) external override(IStakingRegistry) {
address currentProviderAdmin = providerConfigurations[_providerIdentifier].providerAdmin;
require(msg.sender == currentProviderAdmin, StakingRegistry__NotProviderAdmin());
require(_newAdmin != address(0), StakingRegistry__ZeroAddress());
require(_newAdmin != currentProviderAdmin, StakingRegistry__UpdatedProviderAdminToSameAddress());
pendingProviderAdmins[_providerIdentifier] = _newAdmin;
emit ProviderAdminUpdateInitiated(_providerIdentifier, _newAdmin);
}
/**
* @notice Accept the provider admin role
*
* @param _providerIdentifier The identifier of the provider
*
* @dev The provider admin transfer can be initiated in `updateProviderAdmin` and accepted here.
*/
function acceptProviderAdmin(uint256 _providerIdentifier) external override(IStakingRegistry) {
require(msg.sender == pendingProviderAdmins[_providerIdentifier], StakingRegistry__NotPendingProviderAdmin());
providerConfigurations[_providerIdentifier].providerAdmin = msg.sender;
delete pendingProviderAdmins[_providerIdentifier];
emit ProviderAdminUpdated(_providerIdentifier, msg.sender);
}
/**
* @notice Update the rewards recipient of a provider
*
* @param _providerIdentifier The identifier of the provider
* @param _newRewardsRecipient The new rewards recipient address
*
* @dev The rewards recipient will be included in 0xSplits contract's deployed for the provider
*/
function updateProviderRewardsRecipient(uint256 _providerIdentifier, address _newRewardsRecipient)
external
override(IStakingRegistry)
{
require(
msg.sender == providerConfigurations[_providerIdentifier].providerAdmin, StakingRegistry__NotProviderAdmin()
);
require(_newRewardsRecipient != address(0), StakingRegistry__ZeroAddress());
providerConfigurations[_providerIdentifier].providerRewardsRecipient = _newRewardsRecipient;
emit ProviderRewardsRecipientUpdated(_providerIdentifier, _newRewardsRecipient);
}
/**
* @notice Update the take rate of a provider
*
* @param _providerIdentifier The identifier of the provider
* @param _newTakeRate The new take rate
*
* @dev The take rate is a BIPS of the rewards that the provider will receive
*/
function updateProviderTakeRate(uint256 _providerIdentifier, uint16 _newTakeRate)
external
override(IStakingRegistry)
{
require(
msg.sender == providerConfigurations[_providerIdentifier].providerAdmin, StakingRegistry__NotProviderAdmin()
);
require(
_newTakeRate != providerConfigurations[_providerIdentifier].providerTakeRate,
StakingRegistry__UpdatedProviderTakeRateToSameValue()
);
require(_newTakeRate <= Constants.BIPS, StakingRegistry__InvalidTakeRate(_newTakeRate));
providerConfigurations[_providerIdentifier].providerTakeRate = _newTakeRate;
emit ProviderTakeRateUpdated(_providerIdentifier, _newTakeRate);
}
/**
* @notice Drip the provider queue
* If the queue gets into a bad state - e.g a provider deposits a key that is already in the rollup, or a provider deposits bad BLS keys
* The queue can be dripped to remove the bad key.
*
* @param _providerIdentifier The identifier of the provider
* @param _numberOfKeysToDrip The number of keys to drip
*/
function dripProviderQueue(uint256 _providerIdentifier, uint256 _numberOfKeysToDrip)
external
override(IStakingRegistry)
{
ProviderConfiguration memory providerConfiguration = providerConfigurations[_providerIdentifier];
require(msg.sender == providerConfiguration.providerAdmin, StakingRegistry__NotProviderAdmin());
Queue storage providerQueue = providerQueues[_providerIdentifier];
for (uint256 i; i < _numberOfKeysToDrip; ++i) {
KeyStore memory keyStore = providerQueue.dequeue();
emit ProviderQueueDripped(_providerIdentifier, keyStore.attester);
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Provider Queue Getters */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/**
* @notice Get the length of the provider queue
*
* @param _providerIdentifier The identifier of the provider
* @return The length of the provider queue
*/
function getProviderQueueLength(uint256 _providerIdentifier)
external
view
override(IStakingRegistry)
returns (uint256)
{
return providerQueues[_providerIdentifier].length();
}
/**
* @notice Get the first index in the provider queue
*
* @param _providerIdentifier The identifier of the provider
* @return The first index in the provider queue
*/
function getFirstIndexInQueue(uint256 _providerIdentifier)
external
view
override(IStakingRegistry)
returns (uint128)
{
return providerQueues[_providerIdentifier].getFirstIndex();
}
/**
* @notice Get the last index in the provider queue
*
* @param _providerIdentifier The identifier of the provider
* @return The last index in the provider queue
*/
function getLastIndexInQueue(uint256 _providerIdentifier)
external
view
override(IStakingRegistry)
returns (uint128)
{
return providerQueues[_providerIdentifier].getLastIndex();
}
/**
* @notice Get the key store at a given index in the provider queue
*
* @param _providerIdentifier The identifier of the provider
* @param _index The index in the provider queue
* @return The key store at the given index
*/
function getValueAtIndexInQueue(uint256 _providerIdentifier, uint128 _index)
external
view
override(IStakingRegistry)
returns (KeyStore memory)
{
return providerQueues[_providerIdentifier].getValueAtIndex(_index);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* View Functions */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/**
* @notice View function to retrieve the activation threshold for a given rollup version
* @param _rollupVersion The version of the rollup to get the activation threshold for
* @return The activation threshold
*/
function getActivationThreshold(uint256 _rollupVersion) external view override(IStakingRegistry) returns (uint256) {
address rollupAddress = ROLLUP_REGISTRY.getRollup(_rollupVersion);
return IStaking(rollupAddress).getActivationThreshold();
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.30;
import { IStaking } from "@aztec/core/interfaces/IStaking.sol";
import { IGSE } from "@aztec/governance/GSE.sol";
import { ATPNonWithdrawableStaker } from "src/staking/ATPNonWithdrawableStaker.sol";
import { ATPWithdrawableAndClaimableStaker, IERC20, IRegistry, IStakingRegistry } from "src/staking/ATPWithdrawableAndClaimableStaker.sol";
import { ATPWithdrawableStaker } from "src/staking/ATPWithdrawableStaker.sol";
import { IATPNonWithdrawableStaker } from "src/staking/interfaces/IATPNonWithdrawableStaker.sol";
import { IATPWithdrawableStaker } from "src/staking/interfaces/IATPWithdrawableStaker.sol";
contract ATPWithdrawableAndClaimableStakerV2 is ATPWithdrawableAndClaimableStaker {
constructor(
IERC20 _stakingAsset,
IRegistry _rollupRegistry,
IStakingRegistry _stakingRegistry,
uint256 _withdrawalTimestamp
) ATPWithdrawableAndClaimableStaker(_stakingAsset, _rollupRegistry, _stakingRegistry, _withdrawalTimestamp) {}
function finalizeWithdraw(uint256 _version, address _attester)
external
override(IATPWithdrawableStaker, ATPWithdrawableStaker)
{
address rollup = ROLLUP_REGISTRY.getRollup(_version);
IStaking(rollup).finalizeWithdraw(_attester);
}
function delegate(uint256 _version, address _attester, address _delegatee)
external
override(IATPNonWithdrawableStaker, ATPNonWithdrawableStaker)
onlyOperator
{
address rollup = ROLLUP_REGISTRY.getRollup(_version);
IGSE gse = IGSE(IStaking(rollup).getGSE());
address instance = rollup;
// If the attester is not registered on the instance, expect the bonus
// It might not be registered if it have exited, but in that case, it is delegating
// 0 power, so essentially just a no-op at that point.
if (!gse.isRegistered(instance, _attester)) {
instance = gse.getBonusInstanceAddress();
}
gse.delegate(instance, _attester, _delegatee);
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {Address} from "@oz/utils/Address.sol";
import {Ownable} from "@oz/access/Ownable.sol";
interface IGovernanceAcceleratedLock {
error GovernanceAcceleratedLock__LockTimeNotMet();
error GovernanceAcceleratedLock__GovernanceAddressCannotBeZero();
event LockAccelerated();
event LockExtended();
function lockAccelerated() external view returns (bool);
function accelerateLock() external;
function extendLock() external;
function relay(address _target, bytes calldata _data) external returns (bytes memory);
}
contract GovernanceAcceleratedLock is Ownable, IGovernanceAcceleratedLock {
/// @notice The start time of the lock
uint256 public immutable START_TIME;
/// @notice The extended lock time
uint256 public constant EXTENDED_LOCK_TIME = 365 days;
/// @notice The shorter lock time
uint256 public constant SHORTER_LOCK_TIME = 90 days;
/// @notice Whether the lock is currently accelerated
bool public lockAccelerated = false;
/**
* @param _governance The address of the governance contract (Owner)
* @param _startTime The start time of the lock
*/
constructor(address _governance, uint256 _startTime) Ownable(_governance) {
require(_governance != address(0), GovernanceAcceleratedLock__GovernanceAddressCannotBeZero());
START_TIME = _startTime;
}
/**
* @notice Accelerate the lock
* @notice The lock can be decelerated by calling extendLock
*
* @dev Only the owner can accelerate the lock
*/
function accelerateLock() external override(IGovernanceAcceleratedLock) onlyOwner {
lockAccelerated = true;
emit LockAccelerated();
}
/**
* @notice Extend the lock
* @notice The lock can be accelerated by calling accelerateLock
*
* @dev Only the owner can extend the lock
*/
function extendLock() external override(IGovernanceAcceleratedLock) onlyOwner {
lockAccelerated = false;
emit LockExtended();
}
/**
* @notice Relay a call to a target contract
* @notice The call will be relayed if the lock is accelerated
*
* @dev The relay function CANNOT send native tokens (ETH)
* @dev Only the owner can relay the call
*
* @param _target The target contract to relay the call to
* @param _data The data to relay to the target contract
* @return The result of the call
*/
function relay(address _target, bytes calldata _data)
external
override(IGovernanceAcceleratedLock)
onlyOwner
returns (bytes memory)
{
uint256 lockTime = lockAccelerated ? SHORTER_LOCK_TIME : EXTENDED_LOCK_TIME;
require(block.timestamp >= START_TIME + lockTime, GovernanceAcceleratedLock__LockTimeNotMet());
return Address.functionCall(_target, _data);
}
}pragma solidity ^0.8.0;
import {ILBPStrategyBasic} from "@launcher/interfaces/ILBPStrategyBasic.sol";
import {IPositionManager} from "@v4p/interfaces/IPositionManager.sol";
import {IPoolManager} from "@v4c/interfaces/IPoolManager.sol";
interface IVirtualLBPStrategyBasic is ILBPStrategyBasic {
function approveMigration() external;
function auction() external view returns (address);
function positionManager() external view returns (IPositionManager);
function positionRecipient() external view returns (address);
function migrationBlock() external view returns (uint256);
function sweepBlock() external view returns (uint256);
function token() external view returns (address);
function currency() external view returns (address);
function poolLPFee() external view returns (uint24);
function poolTickSpacing() external view returns (int24);
function operator() external view returns (address);
function poolManager() external view returns (IPoolManager);
function UNDERLYING_TOKEN() external view returns (address);
function GOVERNANCE() external view returns (address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.22;
import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*
* @custom:stateless
*/
abstract contract UUPSUpgradeable is IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
/**
* @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC-1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
/**
* @notice The parameters for a lock
* The parameters used to derive the actual lock.
*
* @param startTime The timestamp that the lock starts at (0 before this value)
* @param cliffDuration Time until the cliff is reached
* @param lockDuration Time until the lock is fully unlocked
*/
struct LockParams {
uint256 startTime;
uint256 cliffDuration;
uint256 lockDuration;
}
/**
* @notice The lock struct
* @param startTime The timestamp that the lock starts at (0 before this value)
* @param cliff The timestamp of the cliff of the lock (0 before this value, >= startTime)
* @param endTime The timestamp that the lock ends at, >= cliff
* @param allocation The amount of tokens that are locked
*/
struct Lock {
uint256 startTime;
uint256 cliff;
uint256 endTime;
uint256 allocation;
}
/**
* @title LockLib
* @notice Library for handling "locks" on assets
* A lock is in this case, a curve defining the amount available at any given timestamp.
* The particular lock is a linear curve with a cliff.
*/
library LockLib {
error LockDurationMustBeGTZero();
error LockDurationMustBeGECliffDuration(uint256 lockDuration, uint256 cliffDuration);
/**
* @notice Check if the lock has ended
*
* @param _lock The lock
* @param _timestamp The timestamp to check
*
* @return True if the lock has ended
*/
function hasEnded(Lock memory _lock, uint256 _timestamp) internal pure returns (bool) {
return _timestamp >= _lock.endTime;
}
/**
* @notice Get the unlocked value of the lock at a given timestamp
*
* @param _lock The lock
* @param _timestamp The timestamp to get the value at
*
* @return The unlocked value at the given timestamp
*/
function unlockedAt(Lock memory _lock, uint256 _timestamp) internal pure returns (uint256) {
if (_timestamp < _lock.cliff) {
return 0;
}
if (_timestamp >= _lock.endTime) {
return _lock.allocation;
}
return (_lock.allocation * (_timestamp - _lock.startTime)) / (_lock.endTime - _lock.startTime);
}
/**
* @notice Create a lock
*
* @dev The caller should make sure that `_allocation` is not zero
*
* @param _params The lock params
* @param _allocation The allocation of the lock
*
* @return The lock
*/
function createLock(LockParams memory _params, uint256 _allocation) internal pure returns (Lock memory) {
LockLib.assertValid(_params);
return Lock({
startTime: _params.startTime,
cliff: _params.startTime + _params.cliffDuration,
endTime: _params.startTime + _params.lockDuration,
allocation: _allocation
});
}
/**
* @notice Assert that the lock params are valid
*
* @param _params The lock params
*/
function assertValid(LockParams memory _params) internal pure {
require(_params.lockDuration > 0, LockDurationMustBeGTZero());
require(
_params.lockDuration >= _params.cliffDuration,
LockDurationMustBeGECliffDuration(_params.lockDuration, _params.cliffDuration)
);
}
/**
* @notice Check if the lock params are empty
*
* @param _params The lock params
*
* @return True if the lock params are empty
*/
function isEmpty(LockParams memory _params) internal pure returns (bool) {
return _params.startTime == 0 && _params.cliffDuration == 0 && _params.lockDuration == 0;
}
/**
* @notice Get an empty lock params
*
* @return An empty lock params
*/
function empty() internal pure returns (LockParams memory) {
return LockParams({startTime: 0, cliffDuration: 0, lockDuration: 0});
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {ERC1967Utils} from "@oz/proxy/ERC1967/ERC1967Utils.sol";
import {UUPSUpgradeable} from "@oz/proxy/utils/UUPSUpgradeable.sol";
import {IATPCore} from "../atps/base/IATP.sol";
interface IBaseStaker {
function initialize(address _atp) external;
function getATP() external view returns (address);
function getOperator() external view returns (address);
function getImplementation() external view returns (address);
}
contract BaseStaker is IBaseStaker, UUPSUpgradeable {
address internal atp;
error AlreadyInitialized();
error ZeroATP();
error NotATP(address caller, address atp);
error NotOperator(address caller, address operator);
modifier onlyOperator() {
address operator = getOperator();
require(msg.sender == operator, NotOperator(msg.sender, operator));
_;
}
modifier onlyATP() {
require(msg.sender == address(atp), NotATP(msg.sender, address(atp)));
_;
}
constructor() {
atp = address(0xdead);
}
function initialize(address _atp) external virtual override(IBaseStaker) {
require(address(_atp) != address(0), ZeroATP());
require(address(atp) == address(0), AlreadyInitialized());
atp = _atp;
}
function getImplementation() external view virtual override(IBaseStaker) returns (address) {
return ERC1967Utils.getImplementation();
}
function getATP() public view virtual override(IBaseStaker) returns (address) {
return atp;
}
function getOperator() public view virtual override(IBaseStaker) returns (address) {
return IATPCore(atp).getOperator();
}
function _authorizeUpgrade(address _newImplementation) internal virtual override(UUPSUpgradeable) onlyATP {}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IRollup} from "@aztec/core/interfaces/IRollup.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {IInbox} from "./messagebridge/IInbox.sol";
interface IFeeJuicePortal {
event DepositToAztecPublic(bytes32 indexed to, uint256 amount, bytes32 secretHash, bytes32 key, uint256 index);
event FeesDistributed(address indexed to, uint256 amount);
function distributeFees(address _to, uint256 _amount) external;
function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash) external returns (bytes32, uint256);
// solhint-disable-next-line func-name-mixedcase
function UNDERLYING() external view returns (IERC20);
// solhint-disable-next-line func-name-mixedcase
function L2_TOKEN_ADDRESS() external view returns (bytes32);
// solhint-disable-next-line func-name-mixedcase
function VERSION() external view returns (uint256);
// solhint-disable-next-line func-name-mixedcase
function INBOX() external view returns (IInbox);
// solhint-disable-next-line func-name-mixedcase
function ROLLUP() external view returns (IRollup);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
enum SlasherFlavor {
NONE,
TALLY,
EMPIRE
}
interface ISlasher {
event VetoedPayload(address indexed payload);
event SlashingDisabled(uint256 disabledUntil);
function slash(IPayload _payload) external returns (bool);
function vetoPayload(IPayload _payload) external returns (bool);
function setSlashingEnabled(bool _enabled) external;
function isSlashingEnabled() external view returns (bool);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
interface IVerifier {
function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {DataStructures} from "../../libraries/DataStructures.sol";
/**
* @title Inbox
* @author Aztec Labs
* @notice Lives on L1 and is used to pass messages into the rollup from L1.
*/
interface IInbox {
struct InboxState {
// Rolling hash of all messages inserted into the inbox.
// Used by clients to check for consistency.
bytes16 rollingHash;
// This value is not used much by the contract, but it is useful for synching the node faster
// as it can more easily figure out if it can just skip looking for events for a time period.
uint64 totalMessagesInserted;
// Number of a tree which is currently being filled
uint64 inProgress;
}
/**
* @notice Emitted when a message is sent
* @param l2BlockNumber - The L2 block number in which the message is included
* @param index - The index of the message in the L1 to L2 messages tree
* @param hash - The hash of the message
* @param rollingHash - The rolling hash of all messages inserted into the inbox
*/
event MessageSent(uint256 indexed l2BlockNumber, uint256 index, bytes32 indexed hash, bytes16 rollingHash);
event InboxSynchronized(uint256 indexed inProgress);
// docs:start:send_l1_to_l2_message
/**
* @notice Inserts a new message into the Inbox
* @dev Emits `MessageSent` with data for easy access by the sequencer
* @param _recipient - The recipient of the message
* @param _content - The content of the message (application specific)
* @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed
* on L2)
* @return The key of the message in the set and its leaf index in the tree
*/
function sendL2Message(DataStructures.L2Actor memory _recipient, bytes32 _content, bytes32 _secretHash)
external
returns (bytes32, uint256);
// docs:end:send_l1_to_l2_message
// docs:start:consume
/**
* @notice Consumes the current tree, and starts a new one if needed
* @dev Only callable by the rollup contract
* @dev In the first iteration we return empty tree root because first block's messages tree is always
* empty because there has to be a 1 block lag to prevent sequencer DOS attacks
*
* @param _toConsume - The block number to consume
*
* @return The root of the consumed tree
*/
function consume(uint256 _toConsume) external returns (bytes32);
// docs:end:consume
function catchUp(uint256 _pendingBlockNumber) external;
function getFeeAssetPortal() external view returns (address);
function getRoot(uint256 _blockNumber) external view returns (bytes32);
function getState() external view returns (InboxState memory);
function getTotalMessagesInserted() external view returns (uint64);
function getInProgress() external view returns (uint64);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {DataStructures} from "../../libraries/DataStructures.sol";
/**
* @title IOutbox
* @author Aztec Labs
* @notice Lives on L1 and is used to consume L2 -> L1 messages. Messages are inserted by the Rollup
* and will be consumed by the portal contracts.
*/
interface IOutbox {
event RootAdded(uint256 indexed l2BlockNumber, bytes32 indexed root);
event MessageConsumed(
uint256 indexed l2BlockNumber, bytes32 indexed root, bytes32 indexed messageHash, uint256 leafId
);
// docs:start:outbox_insert
/**
* @notice Inserts the root of a merkle tree containing all of the L2 to L1 messages in
* a block specified by _l2BlockNumber.
* @dev Only callable by the rollup contract
* @dev Emits `RootAdded` upon inserting the root successfully
* @param _l2BlockNumber - The L2 Block Number in which the L2 to L1 messages reside
* @param _root - The merkle root of the tree where all the L2 to L1 messages are leaves
*/
function insert(uint256 _l2BlockNumber, bytes32 _root) external;
// docs:end:outbox_insert
// docs:start:outbox_consume
/**
* @notice Consumes an entry from the Outbox
* @dev Only useable by portals / recipients of messages
* @dev Emits `MessageConsumed` when consuming messages
* @param _message - The L2 to L1 message
* @param _l2BlockNumber - The block number specifying the block that contains the message we want to consume
* @param _leafIndex - The index inside the merkle tree where the message is located
* @param _path - The sibling path used to prove inclusion of the message, the _path length directly depends
* on the total amount of L2 to L1 messages in the block. i.e. the length of _path is equal to the depth of the
* L1 to L2 message tree.
*/
function consume(
DataStructures.L2ToL1Msg calldata _message,
uint256 _l2BlockNumber,
uint256 _leafIndex,
bytes32[] calldata _path
) external;
// docs:end:outbox_consume
// docs:start:outbox_has_message_been_consumed_at_block_and_index
/**
* @notice Checks to see if an L2 to L1 message in a specific block has been consumed
* @dev - This function does not throw. Out-of-bounds access is considered valid, but will always return false
* @param _l2BlockNumber - The block number specifying the block that contains the message we want to check
* @param _leafId - The unique id of the message leaf
*/
function hasMessageBeenConsumedAtBlock(uint256 _l2BlockNumber, uint256 _leafId) external view returns (bool);
// docs:end:outbox_has_message_been_consumed_at_block_and_index
/**
* @notice Fetch the root data for a given block number
* Returns (0, 0) if the block is not proven
*
* @param _l2BlockNumber - The block number to fetch the root data for
*
* @return bytes32 - The root of the merkle tree containing the L2 to L1 messages
*/
function getRootData(uint256 _l2BlockNumber) external view returns (bytes32);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {CompressedFeeHeader, FeeHeader, FeeHeaderLib} from "@aztec/core/libraries/compressed-data/fees/FeeStructs.sol";
import {CompressedSlot, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {Slot} from "@aztec/shared/libraries/TimeMath.sol";
/**
* @notice Struct for storing block data, set in proposal.
* @param archive - Archive tree root of the block
* @param headerHash - Hash of the proposed block header
* @param blobCommitmentsHash - H(...H(H(commitment_0), commitment_1).... commitment_n) - used to validate we are using
* the same blob commitments on L1 and in the rollup circuit
* @param attestationsHash - Hash of the attestations for this block
* @param payloadDigest - Digest of the proposal payload that was attested to
* @param slotNumber - This block's slot
*/
struct BlockLog {
bytes32 archive;
bytes32 headerHash;
bytes32 blobCommitmentsHash;
bytes32 attestationsHash;
bytes32 payloadDigest;
Slot slotNumber;
FeeHeader feeHeader;
}
struct TempBlockLog {
bytes32 headerHash;
bytes32 blobCommitmentsHash;
bytes32 attestationsHash;
bytes32 payloadDigest;
Slot slotNumber;
FeeHeader feeHeader;
}
struct CompressedTempBlockLog {
bytes32 headerHash;
bytes32 blobCommitmentsHash;
bytes32 attestationsHash;
bytes32 payloadDigest;
CompressedSlot slotNumber;
CompressedFeeHeader feeHeader;
}
library CompressedTempBlockLogLib {
using CompressedTimeMath for Slot;
using CompressedTimeMath for CompressedSlot;
using FeeHeaderLib for FeeHeader;
using FeeHeaderLib for CompressedFeeHeader;
function compress(TempBlockLog memory _blockLog) internal pure returns (CompressedTempBlockLog memory) {
return CompressedTempBlockLog({
headerHash: _blockLog.headerHash,
blobCommitmentsHash: _blockLog.blobCommitmentsHash,
attestationsHash: _blockLog.attestationsHash,
payloadDigest: _blockLog.payloadDigest,
slotNumber: _blockLog.slotNumber.compress(),
feeHeader: _blockLog.feeHeader.compress()
});
}
function decompress(CompressedTempBlockLog memory _compressedBlockLog) internal pure returns (TempBlockLog memory) {
return TempBlockLog({
headerHash: _compressedBlockLog.headerHash,
blobCommitmentsHash: _compressedBlockLog.blobCommitmentsHash,
attestationsHash: _compressedBlockLog.attestationsHash,
payloadDigest: _compressedBlockLog.payloadDigest,
slotNumber: _compressedBlockLog.slotNumber.decompress(),
feeHeader: _compressedBlockLog.feeHeader.decompress()
});
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.27;
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
type CompressedStakingQueueConfig is uint256;
/**
* If the number of validators in the rollup is 0, and the number of validators in the queue is less than
* `bootstrapValidatorSetSize`, then `getEntryQueueFlushSize` will return 0.
*
* If the number of validators in the rollup is 0, and the number of validators in the queue is greater than or equal to
* `bootstrapValidatorSetSize`, then `getEntryQueueFlushSize` will return `bootstrapFlushSize`.
*
* If the number of validators in the rollup is greater than 0 and less than `bootstrapValidatorSetSize`, then
* `getEntryQueueFlushSize` will return `bootstrapFlushSize`.
*
* If the number of validators in the rollup is greater than or equal to `bootstrapValidatorSetSize`, then
* `getEntryQueueFlushSize` will return Max( `normalFlushSizeMin`, `activeAttesterCount` / `normalFlushSizeQuotient`).
*
* NOTE: If the normalFlushSizeMin is 0 and the validator set is empty, above will return max(0, 0) and it won't be
* possible to add validators. This can close the queue even if there are members in the validator set if a very high
* `normalFlushSizeQuotient` is used.
*
* NOTE: We will NEVER flush more than `maxQueueFlushSize` validators: it is applied as a Max at the end of every
* calculation.
* This can be used to prevent a situation where flushing the queue would exceed the block gas limit.
*/
struct StakingQueueConfig {
uint256 bootstrapValidatorSetSize;
uint256 bootstrapFlushSize;
uint256 normalFlushSizeMin;
uint256 normalFlushSizeQuotient;
uint256 maxQueueFlushSize;
}
library StakingQueueConfigLib {
using SafeCast for uint256;
uint256 private constant MASK_32BIT = 0xFFFFFFFF;
function compress(StakingQueueConfig memory _config) internal pure returns (CompressedStakingQueueConfig) {
uint256 value = 0;
value |= uint256(_config.maxQueueFlushSize.toUint32());
value |= uint256(_config.normalFlushSizeQuotient.toUint32()) << 32;
value |= uint256(_config.normalFlushSizeMin.toUint32()) << 64;
value |= uint256(_config.bootstrapFlushSize.toUint32()) << 96;
value |= uint256(_config.bootstrapValidatorSetSize.toUint32()) << 128;
return CompressedStakingQueueConfig.wrap(value);
}
function decompress(CompressedStakingQueueConfig _compressedConfig) internal pure returns (StakingQueueConfig memory) {
uint256 value = CompressedStakingQueueConfig.unwrap(_compressedConfig);
return StakingQueueConfig({
bootstrapValidatorSetSize: (value >> 128) & MASK_32BIT,
bootstrapFlushSize: (value >> 96) & MASK_32BIT,
normalFlushSizeMin: (value >> 64) & MASK_32BIT,
normalFlushSizeQuotient: (value >> 32) & MASK_32BIT,
maxQueueFlushSize: value & MASK_32BIT
});
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
struct ChainTips {
uint256 pendingBlockNumber;
uint256 provenBlockNumber;
}
type CompressedChainTips is uint256;
library ChainTipsLib {
using SafeCast for uint256;
uint256 internal constant PENDING_BLOCK_NUMBER_MASK =
0xffffffffffffffffffffffffffffffff00000000000000000000000000000000;
uint256 internal constant PROVEN_BLOCK_NUMBER_MASK = 0xffffffffffffffffffffffffffffffff;
function getPendingBlockNumber(CompressedChainTips _compressedChainTips) internal pure returns (uint256) {
return CompressedChainTips.unwrap(_compressedChainTips) >> 128;
}
function getProvenBlockNumber(CompressedChainTips _compressedChainTips) internal pure returns (uint256) {
return CompressedChainTips.unwrap(_compressedChainTips) & PROVEN_BLOCK_NUMBER_MASK;
}
function updatePendingBlockNumber(CompressedChainTips _compressedChainTips, uint256 _pendingBlockNumber)
internal
pure
returns (CompressedChainTips)
{
uint256 value = CompressedChainTips.unwrap(_compressedChainTips) & ~PENDING_BLOCK_NUMBER_MASK;
return CompressedChainTips.wrap(value | (uint256(_pendingBlockNumber.toUint128()) << 128));
}
function updateProvenBlockNumber(CompressedChainTips _compressedChainTips, uint256 _provenBlockNumber)
internal
pure
returns (CompressedChainTips)
{
uint256 value = CompressedChainTips.unwrap(_compressedChainTips) & ~PROVEN_BLOCK_NUMBER_MASK;
return CompressedChainTips.wrap(value | _provenBlockNumber.toUint128());
}
function compress(ChainTips memory _chainTips) internal pure returns (CompressedChainTips) {
// We are doing cast to uint128 but inside a uint256 to not wreck the shifting.
uint256 pending = _chainTips.pendingBlockNumber.toUint128();
uint256 proven = _chainTips.provenBlockNumber.toUint128();
return CompressedChainTips.wrap((pending << 128) | proven);
}
function decompress(CompressedChainTips _compressedChainTips) internal pure returns (ChainTips memory) {
return ChainTips({
pendingBlockNumber: getPendingBlockNumber(_compressedChainTips),
provenBlockNumber: getProvenBlockNumber(_compressedChainTips)
});
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity ^0.8.27;
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {Signature, SignatureLib} from "@aztec/shared/libraries/SignatureLib.sol";
uint256 constant SIGNATURE_LENGTH = 65; // v (1) + r (32) + s (32)
uint256 constant ADDRESS_LENGTH = 20;
/**
* @notice The domain separator for the signatures
*/
enum SignatureDomainSeparator {
blockProposal,
blockAttestation,
attestationsAndSigners
}
// A committee attestation can be made up of a signature and an address.
// Committee members that have attested will produce a signature, and if they have not attested, the signature will be
// empty and an address provided.
struct CommitteeAttestation {
address addr;
Signature signature;
}
struct CommitteeAttestations {
// bitmap of which indices are signatures
bytes signatureIndices;
// tightly packed signatures and addresses
bytes signaturesOrAddresses;
}
library AttestationLib {
using SignatureLib for Signature;
/**
* @notice Checks if the given CommitteeAttestations is empty
* Wll return true if either component is empty as they are needed together.
* @param _attestations - The committee attestations
* @return True if the committee attestations are empty, false otherwise
*/
function isEmpty(CommitteeAttestations memory _attestations) internal pure returns (bool) {
return _attestations.signatureIndices.length == 0 || _attestations.signaturesOrAddresses.length == 0;
}
/**
* @notice Checks if the given index in the CommitteeAttestations is a signature
* @param _attestations - The committee attestations
* @param _index - The index to check
* @return True if the index is a signature, false otherwise
*
* @dev The signatureIndices is a bitmap of which indices are signatures.
* The index is a signature if the bit at the index is 1.
* The index is an address if the bit at the index is 0.
*
* See its use over in ValidatorSelectionLib.sol
*/
function isSignature(CommitteeAttestations memory _attestations, uint256 _index) internal pure returns (bool) {
uint256 byteIndex = _index / 8;
uint256 shift = 7 - (_index % 8);
return (uint8(_attestations.signatureIndices[byteIndex]) >> shift) & 1 == 1;
}
/**
* @notice Gets the signature at the given index
* @param _attestations - The committee attestations
* @param _index - The index of the signature to get
*/
function getSignature(CommitteeAttestations memory _attestations, uint256 _index)
internal
pure
returns (Signature memory)
{
bytes memory signaturesOrAddresses = _attestations.signaturesOrAddresses;
require(isSignature(_attestations, _index), Errors.AttestationLib__NotASignatureAtIndex(_index));
uint256 dataPtr;
assembly {
// Skip length
dataPtr := add(signaturesOrAddresses, 0x20)
}
// Move to the start of the signature
for (uint256 i = 0; i < _index; ++i) {
dataPtr += isSignature(_attestations, i) ? SIGNATURE_LENGTH : ADDRESS_LENGTH;
}
uint8 v;
bytes32 r;
bytes32 s;
assembly {
v := byte(0, mload(dataPtr))
dataPtr := add(dataPtr, 1)
r := mload(dataPtr)
dataPtr := add(dataPtr, 32)
s := mload(dataPtr)
}
return Signature({v: v, r: r, s: s});
}
/**
* @notice Gets the address at the given index
* @param _attestations - The committee attestations
* @param _index - The index of the address to get
*/
function getAddress(CommitteeAttestations memory _attestations, uint256 _index) internal pure returns (address) {
bytes memory signaturesOrAddresses = _attestations.signaturesOrAddresses;
require(!isSignature(_attestations, _index), Errors.AttestationLib__NotAnAddressAtIndex(_index));
uint256 dataPtr;
assembly {
// Skip length
dataPtr := add(signaturesOrAddresses, 0x20)
}
// Move to the start of the signature
for (uint256 i = 0; i < _index; ++i) {
dataPtr += isSignature(_attestations, i) ? SIGNATURE_LENGTH : ADDRESS_LENGTH;
}
address addr;
assembly {
addr := shr(96, mload(dataPtr))
}
return addr;
}
/**
* Recovers the committee from the addresses in the attestations and signers.
*
* @custom:reverts SignatureIndicesSizeMismatch if the signature indices have a wrong size
* @custom:reverts OutOfBounds throws if reading data beyond the `_attestations`
* @custom:reverts SignaturesOrAddressesSizeMismatch if the signatures or addresses object has wrong size
*
* @param _attestations - The committee attestations
* @param _signers The addresses of the committee members that signed the attestations. Provided in order to not have
* to recover them from their attestations' signatures (and hence save gas). The addresses of the non-signing
* committee members are directly included in the attestations.
* @param _length - The number of addresses to return, should match the number of committee members
* @return The addresses of the committee members.
*/
function reconstructCommitteeFromSigners(
CommitteeAttestations memory _attestations,
address[] memory _signers,
uint256 _length
) internal pure returns (address[] memory) {
uint256 bitmapBytes = (_length + 7) / 8; // Round up to nearest byte
require(
bitmapBytes == _attestations.signatureIndices.length,
Errors.AttestationLib__SignatureIndicesSizeMismatch(bitmapBytes, _attestations.signatureIndices.length)
);
// To get a ref that we can easily use with the assembly down below.
bytes memory signaturesOrAddresses = _attestations.signaturesOrAddresses;
address[] memory addresses = new address[](_length);
uint256 signersIndex;
uint256 dataPtr;
uint256 currentByte;
uint256 bitMask;
assembly {
// Skip length
dataPtr := add(signaturesOrAddresses, 0x20)
}
uint256 offset = dataPtr;
for (uint256 i = 0; i < _length; ++i) {
// Load new byte every 8 iterations
if (i % 8 == 0) {
uint256 byteIndex = i / 8;
currentByte = uint8(_attestations.signatureIndices[byteIndex]);
bitMask = 128; // 0b10000000
}
bool isSignatureFlag = (currentByte & bitMask) != 0;
bitMask >>= 1;
if (isSignatureFlag) {
dataPtr += SIGNATURE_LENGTH;
addresses[i] = _signers[signersIndex];
signersIndex++;
} else {
address addr;
assembly {
addr := shr(96, mload(dataPtr))
dataPtr := add(dataPtr, 20)
}
addresses[i] = addr;
}
}
// Ensure that the size of data provided actually matches what we expect
uint256 sizeOfSignaturesAndAddresses =
(signersIndex * SIGNATURE_LENGTH) + ((_length - signersIndex) * ADDRESS_LENGTH);
require(
sizeOfSignaturesAndAddresses == _attestations.signaturesOrAddresses.length,
Errors.AttestationLib__SignaturesOrAddressesSizeMismatch(
sizeOfSignaturesAndAddresses, _attestations.signaturesOrAddresses.length
)
);
require(signersIndex == _signers.length, Errors.AttestationLib__SignersSizeMismatch(signersIndex, _signers.length));
// Ensure that the reads were within the boundaries of the data, and that we have read all the data.
// This check is an extra precaution. There are two cases, we we would end up with an invalid
// read, and both should be covered by the above checks.
// 1. If trying to read beyond the expected data, the bitmap must have more ones than signatures,
// but this will make the the `sizeOfSignaturesAndAddresses` larger than passed data.
// 2. If trying to read less than expected data, the bitmap must have fewer ones than signatures,
// but this will make the the `sizeOfSignaturesAndAddresses` smaller than passed data.
uint256 upperLimit = offset + _attestations.signaturesOrAddresses.length;
require(dataPtr == upperLimit, Errors.AttestationLib__InvalidDataSize(dataPtr - offset, upperLimit - offset));
return addresses;
}
function getAttestationsAndSignersDigest(CommitteeAttestations memory _attestations, address[] memory _signers)
internal
pure
returns (bytes32)
{
return keccak256(abi.encode(SignatureDomainSeparator.attestationsAndSigners, _attestations, _signers));
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {BlobLib} from "@aztec-blob-lib/BlobLib.sol";
import {
EthValue,
FeeAssetValue,
FeeAssetPerEthE9,
CompressedFeeConfig,
FeeConfigLib,
FeeConfig,
PriceLib
} from "@aztec/core/libraries/compressed-data/fees/FeeConfig.sol";
import {
L1FeeData,
CompressedL1FeeData,
L1GasOracleValues,
FeeStructsLib,
FeeHeader,
CompressedFeeHeader,
FeeHeaderLib
} from "@aztec/core/libraries/compressed-data/fees/FeeStructs.sol";
import {CompressedSlot, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {Math} from "@oz/utils/math/Math.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {SignedMath} from "@oz/utils/math/SignedMath.sol";
import {Errors} from "./../Errors.sol";
import {Slot, Timestamp, TimeLib} from "./../TimeLib.sol";
import {STFLib} from "./STFLib.sol";
// The lowest number of fee asset per eth is 10 with a precision of 1e9.
uint256 constant MINIMUM_FEE_ASSET_PER_ETH = 10e9;
uint256 constant MAX_FEE_ASSET_PRICE_MODIFIER = 1e6;
uint256 constant FEE_ASSET_PRICE_UPDATE_FRACTION = 100e6;
uint256 constant L1_GAS_PER_BLOCK_PROPOSED = 300_000;
uint256 constant L1_GAS_PER_EPOCH_VERIFIED = 1_000_000;
uint256 constant MINIMUM_CONGESTION_MULTIPLIER = 1e9;
// The magic values are used to have the fakeExponential case where
// (numerator / denominator) is close to 0.117, as that leads to ~1.125 multiplier
// per increase by TARGET of the numerator;
uint256 constant MAGIC_CONGESTION_VALUE_DIVISOR = 1e8;
uint256 constant MAGIC_CONGESTION_VALUE_MULTIPLIER = 854_700_854;
uint256 constant BLOB_GAS_PER_BLOB = 2 ** 17;
uint256 constant BLOBS_PER_BLOCK = 3;
struct OracleInput {
int256 feeAssetPriceModifier;
}
struct ManaBaseFeeComponents {
uint256 congestionCost;
uint256 congestionMultiplier;
uint256 sequencerCost;
uint256 proverCost;
}
struct FeeStore {
CompressedFeeConfig config;
L1GasOracleValues l1GasOracleValues;
mapping(uint256 blockNumber => CompressedFeeHeader feeHeader) feeHeaders;
}
library FeeLib {
using Math for uint256;
using SafeCast for int256;
using SafeCast for uint256;
using SignedMath for int256;
using PriceLib for EthValue;
using TimeLib for Slot;
using TimeLib for Timestamp;
using FeeHeaderLib for FeeHeader;
using FeeHeaderLib for CompressedFeeHeader;
using CompressedTimeMath for CompressedSlot;
using CompressedTimeMath for Slot;
using FeeStructsLib for L1FeeData;
using FeeStructsLib for CompressedL1FeeData;
using FeeConfigLib for FeeConfig;
using FeeConfigLib for CompressedFeeConfig;
Slot internal constant LIFETIME = Slot.wrap(5);
Slot internal constant LAG = Slot.wrap(2);
bytes32 private constant FEE_STORE_POSITION = keccak256("aztec.fee.storage");
function initialize(uint256 _manaTarget, EthValue _provingCostPerMana) internal {
FeeStore storage feeStore = getStorage();
feeStore.config = FeeConfig({
manaTarget: _manaTarget,
congestionUpdateFraction: _manaTarget * MAGIC_CONGESTION_VALUE_MULTIPLIER / MAGIC_CONGESTION_VALUE_DIVISOR,
provingCostPerMana: _provingCostPerMana
}).compress();
feeStore.l1GasOracleValues = L1GasOracleValues({
pre: L1FeeData({baseFee: 1 gwei, blobFee: 1}).compress(),
post: L1FeeData({baseFee: block.basefee, blobFee: BlobLib.getBlobBaseFee()}).compress(),
slotOfChange: LIFETIME.compress()
});
}
function updateManaTarget(uint256 _manaTarget) internal {
FeeStore storage feeStore = getStorage();
FeeConfig memory config = feeStore.config.decompress();
config.manaTarget = _manaTarget;
config.congestionUpdateFraction = _manaTarget * MAGIC_CONGESTION_VALUE_MULTIPLIER / MAGIC_CONGESTION_VALUE_DIVISOR;
feeStore.config = config.compress();
}
function updateProvingCostPerMana(EthValue _provingCostPerMana) internal {
FeeStore storage feeStore = getStorage();
FeeConfig memory config = feeStore.config.decompress();
config.provingCostPerMana = _provingCostPerMana;
feeStore.config = config.compress();
}
function updateL1GasFeeOracle() internal {
Slot slot = Timestamp.wrap(block.timestamp).slotFromTimestamp();
// The slot where we find a new queued value acceptable
FeeStore storage feeStore = getStorage();
Slot acceptableSlot = feeStore.l1GasOracleValues.slotOfChange.decompress() + (LIFETIME - LAG);
if (slot < acceptableSlot) {
return;
}
feeStore.l1GasOracleValues = L1GasOracleValues({
pre: feeStore.l1GasOracleValues.post,
post: L1FeeData({baseFee: block.basefee, blobFee: BlobLib.getBlobBaseFee()}).compress(),
slotOfChange: (slot + LAG).compress()
});
}
function computeFeeHeader(
uint256 _blockNumber,
int256 _feeAssetPriceModifier,
uint256 _manaUsed,
uint256 _congestionCost,
uint256 _proverCost
) internal view returns (FeeHeader memory) {
require(
SignedMath.abs(_feeAssetPriceModifier) <= MAX_FEE_ASSET_PRICE_MODIFIER,
Errors.FeeLib__InvalidFeeAssetPriceModifier()
);
CompressedFeeHeader parentFeeHeader = STFLib.getFeeHeader(_blockNumber - 1);
return FeeHeader({
excessMana: FeeLib.computeExcessMana(parentFeeHeader),
feeAssetPriceNumerator: FeeLib.clampedAdd(parentFeeHeader.getFeeAssetPriceNumerator(), _feeAssetPriceModifier),
manaUsed: _manaUsed,
congestionCost: _congestionCost,
proverCost: _proverCost
});
}
function getL1FeesAt(Timestamp _timestamp) internal view returns (L1FeeData memory) {
FeeStore storage feeStore = getStorage();
return _timestamp.slotFromTimestamp() < feeStore.l1GasOracleValues.slotOfChange.decompress()
? feeStore.l1GasOracleValues.pre.decompress()
: feeStore.l1GasOracleValues.post.decompress();
}
function getManaBaseFeeComponentsAt(uint256 _blockOfInterest, Timestamp _timestamp, bool _inFeeAsset)
internal
view
returns (ManaBaseFeeComponents memory)
{
FeeStore storage feeStore = getStorage();
uint256 manaTarget = feeStore.config.getManaTarget();
if (manaTarget == 0) {
return ManaBaseFeeComponents({sequencerCost: 0, proverCost: 0, congestionCost: 0, congestionMultiplier: 0});
}
EthValue sequencerCostPerMana;
EthValue proverCostPerMana;
EthValue total;
{
L1FeeData memory fees = FeeLib.getL1FeesAt(_timestamp);
// Sequencer cost per mana
{
uint256 ethUsed =
(L1_GAS_PER_BLOCK_PROPOSED * fees.baseFee) + (BLOBS_PER_BLOCK * BLOB_GAS_PER_BLOB * fees.blobFee);
sequencerCostPerMana = EthValue.wrap(Math.mulDiv(ethUsed, 1, manaTarget, Math.Rounding.Ceil));
}
// Prover cost per mana
{
proverCostPerMana = EthValue.wrap(
Math.mulDiv(
Math.mulDiv(L1_GAS_PER_EPOCH_VERIFIED, fees.baseFee, TimeLib.getStorage().epochDuration, Math.Rounding.Ceil),
1,
manaTarget,
Math.Rounding.Ceil
)
) + feeStore.config.getProvingCostPerMana();
}
total = sequencerCostPerMana + proverCostPerMana;
}
CompressedFeeHeader parentFeeHeader = STFLib.getFeeHeader(_blockOfInterest);
uint256 excessMana =
FeeLib.clampedAdd(parentFeeHeader.getExcessMana() + parentFeeHeader.getManaUsed(), -int256(manaTarget));
uint256 congestionMultiplier_ = congestionMultiplier(excessMana);
EthValue congestionCost = EthValue.wrap(
Math.mulDiv(EthValue.unwrap(total), congestionMultiplier_, MINIMUM_CONGESTION_MULTIPLIER, Math.Rounding.Floor)
) - total;
FeeAssetPerEthE9 feeAssetPrice =
_inFeeAsset ? FeeLib.getFeeAssetPerEthAtBlock(_blockOfInterest) : FeeAssetPerEthE9.wrap(1e9);
return ManaBaseFeeComponents({
sequencerCost: FeeAssetValue.unwrap(sequencerCostPerMana.toFeeAsset(feeAssetPrice)),
proverCost: FeeAssetValue.unwrap(proverCostPerMana.toFeeAsset(feeAssetPrice)),
congestionCost: FeeAssetValue.unwrap(congestionCost.toFeeAsset(feeAssetPrice)),
congestionMultiplier: congestionMultiplier_
});
}
function isTxsEnabled() internal view returns (bool) {
// If the target is 0, the limit is 0. And no transactions can enter
return getManaTarget() > 0;
}
function getManaTarget() internal view returns (uint256) {
return getStorage().config.getManaTarget();
}
function getManaLimit() internal view returns (uint256) {
FeeStore storage feeStore = getStorage();
return feeStore.config.getManaTarget() * 2;
}
function getProvingCostPerMana() internal view returns (EthValue) {
return getStorage().config.getProvingCostPerMana();
}
function getFeeAssetPerEthAtBlock(uint256 _blockNumber) internal view returns (FeeAssetPerEthE9) {
return getFeeAssetPerEth(STFLib.getFeeHeader(_blockNumber).getFeeAssetPriceNumerator());
}
function computeExcessMana(CompressedFeeHeader _feeHeader) internal view returns (uint256) {
FeeStore storage feeStore = getStorage();
return clampedAdd(_feeHeader.getExcessMana() + _feeHeader.getManaUsed(), -int256(feeStore.config.getManaTarget()));
}
function congestionMultiplier(uint256 _numerator) internal view returns (uint256) {
FeeStore storage feeStore = getStorage();
return fakeExponential(MINIMUM_CONGESTION_MULTIPLIER, _numerator, feeStore.config.getCongestionUpdateFraction());
}
function getFeeAssetPerEth(uint256 _numerator) internal pure returns (FeeAssetPerEthE9) {
return
FeeAssetPerEthE9.wrap(fakeExponential(MINIMUM_FEE_ASSET_PER_ETH, _numerator, FEE_ASSET_PRICE_UPDATE_FRACTION));
}
function summedBaseFee(ManaBaseFeeComponents memory _components) internal pure returns (uint256) {
return _components.sequencerCost + _components.proverCost + _components.congestionCost;
}
function getStorage() internal pure returns (FeeStore storage storageStruct) {
bytes32 position = FEE_STORE_POSITION;
assembly {
storageStruct.slot := position
}
}
/**
* @notice Clamps the addition of a signed integer to a uint256
* Useful for running values, whose minimum value will be 0
* but should not throw if going below.
* @param _a The base value
* @param _b The value to add
* @return The clamped value
*/
function clampedAdd(uint256 _a, int256 _b) internal pure returns (uint256) {
if (_b >= 0) {
return _a + _b.toUint256();
}
uint256 sub = SignedMath.abs(_b);
if (_a > sub) {
return _a - sub;
}
return 0;
}
/**
* @notice An approximation of the exponential function: factor * e ** (numerator / denominator)
*
* The function is the same as used in EIP-4844
* https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md
*
* Approximated using a taylor series.
* For shorthand below, let `a = factor`, `x = numerator`, `d = denominator`
*
* f(x) = a
* + (a * x) / d
* + (a * x ** 2) / (2 * d ** 2)
* + (a * x ** 3) / (6 * d ** 3)
* + (a * x ** 4) / (24 * d ** 4)
* + (a * x ** 5) / (120 * d ** 5)
* + ...
*
* For integer precision purposes, we will multiply by the denominator for intermediary steps and then
* finally do a division by it.
* The notation below might look slightly strange, but it is to try to convey the program flow below.
*
* e(x) = ( a * d
* + a * d * x / d
* + ((a * d * x / d) * x) / (2 * d)
* + ((((a * d * x / d) * x) / (2 * d)) * x) / (3 * d)
* + ((((((a * d * x / d) * x) / (2 * d)) * x) / (3 * d)) * x) / (4 * d)
* + ((((((((a * d * x / d) * x) / (2 * d)) * x) / (3 * d)) * x) / (4 * d)) * x) / (5 * d)
* + ...
* ) / d
*
* The notation might make it a bit of a pain to look at, but f(x) and e(x) are the same.
* Gotta love integer math.
*
* @dev Notice that as _numerator grows, the computation will quickly overflow.
* As long as the `_denominator` is fairly small, it won't bring us back down to not overflow
* For our purposes, this is acceptable, as if we have a fee that is so high that it would overflow and throw
* then we would have other problems.
*
* @param _factor The base value
* @param _numerator The numerator
* @param _denominator The denominator
* @return The approximated value `_factor * e ** (_numerator / _denominator)`
*/
function fakeExponential(uint256 _factor, uint256 _numerator, uint256 _denominator) private pure returns (uint256) {
uint256 i = 1;
uint256 output = 0;
uint256 numeratorAccumulator = _factor * _denominator;
while (numeratorAccumulator > 0) {
output += numeratorAccumulator;
numeratorAccumulator = (numeratorAccumulator * _numerator) / (_denominator * i);
i += 1;
}
return output / _denominator;
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {Hash} from "@aztec/core/libraries/crypto/Hash.sol";
import {Slot, Timestamp} from "@aztec/core/libraries/TimeLib.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
struct AppendOnlyTreeSnapshot {
bytes32 root;
uint32 nextAvailableLeafIndex;
}
struct PartialStateReference {
AppendOnlyTreeSnapshot noteHashTree;
AppendOnlyTreeSnapshot nullifierTree;
AppendOnlyTreeSnapshot publicDataTree;
}
struct StateReference {
AppendOnlyTreeSnapshot l1ToL2MessageTree;
// Note: Can't use "partial" name here as in protocol specs because it is a reserved solidity keyword
PartialStateReference partialStateReference;
}
struct GasFees {
uint128 feePerDaGas;
uint128 feePerL2Gas;
}
struct ContentCommitment {
bytes32 blobsHash;
bytes32 inHash;
bytes32 outHash;
}
struct ProposedHeader {
bytes32 lastArchiveRoot;
ContentCommitment contentCommitment;
Slot slotNumber;
Timestamp timestamp;
address coinbase;
bytes32 feeRecipient;
GasFees gasFees;
uint256 totalManaUsed;
}
/**
* @title ProposedHeader Library
* @author Aztec Labs
* @notice Decoding and validating a proposed L2 block header
*/
library ProposedHeaderLib {
using SafeCast for uint256;
/**
* @notice Hash the proposed header
*
* @dev The hashing here MUST match what is in the proposed_block_header.ts
*
* @param _header The header to hash
*
* @return The hash of the header
*/
function hash(ProposedHeader memory _header) internal pure returns (bytes32) {
return Hash.sha256ToField(
abi.encodePacked(
_header.lastArchiveRoot,
_header.contentCommitment.blobsHash,
_header.contentCommitment.inHash,
_header.contentCommitment.outHash,
_header.slotNumber,
Timestamp.unwrap(_header.timestamp).toUint64(),
_header.coinbase,
_header.feeRecipient,
_header.gasFees.feePerDaGas,
_header.gasFees.feePerL2Gas,
_header.totalManaUsed
)
);
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {BlobLib} from "@aztec-blob-lib/BlobLib.sol";
import {RollupStore, IRollupCore, BlockHeaderValidationFlags} from "@aztec/core/interfaces/IRollup.sol";
import {TempBlockLog} from "@aztec/core/libraries/compressed-data/BlockLog.sol";
import {FeeHeader} from "@aztec/core/libraries/compressed-data/fees/FeeStructs.sol";
import {ChainTipsLib, CompressedChainTips} from "@aztec/core/libraries/compressed-data/Tips.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {SignatureDomainSeparator, CommitteeAttestations} from "@aztec/core/libraries/rollup/AttestationLib.sol";
import {OracleInput, FeeLib, ManaBaseFeeComponents} from "@aztec/core/libraries/rollup/FeeLib.sol";
import {ValidatorSelectionLib} from "@aztec/core/libraries/rollup/ValidatorSelectionLib.sol";
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
import {CompressedSlot, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {Signature} from "@aztec/shared/libraries/SignatureLib.sol";
import {ProposedHeader, ProposedHeaderLib, StateReference} from "./ProposedHeaderLib.sol";
import {STFLib} from "./STFLib.sol";
struct ProposeArgs {
bytes32 archive;
// Including stateReference here so that the archiver can reconstruct the full block header.
// It doesn't need to be in the proposed header as the values are not used in propose() and they are committed to
// by the last archive and blobs hash.
// It can be removed if the archiver can refer to world state for the updated roots.
StateReference stateReference;
OracleInput oracleInput;
ProposedHeader header;
}
struct ProposePayload {
bytes32 archive;
StateReference stateReference;
OracleInput oracleInput;
bytes32 headerHash;
}
struct InterimProposeValues {
ProposedHeader header;
bytes32[] blobHashes;
bytes32 blobsHashesCommitment;
bytes[] blobCommitments;
bytes32 inHash;
bytes32 headerHash;
bytes32 attestationsHash;
bytes32 payloadDigest;
Epoch currentEpoch;
bool isFirstBlockOfEpoch;
bool isTxsEnabled;
}
/**
* @param header - The proposed block header
* @param digest - The digest that signatures signed
* @param currentTime - The time of execution
* @param blobsHashesCommitment - The blobs hash for this block, provided for simpler future simulation
* @param flags - Flags specific to the execution, whether certain checks should be skipped
*/
struct ValidateHeaderArgs {
ProposedHeader header;
bytes32 digest;
uint256 manaBaseFee;
bytes32 blobsHashesCommitment;
BlockHeaderValidationFlags flags;
}
/**
* @title ProposeLib
* @author Aztec Labs
* @notice Library responsible for handling the L2 block proposal flow in the Aztec rollup.
*
* @dev This library implements the core block proposal mechanism that allows designated proposers to submit
* new L2 blocks to extend the rollup chain. It orchestrates the entire proposal process including:
* - Blob validation and commitment calculation
* - Header validation against chain state and timing constraints
* - Validator selection and proposer verification
* - Fee calculation and mana consumption tracking
* - State transitions and archive updates
* - Message processing between L1 and L2 via the Inbox and Outbox contracts
*
* The proposal flow operates within Aztec's time-based model where:
* - Each slot has a designated proposer selected from the validator set
* - Blocks must be proposed in the correct time slot and build on the current chain tip
* - Proposers must provide valid attestations from committee members
* - All state transitions are atomically applied upon successful validation
*
* Key functions:
* - `propose`: Main entry point called from `RollupCore.propose`.
* Handles the complete block proposal process from validation to state updates.
* - `validateHeader`: Validates block header against chain state, timing, and fee requirements.
* Called internally from `propose`, and externally from `RollupCore.validateHeaderWithAttestations`,
* used by proposers to ensure the header is valid before submitting the tx.
*
* Dependencies on other main libraries:
* - STFLib: State Transition Function library for chain state management, pruning, and storage access
* - FeeLib: Fee calculation library for mana pricing, L1 gas oracles, and fee header computation
* - ValidatorSelectionLib: Validator and committee management for epoch setup and proposer verification
* - BlobLib: Blob commitment validation and hash calculation for data availability
* - ProposedHeaderLib: Block header hashing and validation utilities
*
* Security considerations:
* - Only the designated proposer for the current slot can propose a block, enforced by
* validating the proposer validator signature among attestations. All other attestations are not
* verified on chain until time of proof submission.
* - Each block must built on the immediate previous one, ensuring no forks. This is enforced by checking
* the last archive root and block numbers. If the previous block is invalid, the proposer is expected to
* first invalidate it.
* - Blob commitments are validated, to ensure that the values provided correctly match the actual blobs published
*/
library ProposeLib {
using TimeLib for Timestamp;
using TimeLib for Slot;
using TimeLib for Epoch;
using CompressedTimeMath for CompressedSlot;
using ChainTipsLib for CompressedChainTips;
/**
* @notice Publishes a new L2 block to the pending chain.
* @dev Handles a proposed L2 block, validates it, and updates rollup state adding it to the pending chain.
* Orchestrates blob validation, header validation, proposer verification, fee calculations, and state
* transitions. Automatically prunes unproven blocks if the proof submission window has passed.
*
* Note that some validations and processes are disabled if the chain is configured to run without
* transactions, such as during ignition phase:
* - No fee header computation or L1 gas fee oracle update
* - No inbox message consumption or outbox message insertion
*
* Validations performed:
* - Blob commitments against provided blob data: Errors.Rollup__InvalidBlobHash,
* Errors.Rollup__InvalidBlobProof
* - Block header validations (see validateHeader function for details)
* - Proposer signature is valid for designated slot proposer:
* Errors.ValidatorSelection__MissingProposerSignature
* - Inbox hash matches expected value (when txs enabled): Errors.Rollup__InvalidInHash
*
* Validations NOT performed:
* - Committee attestations (only proposer signature verified)
* - Transaction validity and state root computation (done at proof submission via a validity proof)
*
* State changes:
* - Increment pending block number
* - Store archive root for the new block number
* - Store block metadata in circular storage (TempBlockLog)
* - Update L1 gas fee oracle (when txs enabled)
* - Consume inbox messages (when txs enabled)
* - Insert outbox messages (when txs enabled)
* - Setup epoch for validator selection (first block of the epoch)
*
* @param _args - The arguments to propose the block
* @param _attestations - Committee attestations in a packed format:
* - Contains an array of length equal to the committee size
* - At position `i`: if committee member `i` attested, contains their signature over the digest;
* if not, contains their address
* - Includes a bitmap indicating whether position `i` contains a signature (true) or address (false)
* - This format allows reconstructing the committee commitment (hash of all committee addresses)
* by either recovering addresses from signatures or using the addresses
* @param _signers - Addresses of the signers in the attestations:
* - Must match the addresses that would be recovered from signatures in _attestations
* - Same length as the number of signatures in _attestations
* - Used to verify that the proposer is one of the committee members by allowing cheap reconstruction of the
* commitment
* - Allows computing committee commitment without expensive signature recovery on-chain thus saving gas
* - Nodes must validate actual signatures off-chain when downloading blocks
* @param _blobsInput - The bytes to verify our input blob commitments match real blobs:
* - input[:1] - num blobs in block
* - input[1:] - blob commitments (48 bytes * num blobs in block)
* @param _checkBlob - Whether to skip blob related checks. Hardcoded to true in RollupCore, exists only to be
* overridden in tests
*/
function propose(
ProposeArgs calldata _args,
CommitteeAttestations memory _attestations,
address[] memory _signers,
Signature calldata _attestationsAndSignersSignature,
bytes calldata _blobsInput,
bool _checkBlob
) internal {
// Prune unproven blocks if the proof submission window has passed
if (STFLib.canPruneAtTime(Timestamp.wrap(block.timestamp))) {
STFLib.prune();
}
// Keep intermediate values in memory to avoid stack too deep errors
InterimProposeValues memory v;
// Transactions are disabled during ignition phase
v.isTxsEnabled = FeeLib.isTxsEnabled();
// Since ignition have no transactions, we need not waste gas updating pricing oracle.
if (v.isTxsEnabled) {
FeeLib.updateL1GasFeeOracle();
}
// Validate blob commitments against actual blob data and extract hashes
// TODO(#13430): The below blobsHashesCommitment known as blobsHash elsewhere in the code. The name is confusingly
// similar to blobCommitmentsHash, see comment in BlobLib.sol -> validateBlobs().
(v.blobHashes, v.blobsHashesCommitment, v.blobCommitments) = BlobLib.validateBlobs(_blobsInput, _checkBlob);
v.header = _args.header;
// Compute header hash for computing the payload digest
v.headerHash = ProposedHeaderLib.hash(v.header);
// Setup epoch by sampling the committee for the current epoch and setting the seed for the one after the next.
// This is a no-op if the epoch is already set up, so it only gets executed by the first block of the epoch.
v.currentEpoch = Timestamp.wrap(block.timestamp).epochFromTimestamp();
ValidatorSelectionLib.setupEpoch(v.currentEpoch);
// Calculate mana base fee components for header validation
ManaBaseFeeComponents memory components;
if (v.isTxsEnabled) {
// Since ignition have no transactions, we need not waste gas computing the fee components
components = getManaBaseFeeComponentsAt(Timestamp.wrap(block.timestamp), true);
}
// Create payload digest signed by the committee members
v.payloadDigest = digest(
ProposePayload({
archive: _args.archive,
stateReference: _args.stateReference,
oracleInput: _args.oracleInput,
headerHash: v.headerHash
})
);
// Validate block header
validateHeader(
ValidateHeaderArgs({
header: v.header,
digest: v.payloadDigest,
manaBaseFee: FeeLib.summedBaseFee(components),
blobsHashesCommitment: v.blobsHashesCommitment,
flags: BlockHeaderValidationFlags({ignoreDA: false})
})
);
{
// Verify that the proposer is the correct one for this slot by checking their signature in the attestations
ValidatorSelectionLib.verifyProposer(
v.header.slotNumber,
v.currentEpoch,
_attestations,
_signers,
v.payloadDigest,
_attestationsAndSignersSignature,
true
);
}
// Begin state updates - get storage reference and current chain tips
RollupStore storage rollupStore = STFLib.getStorage();
CompressedChainTips tips = rollupStore.tips;
// Increment block number and update chain tips
uint256 blockNumber = tips.getPendingBlockNumber() + 1;
tips = tips.updatePendingBlockNumber(blockNumber);
// Calculate accumulated blob commitments hash for this block
// Blob commitments are collected and proven per root rollup proof (per epoch),
// so we need to know whether we are at the epoch start:
v.isFirstBlockOfEpoch = v.currentEpoch > STFLib.getEpochForBlock(blockNumber - 1) || blockNumber == 1;
bytes32 blobCommitmentsHash = BlobLib.calculateBlobCommitmentsHash(
STFLib.getBlobCommitmentsHash(blockNumber - 1), v.blobCommitments, v.isFirstBlockOfEpoch
);
// Compute fee header for block metadata
FeeHeader memory feeHeader;
if (v.isTxsEnabled) {
// Since ignition have no transactions, we need not waste gas deriving the fee header
feeHeader = FeeLib.computeFeeHeader(
blockNumber,
_args.oracleInput.feeAssetPriceModifier,
v.header.totalManaUsed,
components.congestionCost,
components.proverCost
);
}
// Hash attestations for storage in block log
// Compute attestationsHash from the attestations
v.attestationsHash = keccak256(abi.encode(_attestations));
// Commit state changes: update chain tips and store block data
rollupStore.tips = tips;
rollupStore.archives[blockNumber] = _args.archive;
STFLib.addTempBlockLog(
TempBlockLog({
headerHash: v.headerHash,
blobCommitmentsHash: blobCommitmentsHash,
attestationsHash: v.attestationsHash,
payloadDigest: v.payloadDigest,
slotNumber: v.header.slotNumber,
feeHeader: feeHeader
})
);
// Handle L1<->L2 message processing (only when transactions are enabled)
if (v.isTxsEnabled) {
// Since ignition will have no transactions there will be no method to consume or output message.
// Therefore we can ignore it as long as mana target is zero.
// Since the inbox is async, it must enforce its own check to not try to insert if ignition.
// Consume pending L1->L2 messages and validate against header commitment
// @note The block number here will always be >=1 as the genesis block is at 0
v.inHash = rollupStore.config.inbox.consume(blockNumber);
require(
v.header.contentCommitment.inHash == v.inHash,
Errors.Rollup__InvalidInHash(v.inHash, v.header.contentCommitment.inHash)
);
// Insert L2->L1 messages into outbox for later consumption
rollupStore.config.outbox.insert(blockNumber, v.header.contentCommitment.outHash);
}
// Emit event for external listeners. Nodes rely on this event to update their state.
emit IRollupCore.L2BlockProposed(blockNumber, _args.archive, v.blobHashes);
}
/**
* @notice Validates a proposed block header against chain state and constraints
* @dev Called internally from propose() and externally from RollupCore.validateHeaderWithAttestations()
* for proposers to check header validity before submitting transactions
*
* Header validations performed:
* - Coinbase address is non-zero: Errors.Rollup__InvalidCoinbase
* - Mana usage within limits: Errors.Rollup__ManaLimitExceeded
* - Builds on correct parent block (archive root check): Errors.Rollup__InvalidArchive
* - Slot number greater than last block's slot: Errors.Rollup__SlotAlreadyInChain
* - Slot number matches current timestamp slot: Errors.HeaderLib__InvalidSlotNumber
* - Timestamp matches slot-derived timestamp: Errors.Rollup__InvalidTimestamp
* - Timestamp not in future: Errors.Rollup__TimestampInFuture
* - Blob hashes match commitment (unless DA checks ignored): Errors.Rollup__UnavailableTxs
* - DA fee is zero: Errors.Rollup__NonZeroDaFee
* - L2 gas fee matches computed mana base fee: Errors.Rollup__InvalidManaBaseFee
*
* @param _args Validation arguments including header, digest, mana base fee, and flags
*/
function validateHeader(ValidateHeaderArgs memory _args) internal view {
require(_args.header.coinbase != address(0), Errors.Rollup__InvalidCoinbase());
require(_args.header.totalManaUsed <= FeeLib.getManaLimit(), Errors.Rollup__ManaLimitExceeded());
Timestamp currentTime = Timestamp.wrap(block.timestamp);
RollupStore storage rollupStore = STFLib.getStorage();
uint256 pendingBlockNumber = STFLib.getEffectivePendingBlockNumber(currentTime);
bytes32 tipArchive = rollupStore.archives[pendingBlockNumber];
require(
tipArchive == _args.header.lastArchiveRoot,
Errors.Rollup__InvalidArchive(tipArchive, _args.header.lastArchiveRoot)
);
Slot slot = _args.header.slotNumber;
Slot lastSlot = STFLib.getSlotNumber(pendingBlockNumber);
require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot));
Slot currentSlot = currentTime.slotFromTimestamp();
require(slot == currentSlot, Errors.HeaderLib__InvalidSlotNumber(currentSlot, slot));
Timestamp timestamp = TimeLib.toTimestamp(slot);
require(_args.header.timestamp == timestamp, Errors.Rollup__InvalidTimestamp(timestamp, _args.header.timestamp));
require(timestamp <= currentTime, Errors.Rollup__TimestampInFuture(currentTime, timestamp));
require(
_args.flags.ignoreDA || _args.header.contentCommitment.blobsHash == _args.blobsHashesCommitment,
Errors.Rollup__UnavailableTxs(_args.header.contentCommitment.blobsHash)
);
require(_args.header.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee());
require(
_args.header.gasFees.feePerL2Gas == _args.manaBaseFee,
Errors.Rollup__InvalidManaBaseFee(_args.manaBaseFee, _args.header.gasFees.feePerL2Gas)
);
}
/**
* @notice Gets the mana base fee components
* For more context, consult:
* https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8757-fees/design.md
*
* @param _timestamp - The timestamp of the block
* @param _inFeeAsset - Whether to return the fee in the fee asset or ETH
*
* @return The mana base fee components
*/
function getManaBaseFeeComponentsAt(Timestamp _timestamp, bool _inFeeAsset)
internal
view
returns (ManaBaseFeeComponents memory)
{
uint256 blockOfInterest = STFLib.getEffectivePendingBlockNumber(_timestamp);
return FeeLib.getManaBaseFeeComponentsAt(blockOfInterest, _timestamp, _inFeeAsset);
}
function digest(ProposePayload memory _args) internal pure returns (bytes32) {
return keccak256(abi.encode(SignatureDomainSeparator.blockAttestation, _args));
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {RollupStore, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {CompressedFeeHeader, FeeHeaderLib, FeeLib} from "@aztec/core/libraries/rollup/FeeLib.sol";
import {STFLib} from "@aztec/core/libraries/rollup/STFLib.sol";
import {Epoch, Timestamp, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
import {IBoosterCore} from "@aztec/core/reward-boost/RewardBooster.sol";
import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol";
import {CompressedTimeMath, CompressedTimestamp} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@oz/utils/math/Math.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {BitMaps} from "@oz/utils/structs/BitMaps.sol";
type Bps is uint32;
library BpsLib {
function mul(uint256 _a, Bps _b) internal pure returns (uint256) {
return _a * uint256(Bps.unwrap(_b)) / 10_000;
}
}
struct SubEpochRewards {
uint256 summedShares;
mapping(address prover => uint256 shares) shares;
}
struct EpochRewards {
uint128 longestProvenLength;
uint128 rewards;
mapping(uint256 length => SubEpochRewards) subEpoch;
}
struct RewardConfig {
IRewardDistributor rewardDistributor;
Bps sequencerBps;
IBoosterCore booster;
uint96 blockReward;
}
struct RewardStorage {
mapping(address => uint256) sequencerRewards;
mapping(Epoch => EpochRewards) epochRewards;
mapping(address prover => BitMaps.BitMap claimed) proverClaimed;
RewardConfig config;
CompressedTimestamp earliestRewardsClaimableTimestamp;
bool isRewardsClaimable;
}
struct Values {
address sequencer;
uint256 proverFee;
uint256 sequencerFee;
uint256 sequencerBlockReward;
uint256 manaUsed;
}
struct Totals {
uint256 feesToClaim;
uint256 totalBurn;
}
library RewardLib {
using SafeERC20 for IERC20;
using BitMaps for BitMaps.BitMap;
using CompressedTimeMath for CompressedTimestamp;
using CompressedTimeMath for Timestamp;
using TimeLib for Timestamp;
using TimeLib for Epoch;
using FeeHeaderLib for CompressedFeeHeader;
using SafeCast for uint256;
bytes32 private constant REWARD_STORAGE_POSITION = keccak256("aztec.reward.storage");
// A Cuauhxicalli [kʷaːʍʃiˈkalːi] ("eagle gourd bowl") is a ceremonial Aztec vessel or altar used to hold offerings,
// such as sacrificial hearts, during rituals performed within temples.
address public constant BURN_ADDRESS = address(bytes20("CUAUHXICALLI"));
function initialize(Timestamp _earliestRewardsClaimableTimestamp) internal {
RewardStorage storage rewardStorage = getStorage();
rewardStorage.earliestRewardsClaimableTimestamp = _earliestRewardsClaimableTimestamp.compress();
rewardStorage.isRewardsClaimable = false;
}
function setConfig(RewardConfig memory _config) internal {
require(Bps.unwrap(_config.sequencerBps) <= 10_000, Errors.RewardLib__InvalidSequencerBps());
RewardStorage storage rewardStorage = getStorage();
rewardStorage.config = _config;
}
function setIsRewardsClaimable(bool _isRewardsClaimable) internal {
RewardStorage storage rewardStorage = getStorage();
uint256 earliestRewardsClaimableTimestamp =
Timestamp.unwrap(rewardStorage.earliestRewardsClaimableTimestamp.decompress());
require(
block.timestamp >= earliestRewardsClaimableTimestamp,
Errors.Rollup__TooSoonToSetRewardsClaimable(earliestRewardsClaimableTimestamp, block.timestamp)
);
rewardStorage.isRewardsClaimable = _isRewardsClaimable;
}
function claimSequencerRewards(address _sequencer) internal returns (uint256) {
RewardStorage storage rewardStorage = getStorage();
require(rewardStorage.isRewardsClaimable, Errors.Rollup__RewardsNotClaimable());
RollupStore storage rollupStore = STFLib.getStorage();
uint256 amount = rewardStorage.sequencerRewards[_sequencer];
if (amount > 0) {
rewardStorage.sequencerRewards[_sequencer] = 0;
rollupStore.config.feeAsset.safeTransfer(_sequencer, amount);
}
return amount;
}
function claimProverRewards(address _prover, Epoch[] memory _epochs) internal returns (uint256) {
Epoch currentEpoch = Timestamp.wrap(block.timestamp).epochFromTimestamp();
RollupStore storage rollupStore = STFLib.getStorage();
RewardStorage storage rewardStorage = getStorage();
require(rewardStorage.isRewardsClaimable, Errors.Rollup__RewardsNotClaimable());
uint256 accumulatedRewards = 0;
for (uint256 i = 0; i < _epochs.length; i++) {
require(
!_epochs[i].isAcceptingProofsAtEpoch(currentEpoch),
Errors.Rollup__NotPastDeadline(_epochs[i].toDeadlineEpoch(), currentEpoch)
);
if (rewardStorage.proverClaimed[_prover].get(Epoch.unwrap(_epochs[i]))) {
continue;
}
rewardStorage.proverClaimed[_prover].set(Epoch.unwrap(_epochs[i]));
EpochRewards storage e = rewardStorage.epochRewards[_epochs[i]];
SubEpochRewards storage se = e.subEpoch[e.longestProvenLength];
uint256 shares = se.shares[_prover];
if (shares > 0) {
accumulatedRewards += (shares * e.rewards / se.summedShares);
}
}
if (accumulatedRewards > 0) {
rollupStore.config.feeAsset.safeTransfer(_prover, accumulatedRewards);
}
return accumulatedRewards;
}
function handleRewardsAndFees(SubmitEpochRootProofArgs memory _args, Epoch _endEpoch) internal {
RollupStore storage rollupStore = STFLib.getStorage();
RewardStorage storage rewardStorage = getStorage();
// Determine if this rollup is canonical according to its RewardDistributor.
uint256 length = _args.end - _args.start + 1;
EpochRewards storage $er = rewardStorage.epochRewards[_endEpoch];
{
SubEpochRewards storage $sr = $er.subEpoch[length];
address prover = _args.args.proverId;
require($sr.shares[prover] == 0, Errors.Rollup__ProverHaveAlreadySubmitted(prover, _endEpoch));
// Beware that it is possible to get marked active in an epoch even if you did not provide the longest
// proof. This is acceptable, as they were actually active. And boosting this way is not the most
// efficient way to do it, so this is fine.
uint256 shares = rewardStorage.config.booster.updateAndGetShares(prover);
$sr.shares[prover] = shares;
$sr.summedShares += shares;
}
if (length > $er.longestProvenLength) {
Values memory v;
Totals memory t;
{
uint256 added = length - $er.longestProvenLength;
uint256 blockRewardsDesired = added * getBlockReward();
uint256 blockRewardsAvailable = 0;
// Only if we require block rewards and are canonical will we claim.
if (blockRewardsDesired > 0) {
// Cache the reward distributor contract
IRewardDistributor distributor = rewardStorage.config.rewardDistributor;
if (address(this) == distributor.canonicalRollup()) {
uint256 amountToClaim =
Math.min(blockRewardsDesired, rollupStore.config.feeAsset.balanceOf(address(distributor)));
if (amountToClaim > 0) {
distributor.claim(address(this), amountToClaim);
blockRewardsAvailable = amountToClaim;
}
}
}
uint256 sequenceBlockRewards = BpsLib.mul(blockRewardsAvailable, rewardStorage.config.sequencerBps);
v.sequencerBlockReward = sequenceBlockRewards / added;
$er.rewards += (blockRewardsAvailable - sequenceBlockRewards).toUint128();
}
bool isTxsEnabled = FeeLib.isTxsEnabled();
for (uint256 i = $er.longestProvenLength; i < length; i++) {
if (isTxsEnabled) {
// During ignition there can be no txs, so there can be no fees either
// so we can skip the fee calculation
CompressedFeeHeader feeHeader = STFLib.getFeeHeader(_args.start + i);
v.manaUsed = feeHeader.getManaUsed();
uint256 fee = uint256(_args.fees[1 + i * 2]);
uint256 burn = feeHeader.getCongestionCost() * v.manaUsed;
t.feesToClaim += fee;
t.totalBurn += burn;
// Compute the proving fee in the fee asset
v.proverFee = Math.min(v.manaUsed * feeHeader.getProverCost(), fee - burn);
$er.rewards += v.proverFee.toUint128();
v.sequencerFee = fee - burn - v.proverFee;
}
{
v.sequencer = fieldToAddress(_args.fees[i * 2]);
rewardStorage.sequencerRewards[v.sequencer] += (v.sequencerBlockReward + v.sequencerFee);
}
}
$er.longestProvenLength = length.toUint128();
if (t.feesToClaim > 0) {
rollupStore.config.feeAssetPortal.distributeFees(address(this), t.feesToClaim);
}
if (t.totalBurn > 0) {
rollupStore.config.feeAsset.safeTransfer(BURN_ADDRESS, t.totalBurn);
}
}
}
function getSharesFor(address _prover) internal view returns (uint256) {
return getStorage().config.booster.getSharesFor(_prover);
}
function getSequencerRewards(address _sequencer) internal view returns (uint256) {
return getStorage().sequencerRewards[_sequencer];
}
function getCollectiveProverRewardsForEpoch(Epoch _epoch) internal view returns (uint256) {
return getStorage().epochRewards[_epoch].rewards;
}
function getHasSubmitted(Epoch _epoch, uint256 _length, address _prover) internal view returns (bool) {
return getStorage().epochRewards[_epoch].subEpoch[_length].shares[_prover] > 0;
}
function getHasClaimed(address _prover, Epoch _epoch) internal view returns (bool) {
return getStorage().proverClaimed[_prover].get(Epoch.unwrap(_epoch));
}
function getBlockReward() internal view returns (uint256) {
return getStorage().config.blockReward;
}
function getSpecificProverRewardsForEpoch(Epoch _epoch, address _prover) internal view returns (uint256) {
RewardStorage storage rewardStorage = getStorage();
if (rewardStorage.proverClaimed[_prover].get(Epoch.unwrap(_epoch))) {
return 0;
}
EpochRewards storage er = rewardStorage.epochRewards[_epoch];
SubEpochRewards storage se = er.subEpoch[er.longestProvenLength];
// Only if prover has shares will he get a reward. Also avoid a 0-div
// in case of no shares at all.
if (se.shares[_prover] == 0) {
return 0;
}
return (se.shares[_prover] * er.rewards / se.summedShares);
}
function isRewardsClaimable() internal view returns (bool) {
return getStorage().isRewardsClaimable;
}
function getEarliestRewardsClaimableTimestamp() internal view returns (Timestamp) {
return getStorage().earliestRewardsClaimableTimestamp.decompress();
}
function getStorage() internal pure returns (RewardStorage storage storageStruct) {
bytes32 position = REWARD_STORAGE_POSITION;
assembly {
storageStruct.slot := position
}
}
function fieldToAddress(bytes32 _f) private pure returns (address) {
return address(uint160(uint256(_f)));
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {CompressedEpoch, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {Epoch} from "@aztec/shared/libraries/TimeMath.sol";
import {Math} from "@oz/utils/math/Math.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
struct RewardBoostConfig {
uint32 increment;
uint32 maxScore;
uint32 a; // a
uint32 minimum; // m
uint32 k; // k
}
struct ActivityScore {
Epoch time;
uint32 value;
}
struct CompressedActivityScore {
CompressedEpoch time;
uint32 value;
}
interface IBoosterCore {
function updateAndGetShares(address _prover) external returns (uint256);
function getSharesFor(address _prover) external view returns (uint256);
}
interface IBooster is IBoosterCore {
function getConfig() external view returns (RewardBoostConfig memory);
function getActivityScore(address _prover) external view returns (ActivityScore memory);
}
/**
* @title RewardBooster
*
* @notice Abstracts the accounting related to rewards boosting from the POV of the rollup.
*/
contract RewardBooster is IBooster {
using SafeCast for uint256;
using CompressedTimeMath for Epoch;
using CompressedTimeMath for CompressedEpoch;
IValidatorSelection public immutable ROLLUP;
uint256 private immutable CONFIG_INCREMENT;
uint256 private immutable CONFIG_MAX_SCORE;
uint256 private immutable CONFIG_A;
uint256 private immutable CONFIG_MINIMUM;
uint256 private immutable CONFIG_K;
mapping(address prover => CompressedActivityScore) internal activityScores;
modifier onlyRollup() {
require(msg.sender == address(ROLLUP), Errors.RewardBooster__OnlyRollup(msg.sender));
_;
}
constructor(IValidatorSelection _rollup, RewardBoostConfig memory _config) {
ROLLUP = _rollup;
CONFIG_INCREMENT = _config.increment;
CONFIG_MAX_SCORE = _config.maxScore;
CONFIG_A = _config.a;
CONFIG_MINIMUM = _config.minimum;
CONFIG_K = _config.k;
}
function updateAndGetShares(address _prover) external override(IBoosterCore) onlyRollup returns (uint256) {
Epoch currentEpoch = ROLLUP.getCurrentEpoch();
CompressedActivityScore storage store = activityScores[_prover];
ActivityScore memory curr = _activityScoreAt(store, currentEpoch);
// If the score was already marked active in this epoch, ignore the addition.
if (curr.time != store.time.decompress()) {
store.value = Math.min(curr.value + CONFIG_INCREMENT, CONFIG_MAX_SCORE).toUint32();
store.time = curr.time.compress();
}
return _toShares(store.value);
}
function getConfig() external view override(IBooster) returns (RewardBoostConfig memory) {
return RewardBoostConfig({
increment: CONFIG_INCREMENT.toUint32(),
maxScore: CONFIG_MAX_SCORE.toUint32(),
a: CONFIG_A.toUint32(),
minimum: CONFIG_MINIMUM.toUint32(),
k: CONFIG_K.toUint32()
});
}
function getSharesFor(address _prover) external view override(IBoosterCore) returns (uint256) {
return _toShares(getActivityScore(_prover).value);
}
function getActivityScore(address _prover) public view override(IBooster) returns (ActivityScore memory) {
return _activityScoreAt(activityScores[_prover], ROLLUP.getCurrentEpoch());
}
function _activityScoreAt(CompressedActivityScore storage _score, Epoch _epoch)
internal
view
returns (ActivityScore memory)
{
uint256 decrease = (Epoch.unwrap(_epoch) - Epoch.unwrap(_score.time.decompress())) * 1e5;
return
ActivityScore({value: decrease > uint256(_score.value) ? 0 : _score.value - decrease.toUint32(), time: _epoch});
}
function _toShares(uint256 _value) internal view returns (uint256) {
if (_value >= CONFIG_MAX_SCORE) {
return CONFIG_K;
}
uint256 t = (CONFIG_MAX_SCORE - _value);
uint256 rhs = CONFIG_A * t * t / 1e10;
// Sub would move us below 0
if (CONFIG_K < rhs) {
return CONFIG_MINIMUM;
}
return Math.max(CONFIG_K - rhs, CONFIG_MINIMUM);
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.27;
import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol";
interface IHaveVersion {
function getVersion() external view returns (uint256);
}
interface IRegistry {
event CanonicalRollupUpdated(address indexed instance, uint256 indexed version);
event RewardDistributorUpdated(address indexed rewardDistributor);
function addRollup(IHaveVersion _rollup) external;
function updateRewardDistributor(address _rewardDistributor) external;
// docs:start:registry_get_canonical_rollup
function getCanonicalRollup() external view returns (IHaveVersion);
// docs:end:registry_get_canonical_rollup
// docs:start:registry_get_rollup
function getRollup(uint256 _chainId) external view returns (IHaveVersion);
// docs:end:registry_get_rollup
// docs:start:registry_number_of_versions
function numberOfVersions() external view returns (uint256);
// docs:end:registry_number_of_versions
function getGovernance() external view returns (address);
function getRewardDistributor() external view returns (IRewardDistributor);
function getVersion(uint256 _index) external view returns (uint256);
}// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.27;
interface IRewardDistributor {
function claim(address _to, uint256 _amount) external;
function recover(address _asset, address _to, uint256 _amount) external;
function canonicalRollup() external view returns (address);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity ^0.8.27;
import {ECDSA} from "@oz/utils/cryptography/ECDSA.sol";
// Signature
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
error SignatureLib__InvalidSignature(address, address);
library SignatureLib {
/**
* @notice Verifies a signature, throws if the signature is invalid or empty
*
* @param _signature - The signature to verify
* @param _signer - The expected signer of the signature
* @param _digest - The digest that was signed
*/
function verify(Signature memory _signature, address _signer, bytes32 _digest) internal pure returns (bool) {
address recovered = ECDSA.recover(_digest, _signature.v, _signature.r, _signature.s);
require(_signer == recovered, SignatureLib__InvalidSignature(_signer, recovered));
return true;
}
function isEmpty(Signature memory _signature) internal pure returns (bool) {
return _signature.v == 0;
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
type Timestamp is uint256;
type Slot is uint256;
type Epoch is uint256;
function addTimestamp(Timestamp _a, Timestamp _b) pure returns (Timestamp) {
return Timestamp.wrap(Timestamp.unwrap(_a) + Timestamp.unwrap(_b));
}
function subTimestamp(Timestamp _a, Timestamp _b) pure returns (Timestamp) {
return Timestamp.wrap(Timestamp.unwrap(_a) - Timestamp.unwrap(_b));
}
function ltTimestamp(Timestamp _a, Timestamp _b) pure returns (bool) {
return Timestamp.unwrap(_a) < Timestamp.unwrap(_b);
}
function lteTimestamp(Timestamp _a, Timestamp _b) pure returns (bool) {
return Timestamp.unwrap(_a) <= Timestamp.unwrap(_b);
}
function gtTimestamp(Timestamp _a, Timestamp _b) pure returns (bool) {
return Timestamp.unwrap(_a) > Timestamp.unwrap(_b);
}
function gteTimestamp(Timestamp _a, Timestamp _b) pure returns (bool) {
return Timestamp.unwrap(_a) >= Timestamp.unwrap(_b);
}
function neqTimestamp(Timestamp _a, Timestamp _b) pure returns (bool) {
return Timestamp.unwrap(_a) != Timestamp.unwrap(_b);
}
function eqTimestamp(Timestamp _a, Timestamp _b) pure returns (bool) {
return Timestamp.unwrap(_a) == Timestamp.unwrap(_b);
}
// Slot
function addSlot(Slot _a, Slot _b) pure returns (Slot) {
return Slot.wrap(Slot.unwrap(_a) + Slot.unwrap(_b));
}
function subSlot(Slot _a, Slot _b) pure returns (Slot) {
return Slot.wrap(Slot.unwrap(_a) - Slot.unwrap(_b));
}
function eqSlot(Slot _a, Slot _b) pure returns (bool) {
return Slot.unwrap(_a) == Slot.unwrap(_b);
}
function neqSlot(Slot _a, Slot _b) pure returns (bool) {
return Slot.unwrap(_a) != Slot.unwrap(_b);
}
function ltSlot(Slot _a, Slot _b) pure returns (bool) {
return Slot.unwrap(_a) < Slot.unwrap(_b);
}
function lteSlot(Slot _a, Slot _b) pure returns (bool) {
return Slot.unwrap(_a) <= Slot.unwrap(_b);
}
function gtSlot(Slot _a, Slot _b) pure returns (bool) {
return Slot.unwrap(_a) > Slot.unwrap(_b);
}
function gteSlot(Slot _a, Slot _b) pure returns (bool) {
return Slot.unwrap(_a) >= Slot.unwrap(_b);
}
// Epoch
function eqEpoch(Epoch _a, Epoch _b) pure returns (bool) {
return Epoch.unwrap(_a) == Epoch.unwrap(_b);
}
function neqEpoch(Epoch _a, Epoch _b) pure returns (bool) {
return Epoch.unwrap(_a) != Epoch.unwrap(_b);
}
function subEpoch(Epoch _a, Epoch _b) pure returns (Epoch) {
return Epoch.wrap(Epoch.unwrap(_a) - Epoch.unwrap(_b));
}
function addEpoch(Epoch _a, Epoch _b) pure returns (Epoch) {
return Epoch.wrap(Epoch.unwrap(_a) + Epoch.unwrap(_b));
}
function gteEpoch(Epoch _a, Epoch _b) pure returns (bool) {
return Epoch.unwrap(_a) >= Epoch.unwrap(_b);
}
function gtEpoch(Epoch _a, Epoch _b) pure returns (bool) {
return Epoch.unwrap(_a) > Epoch.unwrap(_b);
}
function lteEpoch(Epoch _a, Epoch _b) pure returns (bool) {
return Epoch.unwrap(_a) <= Epoch.unwrap(_b);
}
function ltEpoch(Epoch _a, Epoch _b) pure returns (bool) {
return Epoch.unwrap(_a) < Epoch.unwrap(_b);
}
using {
addTimestamp as +,
subTimestamp as -,
ltTimestamp as <,
gtTimestamp as >,
lteTimestamp as <=,
gteTimestamp as >=,
neqTimestamp as !=,
eqTimestamp as ==
} for Timestamp global;
using {
addEpoch as +,
subEpoch as -,
eqEpoch as ==,
neqEpoch as !=,
gteEpoch as >=,
gtEpoch as >,
lteEpoch as <=,
ltEpoch as <
} for Epoch global;
using {
eqSlot as ==,
neqSlot as !=,
gteSlot as >=,
gtSlot as >,
lteSlot as <=,
ltSlot as <,
addSlot as +,
subSlot as -
} for Slot global;// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
if (!_safeTransfer(token, to, value, true)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
if (!_safeTransferFrom(token, from, to, value, true)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _safeTransfer(token, to, value, false);
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _safeTransferFrom(token, from, to, value, false);
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
if (!_safeApprove(token, spender, value, false)) {
if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));
if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Oppositely, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the
* return value is optional (but if data is returned, it must not be false).
*
* @param token The token targeted by the call.
* @param to The recipient of the tokens
* @param value The amount of token to transfer
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
*/
function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {
bytes4 selector = IERC20.transfer.selector;
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(0x00, selector)
mstore(0x04, and(to, shr(96, not(0))))
mstore(0x24, value)
success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
// if call success and return is true, all is good.
// otherwise (not success or return is not true), we need to perform further checks
if iszero(and(success, eq(mload(0x00), 1))) {
// if the call was a failure and bubble is enabled, bubble the error
if and(iszero(success), bubble) {
returndatacopy(fmp, 0x00, returndatasize())
revert(fmp, returndatasize())
}
// if the return value is not true, then the call is only successful if:
// - the token address has code
// - the returndata is empty
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
}
mstore(0x40, fmp)
}
}
/**
* @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return
* value: the return value is optional (but if data is returned, it must not be false).
*
* @param token The token targeted by the call.
* @param from The sender of the tokens
* @param to The recipient of the tokens
* @param value The amount of token to transfer
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
*/
function _safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value,
bool bubble
) private returns (bool success) {
bytes4 selector = IERC20.transferFrom.selector;
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(0x00, selector)
mstore(0x04, and(from, shr(96, not(0))))
mstore(0x24, and(to, shr(96, not(0))))
mstore(0x44, value)
success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)
// if call success and return is true, all is good.
// otherwise (not success or return is not true), we need to perform further checks
if iszero(and(success, eq(mload(0x00), 1))) {
// if the call was a failure and bubble is enabled, bubble the error
if and(iszero(success), bubble) {
returndatacopy(fmp, 0x00, returndatasize())
revert(fmp, returndatasize())
}
// if the return value is not true, then the call is only successful if:
// - the token address has code
// - the returndata is empty
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
}
mstore(0x40, fmp)
mstore(0x60, 0)
}
}
/**
* @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:
* the return value is optional (but if data is returned, it must not be false).
*
* @param token The token targeted by the call.
* @param spender The spender of the tokens
* @param value The amount of token to transfer
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
*/
function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {
bytes4 selector = IERC20.approve.selector;
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(0x00, selector)
mstore(0x04, and(spender, shr(96, not(0))))
mstore(0x24, value)
success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
// if call success and return is true, all is good.
// otherwise (not success or return is not true), we need to perform further checks
if iszero(and(success, eq(mload(0x00), 1))) {
// if the call was a failure and bubble is enabled, bubble the error
if and(iszero(success), bubble) {
returndatacopy(fmp, 0x00, returndatasize())
revert(fmp, returndatasize())
}
// if the return value is not true, then the call is only successful if:
// - the token address has code
// - the returndata is empty
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
}
mstore(0x40, fmp)
}
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
library SplitV2Lib {
/* -------------------------------------------------------------------------- */
/* ERRORS */
/* -------------------------------------------------------------------------- */
error InvalidSplit_TotalAllocationMismatch();
error InvalidSplit_LengthMismatch();
/* -------------------------------------------------------------------------- */
/* STRUCTS */
/* -------------------------------------------------------------------------- */
/**
* @notice Split struct
* @dev This struct is used to store the split information.
* @dev There are no hard caps on the number of recipients/totalAllocation/allocation unit. Thus the chain and its
* gas limits will dictate these hard caps. Please double check if the split you are creating can be distributed on
* the chain.
* @param recipients The recipients of the split.
* @param allocations The allocations of the split.
* @param totalAllocation The total allocation of the split.
* @param distributionIncentive The incentive for distribution. Limits max incentive to 6.5%.
*/
struct Split {
address[] recipients;
uint256[] allocations;
uint256 totalAllocation;
uint16 distributionIncentive;
}
/* -------------------------------------------------------------------------- */
/* CONSTANTS */
/* -------------------------------------------------------------------------- */
uint256 internal constant PERCENTAGE_SCALE = 1e6;
/* -------------------------------------------------------------------------- */
/* FUNCTIONS */
/* -------------------------------------------------------------------------- */
function getHash(Split calldata _split) internal pure returns (bytes32) {
return keccak256(abi.encode(_split));
}
function getHashMem(Split memory _split) internal pure returns (bytes32) {
return keccak256(abi.encode(_split));
}
function validate(Split calldata _split) internal pure {
uint256 numOfRecipients = _split.recipients.length;
if (_split.allocations.length != numOfRecipients) {
revert InvalidSplit_LengthMismatch();
}
uint256 totalAllocation;
for (uint256 i; i < numOfRecipients; ++i) {
totalAllocation += _split.allocations[i];
}
if (totalAllocation != _split.totalAllocation) revert InvalidSplit_TotalAllocationMismatch();
}
function getDistributions(
Split calldata _split,
uint256 _amount
)
internal
pure
returns (uint256[] memory amounts, uint256 distributorReward)
{
uint256 numOfRecipients = _split.recipients.length;
amounts = new uint256[](numOfRecipients);
distributorReward = calculateDistributorReward(_split, _amount);
_amount -= distributorReward;
for (uint256 i; i < numOfRecipients; ++i) {
amounts[i] = calculateAllocatedAmount(_split, _amount, i);
}
}
function calculateAllocatedAmount(
Split calldata _split,
uint256 _amount,
uint256 _index
)
internal
pure
returns (uint256 allocatedAmount)
{
allocatedAmount = _amount * _split.allocations[_index] / _split.totalAllocation;
}
function calculateDistributorReward(
Split calldata _split,
uint256 _amount
)
internal
pure
returns (uint256 distributorReward)
{
distributorReward = _amount * _split.distributionIncentive / PERCENTAGE_SCALE;
}
// only used in tests
function getDistributionsMem(
Split memory _split,
uint256 _amount
)
internal
pure
returns (uint256[] memory amounts, uint256 distributorReward)
{
uint256 numOfRecipients = _split.recipients.length;
amounts = new uint256[](numOfRecipients);
distributorReward = _amount * _split.distributionIncentive / PERCENTAGE_SCALE;
_amount -= distributorReward;
for (uint256 i; i < numOfRecipients; ++i) {
amounts[i] = _amount * _split.allocations[i] / _split.totalAllocation;
}
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
import { SplitFactoryV2 } from "../SplitFactoryV2.sol";
import { PullSplit } from "./PullSplit.sol";
/**
* @title Pull split factory
* @author Splits
* @notice Minimal smart wallet clone-factory for pull flow splitters.
*/
contract PullSplitFactory is SplitFactoryV2 {
/* -------------------------------------------------------------------------- */
/* CONSTRUCTOR */
/* -------------------------------------------------------------------------- */
constructor(address _splitsWarehouse) {
SPLIT_WALLET_IMPLEMENTATION = address(new PullSplit(_splitsWarehouse));
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
library Constants {
/// @notice The token decimals
uint256 public constant TOKEN_DECIMALS = 18;
/// @notice The number of basis points in 100%
uint256 public constant BIPS = 10_000;
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
/**
* @title Rollup Registry Minimal Interface
* @author Aztec-Labs
* @notice A minimal interface for the Rollup Registry contract
*
* @dev includes only the function that are interacted with from the staker
*/
interface IRegistry {
function getCanonicalRollup() external view returns (address);
function getRollup(uint256 _version) external view returns (address);
function getGovernance() external view returns (address);
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {BN254Lib} from "src/staking-registry/libs/BN254.sol";
/**
* @title Staking Minimal Interface
* @author Aztec-Labs
* @notice A minimal interface for the Staking contract
*
* @dev includes only the function that are interacted with from the staker
*/
interface IStaking {
// TODO: make this line up with the real staking contract
event Staked(address indexed attester, uint256 amount);
function deposit(
address _attester,
address _withdrawer,
BN254Lib.G1Point memory _publicKeyG1,
BN254Lib.G2Point memory _publicKeyG2,
BN254Lib.G1Point memory _signature,
bool _moveWithRollup
) external;
function initiateWithdraw(address _attester, address _recipient) external;
function finaliseWithdraw(address _attester) external;
function claimSequencerRewards(address _sequencer) external;
function getActivationThreshold() external view returns (uint256);
function getGSE() external view returns (address);
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
library BN254Lib {
/**
* @title G1Point
* @notice A point on the BN254 G1 curve
*/
struct G1Point {
/// @notice The x coordinate
uint256 x;
/// @notice The y coordinate
uint256 y;
}
/**
* @title G2Point
* @notice A point on the BN254 paired G2 curve
*/
struct G2Point {
/// @notice The x coordinate - first part
uint256 x0;
/// @notice The x coordinate - second part
uint256 x1;
/// @notice The y coordinate - first part
uint256 y0;
/// @notice The y coordinate - second part
uint256 y1;
}
struct KeyStore {
/// @notice The address of the attester
address attester;
/// @notice - The BLS public key - BN254 G1
G1Point publicKeyG1;
/// @notice - The BLS public key - BN254 G2
G2Point publicKeyG2;
/// @notice - The BLS signature - required to prevent rogue key attacks
G1Point signature;
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {IStakingRegistry} from "src/staking-registry/StakingRegistry.sol";
struct Queue {
mapping(uint256 index => IStakingRegistry.KeyStore keyStore) keyStores;
uint128 first;
uint128 last;
}
library QueueLib {
error QueueIsEmpty();
error QueueIndexOutOfBounds();
function init(Queue storage _self) internal {
_self.first = 1;
_self.last = 1;
}
function enqueue(Queue storage _self, IStakingRegistry.KeyStore memory _keyStore) internal returns (uint128) {
uint128 queueLocation = _self.last;
_self.keyStores[queueLocation] = _keyStore;
_self.last = queueLocation + 1;
return queueLocation;
}
function dequeue(Queue storage _self) internal returns (IStakingRegistry.KeyStore memory) {
require(_self.last > _self.first, QueueIsEmpty());
IStakingRegistry.KeyStore memory keyStore = _self.keyStores[_self.first];
_self.first += 1;
return keyStore;
}
function getValueAtIndex(Queue storage _self, uint128 _index)
internal
view
returns (IStakingRegistry.KeyStore memory)
{
require(_index >= _self.first && _index < _self.last, QueueIndexOutOfBounds());
return _self.keyStores[_index];
}
function length(Queue storage _self) internal view returns (uint128) {
return _self.last - _self.first;
}
function getFirstIndex(Queue storage _self) internal view returns (uint128) {
return _self.first;
}
function getLastIndex(Queue storage _self) internal view returns (uint128) {
return _self.last;
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {StakingQueueConfig} from "@aztec/core/libraries/compressed-data/StakingQueueConfig.sol";
import {Exit, Status, AttesterView} from "@aztec/core/libraries/rollup/StakingLib.sol";
import {DepositArgs} from "@aztec/core/libraries/StakingQueue.sol";
import {AttesterConfig, GSE} from "@aztec/governance/GSE.sol";
import {G1Point, G2Point} from "@aztec/shared/libraries/BN254Lib.sol";
import {Timestamp, Epoch} from "@aztec/shared/libraries/TimeMath.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
interface IStakingCore {
event SlasherUpdated(address indexed oldSlasher, address indexed newSlasher);
event LocalEjectionThresholdUpdated(
uint256 indexed oldLocalEjectionThreshold, uint256 indexed newLocalEjectionThreshold
);
event ValidatorQueued(address indexed attester, address indexed withdrawer);
event Deposit(
address indexed attester,
address indexed withdrawer,
G1Point publicKeyInG1,
G2Point publicKeyInG2,
G1Point proofOfPossession,
uint256 amount
);
event FailedDeposit(
address indexed attester,
address indexed withdrawer,
G1Point publicKeyInG1,
G2Point publicKeyInG2,
G1Point proofOfPossession
);
event WithdrawInitiated(address indexed attester, address indexed recipient, uint256 amount);
event WithdrawFinalized(address indexed attester, address indexed recipient, uint256 amount);
event Slashed(address indexed attester, uint256 amount);
event StakingQueueConfigUpdated(StakingQueueConfig config);
function setSlasher(address _slasher) external;
function setLocalEjectionThreshold(uint256 _localEjectionThreshold) external;
function deposit(
address _attester,
address _withdrawer,
G1Point memory _publicKeyInG1,
G2Point memory _publicKeyInG2,
G1Point memory _proofOfPossession,
bool _moveWithLatestRollup
) external;
function flushEntryQueue() external;
function flushEntryQueue(uint256 _toAdd) external;
function initiateWithdraw(address _attester, address _recipient) external returns (bool);
function finalizeWithdraw(address _attester) external;
function slash(address _attester, uint256 _amount) external returns (bool);
function vote(uint256 _proposalId) external;
function updateStakingQueueConfig(StakingQueueConfig memory _config) external;
function getEntryQueueFlushSize() external view returns (uint256);
function getActiveAttesterCount() external view returns (uint256);
}
interface IStaking is IStakingCore {
function getConfig(address _attester) external view returns (AttesterConfig memory);
function getExit(address _attester) external view returns (Exit memory);
function getAttesterAtIndex(uint256 _index) external view returns (address);
function getSlasher() external view returns (address);
function getLocalEjectionThreshold() external view returns (uint256);
function getStakingAsset() external view returns (IERC20);
function getActivationThreshold() external view returns (uint256);
function getEjectionThreshold() external view returns (uint256);
function getExitDelay() external view returns (Timestamp);
function getGSE() external view returns (GSE);
function getAttesterView(address _attester) external view returns (AttesterView memory);
function getStatus(address _attester) external view returns (Status);
function getNextFlushableEpoch() external view returns (Epoch);
function getEntryQueueLength() external view returns (uint256);
function getEntryQueueAt(uint256 _index) external view returns (DepositArgs memory);
function getAvailableValidatorFlushes() external view returns (uint256);
function getIsBootstrapped() external view returns (bool);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {Bn254LibWrapper} from "@aztec/governance/Bn254LibWrapper.sol";
import {Governance} from "@aztec/governance/Governance.sol";
import {Proposal} from "@aztec/governance/interfaces/IGovernance.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/governance/libraries/AddressSnapshotLib.sol";
import {
DepositDelegationLib, DepositAndDelegationAccounting
} from "@aztec/governance/libraries/DepositDelegationLib.sol";
import {Errors} from "@aztec/governance/libraries/Errors.sol";
import {G1Point, G2Point} from "@aztec/shared/libraries/BN254Lib.sol";
import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
import {Ownable} from "@oz/access/Ownable.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol";
// Struct to store configuration of an attester (block producer)
// Keep track of the actor who can initiate and control withdraws for the attester.
// Keep track of the public key in G1 of BN254 that has registered on the instance
struct AttesterConfig {
G1Point publicKey;
address withdrawer;
}
// Struct to track the attesters (block producers) on a particular rollup instance
// throughout time, along with each attester's current config.
// Finally a flag to track if the instance exists.
struct InstanceAttesterRegistry {
SnapshottedAddressSet attesters;
bool exists;
}
interface IGSECore {
event Deposit(address indexed instance, address indexed attester, address withdrawer);
function setGovernance(Governance _governance) external;
function setProofOfPossessionGasLimit(uint64 _proofOfPossessionGasLimit) external;
function addRollup(address _rollup) external;
function deposit(
address _attester,
address _withdrawer,
G1Point memory _publicKeyInG1,
G2Point memory _publicKeyInG2,
G1Point memory _proofOfPossession,
bool _moveWithLatestRollup
) external;
function withdraw(address _attester, uint256 _amount) external returns (uint256, bool, uint256);
function delegate(address _instance, address _attester, address _delegatee) external;
function vote(uint256 _proposalId, uint256 _amount, bool _support) external;
function voteWithBonus(uint256 _proposalId, uint256 _amount, bool _support) external;
function finalizeWithdraw(uint256 _withdrawalId) external;
function proposeWithLock(IPayload _proposal, address _to) external returns (uint256);
function isRegistered(address _instance, address _attester) external view returns (bool);
function isRollupRegistered(address _instance) external view returns (bool);
function getLatestRollup() external view returns (address);
function getLatestRollupAt(Timestamp _timestamp) external view returns (address);
function getGovernance() external view returns (Governance);
}
interface IGSE is IGSECore {
function getRegistrationDigest(G1Point memory _publicKey) external view returns (G1Point memory);
function getDelegatee(address _instance, address _attester) external view returns (address);
function getVotingPower(address _attester) external view returns (uint256);
function getVotingPowerAt(address _attester, Timestamp _timestamp) external view returns (uint256);
function getWithdrawer(address _attester) external view returns (address);
function balanceOf(address _instance, address _attester) external view returns (uint256);
function effectiveBalanceOf(address _instance, address _attester) external view returns (uint256);
function supplyOf(address _instance) external view returns (uint256);
function totalSupply() external view returns (uint256);
function getConfig(address _attester) external view returns (AttesterConfig memory);
function getAttesterCountAtTime(address _instance, Timestamp _timestamp) external view returns (uint256);
function getAttestersFromIndicesAtTime(address _instance, Timestamp _timestamp, uint256[] memory _indices)
external
view
returns (address[] memory);
function getG1PublicKeysFromAddresses(address[] memory _attesters) external view returns (G1Point[] memory);
function getAttesterFromIndexAtTime(address _instance, uint256 _index, Timestamp _timestamp)
external
view
returns (address);
function getPowerUsed(address _delegatee, uint256 _proposalId) external view returns (uint256);
function getBonusInstanceAddress() external view returns (address);
}
/**
* @title GSECore
* @author Aztec Labs
* @notice The core Governance Staking Escrow contract that handles the deposits of attesters on rollup instances.
* It is responsible for:
* - depositing/withdrawing attesters on rollup instances
* - providing rollup instances with historical views of their attesters
* - allowing depositors to delegate their voting power
* - allowing delegatees to vote at governance
* - maintaining a set of "bonus" attesters which are always deposited on behalf of the latest rollup
*
* NB: The "bonus" attesters are thus automatically "moved along" whenever the latest rollup changes.
* That is, at the point of the rollup getting added, the bonus is immediately available.
* This allows the latest rollup to start with a set of attesters, rather than requiring them to exit
* the old rollup and deposit in the new one.
*
* NB: The "latest" rollup in this contract does not technically need to be the "canonical" rollup
* according to the Registry, but in practice, it will be unless the new rollup does not use the GSE.
* Proposals which add rollups that DO want to use the GSE MUST call addRollup to both the Registry and the GSE.
* See RegisterNewRollupVersionPayload.sol for an example.
*
* NB: The "owner" of the GSE is intended to be the Governance contract, but there is a circular
* dependency in that we also want the GSE to be registered as the first beneficiary of the governance
* contract so that we don't need to go through a governance proposal to add it. To that end,
* this contract's view of `governance` needs to be set. So the current flow is to deploy the GSE with the owner
* set to the deployer, then deploy Governance, passing the GSE as the initial/sole authorized beneficiary,
* then have the deployer `setGovernance`, and then `transferOwnership` to Governance.
*/
contract GSECore is IGSECore, Ownable {
using AddressSnapshotLib for SnapshottedAddressSet;
using SafeCast for uint256;
using SafeCast for uint224;
using Checkpoints for Checkpoints.Trace224;
using DepositDelegationLib for DepositAndDelegationAccounting;
using SafeERC20 for IERC20;
/**
* Create a special "bonus" address for use by the latest rollup.
* This is a convenience mechanism to allow attesters to always be staked on the latest rollup.
*
* As far as terminology, the GSE tracks deposits and voting/delegation data for "instances",
* and an "instance" is either the address of a "true" rollup contract which was added via `addRollup`,
* or (ONLY IN THIS CONTRACT) this special "bonus" address, which has its own accounting.
*
* NB: in every other context, "instance" refers broadly to a specific instance of an aztec rollup contract
* (possibly inclusive of its family of related contracts e.g. Inbox, Outbox, etc.)
*
* Thus, this bonus address appears in `delegation` and `instances`, and from the perspective of the GSE,
* it is an instance (though it can never be in the list of rollups).
*
* Lower in the code, we use "rollup" if we know we're talking about a rollup (often msg.sender),
* and "instance" if we are talking about about either a rollup instance or the bonus instance.
*
* The latest rollup according to `rollups` may use the attesters and voting power
* from the BONUS_INSTANCE_ADDRESS as a "bonus" to their own.
*
* One invariant of the GSE is that the attesters available to any rollup instance must form a set.
* i.e. there must be no duplicates.
*
* Thus, for the latest rollup, there are two "buckets" of attesters available:
* - the attesters that are associated with the rollup's address
* - the attesters that are associated with the BONUS_INSTANCE_ADDRESS
*
* The GSE ensures that:
* - each bucket individually is a set
* - when you add these two buckets together, it is a set.
*
* For a rollup that is no longer the latest, the attesters available to it are the attesters that are
* associated with the rollup's address. In effect, when a rollup goes from being the latest to not being
* the latest, it loses all attesters that were associated with the bonus instance.
*
* In this way, the "effective" attesters/balance/etc for a rollup (at a point in time) is:
* - the rollup's bucket and the bonus bucket if the rollup was the latest at that point in time
* - only the rollup's bucket if the rollup was not the latest at that point in time
*
* Note further, that operations like deposit and withdraw are initiated by a rollup,
* but the "affected instance" address will be either the rollup's address or the BONUS_INSTANCE_ADDRESS;
* we will typically need to look at both instances to know what to do.
*
* NB: in a large way, the BONUS_INSTANCE_ADDRESS is the entire point of the GSE,
* otherwise the rollups would've managed their own attesters/delegation/etc.
*/
address public constant BONUS_INSTANCE_ADDRESS = address(uint160(uint256(keccak256("bonus-instance"))));
// External wrapper of the BN254 library to more easily allow gas limits.
Bn254LibWrapper internal immutable BN254_LIB_WRAPPER = new Bn254LibWrapper();
// The amount of ASSET needed to add an attester to the set
uint256 public immutable ACTIVATION_THRESHOLD;
// The amount of ASSET needed to keep an attester in the set, if the attester balance fall below this threshold
// the attester will be ejected from the set.
uint256 public immutable EJECTION_THRESHOLD;
// The asset used for sybil resistance and power in governance. Must match the ASSET in `Governance` to work as
// intended.
IERC20 public immutable ASSET;
// The GSE's history of rollups.
Checkpoints.Trace224 internal rollups;
// Mapping from instance address to its historical attester information.
mapping(address instanceAddress => InstanceAttesterRegistry instance) internal instances;
// Global attester information
mapping(address attester => AttesterConfig config) internal configOf;
// Mapping from the hashed public key in G1 of BN254 to the keys are registered.
mapping(bytes32 hashedPK1 => bool isRegistered) public ownedPKs;
/**
* Contains state for:
* checkpointed total supply
* instance => {
* checkpointed supply
* attester => { balance, delegatee }
* }
* delegatee => {
* checkpointed voting power
* proposal ID => { power used }
* }
*/
DepositAndDelegationAccounting internal delegation;
Governance internal governance;
// Gas limit for proof of possession validation.
//
// Must exceed the happy path gas consumption to ensure deposits succeed.
// Acts as a cap on unhappy path gas usage to prevent excessive consumption.
//
// - Happy path average: 150K gas
// - Buffer for loop: 50K gas
// - Buffer for opcode cost changes: 50K gas
//
// WARNING: If set below happy path requirements, all deposits will fail.
// Governance can adjust this value via proposal.
uint64 public proofOfPossessionGasLimit = 250_000;
/**
* @dev enforces that the caller is a registered rollup.
*/
modifier onlyRollup() {
require(isRollupRegistered(msg.sender), Errors.GSE__NotRollup(msg.sender));
_;
}
/**
* @param __owner - The owner of the GSE.
* Initially a deployer to allow adding an initial rollup, then handed over to governance.
* @param _asset - The ERC20 token asset used in governance and for sybil resistance.
* This token is deposited by attesters to gain voting power in governance
* (ratio of voting power to staked amount is 1:1).
* @param _activationThreshold - The amount of asset required to deposit an attester on the rollup.
* @param _ejectionThreshold - The minimum amount of asset required to be in the set to be considered an attester.
* If the balance falls below this threshold, the attester is ejected from the set.
*/
constructor(address __owner, IERC20 _asset, uint256 _activationThreshold, uint256 _ejectionThreshold)
Ownable(__owner)
{
ASSET = _asset;
ACTIVATION_THRESHOLD = _activationThreshold;
EJECTION_THRESHOLD = _ejectionThreshold;
instances[BONUS_INSTANCE_ADDRESS].exists = true;
}
function setGovernance(Governance _governance) external override(IGSECore) onlyOwner {
require(address(governance) == address(0), Errors.GSE__GovernanceAlreadySet());
governance = _governance;
}
function setProofOfPossessionGasLimit(uint64 _proofOfPossessionGasLimit) external override(IGSECore) onlyOwner {
proofOfPossessionGasLimit = _proofOfPossessionGasLimit;
}
/**
* @notice Adds another rollup to the instances, which is the new latest rollup.
* Only callable by the owner (usually governance) and only when the rollup is not already in the set
*
* @dev rollups only have access to the "bonus instance" while they are the most recent rollup.
*
* @dev The GSE only supports adding rollups, not removing them. If a rollup becomes compromised, governance can
* simply add a new rollup and the bonus instance mechanism ensures a smooth transition by allowing the new rollup
* to immediately inherit attesters.
*
* @dev Beware that multiple calls to `addRollup` at the same `block.timestamp` will override each other and only
* the last will be in the `rollups`.
*
* @param _rollup - The address of the rollup to add
*/
function addRollup(address _rollup) external override(IGSECore) onlyOwner {
require(_rollup != address(0), Errors.GSE__InvalidRollupAddress(_rollup));
require(!instances[_rollup].exists, Errors.GSE__RollupAlreadyRegistered(_rollup));
instances[_rollup].exists = true;
rollups.push(block.timestamp.toUint32(), uint224(uint160(_rollup)));
}
/**
* @notice Deposits a new attester
*
* @dev msg.sender must be a registered rollup.
*
* @dev Transfers ASSET from msg.sender to the GSE, and then into Governance.
*
* @dev if _moveWithLatestRollup is true, then msg.sender must be the latest rollup.
*
* @dev An attester configuration is registered globally to avoid BLS troubles when moving stake.
*
* Suppose the registered rollups are A, then B, then C, so C's effective attesters are
* those associated with C and the bonus address.
*
* Alice may come along now and deposit on A or B, with _moveWithLatestRollup=false in either case.
*
* For depositing into C, she can deposit *either* with _moveWithLatestRollup = true OR false.
* If she deposits with _moveWithLatestRollup = false, then she is associated with C's address.
* If she deposits with _moveWithLatestRollup = true, then she is associated with the bonus address.
*
* Suppose she deposits with _moveWithLatestRollup = true, and a new rollup D is added to the rollups.
* Then her stake moves to D, and she is in the effective attesters of D.
*
* @param _attester - The attester address on behalf of which the deposit is made.
* @param _withdrawer - Address which the user wish to use to initiate a withdraw for the `_attester` and
* to update delegation with. The withdrawals are enforced by the rollup to which it is
* controlled, so it is practically a value for the rollup to use, meaning dishonest rollup
* can reject withdrawal attempts.
* @param _publicKeyInG1 - BLS public key for the attester in G1
* @param _publicKeyInG2 - BLS public key for the attester in G2
* @param _proofOfPossession - A proof of possessions for the private key corresponding _publicKey in G1 and G2
* @param _moveWithLatestRollup - Whether to deposit into the specific instance, or the bonus instance
*/
function deposit(
address _attester,
address _withdrawer,
G1Point memory _publicKeyInG1,
G2Point memory _publicKeyInG2,
G1Point memory _proofOfPossession,
bool _moveWithLatestRollup
) external override(IGSECore) onlyRollup {
bool isMsgSenderLatestRollup = getLatestRollup() == msg.sender;
// If _moveWithLatestRollup is true, then msg.sender must be the latest rollup.
if (_moveWithLatestRollup) {
require(isMsgSenderLatestRollup, Errors.GSE__NotLatestRollup(msg.sender));
}
// Ensure that we are not already attesting on the rollup
require(!isRegistered(msg.sender, _attester), Errors.GSE__AlreadyRegistered(msg.sender, _attester));
// Ensure that if we are the latest rollup, we are not already attesting on the bonus instance.
if (isMsgSenderLatestRollup) {
require(
!isRegistered(BONUS_INSTANCE_ADDRESS, _attester),
Errors.GSE__AlreadyRegistered(BONUS_INSTANCE_ADDRESS, _attester)
);
}
// Set the recipient instance address, i.e. the one that will receive the attester.
// From above, we know that if we are here, and _moveWithLatestRollup is true,
// then msg.sender is the latest instance,
// but the user is targeting the bonus address.
// Otherwise, we use the msg.sender, which we know is a registered rollup
// thanks to the modifier.
address recipientInstance = _moveWithLatestRollup ? BONUS_INSTANCE_ADDRESS : msg.sender;
// Add the attester to the instance's checkpointed set of attesters.
require(
instances[recipientInstance].attesters.add(_attester), Errors.GSE__AlreadyRegistered(recipientInstance, _attester)
);
_checkProofOfPossession(_attester, _publicKeyInG1, _publicKeyInG2, _proofOfPossession);
// This is the ONLY place where we set the configuration for an attester.
// This means that their withdrawer and public keys are set once, globally.
// If they exit, they must re-deposit with a new key.
configOf[_attester] = AttesterConfig({withdrawer: _withdrawer, publicKey: _publicKeyInG1});
delegation.delegate(recipientInstance, _attester, recipientInstance);
delegation.increaseBalance(recipientInstance, _attester, ACTIVATION_THRESHOLD);
ASSET.safeTransferFrom(msg.sender, address(this), ACTIVATION_THRESHOLD);
Governance gov = getGovernance();
ASSET.approve(address(gov), ACTIVATION_THRESHOLD);
gov.deposit(address(this), ACTIVATION_THRESHOLD);
emit Deposit(recipientInstance, _attester, _withdrawer);
}
/**
* @notice Withdraws at least the amount specified.
* If the leftover balance is less than the minimum deposit, the entire balance is withdrawn.
*
* @dev To be used by a rollup to withdraw funds from the GSE. For example if slashing or
* just withdrawing events happen, a rollup can use this function to withdraw the funds.
* It looks in both the rollup instance and the bonus address for the attester.
*
* @dev Note that all funds are returned to the rollup, so for slashing the rollup itself must
* address the problem of "what to do" with the funds. And it must look at the returned amount
* withdrawn and the bool.
*
* @param _attester - The attester to withdraw from.
* @param _amount - The amount of staking asset to withdraw. Has 1:1 ratio with voting power.
*
* @return The actual amount withdrawn.
* @return True if attester is removed from set, false otherwise
* @return The id of the withdrawal at the governance
*/
function withdraw(address _attester, uint256 _amount)
external
override(IGSECore)
onlyRollup
returns (uint256, bool, uint256)
{
// We need to figure out where the attester is effectively located
// we start by looking at the instance that is withdrawing the attester
address withdrawingInstance = msg.sender;
InstanceAttesterRegistry storage attesterRegistry = instances[msg.sender];
bool foundAttester = attesterRegistry.attesters.contains(_attester);
// If we haven't found the attester in the rollup instance, and we are latest rollup, go look in the "bonus"
// instance.
if (
!foundAttester && getLatestRollup() == msg.sender
&& instances[BONUS_INSTANCE_ADDRESS].attesters.contains(_attester)
) {
withdrawingInstance = BONUS_INSTANCE_ADDRESS;
attesterRegistry = instances[BONUS_INSTANCE_ADDRESS];
foundAttester = true;
}
require(foundAttester, Errors.GSE__NothingToExit(_attester));
uint256 balance = delegation.getBalanceOf(withdrawingInstance, _attester);
require(balance >= _amount, Errors.GSE__InsufficientBalance(balance, _amount));
// First assume we are only withdrawing the amount specified.
uint256 amountWithdrawn = _amount;
// If the balance after withdrawal is less than the ejection threshold,
// we will remove the attester from the instance.
bool isRemoved = balance - _amount < EJECTION_THRESHOLD;
// Note that the current implementation of the rollup does not allow for partial withdrawals,
// via `initiateWithdraw`, so a "normal" withdrawal will always remove the attester from the instance.
// However, if the attester is slashed, we might just reduce the balance.
if (isRemoved) {
require(attesterRegistry.attesters.remove(_attester), Errors.GSE__FailedToRemove(_attester));
amountWithdrawn = balance;
// When removing the user, remove the delegating as well.
delegation.undelegate(withdrawingInstance, _attester);
// NOTE
// We intentionally did not remove the attester config.
// Attester config is set ONCE when the attester is first seen by the GSE,
// and is shared across all instances.
}
// Decrease the balance of the attester in the instance.
// Move voting power from the attester's delegatee to address(0) (unless the delegatee is already address(0))
// Reduce the supply of the instance and the total supply.
delegation.decreaseBalance(withdrawingInstance, _attester, amountWithdrawn);
// The withdrawal contains a pending amount that may be claimed using the withdrawal ID when a delay enforced by
// the Governance contract has passed.
// Note that the rollup is the one that receives the funds when the withdrawal is claimed.
uint256 withdrawalId = getGovernance().initiateWithdraw(msg.sender, amountWithdrawn);
return (amountWithdrawn, isRemoved, withdrawalId);
}
/**
* @notice A helper function to make it easy for users of the GSE to finalize
* a pending exit in the governance.
*
* Kept in here since it is already connected to Governance:
* we don't want the rollup to have to deal with links to gov etc.
*
* @dev Will be a no operation if the withdrawal is already collected.
*
* @param _withdrawalId - The id of the withdrawal
*/
function finalizeWithdraw(uint256 _withdrawalId) external override(IGSECore) {
Governance gov = getGovernance();
if (!gov.getWithdrawal(_withdrawalId).claimed) {
gov.finalizeWithdraw(_withdrawalId);
}
}
/**
* @notice Make a proposal to Governance via `Governance.proposeWithLock`
*
* @dev It is required to expose this on the GSE, since it is assumed that only the GSE can hold
* power in Governance (see the comment at the top of Governance.sol).
*
* @dev Transfers governance's configured `lockAmount` of ASSET from msg.sender to the GSE,
* and then into Governance.
*
* @dev Immediately creates a withdrawal from Governance for the `lockAmount`.
*
* @dev The delay until the withdrawal may be finalized is equal to the current `lockDelay` in Governance.
*
* @param _payload - The IPayload address, which is a contract that contains the proposed actions to be executed by
* the governance.
* @param _to - The address that will receive the withdrawn funds when the withdrawal is finalized (see
* `finalizeWithdraw`)
*
* @return The id of the proposal
*/
function proposeWithLock(IPayload _payload, address _to) external override(IGSECore) returns (uint256) {
Governance gov = getGovernance();
uint256 amount = gov.getConfiguration().proposeConfig.lockAmount;
ASSET.safeTransferFrom(msg.sender, address(this), amount);
ASSET.approve(address(gov), amount);
gov.deposit(address(this), amount);
return gov.proposeWithLock(_payload, _to);
}
/**
* @notice Delegates the voting power of `_attester` at `_instance` to `_delegatee`
*
* Only callable by the `withdrawer` for the given `_attester` at the given
* `_instance`. This is to ensure that the depositor in poor mans delegation;
* listing another entity as the `attester`, still controls his voting power,
* even if someone else is running the node. Separately, it makes it simpler
* to use cold-storage for more impactful actions.
*
* @dev The delegatee may use this voting power to vote on proposals in Governance.
*
* Note that voting power for a delegatee is timestamped. The delegatee must have this
* power before a proposal becomes "active" in order to use it.
* See `Governance.getProposalState` for more details.
*
* @param _instance - The address of the rollup instance (or bonus instance address)
* to which the `_attester` deposit is pledged.
* @param _attester - The address of the attester to delegate on behalf of
* @param _delegatee - The delegatee that should receive the power
*/
function delegate(address _instance, address _attester, address _delegatee) external override(IGSECore) {
require(isRollupRegistered(_instance), Errors.GSE__InstanceDoesNotExist(_instance));
address withdrawer = configOf[_attester].withdrawer;
require(msg.sender == withdrawer, Errors.GSE__NotWithdrawer(withdrawer, msg.sender));
delegation.delegate(_instance, _attester, _delegatee);
}
/**
* @notice Votes at the governance using the power delegated to `msg.sender`
*
* @param _proposalId - The id of the proposal in the governance to vote on
* @param _amount - The amount of voting power to use in the vote
* In the gov, it is possible to do a vote with partial power
* @param _support - True if supporting the proposal, false otherwise.
*/
function vote(uint256 _proposalId, uint256 _amount, bool _support) external override(IGSECore) {
_vote(msg.sender, _proposalId, _amount, _support);
}
/**
* @notice Votes at the governance using the power delegated to the bonus instance.
* Only callable by the rollup that was the latest rollup at the time of the proposal.
*
* @param _proposalId - The id of the proposal in the governance to vote on
* @param _amount - The amount of voting power to use in the vote
* In the gov, it is possible to do a vote with partial power
*/
function voteWithBonus(uint256 _proposalId, uint256 _amount, bool _support) external override(IGSECore) {
Timestamp ts = _pendingThrough(_proposalId);
require(msg.sender == getLatestRollupAt(ts), Errors.GSE__NotLatestRollup(msg.sender));
_vote(BONUS_INSTANCE_ADDRESS, _proposalId, _amount, _support);
}
function isRollupRegistered(address _instance) public view override(IGSECore) returns (bool) {
return instances[_instance].exists;
}
/**
* @notice Lookup if the `_attester` is in the `_instance` attester set
*
* @param _instance - The instance to look at
* @param _attester - The attester to lookup
*
* @return True if the `_attester` is in the set of `_instance`, false otherwise
*/
function isRegistered(address _instance, address _attester) public view override(IGSECore) returns (bool) {
return instances[_instance].attesters.contains(_attester);
}
/**
* @notice Get the address of latest instance
*
* @return The address of the latest instance
*/
function getLatestRollup() public view override(IGSECore) returns (address) {
return address(rollups.latest().toUint160());
}
/**
* @notice Get the address of the instance that was latest at time `_timestamp`
*
* @param _timestamp - The timestamp to lookup
*
* @return The address of the latest instance at the time of lookup
*/
function getLatestRollupAt(Timestamp _timestamp) public view override(IGSECore) returns (address) {
return address(rollups.upperLookup(Timestamp.unwrap(_timestamp).toUint32()).toUint160());
}
function getGovernance() public view override(IGSECore) returns (Governance) {
return governance;
}
/**
* @notice Inner logic for the vote
*
* @dev Fetches the timestamp where proposal becomes active, and use it for the voting power
* of the `_voter`
*
* @param _voter - The voter
* @param _proposalId - The proposal to vote on
* @param _amount - The amount of power to use
* @param _support - True to support the proposal, false otherwise
*/
function _vote(address _voter, uint256 _proposalId, uint256 _amount, bool _support) internal {
Timestamp ts = _pendingThrough(_proposalId);
// Mark the power as spent within our delegation accounting.
delegation.usePower(_voter, _proposalId, ts, _amount);
// Vote on the proposal
getGovernance().vote(_proposalId, _amount, _support);
}
function _checkProofOfPossession(
address _attester,
G1Point memory _publicKeyInG1,
G2Point memory _publicKeyInG2,
G1Point memory _proofOfPossession
) internal virtual {
// Make sure the attester has not registered before
G1Point memory previouslyRegisteredPoint = configOf[_attester].publicKey;
require(
(previouslyRegisteredPoint.x == 0 && previouslyRegisteredPoint.y == 0),
Errors.GSE__CannotChangePublicKeys(previouslyRegisteredPoint.x, previouslyRegisteredPoint.y)
);
// Make sure the incoming point has not been seen before
// NOTE: we only need to check for the existence of Pk1, and not also for Pk2,
// as the Pk2 will be constrained to have the same underlying secret key as part of the proofOfPossession,
// so existence/correctness of Pk2 is implied by existence/correctness of Pk1.
bytes32 hashedIncomingPoint = keccak256(abi.encodePacked(_publicKeyInG1.x, _publicKeyInG1.y));
require((!ownedPKs[hashedIncomingPoint]), Errors.GSE__ProofOfPossessionAlreadySeen(hashedIncomingPoint));
ownedPKs[hashedIncomingPoint] = true;
// We validate the proof of possession using an external contract to limit gas potentially "sacrificed"
// in case of failure.
require(
BN254_LIB_WRAPPER.proofOfPossession{gas: proofOfPossessionGasLimit}(
_publicKeyInG1, _publicKeyInG2, _proofOfPossession
),
Errors.GSE__InvalidProofOfPossession()
);
}
function _pendingThrough(uint256 _proposalId) internal view returns (Timestamp) {
// Directly compute pendingThrough for memory proposal
Proposal memory proposal = getGovernance().getProposal(_proposalId);
return proposal.creation + proposal.config.votingDelay;
}
}
contract GSE is IGSE, GSECore {
using AddressSnapshotLib for SnapshottedAddressSet;
using SafeCast for uint256;
using SafeCast for uint224;
using Checkpoints for Checkpoints.Trace224;
using DepositDelegationLib for DepositAndDelegationAccounting;
constructor(address __owner, IERC20 _asset, uint256 _activationThreshold, uint256 _ejectionThreshold)
GSECore(__owner, _asset, _activationThreshold, _ejectionThreshold)
{}
/**
* @notice Get the registration digest of a public key
* by hashing the the public key to a point on the curve which may subsequently
* be signed by the corresponding private key.
*
* @param _publicKey - The public key to get the registration digest of
*
* @return The registration digest of the public key. Sign and submit as a proof of possession.
*/
function getRegistrationDigest(G1Point memory _publicKey) external view override(IGSE) returns (G1Point memory) {
return BN254_LIB_WRAPPER.g1ToDigestPoint(_publicKey);
}
function getConfig(address _attester) external view override(IGSE) returns (AttesterConfig memory) {
return configOf[_attester];
}
function getWithdrawer(address _attester) external view override(IGSE) returns (address withdrawer) {
AttesterConfig memory config = configOf[_attester];
return config.withdrawer;
}
function balanceOf(address _instance, address _attester) external view override(IGSE) returns (uint256) {
return delegation.getBalanceOf(_instance, _attester);
}
/**
* @notice Get the effective balance of the attester at the instance.
*
* The effective balance is the balance of the attester at the specific instance or at the bonus if the
* instance is the latest rollup and he was not at the specific. We can do this as an `or` since the
* attester may only be active at one of them.
*
* @param _instance - The instance to look at
* @param _attester - The attester to look at
*
* @return The effective balance of the attester at the instance
*/
function effectiveBalanceOf(address _instance, address _attester) external view override(IGSE) returns (uint256) {
uint256 balance = delegation.getBalanceOf(_instance, _attester);
if (balance == 0 && getLatestRollup() == _instance) {
return delegation.getBalanceOf(BONUS_INSTANCE_ADDRESS, _attester);
}
return balance;
}
function supplyOf(address _instance) external view override(IGSE) returns (uint256) {
return delegation.getSupplyOf(_instance);
}
function totalSupply() external view override(IGSE) returns (uint256) {
return delegation.getSupply();
}
function getDelegatee(address _instance, address _attester) external view override(IGSE) returns (address) {
return delegation.getDelegatee(_instance, _attester);
}
function getVotingPower(address _delegatee) external view override(IGSE) returns (uint256) {
return delegation.getVotingPower(_delegatee);
}
function getAttestersFromIndicesAtTime(address _instance, Timestamp _timestamp, uint256[] memory _indices)
external
view
override(IGSE)
returns (address[] memory)
{
return _getAddressFromIndicesAtTimestamp(_instance, _indices, _timestamp);
}
/**
* @notice Get the G1 public keys of the attesters
*
* NOTE: this function does NOT check if the attesters are CURRENTLY ACTIVE.
*
* @param _attesters - The attesters to lookup
*
* @return The G1 public keys of the attesters
*/
function getG1PublicKeysFromAddresses(address[] memory _attesters)
external
view
override(IGSE)
returns (G1Point[] memory)
{
G1Point[] memory keys = new G1Point[](_attesters.length);
for (uint256 i = 0; i < _attesters.length; i++) {
keys[i] = configOf[_attesters[i]].publicKey;
}
return keys;
}
function getAttesterFromIndexAtTime(address _instance, uint256 _index, Timestamp _timestamp)
external
view
override(IGSE)
returns (address)
{
uint256[] memory indices = new uint256[](1);
indices[0] = _index;
return _getAddressFromIndicesAtTimestamp(_instance, indices, _timestamp)[0];
}
function getPowerUsed(address _delegatee, uint256 _proposalId) external view override(IGSE) returns (uint256) {
return delegation.getPowerUsed(_delegatee, _proposalId);
}
function getBonusInstanceAddress() external pure override(IGSE) returns (address) {
return BONUS_INSTANCE_ADDRESS;
}
function getVotingPowerAt(address _delegatee, Timestamp _timestamp) public view override(IGSE) returns (uint256) {
return delegation.getVotingPowerAt(_delegatee, _timestamp);
}
/**
* @notice Get the number of effective attesters at the instance at the time of `_timestamp`
* (including the bonus instance)
*
* @param _instance - The instance to look at
* @param _timestamp - The timestamp to lookup
*
* @return The number of effective attesters at the instance at the time of `_timestamp`
*/
function getAttesterCountAtTime(address _instance, Timestamp _timestamp) public view override(IGSE) returns (uint256) {
InstanceAttesterRegistry storage store = instances[_instance];
uint32 timestamp = Timestamp.unwrap(_timestamp).toUint32();
uint256 count = store.attesters.lengthAtTimestamp(timestamp);
if (getLatestRollupAt(_timestamp) == _instance) {
count += instances[BONUS_INSTANCE_ADDRESS].attesters.lengthAtTimestamp(timestamp);
}
return count;
}
/**
* @notice Get the addresses of the attesters at the instance at the time of `_timestamp`
*
* @dev
*
* @param _instance - The instance to look at
* @param _indices - The indices of the attesters to lookup
* @param _timestamp - The timestamp to lookup
*
* @return The addresses of the attesters at the instance at the time of `_timestamp`
*/
function _getAddressFromIndicesAtTimestamp(address _instance, uint256[] memory _indices, Timestamp _timestamp)
internal
view
returns (address[] memory)
{
address[] memory attesters = new address[](_indices.length);
// Note: This function could get called where _instance is the bonus instance.
// This is okay, because we know that in this case, `isLatestRollup` will be false.
// So we won't double count.
InstanceAttesterRegistry storage instanceStore = instances[_instance];
InstanceAttesterRegistry storage bonusStore = instances[BONUS_INSTANCE_ADDRESS];
bool isLatestRollup = getLatestRollupAt(_timestamp) == _instance;
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;
// 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) {
attesters[i] = bonusStore.attesters.unsafeGetRecentAddressFromIndexAtTimestamp(index - storeSize, ts);
} else {
revert Errors.GSE__FatalError("SHOULD NEVER HAPPEN");
}
}
return attesters;
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {BaseStaker} from "@atp/staker/BaseStaker.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";
import {BN254Lib} from "src/staking-registry/libs/BN254.sol";
import {IStakingRegistry} from "src/staking-registry/StakingRegistry.sol";
import {IATPNonWithdrawableStaker, IGovernanceATP} from "src/staking/interfaces/IATPNonWithdrawableStaker.sol";
import {IGovernance, IPayload} from "src/staking/rollup-system-interfaces/IGovernance.sol";
import {IGSE} from "src/staking/rollup-system-interfaces/IGSE.sol";
import {IRegistry} from "src/staking/rollup-system-interfaces/IRegistry.sol";
import {IStaking} from "src/staking/rollup-system-interfaces/IStaking.sol";
/**
* @title ATP Staker
* @author Aztec-Labs
* @notice Stake from an ATP to earn rewards
*
* @notice NonWithdrawableStaker does not implement the withdrawal functionality, this will be enabled in an upgrade to the staker contract
* At the time of ignition, Aligned Stakers are expected to stake until their position is withdrawable.
*/
contract ATPNonWithdrawableStaker is IATPNonWithdrawableStaker, BaseStaker {
using SafeERC20 for IERC20;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Events */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
event Staked(address indexed staker, address indexed attester, address indexed rollupAddress);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Immutables */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
IERC20 public immutable STAKING_ASSET;
IRegistry public immutable ROLLUP_REGISTRY;
IStakingRegistry public immutable STAKING_REGISTRY;
constructor(IERC20 _stakingAsset, IRegistry _rollupRegistry, IStakingRegistry _stakingRegistry) {
STAKING_ASSET = _stakingAsset;
ROLLUP_REGISTRY = _rollupRegistry;
STAKING_REGISTRY = _stakingRegistry;
}
/**
* @notice Stake the staking asset to the rollup
*
* Withdrawer is set to this contract address
*
* @param _version The version of the rollup to deposit to
* @param _attester The address of the attester on the rollup
* @param _publicKeyG1 The public key of the attester - BN254Lib.G1Point
* @param _publicKeyG2 The public key of the attester - BN254Lib.G2Point
* @param _signature The signature of the attester - BN254Lib.G1Point
* @param _moveWithLatestRollup Whether to move the funds to the latest rollup version if the rollup has been upgraded
*
* @dev If _moveWithLatestRollup is true, then the rollup version MUST be the latest version
* @dev Requires atp.approveStaker() has been called before
*/
function stake(
uint256 _version,
address _attester,
BN254Lib.G1Point memory _publicKeyG1,
BN254Lib.G2Point memory _publicKeyG2,
BN254Lib.G1Point memory _signature,
bool _moveWithLatestRollup
) external virtual override(IATPNonWithdrawableStaker) onlyOperator {
_stake(_version, _attester, _publicKeyG1, _publicKeyG2, _signature, _moveWithLatestRollup);
}
/**
* @notice Stake with a provider
*
* A provider is an external operator that has registered themselves with the staking provider registry
* When using the staking registry
* - providers register themselves with a given take rate for their services
* - the attester field, is requested from a list of keys that the provider has listed
* - the stake function on the registry performs staking, creating a fee splitting contract for rewards to go into
*
* @param _version The version of the rollup to deposit to
* @param _providerIdentifier The identifier of the provider to stake with
* @param _expectedProviderTakeRate The expected provider take rate
* @param _userRewardsRecipient The address that will receive the user's reward split
* @param _moveWithLatestRollup Whether to move the funds to the latest rollup version if the rollup has been upgraded
*
* @dev Requires atp.approveStaker() has been called before
*/
function stakeWithProvider(
uint256 _version,
uint256 _providerIdentifier,
uint16 _expectedProviderTakeRate,
address _userRewardsRecipient,
bool _moveWithLatestRollup
) external virtual override(IATPNonWithdrawableStaker) onlyOperator {
_stakeWithProvider(
_version, _providerIdentifier, _expectedProviderTakeRate, _userRewardsRecipient, _moveWithLatestRollup
);
}
/**
* @notice If and only if the atp is set as the coinbase for the active validator, then rewards will need to be claimed
* from the rollup
*
* @param _version The version of the rollup to claim rewards from
*/
function claimRewards(uint256 _version) external override(IATPNonWithdrawableStaker) onlyOperator {
address rollup = ROLLUP_REGISTRY.getRollup(_version);
address atp = getATP();
IStaking(rollup).claimSequencerRewards(atp);
}
/**
* @notice delegate voting power to a delegatee
* @notice By default voting power is delegated to the rollup itself, with validators determining
* what proposals will get voted on. This means most users will not need to delegate their vote if they
* are staking.
*
* @dev this function only requires to delegating staked tokens
* use depositIntoGovernance to vote with unstaked tokens
*
* @param _version The version of the rollup to delegate to
* @param _attester The address of the attester the voting power is associated with on the rollup
* @param _delegatee The address of the delegatee
*/
function delegate(uint256 _version, address _attester, address _delegatee)
external
virtual
override(IATPNonWithdrawableStaker)
onlyOperator
{
address rollup = ROLLUP_REGISTRY.getRollup(_version);
address gse = IStaking(rollup).getGSE();
IGSE(gse).delegate(rollup, _attester, _delegatee);
}
/**
* @notice Deposit tokens into governance for voting
* @notice This staker contract becomes the beneficiary and holder of voting power
* voting must take place through the voteInGovernance function
*
* @dev Governance contract is derived from the rollup registry
*
* @param _amount The amount of tokens to deposit into governance
*/
function depositIntoGovernance(uint256 _amount) external override(IGovernanceATP) onlyOperator {
address governance = ROLLUP_REGISTRY.getGovernance();
STAKING_ASSET.safeTransferFrom(address(atp), address(this), _amount);
STAKING_ASSET.approve(address(governance), _amount);
IGovernance(governance).deposit(address(this), _amount);
}
/**
* @notice Vote in governance
* @notice Voting power is held by this staker contract
* Users must first deposit into Goverance via depositIntoGovernance first
*
* @dev Governance contract is derived from the rollup registry
*
* @param _proposalId The ID of the proposal to vote on
* @param _amount The amount of tokens to vote with
* @param _support The support for the proposal
*/
function voteInGovernance(uint256 _proposalId, uint256 _amount, bool _support)
external
override(IGovernanceATP)
onlyOperator
{
address governance = ROLLUP_REGISTRY.getGovernance();
IGovernance(governance).vote(_proposalId, _amount, _support);
}
/**
* @notice Initiate a withdrawal from governance
* @notice This function will initiate a withdrawal from governance
* Users must first deposit into Goverance via depositIntoGovernance first
*
* @dev Governance contract is derived from the rollup registry
*
* @param _amount The amount of tokens to withdraw from governance
* @return The withdrawal ID - this must be used when calling finalizeWithdraw on the Governance contract
*/
function initiateWithdrawFromGovernance(uint256 _amount)
external
override(IGovernanceATP)
onlyOperator
returns (uint256)
{
address governance = ROLLUP_REGISTRY.getGovernance();
address atp = getATP();
return IGovernance(governance).initiateWithdraw(atp, _amount);
}
/**
* @notice Propose With Lock
* @notice This function will make a proposal into Goverance but funds will be locked for an
* extended period of time - see the Gov implementation for more details
*
* @param _proposal The proposal to propose
* @return The proposal ID
*/
function proposeWithLock(IPayload _proposal) external override(IGovernanceATP) onlyOperator returns (uint256) {
address governance = ROLLUP_REGISTRY.getGovernance();
address atp = getATP();
return IGovernance(governance).proposeWithLock(_proposal, atp);
}
/**
* @notice Move the funds back to the ATP
*
* Case in which this is required:
* - When calling deposit with _moveWithLatestRollup set to true, the staker will enter the deposit queue
* - If user gets to the front of the queue, but the rollup has been upgraded, _moveWithLatestRollup will be invalid
* - This will return the funds to the withdrawer (this address)
* - This leaves the user to perform the following steps:
* - return the funds back to the atp
* - then call stake again on the updated rollup version
*
* @dev This function is only callable by the operator
* @dev This function will move the funds back to the ATP ONLY
*/
function moveFundsBackToATP() external override(IATPNonWithdrawableStaker) onlyOperator {
address atp = getATP();
uint256 balance = STAKING_ASSET.balanceOf(address(this));
STAKING_ASSET.safeTransfer(atp, balance);
}
function _stake(
uint256 _version,
address _attester,
BN254Lib.G1Point memory _publicKeyG1,
BN254Lib.G2Point memory _publicKeyG2,
BN254Lib.G1Point memory _signature,
bool _moveWithLatestRollup
) internal virtual onlyOperator {
address rollup = ROLLUP_REGISTRY.getRollup(_version);
uint256 activationThreshold = IStaking(rollup).getActivationThreshold();
STAKING_ASSET.safeTransferFrom(address(atp), address(this), activationThreshold);
STAKING_ASSET.approve(rollup, activationThreshold);
IStaking(rollup)
.deposit(_attester, address(this), _publicKeyG1, _publicKeyG2, _signature, _moveWithLatestRollup);
emit Staked(address(this), _attester, rollup);
}
function _stakeWithProvider(
uint256 _version,
uint256 _providerIdentifier,
uint16 _expectedProviderTakeRate,
address _userRewardsRecipient,
bool _moveWithLatestRollup
) internal virtual onlyOperator {
address rollup = ROLLUP_REGISTRY.getRollup(_version);
uint256 activationThreshold = IStaking(rollup).getActivationThreshold();
STAKING_ASSET.safeTransferFrom(address(atp), address(this), activationThreshold);
STAKING_ASSET.approve(address(STAKING_REGISTRY), activationThreshold);
STAKING_REGISTRY.stake(
_providerIdentifier,
_version,
address(this),
_expectedProviderTakeRate,
_userRewardsRecipient,
_moveWithLatestRollup
);
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {IATPCore} from "@atp/atps/base/IATP.sol";
import {NCATP} from "@atp/atps/noclaim/NCATP.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {BN254Lib} from "src/staking-registry/libs/BN254.sol";
import {IStakingRegistry} from "src/staking-registry/StakingRegistry.sol";
import {ATPNonWithdrawableStaker} from "src/staking/ATPNonWithdrawableStaker.sol";
import {ATPWithdrawableStaker} from "src/staking/ATPWithdrawableStaker.sol";
import {IATPNonWithdrawableStaker} from "src/staking/interfaces/IATPNonWithdrawableStaker.sol";
import {IATPWithdrawableAndClaimableStaker} from "src/staking/interfaces/IATPWithdrawableAndClaimableStaker.sol";
import {IRegistry} from "src/staking/rollup-system-interfaces/IRegistry.sol";
/**
* @title ATP Withdrawable And Claimable Staker
* @author Aztec-Labs
* @notice An implementation of an ATP Staker that allows for withdrawals from the rollup
* and enables NCATP token holders to claim tokens only after staking has occured.
*/
contract ATPWithdrawableAndClaimableStaker is IATPWithdrawableAndClaimableStaker, ATPWithdrawableStaker {
using SafeERC20 for IERC20;
/**
* @dev Storage of the ATPWithdrawableAndClaimableStaker contract.
*
* @custom:storage-location erc7201:aztec.storage.ATPWithdrawableAndClaimableStaker
*/
struct ATPWithdrawableAndClaimableStakerStorage {
/**
* @notice Flag indicating whether staking has occured
*/
bool hasStaked;
}
// keccak256(abi.encode(uint256(keccak256("aztec.storage.ATPWithdrawableAndClaimableStaker")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant _ATP_WITHDRAWABLE_AND_CLAIMABLE_STAKER_STORAGE =
0x2527cfd5830db0c841b72084c1ec066be32b8320e2ee2b8cb438bb32af8d8500;
/**
* @notice The timestamp at which withdrawals are enabled.
*/
uint256 public immutable WITHDRAWAL_TIMESTAMP;
/**
* @notice Emitted when tokens are withdrawn by NCATP holders
* @param recipient The address receiving the tokens
* @param amount The amount of tokens withdrawn
*/
event TokensWithdrawn(address indexed recipient, uint256 amount);
/**
* @notice Emitted when withdrawable status changes
*/
event WithdrawableStatusChanged();
/**
* @notice Emitted when tokens are withdrawn to the beneficiary
* @param beneficiary The address of the ATP beneficiary
* @param amount The amount of tokens withdrawn
*/
event TokensWithdrawnToBeneficiary(address indexed beneficiary, uint256 amount);
/**
* @notice Error thrown when attempting to withdraw before staking has occurred
*/
error StakingNotOccurred();
/**
* @notice Error thrown when attempting to withdraw before the withdrawal delay has passed
*/
error WithdrawalDelayNotPassed();
constructor(
IERC20 _stakingAsset,
IRegistry _rollupRegistry,
IStakingRegistry _stakingRegistry,
uint256 _withdrawalTimestamp
) ATPWithdrawableStaker(_stakingAsset, _rollupRegistry, _stakingRegistry) {
WITHDRAWAL_TIMESTAMP = _withdrawalTimestamp;
}
/**
* @notice Stake the staking asset to the rollup
* @dev Overrides the parent stake function to set withdrawable to true after successful staking
*
* @param _version The version of the rollup to deposit to
* @param _attester The address of the attester on the rollup
* @param _publicKeyG1 The public key of the attester - BN254Lib.G1Point
* @param _publicKeyG2 The public key of the attester - BN254Lib.G2Point
* @param _signature The signature of the attester - BN254Lib.G1Point
* @param _moveWithLatestRollup Whether to move the funds to the latest rollup version if the rollup has been upgraded
*
* @dev If _moveWithLatestRollup is true, then the rollup version MUST be the latest version
* @dev Requires atp.approveStaker() has been called before
*/
function stake(
uint256 _version,
address _attester,
BN254Lib.G1Point memory _publicKeyG1,
BN254Lib.G2Point memory _publicKeyG2,
BN254Lib.G1Point memory _signature,
bool _moveWithLatestRollup
) external override(ATPNonWithdrawableStaker, IATPNonWithdrawableStaker) onlyOperator {
// Call parent stake function
_stake(_version, _attester, _publicKeyG1, _publicKeyG2, _signature, _moveWithLatestRollup);
ATPWithdrawableAndClaimableStakerStorage storage $ = _getATPWithdrawableAndClaimableStakerStorage();
if (!$.hasStaked) {
$.hasStaked = true;
emit WithdrawableStatusChanged();
}
}
/**
* @notice Stake with a provider
* @dev Overrides the parent stakeWithProvider function to set withdrawable to true after successful delegation
*
* @param _version The version of the rollup to deposit to
* @param _providerIdentifier The identifier of the provider to stake wit
* @param _expectedProviderTakeRate The expected provider take rate
* @param _userRewardsRecipient The address that will receive the user's reward split
* @param _moveWithLatestRollup Whether to move the funds to the latest rollup version if the rollup has been upgraded
*/
function stakeWithProvider(
uint256 _version,
uint256 _providerIdentifier,
uint16 _expectedProviderTakeRate,
address _userRewardsRecipient,
bool _moveWithLatestRollup
) external override(ATPNonWithdrawableStaker, IATPNonWithdrawableStaker) onlyOperator {
// Call parent stakeWithProvider function
_stakeWithProvider(
_version, _providerIdentifier, _expectedProviderTakeRate, _userRewardsRecipient, _moveWithLatestRollup
);
// Set withdrawable to true after successful staking
ATPWithdrawableAndClaimableStakerStorage storage $ = _getATPWithdrawableAndClaimableStakerStorage();
if (!$.hasStaked) {
$.hasStaked = true;
emit WithdrawableStatusChanged();
}
}
/**
* @notice Withdraw all available tokens to the beneficiary
* @dev Only callable if staking has occurred (withdrawable == true)
*/
function withdrawAllTokensToBeneficiary() external override(IATPWithdrawableAndClaimableStaker) onlyOperator {
address atp = getATP();
require(hasStaked(), StakingNotOccurred());
require(block.timestamp >= WITHDRAWAL_TIMESTAMP, WithdrawalDelayNotPassed());
uint256 atpBalance = STAKING_ASSET.balanceOf(atp);
address beneficiary = IATPCore(atp).getBeneficiary();
if (atpBalance > 0) {
STAKING_ASSET.safeTransferFrom(atp, beneficiary, atpBalance);
emit TokensWithdrawnToBeneficiary(beneficiary, atpBalance);
}
}
/**
* @notice Returns the hasStaked flag
* @return bool indicating whether staking has occurred
*/
function hasStaked() public view override(IATPWithdrawableAndClaimableStaker) returns (bool) {
ATPWithdrawableAndClaimableStakerStorage storage $ = _getATPWithdrawableAndClaimableStakerStorage();
return $.hasStaked;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
function _getATPWithdrawableAndClaimableStakerStorage()
private
pure
returns (ATPWithdrawableAndClaimableStakerStorage storage $)
{
assembly {
$.slot := _ATP_WITHDRAWABLE_AND_CLAIMABLE_STAKER_STORAGE
}
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {IStakingRegistry} from "src/staking-registry/StakingRegistry.sol";
import {ATPNonWithdrawableStaker} from "src/staking/ATPNonWithdrawableStaker.sol";
import {IATPWithdrawableStaker} from "src/staking/interfaces/IATPWithdrawableStaker.sol";
import {IRegistry} from "src/staking/rollup-system-interfaces/IRegistry.sol";
import {IStaking} from "src/staking/rollup-system-interfaces/IStaking.sol";
/**
* @title ATP Withdrawable Staker
* @author Aztec-Labs
* @notice An implementation of an ATP staker that allows for withdrawals from the rollup
*/
contract ATPWithdrawableStaker is IATPWithdrawableStaker, ATPNonWithdrawableStaker {
constructor(IERC20 _stakingAsset, IRegistry _rollupRegistry, IStakingRegistry _stakingRegistry)
ATPNonWithdrawableStaker(_stakingAsset, _rollupRegistry, _stakingRegistry)
{}
/**
* @notice Initiate a withdrawal from the rollup
*
* @param _version - the version of the rollup the _attester is active on
* @param _attester - the address of the attester on the rollup
*
* @dev Initiating a withdrawal will return funds to the _recipient address, which is set the ATP address
*/
function initiateWithdraw(uint256 _version, address _attester)
external
override(IATPWithdrawableStaker)
onlyOperator
{
address rollup = ROLLUP_REGISTRY.getRollup(_version);
address atp = getATP();
IStaking(rollup).initiateWithdraw(_attester, atp);
}
/**
* @notice Finalize a withdrawal from the rollup
* - Note on the rollup contract, anyone can call this - it just exists for completeness
*
* @param _version The version of the rollup the _attester is active on
* @param _attester The address of the attester on the rollup
*
* @dev This function can be called by anyone on the rollup, and is not necessarily required to be called via the staker
*/
function finalizeWithdraw(uint256 _version, address _attester) external virtual override(IATPWithdrawableStaker) {
address rollup = ROLLUP_REGISTRY.getRollup(_version);
IStaking(rollup).finaliseWithdraw(_attester);
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {BN254Lib} from "src/staking-registry/libs/BN254.sol";
import {IGovernanceATP} from "./IGovernanceATP.sol";
interface IATPNonWithdrawableStaker is IGovernanceATP {
function stake(
uint256 _version,
address _attester,
BN254Lib.G1Point memory _publicKeyG1,
BN254Lib.G2Point memory _publicKeyG2,
BN254Lib.G1Point memory _signature,
bool _moveWithLatestRollup
) external;
function stakeWithProvider(
uint256 _version,
uint256 _providerIdentifier,
uint16 _expectedProviderTakeRate,
address _userRewardsRecipient,
bool _moveWithLatestRollup
) external;
function moveFundsBackToATP() external;
function claimRewards(uint256 _version) external;
function delegate(uint256 _version, address _attester, address _delegatee) external;
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {IATPNonWithdrawableStaker} from "./IATPNonWithdrawableStaker.sol";
interface IATPWithdrawableStaker is IATPNonWithdrawableStaker {
/**
* @notice Initiate a withdrawal from the rollup
*
* @param _version - the rollup version to withdraw from
* @param _attester - the address of the attester being withdrawn
*
* @dev This will revert if the staker is not the withdrawer
*/
function initiateWithdraw(uint256 _version, address _attester) external;
function finalizeWithdraw(uint256 _version, address _attester) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
import {LowLevelCall} from "./LowLevelCall.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
if (LowLevelCall.callNoReturn(recipient, amount, "")) {
// call successful, nothing to do
return;
} else if (LowLevelCall.returnDataSize() > 0) {
LowLevelCall.bubbleRevert();
} else {
revert Errors.FailedCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
bool success = LowLevelCall.callNoReturn(target, value, data);
if (success && (LowLevelCall.returnDataSize() > 0 || target.code.length > 0)) {
return LowLevelCall.returnData();
} else if (success) {
revert AddressEmptyCode(target);
} else if (LowLevelCall.returnDataSize() > 0) {
LowLevelCall.bubbleRevert();
} else {
revert Errors.FailedCall();
}
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
bool success = LowLevelCall.staticcallNoReturn(target, data);
if (success && (LowLevelCall.returnDataSize() > 0 || target.code.length > 0)) {
return LowLevelCall.returnData();
} else if (success) {
revert AddressEmptyCode(target);
} else if (LowLevelCall.returnDataSize() > 0) {
LowLevelCall.bubbleRevert();
} else {
revert Errors.FailedCall();
}
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
bool success = LowLevelCall.delegatecallNoReturn(target, data);
if (success && (LowLevelCall.returnDataSize() > 0 || target.code.length > 0)) {
return LowLevelCall.returnData();
} else if (success) {
revert AddressEmptyCode(target);
} else if (LowLevelCall.returnDataSize() > 0) {
LowLevelCall.bubbleRevert();
} else {
revert Errors.FailedCall();
}
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*
* NOTE: This function is DEPRECATED and may be removed in the next major release.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (success && (returndata.length > 0 || target.code.length > 0)) {
return returndata;
} else if (success) {
revert AddressEmptyCode(target);
} else if (returndata.length > 0) {
LowLevelCall.bubbleRevert(returndata);
} else {
revert Errors.FailedCall();
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {Errors.FailedCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else if (returndata.length > 0) {
LowLevelCall.bubbleRevert(returndata);
} else {
revert Errors.FailedCall();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {IDistributionContract} from "./IDistributionContract.sol";
/// @title ILBPStrategyBasic
/// @notice Interface for the LBPStrategyBasic contract
interface ILBPStrategyBasic is IDistributionContract {
/// @notice Emitted when a v4 pool is created and the liquidity is migrated to it
/// @param key The key of the pool that was created
/// @param initialSqrtPriceX96 The initial sqrt price of the pool
event Migrated(PoolKey indexed key, uint160 initialSqrtPriceX96);
/// @notice Emitted when the auction is created
/// @param auction The address of the auction contract
event AuctionCreated(address indexed auction);
/// @notice Error thrown when migration to a v4 pool is not allowed yet
/// @param migrationBlock The block number at which migration is allowed
/// @param currentBlock The current block number
error MigrationNotAllowed(uint256 migrationBlock, uint256 currentBlock);
/// @notice Emitted when the tokens are swept
event TokensSwept(address indexed operator, uint256 amount);
/// @notice Emitted when the currency is swept
event CurrencySwept(address indexed operator, uint256 amount);
/// @notice Error thrown when the sweep block is before or at the migration block
error InvalidSweepBlock(uint256 sweepBlock, uint256 migrationBlock);
/// @notice Error thrown when the end block is at orafter the migration block
/// @param endBlock The invalid end block
/// @param migrationBlock The migration block
error InvalidEndBlock(uint256 endBlock, uint256 migrationBlock);
/// @notice Error thrown when the currency in the auction parameters is not the same as the currency in the migrator parameters
/// @param auctionCurrency The currency in the auction parameters
/// @param migratorCurrency The currency in the migrator parameters
error InvalidCurrency(address auctionCurrency, address migratorCurrency);
/// @notice Error thrown when the floor price is invalid
/// @param floorPrice The invalid floor price
error InvalidFloorPrice(uint256 floorPrice);
/// @notice Error thrown when the token split is too high
/// @param tokenSplit The invalid token split percentage
error TokenSplitTooHigh(uint24 tokenSplit, uint24 maxTokenSplit);
/// @notice Error thrown when the tick spacing is greater than the max tick spacing or less than the min tick spacing
/// @param tickSpacing The invalid tick spacing
error InvalidTickSpacing(int24 tickSpacing, int24 minTickSpacing, int24 maxTickSpacing);
/// @notice Error thrown when the fee is greater than the max fee
/// @param fee The invalid fee
error InvalidFee(uint24 fee, uint24 maxFee);
/// @notice Error thrown when the position recipient is the zero address, address(1), or address(2)
/// @param positionRecipient The invalid position recipient
error InvalidPositionRecipient(address positionRecipient);
/// @notice Error thrown when the funds recipient is not set to address(1)
/// @param invalidFundsRecipient The invalid funds recipient
/// @param expectedFundsRecipient The expected funds recipient (address(1))
error InvalidFundsRecipient(address invalidFundsRecipient, address expectedFundsRecipient);
/// @notice Error thrown when the reserve supply is too high
/// @param reserveSupply The invalid reserve supply
/// @param maxReserveSupply The maximum reserve supply (type(uint128).max)
error ReserveSupplyIsTooHigh(uint256 reserveSupply, uint256 maxReserveSupply);
/// @notice Error thrown when the liquidity is invalid
/// @param liquidity The invalid liquidity
/// @param maxLiquidity The max liquidity
error InvalidLiquidity(uint128 liquidity, uint128 maxLiquidity);
/// @notice Error thrown when the caller is not the auction
/// @param caller The caller that is not the auction
/// @param auction The auction that is not the caller
error NativeCurrencyTransferNotFromAuction(address caller, address auction);
/// @notice Error thrown when the caller is not the operator
error NotOperator(address caller, address operator);
/// @notice Error thrown when the sweep is not allowed yet
error SweepNotAllowed(uint256 sweepBlock, uint256 currentBlock);
/// @notice Error thrown when the token amount is invalid
/// @param tokenAmount The invalid token amount
/// @param reserveSupply The reserve supply
error InvalidTokenAmount(uint128 tokenAmount, uint128 reserveSupply);
/// @notice Error thrown when the auction supply is zero
error AuctionSupplyIsZero();
/// @notice Error thrown when the currency amount is greater than type(uint128).max
/// @param currencyAmount The invalid currency amount
/// @param maxCurrencyAmount The maximum currency amount (type(uint128).max)
error CurrencyAmountTooHigh(uint256 currencyAmount, uint256 maxCurrencyAmount);
/// @notice Error thrown when the currency amount is invalid
/// @param amountNeeded The currency amount needed
/// @param amountAvailable The balance of the currency in the contract
error InsufficientCurrency(uint256 amountNeeded, uint256 amountAvailable);
/// @notice Error thrown when no currency was raised
error NoCurrencyRaised();
/// @notice Error thrown when the token amount is too high
/// @param tokenAmount The invalid token amount
error AmountOverflow(uint256 tokenAmount);
/// @notice Migrates the raised funds and tokens to a v4 pool
function migrate() external;
/// @notice Allows the operator to sweep tokens from the contract
/// @dev Can only be called after sweepBlock by the operator
function sweepToken() external;
/// @notice Allows the operator to sweep currency from the contract
/// @dev Can only be called after sweepBlock by the operator
function sweepCurrency() external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PositionInfo} from "../libraries/PositionInfoLibrary.sol";
import {INotifier} from "./INotifier.sol";
import {IImmutableState} from "./IImmutableState.sol";
import {IERC721Permit_v4} from "./IERC721Permit_v4.sol";
import {IEIP712_v4} from "./IEIP712_v4.sol";
import {IMulticall_v4} from "./IMulticall_v4.sol";
import {IPoolInitializer_v4} from "./IPoolInitializer_v4.sol";
import {IUnorderedNonce} from "./IUnorderedNonce.sol";
import {IPermit2Forwarder} from "./IPermit2Forwarder.sol";
/// @title IPositionManager
/// @notice Interface for the PositionManager contract
interface IPositionManager is
INotifier,
IImmutableState,
IERC721Permit_v4,
IEIP712_v4,
IMulticall_v4,
IPoolInitializer_v4,
IUnorderedNonce,
IPermit2Forwarder
{
/// @notice Thrown when the caller is not approved to modify a position
error NotApproved(address caller);
/// @notice Thrown when the block.timestamp exceeds the user-provided deadline
error DeadlinePassed(uint256 deadline);
/// @notice Thrown when calling transfer, subscribe, or unsubscribe when the PoolManager is unlocked.
/// @dev This is to prevent hooks from being able to trigger notifications at the same time the position is being modified.
error PoolManagerMustBeLocked();
/// @notice Unlocks Uniswap v4 PoolManager and batches actions for modifying liquidity
/// @dev This is the standard entrypoint for the PositionManager
/// @param unlockData is an encoding of actions, and parameters for those actions
/// @param deadline is the deadline for the batched actions to be executed
function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable;
/// @notice Batches actions for modifying liquidity without unlocking v4 PoolManager
/// @dev This must be called by a contract that has already unlocked the v4 PoolManager
/// @param actions the actions to perform
/// @param params the parameters to provide for the actions
function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params) external payable;
/// @notice Used to get the ID that will be used for the next minted liquidity position
/// @return uint256 The next token ID
function nextTokenId() external view returns (uint256);
/// @notice Returns the liquidity of a position
/// @param tokenId the ERC721 tokenId
/// @return liquidity the position's liquidity, as a liquidityAmount
/// @dev this value can be processed as an amount0 and amount1 by using the LiquidityAmounts library
function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity);
/// @notice Returns the pool key and position info of a position
/// @param tokenId the ERC721 tokenId
/// @return poolKey the pool key of the position
/// @return PositionInfo a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
function getPoolAndPositionInfo(uint256 tokenId) external view returns (PoolKey memory, PositionInfo);
/// @notice Returns the position info of a position
/// @param tokenId the ERC721 tokenId
/// @return a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
function positionInfo(uint256 tokenId) external view returns (PositionInfo);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Currency} from "../types/Currency.sol";
import {PoolKey} from "../types/PoolKey.sol";
import {IHooks} from "./IHooks.sol";
import {IERC6909Claims} from "./external/IERC6909Claims.sol";
import {IProtocolFees} from "./IProtocolFees.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {PoolId} from "../types/PoolId.sol";
import {IExtsload} from "./IExtsload.sol";
import {IExttload} from "./IExttload.sol";
import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol";
/// @notice Interface for the PoolManager
interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload, IExttload {
/// @notice Thrown when a currency is not netted out after the contract is unlocked
error CurrencyNotSettled();
/// @notice Thrown when trying to interact with a non-initialized pool
error PoolNotInitialized();
/// @notice Thrown when unlock is called, but the contract is already unlocked
error AlreadyUnlocked();
/// @notice Thrown when a function is called that requires the contract to be unlocked, but it is not
error ManagerLocked();
/// @notice Pools are limited to type(int16).max tickSpacing in #initialize, to prevent overflow
error TickSpacingTooLarge(int24 tickSpacing);
/// @notice Pools must have a positive non-zero tickSpacing passed to #initialize
error TickSpacingTooSmall(int24 tickSpacing);
/// @notice PoolKey must have currencies where address(currency0) < address(currency1)
error CurrenciesOutOfOrderOrEqual(address currency0, address currency1);
/// @notice Thrown when a call to updateDynamicLPFee is made by an address that is not the hook,
/// or on a pool that does not have a dynamic swap fee.
error UnauthorizedDynamicLPFeeUpdate();
/// @notice Thrown when trying to swap amount of 0
error SwapAmountCannotBeZero();
///@notice Thrown when native currency is passed to a non native settlement
error NonzeroNativeValue();
/// @notice Thrown when `clear` is called with an amount that is not exactly equal to the open currency delta.
error MustClearExactPositiveDelta();
/// @notice Emitted when a new pool is initialized
/// @param id The abi encoded hash of the pool key struct for the new pool
/// @param currency0 The first currency of the pool by address sort order
/// @param currency1 The second currency of the pool by address sort order
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
/// @param tickSpacing The minimum number of ticks between initialized ticks
/// @param hooks The hooks contract address for the pool, or address(0) if none
/// @param sqrtPriceX96 The price of the pool on initialization
/// @param tick The initial tick of the pool corresponding to the initialized price
event Initialize(
PoolId indexed id,
Currency indexed currency0,
Currency indexed currency1,
uint24 fee,
int24 tickSpacing,
IHooks hooks,
uint160 sqrtPriceX96,
int24 tick
);
/// @notice Emitted when a liquidity position is modified
/// @param id The abi encoded hash of the pool key struct for the pool that was modified
/// @param sender The address that modified the pool
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param liquidityDelta The amount of liquidity that was added or removed
/// @param salt The extra data to make positions unique
event ModifyLiquidity(
PoolId indexed id, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta, bytes32 salt
);
/// @notice Emitted for swaps between currency0 and currency1
/// @param id The abi encoded hash of the pool key struct for the pool that was modified
/// @param sender The address that initiated the swap call, and that received the callback
/// @param amount0 The delta of the currency0 balance of the pool
/// @param amount1 The delta of the currency1 balance of the pool
/// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
/// @param liquidity The liquidity of the pool after the swap
/// @param tick The log base 1.0001 of the price of the pool after the swap
/// @param fee The swap fee in hundredths of a bip
event Swap(
PoolId indexed id,
address indexed sender,
int128 amount0,
int128 amount1,
uint160 sqrtPriceX96,
uint128 liquidity,
int24 tick,
uint24 fee
);
/// @notice Emitted for donations
/// @param id The abi encoded hash of the pool key struct for the pool that was donated to
/// @param sender The address that initiated the donate call
/// @param amount0 The amount donated in currency0
/// @param amount1 The amount donated in currency1
event Donate(PoolId indexed id, address indexed sender, uint256 amount0, uint256 amount1);
/// @notice All interactions on the contract that account deltas require unlocking. A caller that calls `unlock` must implement
/// `IUnlockCallback(msg.sender).unlockCallback(data)`, where they interact with the remaining functions on this contract.
/// @dev The only functions callable without an unlocking are `initialize` and `updateDynamicLPFee`
/// @param data Any data to pass to the callback, via `IUnlockCallback(msg.sender).unlockCallback(data)`
/// @return The data returned by the call to `IUnlockCallback(msg.sender).unlockCallback(data)`
function unlock(bytes calldata data) external returns (bytes memory);
/// @notice Initialize the state for a given pool ID
/// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
/// @param key The pool key for the pool to initialize
/// @param sqrtPriceX96 The initial square root price
/// @return tick The initial tick of the pool
function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick);
/// @notice Modify the liquidity for the given pool
/// @dev Poke by calling with a zero liquidityDelta
/// @param key The pool to modify liquidity in
/// @param params The parameters for modifying the liquidity
/// @param hookData The data to pass through to the add/removeLiquidity hooks
/// @return callerDelta The balance delta of the caller of modifyLiquidity. This is the total of both principal, fee deltas, and hook deltas if applicable
/// @return feesAccrued The balance delta of the fees generated in the liquidity range. Returned for informational purposes
/// @dev Note that feesAccrued can be artificially inflated by a malicious actor and integrators should be careful using the value
/// For pools with a single liquidity position, actors can donate to themselves to inflate feeGrowthGlobal (and consequently feesAccrued)
/// atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme
function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData)
external
returns (BalanceDelta callerDelta, BalanceDelta feesAccrued);
/// @notice Swap against the given pool
/// @param key The pool to swap in
/// @param params The parameters for swapping
/// @param hookData The data to pass through to the swap hooks
/// @return swapDelta The balance delta of the address swapping
/// @dev Swapping on low liquidity pools may cause unexpected swap amounts when liquidity available is less than amountSpecified.
/// Additionally note that if interacting with hooks that have the BEFORE_SWAP_RETURNS_DELTA_FLAG or AFTER_SWAP_RETURNS_DELTA_FLAG
/// the hook may alter the swap input/output. Integrators should perform checks on the returned swapDelta.
function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData)
external
returns (BalanceDelta swapDelta);
/// @notice Donate the given currency amounts to the in-range liquidity providers of a pool
/// @dev Calls to donate can be frontrun adding just-in-time liquidity, with the aim of receiving a portion donated funds.
/// Donors should keep this in mind when designing donation mechanisms.
/// @dev This function donates to in-range LPs at slot0.tick. In certain edge-cases of the swap algorithm, the `sqrtPrice` of
/// a pool can be at the lower boundary of tick `n`, but the `slot0.tick` of the pool is already `n - 1`. In this case a call to
/// `donate` would donate to tick `n - 1` (slot0.tick) not tick `n` (getTickAtSqrtPrice(slot0.sqrtPriceX96)).
/// Read the comments in `Pool.swap()` for more information about this.
/// @param key The key of the pool to donate to
/// @param amount0 The amount of currency0 to donate
/// @param amount1 The amount of currency1 to donate
/// @param hookData The data to pass through to the donate hooks
/// @return BalanceDelta The delta of the caller after the donate
function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData)
external
returns (BalanceDelta);
/// @notice Writes the current ERC20 balance of the specified currency to transient storage
/// This is used to checkpoint balances for the manager and derive deltas for the caller.
/// @dev This MUST be called before any ERC20 tokens are sent into the contract, but can be skipped
/// for native tokens because the amount to settle is determined by the sent value.
/// However, if an ERC20 token has been synced and not settled, and the caller instead wants to settle
/// native funds, this function can be called with the native currency to then be able to settle the native currency
function sync(Currency currency) external;
/// @notice Called by the user to net out some value owed to the user
/// @dev Will revert if the requested amount is not available, consider using `mint` instead
/// @dev Can also be used as a mechanism for free flash loans
/// @param currency The currency to withdraw from the pool manager
/// @param to The address to withdraw to
/// @param amount The amount of currency to withdraw
function take(Currency currency, address to, uint256 amount) external;
/// @notice Called by the user to pay what is owed
/// @return paid The amount of currency settled
function settle() external payable returns (uint256 paid);
/// @notice Called by the user to pay on behalf of another address
/// @param recipient The address to credit for the payment
/// @return paid The amount of currency settled
function settleFor(address recipient) external payable returns (uint256 paid);
/// @notice WARNING - Any currency that is cleared, will be non-retrievable, and locked in the contract permanently.
/// A call to clear will zero out a positive balance WITHOUT a corresponding transfer.
/// @dev This could be used to clear a balance that is considered dust.
/// Additionally, the amount must be the exact positive balance. This is to enforce that the caller is aware of the amount being cleared.
function clear(Currency currency, uint256 amount) external;
/// @notice Called by the user to move value into ERC6909 balance
/// @param to The address to mint the tokens to
/// @param id The currency address to mint to ERC6909s, as a uint256
/// @param amount The amount of currency to mint
/// @dev The id is converted to a uint160 to correspond to a currency address
/// If the upper 12 bytes are not 0, they will be 0-ed out
function mint(address to, uint256 id, uint256 amount) external;
/// @notice Called by the user to move value from ERC6909 balance
/// @param from The address to burn the tokens from
/// @param id The currency address to burn from ERC6909s, as a uint256
/// @param amount The amount of currency to burn
/// @dev The id is converted to a uint160 to correspond to a currency address
/// If the upper 12 bytes are not 0, they will be 0-ed out
function burn(address from, uint256 id, uint256 amount) external;
/// @notice Updates the pools lp fees for the a pool that has enabled dynamic lp fees.
/// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
/// @param key The key of the pool to update dynamic LP fees for
/// @param newDynamicLPFee The new dynamic pool LP fee
function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/draft-IERC1822.sol)
pragma solidity >=0.4.16;
/**
* @dev ERC-1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
* proxy whose upgrades are fully controlled by the current implementation.
*/
interface IERC1822Proxiable {
/**
* @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
* address.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy.
*/
function proxiableUUID() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (proxy/ERC1967/ERC1967Utils.sol)
pragma solidity ^0.8.21;
import {IBeacon} from "../beacon/IBeacon.sol";
import {IERC1967} from "../../interfaces/IERC1967.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";
/**
* @dev This library provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots.
*/
library ERC1967Utils {
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev The `implementation` of the proxy is invalid.
*/
error ERC1967InvalidImplementation(address implementation);
/**
* @dev The `admin` of the proxy is invalid.
*/
error ERC1967InvalidAdmin(address admin);
/**
* @dev The `beacon` of the proxy is invalid.
*/
error ERC1967InvalidBeacon(address beacon);
/**
* @dev An upgrade function sees `msg.value > 0` that may be lost.
*/
error ERC1967NonPayable();
/**
* @dev Returns the current implementation address.
*/
function getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the ERC-1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(newImplementation);
}
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Performs implementation upgrade with additional setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) internal {
_setImplementation(newImplementation);
emit IERC1967.Upgraded(newImplementation);
if (data.length > 0) {
Address.functionDelegateCall(newImplementation, data);
} else {
_checkNonPayable();
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
* the https://ethereum.org/developers/docs/apis/json-rpc/#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the ERC-1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
if (newAdmin == address(0)) {
revert ERC1967InvalidAdmin(address(0));
}
StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {IERC1967-AdminChanged} event.
*/
function changeAdmin(address newAdmin) internal {
emit IERC1967.AdminChanged(getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the ERC-1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
if (newBeacon.code.length == 0) {
revert ERC1967InvalidBeacon(newBeacon);
}
StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
address beaconImplementation = IBeacon(newBeacon).implementation();
if (beaconImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(beaconImplementation);
}
}
/**
* @dev Change the beacon and trigger a setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-BeaconUpgraded} event.
*
* CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
* it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
* efficiency.
*/
function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
_setBeacon(newBeacon);
emit IERC1967.BeaconUpgraded(newBeacon);
if (data.length > 0) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
} else {
_checkNonPayable();
}
}
/**
* @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
* if an upgrade doesn't perform an initialization call.
*/
function _checkNonPayable() private {
if (msg.value > 0) {
revert ERC1967NonPayable();
}
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {Lock} from "../../libraries/LockLib.sol";
import {IRegistry, StakerVersion} from "../../Registry.sol";
import {IBaseStaker} from "./../../staker/BaseStaker.sol";
enum ATPType {
Linear,
Milestone,
NonClaim
}
interface IATPCore {
event StakerInitialized(IBaseStaker staker);
event StakerUpgraded(StakerVersion version);
event StakerOperatorUpdated(address operator);
event Claimed(uint256 amount);
event ApprovedStaker(uint256 allowance);
event Rescued(address asset, address to, uint256 amount);
event Revoked(uint256 amount);
error AlreadyInitialized();
error InvalidBeneficiary(address beneficiary);
error NotBeneficiary(address caller, address beneficiary);
error LockHasEnded();
error InvalidTokenAddress(address token);
error InvalidRegistry(address registry);
error AllocationMustBeGreaterThanZero();
error InvalidAsset(address asset);
error ExecutionNotAllowedYet(uint256 timestamp, uint256 executeAllowedAt);
error NotRevokable();
error NotRevoker(address caller, address revoker);
error NoClaimable();
error LockDurationMustBeGTZero(string variant);
error InvalidUpgrade();
function upgradeStaker(StakerVersion _version) external;
function approveStaker(uint256 _allowance) external;
function updateStakerOperator(address _operator) external;
function claim() external returns (uint256);
function rescueFunds(address _asset, address _to) external;
function revoke() external returns (uint256);
function getClaimable() external view returns (uint256);
function getGlobalLock() external view returns (Lock memory);
function getBeneficiary() external view returns (address);
function getOperator() external view returns (address);
}
interface IATPPeriphery {
function getToken() external view returns (IERC20);
function getRegistry() external view returns (IRegistry);
function getExecuteAllowedAt() external view returns (uint256);
function getClaimed() external view returns (uint256);
function getRevoker() external view returns (address);
function getIsRevokable() external view returns (bool);
function getAllocation() external view returns (uint256);
function getType() external view returns (ATPType);
function getStaker() external view returns (IBaseStaker);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
/**
* @title Data Structures Library
* @author Aztec Labs
* @notice Library that contains data structures used throughout the Aztec protocol
*/
library DataStructures {
// docs:start:l1_actor
/**
* @notice Actor on L1.
* @param actor - The address of the actor
* @param chainId - The chainId of the actor
*/
struct L1Actor {
address actor;
uint256 chainId;
}
// docs:end:l1_actor
// docs:start:l2_actor
/**
* @notice Actor on L2.
* @param actor - The aztec address of the actor
* @param version - Ahe Aztec instance the actor is on
*/
struct L2Actor {
bytes32 actor;
uint256 version;
}
// docs:end:l2_actor
// docs:start:l1_to_l2_msg
/**
* @notice Struct containing a message from L1 to L2
* @param sender - The sender of the message
* @param recipient - The recipient of the message
* @param content - The content of the message (application specific) padded to bytes32 or hashed if larger.
* @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on
* L2).
* @param index - Global leaf index on the L1 to L2 messages tree.
*/
struct L1ToL2Msg {
L1Actor sender;
L2Actor recipient;
bytes32 content;
bytes32 secretHash;
uint256 index;
}
// docs:end:l1_to_l2_msg
// docs:start:l2_to_l1_msg
/**
* @notice Struct containing a message from L2 to L1
* @param sender - The sender of the message
* @param recipient - The recipient of the message
* @param content - The content of the message (application specific) padded to bytes32 or hashed if larger.
* @dev Not to be confused with L2ToL1Message in Noir circuits
*/
struct L2ToL1Msg {
DataStructures.L2Actor sender;
DataStructures.L1Actor recipient;
bytes32 content;
}
// docs:end:l2_to_l1_msg
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {CompressedSlot} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
// We are using a type instead of a struct as we don't want to throw away a full 8 bits
// for the bool.
/*struct CompressedFeeHeader {
uint1 preHeat;
uint63 proverCost; Max value: 9.2233720369E18
uint64 congestionCost;
uint48 feeAssetPriceNumerator;
uint48 excessMana;
uint32 manaUsed;
}*/
type CompressedFeeHeader is uint256;
struct FeeHeader {
uint256 excessMana;
uint256 manaUsed;
uint256 feeAssetPriceNumerator;
uint256 congestionCost;
uint256 proverCost;
}
struct L1FeeData {
uint256 baseFee;
uint256 blobFee;
}
// We compress the L1 fee data heavily, capping out at `2**56-1` (7.2057594038E16)
// If the costs rose to this point an eth transfer (21000 gas) would be
// 21000 * 2**56-1 = 1.5132094748E21 wei / 1,513 eth in fees.
type CompressedL1FeeData is uint112;
// (56 + 56) * 2 + 32 = 256
struct L1GasOracleValues {
CompressedL1FeeData pre;
CompressedL1FeeData post;
CompressedSlot slotOfChange;
}
library FeeStructsLib {
using SafeCast for uint256;
uint256 internal constant MASK_56_BITS = 0xFFFFFFFFFFFFFF;
function getBlobFee(CompressedL1FeeData _compressedL1FeeData) internal pure returns (uint256) {
return CompressedL1FeeData.unwrap(_compressedL1FeeData) & MASK_56_BITS;
}
function getBaseFee(CompressedL1FeeData _compressedL1FeeData) internal pure returns (uint256) {
return (CompressedL1FeeData.unwrap(_compressedL1FeeData) >> 56) & MASK_56_BITS;
}
function compress(L1FeeData memory _data) internal pure returns (CompressedL1FeeData) {
uint256 value = 0;
value |= uint256(_data.blobFee.toUint56()) << 0;
value |= uint256(_data.baseFee.toUint56()) << 56;
return CompressedL1FeeData.wrap(value.toUint112());
}
function decompress(CompressedL1FeeData _data) internal pure returns (L1FeeData memory) {
uint256 value = CompressedL1FeeData.unwrap(_data);
uint256 blobFee = value & MASK_56_BITS;
uint256 baseFee = (value >> 56) & MASK_56_BITS;
return L1FeeData({baseFee: uint256(baseFee), blobFee: uint256(blobFee)});
}
}
library FeeHeaderLib {
using SafeCast for uint256;
uint256 internal constant MASK_32_BITS = 0xFFFFFFFF;
uint256 internal constant MASK_48_BITS = 0xFFFFFFFFFFFF;
uint256 internal constant MASK_63_BITS = 0x7FFFFFFFFFFFFFFF;
uint256 internal constant MASK_64_BITS = 0xFFFFFFFFFFFFFFFF;
function getManaUsed(CompressedFeeHeader _compressedFeeHeader) internal pure returns (uint256) {
return CompressedFeeHeader.unwrap(_compressedFeeHeader) & MASK_32_BITS;
}
function getExcessMana(CompressedFeeHeader _compressedFeeHeader) internal pure returns (uint256) {
return (CompressedFeeHeader.unwrap(_compressedFeeHeader) >> 32) & MASK_48_BITS;
}
function getFeeAssetPriceNumerator(CompressedFeeHeader _compressedFeeHeader) internal pure returns (uint256) {
return (CompressedFeeHeader.unwrap(_compressedFeeHeader) >> 80) & MASK_48_BITS;
}
function getCongestionCost(CompressedFeeHeader _compressedFeeHeader) internal pure returns (uint256) {
return (CompressedFeeHeader.unwrap(_compressedFeeHeader) >> 128) & MASK_64_BITS;
}
function getProverCost(CompressedFeeHeader _compressedFeeHeader) internal pure returns (uint256) {
// The prover cost is only 63 bits so use mask to remove first bit
return (CompressedFeeHeader.unwrap(_compressedFeeHeader) >> 192) & MASK_63_BITS;
}
function compress(FeeHeader memory _feeHeader) internal pure returns (CompressedFeeHeader) {
uint256 value = 0;
value |= uint256(_feeHeader.manaUsed.toUint32());
value |= uint256(_feeHeader.excessMana.toUint48()) << 32;
value |= uint256(_feeHeader.feeAssetPriceNumerator.toUint48()) << 80;
value |= uint256(_feeHeader.congestionCost.toUint64()) << 128;
uint256 proverCost = uint256(_feeHeader.proverCost.toUint64());
require(proverCost == proverCost & MASK_63_BITS);
value |= proverCost << 192;
// Preheat
value |= 1 << 255;
return CompressedFeeHeader.wrap(value);
}
function decompress(CompressedFeeHeader _compressedFeeHeader) internal pure returns (FeeHeader memory) {
uint256 value = CompressedFeeHeader.unwrap(_compressedFeeHeader);
uint256 manaUsed = value & MASK_32_BITS;
value >>= 32;
uint256 excessMana = value & MASK_48_BITS;
value >>= 48;
uint256 feeAssetPriceNumerator = value & MASK_48_BITS;
value >>= 48;
uint256 congestionCost = value & MASK_64_BITS;
value >>= 64;
uint256 proverCost = value & MASK_63_BITS;
return FeeHeader({
manaUsed: uint256(manaUsed),
excessMana: uint256(excessMana),
feeAssetPriceNumerator: uint256(feeAssetPriceNumerator),
congestionCost: uint256(congestionCost),
proverCost: uint256(proverCost)
});
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {Timestamp, Slot, Epoch} from "./TimeMath.sol";
type CompressedTimestamp is uint32;
type CompressedSlot is uint32;
type CompressedEpoch is uint32;
library CompressedTimeMath {
function compress(Timestamp _timestamp) internal pure returns (CompressedTimestamp) {
return CompressedTimestamp.wrap(SafeCast.toUint32(Timestamp.unwrap(_timestamp)));
}
function compress(Slot _slot) internal pure returns (CompressedSlot) {
return CompressedSlot.wrap(SafeCast.toUint32(Slot.unwrap(_slot)));
}
function compress(Epoch _epoch) internal pure returns (CompressedEpoch) {
return CompressedEpoch.wrap(SafeCast.toUint32(Epoch.unwrap(_epoch)));
}
function decompress(CompressedTimestamp _ts) internal pure returns (Timestamp) {
return Timestamp.wrap(uint256(CompressedTimestamp.unwrap(_ts)));
}
function decompress(CompressedSlot _slot) internal pure returns (Slot) {
return Slot.wrap(uint256(CompressedSlot.unwrap(_slot)));
}
function decompress(CompressedEpoch _epoch) internal pure returns (Epoch) {
return Epoch.wrap(uint256(CompressedEpoch.unwrap(_epoch)));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
pragma solidity ^0.8.20;
/**
* @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeCast {
/**
* @dev Value doesn't fit in a uint of `bits` size.
*/
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
/**
* @dev An int value doesn't fit in a uint of `bits` size.
*/
error SafeCastOverflowedIntToUint(int256 value);
/**
* @dev Value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
/**
* @dev A uint value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedUintToInt(uint256 value);
/**
* @dev Returns the downcasted uint248 from uint256, reverting on
* overflow (when the input is greater than largest uint248).
*
* Counterpart to Solidity's `uint248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*/
function toUint248(uint256 value) internal pure returns (uint248) {
if (value > type(uint248).max) {
revert SafeCastOverflowedUintDowncast(248, value);
}
return uint248(value);
}
/**
* @dev Returns the downcasted uint240 from uint256, reverting on
* overflow (when the input is greater than largest uint240).
*
* Counterpart to Solidity's `uint240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*/
function toUint240(uint256 value) internal pure returns (uint240) {
if (value > type(uint240).max) {
revert SafeCastOverflowedUintDowncast(240, value);
}
return uint240(value);
}
/**
* @dev Returns the downcasted uint232 from uint256, reverting on
* overflow (when the input is greater than largest uint232).
*
* Counterpart to Solidity's `uint232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*/
function toUint232(uint256 value) internal pure returns (uint232) {
if (value > type(uint232).max) {
revert SafeCastOverflowedUintDowncast(232, value);
}
return uint232(value);
}
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toUint224(uint256 value) internal pure returns (uint224) {
if (value > type(uint224).max) {
revert SafeCastOverflowedUintDowncast(224, value);
}
return uint224(value);
}
/**
* @dev Returns the downcasted uint216 from uint256, reverting on
* overflow (when the input is greater than largest uint216).
*
* Counterpart to Solidity's `uint216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*/
function toUint216(uint256 value) internal pure returns (uint216) {
if (value > type(uint216).max) {
revert SafeCastOverflowedUintDowncast(216, value);
}
return uint216(value);
}
/**
* @dev Returns the downcasted uint208 from uint256, reverting on
* overflow (when the input is greater than largest uint208).
*
* Counterpart to Solidity's `uint208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*/
function toUint208(uint256 value) internal pure returns (uint208) {
if (value > type(uint208).max) {
revert SafeCastOverflowedUintDowncast(208, value);
}
return uint208(value);
}
/**
* @dev Returns the downcasted uint200 from uint256, reverting on
* overflow (when the input is greater than largest uint200).
*
* Counterpart to Solidity's `uint200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*/
function toUint200(uint256 value) internal pure returns (uint200) {
if (value > type(uint200).max) {
revert SafeCastOverflowedUintDowncast(200, value);
}
return uint200(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toUint192(uint256 value) internal pure returns (uint192) {
if (value > type(uint192).max) {
revert SafeCastOverflowedUintDowncast(192, value);
}
return uint192(value);
}
/**
* @dev Returns the downcasted uint184 from uint256, reverting on
* overflow (when the input is greater than largest uint184).
*
* Counterpart to Solidity's `uint184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*/
function toUint184(uint256 value) internal pure returns (uint184) {
if (value > type(uint184).max) {
revert SafeCastOverflowedUintDowncast(184, value);
}
return uint184(value);
}
/**
* @dev Returns the downcasted uint176 from uint256, reverting on
* overflow (when the input is greater than largest uint176).
*
* Counterpart to Solidity's `uint176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*/
function toUint176(uint256 value) internal pure returns (uint176) {
if (value > type(uint176).max) {
revert SafeCastOverflowedUintDowncast(176, value);
}
return uint176(value);
}
/**
* @dev Returns the downcasted uint168 from uint256, reverting on
* overflow (when the input is greater than largest uint168).
*
* Counterpart to Solidity's `uint168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*/
function toUint168(uint256 value) internal pure returns (uint168) {
if (value > type(uint168).max) {
revert SafeCastOverflowedUintDowncast(168, value);
}
return uint168(value);
}
/**
* @dev Returns the downcasted uint160 from uint256, reverting on
* overflow (when the input is greater than largest uint160).
*
* Counterpart to Solidity's `uint160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*/
function toUint160(uint256 value) internal pure returns (uint160) {
if (value > type(uint160).max) {
revert SafeCastOverflowedUintDowncast(160, value);
}
return uint160(value);
}
/**
* @dev Returns the downcasted uint152 from uint256, reverting on
* overflow (when the input is greater than largest uint152).
*
* Counterpart to Solidity's `uint152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*/
function toUint152(uint256 value) internal pure returns (uint152) {
if (value > type(uint152).max) {
revert SafeCastOverflowedUintDowncast(152, value);
}
return uint152(value);
}
/**
* @dev Returns the downcasted uint144 from uint256, reverting on
* overflow (when the input is greater than largest uint144).
*
* Counterpart to Solidity's `uint144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*/
function toUint144(uint256 value) internal pure returns (uint144) {
if (value > type(uint144).max) {
revert SafeCastOverflowedUintDowncast(144, value);
}
return uint144(value);
}
/**
* @dev Returns the downcasted uint136 from uint256, reverting on
* overflow (when the input is greater than largest uint136).
*
* Counterpart to Solidity's `uint136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*/
function toUint136(uint256 value) internal pure returns (uint136) {
if (value > type(uint136).max) {
revert SafeCastOverflowedUintDowncast(136, value);
}
return uint136(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
if (value > type(uint128).max) {
revert SafeCastOverflowedUintDowncast(128, value);
}
return uint128(value);
}
/**
* @dev Returns the downcasted uint120 from uint256, reverting on
* overflow (when the input is greater than largest uint120).
*
* Counterpart to Solidity's `uint120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*/
function toUint120(uint256 value) internal pure returns (uint120) {
if (value > type(uint120).max) {
revert SafeCastOverflowedUintDowncast(120, value);
}
return uint120(value);
}
/**
* @dev Returns the downcasted uint112 from uint256, reverting on
* overflow (when the input is greater than largest uint112).
*
* Counterpart to Solidity's `uint112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*/
function toUint112(uint256 value) internal pure returns (uint112) {
if (value > type(uint112).max) {
revert SafeCastOverflowedUintDowncast(112, value);
}
return uint112(value);
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*/
function toUint104(uint256 value) internal pure returns (uint104) {
if (value > type(uint104).max) {
revert SafeCastOverflowedUintDowncast(104, value);
}
return uint104(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
if (value > type(uint96).max) {
revert SafeCastOverflowedUintDowncast(96, value);
}
return uint96(value);
}
/**
* @dev Returns the downcasted uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toUint88(uint256 value) internal pure returns (uint88) {
if (value > type(uint88).max) {
revert SafeCastOverflowedUintDowncast(88, value);
}
return uint88(value);
}
/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toUint80(uint256 value) internal pure returns (uint80) {
if (value > type(uint80).max) {
revert SafeCastOverflowedUintDowncast(80, value);
}
return uint80(value);
}
/**
* @dev Returns the downcasted uint72 from uint256, reverting on
* overflow (when the input is greater than largest uint72).
*
* Counterpart to Solidity's `uint72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*/
function toUint72(uint256 value) internal pure returns (uint72) {
if (value > type(uint72).max) {
revert SafeCastOverflowedUintDowncast(72, value);
}
return uint72(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
if (value > type(uint64).max) {
revert SafeCastOverflowedUintDowncast(64, value);
}
return uint64(value);
}
/**
* @dev Returns the downcasted uint56 from uint256, reverting on
* overflow (when the input is greater than largest uint56).
*
* Counterpart to Solidity's `uint56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*/
function toUint56(uint256 value) internal pure returns (uint56) {
if (value > type(uint56).max) {
revert SafeCastOverflowedUintDowncast(56, value);
}
return uint56(value);
}
/**
* @dev Returns the downcasted uint48 from uint256, reverting on
* overflow (when the input is greater than largest uint48).
*
* Counterpart to Solidity's `uint48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*/
function toUint48(uint256 value) internal pure returns (uint48) {
if (value > type(uint48).max) {
revert SafeCastOverflowedUintDowncast(48, value);
}
return uint48(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toUint40(uint256 value) internal pure returns (uint40) {
if (value > type(uint40).max) {
revert SafeCastOverflowedUintDowncast(40, value);
}
return uint40(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
if (value > type(uint32).max) {
revert SafeCastOverflowedUintDowncast(32, value);
}
return uint32(value);
}
/**
* @dev Returns the downcasted uint24 from uint256, reverting on
* overflow (when the input is greater than largest uint24).
*
* Counterpart to Solidity's `uint24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*/
function toUint24(uint256 value) internal pure returns (uint24) {
if (value > type(uint24).max) {
revert SafeCastOverflowedUintDowncast(24, value);
}
return uint24(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
if (value > type(uint16).max) {
revert SafeCastOverflowedUintDowncast(16, value);
}
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toUint8(uint256 value) internal pure returns (uint8) {
if (value > type(uint8).max) {
revert SafeCastOverflowedUintDowncast(8, value);
}
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
if (value < 0) {
revert SafeCastOverflowedIntToUint(value);
}
return uint256(value);
}
/**
* @dev Returns the downcasted int248 from int256, reverting on
* overflow (when the input is less than smallest int248 or
* greater than largest int248).
*
* Counterpart to Solidity's `int248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*/
function toInt248(int256 value) internal pure returns (int248 downcasted) {
downcasted = int248(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(248, value);
}
}
/**
* @dev Returns the downcasted int240 from int256, reverting on
* overflow (when the input is less than smallest int240 or
* greater than largest int240).
*
* Counterpart to Solidity's `int240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*/
function toInt240(int256 value) internal pure returns (int240 downcasted) {
downcasted = int240(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(240, value);
}
}
/**
* @dev Returns the downcasted int232 from int256, reverting on
* overflow (when the input is less than smallest int232 or
* greater than largest int232).
*
* Counterpart to Solidity's `int232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*/
function toInt232(int256 value) internal pure returns (int232 downcasted) {
downcasted = int232(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(232, value);
}
}
/**
* @dev Returns the downcasted int224 from int256, reverting on
* overflow (when the input is less than smallest int224 or
* greater than largest int224).
*
* Counterpart to Solidity's `int224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toInt224(int256 value) internal pure returns (int224 downcasted) {
downcasted = int224(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(224, value);
}
}
/**
* @dev Returns the downcasted int216 from int256, reverting on
* overflow (when the input is less than smallest int216 or
* greater than largest int216).
*
* Counterpart to Solidity's `int216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*/
function toInt216(int256 value) internal pure returns (int216 downcasted) {
downcasted = int216(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(216, value);
}
}
/**
* @dev Returns the downcasted int208 from int256, reverting on
* overflow (when the input is less than smallest int208 or
* greater than largest int208).
*
* Counterpart to Solidity's `int208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*/
function toInt208(int256 value) internal pure returns (int208 downcasted) {
downcasted = int208(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(208, value);
}
}
/**
* @dev Returns the downcasted int200 from int256, reverting on
* overflow (when the input is less than smallest int200 or
* greater than largest int200).
*
* Counterpart to Solidity's `int200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*/
function toInt200(int256 value) internal pure returns (int200 downcasted) {
downcasted = int200(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(200, value);
}
}
/**
* @dev Returns the downcasted int192 from int256, reverting on
* overflow (when the input is less than smallest int192 or
* greater than largest int192).
*
* Counterpart to Solidity's `int192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toInt192(int256 value) internal pure returns (int192 downcasted) {
downcasted = int192(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(192, value);
}
}
/**
* @dev Returns the downcasted int184 from int256, reverting on
* overflow (when the input is less than smallest int184 or
* greater than largest int184).
*
* Counterpart to Solidity's `int184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*/
function toInt184(int256 value) internal pure returns (int184 downcasted) {
downcasted = int184(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(184, value);
}
}
/**
* @dev Returns the downcasted int176 from int256, reverting on
* overflow (when the input is less than smallest int176 or
* greater than largest int176).
*
* Counterpart to Solidity's `int176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*/
function toInt176(int256 value) internal pure returns (int176 downcasted) {
downcasted = int176(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(176, value);
}
}
/**
* @dev Returns the downcasted int168 from int256, reverting on
* overflow (when the input is less than smallest int168 or
* greater than largest int168).
*
* Counterpart to Solidity's `int168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*/
function toInt168(int256 value) internal pure returns (int168 downcasted) {
downcasted = int168(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(168, value);
}
}
/**
* @dev Returns the downcasted int160 from int256, reverting on
* overflow (when the input is less than smallest int160 or
* greater than largest int160).
*
* Counterpart to Solidity's `int160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*/
function toInt160(int256 value) internal pure returns (int160 downcasted) {
downcasted = int160(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(160, value);
}
}
/**
* @dev Returns the downcasted int152 from int256, reverting on
* overflow (when the input is less than smallest int152 or
* greater than largest int152).
*
* Counterpart to Solidity's `int152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*/
function toInt152(int256 value) internal pure returns (int152 downcasted) {
downcasted = int152(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(152, value);
}
}
/**
* @dev Returns the downcasted int144 from int256, reverting on
* overflow (when the input is less than smallest int144 or
* greater than largest int144).
*
* Counterpart to Solidity's `int144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*/
function toInt144(int256 value) internal pure returns (int144 downcasted) {
downcasted = int144(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(144, value);
}
}
/**
* @dev Returns the downcasted int136 from int256, reverting on
* overflow (when the input is less than smallest int136 or
* greater than largest int136).
*
* Counterpart to Solidity's `int136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*/
function toInt136(int256 value) internal pure returns (int136 downcasted) {
downcasted = int136(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(136, value);
}
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toInt128(int256 value) internal pure returns (int128 downcasted) {
downcasted = int128(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(128, value);
}
}
/**
* @dev Returns the downcasted int120 from int256, reverting on
* overflow (when the input is less than smallest int120 or
* greater than largest int120).
*
* Counterpart to Solidity's `int120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*/
function toInt120(int256 value) internal pure returns (int120 downcasted) {
downcasted = int120(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(120, value);
}
}
/**
* @dev Returns the downcasted int112 from int256, reverting on
* overflow (when the input is less than smallest int112 or
* greater than largest int112).
*
* Counterpart to Solidity's `int112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*/
function toInt112(int256 value) internal pure returns (int112 downcasted) {
downcasted = int112(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(112, value);
}
}
/**
* @dev Returns the downcasted int104 from int256, reverting on
* overflow (when the input is less than smallest int104 or
* greater than largest int104).
*
* Counterpart to Solidity's `int104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*/
function toInt104(int256 value) internal pure returns (int104 downcasted) {
downcasted = int104(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(104, value);
}
}
/**
* @dev Returns the downcasted int96 from int256, reverting on
* overflow (when the input is less than smallest int96 or
* greater than largest int96).
*
* Counterpart to Solidity's `int96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toInt96(int256 value) internal pure returns (int96 downcasted) {
downcasted = int96(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(96, value);
}
}
/**
* @dev Returns the downcasted int88 from int256, reverting on
* overflow (when the input is less than smallest int88 or
* greater than largest int88).
*
* Counterpart to Solidity's `int88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toInt88(int256 value) internal pure returns (int88 downcasted) {
downcasted = int88(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(88, value);
}
}
/**
* @dev Returns the downcasted int80 from int256, reverting on
* overflow (when the input is less than smallest int80 or
* greater than largest int80).
*
* Counterpart to Solidity's `int80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toInt80(int256 value) internal pure returns (int80 downcasted) {
downcasted = int80(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(80, value);
}
}
/**
* @dev Returns the downcasted int72 from int256, reverting on
* overflow (when the input is less than smallest int72 or
* greater than largest int72).
*
* Counterpart to Solidity's `int72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*/
function toInt72(int256 value) internal pure returns (int72 downcasted) {
downcasted = int72(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(72, value);
}
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toInt64(int256 value) internal pure returns (int64 downcasted) {
downcasted = int64(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(64, value);
}
}
/**
* @dev Returns the downcasted int56 from int256, reverting on
* overflow (when the input is less than smallest int56 or
* greater than largest int56).
*
* Counterpart to Solidity's `int56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*/
function toInt56(int256 value) internal pure returns (int56 downcasted) {
downcasted = int56(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(56, value);
}
}
/**
* @dev Returns the downcasted int48 from int256, reverting on
* overflow (when the input is less than smallest int48 or
* greater than largest int48).
*
* Counterpart to Solidity's `int48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*/
function toInt48(int256 value) internal pure returns (int48 downcasted) {
downcasted = int48(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(48, value);
}
}
/**
* @dev Returns the downcasted int40 from int256, reverting on
* overflow (when the input is less than smallest int40 or
* greater than largest int40).
*
* Counterpart to Solidity's `int40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toInt40(int256 value) internal pure returns (int40 downcasted) {
downcasted = int40(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(40, value);
}
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toInt32(int256 value) internal pure returns (int32 downcasted) {
downcasted = int32(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(32, value);
}
}
/**
* @dev Returns the downcasted int24 from int256, reverting on
* overflow (when the input is less than smallest int24 or
* greater than largest int24).
*
* Counterpart to Solidity's `int24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*/
function toInt24(int256 value) internal pure returns (int24 downcasted) {
downcasted = int24(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(24, value);
}
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toInt16(int256 value) internal pure returns (int16 downcasted) {
downcasted = int16(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(16, value);
}
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toInt8(int256 value) internal pure returns (int8 downcasted) {
downcasted = int8(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(8, value);
}
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
if (value > uint256(type(int256).max)) {
revert SafeCastOverflowedUintToInt(value);
}
return int256(value);
}
/**
* @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
*/
function toUint(bool b) internal pure returns (uint256 u) {
assembly ("memory-safe") {
u := iszero(iszero(b))
}
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {SlashRound} from "@aztec/core/libraries/SlashRoundLib.sol";
import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol";
/**
* @title Errors Library
* @author Aztec Labs
* @notice Library that contains errors used throughout the Aztec protocol
* Errors are prefixed with the contract name to make it easy to identify where the error originated
* when there are multiple contracts that could have thrown the error.
*
* Sigs are provided for easy reference, but don't trust; verify! run `forge inspect
* src/core/libraries/Errors.sol:Errors errors`
*/
library Errors {
// DEVNET related
error DevNet__NoPruningAllowed(); // 0x6984c590
error DevNet__InvalidProposer(address expected, address actual); // 0x11e6e6f7
// Inbox
error Inbox__Unauthorized(); // 0xe5336a6b
error Inbox__ActorTooLarge(bytes32 actor); // 0xa776a06e
error Inbox__VersionMismatch(uint256 expected, uint256 actual); // 0x47452014
error Inbox__ContentTooLarge(bytes32 content); // 0x47452014
error Inbox__SecretHashTooLarge(bytes32 secretHash); // 0xecde7e2c
error Inbox__MustBuildBeforeConsume(); // 0xc4901999
error Inbox__Ignition();
// Outbox
error Outbox__Unauthorized(); // 0x2c9490c2
error Outbox__InvalidChainId(); // 0x577ec7c4
error Outbox__VersionMismatch(uint256 expected, uint256 actual);
error Outbox__NothingToConsume(bytes32 messageHash); // 0xfb4fb506
error Outbox__IncompatibleEntryArguments(
bytes32 messageHash,
uint64 storedFee,
uint64 feePassed,
uint32 storedVersion,
uint32 versionPassed,
uint32 storedDeadline,
uint32 deadlinePassed
); // 0x5e789f34
error Outbox__RootAlreadySetAtBlock(uint256 l2BlockNumber); // 0x3eccfd3e
error Outbox__InvalidRecipient(address expected, address actual); // 0x57aad581
error Outbox__AlreadyNullified(uint256 l2BlockNumber, uint256 leafIndex); // 0xfd71c2d4
error Outbox__NothingToConsumeAtBlock(uint256 l2BlockNumber); // 0xa4508f22
error Outbox__BlockNotProven(uint256 l2BlockNumber); // 0x0e194a6d
error Outbox__BlockAlreadyProven(uint256 l2BlockNumber);
error Outbox__PathTooLong();
error Outbox__LeafIndexOutOfBounds(uint256 leafIndex, uint256 pathLength);
// Rollup
error Rollup__InsufficientBondAmount(uint256 minimum, uint256 provided); // 0xa165f276
error Rollup__InsufficientFundsInEscrow(uint256 required, uint256 available); // 0xa165f276
error Rollup__InvalidArchive(bytes32 expected, bytes32 actual); // 0xb682a40e
error Rollup__InvalidBlockNumber(uint256 expected, uint256 actual); // 0xe5edf847
error Rollup__InvalidInHash(bytes32 expected, bytes32 actual); // 0xcd6f4233
error Rollup__InvalidPreviousArchive(bytes32 expected, bytes32 actual); // 0xb682a40e
error Rollup__InvalidProof(); // 0xa5b2ba17
error Rollup__InvalidProposedArchive(bytes32 expected, bytes32 actual); // 0x32532e73
error Rollup__InvalidTimestamp(Timestamp expected, Timestamp actual); // 0x3132e895
error Rollup__InvalidAttestations();
error Rollup__AttestationsAreValid();
error Rollup__InvalidAttestationIndex();
error Rollup__BlockAlreadyProven();
error Rollup__BlockNotInPendingChain();
error Rollup__InvalidBlobHash(bytes32 expected, bytes32 actual); // 0x13031e6a
error Rollup__InvalidBlobProof(bytes32 blobHash); // 0x5ca17bef
error Rollup__NoEpochToProve(); // 0xcbaa3951
error Rollup__NonSequentialProving(); // 0x1e5be132
error Rollup__NothingToPrune(); // 0x850defd3
error Rollup__SlotAlreadyInChain(Slot lastSlot, Slot proposedSlot); // 0x83510bd0
error Rollup__TimestampInFuture(Timestamp max, Timestamp actual); // 0x89f30690
error Rollup__TimestampTooOld(); // 0x72ed9c81
error Rollup__TryingToProveNonExistingBlock(); // 0x34ef4954
error Rollup__UnavailableTxs(bytes32 txsHash); // 0x414906c3
error Rollup__NonZeroDaFee(); // 0xd9c75f52
error Rollup__InvalidBasisPointFee(uint256 basisPointFee); // 0x4292d136
error Rollup__InvalidManaBaseFee(uint256 expected, uint256 actual); // 0x73b6d896
error Rollup__StartAndEndNotSameEpoch(Epoch start, Epoch end); // 0xb64ec33e
error Rollup__StartIsNotFirstBlockOfEpoch(); // 0x4ef11e0d
error Rollup__StartIsNotBuildingOnProven(); // 0x4a59f42e
error Rollup__TooManyBlocksInEpoch(uint256 expected, uint256 actual); // 0x7d5b1408
error Rollup__NotPastDeadline(Epoch deadline, Epoch currentEpoch);
error Rollup__PastDeadline(Epoch deadline, Epoch currentEpoch);
error Rollup__ProverHaveAlreadySubmitted(address prover, Epoch epoch);
error Rollup__InvalidManaTarget(uint256 minimum, uint256 provided);
error Rollup__ManaLimitExceeded();
error Rollup__RewardsNotClaimable();
error Rollup__TooSoonToSetRewardsClaimable(uint256 earliestRewardsClaimableTimestamp, uint256 currentTimestamp);
error Rollup__InvalidFirstEpochProof();
error Rollup__InvalidCoinbase();
error Rollup__UnavailableTempBlockLog(uint256 blockNumber, uint256 pendingBlockNumber, uint256 upperLimit);
error Rollup__NoBlobsInBlock();
// ProposedHeaderLib
error HeaderLib__InvalidHeaderSize(uint256 expected, uint256 actual); // 0xf3ccb247
error HeaderLib__InvalidSlotNumber(Slot expected, Slot actual); // 0x09ba91ff
// MerkleLib
error MerkleLib__InvalidRoot(bytes32 expected, bytes32 actual, bytes32 leaf, uint256 leafIndex); // 0x5f216bf1
error MerkleLib__InvalidIndexForPathLength();
// SampleLib
error SampleLib__IndexOutOfBounds(uint256 requested, uint256 bound); // 0xa12fc559
error SampleLib__SampleLargerThanIndex(uint256 sample, uint256 index); // 0xa11b0f79
// Sequencer Selection (ValidatorSelection)
error ValidatorSelection__EpochNotSetup(); // 0x10816cae
error ValidatorSelection__InvalidProposer(address expected, address actual); // 0xa8843a68
error ValidatorSelection__MissingProposerSignature(address proposer, uint256 index);
error ValidatorSelection__InvalidDeposit(address attester, address proposer); // 0x533169bd
error ValidatorSelection__InsufficientAttestations(uint256 minimumNeeded, uint256 provided); // 0xaf47297f
error ValidatorSelection__InvalidCommitteeCommitment(bytes32 reconstructed, bytes32 expected); // 0xca8d5954
error ValidatorSelection__InsufficientValidatorSetSize(uint256 actual, uint256 expected); // 0xf4f28e99
error ValidatorSelection__ProposerIndexTooLarge(uint256 index);
// Staking
error Staking__AlreadyQueued(address _attester);
error Staking__QueueEmpty();
error Staking__DepositOutOfGas();
error Staking__AlreadyActive(address attester); // 0x5e206fa4
error Staking__QueueAlreadyFlushed(Epoch epoch); // 0x21148c78
error Staking__AlreadyRegistered(address instance, address attester);
error Staking__CannotSlashExitedStake(address); // 0x45bf4940
error Staking__FailedToRemove(address); // 0xa7d7baab
error Staking__InvalidDeposit(address attester, address proposer); // 0xf33fe8c6
error Staking__InvalidRecipient(address); // 0x7e2f7f1c
error Staking__InsufficientStake(uint256, uint256); // 0x903aee24
error Staking__NoOneToSlash(address); // 0x7e2f7f1c
error Staking__NotExiting(address); // 0xef566ee0
error Staking__InitiateWithdrawNeeded(address);
error Staking__NotSlasher(address, address); // 0x23a6f432
error Staking__NotWithdrawer(address, address); // 0x8e668e5d
error Staking__NothingToExit(address); // 0xd2aac9b6
error Staking__WithdrawalNotUnlockedYet(Timestamp, Timestamp); // 0x88e1826c
error Staking__WithdrawFailed(address); // 0x377422c1
error Staking__OutOfBounds(uint256, uint256); // 0x4bea6597
error Staking__NotRollup(address); // 0xf5509eb3
error Staking__RollupAlreadyRegistered(address); // 0x108a39c8
error Staking__InvalidRollupAddress(address); // 0xd876720e
error Staking__NotCanonical(address); // 0x6244212e
error Staking__InstanceDoesNotExist(address);
error Staking__InsufficientPower(uint256, uint256);
error Staking__AlreadyExiting(address);
error Staking__FatalError(string);
error Staking__NotOurProposal(uint256, address, address);
error Staking__IncorrectGovProposer(uint256);
error Staking__GovernanceAlreadySet();
error Staking__InsufficientBootstrapValidators(uint256 queueSize, uint256 bootstrapFlushSize);
error Staking__InvalidStakingQueueConfig();
error Staking__InvalidNormalFlushSizeQuotient();
// Fee Juice Portal
error FeeJuicePortal__AlreadyInitialized(); // 0xc7a172fe
error FeeJuicePortal__InvalidInitialization(); // 0xfd9b3208
error FeeJuicePortal__Unauthorized(); // 0x67e3691e
// Proof Commitment Escrow
error ProofCommitmentEscrow__InsufficientBalance(uint256 balance, uint256 requested); // 0x09b8b789
error ProofCommitmentEscrow__NotOwner(address caller); // 0x2ac332c1
error ProofCommitmentEscrow__WithdrawRequestNotReady(uint256 current, Timestamp readyAt); // 0xb32ab8a7
// FeeLib
error FeeLib__InvalidFeeAssetPriceModifier(); // 0xf2fb32ad
error FeeLib__AlreadyPreheated();
// SignatureLib (duplicated)
error SignatureLib__InvalidSignature(address, address); // 0xd9cbae6c
error AttestationLib__InvalidDataSize(uint256, uint256);
error AttestationLib__SignatureIndicesSizeMismatch(uint256, uint256);
error AttestationLib__SignaturesOrAddressesSizeMismatch(uint256, uint256);
error AttestationLib__SignersSizeMismatch(uint256, uint256);
error AttestationLib__NotASignatureAtIndex(uint256 index);
error AttestationLib__NotAnAddressAtIndex(uint256 index);
// RewardBooster
error RewardBooster__OnlyRollup(address caller);
error RewardLib__InvalidSequencerBps();
// TallySlashingProposer
error TallySlashingProposer__InvalidSignature();
error TallySlashingProposer__InvalidVoteLength(uint256 expected, uint256 actual);
error TallySlashingProposer__RoundAlreadyExecuted(SlashRound round);
error TallySlashingProposer__InvalidNumberOfCommittees(uint256 expected, uint256 actual);
error TallySlashingProposer__RoundNotComplete(SlashRound round);
error TallySlashingProposer__InvalidCommitteeSize(uint256 expected, uint256 actual);
error TallySlashingProposer__InvalidCommitteeCommitment();
error TallySlashingProposer__InvalidQuorumAndRoundSize(uint256 quorum, uint256 roundSize);
error TallySlashingProposer__QuorumMustBeGreaterThanZero();
error TallySlashingProposer__InvalidSlashAmounts(uint256[3] slashAmounts);
error TallySlashingProposer__LifetimeMustBeGreaterThanExecutionDelay(uint256 lifetime, uint256 executionDelay);
error TallySlashingProposer__LifetimeMustBeLessThanRoundabout(uint256 lifetime, uint256 roundabout);
error TallySlashingProposer__RoundSizeInEpochsMustBeGreaterThanZero(uint256 roundSizeInEpochs);
error TallySlashingProposer__RoundSizeTooLarge(uint256 roundSize, uint256 maxRoundSize);
error TallySlashingProposer__CommitteeSizeMustBeGreaterThanZero(uint256 committeeSize);
error TallySlashingProposer__SlashAmountTooLarge();
error TallySlashingProposer__VoteAlreadyCastInCurrentSlot(Slot slot);
error TallySlashingProposer__RoundOutOfRange(SlashRound round, SlashRound currentRound);
error TallySlashingProposer__RoundSizeMustBeMultipleOfEpochDuration(uint256 roundSize, uint256 epochDuration);
error TallySlashingProposer__VotingNotOpen(SlashRound currentRound);
error TallySlashingProposer__SlashOffsetMustBeGreaterThanZero(uint256 slashOffset);
error TallySlashingProposer__InvalidEpochIndex(uint256 epochIndex, uint256 roundSizeInEpochs);
error TallySlashingProposer__VoteSizeTooBig(uint256 voteSize, uint256 maxSize);
error TallySlashingProposer__VotesMustBeMultipleOf4(uint256 votes);
// SlashPayloadLib
error SlashPayload_ArraySizeMismatch(uint256 expected, uint256 actual);
// OpenZeppelin dependencies
// ECDSA
error ECDSAInvalidSignature();
error ECDSAInvalidSignatureLength(uint256 length);
error ECDSAInvalidSignatureS(bytes32 s);
// Ownable
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
// Checkpoints
error CheckpointUnorderedInsertion();
// ERC20
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
error ERC20InvalidSender(address sender);
error ERC20InvalidReceiver(address receiver);
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
error ERC20InvalidApprover(address approver);
error ERC20InvalidSpender(address spender);
// SafeCast
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
error SafeCastOverflowedIntToUint(int256 value);
error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
error SafeCastOverflowedUintToInt(uint256 value);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {BlobLib as CoreBlobLib} from "@aztec/core/libraries/rollup/BlobLib.sol";
import {Vm} from "forge-std/Vm.sol";
/**
* @title BlobLib - Blob Management and Validation Library
* @author Aztec Labs
* @notice Core library for handling blob operations, validation, and commitment management in the Aztec rollup.
*
* @dev This library provides functionality for managing blobs:
* - Blob hash retrieval and validation against EIP-4844 specifications
* - Blob commitment verification and batched blob proof validation
* - Blob base fee retrieval for transaction cost calculations
* - Accumulated blob commitments hash calculation for epoch proofs
*
* VM_ADDRESS:
* The VM_ADDRESS (0x7109709ECfa91a80626fF3989D68f67F5b1DD12D) is a special address used to detect
* when the contract is running in a Foundry test environment. This address is derived from
* keccak256("hevm cheat code") and corresponds to Foundry's VM contract that provides testing utilities.
* When VM_ADDRESS.code.length > 0, it indicates we' dre in a test environment, allowing the library to:
* - Use Foundry's getBlobBaseFee() cheatcode instead of block.blobbasefee
* - Use Foundry's getBlobhashes() cheatcode instead of the blobhash() opcode
* This enables comprehensive testing of blob functionality without requiring actual blob transactions.
*
* Blob Validation Flow:
* 1. validateBlobs() processes L2 block blob data, extracting commitments and validating against real blobs
* 2. calculateBlobCommitmentsHash() accumulates commitments across an epoch for rollup circuit validation
* 3. validateBatchedBlob() verifies batched blob proofs using the EIP-4844 point evaluation precompile
* 4. calculateBlobHash() computes versioned hashes from commitments following EIP-4844 specification
*/
library BlobLib {
address public constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code"))));
uint256 internal constant VERSIONED_HASH_VERSION_KZG =
0x0100000000000000000000000000000000000000000000000000000000000000; // 0x01 << 248 to be used in blobHashCheck
/**
* @notice Get the blob base fee
*
* @dev If we are in a foundry test, we use the cheatcode to get the blob base fee.
* Otherwise, we use the `block.blobbasefee`
*
* @return uint256 - The blob base fee
*/
function getBlobBaseFee() internal view returns (uint256) {
if (VM_ADDRESS.code.length > 0) {
return Vm(VM_ADDRESS).getBlobBaseFee();
}
return CoreBlobLib.getBlobBaseFee();
}
/**
* @notice Get the blob hash
*
* @dev If we are in a foundry test, we use the cheatcode to get the blob hashes
* Otherwise, we use the `blobhash` function in assembly
*
* @return blobHash - The blob hash
*/
function getBlobHash(uint256 _index) internal view returns (bytes32 blobHash) {
if (VM_ADDRESS.code.length > 0) {
// We know that this one is ABHORRENT. But it should not exists, and only will
// be hit in testing.
bytes32[] memory blobHashes = Vm(VM_ADDRESS).getBlobhashes();
if (_index < blobHashes.length) {
return blobHashes[_index];
}
return bytes32(0);
}
return CoreBlobLib.getBlobHash(_index);
}
function validateBlobs(bytes calldata _blobsInput, bool _checkBlob)
internal
view
returns (bytes32[] memory blobHashes, bytes32 blobsHashesCommitment, bytes[] memory blobCommitments)
{
return CoreBlobLib.validateBlobs(_blobsInput, _checkBlob);
}
function validateBatchedBlob(bytes calldata _blobInput) internal view returns (bool success) {
return CoreBlobLib.validateBatchedBlob(_blobInput);
}
function calculateBlobCommitmentsHash(
bytes32 _previousBlobCommitmentsHash,
bytes[] memory _blobCommitments,
bool _isFirstBlockOfEpoch
) internal pure returns (bytes32 currentBlobCommitmentsHash) {
return
CoreBlobLib.calculateBlobCommitmentsHash(_previousBlobCommitmentsHash, _blobCommitments, _isFirstBlockOfEpoch);
}
function calculateBlobHash(bytes memory _blobCommitment) internal pure returns (bytes32) {
return CoreBlobLib.calculateBlobHash(_blobCommitment);
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {Math} from "@oz/utils/math/Math.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
type EthValue is uint256;
type FeeAssetValue is uint256;
// Precision of 1e9
type FeeAssetPerEthE9 is uint256;
function addEthValue(EthValue _a, EthValue _b) pure returns (EthValue) {
return EthValue.wrap(EthValue.unwrap(_a) + EthValue.unwrap(_b));
}
function subEthValue(EthValue _a, EthValue _b) pure returns (EthValue) {
return EthValue.wrap(EthValue.unwrap(_a) - EthValue.unwrap(_b));
}
using {addEthValue as +, subEthValue as -} for EthValue global;
// 64 bit manaTarget, 128 bit congestionUpdateFraction, 64 bit provingCostPerMana
type CompressedFeeConfig is uint256;
struct FeeConfig {
uint256 manaTarget;
uint256 congestionUpdateFraction;
EthValue provingCostPerMana;
}
library PriceLib {
function toEth(FeeAssetValue _feeAssetValue, FeeAssetPerEthE9 _feeAssetPerEth) internal pure returns (EthValue) {
return EthValue.wrap(
Math.mulDiv(
FeeAssetValue.unwrap(_feeAssetValue), 1e9, FeeAssetPerEthE9.unwrap(_feeAssetPerEth), Math.Rounding.Ceil
)
);
}
function toFeeAsset(EthValue _ethValue, FeeAssetPerEthE9 _feeAssetPerEth) internal pure returns (FeeAssetValue) {
return FeeAssetValue.wrap(
Math.mulDiv(EthValue.unwrap(_ethValue), FeeAssetPerEthE9.unwrap(_feeAssetPerEth), 1e9, Math.Rounding.Ceil)
);
}
}
library FeeConfigLib {
using SafeCast for uint256;
uint256 private constant MASK_64_BITS = 0xFFFFFFFFFFFFFFFF;
uint256 private constant MASK_128_BITS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
function getManaTarget(CompressedFeeConfig _compressedFeeConfig) internal pure returns (uint256) {
return (CompressedFeeConfig.unwrap(_compressedFeeConfig) >> 192) & MASK_64_BITS;
}
function getCongestionUpdateFraction(CompressedFeeConfig _compressedFeeConfig) internal pure returns (uint256) {
return (CompressedFeeConfig.unwrap(_compressedFeeConfig) >> 64) & MASK_128_BITS;
}
function getProvingCostPerMana(CompressedFeeConfig _compressedFeeConfig) internal pure returns (EthValue) {
return EthValue.wrap(CompressedFeeConfig.unwrap(_compressedFeeConfig) & MASK_64_BITS);
}
function compress(FeeConfig memory _config) internal pure returns (CompressedFeeConfig) {
uint256 value = 0;
value |= uint256(EthValue.unwrap(_config.provingCostPerMana).toUint64());
value |= uint256(_config.congestionUpdateFraction.toUint128()) << 64;
value |= uint256(_config.manaTarget.toUint64()) << 192;
return CompressedFeeConfig.wrap(value);
}
function decompress(CompressedFeeConfig _compressedFeeConfig) internal pure returns (FeeConfig memory) {
return FeeConfig({
provingCostPerMana: getProvingCostPerMana(_compressedFeeConfig),
congestionUpdateFraction: getCongestionUpdateFraction(_compressedFeeConfig),
manaTarget: getManaTarget(_compressedFeeConfig)
});
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Return the 512-bit addition of two uint256.
*
* The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
*/
function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
assembly ("memory-safe") {
low := add(a, b)
high := lt(low, a)
}
}
/**
* @dev Return the 512-bit multiplication of two uint256.
*
* The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
*/
function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
// 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
// the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = high * 2²⁵⁶ + low.
assembly ("memory-safe") {
let mm := mulmod(a, b, not(0))
low := mul(a, b)
high := sub(sub(mm, low), lt(mm, low))
}
}
/**
* @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a + b;
success = c >= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a - b;
success = c <= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a * b;
assembly ("memory-safe") {
// Only true when the multiplication doesn't overflow
// (c / a == b) || (a == 0)
success := or(eq(div(c, a), b), iszero(a))
}
// equivalent to: success ? c : 0
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `DIV` opcode returns zero when the denominator is 0.
result := div(a, b)
}
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `MOD` opcode returns zero when the denominator is 0.
result := mod(a, b)
}
}
}
/**
* @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryAdd(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
*/
function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
(, uint256 result) = trySub(a, b);
return result;
}
/**
* @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryMul(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Branchless ternary evaluation for `condition ? a : b`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `condition ? a : b`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * SafeCast.toUint(condition));
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
Panic.panic(Panic.DIVISION_BY_ZERO);
}
// The following calculation ensures accurate ceiling division without overflow.
// Since a is non-zero, (a - 1) / b will not overflow.
// The largest possible result occurs when (a - 1) / b is type(uint256).max,
// but the largest value we can obtain is type(uint256).max - 1, which happens
// when a = type(uint256).max and b = 1.
unchecked {
return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
}
}
/**
* @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
*
* Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
// Handle non-overflow cases, 256 by 256 division.
if (high == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return low / denominator;
}
// Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
if (denominator <= high) {
Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [high low].
uint256 remainder;
assembly ("memory-safe") {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
high := sub(high, gt(remainder, low))
low := sub(low, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly ("memory-safe") {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [high low] by twos.
low := div(low, twos)
// Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from high into low.
low |= high * twos;
// Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
// that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv ≡ 1 mod 2⁴.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2⁸
inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
inverse *= 2 - denominator * inverse; // inverse mod 2³²
inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
// less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
// is no longer required.
result = low * inverse;
return result;
}
}
/**
* @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
}
/**
* @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
*/
function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
if (high >= 1 << n) {
Panic.panic(Panic.UNDER_OVERFLOW);
}
return (high << (256 - n)) | (low >> n);
}
}
/**
* @dev Calculates x * y >> n with full precision, following the selected rounding direction.
*/
function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
}
/**
* @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
*
* If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
* If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
*
* If the input value is not inversible, 0 is returned.
*
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
* inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
*/
function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
unchecked {
if (n == 0) return 0;
// The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
// Used to compute integers x and y such that: ax + ny = gcd(a, n).
// When the gcd is 1, then the inverse of a modulo n exists and it's x.
// ax + ny = 1
// ax = 1 + (-y)n
// ax ≡ 1 (mod n) # x is the inverse of a modulo n
// If the remainder is 0 the gcd is n right away.
uint256 remainder = a % n;
uint256 gcd = n;
// Therefore the initial coefficients are:
// ax + ny = gcd(a, n) = n
// 0a + 1n = n
int256 x = 0;
int256 y = 1;
while (remainder != 0) {
uint256 quotient = gcd / remainder;
(gcd, remainder) = (
// The old remainder is the next gcd to try.
remainder,
// Compute the next remainder.
// Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
// where gcd is at most n (capped to type(uint256).max)
gcd - remainder * quotient
);
(x, y) = (
// Increment the coefficient of a.
y,
// Decrement the coefficient of n.
// Can overflow, but the result is casted to uint256 so that the
// next value of y is "wrapped around" to a value between 0 and n - 1.
x - y * int256(quotient)
);
}
if (gcd != 1) return 0; // No inverse exists.
return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
}
}
/**
* @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
*
* From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
* prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
* `a**(p-2)` is the modular multiplicative inverse of a in Fp.
*
* NOTE: this function does NOT check that `p` is a prime greater than `2`.
*/
function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
unchecked {
return Math.modExp(a, p - 2, p);
}
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
*
* Requirements:
* - modulus can't be zero
* - underlying staticcall to precompile must succeed
*
* IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
* sure the chain you're using it on supports the precompiled contract for modular exponentiation
* at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
* the underlying function will succeed given the lack of a revert, but the result may be incorrectly
* interpreted as 0.
*/
function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
(bool success, uint256 result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
* It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
* to operate modulo 0 or if the underlying precompile reverted.
*
* IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
* you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
* https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
* of a revert, but the result may be incorrectly interpreted as 0.
*/
function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
if (m == 0) return (false, 0);
assembly ("memory-safe") {
let ptr := mload(0x40)
// | Offset | Content | Content (Hex) |
// |-----------|------------|--------------------------------------------------------------------|
// | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x60:0x7f | value of b | 0x<.............................................................b> |
// | 0x80:0x9f | value of e | 0x<.............................................................e> |
// | 0xa0:0xbf | value of m | 0x<.............................................................m> |
mstore(ptr, 0x20)
mstore(add(ptr, 0x20), 0x20)
mstore(add(ptr, 0x40), 0x20)
mstore(add(ptr, 0x60), b)
mstore(add(ptr, 0x80), e)
mstore(add(ptr, 0xa0), m)
// Given the result < m, it's guaranteed to fit in 32 bytes,
// so we can use the memory scratch space located at offset 0.
success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
result := mload(0x00)
}
}
/**
* @dev Variant of {modExp} that supports inputs of arbitrary length.
*/
function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
(bool success, bytes memory result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Variant of {tryModExp} that supports inputs of arbitrary length.
*/
function tryModExp(
bytes memory b,
bytes memory e,
bytes memory m
) internal view returns (bool success, bytes memory result) {
if (_zeroBytes(m)) return (false, new bytes(0));
uint256 mLen = m.length;
// Encode call args in result and move the free memory pointer
result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
assembly ("memory-safe") {
let dataPtr := add(result, 0x20)
// Write result on top of args to avoid allocating extra memory.
success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
// Overwrite the length.
// result.length > returndatasize() is guaranteed because returndatasize() == m.length
mstore(result, mLen)
// Set the memory pointer after the returned data.
mstore(0x40, add(dataPtr, mLen))
}
}
/**
* @dev Returns whether the provided byte array is zero.
*/
function _zeroBytes(bytes memory buffer) private pure returns (bool) {
uint256 chunk;
for (uint256 i = 0; i < buffer.length; i += 0x20) {
// See _unsafeReadBytesOffset from utils/Bytes.sol
assembly ("memory-safe") {
chunk := mload(add(add(buffer, 0x20), i))
}
if (chunk >> (8 * saturatingSub(i + 0x20, buffer.length)) != 0) {
return false;
}
}
return true;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* This method is based on Newton's method for computing square roots; the algorithm is restricted to only
* using integer operations.
*/
function sqrt(uint256 a) internal pure returns (uint256) {
unchecked {
// Take care of easy edge cases when a == 0 or a == 1
if (a <= 1) {
return a;
}
// In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
// sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
// the current value as `ε_n = | x_n - sqrt(a) |`.
//
// For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
// of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
// bigger than any uint256.
//
// By noticing that
// `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
// we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
// to the msb function.
uint256 aa = a;
uint256 xn = 1;
if (aa >= (1 << 128)) {
aa >>= 128;
xn <<= 64;
}
if (aa >= (1 << 64)) {
aa >>= 64;
xn <<= 32;
}
if (aa >= (1 << 32)) {
aa >>= 32;
xn <<= 16;
}
if (aa >= (1 << 16)) {
aa >>= 16;
xn <<= 8;
}
if (aa >= (1 << 8)) {
aa >>= 8;
xn <<= 4;
}
if (aa >= (1 << 4)) {
aa >>= 4;
xn <<= 2;
}
if (aa >= (1 << 2)) {
xn <<= 1;
}
// We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
//
// We can refine our estimation by noticing that the middle of that interval minimizes the error.
// If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
// This is going to be our x_0 (and ε_0)
xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)
// From here, Newton's method give us:
// x_{n+1} = (x_n + a / x_n) / 2
//
// One should note that:
// x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
// = ((x_n² + a) / (2 * x_n))² - a
// = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
// = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
// = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
// = (x_n² - a)² / (2 * x_n)²
// = ((x_n² - a) / (2 * x_n))²
// ≥ 0
// Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
//
// This gives us the proof of quadratic convergence of the sequence:
// ε_{n+1} = | x_{n+1} - sqrt(a) |
// = | (x_n + a / x_n) / 2 - sqrt(a) |
// = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
// = | (x_n - sqrt(a))² / (2 * x_n) |
// = | ε_n² / (2 * x_n) |
// = ε_n² / | (2 * x_n) |
//
// For the first iteration, we have a special case where x_0 is known:
// ε_1 = ε_0² / | (2 * x_0) |
// ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
// ≤ 2**(2*e-4) / (3 * 2**(e-1))
// ≤ 2**(e-3) / 3
// ≤ 2**(e-3-log2(3))
// ≤ 2**(e-4.5)
//
// For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
// ε_{n+1} = ε_n² / | (2 * x_n) |
// ≤ (2**(e-k))² / (2 * 2**(e-1))
// ≤ 2**(2*e-2*k) / 2**e
// ≤ 2**(e-2*k)
xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above
xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5
xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9
xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18
xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36
xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72
// Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
// ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
// sqrt(a) or sqrt(a) + 1.
return xn - SafeCast.toUint(xn > a / xn);
}
}
/**
* @dev Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// If upper 8 bits of 16-bit half set, add 8 to result
r |= SafeCast.toUint((x >> r) > 0xff) << 3;
// If upper 4 bits of 8-bit half set, add 4 to result
r |= SafeCast.toUint((x >> r) > 0xf) << 2;
// Shifts value right by the current result and use it as an index into this lookup table:
//
// | x (4 bits) | index | table[index] = MSB position |
// |------------|---------|-----------------------------|
// | 0000 | 0 | table[0] = 0 |
// | 0001 | 1 | table[1] = 0 |
// | 0010 | 2 | table[2] = 1 |
// | 0011 | 3 | table[3] = 1 |
// | 0100 | 4 | table[4] = 2 |
// | 0101 | 5 | table[5] = 2 |
// | 0110 | 6 | table[6] = 2 |
// | 0111 | 7 | table[7] = 2 |
// | 1000 | 8 | table[8] = 3 |
// | 1001 | 9 | table[9] = 3 |
// | 1010 | 10 | table[10] = 3 |
// | 1011 | 11 | table[11] = 3 |
// | 1100 | 12 | table[12] = 3 |
// | 1101 | 13 | table[13] = 3 |
// | 1110 | 14 | table[14] = 3 |
// | 1111 | 15 | table[15] = 3 |
//
// The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the first 16 bytes (most significant half).
assembly ("memory-safe") {
r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))
}
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
/**
* @dev Counts the number of leading zero bits in a uint256.
*/
function clz(uint256 x) internal pure returns (uint256) {
return ternary(x == 0, 256, 255 - log2(x));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * int256(SafeCast.toUint(condition)));
}
}
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson.
// Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift,
// taking advantage of the most significant (or "sign" bit) in two's complement representation.
// This opcode adds new most significant bits set to the value of the previous most significant bit. As a result,
// the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative).
int256 mask = n >> 255;
// A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it.
return uint256((n + mask) ^ mask);
}
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
// solhint-disable-next-line no-unused-import
import {Timestamp, Slot, Epoch} from "@aztec/shared/libraries/TimeMath.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
struct TimeStorage {
uint128 genesisTime;
uint32 slotDuration; // Number of seconds in a slot
uint32 epochDuration; // Number of slots in an epoch
/**
* @notice Number of epochs after the end of a given epoch that proofs are still accepted. For example, a value of 1
* means that after epoch n ends, the proofs must land *before* epoch n+1 ends. A value of 0 would mean that the
* proofs for epoch n must land while the epoch is ongoing.
*/
uint32 proofSubmissionEpochs;
}
library TimeLib {
using SafeCast for uint256;
bytes32 private constant TIME_STORAGE_POSITION = keccak256("aztec.time.storage");
function initialize(
uint256 _genesisTime,
uint256 _slotDuration,
uint256 _epochDuration,
uint256 _proofSubmissionEpochs
) internal {
TimeStorage storage store = getStorage();
store.genesisTime = _genesisTime.toUint128();
store.slotDuration = _slotDuration.toUint32();
store.epochDuration = _epochDuration.toUint32();
store.proofSubmissionEpochs = _proofSubmissionEpochs.toUint32();
}
function toTimestamp(Slot _a) internal view returns (Timestamp) {
TimeStorage storage store = getStorage();
return Timestamp.wrap(store.genesisTime) + Timestamp.wrap(Slot.unwrap(_a) * store.slotDuration);
}
function slotFromTimestamp(Timestamp _a) internal view returns (Slot) {
TimeStorage storage store = getStorage();
return Slot.wrap((Timestamp.unwrap(_a) - store.genesisTime) / store.slotDuration);
}
function toSlots(Epoch _a) internal view returns (Slot) {
return Slot.wrap(Epoch.unwrap(_a) * getStorage().epochDuration);
}
function toTimestamp(Epoch _a) internal view returns (Timestamp) {
return toTimestamp(toSlots(_a));
}
/**
* @notice An epoch deadline is the epoch at which:
* - proofs are no longer accepted
* - which we may prune if no proof has landed
* - rewards may be claimed
*
* @param _a - The epoch to compute the deadline for
*
* @return The computed epoch
*/
function toDeadlineEpoch(Epoch _a) internal view returns (Epoch) {
TimeStorage storage store = getStorage();
// We add one to the proof submission epochs to account for the current epoch.
// This is because toSlots will return the first slot of the epoch, and in the event
// that proofSubmissionEpochs is 0, we would wait until the end of the current epoch.
return _a + Epoch.wrap(store.proofSubmissionEpochs + 1);
}
/**
* @notice Calculates the maximum number of blocks that can be pruned from the pending chain
* @dev The maximum prunable blocks is determined by:
* - epochDuration: number of slots in an epoch
* - proofSubmissionEpochs: number of epochs allowed for proof submission
*
* The formula is: epochDuration * (proofSubmissionEpochs + 1)
*
* The +1 accounts for blocks in the current epoch, ensuring they are included
* in the prunable window along with blocks from previous epochs within the
* proof submission window.
*
* This value is used to:
* 1. Size the circular storage buffer (roundaboutSize = maxPrunableBlocks + 1)
* 2. Determine when blocks become stale and can be overwritten
*
* @return The maximum number of blocks that can be pruned.
*/
function maxPrunableBlocks() internal view returns (uint256) {
TimeStorage storage store = getStorage();
return uint256(store.epochDuration) * (uint256(store.proofSubmissionEpochs) + 1);
}
/**
* @notice Checks if proofs are being accepted for epoch _a during epoch _b
*
* @param _a - The epoch that may be accepting proofs
* @param _b - The epoch we would like to submit the proof for
*
* @return True if proofs would be accepted for epoch _a during epoch _b
*/
function isAcceptingProofsAtEpoch(Epoch _a, Epoch _b) internal view returns (bool) {
return _b < toDeadlineEpoch(_a);
}
function epochFromTimestamp(Timestamp _a) internal view returns (Epoch) {
TimeStorage storage store = getStorage();
return Epoch.wrap((Timestamp.unwrap(_a) - store.genesisTime) / (store.epochDuration * store.slotDuration));
}
function epochFromSlot(Slot _a) internal view returns (Epoch) {
return Epoch.wrap(Slot.unwrap(_a) / getStorage().epochDuration);
}
function getEpochDurationInSeconds() internal view returns (uint256) {
TimeStorage storage store = getStorage();
return store.epochDuration * store.slotDuration;
}
function getStorage() internal pure returns (TimeStorage storage storageStruct) {
bytes32 position = TIME_STORAGE_POSITION;
assembly {
storageStruct.slot := position
}
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {RollupStore, IRollupCore, GenesisState} from "@aztec/core/interfaces/IRollup.sol";
import {
CompressedTempBlockLog,
TempBlockLog,
CompressedTempBlockLogLib
} from "@aztec/core/libraries/compressed-data/BlockLog.sol";
import {CompressedFeeHeader, FeeHeaderLib} from "@aztec/core/libraries/compressed-data/fees/FeeStructs.sol";
import {ChainTipsLib, CompressedChainTips} from "@aztec/core/libraries/compressed-data/Tips.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
import {CompressedSlot, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
/**
* @title STFLib - State Transition Function Library
* @author Aztec Labs
* @notice Core library responsible for managing the rollup state transition function and block storage.
*
* @dev This library implements the essential state management functionality for the Aztec rollup, including:
* - Archive root storage indexed by block number for permanent state history
* - Circular storage for temporary block logs
* - Block pruning mechanism to remove unproven blocks after proof submission window expires
* - Namespaced storage pattern following EIP-7201 for secure storage isolation
*
* Storage Architecture:
* - Uses EIP-7201 namespaced storage
* - Archives mapping: permanent storage of proven block archive roots
* - TempBlockLogs: circular buffer storing temporary block data (gets overwritten after N blocks)
* - Tips: tracks both pending (latest proposed) and proven (latest with valid proof) block numbers
*
* Circular Storage ("Roundabout") Pattern:
* - The temporary block logs use a circular storage pattern where blocks are stored at index (blockNumber %
* roundaboutSize).
* This reuses storage slots for old blocks that have been proven or pruned.
* The roundabout size is calculated as maxPrunableBlocks() + 1 to ensure at least the last proven block
* remains accessible even after pruning operations. This saves gas costs by minimizing storage writes to fresh
* slots.
*
* Pruning Mechanism:
* - Blocks become eligible for pruning when their proof submission window expires. The proof submission
* window is defined as a configurable number of epochs after the epoch containing the block.
* When pruning occurs, all unproven blocks are removed from the pending chain, and the chain
* resumes from the last proven block.
* - Rationale for pruning is that an epoch may contain a block that provers cannot prove. Pruning allows us to
* trade a large reorg for chain liveness, by removing potential unprovable blocks so we can continue.
* - A prover may not be able to prove a block if the transaction data for that block is not available. Transaction
* data is NOT posted to DA since transactions (along with their ClientIVC proofs) are big, and it would be too
* costly to submit everything to blocks. So we count on the committee to attest to the availability of that
* data, but if for some reason the data does not reach provers via p2p, then provers will not be able to prove.
*
* Security Considerations:
* - Archive roots provide immutable history of proven state transitions
* - Circular storage saves gas while maintaining necessary data
* - Proof submission windows ensure liveness by preventing indefinite stalling
* - EIP-7201 namespaced storage prevents accidental storage collisions with other contracts
*
* @dev TempBlockLog Structure
*
* The TempBlockLog struct represents temporary block data stored in the circular buffer
* until blocks overwritten. It contains:
*
* Fields:
* - headerHash: Hash of the complete block header containing all block metadata
* - blobCommitmentsHash: Hash of all blob commitments used for data availability verification
* - attestationsHash: Hash of committee member attestations validating the block
* - payloadDigest: Digest of the proposal payload that committee members attested to
* - slotNumber: The specific slot when this block was proposed (determines epoch assignment)
* - feeHeader: Compressed fee information including base fees and mana pricing
*
* Storage Optimization:
* The struct is stored in compressed format (CompressedTempBlockLog) to minimize gas costs.
* Compression primarily affects the slotNumber (reduced from 256-bit to smaller representation)
* and feeHeader (packed fee components). Other fields remain as 32-byte hashes.
*/
library STFLib {
using TimeLib for Slot;
using TimeLib for Epoch;
using TimeLib for Timestamp;
using CompressedTimeMath for CompressedSlot;
using ChainTipsLib for CompressedChainTips;
using CompressedTempBlockLogLib for CompressedTempBlockLog;
using CompressedTempBlockLogLib for TempBlockLog;
using CompressedTimeMath for Slot;
using CompressedTimeMath for CompressedSlot;
using FeeHeaderLib for CompressedFeeHeader;
// @note This is also used in the cheatcodes, so if updating, please also update the cheatcode.
bytes32 private constant STF_STORAGE_POSITION = keccak256("aztec.stf.storage");
/**
* @notice Initializes the rollup state with genesis configuration
* @dev Sets up the initial state of the rollup including verification keys and the genesis archive root.
* This function should only be called once during rollup deployment.
*
* @param _genesisState The initial state configuration containing:
* - vkTreeRoot: Root of the verification key tree for circuit verification
* - protocolContractTreeRoot: Root containing protocol contract addresses and configurations
* - genesisArchiveRoot: Initial archive root representing the genesis state
*/
function initialize(GenesisState memory _genesisState) internal {
RollupStore storage rollupStore = STFLib.getStorage();
rollupStore.config.vkTreeRoot = _genesisState.vkTreeRoot;
rollupStore.config.protocolContractTreeRoot = _genesisState.protocolContractTreeRoot;
rollupStore.archives[0] = _genesisState.genesisArchiveRoot;
}
/**
* @notice Stores a temporary block log in the circular storage buffer
* @dev Compresses and stores block data at the appropriate index in the circular buffer.
* The storage index is calculated as (pending block % roundaboutSize) to implement
* the circular storage pattern.
* Don't need to check if storage is stale as always writing to freshest.
*
* @param _tempBlockLog The temporary block log containing header hash, attestations,
* blob commitments, payload digest, slot number, and fee information
*/
function addTempBlockLog(TempBlockLog memory _tempBlockLog) internal {
uint256 blockNumber = STFLib.getStorage().tips.getPendingBlockNumber();
uint256 size = roundaboutSize();
getStorage().tempBlockLogs[blockNumber % size] = _tempBlockLog.compress();
}
/**
* @notice Removes unproven blocks from the pending chain when proof submission window expires
* @dev This function implements the pruning mechanism that maintains rollup liveness by removing
* blocks that cannot be proven within the configured time window. When called:
*
* 1. Identifies the gap between pending and proven block numbers
* 2. Resets the pending chain tip to match the last proven block
* 3. Effectively removes all unproven blocks from the pending chain
*
* The pruning does not delete block data from storage but makes it inaccessible by
* updating the chain tips.
*
* Pruning should only occur when the proof submission window has expired for pending
* blocks, which is validated by the calling function (typically through canPruneAtTime).
*
* Emits PrunedPending event with the proven and previously pending block numbers.
*/
function prune() internal {
RollupStore storage rollupStore = STFLib.getStorage();
CompressedChainTips tips = rollupStore.tips;
uint256 pending = tips.getPendingBlockNumber();
// @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was
// proven.
// We can do because any new block proposed will overwrite a previous block in the block log,
// so no values should "survive".
// People must therefore read the chain using the pendingTip as a boundary.
uint256 proven = tips.getProvenBlockNumber();
rollupStore.tips = tips.updatePendingBlockNumber(proven);
emit IRollupCore.PrunedPending(proven, pending);
}
/**
* @notice Calculates the size of the circular storage buffer for temporary block logs
* @dev The roundabout size determines how many blocks can be stored in the circular buffer
* before older entries are overwritten. The size is calculated as:
*
* roundaboutSize = maxPrunableBlocks() + 1
*
* Where maxPrunableBlocks() = epochDuration * (proofSubmissionEpochs + 1)
*
* This ensures that:
* - All blocks within the proof submission window remain accessible
* - At least the last proven block is available as a trusted anchor
*
* @return The number of slots in the circular storage buffer
*/
function roundaboutSize() internal view returns (uint256) {
// Must be ensured to contain at least the last proven block even after a prune.
return TimeLib.maxPrunableBlocks() + 1;
}
/**
* @notice Returns a storage reference to a compressed temporary block log
* @dev Provides direct access to the compressed block log in storage without decompression.
* Reverts if the block number is stale (no longer accessible in circular storage) or if
* the block have not happened yet.
*
* @dev A temporary block log is stale if it can no longer be accessed in the circular storage buffer.
* The staleness is determined by the relationship between the block number, current pending
* block, and the buffer size.
*
* Example with roundabout size 5 and pending block 7:
* Circular buffer state: [block5, block6, block7, block3, block4]
*
* A block is available if:
* - blockNumber <= pending (it is not in the future)
* - pending < blockNumber + size (the override is in the future)
* Together as a span:
* - blockNumber <= pending < blockNumber + size
*
* For example, block 2 is unavailable since the override has happened:
* - 2 <= 7 (true) && 7 < 2 + 5 (false)
* But block 3 is available as it in the past, but not overridden yet
* - 3 <= 7 (true) && 7 < 3 + 5 (true)
*
* This ensures that only blocks within the current "window" of the circular buffer
* are considered valid and accessible.
*
* @param _blockNumber The block number to get the storage reference for
* @return A storage reference to the compressed temporary block log
*/
function getStorageTempBlockLog(uint256 _blockNumber) internal view returns (CompressedTempBlockLog storage) {
uint256 pending = getStorage().tips.getPendingBlockNumber();
uint256 size = roundaboutSize();
uint256 upperLimit = _blockNumber + size;
bool available = _blockNumber <= pending && pending < upperLimit;
require(available, Errors.Rollup__UnavailableTempBlockLog(_blockNumber, pending, upperLimit));
return getStorage().tempBlockLogs[_blockNumber % size];
}
/**
* @notice Retrieves and decompresses a temporary block log from circular storage
* @dev Fetches the compressed block log from the circular buffer and decompresses it.
* Reverts if the block number is stale and no longer accessible.
* @param _blockNumber The block number to retrieve the log for
* @return The decompressed temporary block log containing all block metadata
*/
function getTempBlockLog(uint256 _blockNumber) internal view returns (TempBlockLog memory) {
return getStorageTempBlockLog(_blockNumber).decompress();
}
/**
* @notice Retrieves the header hash for a specific block number
* @dev Gas-efficient accessor that returns only the header hash without decompressing
* the entire block log. Reverts if the block number is stale.
* @param _blockNumber The block number to get the header hash for
* @return The header hash of the specified block
*/
function getHeaderHash(uint256 _blockNumber) internal view returns (bytes32) {
return getStorageTempBlockLog(_blockNumber).headerHash;
}
/**
* @notice Retrieves the compressed fee header for a specific block number
* @dev Returns the fee information including base fee components and mana costs.
* The data remains in compressed format for gas efficiency. Reverts if the block is stale.
* @param _blockNumber The block number to get the fee header for
* @return The compressed fee header containing fee-related data
*/
function getFeeHeader(uint256 _blockNumber) internal view returns (CompressedFeeHeader) {
return getStorageTempBlockLog(_blockNumber).feeHeader;
}
/**
* @notice Retrieves the blob commitments hash for a specific block number
* @dev Returns the hash of all blob commitments for the block, used for data availability
* verification. Reverts if the block number is stale.
* @param _blockNumber The block number to get the blob commitments hash for
* @return The hash of blob commitments for the specified block
*/
function getBlobCommitmentsHash(uint256 _blockNumber) internal view returns (bytes32) {
return getStorageTempBlockLog(_blockNumber).blobCommitmentsHash;
}
/**
* @notice Retrieves the slot number for a specific block number
* @dev Returns the decompressed slot number indicating when the block was proposed.
* Reverts if the block number is stale.
* @param _blockNumber The block number to get the slot number for
* @return The slot number when the block was proposed
*/
function getSlotNumber(uint256 _blockNumber) internal view returns (Slot) {
return getStorageTempBlockLog(_blockNumber).slotNumber.decompress();
}
/**
* @notice Gets the effective pending block number based on pruning eligibility
* @dev Returns either the pending block number or proven block number depending on
* whether pruning is allowed at the given timestamp. This is used to determine
* the effective chain tip for operations that should respect pruning windows.
*
* If pruning is allowed: returns proven block number (chain should be pruned)
* If pruning is not allowed: returns pending block number (normal operation)
* @param _timestamp The timestamp to evaluate pruning eligibility against
* @return The effective block number that should be considered as the chain tip
*/
function getEffectivePendingBlockNumber(Timestamp _timestamp) internal view returns (uint256) {
RollupStore storage rollupStore = STFLib.getStorage();
CompressedChainTips tips = rollupStore.tips;
return STFLib.canPruneAtTime(_timestamp) ? tips.getProvenBlockNumber() : tips.getPendingBlockNumber();
}
/**
* @notice Determines which epoch a block belongs to
* @dev Calculates the epoch for a given block number by retrieving the block's slot
* and converting it to an epoch. Reverts if the block number exceeds the pending tip.
* @param _blockNumber The block number to get the epoch for
* @return The epoch containing the specified block
*/
function getEpochForBlock(uint256 _blockNumber) internal view returns (Epoch) {
RollupStore storage rollupStore = STFLib.getStorage();
require(
_blockNumber <= rollupStore.tips.getPendingBlockNumber(),
Errors.Rollup__InvalidBlockNumber(rollupStore.tips.getPendingBlockNumber(), _blockNumber)
);
return getSlotNumber(_blockNumber).epochFromSlot();
}
/**
* @notice Determines if the chain can be pruned at a given timestamp
* @dev Checks whether the proof submission window has expired for the oldest pending blocks.
* Pruning is allowed when:
*
* 1. There are unproven blocks (pending > proven)
* 2. The oldest pending epoch is no longer accepting proofs at the epoch at _ts
*
* The proof submission window is defined by the aztecProofSubmissionEpochs configuration,
* which specifies how many epochs after an epoch ends that proofs are still accepted.
*
* Example timeline:
* - Block proposed in epoch N
* - Proof submission window = 1 epochs
* - Proof deadline epoch = N + Proof submission window + 1
* The deadline is the point in time where it is no longer acceptable, (if you touch the line you die)
* - If epoch(_ts) >= epoch N + Proof submission window + 1, pruning is allowed
*
* This mechanism ensures rollup liveness by preventing indefinite stalling on unprovable blocks (e.g due to
* the committee failing to disseminate the data) while providing sufficient time for proof generation and
* submission.
*
* @param _ts The current timestamp to check against
* @return True if pruning is allowed at the given timestamp, false otherwise
*/
function canPruneAtTime(Timestamp _ts) internal view returns (bool) {
RollupStore storage rollupStore = STFLib.getStorage();
CompressedChainTips tips = rollupStore.tips;
if (tips.getPendingBlockNumber() == tips.getProvenBlockNumber()) {
return false;
}
Epoch oldestPendingEpoch = getEpochForBlock(tips.getProvenBlockNumber() + 1);
Epoch currentEpoch = _ts.epochFromTimestamp();
return !oldestPendingEpoch.isAcceptingProofsAtEpoch(currentEpoch);
}
/**
* @notice Retrieves the namespaced storage for the STFLib using EIP-7201 pattern
* @dev Uses inline assembly to access storage at a specific slot calculated from the
* keccak256 hash of "aztec.stf.storage". This ensures storage isolation and
* prevents collisions with other contracts or libraries.
*
* The storage contains:
* - Chain tips (pending and proven block numbers)
* - Archives mapping (permanent block archive storage)
* - TempBlockLogs mapping (circular buffer for temporary block data)
* - Rollup configuration
* @return storageStruct A storage pointer to the RollupStore struct
*/
function getStorage() internal pure returns (RollupStore storage storageStruct) {
bytes32 position = STF_STORAGE_POSITION;
assembly {
storageStruct.slot := position
}
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {DataStructures} from "@aztec/core/libraries/DataStructures.sol";
/**
* @title Hash library
* @author Aztec Labs
* @notice Library that contains helper functions to compute hashes for data structures and convert to field elements
* Using sha256 as the hash function since it hits a good balance between gas cost and circuit size.
*/
library Hash {
/**
* @notice Computes the sha256 hash of the L1 to L2 message and converts it to a field element
* @param _message - The L1 to L2 message to hash
* @return The hash of the provided message as a field element
*/
function sha256ToField(DataStructures.L1ToL2Msg memory _message) internal pure returns (bytes32) {
return sha256ToField(
abi.encode(_message.sender, _message.recipient, _message.content, _message.secretHash, _message.index)
);
}
/**
* @notice Computes the sha256 hash of the L2 to L1 message and converts it to a field element
* @param _message - The L2 to L1 message to hash
* @return The hash of the provided message as a field element
*/
function sha256ToField(DataStructures.L2ToL1Msg memory _message) internal pure returns (bytes32) {
return sha256ToField(
abi.encodePacked(
_message.sender.actor,
_message.sender.version,
_message.recipient.actor,
_message.recipient.chainId,
_message.content
)
);
}
/**
* @notice Computes the sha256 hash of the provided data and converts it to a field element
* @dev Truncating one byte to convert the hash to a field element. We prepend a byte rather than cast
* bytes31(bytes32) to match Noir's to_be_bytes.
* @param _data - The bytes to hash
* @return The hash of the provided data as a field element
*/
function sha256ToField(bytes memory _data) internal pure returns (bytes32) {
return bytes32(bytes.concat(new bytes(1), bytes31(sha256(_data))));
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {RollupStore} from "@aztec/core/interfaces/IRollup.sol";
import {ValidatorSelectionStorage} from "@aztec/core/interfaces/IValidatorSelection.sol";
import {SampleLib} from "@aztec/core/libraries/crypto/SampleLib.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {AttestationLib, CommitteeAttestations} from "@aztec/core/libraries/rollup/AttestationLib.sol";
import {StakingLib} from "@aztec/core/libraries/rollup/StakingLib.sol";
import {STFLib} from "@aztec/core/libraries/rollup/STFLib.sol";
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
import {SignatureLib, Signature} from "@aztec/shared/libraries/SignatureLib.sol";
import {ECDSA} from "@oz/utils/cryptography/ECDSA.sol";
import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {SlotDerivation} from "@oz/utils/SlotDerivation.sol";
import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol";
import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol";
import {TransientSlot} from "@oz/utils/TransientSlot.sol";
/**
* @title ValidatorSelectionLib
* @author Aztec Labs
* @notice Core library responsible for validator selection, committee management, and proposer verification in the
* Aztec rollup.
*
* @dev This library implements the validator selection system:
* - Epoch-based committee sampling
* - Slot-based proposer selection within committee members
* - Signature verification for block proposals and attestations
* - Committee commitment validation and caching mechanisms
* - Randomness seed management for unpredictable but deterministic selection
*
* Key Components:
*
* 1. Committee Selection:
* - At the start of each epoch, a committee is sampled from the active validator set
* - Committee size is configurable at deployment (targetCommitteeSize), and must be met
* - Selection uses cryptographic randomness (prevrandao + epoch)
* - Committee remains stable throughout the entire epoch for consistency
* - Committee commitment is stored on-chain and validated against reconstructed committees
*
* 2. Proposer Selection:
* - For each slot within an epoch, one committee member is selected as the proposer (this may change)
* - Selection is deterministic based on epoch, slot, and the epoch's sample seed
* - Proposers have exclusive rights to propose blocks during their assigned slot
* - Proposer verification ensures only the correct validator can submit blocks
*
* 3. Attestation System:
* - Committee members attest to blocks by providing signatures
* - Attestations serve dual purpose: data availability and state validation
* - Blocks require >2/3 committee signatures to be considered valid
* - Signatures are verified against expected committee members using ECDSA recovery
* - Mixed signature/address format allows optimization (addresses included only for non-signing members,
* addresses for signing members can be recovered from the signatures and hence are not needed for DA
* purposes)
* - Signature verification is delayed until proof submission to save gas
*
* 4. Seed Management:
* - Sample seeds determine committee and proposer selection for each epoch
* - Seeds use prevrandao from L1 blocks combined with epoch number for unpredictability
* - Prevrandao are set 2 epochs in advance to prevent last-minute manipulation and provide L1-reorg resistance
* - First two epochs use randao values (type(uint224).max) for bootstrap (this results in the committee
* being predictable in the first 2 epochs which is considered acceptable when bootstrapping the network)
*
* 5. Caching and Optimization:
* - Transient storage caches proposer computations within the same transaction
* - This is used when signaling for a governance or slashing payload after a block proposal
* - Committee commitments are stored to avoid recomputation during verification
* - Validator indices are sampled once and reused for address resolution
*
* Integration with Rollup System:
* - Called from RollupCore.setupEpoch() to initialize epoch committees
* - Used in ProposeLib.propose() for proposer verification during block submission
* - Integrates with StakingLib to resolve validator addresses from staking indices
* - Works with InvalidateLib for committee verification during invalidation
*
* Security Model:
* - Randomness comes from L1 prevrandao
* - Committee selection happens before epoch start, preventing manipulation
* - Signature verification ensures only legitimate committee members can attest
* - Committee commitments prevent committee substitution attacks
* - Two-epoch delay in seed setting prevents last-minute influence and provides L1-reorg resistance
*
* Time-based Architecture:
* - Epochs define committee boundaries (committee stable within epoch)
* - Slots define proposer assignments (one proposer per slot)
* - Sampling uses a lagging time for the epoch to ensure validator set stability
* - Validator set snapshots taken at deterministic timestamps for consistency
*/
library ValidatorSelectionLib {
using EnumerableSet for EnumerableSet.AddressSet;
using MessageHashUtils for bytes32;
using SignatureLib for Signature;
using TimeLib for Timestamp;
using TimeLib for Epoch;
using TimeLib for Slot;
using Checkpoints for Checkpoints.Trace224;
using SafeCast for *;
using TransientSlot for *;
using SlotDerivation for string;
using SlotDerivation for bytes32;
using AttestationLib for CommitteeAttestations;
/**
* @dev Stack struct used in verifyAttestations to avoid stack too deep errors
* Used when reconstructing the committee commitment from the attestations
* @param proposerIndex Index of the proposer within the committee
* @param index Working index for iteration (unused in current implementation)
* @param needed Number of signatures required (2/3 + 1 of committee size)
* @param signaturesRecovered Number of valid signatures found
* @param reconstructedCommittee Array of committee member addresses reconstructed from attestations
*/
struct VerifyStack {
uint256 proposerIndex;
uint256 index;
uint256 needed;
uint256 signaturesRecovered;
address[] reconstructedCommittee;
}
bytes32 private constant VALIDATOR_SELECTION_STORAGE_POSITION = keccak256("aztec.validator_selection.storage");
// Namespace for cached proposer computations
string private constant PROPOSER_NAMESPACE = "aztec.validator_selection.transient.proposer";
/**
* @notice Initializes the validator selection system with target committee size
* @dev Sets up the initial configuration and bootstrap seeds for the first two epochs.
* The first two epochs use maximum seed values for startup.
* @param _targetCommitteeSize The desired number of validators in each epoch's committee
*/
function initialize(uint256 _targetCommitteeSize, uint256 _lagInEpochs) internal {
ValidatorSelectionStorage storage store = getStorage();
store.targetCommitteeSize = _targetCommitteeSize.toUint32();
store.lagInEpochs = _lagInEpochs.toUint32();
checkpointRandao(Epoch.wrap(0));
}
/**
* @notice Performs epoch setup by sampling the committee and setting future seeds
* @dev This function handles the epoch transition by:
* 1. Retrieving the sample seed for the current epoch
* 2. Setting the sample seed for the next epoch (if not already set)
* 3. Sampling and storing the committee for the current epoch (if not already done)
*
* This setup ensures that each epoch has a stable committee and that future epochs
* have their randomness seeds prepared in advance.
* @param _epochNumber The epoch number to set up
*/
function setupEpoch(Epoch _epochNumber) internal {
ValidatorSelectionStorage storage store = getStorage();
bytes32 committeeCommitment = store.committeeCommitments[_epochNumber];
if (committeeCommitment != bytes32(0)) {
// We already have the commitment stored for the epoch meaning the epoch has already been setup.
return;
}
//################ Seeds ################
// Get the sample seed for this current epoch.
uint256 sampleSeed = getSampleSeed(_epochNumber);
// Checkpoint randao for future sampling if required
// function handles the case where it is already set
checkpointRandao(_epochNumber);
//################ Committee ################
// If the committee is not set for this epoch, we need to sample it
address[] memory committee = sampleValidators(_epochNumber, sampleSeed);
store.committeeCommitments[_epochNumber] = computeCommitteeCommitment(committee);
}
/**
* @notice Verifies that the block proposal has been signed by the correct proposer
* @dev Validates proposer eligibility and signature for block proposals by:
* 1. Attempting to load cached proposer from transient storage
* 2. If not cached, reconstructing committee from attestations and verifying against stored commitment
* 3. Computing proposer index using epoch, slot, and sample seed
* 4. Verifying the proposer has provided a valid signature in the attestations
*
* The attestation is checked by reconstructing the committee commitment from the attestations and signers,
* and then ensuring it matches the stored commitment for the epoch.
*
* Uses transient storage caching to avoid recomputation within the same transaction. (This caching mechanism is
* commonly used when a proposer signals in governance and submits a proposal within the same transaction - then
* `getProposerAt` function is called).
* @param _slot The slot of the block being proposed
* @param _epochNumber The epoch number of the block
* @param _attestations The committee attestations for the block proposal
* @param _signers The addresses of the committee members that signed the attestations. Provided in order to not have
* to recover them from their attestations' signatures (and hence save gas). The addresses of the non-signing
* committee members are directly included in the attestations.
* @param _digest The digest of the block being proposed
* @param _updateCache Flag to identify that the proposer should be written to transient cache.
* @custom:reverts Errors.ValidatorSelection__InvalidCommitteeCommitment if reconstructed committee doesn't match
* stored commitment
* @custom:reverts Errors.ValidatorSelection__MissingProposerSignature if proposer hasn't signed their attestation
* @custom:reverts SignatureLib verification errors if proposer signature is invalid
*/
function verifyProposer(
Slot _slot,
Epoch _epochNumber,
CommitteeAttestations memory _attestations,
address[] memory _signers,
bytes32 _digest,
Signature memory _attestationsAndSignersSignature,
bool _updateCache
) internal {
uint256 proposerIndex;
address proposer;
{
// Load the committee commitment for the epoch
(bytes32 committeeCommitment, uint256 committeeSize) = getCommitteeCommitmentAt(_epochNumber);
// If the rollup is *deployed* with a target committee size of 0, we skip the validation.
// Note: This generally only happens in test setups; In production, the target committee is non-zero,
// and one can see in `sampleValidators` that we will revert if the target committee size is not met.
if (committeeSize == 0) {
return;
}
// Reconstruct the committee from the attestations and signers
address[] memory committee = _attestations.reconstructCommitteeFromSigners(_signers, committeeSize);
// Check reconstructed committee commitment matches the expected one for the epoch
bytes32 reconstructedCommitment = computeCommitteeCommitment(committee);
if (reconstructedCommitment != committeeCommitment) {
revert Errors.ValidatorSelection__InvalidCommitteeCommitment(reconstructedCommitment, committeeCommitment);
}
// Get the proposer from the committee based on the epoch, slot, and sample seed
uint256 sampleSeed = getSampleSeed(_epochNumber);
proposerIndex = computeProposerIndex(_epochNumber, _slot, sampleSeed, committeeSize);
proposer = committee[proposerIndex];
}
// We check that the proposer agrees with the proposal by checking that he attested to it. If we fail to get
// the proposer's attestation signature or if we fail to verify it, we revert.
bool hasProposerSignature = _attestations.isSignature(proposerIndex);
if (!hasProposerSignature) {
revert Errors.ValidatorSelection__MissingProposerSignature(proposer, proposerIndex);
}
// Check if the signature is correct
bytes32 digest = _digest.toEthSignedMessageHash();
Signature memory signature = _attestations.getSignature(proposerIndex);
SignatureLib.verify(signature, proposer, digest);
// Check that the proposer have signed the `_attestations|_signers` data such that invalid `_attestations|_signers`
// data can be attributed to the `proposer` specifically.
bytes32 attestationsAndSignersDigest =
_attestations.getAttestationsAndSignersDigest(_signers).toEthSignedMessageHash();
SignatureLib.verify(_attestationsAndSignersSignature, proposer, attestationsAndSignersDigest);
if (_updateCache) {
setCachedProposer(_slot, proposer, proposerIndex);
}
}
/**
* @notice Verifies committee attestations meet the required threshold and signature validity
* @dev Performs attestation validation by:
* 1. Retrieving stored committee commitment and target committee size
* 2. Computing proposer index for signature verification optimization
* 3. Extracting and verifying signatures from packed attestation data
* 4. Reconstructing committee addresses from signatures and provided addresses
* 5. Validating reconstructed committee matches stored commitment
* 6. Ensuring at least 2/3 + 1 committee members provided signatures
*
* Each committee attestation is either their:
* - Signature (65 bytes: v, r, s) for attestation
* - Address (20 bytes) for non-signing members
*
* Note that providing the addresses of non-signing members allows for reconstructing the committee commitment
* directly from calldata.
*
* Skips validation entirely if target committee size is 0 (test configurations).
* @param _slot The slot of the block
* @param _epochNumber The epoch of the block
* @param _attestations The packed signatures and addresses of committee members
* @param _digest The digest of the block that attestations are signed over
* @custom:reverts Errors.ValidatorSelection__InsufficientAttestations if less than 2/3 + 1 signatures provided
* @custom:reverts Errors.ValidatorSelection__InvalidCommitteeCommitment if reconstructed committee doesn't match
* stored commitment
*/
function verifyAttestations(
Slot _slot,
Epoch _epochNumber,
CommitteeAttestations memory _attestations,
bytes32 _digest
) internal {
(bytes32 committeeCommitment, uint256 targetCommitteeSize) = getCommitteeCommitmentAt(_epochNumber);
// If the rollup is *deployed* with a target committee size of 0, we skip the validation.
// Note: This generally only happens in test setups; In production, the target committee is non-zero,
// and one can see in `sampleValidators` that we will revert if the target committee size is not met.
if (targetCommitteeSize == 0) {
return;
}
VerifyStack memory stack = VerifyStack({
proposerIndex: computeProposerIndex(_epochNumber, _slot, getSampleSeed(_epochNumber), targetCommitteeSize),
needed: (targetCommitteeSize << 1) / 3 + 1, // targetCommitteeSize * 2 / 3 + 1, but cheaper
index: 0,
signaturesRecovered: 0,
reconstructedCommittee: new address[](targetCommitteeSize)
});
bytes32 digest = _digest.toEthSignedMessageHash();
bytes memory signaturesOrAddresses = _attestations.signaturesOrAddresses;
uint256 dataPtr;
assembly {
dataPtr := add(signaturesOrAddresses, 0x20) // Skip length, cache pointer
}
unchecked {
for (uint256 i = 0; i < targetCommitteeSize; ++i) {
bool isSignature = _attestations.isSignature(i);
if (isSignature) {
uint8 v;
bytes32 r;
bytes32 s;
assembly {
v := byte(0, mload(dataPtr))
dataPtr := add(dataPtr, 1)
r := mload(dataPtr)
dataPtr := add(dataPtr, 32)
s := mload(dataPtr)
dataPtr := add(dataPtr, 32)
}
++stack.signaturesRecovered;
stack.reconstructedCommittee[i] = ECDSA.recover(digest, v, r, s);
} else {
address addr;
assembly {
addr := shr(96, mload(dataPtr))
dataPtr := add(dataPtr, 20)
}
stack.reconstructedCommittee[i] = addr;
}
}
}
require(
stack.signaturesRecovered >= stack.needed,
Errors.ValidatorSelection__InsufficientAttestations(stack.needed, stack.signaturesRecovered)
);
// Check the committee commitment
bytes32 reconstructedCommitment = computeCommitteeCommitment(stack.reconstructedCommittee);
if (reconstructedCommitment != committeeCommitment) {
revert Errors.ValidatorSelection__InvalidCommitteeCommitment(reconstructedCommitment, committeeCommitment);
}
}
/**
* @notice Caches proposer information in transient storage for the current transaction
* @dev Uses EIP-1153 transient storage to cache proposer data, avoiding recomputation within the same transaction.
* Packs proposer address (160 bits) and index (96 bits) into a single 32-byte slot for efficiency.
* @param _slot The slot to cache the proposer for
* @param _proposer The proposer's address
* @param _proposerIndex The proposer's index within the committee
* @custom:reverts Errors.ValidatorSelection__ProposerIndexTooLarge if proposer index exceeds uint96 max
*/
function setCachedProposer(Slot _slot, address _proposer, uint256 _proposerIndex) internal {
require(_proposerIndex <= type(uint96).max, Errors.ValidatorSelection__ProposerIndexTooLarge(_proposerIndex));
bytes32 packed = bytes32(uint256(uint160(_proposer))) | (bytes32(_proposerIndex) << 160);
PROPOSER_NAMESPACE.erc7201Slot().deriveMapping(Slot.unwrap(_slot)).asBytes32().tstore(packed);
}
/**
* @notice Gets the proposer for a specific slot, using cache or computing if necessary
* @dev First checks transient storage cache, then computes proposer if not cached.
* Computation involves sampling validator indices and selecting based on slot.
* @param _slot The slot to get the proposer for
* @return proposer The address of the proposer for the slot
* @return proposerIndex The index of the proposer within the committee, zero address and index if committee size is
* 0 (ie test configuration).
*/
function getProposerAt(Slot _slot) internal returns (address, uint256) {
(address cachedProposer, uint256 cachedProposerIndex) = getCachedProposer(_slot);
if (cachedProposer != address(0)) {
return (cachedProposer, cachedProposerIndex);
}
Epoch epochNumber = _slot.epochFromSlot();
uint256 sampleSeed = getSampleSeed(epochNumber);
(uint32 ts, uint256[] memory indices) = sampleValidatorsIndices(epochNumber, sampleSeed);
uint256 committeeSize = indices.length;
if (committeeSize == 0) {
return (address(0), 0);
}
uint256 proposerIndex = computeProposerIndex(epochNumber, _slot, sampleSeed, committeeSize);
return (StakingLib.getAttesterFromIndexAtTime(indices[proposerIndex], Timestamp.wrap(ts)), proposerIndex);
}
/**
* @notice Samples validator addresses for a specific epoch using cryptographic randomness
* @dev Samples validator indices first, then resolves to addresses at the appropriate timestamp.
* Only used internally for epoch setup - should never be called for past or distant future epochs.
* @param _epoch The epoch to sample validators for
* @param _seed The cryptographic seed for sampling randomness
* @return The array of validator addresses selected for the committee
*/
function sampleValidators(Epoch _epoch, uint256 _seed) internal returns (address[] memory) {
(uint32 ts, uint256[] memory indices) = sampleValidatorsIndices(_epoch, _seed);
return StakingLib.getAttestersFromIndicesAtTime(Timestamp.wrap(ts), indices);
}
/**
* @notice Gets the committee addresses for a specific epoch
* @dev Retrieves the sample seed for the epoch and uses it to sample the validator committee.
* This function will trigger committee sampling if not already done for the epoch.
* @param _epochNumber The epoch to get the committee for
* @return The array of committee member addresses for the epoch
*/
function getCommitteeAt(Epoch _epochNumber) internal returns (address[] memory) {
uint256 seed = getSampleSeed(_epochNumber);
return sampleValidators(_epochNumber, seed);
}
/**
* @notice Gets the committee commitment and size for an epoch
* @dev Retrieves the stored committee commitment, or computes it if not yet stored.
* The commitment is a keccak256 hash of the committee member addresses array.
* @param _epochNumber The epoch to get the committee commitment for
* @return committeeCommitment The keccak256 hash of the committee member addresses
* @return committeeSize The target committee size (same for all epochs)
*/
function getCommitteeCommitmentAt(Epoch _epochNumber)
internal
returns (bytes32 committeeCommitment, uint256 committeeSize)
{
ValidatorSelectionStorage storage store = getStorage();
committeeCommitment = store.committeeCommitments[_epochNumber];
if (committeeCommitment == 0) {
// This is an edge case that can happen if `setupEpoch` has not been called (see documentation of
// `RollupCore.setupEpoch` for details), so we compute the commitment again to guarantee that we get a real value.
committeeCommitment = computeCommitteeCommitment(sampleValidators(_epochNumber, getSampleSeed(_epochNumber)));
}
return (committeeCommitment, store.targetCommitteeSize);
}
/**
* @notice Checkpoints randao value for future usage
* @dev Checks if already stored before storing the randao value.
* @param _epoch The current epoch
*/
function checkpointRandao(Epoch _epoch) internal {
ValidatorSelectionStorage storage store = getStorage();
// Check if the latest checkpoint is for the next epoch
// It should be impossible that zero epoch snapshots exist, as in the genesis state we push the first values
// into the store
(, uint32 mostRecentTs,) = store.randaos.latestCheckpoint();
uint32 ts = Timestamp.unwrap(_epoch.toTimestamp()).toUint32();
// If the most recently stored epoch is less than the epoch we are querying, then we need to store randao for
// later use. We truncate to save storage costs.
if (mostRecentTs < ts) {
store.randaos.push(ts, uint224(block.prevrandao));
}
}
/**
* @notice Validates if a specific validator can propose a block at a given time and chain state
* @dev Performs comprehensive validation including:
* - Slot timing (must be after the last block's slot)
* - Archive consistency (must build on current chain tip)
* - Proposer authorization (must be the designated proposer for the slot)
* @param _ts The timestamp of the proposed block
* @param _archive The archive root the block claims to build on
* @param _who The address attempting to propose the block
* @return slot The slot number derived from the timestamp
* @return blockNumber The next block number that will be assigned
* @custom:reverts Errors.Rollup__SlotAlreadyInChain if trying to propose for a past slot
* @custom:reverts Errors.Rollup__InvalidArchive if archive doesn't match current chain tip
* @custom:reverts Errors.ValidatorSelection__InvalidProposer if _who is not the designated proposer
*/
function canProposeAtTime(Timestamp _ts, bytes32 _archive, address _who) internal returns (Slot, uint256) {
Slot slot = _ts.slotFromTimestamp();
RollupStore storage rollupStore = STFLib.getStorage();
// Pending chain tip
uint256 pendingBlockNumber = STFLib.getEffectivePendingBlockNumber(_ts);
Slot lastSlot = STFLib.getSlotNumber(pendingBlockNumber);
require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot));
// Make sure that the proposer is up to date and on the right chain (ie no reorgs)
bytes32 tipArchive = rollupStore.archives[pendingBlockNumber];
require(tipArchive == _archive, Errors.Rollup__InvalidArchive(tipArchive, _archive));
(address proposer,) = getProposerAt(slot);
require(proposer == _who, Errors.ValidatorSelection__InvalidProposer(proposer, _who));
return (slot, pendingBlockNumber + 1);
}
/**
* @notice Retrieves cached proposer information from transient storage
* @dev Reads packed proposer data (address + index) from EIP-1153 transient storage.
* Returns zero values if no proposer is cached for the slot.
* @param _slot The slot to check for cached proposer
* @return proposer The cached proposer address (address(0) if not cached)
* @return proposerIndex The cached proposer index (0 if not cached)
*/
function getCachedProposer(Slot _slot) internal view returns (address proposer, uint256 proposerIndex) {
bytes32 packed = PROPOSER_NAMESPACE.erc7201Slot().deriveMapping(Slot.unwrap(_slot)).asBytes32().tload();
// Extract address from lower 160 bits
proposer = address(uint160(uint256(packed)));
// Extract uint96 from upper 96 bits
proposerIndex = uint256(packed >> 160);
}
/**
* @notice Converts an epoch number to the timestamp used for validator set sampling
* @dev Calculates the sampling timestamp by:
* 1. Taking the epoch start timestamp
* 2. Subtracting `lagInEpochs` full epoch duration to ensure stability
*
* This ensures validator set sampling uses stable historical data that won't be
* affected by last-minute changes or L1 reorgs during synchronization.
* @param _epoch The epoch to calculate sampling time for
* @return The Unix timestamp (uint32) to use for validator set sampling
*/
function epochToSampleTime(Epoch _epoch) internal view returns (uint32) {
uint32 sub = getStorage().lagInEpochs * TimeLib.getEpochDurationInSeconds().toUint32();
return Timestamp.unwrap(_epoch.toTimestamp()).toUint32() - sub;
}
/**
* @notice Gets the cryptographic sample seed for an epoch
* @dev Retrieves the randao from the checkpointed randaos mapping using upperLookup.
* Then computes the sample seed using keccak256(epoch, randao)
* @param _epoch The epoch to get the sample seed for
* @return The sample seed used for validator selection randomness
*/
function getSampleSeed(Epoch _epoch) internal view returns (uint256) {
ValidatorSelectionStorage storage store = getStorage();
uint32 ts = epochToSampleTime(_epoch);
return uint256(keccak256(abi.encode(_epoch, store.randaos.upperLookup(ts))));
}
function getSamplingSize(Epoch _epoch) internal view returns (uint256) {
uint32 ts = epochToSampleTime(_epoch);
return StakingLib.getAttesterCountAtTime(Timestamp.wrap(ts));
}
function getLagInEpochs() internal view returns (uint256) {
return getStorage().lagInEpochs;
}
/**
* @notice Gets the validator selection storage struct using EIP-7201 namespaced storage
* @dev Uses assembly to access storage at the predetermined slot to avoid collisions.
* @return storageStruct The validator selection storage struct
*/
function getStorage() internal pure returns (ValidatorSelectionStorage storage storageStruct) {
bytes32 position = VALIDATOR_SELECTION_STORAGE_POSITION;
assembly {
storageStruct.slot := position
}
}
/**
* @notice Computes the committee index of the proposer for a specific slot
* @dev Uses keccak256 hash of epoch, slot, and seed to deterministically select a committee member.
* The result is modulo committee size to ensure valid index.
* The result being modulo biased is not a problem here as the validators in the committee were chosen randomly
* and are not ordered.
* @param _epoch The epoch containing the slot
* @param _slot The specific slot to compute proposer for
* @param _seed The epoch's sample seed for randomness
* @param _size The size of the committee
* @return The index (0 to _size-1) of the committee member who should propose for this slot
*/
function computeProposerIndex(Epoch _epoch, Slot _slot, uint256 _seed, uint256 _size) internal pure returns (uint256) {
return uint256(keccak256(abi.encode(_epoch, _slot, _seed))) % _size;
}
/**
* @notice Samples validator indices for a specific epoch using cryptographic randomness
* @dev Determines sample timestamp, gets validator set size, and uses SampleLib to select committee indices.
* Validates that enough validators are available to meet target committee size.
* @param _epoch The epoch to sample validators for
* @param _seed The cryptographic seed for sampling randomness
* @return sampleTime The timestamp used for validator set sampling
* @return indices Array of validator indices selected for the committee
* @custom:reverts Errors.ValidatorSelection__InsufficientValidatorSetSize if not enough validators available
*/
function sampleValidatorsIndices(Epoch _epoch, uint256 _seed) private returns (uint32, uint256[] memory) {
ValidatorSelectionStorage storage store = getStorage();
uint32 ts = epochToSampleTime(_epoch);
uint256 validatorSetSize = StakingLib.getAttesterCountAtTime(Timestamp.wrap(ts));
uint256 targetCommitteeSize = store.targetCommitteeSize;
require(
validatorSetSize >= targetCommitteeSize,
Errors.ValidatorSelection__InsufficientValidatorSetSize(validatorSetSize, targetCommitteeSize)
);
if (targetCommitteeSize == 0) {
return (ts, new uint256[](0));
}
return (ts, SampleLib.computeCommittee(targetCommitteeSize, validatorSetSize, _seed));
}
/**
* @notice Computes the keccak256 commitment hash for a committee member array
* @dev Creates a cryptographic commitment to the committee composition that can be verified later.
* Used to prevent committee substitution attacks during attestation verification.
* @param _committee The array of committee member addresses
* @return The keccak256 hash of the ABI-encoded committee array
*/
function computeCommitteeCommitment(address[] memory _committee) private pure returns (bytes32) {
return keccak256(abi.encode(_committee));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/BitMaps.sol)
pragma solidity ^0.8.20;
/**
* @dev Library for managing uint256 to bool mapping in a compact and efficient way, provided the keys are sequential.
* Largely inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor].
*
* BitMaps pack 256 booleans across each bit of a single 256-bit slot of `uint256` type.
* Hence booleans corresponding to 256 _sequential_ indices would only consume a single slot,
* unlike the regular `bool` which would consume an entire slot for a single value.
*
* This results in gas savings in two ways:
*
* - Setting a zero value to non-zero only once every 256 times
* - Accessing the same warm slot for every 256 _sequential_ indices
*/
library BitMaps {
struct BitMap {
mapping(uint256 bucket => uint256) _data;
}
/**
* @dev Returns whether the bit at `index` is set.
*/
function get(BitMap storage bitmap, uint256 index) internal view returns (bool) {
uint256 bucket = index >> 8;
uint256 mask = 1 << (index & 0xff);
return bitmap._data[bucket] & mask != 0;
}
/**
* @dev Sets the bit at `index` to the boolean `value`.
*/
function setTo(BitMap storage bitmap, uint256 index, bool value) internal {
if (value) {
set(bitmap, index);
} else {
unset(bitmap, index);
}
}
/**
* @dev Sets the bit at `index`.
*/
function set(BitMap storage bitmap, uint256 index) internal {
uint256 bucket = index >> 8;
uint256 mask = 1 << (index & 0xff);
bitmap._data[bucket] |= mask;
}
/**
* @dev Unsets the bit at `index`.
*/
function unset(BitMap storage bitmap, uint256 index) internal {
uint256 bucket = index >> 8;
uint256 mask = 1 << (index & 0xff);
bitmap._data[bucket] &= ~mask;
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IEmperor} from "@aztec/governance/interfaces/IEmpire.sol";
import {Timestamp, Slot, Epoch} from "@aztec/shared/libraries/TimeMath.sol";
import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol";
struct ValidatorSelectionStorage {
// A mapping to snapshots of the validator set
mapping(Epoch => bytes32 committeeCommitment) committeeCommitments;
// Checkpointed map of epoch -> randao value
Checkpoints.Trace224 randaos;
uint32 targetCommitteeSize;
uint32 lagInEpochs;
}
interface IValidatorSelectionCore {
function setupEpoch() external;
function checkpointRandao() external;
}
interface IValidatorSelection is IValidatorSelectionCore, IEmperor {
function getProposerAt(Timestamp _ts) external returns (address);
// Non view as uses transient storage
function getCurrentEpochCommittee() external returns (address[] memory);
function getCommitteeAt(Timestamp _ts) external returns (address[] memory);
function getCommitteeCommitmentAt(Timestamp _ts) external returns (bytes32, uint256);
function getEpochCommittee(Epoch _epoch) external returns (address[] memory);
function getEpochCommitteeCommitment(Epoch _epoch) external returns (bytes32, uint256);
// Stable
function getCurrentEpoch() external view returns (Epoch);
// Consider removing below this point
function getTimestampForSlot(Slot _slotNumber) external view returns (Timestamp);
function getSampleSeedAt(Timestamp _ts) external view returns (uint256);
function getSamplingSizeAt(Timestamp _ts) external view returns (uint256);
function getLagInEpochs() external view returns (uint256);
function getCurrentSampleSeed() external view returns (uint256);
function getEpochAt(Timestamp _ts) external view returns (Epoch);
function getSlotAt(Timestamp _ts) external view returns (Slot);
function getEpochAtSlot(Slot _slotNumber) external view returns (Epoch);
function getGenesisTime() external view returns (Timestamp);
function getSlotDuration() external view returns (uint256);
function getEpochDuration() external view returns (uint256);
function getTargetCommitteeSize() external view returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature is invalid.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* NOTE: This function only supports 65-byte signatures. ERC-2098 short signatures are rejected. This restriction
* is DEPRECATED and will be removed in v6.0. Developers SHOULD NOT use signatures as unique identifiers; use hash
* invalidation or nonces for replay protection.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
*
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(
bytes32 hash,
bytes memory signature
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly ("memory-safe") {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Variant of {tryRecover} that takes a signature in calldata
*/
function tryRecoverCalldata(
bytes32 hash,
bytes calldata signature
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, calldata slices would work here, but are
// significantly more expensive (length check) than using calldataload in assembly.
assembly ("memory-safe") {
r := calldataload(signature.offset)
s := calldataload(add(signature.offset, 0x20))
v := byte(0, calldataload(add(signature.offset, 0x40)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* NOTE: This function only supports 65-byte signatures. ERC-2098 short signatures are rejected. This restriction
* is DEPRECATED and will be removed in v6.0. Developers SHOULD NOT use signatures as unique identifiers; use hash
* invalidation or nonces for replay protection.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Variant of {recover} that takes a signature in calldata
*/
function recoverCalldata(bytes32 hash, bytes calldata signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecoverCalldata(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r` and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Parse a signature into its `v`, `r` and `s` components. Supports 65-byte and 64-byte (ERC-2098)
* formats. Returns (0,0,0) for invalid signatures.
*
* For 64-byte signatures, `v` is automatically normalized to 27 or 28.
* For 65-byte signatures, `v` is returned as-is and MUST already be 27 or 28 for use with ecrecover.
*
* Consider validating the result before use, or use {tryRecover}/{recover} which perform full validation.
*/
function parse(bytes memory signature) internal pure returns (uint8 v, bytes32 r, bytes32 s) {
assembly ("memory-safe") {
// Check the signature length
switch mload(signature)
// - case 65: r,s,v signature (standard)
case 65 {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098)
case 64 {
let vs := mload(add(signature, 0x40))
r := mload(add(signature, 0x20))
s := and(vs, shr(1, not(0)))
v := add(shr(255, vs), 27)
}
default {
r := 0
s := 0
v := 0
}
}
}
/**
* @dev Variant of {parse} that takes a signature in calldata
*/
function parseCalldata(bytes calldata signature) internal pure returns (uint8 v, bytes32 r, bytes32 s) {
assembly ("memory-safe") {
// Check the signature length
switch signature.length
// - case 65: r,s,v signature (standard)
case 65 {
r := calldataload(signature.offset)
s := calldataload(add(signature.offset, 0x20))
v := byte(0, calldataload(add(signature.offset, 0x40)))
}
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098)
case 64 {
let vs := calldataload(add(signature.offset, 0x20))
r := calldataload(signature.offset)
s := and(vs, shr(1, not(0)))
v := add(shr(255, vs), 27)
}
default {
r := 0
s := 0
v := 0
}
}
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)
pragma solidity >=0.6.2;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
import { Clone } from "../libraries/Clone.sol";
import { SplitV2Lib } from "../libraries/SplitV2.sol";
import { Nonces } from "../utils/Nonces.sol";
import { SplitWalletV2 } from "./SplitWalletV2.sol";
/**
* @title SplitFactoryV2
* @author Splits
* @notice Minimal smart wallet clone-factory for v2 splitters.
*/
abstract contract SplitFactoryV2 is Nonces {
/* -------------------------------------------------------------------------- */
/* EVENTS */
/* -------------------------------------------------------------------------- */
event SplitCreated(
address indexed split, SplitV2Lib.Split splitParams, address owner, address creator, bytes32 salt
);
event SplitCreated(
address indexed split, SplitV2Lib.Split splitParams, address owner, address creator, uint256 nonce
);
/* -------------------------------------------------------------------------- */
/* STORAGE */
/* -------------------------------------------------------------------------- */
/// @notice address of Split Wallet V2 implementation.
address public immutable SPLIT_WALLET_IMPLEMENTATION;
/* -------------------------------------------------------------------------- */
/* EXTERNAL FUNCTIONS */
/* -------------------------------------------------------------------------- */
/**
* @notice Create a new split using create2.
* @dev Returns existing split if already created, otherwise creates new split.
* @param _splitParams Params to create split with.
* @param _owner Owner of created split.
* @param _creator Creator of created split.
* @param _salt Salt for create2.
*/
function createSplitDeterministic(
SplitV2Lib.Split calldata _splitParams,
address _owner,
address _creator,
bytes32 _salt
)
external
returns (address split)
{
bytes32 salt = _getSalt({ _splitParams: _splitParams, _owner: _owner, _salt: _salt });
split = Clone.predictDeterministicAddress({
_implementation: SPLIT_WALLET_IMPLEMENTATION,
_salt: salt,
_deployer: address(this)
});
if (split.code.length > 0) {
return split;
}
split = Clone.cloneDeterministic({ _implementation: SPLIT_WALLET_IMPLEMENTATION, _salt: salt });
SplitWalletV2(split).initialize(_splitParams, _owner);
emit SplitCreated({ split: split, splitParams: _splitParams, owner: _owner, creator: _creator, salt: _salt });
}
/**
* @notice Create a new split with params and owner.
* @dev Uses a hash-based incrementing nonce over params and owner.
* @dev designed to be used with integrating contracts to avoid salt management and needing to handle the potential
* for griefing via front-running. See docs for more information.
* @param _splitParams Params to create split with.
* @param _owner Owner of created split.
* @param _creator Creator of created split.
*/
function createSplit(
SplitV2Lib.Split calldata _splitParams,
address _owner,
address _creator
)
external
returns (address split)
{
bytes32 hash = keccak256(abi.encode(_splitParams, _owner));
uint256 nonce = useNonce(hash);
split = Clone.cloneDeterministic({
_implementation: SPLIT_WALLET_IMPLEMENTATION,
_salt: keccak256(bytes.concat(hash, abi.encode(nonce)))
});
SplitWalletV2(split).initialize(_splitParams, _owner);
emit SplitCreated({ split: split, splitParams: _splitParams, owner: _owner, creator: _creator, nonce: nonce });
}
/**
* @notice Predict the address of a new split based on split params, owner, and salt.
* @param _splitParams Params to create split with
* @param _owner Owner of created split
* @param _salt Salt for create2
*/
function predictDeterministicAddress(
SplitV2Lib.Split calldata _splitParams,
address _owner,
bytes32 _salt
)
external
view
returns (address)
{
return _predictDeterministicAddress({ _splitParams: _splitParams, _owner: _owner, _salt: _salt });
}
/**
* @notice Predict the address of a new split based on the nonce of the hash of the params and owner.
* @param _splitParams Params to create split with.
* @param _owner Owner of created split.
*/
function predictDeterministicAddress(
SplitV2Lib.Split calldata _splitParams,
address _owner
)
external
view
returns (address)
{
bytes32 hash = keccak256(abi.encode(_splitParams, _owner));
return Clone.predictDeterministicAddress({
_implementation: SPLIT_WALLET_IMPLEMENTATION,
_salt: keccak256(bytes.concat(hash, abi.encode(nonces(hash)))),
_deployer: address(this)
});
}
/**
* @notice Predict the address of a new split and check if it is deployed.
* @param _splitParams Params to create split with.
* @param _owner Owner of created split.
* @param _salt Salt for create2.
*/
function isDeployed(
SplitV2Lib.Split calldata _splitParams,
address _owner,
bytes32 _salt
)
external
view
returns (address split, bool exists)
{
split = _predictDeterministicAddress({ _splitParams: _splitParams, _owner: _owner, _salt: _salt });
exists = split.code.length > 0;
}
/* -------------------------------------------------------------------------- */
/* PRIVATE/INTERNAL FUNCTIONS */
/* -------------------------------------------------------------------------- */
function _getSalt(
SplitV2Lib.Split calldata _splitParams,
address _owner,
bytes32 _salt
)
internal
pure
returns (bytes32)
{
return keccak256(bytes.concat(abi.encode(_splitParams, _owner), _salt));
}
function _predictDeterministicAddress(
SplitV2Lib.Split calldata _splitParams,
address _owner,
bytes32 _salt
)
internal
view
returns (address)
{
return Clone.predictDeterministicAddress({
_implementation: SPLIT_WALLET_IMPLEMENTATION,
_salt: _getSalt({ _splitParams: _splitParams, _owner: _owner, _salt: _salt }),
_deployer: address(this)
});
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
import { Cast } from "../../libraries/Cast.sol";
import { SplitV2Lib } from "../../libraries/SplitV2.sol";
import { SplitWalletV2 } from "../SplitWalletV2.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title Pull Split Wallet
* @author Splits
* @notice The implementation logic for a splitter that distributes using the splits warehouse.
* @dev `SplitProxy` handles `receive()` itself to avoid the gas cost with `DELEGATECALL`.
*/
contract PullSplit is SplitWalletV2 {
using SplitV2Lib for SplitV2Lib.Split;
using SafeERC20 for IERC20;
using Cast for address;
/* -------------------------------------------------------------------------- */
/* CONSTRUCTOR & INITIALIZER */
/* -------------------------------------------------------------------------- */
constructor(address _splitWarehouse) SplitWalletV2(_splitWarehouse, "PullSplit") { }
/* -------------------------------------------------------------------------- */
/* PUBLIC/EXTERNAL FUNCTIONS */
/* -------------------------------------------------------------------------- */
/**
* @notice Distributes the tokens in the split & Warehouse to the recipients through the warehouse.
* @dev The split must be initialized and the hash of _split must match splitHash.
* @param _split The split struct containing the split data that gets distributed.
* @param _token The token to distribute.
* @param _distributor The distributor of the split.
*/
function distribute(
SplitV2Lib.Split calldata _split,
address _token,
address _distributor
)
external
override
pausable
{
if (splitHash != _split.getHash()) revert InvalidSplit();
(uint256 splitBalance, uint256 warehouseBalance) = getSplitBalance(_token);
// @solidity memory-safe-assembly
// solhint-disable-next-line no-inline-assembly
assembly {
// splitBalance -= uint(splitBalance > 0);
splitBalance := sub(splitBalance, iszero(iszero(splitBalance)))
// warehouseBalance -= uint(warehouseBalance > 0);
warehouseBalance := sub(warehouseBalance, iszero(iszero(warehouseBalance)))
}
if (splitBalance > 0) _depositToWarehouse(_token, splitBalance);
_distribute({
_split: _split,
_token: _token,
_amount: warehouseBalance + splitBalance,
_distributor: _distributor
});
}
/**
* @notice Distributes a specific amount of tokens in the split & Warehouse to the recipients through the warehouse.
* @dev The split must be initialized and the hash of _split must match splitHash.
* @dev Will revert if the amount of tokens to transfer or distribute doesn't exist.
* @param _split The split struct containing the split data that gets distributed.
* @param _token The token to distribute.
* @param _distributeAmount The amount of tokens to distribute.
* @param _performWarehouseTransfer if true, deposits all but 1 amount of tokens to the warehouse.
* @param _distributor The distributor of the split.
*/
function distribute(
SplitV2Lib.Split calldata _split,
address _token,
uint256 _distributeAmount,
bool _performWarehouseTransfer,
address _distributor
)
external
override
pausable
{
if (splitHash != _split.getHash()) revert InvalidSplit();
if (_performWarehouseTransfer) {
uint256 amount =
(_token == NATIVE_TOKEN ? address(this).balance : IERC20(_token).balanceOf(address(this))) - 1;
_depositToWarehouse(_token, amount);
}
_distribute({ _split: _split, _token: _token, _amount: _distributeAmount, _distributor: _distributor });
}
/**
* @notice Deposits tokens to the warehouse.
* @param _token The token to deposit.
* @param _amount The amount of tokens to deposit
*/
function depositToWarehouse(address _token, uint256 _amount) external pausable {
_depositToWarehouse(_token, _amount);
}
/* -------------------------------------------------------------------------- */
/* INTERNAL/PRIVATE */
/* -------------------------------------------------------------------------- */
function _depositToWarehouse(address _token, uint256 _amount) internal {
if (_token == NATIVE_TOKEN) {
SPLITS_WAREHOUSE.deposit{ value: _amount }({ receiver: address(this), token: _token, amount: _amount });
} else {
uint256 allowance = IERC20(_token).allowance(address(this), address(SPLITS_WAREHOUSE));
if (allowance < _amount) {
IERC20(_token).forceApprove({ spender: address(SPLITS_WAREHOUSE), value: type(uint256).max });
}
SPLITS_WAREHOUSE.deposit({ receiver: address(this), token: _token, amount: _amount });
}
}
/// @dev Assumes the amount is already deposited to the warehouse.
function _distribute(
SplitV2Lib.Split calldata _split,
address _token,
uint256 _amount,
address _distributor
)
internal
{
(uint256[] memory amounts, uint256 distributorReward) = _split.getDistributions(_amount);
SPLITS_WAREHOUSE.batchTransfer({ receivers: _split.recipients, token: _token, amounts: amounts });
if (distributorReward > 0) {
SPLITS_WAREHOUSE.transfer({ receiver: _distributor, id: _token.toUint256(), amount: distributorReward });
}
emit SplitDistributed({ token: _token, distributor: _distributor, amount: _amount });
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IStakingCore} from "@aztec/core/interfaces/IStaking.sol";
import {
StakingQueueConfig,
CompressedStakingQueueConfig,
StakingQueueConfigLib
} from "@aztec/core/libraries/compressed-data/StakingQueueConfig.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {StakingQueueLib, StakingQueue, DepositArgs} from "@aztec/core/libraries/StakingQueue.sol";
import {TimeLib, Timestamp, Epoch} from "@aztec/core/libraries/TimeLib.sol";
import {Governance} from "@aztec/governance/Governance.sol";
import {GSE, AttesterConfig, IGSECore} from "@aztec/governance/GSE.sol";
import {Proposal} from "@aztec/governance/interfaces/IGovernance.sol";
import {ProposalLib} from "@aztec/governance/libraries/ProposalLib.sol";
import {GovernanceProposer} from "@aztec/governance/proposer/GovernanceProposer.sol";
import {G1Point, G2Point} from "@aztec/shared/libraries/BN254Lib.sol";
import {
CompressedTimeMath, CompressedTimestamp, CompressedEpoch
} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@oz/utils/math/Math.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
// None -> Does not exist in our setup
// Validating -> Participating as validator
// Zombie -> Not participating as validator, but have funds in setup,
// hit if slashes and going below the minimum
// Exiting -> In the process of exiting the system
enum Status {
NONE,
VALIDATING,
ZOMBIE,
EXITING
}
/**
* @notice Represents a validator's exit from the staking system
* @dev Used to track withdrawal details and timing for validators leaving the system.
* The exit can be created in two scenarios:
* 1. Voluntary withdrawal: Validator calls initiateWithdraw() -> recipientOrWithdrawer is the final recipient
* 2. Slashing-induced exit: Validator gets slashed -> recipientOrWithdrawer is the withdrawer who must later
* call initiateWithdraw() to specify a recipient
*
* The recipientOrWithdrawer field serves dual purposes:
* - When isRecipient=true: This address will receive the withdrawn funds
* - When isRecipient=false: This address (the withdrawer) can call initiateWithdraw() to set a recipient
*
* Workflow for slashing-induced exits:
* 1. Slashing occurs -> Exit created with recipientOrWithdrawer=withdrawer, isRecipient=false
* 2. Withdrawer calls initiateWithdraw() -> Updates to recipientOrWithdrawer=recipient, isRecipient=true
* 3. After delay period -> finalizeWithdraw() can transfer funds to the recipient
* @param withdrawalId Unique identifier for this withdrawal from the GSE contract
* @param amount The amount of stake being withdrawn
* @param exitableAt Timestamp when the stake becomes withdrawable after delay period
* @param recipientOrWithdrawer Address that can either receive funds (if isRecipient) or initiate withdrawal (if
* !isRecipient)
* @param isRecipient True if recipientOrWithdrawer is the recipient, false if it's the withdrawer
* @param exists True if this exit record exists, false if not yet created
*/
struct Exit {
uint256 withdrawalId;
uint256 amount;
Timestamp exitableAt;
address recipientOrWithdrawer;
bool isRecipient;
bool exists;
}
struct AttesterView {
Status status;
uint256 effectiveBalance;
Exit exit;
AttesterConfig config;
}
struct StakingStorage {
IERC20 stakingAsset;
address slasher;
uint96 localEjectionThreshold;
GSE gse;
CompressedTimestamp exitDelay;
mapping(address attester => Exit) exits;
CompressedStakingQueueConfig queueConfig;
StakingQueue entryQueue;
CompressedEpoch nextFlushableEpoch;
uint32 availableValidatorFlushes;
bool isBootstrapped;
}
library StakingLib {
using SafeCast for uint256;
using SafeERC20 for IERC20;
using StakingQueueLib for StakingQueue;
using ProposalLib for Proposal;
using StakingQueueConfigLib for CompressedStakingQueueConfig;
using StakingQueueConfigLib for StakingQueueConfig;
using CompressedTimeMath for CompressedTimestamp;
using CompressedTimeMath for Timestamp;
using CompressedTimeMath for CompressedEpoch;
using CompressedTimeMath for Epoch;
bytes32 private constant STAKING_SLOT = keccak256("aztec.core.staking.storage");
function initialize(
IERC20 _stakingAsset,
GSE _gse,
Timestamp _exitDelay,
address _slasher,
StakingQueueConfig memory _config,
uint256 _localEjectionThreshold
) internal {
StakingStorage storage store = getStorage();
store.stakingAsset = _stakingAsset;
store.gse = _gse;
store.exitDelay = _exitDelay.compress();
store.slasher = _slasher;
store.queueConfig = _config.compress();
store.entryQueue.init();
store.localEjectionThreshold = _localEjectionThreshold.toUint96();
}
function setSlasher(address _slasher) internal {
StakingStorage storage store = getStorage();
address oldSlasher = store.slasher;
store.slasher = _slasher;
emit IStakingCore.SlasherUpdated(oldSlasher, _slasher);
}
function setLocalEjectionThreshold(uint256 _localEjectionThreshold) internal {
StakingStorage storage store = getStorage();
uint256 oldLocalEjectionThreshold = store.localEjectionThreshold;
store.localEjectionThreshold = _localEjectionThreshold.toUint96();
emit IStakingCore.LocalEjectionThresholdUpdated(oldLocalEjectionThreshold, _localEjectionThreshold);
}
/**
* @notice Vote on a governance proposal with the rollup's voting power
* @dev Only votes if:
* 1. This rollup is the current canonical instance according to governance proposer
* 2. This rollup was canonical when the proposal was created
* 3. The proposal was created by the governance proposer
* @param _proposalId The ID of the proposal to vote on
*/
function vote(uint256 _proposalId) internal {
StakingStorage storage store = getStorage();
Governance gov = store.gse.getGovernance();
GovernanceProposer govProposer = GovernanceProposer(gov.governanceProposer());
// We only vote if we are the canonical instance
require(address(this) == govProposer.getInstance(), Errors.Staking__NotCanonical(address(this)));
address proposalProposer = govProposer.getProposalProposer(_proposalId);
// We only vote if we were canonical when the proposal was created
require(
address(this) == proposalProposer, Errors.Staking__NotOurProposal(_proposalId, address(this), proposalProposer)
);
// We only vote if the proposal was created by the governance proposer
Proposal memory proposal = gov.getProposal(_proposalId);
require(proposal.proposer == address(govProposer), Errors.Staking__IncorrectGovProposer(_proposalId));
Timestamp ts = proposal.creation + proposal.config.votingDelay;
// Cast votes with all our power
uint256 vp = store.gse.getVotingPowerAt(address(this), ts);
store.gse.vote(_proposalId, vp, true);
// If we are the canonical at the time of the proposal we also cast those votes.
if (store.gse.getLatestRollupAt(ts) == address(this)) {
address bonusInstance = store.gse.getBonusInstanceAddress();
vp = store.gse.getVotingPowerAt(bonusInstance, ts);
store.gse.voteWithBonus(_proposalId, vp, true);
}
}
/**
* @notice Completes a validator's withdrawal after the exit delay period
* @param _attester The address of the validator completing withdrawal
* @dev Reverts if the attester has no valid exit request (Staking__NotExiting) or if the exit delay period has not
* elapsed (Staking__WithdrawalNotUnlockedYet)
*/
function finalizeWithdraw(address _attester) internal {
StakingStorage storage store = getStorage();
// We load it into memory to cache it, as we will delete it before we use it.
Exit memory exit = store.exits[_attester];
require(exit.exists, Errors.Staking__NotExiting(_attester));
require(exit.isRecipient, Errors.Staking__InitiateWithdrawNeeded(_attester));
require(
exit.exitableAt <= Timestamp.wrap(block.timestamp),
Errors.Staking__WithdrawalNotUnlockedYet(Timestamp.wrap(block.timestamp), exit.exitableAt)
);
delete store.exits[_attester];
store.gse.finalizeWithdraw(exit.withdrawalId);
store.stakingAsset.safeTransfer(exit.recipientOrWithdrawer, exit.amount);
emit IStakingCore.WithdrawFinalized(_attester, exit.recipientOrWithdrawer, exit.amount);
}
function trySlash(address _attester, uint256 _amount) internal returns (bool) {
if (!isSlashable(_attester)) {
return false;
}
slash(_attester, _amount);
return true;
}
/**
* @notice Slashes a validator's stake as punishment for misbehavior
* @dev Only callable by the authorized slasher contract. Handles slashing for both exiting and active validators.
* For exiting validators, reduces their exit amount. For active validators, the balance will be reduced and
* an exit will be created if the remaining stake falls below the ejection threshold.
* @param _attester The address of the validator to slash
* @param _amount The amount of stake to slash
*/
function slash(address _attester, uint256 _amount) internal {
StakingStorage storage store = getStorage();
require(msg.sender == store.slasher, Errors.Staking__NotSlasher(store.slasher, msg.sender));
Exit storage exit = store.exits[_attester];
if (exit.exists) {
require(exit.exitableAt > Timestamp.wrap(block.timestamp), Errors.Staking__CannotSlashExitedStake(_attester));
// If the slash amount is greater than the exit amount, bound it to the exit amount
uint256 slashAmount = Math.min(_amount, exit.amount);
if (exit.amount == slashAmount) {
// If we slash the entire thing, nuke it entirely
delete store.exits[_attester];
} else {
exit.amount -= slashAmount;
}
emit IStakingCore.Slashed(_attester, slashAmount);
} else {
// Get the effective balance of the attester
uint256 effectiveBalance = store.gse.effectiveBalanceOf(address(this), _attester);
require(effectiveBalance > 0, Errors.Staking__NoOneToSlash(_attester));
address withdrawer = store.gse.getWithdrawer(_attester);
// If the slash amount is greater than the effective balance, bound it to the effective balance
uint256 slashAmount = Math.min(_amount, effectiveBalance);
// The `localEjectionThreshold` might be stricter (larger) than the global (gse ejection threshold)
uint256 toWithdraw =
effectiveBalance - slashAmount < store.localEjectionThreshold ? effectiveBalance : slashAmount;
(uint256 amountWithdrawn, bool isRemoved, uint256 withdrawalId) = store.gse.withdraw(_attester, toWithdraw);
// The slashed amount remains in the contract permanently, effectively burning those tokens.
uint256 toUser = amountWithdrawn - slashAmount;
if (isRemoved && toUser > 0) {
// Only if we remove the attester AND there is something left will we create an exit
store.exits[_attester] = Exit({
withdrawalId: withdrawalId,
amount: toUser,
exitableAt: Timestamp.wrap(block.timestamp) + store.exitDelay.decompress(),
recipientOrWithdrawer: withdrawer,
isRecipient: false,
exists: true
});
}
emit IStakingCore.Slashed(_attester, slashAmount);
}
}
/**
* @notice Deposits stake to add a new validator to the entry queue
* @dev Transfers stake from the caller and adds the validator to the entry queue.
* The validator must not already be exiting. The attester and withdrawer addresses
* must be non-zero. The stake amount is fixed at the activation threshold.
* The validator will be processed from the queue in a future flushEntryQueue call.
*
* @param _attester The address that will act as the validator (sign attestations)
* @param _withdrawer The address that can withdraw the stake
* @param _publicKeyInG1 The G1 point for the BLS public key (used for efficient signature verification in GSE)
* @param _publicKeyInG2 The G2 point for the BLS public key (used for BLS aggregation and pairing operations in GSE)
* @param _proofOfPossession The proof of possession to show that the keys in G1 and G2 share the same secret key
* @param _moveWithLatestRollup Whether to automatically stake on a new rollup instance after an upgrade
*/
function deposit(
address _attester,
address _withdrawer,
G1Point memory _publicKeyInG1,
G2Point memory _publicKeyInG2,
G1Point memory _proofOfPossession,
bool _moveWithLatestRollup
) internal {
require(
_attester != address(0) && _withdrawer != address(0), Errors.Staking__InvalidDeposit(_attester, _withdrawer)
);
StakingStorage storage store = getStorage();
// We don't allow deposits, if we are currently exiting.
require(!store.exits[_attester].exists, Errors.Staking__AlreadyExiting(_attester));
uint256 amount = store.gse.ACTIVATION_THRESHOLD();
store.stakingAsset.safeTransferFrom(msg.sender, address(this), amount);
store.entryQueue.enqueue(
_attester, _withdrawer, _publicKeyInG1, _publicKeyInG2, _proofOfPossession, _moveWithLatestRollup
);
emit IStakingCore.ValidatorQueued(_attester, _withdrawer);
}
function updateAndGetAvailableFlushes() internal returns (uint256) {
(uint256 flushes, Epoch currentEpoch, bool shouldUpdateState) = _calculateAvailableFlushes();
if (shouldUpdateState) {
StakingStorage storage store = getStorage();
store.nextFlushableEpoch = (currentEpoch + Epoch.wrap(1)).compress();
store.availableValidatorFlushes = flushes.toUint32();
}
return flushes;
}
/**
* @notice Processes the validator entry queue to add new validators to the active set
* @dev Processes up to min(maxAddableValidators, _toAdd) entries from the queue,
* attempting to deposit each validator into the Governance Staking Escrow (GSE).
*
* For each validator:
* - Dequeues their entry from the queue
* - Attempts to deposit them into the GSE contract
* - On success: emits Deposit event
* - On failure: refunds their stake and emits FailedDeposit event
*
* The function will revert if:
* - A deposit fails due to out of gas (to prevent queue draining attacks)
*
* The function approves the GSE contract to spend the total stake amount needed for all deposits,
* then revokes the approval after processing is complete.
* It also updates the available validator flushes
*
* @param _toAdd - The max number the caller will try to add
*/
function flushEntryQueue(uint256 _toAdd) internal {
uint256 maxAddableValidators = updateAndGetAvailableFlushes();
if (maxAddableValidators == 0) {
return;
}
StakingStorage storage store = getStorage();
uint256 queueLength = store.entryQueue.length();
uint256 numToDequeue = Math.min(Math.min(maxAddableValidators, queueLength), _toAdd);
if (numToDequeue == 0) {
return;
}
// Approve the GSE to spend the total stake amount needed for all deposits.
uint256 amount = store.gse.ACTIVATION_THRESHOLD();
store.stakingAsset.approve(address(store.gse), amount * numToDequeue);
uint256 depositCount = 0;
for (uint256 i = 0; i < numToDequeue; i++) {
DepositArgs memory args = store.entryQueue.dequeue();
(bool success, bytes memory data) = address(store.gse).call(
abi.encodeWithSelector(
IGSECore.deposit.selector,
args.attester,
args.withdrawer,
args.publicKeyInG1,
args.publicKeyInG2,
args.proofOfPossession,
args.moveWithLatestRollup
)
);
if (success) {
depositCount++;
emit IStakingCore.Deposit(
args.attester, args.withdrawer, args.publicKeyInG1, args.publicKeyInG2, args.proofOfPossession, amount
);
} else {
// If the deposit fails, we need to handle two cases:
// 1. Normal failure (data.length > 0): We return the funds to the withdrawer and continue processing
// the queue. This prevents a single failed deposit from blocking the entire queue.
// 2. Out of gas failure (data.length == 0): We revert the entire transaction. This prevents an attack
// where someone could drain the queue without making any deposits.
// We can safely assume data.length == 0 means out of gas since we only call trusted GSE contract.
require(data.length > 0, Errors.Staking__DepositOutOfGas());
store.stakingAsset.safeTransfer(args.withdrawer, amount);
emit IStakingCore.FailedDeposit(
args.attester, args.withdrawer, args.publicKeyInG1, args.publicKeyInG2, args.proofOfPossession
);
}
}
store.stakingAsset.approve(address(store.gse), 0);
store.availableValidatorFlushes -= depositCount.toUint32();
// If we have reached the bootstrap size, mark it as bootstrapped such that we don't re-enter it.
if (
!store.isBootstrapped
&& getAttesterCountAtTime(Timestamp.wrap(block.timestamp))
>= store.queueConfig.decompress().bootstrapValidatorSetSize
) {
store.isBootstrapped = true;
}
}
/**
* @notice Initiates withdrawal of a validator's stake
* @dev Can be called by the registered withdrawer to start the exit process for a validator.
* Handles two cases:
* 1. If an exit already exists (e.g. from slashing):
* - Only allows updating recipient if caller is withdrawer
* - Does not update the exit delay timer
* 2. If no exit exists:
* - Requires validator has non-zero balance
* - Only allows registered withdrawer to initiate
* - Withdraws stake from GSE contract
* - Creates new exit with delay timer
* @param _attester The validator address to withdraw stake for
* @param _recipient The address that will receive the withdrawn stake
* @return True if withdrawal was successfully initiated
*/
function initiateWithdraw(address _attester, address _recipient) internal returns (bool) {
require(_recipient != address(0), Errors.Staking__InvalidRecipient(_recipient));
StakingStorage storage store = getStorage();
if (store.exits[_attester].exists) {
// If there is already an exit, we either started it and should revert
// or it is because of a slash and we should update the recipient
// Still only if we are the withdrawer
// We DO NOT update the exitableAt
require(!store.exits[_attester].isRecipient, Errors.Staking__NothingToExit(_attester));
require(
store.exits[_attester].recipientOrWithdrawer == msg.sender,
Errors.Staking__NotWithdrawer(store.exits[_attester].recipientOrWithdrawer, msg.sender)
);
store.exits[_attester].recipientOrWithdrawer = _recipient;
store.exits[_attester].isRecipient = true;
emit IStakingCore.WithdrawInitiated(_attester, _recipient, store.exits[_attester].amount);
} else {
uint256 effectiveBalance = store.gse.effectiveBalanceOf(address(this), _attester);
require(effectiveBalance > 0, Errors.Staking__NothingToExit(_attester));
address withdrawer = store.gse.getWithdrawer(_attester);
require(msg.sender == withdrawer, Errors.Staking__NotWithdrawer(withdrawer, msg.sender));
(uint256 actualAmount, bool removed, uint256 withdrawalId) = store.gse.withdraw(_attester, effectiveBalance);
require(removed, Errors.Staking__WithdrawFailed(_attester));
store.exits[_attester] = Exit({
withdrawalId: withdrawalId,
amount: actualAmount,
exitableAt: Timestamp.wrap(block.timestamp) + store.exitDelay.decompress(),
recipientOrWithdrawer: _recipient,
isRecipient: true,
exists: true
});
emit IStakingCore.WithdrawInitiated(_attester, _recipient, actualAmount);
}
return true;
}
function updateStakingQueueConfig(StakingQueueConfig memory _config) internal {
getStorage().queueConfig = _config.compress();
emit IStakingCore.StakingQueueConfigUpdated(_config);
}
function getNextFlushableEpoch() internal view returns (Epoch) {
return getStorage().nextFlushableEpoch.decompress();
}
function getEntryQueueLength() internal view returns (uint256) {
return getStorage().entryQueue.length();
}
function isSlashable(address _attester) internal view returns (bool) {
StakingStorage storage store = getStorage();
Exit storage exit = store.exits[_attester];
if (exit.exists) {
return exit.exitableAt > Timestamp.wrap(block.timestamp);
}
uint256 effectiveBalance = store.gse.effectiveBalanceOf(address(this), _attester);
return effectiveBalance > 0;
}
function getAttesterCountAtTime(Timestamp _timestamp) internal view returns (uint256) {
return getStorage().gse.getAttesterCountAtTime(address(this), _timestamp);
}
function getAttesterAtIndex(uint256 _index) internal view returns (address) {
return getStorage().gse.getAttesterFromIndexAtTime(address(this), _index, Timestamp.wrap(block.timestamp));
}
function getEntryQueueAt(uint256 _index) internal view returns (DepositArgs memory) {
return getStorage().entryQueue.at(_index);
}
function getAttesterFromIndexAtTime(uint256 _index, Timestamp _timestamp) internal view returns (address) {
return getStorage().gse.getAttesterFromIndexAtTime(address(this), _index, _timestamp);
}
function getAttestersFromIndicesAtTime(Timestamp _timestamp, uint256[] memory _indices)
internal
view
returns (address[] memory)
{
return getStorage().gse.getAttestersFromIndicesAtTime(address(this), _timestamp, _indices);
}
function getExit(address _attester) internal view returns (Exit memory) {
return getStorage().exits[_attester];
}
function getConfig(address _attester) internal view returns (AttesterConfig memory) {
return getStorage().gse.getConfig(_attester);
}
function getAttesterView(address _attester) internal view returns (AttesterView memory) {
return AttesterView({
status: getStatus(_attester),
effectiveBalance: getStorage().gse.effectiveBalanceOf(address(this), _attester),
exit: getExit(_attester),
config: getConfig(_attester)
});
}
function getStatus(address _attester) internal view returns (Status) {
Exit memory exit = getExit(_attester);
uint256 effectiveBalance = getStorage().gse.effectiveBalanceOf(address(this), _attester);
Status status;
if (exit.exists) {
status = exit.isRecipient ? Status.EXITING : Status.ZOMBIE;
} else {
status = effectiveBalance > 0 ? Status.VALIDATING : Status.NONE;
}
return status;
}
/**
* @notice Determines the maximum number of validators that could be flushed from the entry queue if there were
* an unlimited number of validators in the queue - this function provides a theoretical limit.
* @dev Implements three-phase validator set management to control initial validator onboarding (called floodgates):
* 1. Bootstrap phase: When no active validators exist, the queue must grow to the bootstrap validator set size
* constant from config before any validators can be flushed. This creates an initial "floodgate" that
* prevents small numbers of validators from activating before reaching the desired bootstrap size.
* 2. Growth phase: Once the bootstrap size is reached, allows a large fixed batch size (bootstrapFlushSize) to
* be flushed at once. This enables the initial large cohort of validators to activate together.
* 3. Normal phase: After the initial bootstrap and growth phases, returns a number proportional to the current
* set size for conservative steady-state growth, unless constrained by configuration (`normalFlushSizeMin`).
*
* All phases are subject to a hard cap of `maxQueueFlushSize`.
*
* The motivation for floodgates is that the whole system starts producing blocks with what is considered
* a sufficiently decentralized set of validators.
*
* Note that Governance has the ability to close the validator set for this instance by setting
* `normalFlushSizeMin` to zero and `normalFlushSizeQuotient` to a very high value. If this is done, this
* function will always return zero and no new validator can enter.
*
* @param _activeAttesterCount - The number of active attesters
* @return - The maximum number of validators that could be flushed from the entry queue.
*/
function getEntryQueueFlushSize(uint256 _activeAttesterCount) internal view returns (uint256) {
StakingStorage storage store = getStorage();
StakingQueueConfig memory config = store.queueConfig.decompress();
uint256 queueSize = store.entryQueue.length();
// Only if there is bootstrap values configured will we look into bootstrap or growth phases.
if (config.bootstrapValidatorSetSize > 0 && !store.isBootstrapped) {
// If bootstrap:
if (_activeAttesterCount == 0 && queueSize < config.bootstrapValidatorSetSize) {
return 0;
}
// If growth:
if (_activeAttesterCount < config.bootstrapValidatorSetSize) {
return config.bootstrapFlushSize;
}
}
// If normal:
return Math.min(
Math.max(_activeAttesterCount / config.normalFlushSizeQuotient, config.normalFlushSizeMin),
config.maxQueueFlushSize
);
}
function getAvailableValidatorFlushes() internal view returns (uint256) {
(uint256 flushes,,) = _calculateAvailableFlushes();
return flushes;
}
function getCachedAvailableValidatorFlushes() internal view returns (uint256) {
return getStorage().availableValidatorFlushes;
}
function getStorage() internal pure returns (StakingStorage storage storageStruct) {
bytes32 position = STAKING_SLOT;
assembly {
storageStruct.slot := position
}
}
function _calculateAvailableFlushes()
private
view
returns (uint256 flushes, Epoch currentEpoch, bool shouldUpdateState)
{
StakingStorage storage store = getStorage();
currentEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp));
if (store.nextFlushableEpoch.decompress() > currentEpoch) {
return (store.availableValidatorFlushes, currentEpoch, false);
}
uint256 activeAttesterCount = getAttesterCountAtTime(Timestamp.wrap(block.timestamp));
uint256 newFlushes = getEntryQueueFlushSize(activeAttesterCount);
return (newFlushes, currentEpoch, true);
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.27;
import {G1Point, G2Point} from "@aztec/shared/libraries/BN254Lib.sol";
import {Errors} from "./Errors.sol";
/**
* @notice A struct containing the arguments needed for GSE.deposit(...) function
* @dev Used to store validator information in the entry queue before they are processed
*/
struct DepositArgs {
address attester;
address withdrawer;
G1Point publicKeyInG1;
G2Point publicKeyInG2;
G1Point proofOfPossession;
bool moveWithLatestRollup;
}
/**
* @notice A queue data structure for managing validator deposits
* @dev Implements a FIFO queue using a mapping and two pointers
* @param validators Mapping from queue index to validator deposit arguments
* @param first Index of the first element in the queue (head)
* @param last Index of the next available slot in the queue (tail)
*/
struct StakingQueue {
mapping(uint256 index => DepositArgs validator) validators;
uint128 first;
uint128 last;
}
library StakingQueueLib {
function init(StakingQueue storage self) internal {
self.first = 1;
self.last = 1;
}
function enqueue(
StakingQueue storage self,
address _attester,
address _withdrawer,
G1Point memory _publicKeyInG1,
G2Point memory _publicKeyInG2,
G1Point memory _proofOfPossession,
bool _moveWithLatestRollup
) internal returns (uint256) {
uint128 queueLocation = self.last;
self.validators[queueLocation] = DepositArgs({
attester: _attester,
withdrawer: _withdrawer,
publicKeyInG1: _publicKeyInG1,
publicKeyInG2: _publicKeyInG2,
proofOfPossession: _proofOfPossession,
moveWithLatestRollup: _moveWithLatestRollup
});
self.last = queueLocation + 1;
return queueLocation;
}
function dequeue(StakingQueue storage self) internal returns (DepositArgs memory validator) {
require(self.last > self.first, Errors.Staking__QueueEmpty());
validator = self.validators[self.first];
self.first += 1;
}
function length(StakingQueue storage self) internal view returns (uint256 len) {
len = self.last - self.first;
}
function at(StakingQueue storage self, uint256 index) internal view returns (DepositArgs memory validator) {
validator = self.validators[self.first + index];
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
struct G1Point {
uint256 x;
uint256 y;
}
struct G2Point {
uint256 x0;
uint256 x1;
uint256 y0;
uint256 y1;
}
/**
* Credit:
* Primary inspiration from https://hackmd.io/7B4nfNShSY2Cjln-9ViQrA, which points out the
* optimization of linking/using a G1 and G2 key and provides an implementation for
* the hashToPoint and sqrt functions.
*/
/**
* Library for registering public keys and computing BLS signatures over the BN254 curve.
* The BN254 curve has been chosen over the BLS12-381 curve for gas efficiency, and
* because the Aztec rollup's security is already reliant on BN254.
*/
library BN254Lib {
/**
* We use uint256[2] for G1 points and uint256[4] for G2 points.
* For G1 points, the expected order is (x, y).
* For G2 points, the expected order is (x_imaginary, x_real, y_imaginary, y_real)
* Using structs would be more readable, but it would be more expensive to use them, particularly
* when aggregating the public keys, since we need to convert to uint256[2] and uint256[4] anyway.
*/
// See bn254_registration.test.ts and BLSKey.t.sol for tests which validate these constants.
uint256 public constant BASE_FIELD_ORDER =
21_888_242_871_839_275_222_246_405_745_257_275_088_696_311_157_297_823_662_689_037_894_645_226_208_583;
uint256 public constant GROUP_ORDER =
21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617;
bytes32 public constant STAKING_DOMAIN_SEPARATOR = bytes32("AZTEC_BLS_POP_BN254_V1");
error AddPointFail();
error MulPointFail();
error GammaZero();
error SqrtFail();
error PairingFail();
error NoPointFound();
error InfinityNotAllowed();
/**
* @notice Prove possession of a secret for a point in G1 and G2.
*
* Ultimately, we want to check:
* - That the caller knows the secret key of pk2 (to prevent rogue-key attacks)
* - That pk1 and pk2 have the same secret key (as an optimization)
*
* Registering two public keys is an optimization: It means we can do G1-only operations
* at the time of verifying a signature, which is much cheaper than G2 operations.
*
* In this function, we check:
* e(signature + gamma * pk1, -G2) * e(hashToPoint(pk1) + gamma * G1, pk2) == 1
*
* Which is effectively a check that:
* e(signature, G2) == e(hashToPoint(pk1), pk2) // a BLS signature over msg = pk1, to prove knowledge of the sk.
* e(pk1, G2) == e(G1, pk2) // a demonstration that pk1 and pk2 have the same sk.
*
* @param pk1 The G1 point of the BLS public key (x, y coordinates)
* @param pk2 The G2 point of the BLS public key (x_1, x_0, y_1, y_0 coordinates)
* @param signature The G1 point that acts as a proof of possession of the private keys corresponding to pk1 and pk2
*/
function proofOfPossession(G1Point memory pk1, G2Point memory pk2, G1Point memory signature)
internal
view
returns (bool)
{
// Ensure that provided points are not infinity
require(!isZero(pk1), InfinityNotAllowed());
require(!isZero(pk2), InfinityNotAllowed());
require(!isZero(signature), InfinityNotAllowed());
// Compute the point "digest" of the pk1 that sigma is a signature over
G1Point memory pk1DigestPoint = g1ToDigestPoint(pk1);
// Random challenge:
// gamma = keccak(pk1, pk2, signature) mod |Fr|
uint256 gamma = gammaOf(pk1, pk2, signature);
require(gamma != 0, GammaZero());
// Build G1 L = signature + gamma * pk1
G1Point memory left = g1Add(signature, g1Mul(pk1, gamma));
// Build G1 R = pk1DigestPoint + gamma * G1
G1Point memory right = g1Add(pk1DigestPoint, g1Mul(g1Generator(), gamma));
// Pairing: e(L, -G2) * e(R, pk2) == 1
return bn254Pairing(left, g2NegatedGenerator(), right, pk2);
}
/// @notice Convert a G1 point (public key) to the digest point that must be signed to prove possession.
/// @dev exposed as public to allow clients not to have implemented the hashToPoint function.
function g1ToDigestPoint(G1Point memory pk1) internal view returns (G1Point memory) {
bytes memory pk1Bytes = abi.encodePacked(pk1.x, pk1.y);
return hashToPoint(STAKING_DOMAIN_SEPARATOR, pk1Bytes);
}
/// @dev Add two points on BN254 G1 (affine coords).
/// Reverts if the inputs are not on‐curve.
function g1Add(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory output) {
uint256[4] memory input;
input[0] = p1.x;
input[1] = p1.y;
input[2] = p2.x;
input[3] = p2.y;
bool success;
assembly {
// call(gas, to, value, in, insize, out, outsize)
// STATICCALL is 40 gas vs 700 gas for CALL
success :=
staticcall(
sub(gas(), 2000),
0x06, // precompile address
input,
0x80, // input size = 4 × 32 bytes
output,
0x40 // output size = 2 × 32 bytes
)
}
if (!success) revert AddPointFail();
return output;
}
/// @dev Multiply a point by a scalar (little‑endian 256‑bit integer).
/// Reverts if the point is not on‐curve or the scalar ≥ p.
function g1Mul(G1Point memory p, uint256 s) internal view returns (G1Point memory output) {
uint256[3] memory input;
input[0] = p.x;
input[1] = p.y;
input[2] = s;
bool success;
assembly {
success :=
staticcall(
sub(gas(), 2000),
0x07, // precompile address
input,
0x60, // input size = 3 × 32 bytes
output,
0x40 // output size = 2 × 32 bytes
)
}
if (!success) revert MulPointFail();
return output;
}
function bn254Pairing(G1Point memory g1a, G2Point memory g2a, G1Point memory g1b, G2Point memory g2b)
internal
view
returns (bool)
{
uint256[12] memory input;
input[0] = g1a.x;
input[1] = g1a.y;
input[2] = g2a.x1;
input[3] = g2a.x0;
input[4] = g2a.y1;
input[5] = g2a.y0;
input[6] = g1b.x;
input[7] = g1b.y;
input[8] = g2b.x1;
input[9] = g2b.x0;
input[10] = g2b.y1;
input[11] = g2b.y0;
uint256[1] memory result;
bool didCallSucceed;
assembly {
didCallSucceed :=
staticcall(
sub(gas(), 2000),
8,
input,
0x180, // input size = 12 * 32 bytes
result,
0x20 // output size = 32 bytes
)
}
require(didCallSucceed, PairingFail());
return result[0] == 1;
}
// The hash to point is based on the "mapToPoint" function in https://www.iacr.org/archive/asiacrypt2001/22480516.pdf
function hashToPoint(bytes32 domain, bytes memory message) internal view returns (G1Point memory output) {
bool found = false;
uint256 attempts = 0;
while (true) {
uint256 x = uint256(keccak256(abi.encode(domain, message, attempts)));
attempts++;
if (x >= BASE_FIELD_ORDER) {
continue;
}
uint256 y = mulmod(x, x, BASE_FIELD_ORDER);
y = mulmod(y, x, BASE_FIELD_ORDER);
y = addmod(y, 3, BASE_FIELD_ORDER);
(y, found) = sqrt(y);
if (found) {
uint256 y0 = y;
uint256 y1 = BASE_FIELD_ORDER - y;
// Ensure that y1 > y0, flip em if necessary
if (y0 > y1) {
(y0, y1) = (y1, y0);
}
uint256 b = uint256(keccak256(abi.encode(domain, message, type(uint256).max)));
if (b & 1 == 0) {
output = G1Point({x: x, y: y0});
} else {
output = G1Point({x: x, y: y1});
}
break;
}
}
require(found, NoPointFound());
return output;
}
function sqrt(uint256 xx) internal view returns (uint256 x, bool hasRoot) {
bool callSuccess;
assembly {
let freeMem := mload(0x40)
mstore(freeMem, 0x20)
mstore(add(freeMem, 0x20), 0x20)
mstore(add(freeMem, 0x40), 0x20)
mstore(add(freeMem, 0x60), xx)
// (N + 1) / 4 = 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52
mstore(add(freeMem, 0x80), 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52)
// N = BASE_FIELD_ORDER
mstore(add(freeMem, 0xA0), BASE_FIELD_ORDER)
callSuccess := staticcall(sub(gas(), 2000), 5, freeMem, 0xC0, freeMem, 0x20)
x := mload(freeMem)
hasRoot := eq(xx, mulmod(x, x, BASE_FIELD_ORDER))
}
require(callSuccess, SqrtFail());
}
/// @notice γ = keccak(PK1, PK2, σ_init) mod Fr
function gammaOf(G1Point memory pk1, G2Point memory pk2, G1Point memory sigmaInit) internal pure returns (uint256) {
return uint256(keccak256(abi.encode(pk1.x, pk1.y, pk2.x0, pk2.x1, pk2.y0, pk2.y1, sigmaInit.x, sigmaInit.y)))
% GROUP_ORDER;
}
function g1Negate(G1Point memory p) internal pure returns (G1Point memory) {
if (p.x == 0 && p.y == 0) {
// Point at infinity remains unchanged
return p;
}
// For a point (x, y), its negation is (x, -y mod p)
// Since we're working in the field Fp, -y mod p = p - y
return G1Point({x: p.x, y: BASE_FIELD_ORDER - p.y});
}
function g1Zero() internal pure returns (G1Point memory) {
return G1Point({x: 0, y: 0});
}
function isZero(G1Point memory p) internal pure returns (bool) {
return p.x == 0 && p.y == 0;
}
function g1Generator() internal pure returns (G1Point memory) {
return G1Point({x: 1, y: 2});
}
function g2Zero() internal pure returns (G2Point memory) {
return G2Point({x0: 0, x1: 0, y0: 0, y1: 0});
}
function isZero(G2Point memory p) internal pure returns (bool) {
return p.x0 == 0 && p.x1 == 0 && p.y0 == 0 && p.y1 == 0;
}
function g2NegatedGenerator() internal pure returns (G2Point memory) {
return G2Point({
x0: 10_857_046_999_023_057_135_944_570_762_232_829_481_370_756_359_578_518_086_990_519_993_285_655_852_781,
x1: 11_559_732_032_986_387_107_991_004_021_392_285_783_925_812_861_821_192_530_917_403_151_452_391_805_634,
y0: 13_392_588_948_715_843_804_641_432_497_768_002_650_278_120_570_034_223_513_918_757_245_338_268_106_653,
y1: 17_805_874_995_975_841_540_914_202_342_111_839_520_379_459_829_704_422_454_583_296_818_431_106_115_052
});
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {BN254Lib, G1Point, G2Point} from "@aztec/shared/libraries/BN254Lib.sol";
import {IBn254LibWrapper} from "./interfaces/IBn254LibWrapper.sol";
contract Bn254LibWrapper is IBn254LibWrapper {
function proofOfPossession(
G1Point memory _publicKeyInG1,
G2Point memory _publicKeyInG2,
G1Point memory _proofOfPossession
) external view override(IBn254LibWrapper) returns (bool) {
return BN254Lib.proofOfPossession(_publicKeyInG1, _publicKeyInG2, _proofOfPossession);
}
function g1ToDigestPoint(G1Point memory pk1) external view override(IBn254LibWrapper) returns (G1Point memory) {
return BN254Lib.g1ToDigestPoint(pk1);
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {
IGovernance,
Proposal,
ProposalState,
Configuration,
ProposeWithLockConfiguration,
Withdrawal
} from "@aztec/governance/interfaces/IGovernance.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
import {Checkpoints, CheckpointedUintLib} from "@aztec/governance/libraries/CheckpointedUintLib.sol";
import {Ballot, CompressedBallot, BallotLib} from "@aztec/governance/libraries/compressed-data/Ballot.sol";
import {
CompressedConfiguration,
CompressedConfigurationLib
} from "@aztec/governance/libraries/compressed-data/Configuration.sol";
import {CompressedProposal, CompressedProposalLib} from "@aztec/governance/libraries/compressed-data/Proposal.sol";
import {ConfigurationLib} from "@aztec/governance/libraries/ConfigurationLib.sol";
import {Errors} from "@aztec/governance/libraries/Errors.sol";
import {ProposalLib, VoteTabulationReturn} from "@aztec/governance/libraries/ProposalLib.sol";
import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";
/**
* @dev a whitelist, controlling who may have power in the governance contract.
* That is, an address must be an approved beneficiary to receive power via `deposit`.
*
* The caveat is that the owner of the contract may open the floodgates, allowing all addresses to hold power.
* This is currently a "one-way-valve", since if it were reopened after being shut,
* the contract is in an odd state where entities are holding power, but not allowed to receive more;
* the whitelist is enabled, but does not reflect the functional entities in the system.
* As an aside, it is unlikely that in the event Governance were opened up to all addresses,
* those same addresses would subsequently vote to close it again.
*
* In practice, it is expected that the only authorized beneficiary will be the GSE.
* This is because all rollup instances deposit their stake into the GSE, which in turn deposits it into the governance
* contract. In turn, it is the GSE that votes on proposals.
*/
struct DepositControl {
mapping(address beneficiary => bool allowed) isAllowed;
bool allBeneficiariesAllowed;
}
/**
* @title Governance
* @author Aztec Labs
* @notice A contract that implements governance logic for proposal creation, voting, and execution.
* Uses a snapshot-based voting model with partial vote support to enable aggregated voting.
*
* Partial vote support: Allows voters to split their voting power across multiple proposals
* or options, rather than using all their votes on a single choice.
*
* Aggregated voting: The contract collects and sums votes from multiple sources or over time,
* combining them to determine the final outcome of each proposal.
*
* @dev KEY CONCEPTS:
*
* **Power**: Funds received via `deposit` are held by Governance and tracked 1:1 as "power" for the beneficiary.
*
* **Proposals**: Payloads containing actions to be executed by governance (excluding calls to the governance ASSET).
*
* **Deposit Control**: A whitelist system controlling who can hold power in governance.
* - Initially restricted to approved beneficiaries (expected to be only the GSE)
* - Can be opened to all addresses via `openFloodgates` (one-way valve)
* - The GSE aggregates stake from all rollup instances and votes on their behalf
*
* **Voting Power**: Based on checkpointed deposit history, calculated per proposal.
*
* @dev PROPOSAL LIFECYCLE: (see `getProposalState` for details)
*
* The current state of a proposal may be retrieved via `getProposalState`.
*
* 1. **Pending** (creation → creation + votingDelay)
* - Proposal exists but voting hasn't started
* - Power snapshot taken at end of this phase
*
* 2. **Active** (pendingThrough + 1 → pendingThrough + votingDuration)
* - Voting open using power from snapshot
* - Multiple partial votes allowed per user
*
* 3. **Vote Evaluation** → Rejected if criteria not met:
* - Minimum quorum (% of total power)
* - Required yea margin (yea votes minus nay votes)
*
* 4. **Queued** (activeThrough + 1 → activeThrough + executionDelay)
* - Timelock period before execution
*
* 5. **Executable** (queuedThrough + 1 → queuedThrough + gracePeriod)
* - Anyone can execute during this window
*
* 6. **Other States**:
* - Executed: Successfully completed
* - Expired: Execution window passed
* - Rejected: Failed voting criteria
* - Droppable: Proposer changed
* - Dropped: Proposal dropped via `dropProposal`
*
* @dev USER FLOW:
*
* 1. **Deposit**: Transfer ASSET to governance for voting power
* - Only whitelisted beneficiaries can hold power
* - Power is checkpointed for historical lookups
*
* 2. **Vote**: Use power from proposal's snapshot timestamp
* - Support partial voting (multiple votes allowed, both yea and nay)
* - A user's total votes may not exceed their power snapshot for the proposal
*
* 3. **Withdraw**: Two-step process with delay
* - Initiate: Reduce power
* - Finalize: Transfer funds after delay expires
* - Standard delay: votingDelay/5 + votingDuration + executionDelay
*
* @dev PROPOSAL CREATION:
*
* - **Standard**: `governanceProposer` calls `propose`
* - **Emergency**: Anyone with sufficient power calls `proposeWithLock`
* - Requires withdrawing `lockAmount` of power with a finalization delay of `lockDelay`
* - Proposal proposer becomes governance itself (cannot be dropped)
*
* @dev CONFIGURATION:
* All timing parameters are controlled by the governance configuration:
* - votingDelay: Buffer before voting opens
* - votingDuration: Voting period length
* - executionDelay: Timelock after voting before the proposal may be executed
* - gracePeriod: Execution window
* - minimumVotes: Absolute minimum voting power in system
* - quorum: Minimum acceptable participation as a percentage of total power
* - requiredYeaMargin: Required difference between yea and nay votes as a percentage of the votes cast
* - lockAmount: The amount of power to withdraw when `proposeWithLock` is called
* - lockDelay: The delay before a withdrawal created by `proposeWithLock` is finalized
*/
contract Governance is IGovernance {
using SafeERC20 for IERC20;
using ProposalLib for CompressedProposal;
using CheckpointedUintLib for Checkpoints.Trace224;
using ConfigurationLib for Configuration;
using ConfigurationLib for CompressedConfiguration;
using CompressedConfigurationLib for CompressedConfiguration;
using CompressedProposalLib for CompressedProposal;
using BallotLib for CompressedBallot;
IERC20 public immutable ASSET;
/**
* @dev The address that is allowed to `propose` new proposals.
*
* This address can only be updated by the governance itself through a proposal.
*/
address public governanceProposer;
/**
* @dev The whitelist of beneficiaries that are allowed to hold power via `deposit`,
* and the flag to allow all beneficiaries to hold power.
*/
DepositControl internal depositControl;
/**
* @dev The proposals that have been made.
*
* The proposal ID is the current count of proposals (see `proposalCount`).
* New proposals are created by calling `_propose`, via `propose` or `proposeWithLock`.
* The storage of a proposal may be modified by calling `vote`, `execute`, or `dropProposal`.
*/
mapping(uint256 proposalId => CompressedProposal proposal) internal proposals;
/**
* @dev The ballots that have been cast for each proposal.
*
* `CompressedBallot`s contain a compressed `yea` and `nay` count (uint128 each packed into uint256),
* which are the number of votes for and against the proposal.
* `ballots` is only updated during `vote`.
*/
mapping(uint256 proposalId => mapping(address user => CompressedBallot ballot)) internal ballots;
/**
* @dev Checkpointed deposit amounts for an address.
*
* `users` is only updated during `deposit`, `initiateWithdraw`, and `proposeWithLock`.
*/
mapping(address userAddress => Checkpoints.Trace224 user) internal users;
/**
* @dev Withdrawals that have been initiated.
*
* `withdrawals` is only updated during `initiateWithdraw`, `proposeWithLock`, and `finalizeWithdraw`.
*/
mapping(uint256 withdrawalId => Withdrawal withdrawal) internal withdrawals;
/**
* @dev The configuration of the governance contract.
*
* `configuration` is set in the constructor, and is only updated during `updateConfiguration`,
* which must be done via a proposal.
*/
CompressedConfiguration internal configuration;
/**
* @dev The total power of the governance contract.
*
* `total` is only updated during `deposit`, `initiateWithdraw`, and `proposeWithLock`.
*/
Checkpoints.Trace224 internal total;
/**
* @dev The count of proposals that have been made.
*
* `proposalCount` is only updated during `_propose`.
*/
uint256 public proposalCount;
/**
* @dev The count of withdrawals that have been initiated.
*
* `withdrawalCount` is only updated during `initiateWithdraw` and `proposeWithLock`.
*/
uint256 public withdrawalCount;
/**
* @dev Modifier to ensure that the caller is the governance contract itself.
*
* 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)));
_;
}
/**
* @dev Modifier to ensure that the beneficiary is allowed to hold power in Governance.
*/
modifier isDepositAllowed(address _beneficiary) {
require(msg.sender != address(this), Errors.Governance__CallerCannotBeSelf());
require(
depositControl.allBeneficiariesAllowed || depositControl.isAllowed[_beneficiary],
Errors.Governance__DepositNotAllowed()
);
_;
}
/**
* @dev the initial _beneficiary is expected to be the GSE or address(0) for anyone
*/
constructor(IERC20 _asset, address _governanceProposer, address _beneficiary, Configuration memory _configuration) {
ASSET = _asset;
governanceProposer = _governanceProposer;
_configuration.assertValid();
configuration = CompressedConfigurationLib.compress(_configuration);
if (_beneficiary == address(0)) {
depositControl.allBeneficiariesAllowed = true;
emit FloodGatesOpened();
} else {
depositControl.allBeneficiariesAllowed = false;
depositControl.isAllowed[_beneficiary] = true;
emit BeneficiaryAdded(_beneficiary);
}
}
/**
* @notice Add a beneficiary to the whitelist.
* @dev The beneficiary may hold power in the governance contract after this call.
* only callable by the governance contract itself.
*
* @param _beneficiary The address to add to the whitelist.
*/
function addBeneficiary(address _beneficiary) external override(IGovernance) onlySelf {
depositControl.isAllowed[_beneficiary] = true;
emit BeneficiaryAdded(_beneficiary);
}
/**
* @notice Allow all addresses to hold power in the governance contract.
* @dev This is a one-way valve.
* only callable by the governance contract itself.
*/
function openFloodgates() external override(IGovernance) onlySelf {
depositControl.allBeneficiariesAllowed = true;
emit FloodGatesOpened();
}
/**
* @notice Update the governance proposer.
* @dev The governance proposer is the address that is allowed to use `propose`.
*
* @dev only callable by the governance contract itself.
*
* @dev causes all proposals proposed by the previous governance proposer to be `Droppable`.
*
* @dev prevents the governance proposer from being set to the governance contract itself.
*
* @param _governanceProposer The new governance proposer.
*/
function updateGovernanceProposer(address _governanceProposer) external override(IGovernance) onlySelf {
require(_governanceProposer != address(this), Errors.Governance__GovernanceProposerCannotBeSelf());
governanceProposer = _governanceProposer;
emit GovernanceProposerUpdated(_governanceProposer);
}
/**
* @notice Update the governance configuration.
* only callable by the governance contract itself.
*
* @dev all existing proposals will use the configuration they were created with.
*/
function updateConfiguration(Configuration memory _configuration) external override(IGovernance) onlySelf {
// This following MUST revert if the configuration is invalid
_configuration.assertValid();
configuration = CompressedConfigurationLib.compress(_configuration);
emit ConfigurationUpdated(Timestamp.wrap(block.timestamp));
}
/**
* @notice Deposit funds into the governance contract, transferring ASSET from msg.sender to the governance contract,
* increasing the power 1:1 of the beneficiary within the governance contract.
*
* @dev The beneficiary must be allowed to hold power in the governance contract,
* according to `depositControl`.
*
* Increments the checkpointed power of the specified beneficiary, and the total power of the governance contract.
*
* Note that anyone may deposit funds into the governance contract, and the only restriction is that
* 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, 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.
*/
function deposit(address _beneficiary, uint256 _amount) external override(IGovernance) isDepositAllowed(_beneficiary) {
ASSET.safeTransferFrom(msg.sender, address(this), _amount);
users[_beneficiary].add(_amount);
total.add(_amount);
emit Deposit(msg.sender, _beneficiary, _amount);
}
/**
* @notice Initiate a withdrawal of funds from the governance contract,
* decreasing the power of the beneficiary within the governance contract.
*
* @dev the withdraw may be finalized by anyone after configuration.getWithdrawalDelay() has passed.
*
* @param _to The address that will receive the funds when the withdrawal is finalized.
* @param _amount The amount of power to reduce, and thus funds to withdraw.
* @return The id of the withdrawal, passed to `finalizeWithdraw`.
*/
function initiateWithdraw(address _to, uint256 _amount) external override(IGovernance) returns (uint256) {
return _initiateWithdraw(msg.sender, _to, _amount, configuration.getWithdrawalDelay());
}
/**
* @notice Finalize a withdrawal of funds from the governance contract,
* transferring ASSET from the governance contract to the recipient specified in the withdrawal.
*
* @dev The withdrawal must not have been claimed, and the delay specified on the withdrawal must have passed.
*
* @param _withdrawalId The id of the withdrawal to finalize.
*/
function finalizeWithdraw(uint256 _withdrawalId) external override(IGovernance) {
Withdrawal storage withdrawal = withdrawals[_withdrawalId];
// This is a sanity check, the `recipient` will only be zero for a non-existent withdrawal, so this avoids
// `finalize`ing non-existent withdrawals. Note, that `_initiateWithdraw` will fail if `_to` is `address(0)`
require(withdrawal.recipient != address(0), Errors.Governance__WithdrawalNotInitiated());
require(!withdrawal.claimed, Errors.Governance__WithdrawalAlreadyClaimed());
require(
Timestamp.wrap(block.timestamp) >= withdrawal.unlocksAt,
Errors.Governance__WithdrawalNotUnlockedYet(Timestamp.wrap(block.timestamp), withdrawal.unlocksAt)
);
withdrawal.claimed = true;
emit WithdrawFinalized(_withdrawalId);
ASSET.safeTransfer(withdrawal.recipient, withdrawal.amount);
}
/**
* @notice Propose a new proposal as the governanceProposer
*
* @dev the state of the proposal may be retrieved via `getProposalState`.
*
* Note that the `proposer` of the proposal is the *current* governanceProposer; if the governanceProposer
* no longer matches the one stored in the proposal, the state of the proposal will be `Droppable`.
*
* @param _proposal The IPayload address, which is a contract that contains the proposed actions to be executed by the
* governance.
* @return The id of the proposal.
*/
function propose(IPayload _proposal) external override(IGovernance) returns (uint256) {
require(
msg.sender == governanceProposer, Errors.Governance__CallerNotGovernanceProposer(msg.sender, governanceProposer)
);
return _propose(_proposal, governanceProposer);
}
/**
* @notice Propose a new proposal by withdrawing an existing amount of power from Governance with a longer delay.
*
* @dev proposals made in this way are identical to those made by the governanceProposer, with the exception
* that the "proposer" stored in the proposal is the address of the governance contract itself,
* which means it will not transition to a "Droppable" state if the governanceProposer changes.
*
* @dev this is intended to only be used in an emergency, where the governanceProposer is compromised.
*
* @dev We don't actually need to check available power here, since if the msg.sender does not have
* sufficient balance, the `_initiateWithdraw` would revert with an underflow.
*
* @param _proposal The IPayload address, which is a contract that contains the proposed actions to be executed by
* the governance.
* @param _to The address that will receive the withdrawn funds when the withdrawal is finalized (see
* `finalizeWithdraw`)
* @return The id of the proposal
*/
function proposeWithLock(IPayload _proposal, address _to) external override(IGovernance) returns (uint256) {
ProposeWithLockConfiguration memory proposeConfig = configuration.getProposeConfig();
_initiateWithdraw(msg.sender, _to, proposeConfig.lockAmount, proposeConfig.lockDelay);
return _propose(_proposal, address(this));
}
/**
* @notice Vote on a proposal.
* @dev The proposal must be `Active` to vote on it.
*
* NOTE: The amount of power to vote is equal to the power of msg.sender at the time
* just before the proposal became active.
*
* The same caller (e.g. the GSE) may `vote` multiple times, voting different ways,
* so long as their total votes are less than or equal to their available power;
* each vote is tracked per proposal, per caller within the `ballots` mapping.
*
* We keep track of the total yea and nay votes as a `summedBallot` on the proposal in storage.
*
* @param _proposalId The id of the proposal to vote on.
* @param _amount The amount of power to vote with, which must be less than the available power.
* @param _support The support of the vote.
*/
function vote(uint256 _proposalId, uint256 _amount, bool _support) external override(IGovernance) {
ProposalState state = getProposalState(_proposalId);
require(state == ProposalState.Active, Errors.Governance__ProposalNotActive());
// 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());
CompressedBallot userBallot = ballots[_proposalId][msg.sender];
uint256 availablePower = userPower - (userBallot.getNay() + userBallot.getYea());
require(_amount <= availablePower, Errors.Governance__InsufficientPower(msg.sender, availablePower, _amount));
CompressedProposal storage proposal = proposals[_proposalId];
if (_support) {
ballots[_proposalId][msg.sender] = userBallot.addYea(_amount);
proposal.addYea(_amount);
} else {
ballots[_proposalId][msg.sender] = userBallot.addNay(_amount);
proposal.addNay(_amount);
}
emit VoteCast(_proposalId, msg.sender, _support, _amount);
}
/**
* @notice Execute a proposal.
* @dev The proposal must be `Executable` to execute it.
* If it is, we mark the proposal as `Executed` and execute the actions,
* simply looping through and calling them.
*
* As far as the individual calls, there are 2 safety measures:
* - The call cannot target the ASSET which underlies the governance contract
* - The call must succeed
*
* @param _proposalId The id of the proposal to execute.
*/
function execute(uint256 _proposalId) external override(IGovernance) {
ProposalState state = getProposalState(_proposalId);
require(state == ProposalState.Executable, Errors.Governance__ProposalNotExecutable());
CompressedProposal storage proposal = proposals[_proposalId];
proposal.cachedState = ProposalState.Executed;
IPayload.Action[] memory actions = proposal.payload.getActions();
for (uint256 i = 0; i < actions.length; i++) {
require(actions[i].target != address(ASSET), Errors.Governance__CannotCallAsset());
// We allow calls to EOAs. If you really want be my guest.
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = actions[i].target.call(actions[i].data);
require(success, Errors.Governance__CallFailed(actions[i].target));
}
emit ProposalExecuted(_proposalId);
}
/**
* @notice Update a proposal to be `Dropped`.
* @dev The proposal must be `Droppable` to mark it permanently as `Dropped`.
* See `getProposalState` for more details.
*
* @param _proposalId The id of the proposal to mark as `Dropped`.
*/
function dropProposal(uint256 _proposalId) external override(IGovernance) {
CompressedProposal storage self = proposals[_proposalId];
require(self.cachedState != ProposalState.Dropped, Errors.Governance__ProposalAlreadyDropped());
require(getProposalState(_proposalId) == ProposalState.Droppable, Errors.Governance__ProposalCannotBeDropped());
self.cachedState = ProposalState.Dropped;
emit ProposalDropped(_proposalId);
}
/**
* @notice Get the power of an address at a given timestamp.
*
* @param _owner The address to get the power of.
* @param _ts The timestamp to get the power at.
* @return The power of the address at the given timestamp.
*/
function powerAt(address _owner, Timestamp _ts) external view override(IGovernance) returns (uint256) {
return users[_owner].valueAt(_ts);
}
/**
* @notice Get the power of an address at the current block timestamp.
*
* Note that `powerNow` with the current block timestamp is NOT STABLE.
*
* For example, imagine a transaction that performs the following:
* 1. deposit
* 2. powerNow
* 3. deposit
* 4. powerNow
*
* The powerNow at 4 will be different from the powerNow at 2.
*
* @param _owner The address to get the power of.
* @return The power of the address at the current block timestamp.
*/
function powerNow(address _owner) external view override(IGovernance) returns (uint256) {
return users[_owner].valueNow();
}
/**
* @notice Get the total power in Governance at a given timestamp.
*
* @param _ts The timestamp to get the power at.
* @return The total power at the given timestamp.
*/
function totalPowerAt(Timestamp _ts) external view override(IGovernance) returns (uint256) {
return total.valueAt(_ts);
}
/**
* @notice Get the total power in Governance at the current block timestamp.
* Note that `powerNow` with the current block timestamp is NOT STABLE.
*
* @return The total power at the current block timestamp.
*/
function totalPowerNow() external view override(IGovernance) returns (uint256) {
return total.valueNow();
}
/**
* @notice Check if an address is permitted to hold power in Governance.
*
* @param _beneficiary The address to check.
* @return True if the address is permitted to hold power in Governance.
*/
function isPermittedInGovernance(address _beneficiary) external view override(IGovernance) returns (bool) {
return depositControl.isAllowed[_beneficiary];
}
/**
* @notice Check if everyone is permitted to hold power in Governance.
*
* @return True if everyone is permitted to hold power in Governance.
*/
function isAllBeneficiariesAllowed() external view override(IGovernance) returns (bool) {
return depositControl.allBeneficiariesAllowed;
}
function getConfiguration() external view override(IGovernance) returns (Configuration memory) {
return configuration.decompress();
}
/**
* @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].decompress();
}
/**
* @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];
}
/**
* @notice Get a user's ballot for a specific proposal.
*
* @dev Returns the uncompressed Ballot struct for external callers.
*
* @param _proposalId The id of the proposal.
* @param _user The address of the user.
* @return The user's ballot with yea and nay votes.
*/
function getBallot(uint256 _proposalId, address _user) external view override(IGovernance) returns (Ballot memory) {
return ballots[_proposalId][_user].decompress();
}
/**
* @notice Get the state of a proposal in the governance system
*
* @dev Determine the current state of a proposal based on timestamps, vote results, and governance configuration.
*
* @dev NB: the state returned here is LOGICAL, and is the "true state" of the proposal:
* it need not match the state of the proposal in storage, which is effectively just a cache.
*
* Flow Logic:
* 1. Check if proposal exists (revert if not)
* 2. If the cached state of the proposal is "stable" (Executed/Dropped), return that state
* 3. Check if governance proposer changed (→ Droppable, unless proposed via lock)
* 4. Time-based state transitions:
* - currentTime ≤ pendingThrough() → Pending
* - currentTime ≤ activeThrough() → Active
* - Vote tabulation check → Rejected if not accepted
* - currentTime ≤ queuedThrough() → Queued
* - currentTime ≤ executableThrough() → Executable
* - Otherwise → Expired
*
* @dev State Descriptions:
* - Pending: Proposal created but voting hasn't started yet
* - Active: Voting is currently open
* - Rejected: Voting closed but proposal didn't meet acceptance criteria
* - Queued: Proposal accepted and waiting for execution window
* - Executable: Proposal can be executed
* - Expired: Execution window has passed
* - Droppable: Proposer changed
* - Dropped: Proposal dropped by calling `dropProposal`
* - Executed: Proposal has been successfully executed
*
* @dev edge case: it is possible that a proposal be "Droppable" according to the logic here,
* but no one called `dropProposal`, and then be in a different state later.
* This can happen if, for whatever reason, the governance proposer stored by this contract changes
* from the one the proposal is made via, (which would cause this function to return `Droppable`),
* but then a separate proposal is executed which restores the original governance proposer.
* So, `Dropped` is permanent, but `Droppable` is not.
*
* @param _proposalId The ID of the proposal to check
* @return The current state of the proposal
*/
function getProposalState(uint256 _proposalId) public view override(IGovernance) returns (ProposalState) {
require(_proposalId < proposalCount, Errors.Governance__ProposalDoesNotExists(_proposalId));
CompressedProposal storage self = proposals[_proposalId];
// A proposal's state is "stable" after `execute` or `dropProposal` has been called on it.
// In this case, the state of the proposal as returned by `getProposalState` is the same as the cached state,
// and the state will not change.
if (self.cachedState == ProposalState.Executed || self.cachedState == ProposalState.Dropped) {
return self.cachedState;
}
// If the governanceProposer has changed, and the proposal did not come through `proposeWithLock`,
// the state of the proposal is `Droppable`.
if (governanceProposer != self.proposer && address(this) != self.proposer) {
return ProposalState.Droppable;
}
Timestamp currentTime = Timestamp.wrap(block.timestamp);
if (currentTime <= self.pendingThrough()) {
return ProposalState.Pending;
}
if (currentTime <= self.activeThrough()) {
return ProposalState.Active;
}
uint256 totalPower = total.valueAt(self.pendingThrough());
(VoteTabulationReturn vtr,) = self.voteTabulation(totalPower);
if (vtr != VoteTabulationReturn.Accepted) {
return ProposalState.Rejected;
}
if (currentTime <= self.queuedThrough()) {
return ProposalState.Queued;
}
if (currentTime <= self.executableThrough()) {
return ProposalState.Executable;
}
return ProposalState.Expired;
}
/**
* @dev reduce the user's power, the total power, and insert a new withdrawal.
*
* The reason for a configurable delay is that `proposeWithLock` creates a withdrawal,
* which has a (presumably) very long delay, whereas `initiateWithdraw` has a much shorter delay.
*
* @param _from The address to reduce the power of.
* @param _to The address to send the funds to.
* @param _amount The amount of power to reduce, and thus funds to withdraw.
* @param _delay The delay before the funds can be withdrawn.
* @return The id of the withdrawal.
*/
function _initiateWithdraw(address _from, address _to, uint256 _amount, Timestamp _delay) internal returns (uint256) {
require(_to != address(0), Errors.Governance__CannotWithdrawToAddressZero());
users[_from].sub(_amount);
total.sub(_amount);
uint256 withdrawalId = withdrawalCount++;
withdrawals[withdrawalId] =
Withdrawal({amount: _amount, unlocksAt: Timestamp.wrap(block.timestamp) + _delay, recipient: _to, claimed: false});
emit WithdrawInitiated(withdrawalId, _to, _amount);
return withdrawalId;
}
/**
* @dev create a new proposal. In it we store:
*
* - 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`
*
* @param _proposal The proposal to propose.
* @param _proposer The address that is proposing the proposal.
* @return The id of the proposal, which is one less than the current count of proposals.
*/
function _propose(IPayload _proposal, address _proposer) internal returns (uint256) {
uint256 proposalId = proposalCount++;
proposals[proposalId] =
CompressedProposalLib.create(_proposer, _proposal, Timestamp.wrap(block.timestamp), configuration);
emit Proposed(proposalId, address(_proposal));
return proposalId;
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
import {Ballot} from "@aztec/governance/libraries/compressed-data/Ballot.sol";
import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
// @notice if this changes, please update the enum in governance.ts
enum ProposalState {
Pending,
Active,
Queued,
Executable,
Rejected,
Executed,
Droppable,
Dropped,
Expired
}
struct ProposeWithLockConfiguration {
Timestamp lockDelay;
uint256 lockAmount;
}
struct Configuration {
ProposeWithLockConfiguration proposeConfig;
Timestamp votingDelay;
Timestamp votingDuration;
Timestamp executionDelay;
Timestamp gracePeriod;
uint256 quorum;
uint256 requiredYeaMargin;
uint256 minimumVotes;
}
// Configuration for proposals - same as Configuration but without proposeConfig
// since proposeConfig is only used for proposeWithLock, not for the proposal itself
struct ProposalConfiguration {
Timestamp votingDelay;
Timestamp votingDuration;
Timestamp executionDelay;
Timestamp gracePeriod;
uint256 quorum;
uint256 requiredYeaMargin;
uint256 minimumVotes;
}
struct Proposal {
ProposalConfiguration config;
ProposalState cachedState;
IPayload payload;
address proposer;
Timestamp creation;
Ballot summedBallot;
}
struct Withdrawal {
uint256 amount;
Timestamp unlocksAt;
address recipient;
bool claimed;
}
interface IGovernance {
event BeneficiaryAdded(address beneficiary);
event FloodGatesOpened();
event Proposed(uint256 indexed proposalId, address indexed proposal);
event VoteCast(uint256 indexed proposalId, address indexed voter, bool support, uint256 amount);
event ProposalExecuted(uint256 indexed proposalId);
event ProposalDropped(uint256 indexed proposalId);
event GovernanceProposerUpdated(address indexed governanceProposer);
event ConfigurationUpdated(Timestamp indexed time);
event Deposit(address indexed depositor, address indexed onBehalfOf, uint256 amount);
event WithdrawInitiated(uint256 indexed withdrawalId, address indexed recipient, uint256 amount);
event WithdrawFinalized(uint256 indexed withdrawalId);
function addBeneficiary(address _beneficiary) external;
function openFloodgates() external;
function updateGovernanceProposer(address _governanceProposer) external;
function updateConfiguration(Configuration memory _configuration) external;
function deposit(address _onBehalfOf, uint256 _amount) external;
function initiateWithdraw(address _to, uint256 _amount) external returns (uint256);
function finalizeWithdraw(uint256 _withdrawalId) external;
function propose(IPayload _proposal) external returns (uint256);
function proposeWithLock(IPayload _proposal, address _to) external returns (uint256);
function vote(uint256 _proposalId, uint256 _amount, bool _support) external;
function execute(uint256 _proposalId) external;
function dropProposal(uint256 _proposalId) external;
function isPermittedInGovernance(address _caller) external view returns (bool);
function isAllBeneficiariesAllowed() external view returns (bool);
function powerAt(address _owner, Timestamp _ts) external view returns (uint256);
function powerNow(address _owner) external view returns (uint256);
function totalPowerAt(Timestamp _ts) external view returns (uint256);
function totalPowerNow() external view returns (uint256);
function getProposalState(uint256 _proposalId) external view returns (ProposalState);
function getConfiguration() external view returns (Configuration memory);
function getProposal(uint256 _proposalId) external view returns (Proposal memory);
function getWithdrawal(uint256 _withdrawalId) external view returns (Withdrawal memory);
function getBallot(uint256 _proposalId, address _user) external view returns (Ballot memory);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Aztec Labs.
pragma solidity >=0.8.27;
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol";
/**
* @notice Structure to store a set of addresses with their historical snapshots
* @param size The timestamped history of the number of addresses in the set
* @param indexToAddressHistory Mapping of index to array of timestamped address history
* @param addressToCurrentIndex Mapping of address to its current index in the set
*/
struct SnapshottedAddressSet {
// This size must also be snapshotted
Checkpoints.Trace224 size;
// For each index, store the timestamped history of addresses
mapping(uint256 index => Checkpoints.Trace224) indexToAddressHistory;
// For each address, store its current index in the set
mapping(address addr => Index index) addressToCurrentIndex;
}
struct Index {
bool exists;
uint224 index;
}
// AddressSnapshotLib
error AddressSnapshotLib__IndexOutOfBounds(uint256 index, uint256 size); // 0xd789b71a
error AddressSnapshotLib__CannotAddAddressZero();
/**
* @title AddressSnapshotLib
* @notice A library for managing a set of addresses with historical snapshots
* @dev This library provides functionality similar to EnumerableSet but can track addresses across time
* and allows querying the state of addresses at any point in time. This is used to track the
* list of stakers on a particular rollup instance in the GSE throughout time.
*
* The SnapshottedAddressSet is maintained such that the you can take a timestamp, and from it:
* 1. Get the `size` of the set at that timestamp
* 2. Query the first `size` indices in `indexToAddressHistory` at that timestamp to get a set of addresses of size
* `size`
*/
library AddressSnapshotLib {
using SafeCast for *;
using Checkpoints for Checkpoints.Trace224;
/**
* @notice Adds a validator to the set
* @param _self The storage reference to the set
* @param _address The address to add
* @return bool True if the address was added, false if it was already present
*/
function add(SnapshottedAddressSet storage _self, address _address) internal returns (bool) {
require(_address != address(0), AddressSnapshotLib__CannotAddAddressZero());
// Prevent against double insertion
if (_self.addressToCurrentIndex[_address].exists) {
return false;
}
uint224 index = _self.size.latest();
_self.addressToCurrentIndex[_address] = Index({exists: true, index: index});
uint32 key = block.timestamp.toUint32();
_self.indexToAddressHistory[index].push(key, uint160(_address).toUint224());
_self.size.push(key, (index + 1).toUint224());
return true;
}
/**
* @notice Removes a address from the set by address
*
* @param _self The storage reference to the set
* @param _address The address of the address to remove
* @return bool True if the address was removed, false if it wasn't found
*/
function remove(SnapshottedAddressSet storage _self, address _address) internal returns (bool) {
Index memory index = _self.addressToCurrentIndex[_address];
if (!index.exists) {
return false;
}
return _remove(_self, index.index, _address);
}
/**
* @notice Removes a validator from the set by index
* @param _self The storage reference to the set
* @param _index The index of the validator to remove
* @return bool True if the validator was removed, reverts otherwise
*/
function remove(SnapshottedAddressSet storage _self, uint224 _index) internal returns (bool) {
address _address = address(_self.indexToAddressHistory[_index].latest().toUint160());
return _remove(_self, _index, _address);
}
/**
* @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) {
uint224 currentSize = _self.size.latest();
if (_index >= currentSize) {
revert AddressSnapshotLib__IndexOutOfBounds(_index, currentSize);
}
// Mark the address to remove as not existing
_self.addressToCurrentIndex[_address] = Index({exists: false, index: 0});
// Now we need to update the indexToAddressHistory.
// Suppose the current size is 3, and we are removing Bob from index 1, and Charlie is at index 2.
// We effectively push Charlie into the snapshot at index 1,
// then update Charlie in addressToCurrentIndex to reflect the new index of 1.
uint224 lastIndex = currentSize - 1;
uint32 key = block.timestamp.toUint32();
// If not removing the last item, swap the value of the last item into the `_index` to remove
if (lastIndex != _index) {
address lastValidator = address(_self.indexToAddressHistory[lastIndex].latest().toUint160());
_self.addressToCurrentIndex[lastValidator] = Index({exists: true, index: _index.toUint224()});
_self.indexToAddressHistory[_index].push(key, uint160(lastValidator).toUint224());
}
// Then "pop" the last index by setting the value to `address(0)`
_self.indexToAddressHistory[lastIndex].push(key, uint224(0));
// Finally, we update the size to reflect the new size of the set.
_self.size.push(key, (lastIndex).toUint224());
return true;
}
/**
* @notice Gets the current address at a specific index at the time right now
* @param _self The storage reference to the set
* @param _index The index to query
* @return address The current address at the given index
*/
function at(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) {
return getAddressFromIndexAtTimestamp(_self, _index, block.timestamp.toUint32());
}
/**
* @notice Gets the address at a specific index and timestamp
* @param _self The storage reference to the set
* @param _index The index to query
* @param _timestamp The timestamp to query
* @return address The address at the given index and timestamp
*/
function getAddressFromIndexAtTimestamp(SnapshottedAddressSet storage _self, uint256 _index, uint32 _timestamp)
internal
view
returns (address)
{
uint256 size = lengthAtTimestamp(_self, _timestamp);
require(_index < size, AddressSnapshotLib__IndexOutOfBounds(_index, size));
// Since the _index is less than the size, we know that the address at _index
// exists at/before _timestamp.
uint224 addr = _self.indexToAddressHistory[_index].upperLookup(_timestamp);
return address(addr.toUint160());
}
/**
* @notice Gets the address at a specific index and timestamp
*
* @dev The caller MUST have ensure that `_index` < `size`
* at the `_timestamp` provided.
* @dev Primed for recent checkpoints in the address history.
*
* @param _self The storage reference to the set
* @param _index The index to query
* @param _timestamp The timestamp to query
* @return address The address at the given index and timestamp
*/
function unsafeGetRecentAddressFromIndexAtTimestamp(
SnapshottedAddressSet storage _self,
uint256 _index,
uint32 _timestamp
) internal view returns (address) {
uint224 addr = _self.indexToAddressHistory[_index].upperLookupRecent(_timestamp);
return address(addr.toUint160());
}
/**
* @notice Gets the current size of the set
* @param _self The storage reference to the set
* @return uint256 The number of addresses in the set
*/
function length(SnapshottedAddressSet storage _self) internal view returns (uint256) {
return lengthAtTimestamp(_self, block.timestamp.toUint32());
}
/**
* @notice Gets the size of the set at a specific timestamp
* @param _self The storage reference to the set
* @param _timestamp The timestamp to query
* @return uint256 The number of addresses in the set at the given timestamp
*
* @dev Note, the values returned from this function are in flux if the timestamp is in the future.
*/
function lengthAtTimestamp(SnapshottedAddressSet storage _self, uint32 _timestamp) internal view returns (uint256) {
return _self.size.upperLookup(_timestamp);
}
/**
* @notice Gets all current addresses in the set
*
* @dev This function is only used in tests.
*
* @param _self The storage reference to the set
* @return address[] Array of all current addresses in the set
*/
function values(SnapshottedAddressSet storage _self) internal view returns (address[] memory) {
return valuesAtTimestamp(_self, block.timestamp.toUint32());
}
/**
* @notice Gets all addresses in the set at a specific timestamp
*
* @dev This function is only used in tests.
*
* @param _self The storage reference to the set
* @param _timestamp The timestamp to query
* @return address[] Array of all addresses in the set at the given timestamp
*
* @dev Note, the values returned from this function are in flux if the timestamp is in the future.
*
*/
function valuesAtTimestamp(SnapshottedAddressSet storage _self, uint32 _timestamp)
internal
view
returns (address[] memory)
{
uint256 size = lengthAtTimestamp(_self, _timestamp);
address[] memory vals = new address[](size);
for (uint256 i; i < size;) {
vals[i] = getAddressFromIndexAtTimestamp(_self, i, _timestamp);
unchecked {
++i;
}
}
return vals;
}
function contains(SnapshottedAddressSet storage _self, address _address) internal view returns (bool) {
return _self.addressToCurrentIndex[_address].exists;
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {Checkpoints, CheckpointedUintLib} from "@aztec/governance/libraries/CheckpointedUintLib.sol";
import {Errors} from "@aztec/governance/libraries/Errors.sol";
import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
// A struct storing balance and delegatee for an attester
struct DepositPosition {
uint256 balance;
address delegatee;
}
// A struct storing all the positions for an instance along with a supply
struct DepositLedger {
mapping(address attester => DepositPosition position) positions;
Checkpoints.Trace224 supply;
}
// A struct storing the voting power used for each proposal for a delegatee
// as well as their checkpointed voting power
struct VotingAccount {
mapping(uint256 proposalId => uint256 powerUsed) powerUsed;
Checkpoints.Trace224 votingPower;
}
// A struct storing the ledgers for the individual rollup instances, the voting
// account for delegatees and the total supply.
struct DepositAndDelegationAccounting {
mapping(address instance => DepositLedger ledger) ledgers;
mapping(address delegatee => VotingAccount votingAccount) votingAccounts;
Checkpoints.Trace224 supply;
}
// This library have a lot of overlap with `Votes.sol` from Openzeppelin,
// It mainly differs as it is a library to allow us having many accountings in the same contract
// the unit of time and allowing multiple uses of power.
library DepositDelegationLib {
using CheckpointedUintLib for Checkpoints.Trace224;
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,
address _attester,
uint256 _amount
) internal {
if (_amount == 0) {
return;
}
DepositLedger storage instance = _self.ledgers[_instance];
instance.positions[_attester].balance += _amount;
moveVotingPower(_self, address(0), instance.positions[_attester].delegatee, _amount);
instance.supply.add(_amount);
_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,
address _attester,
uint256 _amount
) internal {
if (_amount == 0) {
return;
}
DepositLedger storage instance = _self.ledgers[_instance];
instance.positions[_attester].balance -= _amount;
moveVotingPower(_self, instance.positions[_attester].delegatee, address(0), _amount);
instance.supply.sub(_amount);
_self.supply.sub(_amount);
}
/**
* @notice Use `_amount` of `_delegatee`'s voting power on `_proposalId`
* The `_delegatee`'s voting power based on the snapshot at `_timestamp`
*
* @dev If different timestamps are passed, it can cause mismatch in the amount of
* power that can be voted with, so it is very important that it is stable for
* a given `_proposalId`
*
* @param _self - The DelegationDate struct to modify in storage
* @param _delegatee - The delegatee using their power
* @param _proposalId - The id to use for accounting
* @param _timestamp - The timestamp for voting power of the specific `_proposalId`
* @param _amount - The amount of power to use
*/
function usePower(
DepositAndDelegationAccounting storage _self,
address _delegatee,
uint256 _proposalId,
Timestamp _timestamp,
uint256 _amount
) internal {
uint256 powerAt = getVotingPowerAt(_self, _delegatee, _timestamp);
uint256 powerUsed = getPowerUsed(_self, _delegatee, _proposalId);
require(
powerAt >= powerUsed + _amount, Errors.Delegation__InsufficientPower(_delegatee, powerAt, powerUsed + _amount)
);
_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,
address _attester,
address _delegatee
) internal {
address oldDelegate = getDelegatee(_self, _instance, _attester);
if (oldDelegate == _delegatee) {
return;
}
_self.ledgers[_instance].positions[_attester].delegatee = _delegatee;
emit DelegateChanged(_attester, oldDelegate, _delegatee);
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
returns (uint256)
{
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
returns (address)
{
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
returns (uint256)
{
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
returns (uint256)
{
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
returns (uint256)
{
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
{
if (_from == _to || _amount == 0) {
return;
}
if (_from != address(0)) {
(uint256 oldValue, uint256 newValue) = _self.votingAccounts[_from].votingPower.sub(_amount);
emit DelegateVotesChanged(_from, oldValue, newValue);
}
if (_to != address(0)) {
(uint256 oldValue, uint256 newValue) = _self.votingAccounts[_to].votingPower.add(_amount);
emit DelegateVotesChanged(_to, oldValue, newValue);
}
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
import {Slot, Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
/**
* @title Errors Library
* @author Aztec Labs
* @notice Library that contains errors used throughout the Aztec governance
* Errors are prefixed with the contract name to make it easy to identify where the error originated
* when there are multiple contracts that could have thrown the error.
*/
library Errors {
error Governance__CallerNotGovernanceProposer(address caller, address governanceProposer);
error Governance__GovernanceProposerCannotBeSelf();
error Governance__CallerNotSelf(address caller, address self);
error Governance__CallerCannotBeSelf();
error Governance__InsufficientPower(address voter, uint256 have, uint256 required);
error Governance__CannotWithdrawToAddressZero();
error Governance__WithdrawalNotInitiated();
error Governance__WithdrawalAlreadyClaimed();
error Governance__WithdrawalNotUnlockedYet(Timestamp currentTime, Timestamp unlocksAt);
error Governance__ProposalNotActive();
error Governance__ProposalNotExecutable();
error Governance__CannotCallAsset();
error Governance__CallFailed(address target);
error Governance__ProposalDoesNotExists(uint256 proposalId);
error Governance__ProposalAlreadyDropped();
error Governance__ProposalCannotBeDropped();
error Governance__DepositNotAllowed();
error Governance__CheckpointedUintLib__InsufficientValue(address owner, uint256 have, uint256 required);
error Governance__CheckpointedUintLib__NotInPast();
error Governance__ConfigurationLib__InvalidMinimumVotes();
error Governance__ConfigurationLib__LockAmountTooSmall();
error Governance__ConfigurationLib__LockAmountTooBig();
error Governance__ConfigurationLib__QuorumTooSmall();
error Governance__ConfigurationLib__QuorumTooBig();
error Governance__ConfigurationLib__RequiredYeaMarginTooBig();
error Governance__ConfigurationLib__TimeTooSmall(string name);
error Governance__ConfigurationLib__TimeTooBig(string name);
error EmpireBase__FailedToSubmitRoundWinner(IPayload payload);
error EmpireBase__InstanceHaveNoCode(address instance);
error EmpireBase__InsufficientSignals(uint256 signalsCast, uint256 signalsNeeded);
error EmpireBase__InvalidQuorumAndRoundSize(uint256 quorumSize, uint256 roundSize);
error EmpireBase__QuorumCannotBeLargerThanRoundSize(uint256 quorumSize, uint256 roundSize);
error EmpireBase__InvalidLifetimeAndExecutionDelay(uint256 lifetimeInRounds, uint256 executionDelayInRounds);
error EmpireBase__OnlyProposerCanSignal(address caller, address proposer);
error EmpireBase__PayloadAlreadySubmitted(uint256 roundNumber);
error EmpireBase__PayloadCannotBeAddressZero();
error EmpireBase__RoundTooOld(uint256 roundNumber, uint256 currentRoundNumber);
error EmpireBase__RoundTooNew(uint256 roundNumber, uint256 currentRoundNumber);
error EmpireBase__SignalAlreadyCastForSlot(Slot slot);
error GovernanceProposer__GSEPayloadInvalid();
error CoinIssuer__InsufficientMintAvailable(uint256 available, uint256 needed); // 0xa1cc8799
error CoinIssuer__InvalidConfiguration();
error Registry__RollupAlreadyRegistered(address rollup); // 0x3c34eabf
error Registry__RollupNotRegistered(uint256 version);
error Registry__NoRollupsRegistered();
error RewardDistributor__InvalidCaller(address caller, address canonical); // 0xb95e39f6
error GSE__NotRollup(address);
error GSE__GovernanceAlreadySet();
error GSE__InvalidRollupAddress(address);
error GSE__RollupAlreadyRegistered(address);
error GSE__NotLatestRollup(address);
error GSE__AlreadyRegistered(address, address);
error GSE__NothingToExit(address);
error GSE__InsufficientBalance(uint256, uint256);
error GSE__FailedToRemove(address);
error GSE__InstanceDoesNotExist(address);
error GSE__NotWithdrawer(address, address);
error GSE__OutOfBounds(uint256, uint256);
error GSE__FatalError(string);
error GSE__InvalidProofOfPossession();
error GSE__CannotChangePublicKeys(uint256 existingPk1x, uint256 existingPk1y);
error GSE__ProofOfPossessionAlreadySeen(bytes32 hashedPK1);
error Delegation__InsufficientPower(address, uint256, uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/structs/Checkpoints.sol)
// This file was procedurally generated from scripts/generate/templates/Checkpoints.js.
pragma solidity ^0.8.20;
import {Math} from "../math/Math.sol";
/**
* @dev This library defines the `Trace*` struct, for checkpointing values as they change at different points in
* time, and later looking up past values by block number. See {Votes} as an example.
*
* To create a history of checkpoints define a variable type `Checkpoints.Trace*` in your contract, and store a new
* checkpoint for the current transaction block using the {push} function.
*/
library Checkpoints {
/**
* @dev A value was attempted to be inserted on a past checkpoint.
*/
error CheckpointUnorderedInsertion();
struct Trace256 {
Checkpoint256[] _checkpoints;
}
struct Checkpoint256 {
uint256 _key;
uint256 _value;
}
/**
* @dev Pushes a (`key`, `value`) pair into a Trace256 so that it is stored as the checkpoint.
*
* Returns previous value and new value.
*
* IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint256).max` key set will disable the
* library.
*/
function push(
Trace256 storage self,
uint256 key,
uint256 value
) internal returns (uint256 oldValue, uint256 newValue) {
return _insert(self._checkpoints, key, value);
}
/**
* @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if
* there is none.
*/
function lowerLookup(Trace256 storage self, uint256 key) internal view returns (uint256) {
uint256 len = self._checkpoints.length;
uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len);
return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value;
}
/**
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
* if there is none.
*/
function upperLookup(Trace256 storage self, uint256 key) internal view returns (uint256) {
uint256 len = self._checkpoints.length;
uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
* if there is none.
*
* NOTE: This is a variant of {upperLookup} that is optimized to find "recent" checkpoint (checkpoints with high
* keys).
*/
function upperLookupRecent(Trace256 storage self, uint256 key) internal view returns (uint256) {
uint256 len = self._checkpoints.length;
uint256 low = 0;
uint256 high = len;
if (len > 5) {
uint256 mid = len - Math.sqrt(len);
if (key < _unsafeAccess(self._checkpoints, mid)._key) {
high = mid;
} else {
low = mid + 1;
}
}
uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
*/
function latest(Trace256 storage self) internal view returns (uint256) {
uint256 pos = self._checkpoints.length;
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
* in the most recent checkpoint.
*/
function latestCheckpoint(Trace256 storage self) internal view returns (bool exists, uint256 _key, uint256 _value) {
uint256 pos = self._checkpoints.length;
if (pos == 0) {
return (false, 0, 0);
} else {
Checkpoint256 storage ckpt = _unsafeAccess(self._checkpoints, pos - 1);
return (true, ckpt._key, ckpt._value);
}
}
/**
* @dev Returns the number of checkpoints.
*/
function length(Trace256 storage self) internal view returns (uint256) {
return self._checkpoints.length;
}
/**
* @dev Returns checkpoint at given position.
*/
function at(Trace256 storage self, uint32 pos) internal view returns (Checkpoint256 memory) {
return self._checkpoints[pos];
}
/**
* @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
* or by updating the last one.
*/
function _insert(
Checkpoint256[] storage self,
uint256 key,
uint256 value
) private returns (uint256 oldValue, uint256 newValue) {
uint256 pos = self.length;
if (pos > 0) {
Checkpoint256 storage last = _unsafeAccess(self, pos - 1);
uint256 lastKey = last._key;
uint256 lastValue = last._value;
// Checkpoint keys must be non-decreasing.
if (lastKey > key) {
revert CheckpointUnorderedInsertion();
}
// Update or push new checkpoint
if (lastKey == key) {
last._value = value;
} else {
self.push(Checkpoint256({_key: key, _value: value}));
}
return (lastValue, value);
} else {
self.push(Checkpoint256({_key: key, _value: value}));
return (0, value);
}
}
/**
* @dev Return the index of the first (oldest) checkpoint with key strictly bigger than the search key, or `high`
* if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
* `high`.
*
* WARNING: `high` should not be greater than the array's length.
*/
function _upperBinaryLookup(
Checkpoint256[] storage self,
uint256 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid)._key > key) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}
/**
* @dev Return the index of the first (oldest) checkpoint with key greater or equal than the search key, or `high`
* if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
* `high`.
*
* WARNING: `high` should not be greater than the array's length.
*/
function _lowerBinaryLookup(
Checkpoint256[] storage self,
uint256 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid)._key < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}
/**
* @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
*/
function _unsafeAccess(
Checkpoint256[] storage self,
uint256 pos
) private pure returns (Checkpoint256 storage result) {
assembly {
mstore(0x00, self.slot)
result.slot := add(keccak256(0x00, 0x20), mul(pos, 2))
}
}
struct Trace224 {
Checkpoint224[] _checkpoints;
}
struct Checkpoint224 {
uint32 _key;
uint224 _value;
}
/**
* @dev Pushes a (`key`, `value`) pair into a Trace224 so that it is stored as the checkpoint.
*
* Returns previous value and new value.
*
* IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint32).max` key set will disable the
* library.
*/
function push(
Trace224 storage self,
uint32 key,
uint224 value
) internal returns (uint224 oldValue, uint224 newValue) {
return _insert(self._checkpoints, key, value);
}
/**
* @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if
* there is none.
*/
function lowerLookup(Trace224 storage self, uint32 key) internal view returns (uint224) {
uint256 len = self._checkpoints.length;
uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len);
return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value;
}
/**
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
* if there is none.
*/
function upperLookup(Trace224 storage self, uint32 key) internal view returns (uint224) {
uint256 len = self._checkpoints.length;
uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
* if there is none.
*
* NOTE: This is a variant of {upperLookup} that is optimized to find "recent" checkpoint (checkpoints with high
* keys).
*/
function upperLookupRecent(Trace224 storage self, uint32 key) internal view returns (uint224) {
uint256 len = self._checkpoints.length;
uint256 low = 0;
uint256 high = len;
if (len > 5) {
uint256 mid = len - Math.sqrt(len);
if (key < _unsafeAccess(self._checkpoints, mid)._key) {
high = mid;
} else {
low = mid + 1;
}
}
uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
*/
function latest(Trace224 storage self) internal view returns (uint224) {
uint256 pos = self._checkpoints.length;
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
* in the most recent checkpoint.
*/
function latestCheckpoint(Trace224 storage self) internal view returns (bool exists, uint32 _key, uint224 _value) {
uint256 pos = self._checkpoints.length;
if (pos == 0) {
return (false, 0, 0);
} else {
Checkpoint224 storage ckpt = _unsafeAccess(self._checkpoints, pos - 1);
return (true, ckpt._key, ckpt._value);
}
}
/**
* @dev Returns the number of checkpoints.
*/
function length(Trace224 storage self) internal view returns (uint256) {
return self._checkpoints.length;
}
/**
* @dev Returns checkpoint at given position.
*/
function at(Trace224 storage self, uint32 pos) internal view returns (Checkpoint224 memory) {
return self._checkpoints[pos];
}
/**
* @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
* or by updating the last one.
*/
function _insert(
Checkpoint224[] storage self,
uint32 key,
uint224 value
) private returns (uint224 oldValue, uint224 newValue) {
uint256 pos = self.length;
if (pos > 0) {
Checkpoint224 storage last = _unsafeAccess(self, pos - 1);
uint32 lastKey = last._key;
uint224 lastValue = last._value;
// Checkpoint keys must be non-decreasing.
if (lastKey > key) {
revert CheckpointUnorderedInsertion();
}
// Update or push new checkpoint
if (lastKey == key) {
last._value = value;
} else {
self.push(Checkpoint224({_key: key, _value: value}));
}
return (lastValue, value);
} else {
self.push(Checkpoint224({_key: key, _value: value}));
return (0, value);
}
}
/**
* @dev Return the index of the first (oldest) checkpoint with key strictly bigger than the search key, or `high`
* if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
* `high`.
*
* WARNING: `high` should not be greater than the array's length.
*/
function _upperBinaryLookup(
Checkpoint224[] storage self,
uint32 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid)._key > key) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}
/**
* @dev Return the index of the first (oldest) checkpoint with key greater or equal than the search key, or `high`
* if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
* `high`.
*
* WARNING: `high` should not be greater than the array's length.
*/
function _lowerBinaryLookup(
Checkpoint224[] storage self,
uint32 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid)._key < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}
/**
* @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
*/
function _unsafeAccess(
Checkpoint224[] storage self,
uint256 pos
) private pure returns (Checkpoint224 storage result) {
assembly {
mstore(0x00, self.slot)
result.slot := add(keccak256(0x00, 0x20), pos)
}
}
struct Trace208 {
Checkpoint208[] _checkpoints;
}
struct Checkpoint208 {
uint48 _key;
uint208 _value;
}
/**
* @dev Pushes a (`key`, `value`) pair into a Trace208 so that it is stored as the checkpoint.
*
* Returns previous value and new value.
*
* IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint48).max` key set will disable the
* library.
*/
function push(
Trace208 storage self,
uint48 key,
uint208 value
) internal returns (uint208 oldValue, uint208 newValue) {
return _insert(self._checkpoints, key, value);
}
/**
* @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if
* there is none.
*/
function lowerLookup(Trace208 storage self, uint48 key) internal view returns (uint208) {
uint256 len = self._checkpoints.length;
uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len);
return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value;
}
/**
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
* if there is none.
*/
function upperLookup(Trace208 storage self, uint48 key) internal view returns (uint208) {
uint256 len = self._checkpoints.length;
uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
* if there is none.
*
* NOTE: This is a variant of {upperLookup} that is optimized to find "recent" checkpoint (checkpoints with high
* keys).
*/
function upperLookupRecent(Trace208 storage self, uint48 key) internal view returns (uint208) {
uint256 len = self._checkpoints.length;
uint256 low = 0;
uint256 high = len;
if (len > 5) {
uint256 mid = len - Math.sqrt(len);
if (key < _unsafeAccess(self._checkpoints, mid)._key) {
high = mid;
} else {
low = mid + 1;
}
}
uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
*/
function latest(Trace208 storage self) internal view returns (uint208) {
uint256 pos = self._checkpoints.length;
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
* in the most recent checkpoint.
*/
function latestCheckpoint(Trace208 storage self) internal view returns (bool exists, uint48 _key, uint208 _value) {
uint256 pos = self._checkpoints.length;
if (pos == 0) {
return (false, 0, 0);
} else {
Checkpoint208 storage ckpt = _unsafeAccess(self._checkpoints, pos - 1);
return (true, ckpt._key, ckpt._value);
}
}
/**
* @dev Returns the number of checkpoints.
*/
function length(Trace208 storage self) internal view returns (uint256) {
return self._checkpoints.length;
}
/**
* @dev Returns checkpoint at given position.
*/
function at(Trace208 storage self, uint32 pos) internal view returns (Checkpoint208 memory) {
return self._checkpoints[pos];
}
/**
* @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
* or by updating the last one.
*/
function _insert(
Checkpoint208[] storage self,
uint48 key,
uint208 value
) private returns (uint208 oldValue, uint208 newValue) {
uint256 pos = self.length;
if (pos > 0) {
Checkpoint208 storage last = _unsafeAccess(self, pos - 1);
uint48 lastKey = last._key;
uint208 lastValue = last._value;
// Checkpoint keys must be non-decreasing.
if (lastKey > key) {
revert CheckpointUnorderedInsertion();
}
// Update or push new checkpoint
if (lastKey == key) {
last._value = value;
} else {
self.push(Checkpoint208({_key: key, _value: value}));
}
return (lastValue, value);
} else {
self.push(Checkpoint208({_key: key, _value: value}));
return (0, value);
}
}
/**
* @dev Return the index of the first (oldest) checkpoint with key strictly bigger than the search key, or `high`
* if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
* `high`.
*
* WARNING: `high` should not be greater than the array's length.
*/
function _upperBinaryLookup(
Checkpoint208[] storage self,
uint48 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid)._key > key) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}
/**
* @dev Return the index of the first (oldest) checkpoint with key greater or equal than the search key, or `high`
* if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
* `high`.
*
* WARNING: `high` should not be greater than the array's length.
*/
function _lowerBinaryLookup(
Checkpoint208[] storage self,
uint48 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid)._key < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}
/**
* @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
*/
function _unsafeAccess(
Checkpoint208[] storage self,
uint256 pos
) private pure returns (Checkpoint208 storage result) {
assembly {
mstore(0x00, self.slot)
result.slot := add(keccak256(0x00, 0x20), pos)
}
}
struct Trace160 {
Checkpoint160[] _checkpoints;
}
struct Checkpoint160 {
uint96 _key;
uint160 _value;
}
/**
* @dev Pushes a (`key`, `value`) pair into a Trace160 so that it is stored as the checkpoint.
*
* Returns previous value and new value.
*
* IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint96).max` key set will disable the
* library.
*/
function push(
Trace160 storage self,
uint96 key,
uint160 value
) internal returns (uint160 oldValue, uint160 newValue) {
return _insert(self._checkpoints, key, value);
}
/**
* @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if
* there is none.
*/
function lowerLookup(Trace160 storage self, uint96 key) internal view returns (uint160) {
uint256 len = self._checkpoints.length;
uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len);
return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value;
}
/**
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
* if there is none.
*/
function upperLookup(Trace160 storage self, uint96 key) internal view returns (uint160) {
uint256 len = self._checkpoints.length;
uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
* if there is none.
*
* NOTE: This is a variant of {upperLookup} that is optimized to find "recent" checkpoint (checkpoints with high
* keys).
*/
function upperLookupRecent(Trace160 storage self, uint96 key) internal view returns (uint160) {
uint256 len = self._checkpoints.length;
uint256 low = 0;
uint256 high = len;
if (len > 5) {
uint256 mid = len - Math.sqrt(len);
if (key < _unsafeAccess(self._checkpoints, mid)._key) {
high = mid;
} else {
low = mid + 1;
}
}
uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
*/
function latest(Trace160 storage self) internal view returns (uint160) {
uint256 pos = self._checkpoints.length;
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}
/**
* @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
* in the most recent checkpoint.
*/
function latestCheckpoint(Trace160 storage self) internal view returns (bool exists, uint96 _key, uint160 _value) {
uint256 pos = self._checkpoints.length;
if (pos == 0) {
return (false, 0, 0);
} else {
Checkpoint160 storage ckpt = _unsafeAccess(self._checkpoints, pos - 1);
return (true, ckpt._key, ckpt._value);
}
}
/**
* @dev Returns the number of checkpoints.
*/
function length(Trace160 storage self) internal view returns (uint256) {
return self._checkpoints.length;
}
/**
* @dev Returns checkpoint at given position.
*/
function at(Trace160 storage self, uint32 pos) internal view returns (Checkpoint160 memory) {
return self._checkpoints[pos];
}
/**
* @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
* or by updating the last one.
*/
function _insert(
Checkpoint160[] storage self,
uint96 key,
uint160 value
) private returns (uint160 oldValue, uint160 newValue) {
uint256 pos = self.length;
if (pos > 0) {
Checkpoint160 storage last = _unsafeAccess(self, pos - 1);
uint96 lastKey = last._key;
uint160 lastValue = last._value;
// Checkpoint keys must be non-decreasing.
if (lastKey > key) {
revert CheckpointUnorderedInsertion();
}
// Update or push new checkpoint
if (lastKey == key) {
last._value = value;
} else {
self.push(Checkpoint160({_key: key, _value: value}));
}
return (lastValue, value);
} else {
self.push(Checkpoint160({_key: key, _value: value}));
return (0, value);
}
}
/**
* @dev Return the index of the first (oldest) checkpoint with key strictly bigger than the search key, or `high`
* if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
* `high`.
*
* WARNING: `high` should not be greater than the array's length.
*/
function _upperBinaryLookup(
Checkpoint160[] storage self,
uint96 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid)._key > key) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}
/**
* @dev Return the index of the first (oldest) checkpoint with key greater or equal than the search key, or `high`
* if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
* `high`.
*
* WARNING: `high` should not be greater than the array's length.
*/
function _lowerBinaryLookup(
Checkpoint160[] storage self,
uint96 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid)._key < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}
/**
* @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
*/
function _unsafeAccess(
Checkpoint160[] storage self,
uint256 pos
) private pure returns (Checkpoint160 storage result) {
assembly {
mstore(0x00, self.slot)
result.slot := add(keccak256(0x00, 0x20), pos)
}
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
interface IPayload {
struct Action {
address target;
bytes data;
}
/**
* @notice A URI that can be used to refer to where a non-coder human readable description
* of the payload can be found.
*
* @dev Not used in the contracts, so could be any string really
*
* @return - Ideally a useful URI for the payload description
*/
function getURI() external view returns (string memory);
function getActions() external view returns (Action[] memory);
}
interface IGovernance {
event Deposit(address indexed depositor, address indexed onBehalfOf, uint256 amount);
event WithdrawInitiated(uint256 indexed withdrawalId, address indexed recipient, uint256 amount);
event WithdrawFinalized(uint256 indexed withdrawalId);
event Proposed(uint256 indexed proposalId, address indexed proposal);
event VoteCast(uint256 indexed proposalId, address indexed voter, bool support, uint256 amount);
function deposit(address _onBehalfOf, uint256 _amount) external;
function initiateWithdraw(address _to, uint256 _amount) external returns (uint256);
function finalizeWithdraw(uint256 _withdrawalId) external;
function proposeWithLock(IPayload _proposal, address _to) external returns (uint256);
function vote(uint256 _proposalId, uint256 _amount, bool _support) external;
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
/**
* @title Governance Staking Escrow Minimal Interface
* @author Aztec-Labs
* @notice A minimal interface for the Governance Staking Escrow contract
*
* @dev includes only the function that are interacted with from the staker
*/
interface IGSE {
function delegate(address _instance, address _attester, address _delegatee) external;
function ACTIVATION_THRESHOLD() external view returns (uint256);
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {ATPType, IATPCore} from "./../base/IATP.sol";
import {LATP} from "./../linear/LATP.sol";
import {LATPCore, IERC20, IRegistry} from "./../linear/LATPCore.sol";
/**
* @title Non Claimable Linear Aztec Position
* @notice An override of the LATP contract to make it non-claimable.
*/
contract NCATP is LATP {
constructor(IRegistry _registry, IERC20 _token) LATP(_registry, _token) {}
function claim() external override(IATPCore, LATPCore) onlyBeneficiary returns (uint256) {
revert NoClaimable();
}
function getType() external pure override(LATP) returns (ATPType) {
return ATPType.NonClaim;
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {IATPWithdrawableStaker} from "./IATPWithdrawableStaker.sol";
/**
* @title IATPWithdrawableAndClaimableStaker Interface
* @author Aztec-Labs
* @notice Interface for an ATP staker that allows for withdrawals from the rollup
* and enables ATP token holders to claim tokens only after staking has occurred
*/
interface IATPWithdrawableAndClaimableStaker is IATPWithdrawableStaker {
/**
* @notice Withdraw all available tokens to the beneficiary of the ATP
* @dev Only callable if staking has occurred (withdrawable == true)
* @dev Only callable by the operator
*
* Requirements:
* - withdrawable must be true (staking must have occurred)
* - Only operator can call this function
*/
function withdrawAllTokensToBeneficiary() external;
/**
* @notice The timestamp at which withdrawals are enabled.
*/
function WITHDRAWAL_TIMESTAMP() external view returns (uint256);
/**
* @notice Check if staking has occurred
* @return bool indicating whether staking has occurred
*/
function hasStaked() external view returns (bool);
}// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;
import {IPayload} from "src/staking/rollup-system-interfaces/IGovernance.sol";
interface IGovernanceATP {
function depositIntoGovernance(uint256 _amount) external;
function voteInGovernance(uint256 _proposalId, uint256 _amount, bool _support) external;
function initiateWithdrawFromGovernance(uint256 _amount) external returns (uint256);
function proposeWithLock(IPayload _proposal) external returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/LowLevelCall.sol)
pragma solidity ^0.8.20;
/**
* @dev Library of low level call functions that implement different calling strategies to deal with the return data.
*
* WARNING: Using this library requires an advanced understanding of Solidity and how the EVM works. It is recommended
* to use the {Address} library instead.
*/
library LowLevelCall {
/// @dev Performs a Solidity function call using a low level `call` and ignoring the return data.
function callNoReturn(address target, bytes memory data) internal returns (bool success) {
return callNoReturn(target, 0, data);
}
/// @dev Same as {callNoReturn-address-bytes}, but allows specifying the value to be sent in the call.
function callNoReturn(address target, uint256 value, bytes memory data) internal returns (bool success) {
assembly ("memory-safe") {
success := call(gas(), target, value, add(data, 0x20), mload(data), 0x00, 0x00)
}
}
/// @dev Performs a Solidity function call using a low level `call` and returns the first 64 bytes of the result
/// in the scratch space of memory. Useful for functions that return a tuple of single-word values.
///
/// WARNING: Do not assume that the results are zero if `success` is false. Memory can be already allocated
/// and this function doesn't zero it out.
function callReturn64Bytes(
address target,
bytes memory data
) internal returns (bool success, bytes32 result1, bytes32 result2) {
return callReturn64Bytes(target, 0, data);
}
/// @dev Same as {callReturn64Bytes-address-bytes}, but allows specifying the value to be sent in the call.
function callReturn64Bytes(
address target,
uint256 value,
bytes memory data
) internal returns (bool success, bytes32 result1, bytes32 result2) {
assembly ("memory-safe") {
success := call(gas(), target, value, add(data, 0x20), mload(data), 0x00, 0x40)
result1 := mload(0x00)
result2 := mload(0x20)
}
}
/// @dev Performs a Solidity function call using a low level `staticcall` and ignoring the return data.
function staticcallNoReturn(address target, bytes memory data) internal view returns (bool success) {
assembly ("memory-safe") {
success := staticcall(gas(), target, add(data, 0x20), mload(data), 0x00, 0x00)
}
}
/// @dev Performs a Solidity function call using a low level `staticcall` and returns the first 64 bytes of the result
/// in the scratch space of memory. Useful for functions that return a tuple of single-word values.
///
/// WARNING: Do not assume that the results are zero if `success` is false. Memory can be already allocated
/// and this function doesn't zero it out.
function staticcallReturn64Bytes(
address target,
bytes memory data
) internal view returns (bool success, bytes32 result1, bytes32 result2) {
assembly ("memory-safe") {
success := staticcall(gas(), target, add(data, 0x20), mload(data), 0x00, 0x40)
result1 := mload(0x00)
result2 := mload(0x20)
}
}
/// @dev Performs a Solidity function call using a low level `delegatecall` and ignoring the return data.
function delegatecallNoReturn(address target, bytes memory data) internal returns (bool success) {
assembly ("memory-safe") {
success := delegatecall(gas(), target, add(data, 0x20), mload(data), 0x00, 0x00)
}
}
/// @dev Performs a Solidity function call using a low level `delegatecall` and returns the first 64 bytes of the result
/// in the scratch space of memory. Useful for functions that return a tuple of single-word values.
///
/// WARNING: Do not assume that the results are zero if `success` is false. Memory can be already allocated
/// and this function doesn't zero it out.
function delegatecallReturn64Bytes(
address target,
bytes memory data
) internal returns (bool success, bytes32 result1, bytes32 result2) {
assembly ("memory-safe") {
success := delegatecall(gas(), target, add(data, 0x20), mload(data), 0x00, 0x40)
result1 := mload(0x00)
result2 := mload(0x20)
}
}
/// @dev Returns the size of the return data buffer.
function returnDataSize() internal pure returns (uint256 size) {
assembly ("memory-safe") {
size := returndatasize()
}
}
/// @dev Returns a buffer containing the return data from the last call.
function returnData() internal pure returns (bytes memory result) {
assembly ("memory-safe") {
result := mload(0x40)
mstore(result, returndatasize())
returndatacopy(add(result, 0x20), 0x00, returndatasize())
mstore(0x40, add(result, add(0x20, returndatasize())))
}
}
/// @dev Revert with the return data from the last call.
function bubbleRevert() internal pure {
assembly ("memory-safe") {
let fmp := mload(0x40)
returndatacopy(fmp, 0x00, returndatasize())
revert(fmp, returndatasize())
}
}
function bubbleRevert(bytes memory returndata) internal pure {
assembly ("memory-safe") {
revert(add(returndata, 0x20), mload(returndata))
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Currency} from "./Currency.sol";
import {IHooks} from "../interfaces/IHooks.sol";
import {PoolIdLibrary} from "./PoolId.sol";
using PoolIdLibrary for PoolKey global;
/// @notice Returns the key for identifying a pool
struct PoolKey {
/// @notice The lower currency of the pool, sorted numerically
Currency currency0;
/// @notice The higher currency of the pool, sorted numerically
Currency currency1;
/// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000
uint24 fee;
/// @notice Ticks that involve positions must be a multiple of tick spacing
int24 tickSpacing;
/// @notice The hooks of the pool
IHooks hooks;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IDistributionContract
/// @notice Interface for token distribution contracts.
interface IDistributionContract {
/// @notice Error thrown when the token address is invalid
error InvalidToken(address token);
/// @notice Error thrown when the amount received is invalid upon receiving tokens
/// @param expected The expected amount
/// @param received The received amount
error InvalidAmountReceived(uint256 expected, uint256 received);
/// @notice Notify a distribution contract that it has received the tokens to distribute
function onTokensReceived() external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
/**
* @dev PositionInfo is a packed version of solidity structure.
* Using the packaged version saves gas and memory by not storing the structure fields in memory slots.
*
* Layout:
* 200 bits poolId | 24 bits tickUpper | 24 bits tickLower | 8 bits hasSubscriber
*
* Fields in the direction from the least significant bit:
*
* A flag to know if the tokenId is subscribed to an address
* uint8 hasSubscriber;
*
* The tickUpper of the position
* int24 tickUpper;
*
* The tickLower of the position
* int24 tickLower;
*
* The truncated poolId. Truncates a bytes32 value so the most signifcant (highest) 200 bits are used.
* bytes25 poolId;
*
* Note: If more bits are needed, hasSubscriber can be a single bit.
*
*/
type PositionInfo is uint256;
using PositionInfoLibrary for PositionInfo global;
library PositionInfoLibrary {
PositionInfo internal constant EMPTY_POSITION_INFO = PositionInfo.wrap(0);
uint256 internal constant MASK_UPPER_200_BITS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000;
uint256 internal constant MASK_8_BITS = 0xFF;
uint24 internal constant MASK_24_BITS = 0xFFFFFF;
uint256 internal constant SET_UNSUBSCRIBE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00;
uint256 internal constant SET_SUBSCRIBE = 0x01;
uint8 internal constant TICK_LOWER_OFFSET = 8;
uint8 internal constant TICK_UPPER_OFFSET = 32;
/// @dev This poolId is NOT compatible with the poolId used in UniswapV4 core. It is truncated to 25 bytes, and just used to lookup PoolKey in the poolKeys mapping.
function poolId(PositionInfo info) internal pure returns (bytes25 _poolId) {
assembly ("memory-safe") {
_poolId := and(MASK_UPPER_200_BITS, info)
}
}
function tickLower(PositionInfo info) internal pure returns (int24 _tickLower) {
assembly ("memory-safe") {
_tickLower := signextend(2, shr(TICK_LOWER_OFFSET, info))
}
}
function tickUpper(PositionInfo info) internal pure returns (int24 _tickUpper) {
assembly ("memory-safe") {
_tickUpper := signextend(2, shr(TICK_UPPER_OFFSET, info))
}
}
function hasSubscriber(PositionInfo info) internal pure returns (bool _hasSubscriber) {
assembly ("memory-safe") {
_hasSubscriber := and(MASK_8_BITS, info)
}
}
/// @dev this does not actually set any storage
function setSubscribe(PositionInfo info) internal pure returns (PositionInfo _info) {
assembly ("memory-safe") {
_info := or(info, SET_SUBSCRIBE)
}
}
/// @dev this does not actually set any storage
function setUnsubscribe(PositionInfo info) internal pure returns (PositionInfo _info) {
assembly ("memory-safe") {
_info := and(info, SET_UNSUBSCRIBE)
}
}
/// @notice Creates the default PositionInfo struct
/// @dev Called when minting a new position
/// @param _poolKey the pool key of the position
/// @param _tickLower the lower tick of the position
/// @param _tickUpper the upper tick of the position
/// @return info packed position info, with the truncated poolId and the hasSubscriber flag set to false
function initialize(PoolKey memory _poolKey, int24 _tickLower, int24 _tickUpper)
internal
pure
returns (PositionInfo info)
{
bytes25 _poolId = bytes25(PoolId.unwrap(_poolKey.toId()));
assembly {
info :=
or(
or(and(MASK_UPPER_200_BITS, _poolId), shl(TICK_UPPER_OFFSET, and(MASK_24_BITS, _tickUpper))),
shl(TICK_LOWER_OFFSET, and(MASK_24_BITS, _tickLower))
)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ISubscriber} from "./ISubscriber.sol";
/// @title INotifier
/// @notice Interface for the Notifier contract
interface INotifier {
/// @notice Thrown when unsubscribing without a subscriber
error NotSubscribed();
/// @notice Thrown when a subscriber does not have code
error NoCodeSubscriber();
/// @notice Thrown when a user specifies a gas limit too low to avoid valid unsubscribe notifications
error GasLimitTooLow();
/// @notice Wraps the revert message of the subscriber contract on a reverting subscription
error SubscriptionReverted(address subscriber, bytes reason);
/// @notice Wraps the revert message of the subscriber contract on a reverting modify liquidity notification
error ModifyLiquidityNotificationReverted(address subscriber, bytes reason);
/// @notice Wraps the revert message of the subscriber contract on a reverting burn notification
error BurnNotificationReverted(address subscriber, bytes reason);
/// @notice Thrown when a tokenId already has a subscriber
error AlreadySubscribed(uint256 tokenId, address subscriber);
/// @notice Emitted on a successful call to subscribe
event Subscription(uint256 indexed tokenId, address indexed subscriber);
/// @notice Emitted on a successful call to unsubscribe
event Unsubscription(uint256 indexed tokenId, address indexed subscriber);
/// @notice Returns the subscriber for a respective position
/// @param tokenId the ERC721 tokenId
/// @return subscriber the subscriber contract
function subscriber(uint256 tokenId) external view returns (ISubscriber subscriber);
/// @notice Enables the subscriber to receive notifications for a respective position
/// @param tokenId the ERC721 tokenId
/// @param newSubscriber the address of the subscriber contract
/// @param data caller-provided data that's forwarded to the subscriber contract
/// @dev Calling subscribe when a position is already subscribed will revert
/// @dev payable so it can be multicalled with NATIVE related actions
/// @dev will revert if pool manager is locked
function subscribe(uint256 tokenId, address newSubscriber, bytes calldata data) external payable;
/// @notice Removes the subscriber from receiving notifications for a respective position
/// @param tokenId the ERC721 tokenId
/// @dev Callers must specify a high gas limit (remaining gas should be higher than unsubscriberGasLimit) such that the subscriber can be notified
/// @dev payable so it can be multicalled with NATIVE related actions
/// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable.
/// @dev will revert if pool manager is locked
function unsubscribe(uint256 tokenId) external payable;
/// @notice Returns and determines the maximum allowable gas-used for notifying unsubscribe
/// @return uint256 the maximum gas limit when notifying a subscriber's `notifyUnsubscribe` function
function unsubscribeGasLimit() external view returns (uint256);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
/// @title IImmutableState
/// @notice Interface for the ImmutableState contract
interface IImmutableState {
/// @notice The Uniswap v4 PoolManager contract
function poolManager() external view returns (IPoolManager);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IERC721Permit_v4
/// @notice Interface for the ERC721Permit_v4 contract
interface IERC721Permit_v4 {
error SignatureDeadlineExpired();
error NoSelfPermit();
error Unauthorized();
/// @notice Approve of a specific token ID for spending by spender via signature
/// @param spender The account that is being approved
/// @param tokenId The ID of the token that is being approved for spending
/// @param deadline The deadline timestamp by which the call must be mined for the approve to work
/// @param nonce a unique value, for an owner, to prevent replay attacks; an unordered nonce where the top 248 bits correspond to a word and the bottom 8 bits calculate the bit position of the word
/// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v)
/// @dev payable so it can be multicalled with NATIVE related actions
function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature)
external
payable;
/// @notice Set an operator with full permission to an owner's tokens via signature
/// @param owner The address that is setting the operator
/// @param operator The address that will be set as an operator for the owner
/// @param approved The permission to set on the operator
/// @param deadline The deadline timestamp by which the call must be mined for the approve to work
/// @param nonce a unique value, for an owner, to prevent replay attacks; an unordered nonce where the top 248 bits correspond to a word and the bottom 8 bits calculate the bit position of the word
/// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v)
/// @dev payable so it can be multicalled with NATIVE related actions
function permitForAll(
address owner,
address operator,
bool approved,
uint256 deadline,
uint256 nonce,
bytes calldata signature
) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IEIP712_v4
/// @notice Interface for the EIP712 contract
interface IEIP712_v4 {
/// @notice Returns the domain separator for the current chain.
/// @return bytes32 The domain separator
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IMulticall_v4
/// @notice Interface for the Multicall_v4 contract
interface IMulticall_v4 {
/// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
/// @dev The `msg.value` is passed onto all subcalls, even if a previous subcall has consumed the ether.
/// Subcalls can instead use `address(this).value` to see the available ETH, and consume it using {value: x}.
/// @param data The encoded function data for each of the calls to make to this contract
/// @return results The results from each of the calls passed in via data
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
/// @title IPoolInitializer_v4
/// @notice Interface for the PoolInitializer_v4 contract
interface IPoolInitializer_v4 {
/// @notice Initialize a Uniswap v4 Pool
/// @dev If the pool is already initialized, this function will not revert and just return type(int24).max
/// @param key The PoolKey of the pool to initialize
/// @param sqrtPriceX96 The initial starting price of the pool, expressed as a sqrtPriceX96
/// @return The current tick of the pool, or type(int24).max if the pool creation failed, or the pool already existed
function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IUnorderedNonce
/// @notice Interface for the UnorderedNonce contract
interface IUnorderedNonce {
error NonceAlreadyUsed();
/// @notice mapping of nonces consumed by each address, where a nonce is a single bit on the 256-bit bitmap
/// @dev word is at most type(uint248).max
function nonces(address owner, uint256 word) external view returns (uint256);
/// @notice Revoke a nonce by spending it, preventing it from being used again
/// @dev Used in cases where a valid nonce has not been broadcasted onchain, and the owner wants to revoke the validity of the nonce
/// @dev payable so it can be multicalled with native-token related actions
function revokeNonce(uint256 nonce) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
/// @title IPermit2Forwarder
/// @notice Interface for the Permit2Forwarder contract
interface IPermit2Forwarder {
/// @notice allows forwarding a single permit to permit2
/// @dev this function is payable to allow multicall with NATIVE based actions
/// @param owner the owner of the tokens
/// @param permitSingle the permit data
/// @param signature the signature of the permit; abi.encodePacked(r, s, v)
/// @return err the error returned by a reverting permit call, empty if successful
function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature)
external
payable
returns (bytes memory err);
/// @notice allows forwarding batch permits to permit2
/// @dev this function is payable to allow multicall with NATIVE based actions
/// @param owner the owner of the tokens
/// @param _permitBatch a batch of approvals
/// @param signature the signature of the permit; abi.encodePacked(r, s, v)
/// @return err the error returned by a reverting permit call, empty if successful
function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature)
external
payable
returns (bytes memory err);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol";
import {CustomRevert} from "../libraries/CustomRevert.sol";
type Currency is address;
using {greaterThan as >, lessThan as <, greaterThanOrEqualTo as >=, equals as ==} for Currency global;
using CurrencyLibrary for Currency global;
function equals(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(other);
}
function greaterThan(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) > Currency.unwrap(other);
}
function lessThan(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) < Currency.unwrap(other);
}
function greaterThanOrEqualTo(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) >= Currency.unwrap(other);
}
/// @title CurrencyLibrary
/// @dev This library allows for transferring and holding native tokens and ERC20 tokens
library CurrencyLibrary {
/// @notice Additional context for ERC-7751 wrapped error when a native transfer fails
error NativeTransferFailed();
/// @notice Additional context for ERC-7751 wrapped error when an ERC20 transfer fails
error ERC20TransferFailed();
/// @notice A constant to represent the native currency
Currency public constant ADDRESS_ZERO = Currency.wrap(address(0));
function transfer(Currency currency, address to, uint256 amount) internal {
// altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol
// modified custom error selectors
bool success;
if (currency.isAddressZero()) {
assembly ("memory-safe") {
// Transfer the ETH and revert if it fails.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
// revert with NativeTransferFailed, containing the bubbled up error as an argument
if (!success) {
CustomRevert.bubbleUpAndRevertWith(to, bytes4(0), NativeTransferFailed.selector);
}
} else {
assembly ("memory-safe") {
// Get a pointer to some free memory.
let fmp := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=
and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), currency, 0, fmp, 68, 0, 32)
)
// Now clean the memory we used
mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here
mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here
mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here
}
// revert with ERC20TransferFailed, containing the bubbled up error as an argument
if (!success) {
CustomRevert.bubbleUpAndRevertWith(
Currency.unwrap(currency), IERC20Minimal.transfer.selector, ERC20TransferFailed.selector
);
}
}
}
function balanceOfSelf(Currency currency) internal view returns (uint256) {
if (currency.isAddressZero()) {
return address(this).balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(address(this));
}
}
function balanceOf(Currency currency, address owner) internal view returns (uint256) {
if (currency.isAddressZero()) {
return owner.balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(owner);
}
}
function isAddressZero(Currency currency) internal pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO);
}
function toId(Currency currency) internal pure returns (uint256) {
return uint160(Currency.unwrap(currency));
}
// If the upper 12 bytes are non-zero, they will be zero-ed out
// Therefore, fromId() and toId() are not inverses of each other
function fromId(uint256 id) internal pure returns (Currency) {
return Currency.wrap(address(uint160(id)));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol";
import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol";
/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits
/// of the address that the hooks contract is deployed to.
/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400
/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used.
/// See the Hooks library for the full spec.
/// @dev Should only be callable by the v4 PoolManager.
interface IHooks {
/// @notice The hook called before the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @return bytes4 The function selector for the hook
function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4);
/// @notice The hook called after the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @param tick The current tick after the state of a pool is initialized
/// @return bytes4 The function selector for the hook
function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick)
external
returns (bytes4);
/// @notice The hook called before liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeAddLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterAddLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeRemoveLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param delta The caller's balance delta after removing liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterRemoveLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param params The parameters for the swap
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
/// @return uint24 Optionally override the lp fee, only used if three conditions are met: 1. the Pool has a dynamic fee, 2. the value's 2nd highest bit is set (23rd bit, 0x400000), and 3. the value is less than or equal to the maximum fee (1 million)
function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData)
external
returns (bytes4, BeforeSwapDelta, uint24);
/// @notice The hook called after a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param params The parameters for the swap
/// @param delta The amount owed to the caller (positive) or owed to the pool (negative)
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterSwap(
address sender,
PoolKey calldata key,
SwapParams calldata params,
BalanceDelta delta,
bytes calldata hookData
) external returns (bytes4, int128);
/// @notice The hook called before donate
/// @param sender The initial msg.sender for the donate call
/// @param key The key for the pool
/// @param amount0 The amount of token0 being donated
/// @param amount1 The amount of token1 being donated
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after donate
/// @param sender The initial msg.sender for the donate call
/// @param key The key for the pool
/// @param amount0 The amount of token0 being donated
/// @param amount1 The amount of token1 being donated
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function afterDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external returns (bytes4);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Interface for claims over a contract balance, wrapped as a ERC6909
interface IERC6909Claims {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OperatorSet(address indexed owner, address indexed operator, bool approved);
event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount);
/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Owner balance of an id.
/// @param owner The address of the owner.
/// @param id The id of the token.
/// @return amount The balance of the token.
function balanceOf(address owner, uint256 id) external view returns (uint256 amount);
/// @notice Spender allowance of an id.
/// @param owner The address of the owner.
/// @param spender The address of the spender.
/// @param id The id of the token.
/// @return amount The allowance of the token.
function allowance(address owner, address spender, uint256 id) external view returns (uint256 amount);
/// @notice Checks if a spender is approved by an owner as an operator
/// @param owner The address of the owner.
/// @param spender The address of the spender.
/// @return approved The approval status.
function isOperator(address owner, address spender) external view returns (bool approved);
/// @notice Transfers an amount of an id from the caller to a receiver.
/// @param receiver The address of the receiver.
/// @param id The id of the token.
/// @param amount The amount of the token.
/// @return bool True, always, unless the function reverts
function transfer(address receiver, uint256 id, uint256 amount) external returns (bool);
/// @notice Transfers an amount of an id from a sender to a receiver.
/// @param sender The address of the sender.
/// @param receiver The address of the receiver.
/// @param id The id of the token.
/// @param amount The amount of the token.
/// @return bool True, always, unless the function reverts
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external returns (bool);
/// @notice Approves an amount of an id to a spender.
/// @param spender The address of the spender.
/// @param id The id of the token.
/// @param amount The amount of the token.
/// @return bool True, always
function approve(address spender, uint256 id, uint256 amount) external returns (bool);
/// @notice Sets or removes an operator for the caller.
/// @param operator The address of the operator.
/// @param approved The approval status.
/// @return bool True, always
function setOperator(address operator, bool approved) external returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Currency} from "../types/Currency.sol";
import {PoolId} from "../types/PoolId.sol";
import {PoolKey} from "../types/PoolKey.sol";
/// @notice Interface for all protocol-fee related functions in the pool manager
interface IProtocolFees {
/// @notice Thrown when protocol fee is set too high
error ProtocolFeeTooLarge(uint24 fee);
/// @notice Thrown when collectProtocolFees or setProtocolFee is not called by the controller.
error InvalidCaller();
/// @notice Thrown when collectProtocolFees is attempted on a token that is synced.
error ProtocolFeeCurrencySynced();
/// @notice Emitted when the protocol fee controller address is updated in setProtocolFeeController.
event ProtocolFeeControllerUpdated(address indexed protocolFeeController);
/// @notice Emitted when the protocol fee is updated for a pool.
event ProtocolFeeUpdated(PoolId indexed id, uint24 protocolFee);
/// @notice Given a currency address, returns the protocol fees accrued in that currency
/// @param currency The currency to check
/// @return amount The amount of protocol fees accrued in the currency
function protocolFeesAccrued(Currency currency) external view returns (uint256 amount);
/// @notice Sets the protocol fee for the given pool
/// @param key The key of the pool to set a protocol fee for
/// @param newProtocolFee The fee to set
function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external;
/// @notice Sets the protocol fee controller
/// @param controller The new protocol fee controller
function setProtocolFeeController(address controller) external;
/// @notice Collects the protocol fees for a given recipient and currency, returning the amount collected
/// @dev This will revert if the contract is unlocked
/// @param recipient The address to receive the protocol fees
/// @param currency The currency to withdraw
/// @param amount The amount of currency to withdraw
/// @return amountCollected The amount of currency successfully withdrawn
function collectProtocolFees(address recipient, Currency currency, uint256 amount)
external
returns (uint256 amountCollected);
/// @notice Returns the current protocol fee controller address
/// @return address The current protocol fee controller address
function protocolFeeController() external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {SafeCast} from "../libraries/SafeCast.sol";
/// @dev Two `int128` values packed into a single `int256` where the upper 128 bits represent the amount0
/// and the lower 128 bits represent the amount1.
type BalanceDelta is int256;
using {add as +, sub as -, eq as ==, neq as !=} for BalanceDelta global;
using BalanceDeltaLibrary for BalanceDelta global;
using SafeCast for int256;
function toBalanceDelta(int128 _amount0, int128 _amount1) pure returns (BalanceDelta balanceDelta) {
assembly ("memory-safe") {
balanceDelta := or(shl(128, _amount0), and(sub(shl(128, 1), 1), _amount1))
}
}
function add(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) {
int256 res0;
int256 res1;
assembly ("memory-safe") {
let a0 := sar(128, a)
let a1 := signextend(15, a)
let b0 := sar(128, b)
let b1 := signextend(15, b)
res0 := add(a0, b0)
res1 := add(a1, b1)
}
return toBalanceDelta(res0.toInt128(), res1.toInt128());
}
function sub(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) {
int256 res0;
int256 res1;
assembly ("memory-safe") {
let a0 := sar(128, a)
let a1 := signextend(15, a)
let b0 := sar(128, b)
let b1 := signextend(15, b)
res0 := sub(a0, b0)
res1 := sub(a1, b1)
}
return toBalanceDelta(res0.toInt128(), res1.toInt128());
}
function eq(BalanceDelta a, BalanceDelta b) pure returns (bool) {
return BalanceDelta.unwrap(a) == BalanceDelta.unwrap(b);
}
function neq(BalanceDelta a, BalanceDelta b) pure returns (bool) {
return BalanceDelta.unwrap(a) != BalanceDelta.unwrap(b);
}
/// @notice Library for getting the amount0 and amount1 deltas from the BalanceDelta type
library BalanceDeltaLibrary {
/// @notice A BalanceDelta of 0
BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0);
function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) {
assembly ("memory-safe") {
_amount0 := sar(128, balanceDelta)
}
}
function amount1(BalanceDelta balanceDelta) internal pure returns (int128 _amount1) {
assembly ("memory-safe") {
_amount1 := signextend(15, balanceDelta)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "./PoolKey.sol";
type PoolId is bytes32;
/// @notice Library for computing the ID of a pool
library PoolIdLibrary {
/// @notice Returns value equal to keccak256(abi.encode(poolKey))
function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) {
assembly ("memory-safe") {
// 0xa0 represents the total size of the poolKey struct (5 slots of 32 bytes)
poolId := keccak256(poolKey, 0xa0)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Interface for functions to access any storage slot in a contract
interface IExtsload {
/// @notice Called by external contracts to access granular pool state
/// @param slot Key of slot to sload
/// @return value The value of the slot as bytes32
function extsload(bytes32 slot) external view returns (bytes32 value);
/// @notice Called by external contracts to access granular pool state
/// @param startSlot Key of slot to start sloading from
/// @param nSlots Number of slots to load into return value
/// @return values List of loaded values.
function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes32[] memory values);
/// @notice Called by external contracts to access sparse pool state
/// @param slots List of slots to SLOAD from.
/// @return values List of loaded values.
function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory values);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @notice Interface for functions to access any transient storage slot in a contract
interface IExttload {
/// @notice Called by external contracts to access transient storage of the contract
/// @param slot Key of slot to tload
/// @return value The value of the slot as bytes32
function exttload(bytes32 slot) external view returns (bytes32 value);
/// @notice Called by external contracts to access sparse transient pool state
/// @param slots List of slots to tload
/// @return values List of loaded values
function exttload(bytes32[] calldata slots) external view returns (bytes32[] memory values);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
/// @notice Parameter struct for `ModifyLiquidity` pool operations
struct ModifyLiquidityParams {
// the lower and upper tick of the position
int24 tickLower;
int24 tickUpper;
// how to modify the liquidity
int256 liquidityDelta;
// a value to set if you want unique liquidity positions at the same range
bytes32 salt;
}
/// @notice Parameter struct for `Swap` pool operations
struct SwapParams {
/// Whether to swap token0 for token1 or vice versa
bool zeroForOne;
/// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut)
int256 amountSpecified;
/// The sqrt price at which, if reached, the swap will stop executing
uint160 sqrtPriceLimitX96;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (proxy/beacon/IBeacon.sol)
pragma solidity >=0.4.16;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {UpgradeableBeacon} will check that this address is a contract.
*/
function implementation() external view returns (address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1967.sol)
pragma solidity >=0.4.11;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC-1967 implementation slot:
* ```solidity
* contract ERC1967 {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct Int256Slot {
int256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Int256Slot` with member `value` located at `slot`.
*/
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
/**
* @dev Returns a `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
type SlashRound is uint256;
function addSlashRound(SlashRound _a, SlashRound _b) pure returns (SlashRound) {
return SlashRound.wrap(SlashRound.unwrap(_a) + SlashRound.unwrap(_b));
}
function subSlashRound(SlashRound _a, SlashRound _b) pure returns (SlashRound) {
return SlashRound.wrap(SlashRound.unwrap(_a) - SlashRound.unwrap(_b));
}
function eqSlashRound(SlashRound _a, SlashRound _b) pure returns (bool) {
return SlashRound.unwrap(_a) == SlashRound.unwrap(_b);
}
function neqSlashRound(SlashRound _a, SlashRound _b) pure returns (bool) {
return SlashRound.unwrap(_a) != SlashRound.unwrap(_b);
}
function ltSlashRound(SlashRound _a, SlashRound _b) pure returns (bool) {
return SlashRound.unwrap(_a) < SlashRound.unwrap(_b);
}
function lteSlashRound(SlashRound _a, SlashRound _b) pure returns (bool) {
return SlashRound.unwrap(_a) <= SlashRound.unwrap(_b);
}
function gtSlashRound(SlashRound _a, SlashRound _b) pure returns (bool) {
return SlashRound.unwrap(_a) > SlashRound.unwrap(_b);
}
function gteSlashRound(SlashRound _a, SlashRound _b) pure returns (bool) {
return SlashRound.unwrap(_a) >= SlashRound.unwrap(_b);
}
using {
addSlashRound as +,
subSlashRound as -,
eqSlashRound as ==,
neqSlashRound as !=,
ltSlashRound as <,
lteSlashRound as <=,
gtSlashRound as >,
gteSlashRound as >=
} for SlashRound global;
type CompressedSlashRound is uint32;
library CompressedSlashRoundMath {
function compress(SlashRound _round) internal pure returns (CompressedSlashRound) {
return CompressedSlashRound.wrap(SafeCast.toUint32(SlashRound.unwrap(_round)));
}
function decompress(CompressedSlashRound _round) internal pure returns (SlashRound) {
return SlashRound.wrap(uint256(CompressedSlashRound.unwrap(_round)));
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {Constants} from "@aztec/core/libraries/ConstantsGen.sol";
import {Hash} from "@aztec/core/libraries/crypto/Hash.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
/**
* @title BlobLib - Blob Management and Validation Library
* @author Aztec Labs
* @notice Core library for handling blob operations, validation, and commitment management in the Aztec rollup.
*
* @dev This library provides functionality for managing blobs:
* - Blob hash retrieval and validation against EIP-4844 specifications
* - Blob commitment verification and batched blob proof validation
* - Blob base fee retrieval for transaction cost calculations
* - Accumulated blob commitments hash calculation for epoch proofs
*
* VM_ADDRESS:
* The VM_ADDRESS (0x7109709ECfa91a80626fF3989D68f67F5b1DD12D) is a special address used to detect
* when the contract is running in a Foundry test environment. This address is derived from
* keccak256("hevm cheat code") and corresponds to Foundry's VM contract that provides testing utilities.
* When block.chainid == 31337 && VM_ADDRESS.code.length > 0, it indicates we're in a test environment,
* allowing the library to:
* - Use Foundry's getBlobBaseFee() cheatcode instead of block.blobbasefee
* - Use Foundry's getBlobhashes() cheatcode instead of the blobhash() opcode
* This enables comprehensive testing of blob functionality without requiring actual blob transactions.
*
* Blob Validation Flow:
* 1. validateBlobs() processes L2 block blob data, extracting commitments and validating against real blobs
* 2. calculateBlobCommitmentsHash() accumulates commitments across an epoch for rollup circuit validation
* 3. validateBatchedBlob() verifies batched blob proofs using the EIP-4844 point evaluation precompile
* 4. calculateBlobHash() computes versioned hashes from commitments following EIP-4844 specification
*/
library BlobLib {
uint256 internal constant VERSIONED_HASH_VERSION_KZG =
0x0100000000000000000000000000000000000000000000000000000000000000; // 0x01 << 248 to be used in blobHashCheck
/**
* @notice Get the blob base fee
*
* @return uint256 - The blob base fee
*/
function getBlobBaseFee() internal view returns (uint256) {
return block.blobbasefee;
}
/**
* @notice Get the blob hash
*
* @return blobHash - The blob hash
*/
function getBlobHash(uint256 _index) internal view returns (bytes32 blobHash) {
assembly {
blobHash := blobhash(_index)
}
}
/**
* @notice Validate an L2 block's blobs and return the blobHashes, the hashed blobHashes, and blob commitments.
*
* We assume that the Aztec related blobs will be first in the propose transaction, additional blobs can be
* at the end.
*
* Input bytes:
* input[0] - num blobs in block
* input[1:] - blob commitments (48 bytes * num blobs in block)
* @param _blobsInput - The above bytes to verify our input blob commitments match real blobs
* @param _checkBlob - Whether to skip blob related checks. Hardcoded to true (See RollupCore.sol -> checkBlob),
* exists only to be overridden in tests.
*
* Returns for proposal:
* @return blobHashes - All of the blob hashes included in this block, to be emitted in L2BlockProposed event.
* @return blobsHashesCommitment - A hash of all blob hashes in this block, to be included in the block header. See
* comment at the end of this fn for more info.
* @return blobCommitments - All of the blob commitments included in this block, to be stored then validated against
* those used in the rollup in epoch proof verification.
*/
function validateBlobs(bytes calldata _blobsInput, bool _checkBlob)
internal
view
returns (bytes32[] memory blobHashes, bytes32 blobsHashesCommitment, bytes[] memory blobCommitments)
{
// We cannot input the incorrect number of blobs below, as the blobsHash
// and epoch proof verification will fail.
uint8 numBlobs = uint8(_blobsInput[0]);
require(numBlobs > 0, Errors.Rollup__NoBlobsInBlock());
blobHashes = new bytes32[](numBlobs);
blobCommitments = new bytes[](numBlobs);
bytes32 blobHash;
// Add 1 for the numBlobs prefix
uint256 blobInputStart = 1;
for (uint256 i = 0; i < numBlobs; i++) {
// Commitments = arrays of bytes48 compressed points
blobCommitments[i] =
abi.encodePacked(_blobsInput[blobInputStart:blobInputStart + Constants.BLS12_POINT_COMPRESSED_BYTES]);
blobInputStart += Constants.BLS12_POINT_COMPRESSED_BYTES;
bytes32 blobHashCheck = calculateBlobHash(blobCommitments[i]);
if (_checkBlob) {
blobHash = getBlobHash(i);
// The below check ensures that our injected blobCommitments indeed match the real
// blobs submitted with this block. They are then used in the blobCommitmentsHash (see below).
require(blobHash == blobHashCheck, Errors.Rollup__InvalidBlobHash(blobHash, blobHashCheck));
} else {
blobHash = blobHashCheck;
}
blobHashes[i] = blobHash;
}
// Hash the EVM blob hashes for the block header
// TODO(#13430): The below blobsHashesCommitment known as blobsHash elsewhere in the code. The name
// blobsHashesCommitment is confusingly similar to blobCommitmentsHash
// which are different values:
// - blobsHash := sha256([blobhash_0, ..., blobhash_m]) = a hash of all blob hashes in a block with m+1 blobs
// inserted into the header, exists so a user can cross check blobs.
// - blobCommitmentsHash := sha256( ...sha256(sha256(C_0), C_1) ... C_n) = iteratively calculated hash of all blob
// commitments in an epoch with n+1 blobs (see calculateBlobCommitmentsHash()),
// exists so we can validate injected commitments to the rollup circuits correspond to the correct real blobs.
// We may be able to combine these values e.g. blobCommitmentsHash := sha256( ...sha256(sha256(blobshash_0),
// blobshash_1) ... blobshash_l) for an epoch with l+1 blocks.
blobsHashesCommitment = Hash.sha256ToField(abi.encodePacked(blobHashes));
}
/**
* @notice Validate a batched blob.
* Input bytes:
* input[:32] - versioned_hash - NB for a batched blob, this is simply the versioned hash of the batched
* commitment
* input[32:64] - z = poseidon2( ...poseidon2(poseidon2(z_0, z_1), z_2) ... z_n)
* input[64:96] - y = y_0 + gamma * y_1 + gamma^2 * y_2 + ... + gamma^n * y_n
* input[96:144] - commitment C = C_0 + gamma * C_1 + gamma^2 * C_2 + ... + gamma^n * C_n
* input[144:192] - proof (a commitment to the quotient polynomial q(X)) = Q_0 + gamma * Q_1 + gamma^2 * Q_2 + ... +
* gamma^n * Q_n
* @param _blobInput - The above bytes to verify a batched blob
*
* If this function passes where the values of z, y, and C are valid public inputs to the final epoch root proof, then
* we know that the data in each blob of the epoch corresponds to the tx effects of all our proven txs in the epoch.
*
* The rollup circuits calculate each z_i and y_i as above, so if this function passes but they do not match the
* values from the circuit, then proof verification will fail.
*
* Each commitment C_i is injected into the circuits and their correctness is validated using the blobCommitmentsHash,
* as explained below in calculateBlobCommitmentsHash().
*
*/
function validateBatchedBlob(bytes calldata _blobInput) internal view returns (bool success) {
// Staticcall the point eval precompile https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile :
(success,) = address(0x0a).staticcall(_blobInput);
require(success, Errors.Rollup__InvalidBlobProof(bytes32(_blobInput[0:32])));
}
/**
* @notice Calculate the current state of the blobCommitmentsHash. Called for each new proposed block.
* @param _previousBlobCommitmentsHash - The previous block's blobCommitmentsHash.
* @param _blobCommitments - The commitments corresponding to this block's blobs.
* @param _isFirstBlockOfEpoch - Whether this block is the first of an epoch (see below).
*
* The blobCommitmentsHash is an accumulated value calculated in the rollup circuits as:
* blobCommitmentsHash_i := sha256(blobCommitmentsHash_(i - 1), C_i)
* for each blob commitment C_i in an epoch. For the first blob in the epoch (i = 0):
* blobCommitmentsHash_i := sha256(C_0)
* which is why we require _isFirstBlockOfEpoch here.
*
* Each blob commitment is injected into the rollup circuits and we rely on the L1 contracts to validate
* these commitments correspond to real blobs. The input _blobCommitments below come from validateBlobs()
* so we know they are valid commitments here.
*
* We recalculate the same blobCommitmentsHash (which encompasses all claimed blobs in the epoch)
* as in the rollup circuits, then use the final value as a public input to the root rollup proof
* verification in EpochProofLib.sol.
*
* If the proof verifies, we know that the injected commitments used in the rollup circuits match
* the real commitments to L1 blobs.
*
*/
function calculateBlobCommitmentsHash(
bytes32 _previousBlobCommitmentsHash,
bytes[] memory _blobCommitments,
bool _isFirstBlockOfEpoch
) internal pure returns (bytes32 currentBlobCommitmentsHash) {
uint256 i = 0;
currentBlobCommitmentsHash = _previousBlobCommitmentsHash;
// If we are at the first block of an epoch, we reinitialize the blobCommitmentsHash.
// Blob commitments are collected and proven per root rollup proof => per epoch.
if (_isFirstBlockOfEpoch) {
// Initialize the blobCommitmentsHash
currentBlobCommitmentsHash = Hash.sha256ToField(abi.encodePacked(_blobCommitments[i++]));
}
for (i; i < _blobCommitments.length; i++) {
currentBlobCommitmentsHash = Hash.sha256ToField(abi.encodePacked(currentBlobCommitmentsHash, _blobCommitments[i]));
}
}
/**
* @notice Calculate the expected blob hash given a blob commitment
* @dev TODO(#14646): Use kzg_to_versioned_hash & VERSIONED_HASH_VERSION_KZG
* Until we use an external kzg_to_versioned_hash(), calculating it here:
* EIP-4844 spec blobhash is 32 bytes: [version, ...sha256(commitment)[1:32]]
* The version = VERSIONED_HASH_VERSION_KZG, currently 0x01.
* @param _blobCommitment - The 48 byte blob commitment
* @return bytes32 - The blob hash
*/
function calculateBlobHash(bytes memory _blobCommitment) internal pure returns (bytes32) {
return bytes32(
(uint256(sha256(_blobCommitment)) & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
| VERSIONED_HASH_VERSION_KZG
);
}
}// Automatically @generated by scripts/vm.py. Do not modify manually.
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.2 <0.9.0;
pragma experimental ABIEncoderV2;
/// The `VmSafe` interface does not allow manipulation of the EVM state or other actions that may
/// result in Script simulations differing from on-chain execution. It is recommended to only use
/// these cheats in scripts.
interface VmSafe {
/// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`.
enum CallerMode {
// No caller modification is currently active.
None,
// A one time broadcast triggered by a `vm.broadcast()` call is currently active.
Broadcast,
// A recurrent broadcast triggered by a `vm.startBroadcast()` call is currently active.
RecurrentBroadcast,
// A one time prank triggered by a `vm.prank()` call is currently active.
Prank,
// A recurrent prank triggered by a `vm.startPrank()` call is currently active.
RecurrentPrank
}
/// The kind of account access that occurred.
enum AccountAccessKind {
// The account was called.
Call,
// The account was called via delegatecall.
DelegateCall,
// The account was called via callcode.
CallCode,
// The account was called via staticcall.
StaticCall,
// The account was created.
Create,
// The account was selfdestructed.
SelfDestruct,
// Synthetic access indicating the current context has resumed after a previous sub-context (AccountAccess).
Resume,
// The account's balance was read.
Balance,
// The account's codesize was read.
Extcodesize,
// The account's codehash was read.
Extcodehash,
// The account's code was copied.
Extcodecopy
}
/// Forge execution contexts.
enum ForgeContext {
// Test group execution context (test, coverage or snapshot).
TestGroup,
// `forge test` execution context.
Test,
// `forge coverage` execution context.
Coverage,
// `forge snapshot` execution context.
Snapshot,
// Script group execution context (dry run, broadcast or resume).
ScriptGroup,
// `forge script` execution context.
ScriptDryRun,
// `forge script --broadcast` execution context.
ScriptBroadcast,
// `forge script --resume` execution context.
ScriptResume,
// Unknown `forge` execution context.
Unknown
}
/// The transaction type (`txType`) of the broadcast.
enum BroadcastTxType {
// Represents a CALL broadcast tx.
Call,
// Represents a CREATE broadcast tx.
Create,
// Represents a CREATE2 broadcast tx.
Create2
}
/// An Ethereum log. Returned by `getRecordedLogs`.
struct Log {
// The topics of the log, including the signature, if any.
bytes32[] topics;
// The raw data of the log.
bytes data;
// The address of the log's emitter.
address emitter;
}
/// An RPC URL and its alias. Returned by `rpcUrlStructs`.
struct Rpc {
// The alias of the RPC URL.
string key;
// The RPC URL.
string url;
}
/// An RPC log object. Returned by `eth_getLogs`.
struct EthGetLogs {
// The address of the log's emitter.
address emitter;
// The topics of the log, including the signature, if any.
bytes32[] topics;
// The raw data of the log.
bytes data;
// The block hash.
bytes32 blockHash;
// The block number.
uint64 blockNumber;
// The transaction hash.
bytes32 transactionHash;
// The transaction index in the block.
uint64 transactionIndex;
// The log index.
uint256 logIndex;
// Whether the log was removed.
bool removed;
}
/// A single entry in a directory listing. Returned by `readDir`.
struct DirEntry {
// The error message, if any.
string errorMessage;
// The path of the entry.
string path;
// The depth of the entry.
uint64 depth;
// Whether the entry is a directory.
bool isDir;
// Whether the entry is a symlink.
bool isSymlink;
}
/// Metadata information about a file.
/// This structure is returned from the `fsMetadata` function and represents known
/// metadata about a file such as its permissions, size, modification
/// times, etc.
struct FsMetadata {
// True if this metadata is for a directory.
bool isDir;
// True if this metadata is for a symlink.
bool isSymlink;
// The size of the file, in bytes, this metadata is for.
uint256 length;
// True if this metadata is for a readonly (unwritable) file.
bool readOnly;
// The last modification time listed in this metadata.
uint256 modified;
// The last access time of this metadata.
uint256 accessed;
// The creation time listed in this metadata.
uint256 created;
}
/// A wallet with a public and private key.
struct Wallet {
// The wallet's address.
address addr;
// The wallet's public key `X`.
uint256 publicKeyX;
// The wallet's public key `Y`.
uint256 publicKeyY;
// The wallet's private key.
uint256 privateKey;
}
/// The result of a `tryFfi` call.
struct FfiResult {
// The exit code of the call.
int32 exitCode;
// The optionally hex-decoded `stdout` data.
bytes stdout;
// The `stderr` data.
bytes stderr;
}
/// Information on the chain and fork.
struct ChainInfo {
// The fork identifier. Set to zero if no fork is active.
uint256 forkId;
// The chain ID of the current fork.
uint256 chainId;
}
/// Information about a blockchain.
struct Chain {
// The chain name.
string name;
// The chain's Chain ID.
uint256 chainId;
// The chain's alias. (i.e. what gets specified in `foundry.toml`).
string chainAlias;
// A default RPC endpoint for this chain.
string rpcUrl;
}
/// The result of a `stopAndReturnStateDiff` call.
struct AccountAccess {
// The chain and fork the access occurred.
ChainInfo chainInfo;
// The kind of account access that determines what the account is.
// If kind is Call, DelegateCall, StaticCall or CallCode, then the account is the callee.
// If kind is Create, then the account is the newly created account.
// If kind is SelfDestruct, then the account is the selfdestruct recipient.
// If kind is a Resume, then account represents a account context that has resumed.
AccountAccessKind kind;
// The account that was accessed.
// It's either the account created, callee or a selfdestruct recipient for CREATE, CALL or SELFDESTRUCT.
address account;
// What accessed the account.
address accessor;
// If the account was initialized or empty prior to the access.
// An account is considered initialized if it has code, a
// non-zero nonce, or a non-zero balance.
bool initialized;
// The previous balance of the accessed account.
uint256 oldBalance;
// The potential new balance of the accessed account.
// That is, all balance changes are recorded here, even if reverts occurred.
uint256 newBalance;
// Code of the account deployed by CREATE.
bytes deployedCode;
// Value passed along with the account access
uint256 value;
// Input data provided to the CREATE or CALL
bytes data;
// If this access reverted in either the current or parent context.
bool reverted;
// An ordered list of storage accesses made during an account access operation.
StorageAccess[] storageAccesses;
// Call depth traversed during the recording of state differences
uint64 depth;
}
/// The storage accessed during an `AccountAccess`.
struct StorageAccess {
// The account whose storage was accessed.
address account;
// The slot that was accessed.
bytes32 slot;
// If the access was a write.
bool isWrite;
// The previous value of the slot.
bytes32 previousValue;
// The new value of the slot.
bytes32 newValue;
// If the access was reverted.
bool reverted;
}
/// Gas used. Returned by `lastCallGas`.
struct Gas {
// The gas limit of the call.
uint64 gasLimit;
// The total gas used.
uint64 gasTotalUsed;
// DEPRECATED: The amount of gas used for memory expansion. Ref: <https://github.com/foundry-rs/foundry/pull/7934#pullrequestreview-2069236939>
uint64 gasMemoryUsed;
// The amount of gas refunded.
int64 gasRefunded;
// The amount of gas remaining.
uint64 gasRemaining;
}
/// The result of the `stopDebugTraceRecording` call
struct DebugStep {
// The stack before executing the step of the run.
// stack\[0\] represents the top of the stack.
// and only stack data relevant to the opcode execution is contained.
uint256[] stack;
// The memory input data before executing the step of the run.
// only input data relevant to the opcode execution is contained.
// e.g. for MLOAD, it will have memory\[offset:offset+32\] copied here.
// the offset value can be get by the stack data.
bytes memoryInput;
// The opcode that was accessed.
uint8 opcode;
// The call depth of the step.
uint64 depth;
// Whether the call end up with out of gas error.
bool isOutOfGas;
// The contract address where the opcode is running
address contractAddr;
}
/// Represents a transaction's broadcast details.
struct BroadcastTxSummary {
// The hash of the transaction that was broadcasted
bytes32 txHash;
// Represent the type of transaction among CALL, CREATE, CREATE2
BroadcastTxType txType;
// The address of the contract that was called or created.
// This is address of the contract that is created if the txType is CREATE or CREATE2.
address contractAddress;
// The block number the transaction landed in.
uint64 blockNumber;
// Status of the transaction, retrieved from the transaction receipt.
bool success;
}
/// Holds a signed EIP-7702 authorization for an authority account to delegate to an implementation.
struct SignedDelegation {
// The y-parity of the recovered secp256k1 signature (0 or 1).
uint8 v;
// First 32 bytes of the signature.
bytes32 r;
// Second 32 bytes of the signature.
bytes32 s;
// The current nonce of the authority account at signing time.
// Used to ensure signature can't be replayed after account nonce changes.
uint64 nonce;
// Address of the contract implementation that will be delegated to.
// Gets encoded into delegation code: 0xef0100 || implementation.
address implementation;
}
/// Represents a "potential" revert reason from a single subsequent call when using `vm.assumeNoReverts`.
/// Reverts that match will result in a FOUNDRY::ASSUME rejection, whereas unmatched reverts will be surfaced
/// as normal.
struct PotentialRevert {
// The allowed origin of the revert opcode; address(0) allows reverts from any address
address reverter;
// When true, only matches on the beginning of the revert data, otherwise, matches on entire revert data
bool partialMatch;
// The data to use to match encountered reverts
bytes revertData;
}
/// An EIP-2930 access list item.
struct AccessListItem {
// The address to be added in access list.
address target;
// The storage keys to be added in access list.
bytes32[] storageKeys;
}
// ======== Crypto ========
/// Derives a private key from the name, labels the account with that name, and returns the wallet.
function createWallet(string calldata walletLabel) external returns (Wallet memory wallet);
/// Generates a wallet from the private key and returns the wallet.
function createWallet(uint256 privateKey) external returns (Wallet memory wallet);
/// Generates a wallet from the private key, labels the account with that name, and returns the wallet.
function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet);
/// Derive a private key from a provided mnenomic string (or mnenomic file path)
/// at the derivation path `m/44'/60'/0'/0/{index}`.
function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey);
/// Derive a private key from a provided mnenomic string (or mnenomic file path)
/// at `{derivationPath}{index}`.
function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index)
external
pure
returns (uint256 privateKey);
/// Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language
/// at the derivation path `m/44'/60'/0'/0/{index}`.
function deriveKey(string calldata mnemonic, uint32 index, string calldata language)
external
pure
returns (uint256 privateKey);
/// Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language
/// at `{derivationPath}{index}`.
function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language)
external
pure
returns (uint256 privateKey);
/// Derives secp256r1 public key from the provided `privateKey`.
function publicKeyP256(uint256 privateKey) external pure returns (uint256 publicKeyX, uint256 publicKeyY);
/// Adds a private key to the local forge wallet and returns the address.
function rememberKey(uint256 privateKey) external returns (address keyAddr);
/// Derive a set number of wallets from a mnemonic at the derivation path `m/44'/60'/0'/0/{0..count}`.
/// The respective private keys are saved to the local forge wallet for later use and their addresses are returned.
function rememberKeys(string calldata mnemonic, string calldata derivationPath, uint32 count)
external
returns (address[] memory keyAddrs);
/// Derive a set number of wallets from a mnemonic in the specified language at the derivation path `m/44'/60'/0'/0/{0..count}`.
/// The respective private keys are saved to the local forge wallet for later use and their addresses are returned.
function rememberKeys(
string calldata mnemonic,
string calldata derivationPath,
string calldata language,
uint32 count
) external returns (address[] memory keyAddrs);
/// Signs data with a `Wallet`.
/// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the
/// signature's `s` value, and the recovery id `v` in a single bytes32.
/// This format reduces the signature size from 65 to 64 bytes.
function signCompact(Wallet calldata wallet, bytes32 digest) external returns (bytes32 r, bytes32 vs);
/// Signs `digest` with `privateKey` using the secp256k1 curve.
/// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the
/// signature's `s` value, and the recovery id `v` in a single bytes32.
/// This format reduces the signature size from 65 to 64 bytes.
function signCompact(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 vs);
/// Signs `digest` with signer provided to script using the secp256k1 curve.
/// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the
/// signature's `s` value, and the recovery id `v` in a single bytes32.
/// This format reduces the signature size from 65 to 64 bytes.
/// If `--sender` is provided, the signer with provided address is used, otherwise,
/// if exactly one signer is provided to the script, that signer is used.
/// Raises error if signer passed through `--sender` does not match any unlocked signers or
/// if `--sender` is not provided and not exactly one signer is passed to the script.
function signCompact(bytes32 digest) external pure returns (bytes32 r, bytes32 vs);
/// Signs `digest` with signer provided to script using the secp256k1 curve.
/// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the
/// signature's `s` value, and the recovery id `v` in a single bytes32.
/// This format reduces the signature size from 65 to 64 bytes.
/// Raises error if none of the signers passed into the script have provided address.
function signCompact(address signer, bytes32 digest) external pure returns (bytes32 r, bytes32 vs);
/// Signs `digest` with `privateKey` using the secp256r1 curve.
function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s);
/// Signs data with a `Wallet`.
function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);
/// Signs `digest` with `privateKey` using the secp256k1 curve.
function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);
/// Signs `digest` with signer provided to script using the secp256k1 curve.
/// If `--sender` is provided, the signer with provided address is used, otherwise,
/// if exactly one signer is provided to the script, that signer is used.
/// Raises error if signer passed through `--sender` does not match any unlocked signers or
/// if `--sender` is not provided and not exactly one signer is passed to the script.
function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);
/// Signs `digest` with signer provided to script using the secp256k1 curve.
/// Raises error if none of the signers passed into the script have provided address.
function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);
// ======== Environment ========
/// Gets the environment variable `name` and parses it as `address`.
/// Reverts if the variable was not found or could not be parsed.
function envAddress(string calldata name) external view returns (address value);
/// Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`.
/// Reverts if the variable was not found or could not be parsed.
function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value);
/// Gets the environment variable `name` and parses it as `bool`.
/// Reverts if the variable was not found or could not be parsed.
function envBool(string calldata name) external view returns (bool value);
/// Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`.
/// Reverts if the variable was not found or could not be parsed.
function envBool(string calldata name, string calldata delim) external view returns (bool[] memory value);
/// Gets the environment variable `name` and parses it as `bytes32`.
/// Reverts if the variable was not found or could not be parsed.
function envBytes32(string calldata name) external view returns (bytes32 value);
/// Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`.
/// Reverts if the variable was not found or could not be parsed.
function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value);
/// Gets the environment variable `name` and parses it as `bytes`.
/// Reverts if the variable was not found or could not be parsed.
function envBytes(string calldata name) external view returns (bytes memory value);
/// Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`.
/// Reverts if the variable was not found or could not be parsed.
function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value);
/// Gets the environment variable `name` and returns true if it exists, else returns false.
function envExists(string calldata name) external view returns (bool result);
/// Gets the environment variable `name` and parses it as `int256`.
/// Reverts if the variable was not found or could not be parsed.
function envInt(string calldata name) external view returns (int256 value);
/// Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`.
/// Reverts if the variable was not found or could not be parsed.
function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value);
/// Gets the environment variable `name` and parses it as `bool`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, bool defaultValue) external view returns (bool value);
/// Gets the environment variable `name` and parses it as `uint256`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, uint256 defaultValue) external view returns (uint256 value);
/// Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, string calldata delim, address[] calldata defaultValue)
external
view
returns (address[] memory value);
/// Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue)
external
view
returns (bytes32[] memory value);
/// Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, string calldata delim, string[] calldata defaultValue)
external
view
returns (string[] memory value);
/// Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue)
external
view
returns (bytes[] memory value);
/// Gets the environment variable `name` and parses it as `int256`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, int256 defaultValue) external view returns (int256 value);
/// Gets the environment variable `name` and parses it as `address`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, address defaultValue) external view returns (address value);
/// Gets the environment variable `name` and parses it as `bytes32`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, bytes32 defaultValue) external view returns (bytes32 value);
/// Gets the environment variable `name` and parses it as `string`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, string calldata defaultValue) external view returns (string memory value);
/// Gets the environment variable `name` and parses it as `bytes`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, bytes calldata defaultValue) external view returns (bytes memory value);
/// Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue)
external
view
returns (bool[] memory value);
/// Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue)
external
view
returns (uint256[] memory value);
/// Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`.
/// Reverts if the variable could not be parsed.
/// Returns `defaultValue` if the variable was not found.
function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue)
external
view
returns (int256[] memory value);
/// Gets the environment variable `name` and parses it as `string`.
/// Reverts if the variable was not found or could not be parsed.
function envString(string calldata name) external view returns (string memory value);
/// Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`.
/// Reverts if the variable was not found or could not be parsed.
function envString(string calldata name, string calldata delim) external view returns (string[] memory value);
/// Gets the environment variable `name` and parses it as `uint256`.
/// Reverts if the variable was not found or could not be parsed.
function envUint(string calldata name) external view returns (uint256 value);
/// Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`.
/// Reverts if the variable was not found or could not be parsed.
function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value);
/// Returns true if `forge` command was executed in given context.
function isContext(ForgeContext context) external view returns (bool result);
/// Sets environment variables.
function setEnv(string calldata name, string calldata value) external;
// ======== EVM ========
/// Gets all accessed reads and write slot from a `vm.record` session, for a given address.
function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots);
/// Gets the address for a given private key.
function addr(uint256 privateKey) external pure returns (address keyAddr);
/// Gets all the logs according to specified filter.
function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics)
external
returns (EthGetLogs[] memory logs);
/// Gets the current `block.blobbasefee`.
/// You should use this instead of `block.blobbasefee` if you use `vm.blobBaseFee`, as `block.blobbasefee` is assumed to be constant across a transaction,
/// and as a result will get optimized out by the compiler.
/// See https://github.com/foundry-rs/foundry/issues/6180
function getBlobBaseFee() external view returns (uint256 blobBaseFee);
/// Gets the current `block.number`.
/// You should use this instead of `block.number` if you use `vm.roll`, as `block.number` is assumed to be constant across a transaction,
/// and as a result will get optimized out by the compiler.
/// See https://github.com/foundry-rs/foundry/issues/6180
function getBlockNumber() external view returns (uint256 height);
/// Gets the current `block.timestamp`.
/// You should use this instead of `block.timestamp` if you use `vm.warp`, as `block.timestamp` is assumed to be constant across a transaction,
/// and as a result will get optimized out by the compiler.
/// See https://github.com/foundry-rs/foundry/issues/6180
function getBlockTimestamp() external view returns (uint256 timestamp);
/// Gets the map key and parent of a mapping at a given slot, for a given address.
function getMappingKeyAndParentOf(address target, bytes32 elementSlot)
external
returns (bool found, bytes32 key, bytes32 parent);
/// Gets the number of elements in the mapping at the given slot, for a given address.
function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length);
/// Gets the elements at index idx of the mapping at the given slot, for a given address. The
/// index must be less than the length of the mapping (i.e. the number of keys in the mapping).
function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value);
/// Gets the nonce of an account.
function getNonce(address account) external view returns (uint64 nonce);
/// Get the nonce of a `Wallet`.
function getNonce(Wallet calldata wallet) external returns (uint64 nonce);
/// Gets all the recorded logs.
function getRecordedLogs() external returns (Log[] memory logs);
/// Returns state diffs from current `vm.startStateDiffRecording` session.
function getStateDiff() external view returns (string memory diff);
/// Returns state diffs from current `vm.startStateDiffRecording` session, in json format.
function getStateDiffJson() external view returns (string memory diff);
/// Gets the gas used in the last call from the callee perspective.
function lastCallGas() external view returns (Gas memory gas);
/// Loads a storage slot from an address.
function load(address target, bytes32 slot) external view returns (bytes32 data);
/// Pauses gas metering (i.e. gas usage is not counted). Noop if already paused.
function pauseGasMetering() external;
/// Records all storage reads and writes.
function record() external;
/// Record all the transaction logs.
function recordLogs() external;
/// Reset gas metering (i.e. gas usage is set to gas limit).
function resetGasMetering() external;
/// Resumes gas metering (i.e. gas usage is counted again). Noop if already on.
function resumeGasMetering() external;
/// Performs an Ethereum JSON-RPC request to the current fork URL.
function rpc(string calldata method, string calldata params) external returns (bytes memory data);
/// Performs an Ethereum JSON-RPC request to the given endpoint.
function rpc(string calldata urlOrAlias, string calldata method, string calldata params)
external
returns (bytes memory data);
/// Records the debug trace during the run.
function startDebugTraceRecording() external;
/// Starts recording all map SSTOREs for later retrieval.
function startMappingRecording() external;
/// Record all account accesses as part of CREATE, CALL or SELFDESTRUCT opcodes in order,
/// along with the context of the calls
function startStateDiffRecording() external;
/// Stop debug trace recording and returns the recorded debug trace.
function stopAndReturnDebugTraceRecording() external returns (DebugStep[] memory step);
/// Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session.
function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses);
/// Stops recording all map SSTOREs for later retrieval and clears the recorded data.
function stopMappingRecording() external;
// ======== Filesystem ========
/// Closes file for reading, resetting the offset and allowing to read it from beginning with readLine.
/// `path` is relative to the project root.
function closeFile(string calldata path) external;
/// Copies the contents of one file to another. This function will **overwrite** the contents of `to`.
/// On success, the total number of bytes copied is returned and it is equal to the length of the `to` file as reported by `metadata`.
/// Both `from` and `to` are relative to the project root.
function copyFile(string calldata from, string calldata to) external returns (uint64 copied);
/// Creates a new, empty directory at the provided path.
/// This cheatcode will revert in the following situations, but is not limited to just these cases:
/// - User lacks permissions to modify `path`.
/// - A parent of the given path doesn't exist and `recursive` is false.
/// - `path` already exists and `recursive` is false.
/// `path` is relative to the project root.
function createDir(string calldata path, bool recursive) external;
/// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
function deployCode(string calldata artifactPath) external returns (address deployedAddress);
/// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
/// Additionally accepts abi-encoded constructor arguments.
function deployCode(string calldata artifactPath, bytes calldata constructorArgs)
external
returns (address deployedAddress);
/// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
/// Additionally accepts `msg.value`.
function deployCode(string calldata artifactPath, uint256 value) external returns (address deployedAddress);
/// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
/// Additionally accepts abi-encoded constructor arguments and `msg.value`.
function deployCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value)
external
returns (address deployedAddress);
/// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
function deployCode(string calldata artifactPath, bytes32 salt) external returns (address deployedAddress);
/// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
/// Additionally accepts abi-encoded constructor arguments.
function deployCode(string calldata artifactPath, bytes calldata constructorArgs, bytes32 salt)
external
returns (address deployedAddress);
/// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
/// Additionally accepts `msg.value`.
function deployCode(string calldata artifactPath, uint256 value, bytes32 salt)
external
returns (address deployedAddress);
/// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
/// Additionally accepts abi-encoded constructor arguments and `msg.value`.
function deployCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value, bytes32 salt)
external
returns (address deployedAddress);
/// Returns true if the given path points to an existing entity, else returns false.
function exists(string calldata path) external view returns (bool result);
/// Performs a foreign function call via the terminal.
function ffi(string[] calldata commandInput) external returns (bytes memory result);
/// Given a path, query the file system to get information about a file, directory, etc.
function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata);
/// Gets the artifact path from code (aka. creation code).
function getArtifactPathByCode(bytes calldata code) external view returns (string memory path);
/// Gets the artifact path from deployed code (aka. runtime code).
function getArtifactPathByDeployedCode(bytes calldata deployedCode) external view returns (string memory path);
/// Returns the most recent broadcast for the given contract on `chainId` matching `txType`.
/// For example:
/// The most recent deployment can be fetched by passing `txType` as `CREATE` or `CREATE2`.
/// The most recent call can be fetched by passing `txType` as `CALL`.
function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType)
external
view
returns (BroadcastTxSummary memory);
/// Returns all broadcasts for the given contract on `chainId` with the specified `txType`.
/// Sorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber.
function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType)
external
view
returns (BroadcastTxSummary[] memory);
/// Returns all broadcasts for the given contract on `chainId`.
/// Sorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber.
function getBroadcasts(string calldata contractName, uint64 chainId)
external
view
returns (BroadcastTxSummary[] memory);
/// Gets the creation bytecode from an artifact file. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode);
/// Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode);
/// Returns the most recent deployment for the current `chainId`.
function getDeployment(string calldata contractName) external view returns (address deployedAddress);
/// Returns the most recent deployment for the given contract on `chainId`
function getDeployment(string calldata contractName, uint64 chainId)
external
view
returns (address deployedAddress);
/// Returns all deployments for the given contract on `chainId`
/// Sorted in descending order of deployment time i.e descending order of BroadcastTxSummary.blockNumber.
/// The most recent deployment is the first element, and the oldest is the last.
function getDeployments(string calldata contractName, uint64 chainId)
external
view
returns (address[] memory deployedAddresses);
/// Returns true if the path exists on disk and is pointing at a directory, else returns false.
function isDir(string calldata path) external view returns (bool result);
/// Returns true if the path exists on disk and is pointing at a regular file, else returns false.
function isFile(string calldata path) external view returns (bool result);
/// Get the path of the current project root.
function projectRoot() external view returns (string memory path);
/// Prompts the user for a string value in the terminal.
function prompt(string calldata promptText) external returns (string memory input);
/// Prompts the user for an address in the terminal.
function promptAddress(string calldata promptText) external returns (address);
/// Prompts the user for a hidden string value in the terminal.
function promptSecret(string calldata promptText) external returns (string memory input);
/// Prompts the user for hidden uint256 in the terminal (usually pk).
function promptSecretUint(string calldata promptText) external returns (uint256);
/// Prompts the user for uint256 in the terminal.
function promptUint(string calldata promptText) external returns (uint256);
/// Reads the directory at the given path recursively, up to `maxDepth`.
/// `maxDepth` defaults to 1, meaning only the direct children of the given directory will be returned.
/// Follows symbolic links if `followLinks` is true.
function readDir(string calldata path) external view returns (DirEntry[] memory entries);
/// See `readDir(string)`.
function readDir(string calldata path, uint64 maxDepth) external view returns (DirEntry[] memory entries);
/// See `readDir(string)`.
function readDir(string calldata path, uint64 maxDepth, bool followLinks)
external
view
returns (DirEntry[] memory entries);
/// Reads the entire content of file to string. `path` is relative to the project root.
function readFile(string calldata path) external view returns (string memory data);
/// Reads the entire content of file as binary. `path` is relative to the project root.
function readFileBinary(string calldata path) external view returns (bytes memory data);
/// Reads next line of file to string.
function readLine(string calldata path) external view returns (string memory line);
/// Reads a symbolic link, returning the path that the link points to.
/// This cheatcode will revert in the following situations, but is not limited to just these cases:
/// - `path` is not a symbolic link.
/// - `path` does not exist.
function readLink(string calldata linkPath) external view returns (string memory targetPath);
/// Removes a directory at the provided path.
/// This cheatcode will revert in the following situations, but is not limited to just these cases:
/// - `path` doesn't exist.
/// - `path` isn't a directory.
/// - User lacks permissions to modify `path`.
/// - The directory is not empty and `recursive` is false.
/// `path` is relative to the project root.
function removeDir(string calldata path, bool recursive) external;
/// Removes a file from the filesystem.
/// This cheatcode will revert in the following situations, but is not limited to just these cases:
/// - `path` points to a directory.
/// - The file doesn't exist.
/// - The user lacks permissions to remove the file.
/// `path` is relative to the project root.
function removeFile(string calldata path) external;
/// Performs a foreign function call via terminal and returns the exit code, stdout, and stderr.
function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result);
/// Returns the time since unix epoch in milliseconds.
function unixTime() external view returns (uint256 milliseconds);
/// Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does.
/// `path` is relative to the project root.
function writeFile(string calldata path, string calldata data) external;
/// Writes binary data to a file, creating a file if it does not exist, and entirely replacing its contents if it does.
/// `path` is relative to the project root.
function writeFileBinary(string calldata path, bytes calldata data) external;
/// Writes line to file, creating a file if it does not exist.
/// `path` is relative to the project root.
function writeLine(string calldata path, string calldata data) external;
// ======== JSON ========
/// Checks if `key` exists in a JSON object.
function keyExistsJson(string calldata json, string calldata key) external view returns (bool);
/// Parses a string of JSON data at `key` and coerces it to `address`.
function parseJsonAddress(string calldata json, string calldata key) external pure returns (address);
/// Parses a string of JSON data at `key` and coerces it to `address[]`.
function parseJsonAddressArray(string calldata json, string calldata key)
external
pure
returns (address[] memory);
/// Parses a string of JSON data at `key` and coerces it to `bool`.
function parseJsonBool(string calldata json, string calldata key) external pure returns (bool);
/// Parses a string of JSON data at `key` and coerces it to `bool[]`.
function parseJsonBoolArray(string calldata json, string calldata key) external pure returns (bool[] memory);
/// Parses a string of JSON data at `key` and coerces it to `bytes`.
function parseJsonBytes(string calldata json, string calldata key) external pure returns (bytes memory);
/// Parses a string of JSON data at `key` and coerces it to `bytes32`.
function parseJsonBytes32(string calldata json, string calldata key) external pure returns (bytes32);
/// Parses a string of JSON data at `key` and coerces it to `bytes32[]`.
function parseJsonBytes32Array(string calldata json, string calldata key)
external
pure
returns (bytes32[] memory);
/// Parses a string of JSON data at `key` and coerces it to `bytes[]`.
function parseJsonBytesArray(string calldata json, string calldata key) external pure returns (bytes[] memory);
/// Parses a string of JSON data at `key` and coerces it to `int256`.
function parseJsonInt(string calldata json, string calldata key) external pure returns (int256);
/// Parses a string of JSON data at `key` and coerces it to `int256[]`.
function parseJsonIntArray(string calldata json, string calldata key) external pure returns (int256[] memory);
/// Returns an array of all the keys in a JSON object.
function parseJsonKeys(string calldata json, string calldata key) external pure returns (string[] memory keys);
/// Parses a string of JSON data at `key` and coerces it to `string`.
function parseJsonString(string calldata json, string calldata key) external pure returns (string memory);
/// Parses a string of JSON data at `key` and coerces it to `string[]`.
function parseJsonStringArray(string calldata json, string calldata key) external pure returns (string[] memory);
/// Parses a string of JSON data at `key` and coerces it to type array corresponding to `typeDescription`.
function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription)
external
pure
returns (bytes memory);
/// Parses a string of JSON data and coerces it to type corresponding to `typeDescription`.
function parseJsonType(string calldata json, string calldata typeDescription)
external
pure
returns (bytes memory);
/// Parses a string of JSON data at `key` and coerces it to type corresponding to `typeDescription`.
function parseJsonType(string calldata json, string calldata key, string calldata typeDescription)
external
pure
returns (bytes memory);
/// Parses a string of JSON data at `key` and coerces it to `uint256`.
function parseJsonUint(string calldata json, string calldata key) external pure returns (uint256);
/// Parses a string of JSON data at `key` and coerces it to `uint256[]`.
function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory);
/// ABI-encodes a JSON object.
function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData);
/// ABI-encodes a JSON object at `key`.
function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData);
/// See `serializeJson`.
function serializeAddress(string calldata objectKey, string calldata valueKey, address value)
external
returns (string memory json);
/// See `serializeJson`.
function serializeAddress(string calldata objectKey, string calldata valueKey, address[] calldata values)
external
returns (string memory json);
/// See `serializeJson`.
function serializeBool(string calldata objectKey, string calldata valueKey, bool value)
external
returns (string memory json);
/// See `serializeJson`.
function serializeBool(string calldata objectKey, string calldata valueKey, bool[] calldata values)
external
returns (string memory json);
/// See `serializeJson`.
function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32 value)
external
returns (string memory json);
/// See `serializeJson`.
function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32[] calldata values)
external
returns (string memory json);
/// See `serializeJson`.
function serializeBytes(string calldata objectKey, string calldata valueKey, bytes calldata value)
external
returns (string memory json);
/// See `serializeJson`.
function serializeBytes(string calldata objectKey, string calldata valueKey, bytes[] calldata values)
external
returns (string memory json);
/// See `serializeJson`.
function serializeInt(string calldata objectKey, string calldata valueKey, int256 value)
external
returns (string memory json);
/// See `serializeJson`.
function serializeInt(string calldata objectKey, string calldata valueKey, int256[] calldata values)
external
returns (string memory json);
/// Serializes a key and value to a JSON object stored in-memory that can be later written to a file.
/// Returns the stringified version of the specific JSON file up to that moment.
function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json);
/// See `serializeJson`.
function serializeJsonType(string calldata typeDescription, bytes calldata value)
external
pure
returns (string memory json);
/// See `serializeJson`.
function serializeJsonType(
string calldata objectKey,
string calldata valueKey,
string calldata typeDescription,
bytes calldata value
) external returns (string memory json);
/// See `serializeJson`.
function serializeString(string calldata objectKey, string calldata valueKey, string calldata value)
external
returns (string memory json);
/// See `serializeJson`.
function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values)
external
returns (string memory json);
/// See `serializeJson`.
function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value)
external
returns (string memory json);
/// See `serializeJson`.
function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value)
external
returns (string memory json);
/// See `serializeJson`.
function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values)
external
returns (string memory json);
/// Write a serialized JSON object to a file. If the file exists, it will be overwritten.
function writeJson(string calldata json, string calldata path) external;
/// Write a serialized JSON object to an **existing** JSON file, replacing a value with key = <value_key.>
/// This is useful to replace a specific value of a JSON file, without having to parse the entire thing.
function writeJson(string calldata json, string calldata path, string calldata valueKey) external;
/// Checks if `key` exists in a JSON object
/// `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions.
function keyExists(string calldata json, string calldata key) external view returns (bool);
// ======== Scripting ========
/// Attach an EIP-4844 blob to the next call
function attachBlob(bytes calldata blob) external;
/// Designate the next call as an EIP-7702 transaction
function attachDelegation(SignedDelegation calldata signedDelegation) external;
/// Takes a signed transaction and broadcasts it to the network.
function broadcastRawTransaction(bytes calldata data) external;
/// Has the next call (at this call depth only) create transactions that can later be signed and sent onchain.
/// Broadcasting address is determined by checking the following in order:
/// 1. If `--sender` argument was provided, that address is used.
/// 2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used.
/// 3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used.
function broadcast() external;
/// Has the next call (at this call depth only) create a transaction with the address provided
/// as the sender that can later be signed and sent onchain.
function broadcast(address signer) external;
/// Has the next call (at this call depth only) create a transaction with the private key
/// provided as the sender that can later be signed and sent onchain.
function broadcast(uint256 privateKey) external;
/// Returns addresses of available unlocked wallets in the script environment.
function getWallets() external returns (address[] memory wallets);
/// Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction
function signAndAttachDelegation(address implementation, uint256 privateKey)
external
returns (SignedDelegation memory signedDelegation);
/// Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction for specific nonce
function signAndAttachDelegation(address implementation, uint256 privateKey, uint64 nonce)
external
returns (SignedDelegation memory signedDelegation);
/// Sign an EIP-7702 authorization for delegation
function signDelegation(address implementation, uint256 privateKey)
external
returns (SignedDelegation memory signedDelegation);
/// Sign an EIP-7702 authorization for delegation for specific nonce
function signDelegation(address implementation, uint256 privateKey, uint64 nonce)
external
returns (SignedDelegation memory signedDelegation);
/// Has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain.
/// Broadcasting address is determined by checking the following in order:
/// 1. If `--sender` argument was provided, that address is used.
/// 2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used.
/// 3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used.
function startBroadcast() external;
/// Has all subsequent calls (at this call depth only) create transactions with the address
/// provided that can later be signed and sent onchain.
function startBroadcast(address signer) external;
/// Has all subsequent calls (at this call depth only) create transactions with the private key
/// provided that can later be signed and sent onchain.
function startBroadcast(uint256 privateKey) external;
/// Stops collecting onchain transactions.
function stopBroadcast() external;
// ======== String ========
/// Returns true if `search` is found in `subject`, false otherwise.
function contains(string calldata subject, string calldata search) external returns (bool result);
/// Returns the index of the first occurrence of a `key` in an `input` string.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `key` is not found.
/// Returns 0 in case of an empty `key`.
function indexOf(string calldata input, string calldata key) external pure returns (uint256);
/// Parses the given `string` into an `address`.
function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue);
/// Parses the given `string` into a `bool`.
function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue);
/// Parses the given `string` into `bytes`.
function parseBytes(string calldata stringifiedValue) external pure returns (bytes memory parsedValue);
/// Parses the given `string` into a `bytes32`.
function parseBytes32(string calldata stringifiedValue) external pure returns (bytes32 parsedValue);
/// Parses the given `string` into a `int256`.
function parseInt(string calldata stringifiedValue) external pure returns (int256 parsedValue);
/// Parses the given `string` into a `uint256`.
function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue);
/// Replaces occurrences of `from` in the given `string` with `to`.
function replace(string calldata input, string calldata from, string calldata to)
external
pure
returns (string memory output);
/// Splits the given `string` into an array of strings divided by the `delimiter`.
function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs);
/// Converts the given `string` value to Lowercase.
function toLowercase(string calldata input) external pure returns (string memory output);
/// Converts the given value to a `string`.
function toString(address value) external pure returns (string memory stringifiedValue);
/// Converts the given value to a `string`.
function toString(bytes calldata value) external pure returns (string memory stringifiedValue);
/// Converts the given value to a `string`.
function toString(bytes32 value) external pure returns (string memory stringifiedValue);
/// Converts the given value to a `string`.
function toString(bool value) external pure returns (string memory stringifiedValue);
/// Converts the given value to a `string`.
function toString(uint256 value) external pure returns (string memory stringifiedValue);
/// Converts the given value to a `string`.
function toString(int256 value) external pure returns (string memory stringifiedValue);
/// Converts the given `string` value to Uppercase.
function toUppercase(string calldata input) external pure returns (string memory output);
/// Trims leading and trailing whitespace from the given `string` value.
function trim(string calldata input) external pure returns (string memory output);
// ======== Testing ========
/// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.
/// Formats values with decimals in failure message.
function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure;
/// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertApproxEqAbsDecimal(
uint256 left,
uint256 right,
uint256 maxDelta,
uint256 decimals,
string calldata error
) external pure;
/// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.
/// Formats values with decimals in failure message.
function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure;
/// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertApproxEqAbsDecimal(
int256 left,
int256 right,
uint256 maxDelta,
uint256 decimals,
string calldata error
) external pure;
/// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.
function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure;
/// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.
/// Includes error message into revert string on failure.
function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure;
/// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.
function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure;
/// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.
/// Includes error message into revert string on failure.
function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure;
/// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.
/// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%
/// Formats values with decimals in failure message.
function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals)
external
pure;
/// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.
/// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertApproxEqRelDecimal(
uint256 left,
uint256 right,
uint256 maxPercentDelta,
uint256 decimals,
string calldata error
) external pure;
/// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.
/// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%
/// Formats values with decimals in failure message.
function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals)
external
pure;
/// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.
/// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertApproxEqRelDecimal(
int256 left,
int256 right,
uint256 maxPercentDelta,
uint256 decimals,
string calldata error
) external pure;
/// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.
/// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%
function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure;
/// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.
/// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%
/// Includes error message into revert string on failure.
function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error)
external
pure;
/// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.
/// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%
function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure;
/// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.
/// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%
/// Includes error message into revert string on failure.
function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error)
external
pure;
/// Asserts that two `uint256` values are equal, formatting them with decimals in failure message.
function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure;
/// Asserts that two `uint256` values are equal, formatting them with decimals in failure message.
/// Includes error message into revert string on failure.
function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;
/// Asserts that two `int256` values are equal, formatting them with decimals in failure message.
function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure;
/// Asserts that two `int256` values are equal, formatting them with decimals in failure message.
/// Includes error message into revert string on failure.
function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;
/// Asserts that two `bool` values are equal.
function assertEq(bool left, bool right) external pure;
/// Asserts that two `bool` values are equal and includes error message into revert string on failure.
function assertEq(bool left, bool right, string calldata error) external pure;
/// Asserts that two `string` values are equal.
function assertEq(string calldata left, string calldata right) external pure;
/// Asserts that two `string` values are equal and includes error message into revert string on failure.
function assertEq(string calldata left, string calldata right, string calldata error) external pure;
/// Asserts that two `bytes` values are equal.
function assertEq(bytes calldata left, bytes calldata right) external pure;
/// Asserts that two `bytes` values are equal and includes error message into revert string on failure.
function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure;
/// Asserts that two arrays of `bool` values are equal.
function assertEq(bool[] calldata left, bool[] calldata right) external pure;
/// Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure.
function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;
/// Asserts that two arrays of `uint256 values are equal.
function assertEq(uint256[] calldata left, uint256[] calldata right) external pure;
/// Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure.
function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;
/// Asserts that two arrays of `int256` values are equal.
function assertEq(int256[] calldata left, int256[] calldata right) external pure;
/// Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure.
function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;
/// Asserts that two `uint256` values are equal.
function assertEq(uint256 left, uint256 right) external pure;
/// Asserts that two arrays of `address` values are equal.
function assertEq(address[] calldata left, address[] calldata right) external pure;
/// Asserts that two arrays of `address` values are equal and includes error message into revert string on failure.
function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure;
/// Asserts that two arrays of `bytes32` values are equal.
function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure;
/// Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure.
function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;
/// Asserts that two arrays of `string` values are equal.
function assertEq(string[] calldata left, string[] calldata right) external pure;
/// Asserts that two arrays of `string` values are equal and includes error message into revert string on failure.
function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure;
/// Asserts that two arrays of `bytes` values are equal.
function assertEq(bytes[] calldata left, bytes[] calldata right) external pure;
/// Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure.
function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;
/// Asserts that two `uint256` values are equal and includes error message into revert string on failure.
function assertEq(uint256 left, uint256 right, string calldata error) external pure;
/// Asserts that two `int256` values are equal.
function assertEq(int256 left, int256 right) external pure;
/// Asserts that two `int256` values are equal and includes error message into revert string on failure.
function assertEq(int256 left, int256 right, string calldata error) external pure;
/// Asserts that two `address` values are equal.
function assertEq(address left, address right) external pure;
/// Asserts that two `address` values are equal and includes error message into revert string on failure.
function assertEq(address left, address right, string calldata error) external pure;
/// Asserts that two `bytes32` values are equal.
function assertEq(bytes32 left, bytes32 right) external pure;
/// Asserts that two `bytes32` values are equal and includes error message into revert string on failure.
function assertEq(bytes32 left, bytes32 right, string calldata error) external pure;
/// Asserts that the given condition is false.
function assertFalse(bool condition) external pure;
/// Asserts that the given condition is false and includes error message into revert string on failure.
function assertFalse(bool condition, string calldata error) external pure;
/// Compares two `uint256` values. Expects first value to be greater than or equal to second.
/// Formats values with decimals in failure message.
function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure;
/// Compares two `uint256` values. Expects first value to be greater than or equal to second.
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;
/// Compares two `int256` values. Expects first value to be greater than or equal to second.
/// Formats values with decimals in failure message.
function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure;
/// Compares two `int256` values. Expects first value to be greater than or equal to second.
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;
/// Compares two `uint256` values. Expects first value to be greater than or equal to second.
function assertGe(uint256 left, uint256 right) external pure;
/// Compares two `uint256` values. Expects first value to be greater than or equal to second.
/// Includes error message into revert string on failure.
function assertGe(uint256 left, uint256 right, string calldata error) external pure;
/// Compares two `int256` values. Expects first value to be greater than or equal to second.
function assertGe(int256 left, int256 right) external pure;
/// Compares two `int256` values. Expects first value to be greater than or equal to second.
/// Includes error message into revert string on failure.
function assertGe(int256 left, int256 right, string calldata error) external pure;
/// Compares two `uint256` values. Expects first value to be greater than second.
/// Formats values with decimals in failure message.
function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure;
/// Compares two `uint256` values. Expects first value to be greater than second.
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;
/// Compares two `int256` values. Expects first value to be greater than second.
/// Formats values with decimals in failure message.
function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure;
/// Compares two `int256` values. Expects first value to be greater than second.
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;
/// Compares two `uint256` values. Expects first value to be greater than second.
function assertGt(uint256 left, uint256 right) external pure;
/// Compares two `uint256` values. Expects first value to be greater than second.
/// Includes error message into revert string on failure.
function assertGt(uint256 left, uint256 right, string calldata error) external pure;
/// Compares two `int256` values. Expects first value to be greater than second.
function assertGt(int256 left, int256 right) external pure;
/// Compares two `int256` values. Expects first value to be greater than second.
/// Includes error message into revert string on failure.
function assertGt(int256 left, int256 right, string calldata error) external pure;
/// Compares two `uint256` values. Expects first value to be less than or equal to second.
/// Formats values with decimals in failure message.
function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure;
/// Compares two `uint256` values. Expects first value to be less than or equal to second.
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;
/// Compares two `int256` values. Expects first value to be less than or equal to second.
/// Formats values with decimals in failure message.
function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure;
/// Compares two `int256` values. Expects first value to be less than or equal to second.
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;
/// Compares two `uint256` values. Expects first value to be less than or equal to second.
function assertLe(uint256 left, uint256 right) external pure;
/// Compares two `uint256` values. Expects first value to be less than or equal to second.
/// Includes error message into revert string on failure.
function assertLe(uint256 left, uint256 right, string calldata error) external pure;
/// Compares two `int256` values. Expects first value to be less than or equal to second.
function assertLe(int256 left, int256 right) external pure;
/// Compares two `int256` values. Expects first value to be less than or equal to second.
/// Includes error message into revert string on failure.
function assertLe(int256 left, int256 right, string calldata error) external pure;
/// Compares two `uint256` values. Expects first value to be less than second.
/// Formats values with decimals in failure message.
function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure;
/// Compares two `uint256` values. Expects first value to be less than second.
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;
/// Compares two `int256` values. Expects first value to be less than second.
/// Formats values with decimals in failure message.
function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure;
/// Compares two `int256` values. Expects first value to be less than second.
/// Formats values with decimals in failure message. Includes error message into revert string on failure.
function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;
/// Compares two `uint256` values. Expects first value to be less than second.
function assertLt(uint256 left, uint256 right) external pure;
/// Compares two `uint256` values. Expects first value to be less than second.
/// Includes error message into revert string on failure.
function assertLt(uint256 left, uint256 right, string calldata error) external pure;
/// Compares two `int256` values. Expects first value to be less than second.
function assertLt(int256 left, int256 right) external pure;
/// Compares two `int256` values. Expects first value to be less than second.
/// Includes error message into revert string on failure.
function assertLt(int256 left, int256 right, string calldata error) external pure;
/// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message.
function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure;
/// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message.
/// Includes error message into revert string on failure.
function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;
/// Asserts that two `int256` values are not equal, formatting them with decimals in failure message.
function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure;
/// Asserts that two `int256` values are not equal, formatting them with decimals in failure message.
/// Includes error message into revert string on failure.
function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;
/// Asserts that two `bool` values are not equal.
function assertNotEq(bool left, bool right) external pure;
/// Asserts that two `bool` values are not equal and includes error message into revert string on failure.
function assertNotEq(bool left, bool right, string calldata error) external pure;
/// Asserts that two `string` values are not equal.
function assertNotEq(string calldata left, string calldata right) external pure;
/// Asserts that two `string` values are not equal and includes error message into revert string on failure.
function assertNotEq(string calldata left, string calldata right, string calldata error) external pure;
/// Asserts that two `bytes` values are not equal.
function assertNotEq(bytes calldata left, bytes calldata right) external pure;
/// Asserts that two `bytes` values are not equal and includes error message into revert string on failure.
function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure;
/// Asserts that two arrays of `bool` values are not equal.
function assertNotEq(bool[] calldata left, bool[] calldata right) external pure;
/// Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure.
function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;
/// Asserts that two arrays of `uint256` values are not equal.
function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure;
/// Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure.
function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;
/// Asserts that two arrays of `int256` values are not equal.
function assertNotEq(int256[] calldata left, int256[] calldata right) external pure;
/// Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure.
function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;
/// Asserts that two `uint256` values are not equal.
function assertNotEq(uint256 left, uint256 right) external pure;
/// Asserts that two arrays of `address` values are not equal.
function assertNotEq(address[] calldata left, address[] calldata right) external pure;
/// Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure.
function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure;
/// Asserts that two arrays of `bytes32` values are not equal.
function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure;
/// Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure.
function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;
/// Asserts that two arrays of `string` values are not equal.
function assertNotEq(string[] calldata left, string[] calldata right) external pure;
/// Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure.
function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure;
/// Asserts that two arrays of `bytes` values are not equal.
function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure;
/// Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure.
function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;
/// Asserts that two `uint256` values are not equal and includes error message into revert string on failure.
function assertNotEq(uint256 left, uint256 right, string calldata error) external pure;
/// Asserts that two `int256` values are not equal.
function assertNotEq(int256 left, int256 right) external pure;
/// Asserts that two `int256` values are not equal and includes error message into revert string on failure.
function assertNotEq(int256 left, int256 right, string calldata error) external pure;
/// Asserts that two `address` values are not equal.
function assertNotEq(address left, address right) external pure;
/// Asserts that two `address` values are not equal and includes error message into revert string on failure.
function assertNotEq(address left, address right, string calldata error) external pure;
/// Asserts that two `bytes32` values are not equal.
function assertNotEq(bytes32 left, bytes32 right) external pure;
/// Asserts that two `bytes32` values are not equal and includes error message into revert string on failure.
function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure;
/// Asserts that the given condition is true.
function assertTrue(bool condition) external pure;
/// Asserts that the given condition is true and includes error message into revert string on failure.
function assertTrue(bool condition, string calldata error) external pure;
/// If the condition is false, discard this run's fuzz inputs and generate new ones.
function assume(bool condition) external pure;
/// Discard this run's fuzz inputs and generate new ones if next call reverted.
function assumeNoRevert() external pure;
/// Discard this run's fuzz inputs and generate new ones if next call reverts with the potential revert parameters.
function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure;
/// Discard this run's fuzz inputs and generate new ones if next call reverts with the any of the potential revert parameters.
function assumeNoRevert(PotentialRevert[] calldata potentialReverts) external pure;
/// Writes a breakpoint to jump to in the debugger.
function breakpoint(string calldata char) external pure;
/// Writes a conditional breakpoint to jump to in the debugger.
function breakpoint(string calldata char, bool value) external pure;
/// Returns true if the current Foundry version is greater than or equal to the given version.
/// The given version string must be in the format `major.minor.patch`.
/// This is equivalent to `foundryVersionCmp(version) >= 0`.
function foundryVersionAtLeast(string calldata version) external view returns (bool);
/// Compares the current Foundry version with the given version string.
/// The given version string must be in the format `major.minor.patch`.
/// Returns:
/// -1 if current Foundry version is less than the given version
/// 0 if current Foundry version equals the given version
/// 1 if current Foundry version is greater than the given version
/// This result can then be used with a comparison operator against `0`.
/// For example, to check if the current Foundry version is greater than or equal to `1.0.0`:
/// `if (foundryVersionCmp("1.0.0") >= 0) { ... }`
function foundryVersionCmp(string calldata version) external view returns (int256);
/// Returns a Chain struct for specific alias
function getChain(string calldata chainAlias) external view returns (Chain memory chain);
/// Returns a Chain struct for specific chainId
function getChain(uint256 chainId) external view returns (Chain memory chain);
/// Returns the Foundry version.
/// Format: <cargo_version>-<tag>+<git_sha_short>.<unix_build_timestamp>.<profile>
/// Sample output: 0.3.0-nightly+3cb96bde9b.1737036656.debug
/// Note: Build timestamps may vary slightly across platforms due to separate CI jobs.
/// For reliable version comparisons, use UNIX format (e.g., >= 1700000000)
/// to compare timestamps while ignoring minor time differences.
function getFoundryVersion() external view returns (string memory version);
/// Returns the RPC url for the given alias.
function rpcUrl(string calldata rpcAlias) external view returns (string memory json);
/// Returns all rpc urls and their aliases as structs.
function rpcUrlStructs() external view returns (Rpc[] memory urls);
/// Returns all rpc urls and their aliases `[alias, url][]`.
function rpcUrls() external view returns (string[2][] memory urls);
/// Suspends execution of the main thread for `duration` milliseconds.
function sleep(uint256 duration) external;
// ======== Toml ========
/// Checks if `key` exists in a TOML table.
function keyExistsToml(string calldata toml, string calldata key) external view returns (bool);
/// Parses a string of TOML data at `key` and coerces it to `address`.
function parseTomlAddress(string calldata toml, string calldata key) external pure returns (address);
/// Parses a string of TOML data at `key` and coerces it to `address[]`.
function parseTomlAddressArray(string calldata toml, string calldata key)
external
pure
returns (address[] memory);
/// Parses a string of TOML data at `key` and coerces it to `bool`.
function parseTomlBool(string calldata toml, string calldata key) external pure returns (bool);
/// Parses a string of TOML data at `key` and coerces it to `bool[]`.
function parseTomlBoolArray(string calldata toml, string calldata key) external pure returns (bool[] memory);
/// Parses a string of TOML data at `key` and coerces it to `bytes`.
function parseTomlBytes(string calldata toml, string calldata key) external pure returns (bytes memory);
/// Parses a string of TOML data at `key` and coerces it to `bytes32`.
function parseTomlBytes32(string calldata toml, string calldata key) external pure returns (bytes32);
/// Parses a string of TOML data at `key` and coerces it to `bytes32[]`.
function parseTomlBytes32Array(string calldata toml, string calldata key)
external
pure
returns (bytes32[] memory);
/// Parses a string of TOML data at `key` and coerces it to `bytes[]`.
function parseTomlBytesArray(string calldata toml, string calldata key) external pure returns (bytes[] memory);
/// Parses a string of TOML data at `key` and coerces it to `int256`.
function parseTomlInt(string calldata toml, string calldata key) external pure returns (int256);
/// Parses a string of TOML data at `key` and coerces it to `int256[]`.
function parseTomlIntArray(string calldata toml, string calldata key) external pure returns (int256[] memory);
/// Returns an array of all the keys in a TOML table.
function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys);
/// Parses a string of TOML data at `key` and coerces it to `string`.
function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory);
/// Parses a string of TOML data at `key` and coerces it to `string[]`.
function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory);
/// Parses a string of TOML data at `key` and coerces it to type array corresponding to `typeDescription`.
function parseTomlTypeArray(string calldata toml, string calldata key, string calldata typeDescription)
external
pure
returns (bytes memory);
/// Parses a string of TOML data and coerces it to type corresponding to `typeDescription`.
function parseTomlType(string calldata toml, string calldata typeDescription)
external
pure
returns (bytes memory);
/// Parses a string of TOML data at `key` and coerces it to type corresponding to `typeDescription`.
function parseTomlType(string calldata toml, string calldata key, string calldata typeDescription)
external
pure
returns (bytes memory);
/// Parses a string of TOML data at `key` and coerces it to `uint256`.
function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256);
/// Parses a string of TOML data at `key` and coerces it to `uint256[]`.
function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory);
/// ABI-encodes a TOML table.
function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData);
/// ABI-encodes a TOML table at `key`.
function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData);
/// Takes serialized JSON, converts to TOML and write a serialized TOML to a file.
function writeToml(string calldata json, string calldata path) external;
/// Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = <value_key.>
/// This is useful to replace a specific value of a TOML file, without having to parse the entire thing.
function writeToml(string calldata json, string calldata path, string calldata valueKey) external;
// ======== Utilities ========
/// Compute the address of a contract created with CREATE2 using the given CREATE2 deployer.
function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer)
external
pure
returns (address);
/// Compute the address of a contract created with CREATE2 using the default CREATE2 deployer.
function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address);
/// Compute the address a contract will be deployed at for a given deployer address and nonce.
function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address);
/// Utility cheatcode to copy storage of `from` contract to another `to` contract.
function copyStorage(address from, address to) external;
/// Returns ENS namehash for provided string.
function ensNamehash(string calldata name) external pure returns (bytes32);
/// Gets the label for the specified address.
function getLabel(address account) external view returns (string memory currentLabel);
/// Labels an address in call traces.
function label(address account, string calldata newLabel) external;
/// Pauses collection of call traces. Useful in cases when you want to skip tracing of
/// complex calls which are not useful for debugging.
function pauseTracing() external view;
/// Returns a random `address`.
function randomAddress() external returns (address);
/// Returns a random `bool`.
function randomBool() external view returns (bool);
/// Returns a random byte array value of the given length.
function randomBytes(uint256 len) external view returns (bytes memory);
/// Returns a random fixed-size byte array of length 4.
function randomBytes4() external view returns (bytes4);
/// Returns a random fixed-size byte array of length 8.
function randomBytes8() external view returns (bytes8);
/// Returns a random `int256` value.
function randomInt() external view returns (int256);
/// Returns a random `int256` value of given bits.
function randomInt(uint256 bits) external view returns (int256);
/// Returns a random uint256 value.
function randomUint() external returns (uint256);
/// Returns random uint256 value between the provided range (=min..=max).
function randomUint(uint256 min, uint256 max) external returns (uint256);
/// Returns a random `uint256` value of given bits.
function randomUint(uint256 bits) external view returns (uint256);
/// Unpauses collection of call traces.
function resumeTracing() external view;
/// Utility cheatcode to set arbitrary storage for given target address.
function setArbitraryStorage(address target) external;
/// Utility cheatcode to set arbitrary storage for given target address and overwrite
/// any storage slots that have been previously set.
function setArbitraryStorage(address target, bool overwrite) external;
/// Randomly shuffles an array.
function shuffle(uint256[] calldata array) external returns (uint256[] memory);
/// Sorts an array in ascending order.
function sort(uint256[] calldata array) external returns (uint256[] memory);
/// Encodes a `bytes` value to a base64url string.
function toBase64URL(bytes calldata data) external pure returns (string memory);
/// Encodes a `string` value to a base64url string.
function toBase64URL(string calldata data) external pure returns (string memory);
/// Encodes a `bytes` value to a base64 string.
function toBase64(bytes calldata data) external pure returns (string memory);
/// Encodes a `string` value to a base64 string.
function toBase64(string calldata data) external pure returns (string memory);
}
/// The `Vm` interface does allow manipulation of the EVM state. These are all intended to be used
/// in tests, but it is not recommended to use these cheats in scripts.
interface Vm is VmSafe {
// ======== EVM ========
/// Utility cheatcode to set an EIP-2930 access list for all subsequent transactions.
function accessList(AccessListItem[] calldata access) external;
/// Returns the identifier of the currently active fork. Reverts if no fork is currently active.
function activeFork() external view returns (uint256 forkId);
/// In forking mode, explicitly grant the given address cheatcode access.
function allowCheatcodes(address account) external;
/// Sets `block.blobbasefee`
function blobBaseFee(uint256 newBlobBaseFee) external;
/// Sets the blobhashes in the transaction.
/// Not available on EVM versions before Cancun.
/// If used on unsupported EVM versions it will revert.
function blobhashes(bytes32[] calldata hashes) external;
/// Sets `block.chainid`.
function chainId(uint256 newChainId) external;
/// Clears all mocked calls.
function clearMockedCalls() external;
/// Clones a source account code, state, balance and nonce to a target account and updates in-memory EVM state.
function cloneAccount(address source, address target) external;
/// Sets `block.coinbase`.
function coinbase(address newCoinbase) external;
/// Marks the slots of an account and the account address as cold.
function cool(address target) external;
/// Utility cheatcode to mark specific storage slot as cold, simulating no prior read.
function coolSlot(address target, bytes32 slot) external;
/// Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork.
function createFork(string calldata urlOrAlias) external returns (uint256 forkId);
/// Creates a new fork with the given endpoint and block and returns the identifier of the fork.
function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId);
/// Creates a new fork with the given endpoint and at the block the given transaction was mined in,
/// replays all transaction mined in the block before the transaction, and returns the identifier of the fork.
function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId);
/// Creates and also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork.
function createSelectFork(string calldata urlOrAlias) external returns (uint256 forkId);
/// Creates and also selects a new fork with the given endpoint and block and returns the identifier of the fork.
function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId);
/// Creates and also selects new fork with the given endpoint and at the block the given transaction was mined in,
/// replays all transaction mined in the block before the transaction, returns the identifier of the fork.
function createSelectFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId);
/// Sets an address' balance.
function deal(address account, uint256 newBalance) external;
/// Removes the snapshot with the given ID created by `snapshot`.
/// Takes the snapshot ID to delete.
/// Returns `true` if the snapshot was successfully deleted.
/// Returns `false` if the snapshot does not exist.
function deleteStateSnapshot(uint256 snapshotId) external returns (bool success);
/// Removes _all_ snapshots previously created by `snapshot`.
function deleteStateSnapshots() external;
/// Sets `block.difficulty`.
/// Not available on EVM versions from Paris onwards. Use `prevrandao` instead.
/// Reverts if used on unsupported EVM versions.
function difficulty(uint256 newDifficulty) external;
/// Dump a genesis JSON file's `allocs` to disk.
function dumpState(string calldata pathToStateJson) external;
/// Sets an address' code.
function etch(address target, bytes calldata newRuntimeBytecode) external;
/// Sets `block.basefee`.
function fee(uint256 newBasefee) external;
/// Gets the blockhashes from the current transaction.
/// Not available on EVM versions before Cancun.
/// If used on unsupported EVM versions it will revert.
function getBlobhashes() external view returns (bytes32[] memory hashes);
/// Returns true if the account is marked as persistent.
function isPersistent(address account) external view returns (bool persistent);
/// Load a genesis JSON file's `allocs` into the in-memory EVM state.
function loadAllocs(string calldata pathToAllocsJson) external;
/// Marks that the account(s) should use persistent storage across fork swaps in a multifork setup
/// Meaning, changes made to the state of this account will be kept when switching forks.
function makePersistent(address account) external;
/// See `makePersistent(address)`.
function makePersistent(address account0, address account1) external;
/// See `makePersistent(address)`.
function makePersistent(address account0, address account1, address account2) external;
/// See `makePersistent(address)`.
function makePersistent(address[] calldata accounts) external;
/// Reverts a call to an address with specified revert data.
function mockCallRevert(address callee, bytes calldata data, bytes calldata revertData) external;
/// Reverts a call to an address with a specific `msg.value`, with specified revert data.
function mockCallRevert(address callee, uint256 msgValue, bytes calldata data, bytes calldata revertData)
external;
/// Reverts a call to an address with specified revert data.
/// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.
function mockCallRevert(address callee, bytes4 data, bytes calldata revertData) external;
/// Reverts a call to an address with a specific `msg.value`, with specified revert data.
/// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.
function mockCallRevert(address callee, uint256 msgValue, bytes4 data, bytes calldata revertData) external;
/// Mocks a call to an address, returning specified data.
/// Calldata can either be strict or a partial match, e.g. if you only
/// pass a Solidity selector to the expected calldata, then the entire Solidity
/// function will be mocked.
function mockCall(address callee, bytes calldata data, bytes calldata returnData) external;
/// Mocks a call to an address with a specific `msg.value`, returning specified data.
/// Calldata match takes precedence over `msg.value` in case of ambiguity.
function mockCall(address callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external;
/// Mocks a call to an address, returning specified data.
/// Calldata can either be strict or a partial match, e.g. if you only
/// pass a Solidity selector to the expected calldata, then the entire Solidity
/// function will be mocked.
/// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.
function mockCall(address callee, bytes4 data, bytes calldata returnData) external;
/// Mocks a call to an address with a specific `msg.value`, returning specified data.
/// Calldata match takes precedence over `msg.value` in case of ambiguity.
/// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.
function mockCall(address callee, uint256 msgValue, bytes4 data, bytes calldata returnData) external;
/// Mocks multiple calls to an address, returning specified data for each call.
function mockCalls(address callee, bytes calldata data, bytes[] calldata returnData) external;
/// Mocks multiple calls to an address with a specific `msg.value`, returning specified data for each call.
function mockCalls(address callee, uint256 msgValue, bytes calldata data, bytes[] calldata returnData) external;
/// Whenever a call is made to `callee` with calldata `data`, this cheatcode instead calls
/// `target` with the same calldata. This functionality is similar to a delegate call made to
/// `target` contract from `callee`.
/// Can be used to substitute a call to a function with another implementation that captures
/// the primary logic of the original function but is easier to reason about.
/// If calldata is not a strict match then partial match by selector is attempted.
function mockFunction(address callee, address target, bytes calldata data) external;
/// Utility cheatcode to remove any EIP-2930 access list set by `accessList` cheatcode.
function noAccessList() external;
/// Sets the *next* call's `msg.sender` to be the input address.
function prank(address msgSender) external;
/// Sets the *next* call's `msg.sender` to be the input address, and the `tx.origin` to be the second input.
function prank(address msgSender, address txOrigin) external;
/// Sets the *next* delegate call's `msg.sender` to be the input address.
function prank(address msgSender, bool delegateCall) external;
/// Sets the *next* delegate call's `msg.sender` to be the input address, and the `tx.origin` to be the second input.
function prank(address msgSender, address txOrigin, bool delegateCall) external;
/// Sets `block.prevrandao`.
/// Not available on EVM versions before Paris. Use `difficulty` instead.
/// If used on unsupported EVM versions it will revert.
function prevrandao(bytes32 newPrevrandao) external;
/// Sets `block.prevrandao`.
/// Not available on EVM versions before Paris. Use `difficulty` instead.
/// If used on unsupported EVM versions it will revert.
function prevrandao(uint256 newPrevrandao) external;
/// Reads the current `msg.sender` and `tx.origin` from state and reports if there is any active caller modification.
function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin);
/// Resets the nonce of an account to 0 for EOAs and 1 for contract accounts.
function resetNonce(address account) external;
/// Revert the state of the EVM to a previous snapshot
/// Takes the snapshot ID to revert to.
/// Returns `true` if the snapshot was successfully reverted.
/// Returns `false` if the snapshot does not exist.
/// **Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteStateSnapshot`.
function revertToState(uint256 snapshotId) external returns (bool success);
/// Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots
/// Takes the snapshot ID to revert to.
/// Returns `true` if the snapshot was successfully reverted and deleted.
/// Returns `false` if the snapshot does not exist.
function revertToStateAndDelete(uint256 snapshotId) external returns (bool success);
/// Revokes persistent status from the address, previously added via `makePersistent`.
function revokePersistent(address account) external;
/// See `revokePersistent(address)`.
function revokePersistent(address[] calldata accounts) external;
/// Sets `block.height`.
function roll(uint256 newHeight) external;
/// Updates the currently active fork to given block number
/// This is similar to `roll` but for the currently active fork.
function rollFork(uint256 blockNumber) external;
/// Updates the currently active fork to given transaction. This will `rollFork` with the number
/// of the block the transaction was mined in and replays all transaction mined before it in the block.
function rollFork(bytes32 txHash) external;
/// Updates the given fork to given block number.
function rollFork(uint256 forkId, uint256 blockNumber) external;
/// Updates the given fork to block number of the given transaction and replays all transaction mined before it in the block.
function rollFork(uint256 forkId, bytes32 txHash) external;
/// Takes a fork identifier created by `createFork` and sets the corresponding forked state as active.
function selectFork(uint256 forkId) external;
/// Set blockhash for the current block.
/// It only sets the blockhash for blocks where `block.number - 256 <= number < block.number`.
function setBlockhash(uint256 blockNumber, bytes32 blockHash) external;
/// Sets the nonce of an account. Must be higher than the current nonce of the account.
function setNonce(address account, uint64 newNonce) external;
/// Sets the nonce of an account to an arbitrary value.
function setNonceUnsafe(address account, uint64 newNonce) external;
/// Snapshot capture the gas usage of the last call by name from the callee perspective.
function snapshotGasLastCall(string calldata name) external returns (uint256 gasUsed);
/// Snapshot capture the gas usage of the last call by name in a group from the callee perspective.
function snapshotGasLastCall(string calldata group, string calldata name) external returns (uint256 gasUsed);
/// Snapshot the current state of the evm.
/// Returns the ID of the snapshot that was created.
/// To revert a snapshot use `revertToState`.
function snapshotState() external returns (uint256 snapshotId);
/// Snapshot capture an arbitrary numerical value by name.
/// The group name is derived from the contract name.
function snapshotValue(string calldata name, uint256 value) external;
/// Snapshot capture an arbitrary numerical value by name in a group.
function snapshotValue(string calldata group, string calldata name, uint256 value) external;
/// Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called.
function startPrank(address msgSender) external;
/// Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input.
function startPrank(address msgSender, address txOrigin) external;
/// Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called.
function startPrank(address msgSender, bool delegateCall) external;
/// Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input.
function startPrank(address msgSender, address txOrigin, bool delegateCall) external;
/// Start a snapshot capture of the current gas usage by name.
/// The group name is derived from the contract name.
function startSnapshotGas(string calldata name) external;
/// Start a snapshot capture of the current gas usage by name in a group.
function startSnapshotGas(string calldata group, string calldata name) external;
/// Resets subsequent calls' `msg.sender` to be `address(this)`.
function stopPrank() external;
/// Stop the snapshot capture of the current gas by latest snapshot name, capturing the gas used since the start.
function stopSnapshotGas() external returns (uint256 gasUsed);
/// Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start.
/// The group name is derived from the contract name.
function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed);
/// Stop the snapshot capture of the current gas usage by name in a group, capturing the gas used since the start.
function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed);
/// Stores a value to an address' storage slot.
function store(address target, bytes32 slot, bytes32 value) external;
/// Fetches the given transaction from the active fork and executes it on the current state.
function transact(bytes32 txHash) external;
/// Fetches the given transaction from the given fork and executes it on the current state.
function transact(uint256 forkId, bytes32 txHash) external;
/// Sets `tx.gasprice`.
function txGasPrice(uint256 newGasPrice) external;
/// Utility cheatcode to mark specific storage slot as warm, simulating a prior read.
function warmSlot(address target, bytes32 slot) external;
/// Sets `block.timestamp`.
function warp(uint256 newTimestamp) external;
/// `deleteSnapshot` is being deprecated in favor of `deleteStateSnapshot`. It will be removed in future versions.
function deleteSnapshot(uint256 snapshotId) external returns (bool success);
/// `deleteSnapshots` is being deprecated in favor of `deleteStateSnapshots`. It will be removed in future versions.
function deleteSnapshots() external;
/// `revertToAndDelete` is being deprecated in favor of `revertToStateAndDelete`. It will be removed in future versions.
function revertToAndDelete(uint256 snapshotId) external returns (bool success);
/// `revertTo` is being deprecated in favor of `revertToState`. It will be removed in future versions.
function revertTo(uint256 snapshotId) external returns (bool success);
/// `snapshot` is being deprecated in favor of `snapshotState`. It will be removed in future versions.
function snapshot() external returns (uint256 snapshotId);
// ======== Testing ========
/// Expect a call to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas.
function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external;
/// Expect given number of calls to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas.
function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count)
external;
/// Expects a call to an address with the specified calldata.
/// Calldata can either be a strict or a partial match.
function expectCall(address callee, bytes calldata data) external;
/// Expects given number of calls to an address with the specified calldata.
function expectCall(address callee, bytes calldata data, uint64 count) external;
/// Expects a call to an address with the specified `msg.value` and calldata.
function expectCall(address callee, uint256 msgValue, bytes calldata data) external;
/// Expects given number of calls to an address with the specified `msg.value` and calldata.
function expectCall(address callee, uint256 msgValue, bytes calldata data, uint64 count) external;
/// Expect a call to an address with the specified `msg.value`, gas, and calldata.
function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) external;
/// Expects given number of calls to an address with the specified `msg.value`, gas, and calldata.
function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data, uint64 count) external;
/// Expects the deployment of the specified bytecode by the specified address using the CREATE opcode
function expectCreate(bytes calldata bytecode, address deployer) external;
/// Expects the deployment of the specified bytecode by the specified address using the CREATE2 opcode
function expectCreate2(bytes calldata bytecode, address deployer) external;
/// Prepare an expected anonymous log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).
/// Call this function, then emit an anonymous event, then call a function. Internally after the call, we check if
/// logs were emitted in the expected order with the expected topics and data (as specified by the booleans).
function expectEmitAnonymous(bool checkTopic0, bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData)
external;
/// Same as the previous method, but also checks supplied address against emitting contract.
function expectEmitAnonymous(
bool checkTopic0,
bool checkTopic1,
bool checkTopic2,
bool checkTopic3,
bool checkData,
address emitter
) external;
/// Prepare an expected anonymous log with all topic and data checks enabled.
/// Call this function, then emit an anonymous event, then call a function. Internally after the call, we check if
/// logs were emitted in the expected order with the expected topics and data.
function expectEmitAnonymous() external;
/// Same as the previous method, but also checks supplied address against emitting contract.
function expectEmitAnonymous(address emitter) external;
/// Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).
/// Call this function, then emit an event, then call a function. Internally after the call, we check if
/// logs were emitted in the expected order with the expected topics and data (as specified by the booleans).
function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;
/// Same as the previous method, but also checks supplied address against emitting contract.
function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter)
external;
/// Prepare an expected log with all topic and data checks enabled.
/// Call this function, then emit an event, then call a function. Internally after the call, we check if
/// logs were emitted in the expected order with the expected topics and data.
function expectEmit() external;
/// Same as the previous method, but also checks supplied address against emitting contract.
function expectEmit(address emitter) external;
/// Expect a given number of logs with the provided topics.
function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, uint64 count) external;
/// Expect a given number of logs from a specific emitter with the provided topics.
function expectEmit(
bool checkTopic1,
bool checkTopic2,
bool checkTopic3,
bool checkData,
address emitter,
uint64 count
) external;
/// Expect a given number of logs with all topic and data checks enabled.
function expectEmit(uint64 count) external;
/// Expect a given number of logs from a specific emitter with all topic and data checks enabled.
function expectEmit(address emitter, uint64 count) external;
/// Expects an error on next call that starts with the revert data.
function expectPartialRevert(bytes4 revertData) external;
/// Expects an error on next call to reverter address, that starts with the revert data.
function expectPartialRevert(bytes4 revertData, address reverter) external;
/// Expects an error on next call with any revert data.
function expectRevert() external;
/// Expects an error on next call that exactly matches the revert data.
function expectRevert(bytes4 revertData) external;
/// Expects a `count` number of reverts from the upcoming calls from the reverter address that match the revert data.
function expectRevert(bytes4 revertData, address reverter, uint64 count) external;
/// Expects a `count` number of reverts from the upcoming calls from the reverter address that exactly match the revert data.
function expectRevert(bytes calldata revertData, address reverter, uint64 count) external;
/// Expects an error on next call that exactly matches the revert data.
function expectRevert(bytes calldata revertData) external;
/// Expects an error with any revert data on next call to reverter address.
function expectRevert(address reverter) external;
/// Expects an error from reverter address on next call, with any revert data.
function expectRevert(bytes4 revertData, address reverter) external;
/// Expects an error from reverter address on next call, that exactly matches the revert data.
function expectRevert(bytes calldata revertData, address reverter) external;
/// Expects a `count` number of reverts from the upcoming calls with any revert data or reverter.
function expectRevert(uint64 count) external;
/// Expects a `count` number of reverts from the upcoming calls that match the revert data.
function expectRevert(bytes4 revertData, uint64 count) external;
/// Expects a `count` number of reverts from the upcoming calls that exactly match the revert data.
function expectRevert(bytes calldata revertData, uint64 count) external;
/// Expects a `count` number of reverts from the upcoming calls from the reverter address.
function expectRevert(address reverter, uint64 count) external;
/// Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other
/// memory is written to, the test will fail. Can be called multiple times to add more ranges to the set.
function expectSafeMemory(uint64 min, uint64 max) external;
/// Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext.
/// If any other memory is written to, the test will fail. Can be called multiple times to add more ranges
/// to the set.
function expectSafeMemoryCall(uint64 min, uint64 max) external;
/// Marks a test as skipped. Must be called at the top level of a test.
function skip(bool skipTest) external;
/// Marks a test as skipped with a reason. Must be called at the top level of a test.
function skip(bool skipTest, string calldata reason) external;
/// Stops all safe memory expectation in the current subcontext.
function stopExpectSafeMemory() external;
// ======== Utilities ========
/// Causes the next contract creation (via new) to fail and return its initcode in the returndata buffer.
/// This allows type-safe access to the initcode payload that would be used for contract creation.
/// Example usage:
/// vm.interceptInitcode();
/// bytes memory initcode;
/// try new MyContract(param1, param2) { assert(false); }
/// catch (bytes memory interceptedInitcode) { initcode = interceptedInitcode; }
function interceptInitcode() external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)
pragma solidity ^0.8.20;
/**
* @dev Helper library for emitting standardized panic codes.
*
* ```solidity
* contract Example {
* using Panic for uint256;
*
* // Use any of the declared internal constants
* function foo() { Panic.GENERIC.panic(); }
*
* // Alternatively
* function foo() { Panic.panic(Panic.GENERIC); }
* }
* ```
*
* Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
*
* _Available since v5.1._
*/
// slither-disable-next-line unused-state
library Panic {
/// @dev generic / unspecified error
uint256 internal constant GENERIC = 0x00;
/// @dev used by the assert() builtin
uint256 internal constant ASSERT = 0x01;
/// @dev arithmetic underflow or overflow
uint256 internal constant UNDER_OVERFLOW = 0x11;
/// @dev division or modulo by zero
uint256 internal constant DIVISION_BY_ZERO = 0x12;
/// @dev enum conversion error
uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
/// @dev invalid encoding in storage
uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
/// @dev empty array pop
uint256 internal constant EMPTY_ARRAY_POP = 0x31;
/// @dev array out of bounds access
uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
/// @dev resource error (too large allocation or too large array)
uint256 internal constant RESOURCE_ERROR = 0x41;
/// @dev calling invalid internal function
uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;
/// @dev Reverts with a panic code. Recommended to use with
/// the internal constants with predefined codes.
function panic(uint256 code) internal pure {
assembly ("memory-safe") {
mstore(0x00, 0x4e487b71)
mstore(0x20, code)
revert(0x1c, 0x24)
}
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {SlotDerivation} from "@oz/utils/SlotDerivation.sol";
import {TransientSlot} from "@oz/utils/TransientSlot.sol";
/**
* @title SampleLib
* @author Anaxandridas II
* @notice A tiny library to draw committee indices using a sample without replacement algorithm.
*/
library SampleLib {
using SlotDerivation for string;
using SlotDerivation for bytes32;
using TransientSlot for *;
// Namespace for transient storage keys used within this library
string private constant OVERRIDE_NAMESPACE = "Aztec.SampleLib.Override";
/**
* Compute Committee
*
* @param _committeeSize - The size of the committee
* @param _indexCount - The total number of indices
* @param _seed - The seed to use for shuffling
*
* @dev assumption, _committeeSize <= _indexCount
*
* @return indices - The indices of the committee
*/
function computeCommittee(uint256 _committeeSize, uint256 _indexCount, uint256 _seed)
internal
returns (uint256[] memory)
{
require(_committeeSize <= _indexCount, Errors.SampleLib__SampleLargerThanIndex(_committeeSize, _indexCount));
if (_committeeSize == 0) {
return new uint256[](0);
}
uint256[] memory sampledIndices = new uint256[](_committeeSize);
uint256 upperLimit = _indexCount - 1;
for (uint256 index = 0; index < _committeeSize; index++) {
uint256 sampledIndex = computeSampleIndex(index, upperLimit + 1, _seed);
// Get index, or its swapped override
sampledIndices[index] = getValue(sampledIndex);
if (upperLimit > 0) {
// Swap with the last index
setOverrideValue(sampledIndex, getValue(upperLimit));
// Decrement the upper limit
upperLimit--;
}
}
// Clear transient storage.
// Note that we are clearing the `sampleIndices` and do not keep track of a separate list of
// `sampleIndex` values that were written to. The reasoning is that we only overwrite values for
// duplicate cases, so `sampleIndices` is a superset of the `sampleIndex` values that have been drawn
// (to account for duplicates). Therefore, clearing `sampleIndices` clears everything.
// Due to the cost of `tstore` and `tload` operations, it is cheaper to overwrite all values
// rather than checking if there is anything to override.
for (uint256 i = 0; i < _committeeSize; i++) {
setOverrideValue(sampledIndices[i], 0);
}
return sampledIndices;
}
function setOverrideValue(uint256 _index, uint256 _value) internal {
OVERRIDE_NAMESPACE.erc7201Slot().deriveMapping(_index).asUint256().tstore(_value);
}
function getValue(uint256 _index) internal view returns (uint256) {
uint256 overrideValue = getOverrideValue(_index);
if (overrideValue != 0) {
return overrideValue;
}
return _index;
}
function getOverrideValue(uint256 _index) internal view returns (uint256) {
return OVERRIDE_NAMESPACE.erc7201Slot().deriveMapping(_index).asUint256().tload();
}
/**
* @notice Compute the sample index for a given index, seed and index count.
*
* @param _index - The index to shuffle
* @param _indexCount - The total number of indices
* @param _seed - The seed to use for shuffling
*
* @return shuffledIndex - The shuffled index
*/
function computeSampleIndex(uint256 _index, uint256 _indexCount, uint256 _seed) internal pure returns (uint256) {
// Cannot modulo by 0 and if 1, then only acceptable value is 0
if (_indexCount <= 1) {
return 0;
}
return uint256(keccak256(abi.encodePacked(_seed, _index))) % _indexCount;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/cryptography/MessageHashUtils.sol)
pragma solidity ^0.8.24;
import {Strings} from "../Strings.sol";
/**
* @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
*
* The library provides methods for generating a hash of a message that conforms to the
* https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
* specifications.
*/
library MessageHashUtils {
error ERC5267ExtensionsNotSupported();
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing a bytes32 `messageHash` with
* `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
* keccak256, although any bytes32 value can be safely used because the final digest will
* be re-hashed.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
}
}
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing an arbitrary `message` with
* `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
}
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x00` (data with intended validator).
*
* The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
* `validator` address. Then hashing the result.
*
* See {ECDSA-recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(hex"19_00", validator, data));
}
/**
* @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.
*/
function toDataWithIntendedValidatorHash(
address validator,
bytes32 messageHash
) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, hex"19_00")
mstore(0x02, shl(96, validator))
mstore(0x16, messageHash)
digest := keccak256(0x00, 0x36)
}
}
/**
* @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).
*
* The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
* `\x19\x01` and hashing the result. It corresponds to the hash signed by the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
*
* See {ECDSA-recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
/**
* @dev Returns the EIP-712 domain separator constructed from an `eip712Domain`. See {IERC5267-eip712Domain}
*
* This function dynamically constructs the domain separator based on which fields are present in the
* `fields` parameter. It contains flags that indicate which domain fields are present:
*
* * Bit 0 (0x01): name
* * Bit 1 (0x02): version
* * Bit 2 (0x04): chainId
* * Bit 3 (0x08): verifyingContract
* * Bit 4 (0x10): salt
*
* Arguments that correspond to fields which are not present in `fields` are ignored. For example, if `fields` is
* `0x0f` (`0b01111`), then the `salt` parameter is ignored.
*/
function toDomainSeparator(
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt
) internal pure returns (bytes32 hash) {
return
toDomainSeparator(
fields,
keccak256(bytes(name)),
keccak256(bytes(version)),
chainId,
verifyingContract,
salt
);
}
/// @dev Variant of {toDomainSeparator-bytes1-string-string-uint256-address-bytes32} that uses hashed name and version.
function toDomainSeparator(
bytes1 fields,
bytes32 nameHash,
bytes32 versionHash,
uint256 chainId,
address verifyingContract,
bytes32 salt
) internal pure returns (bytes32 hash) {
bytes32 domainTypeHash = toDomainTypeHash(fields);
assembly ("memory-safe") {
// align fields to the right for easy processing
fields := shr(248, fields)
// FMP used as scratch space
let fmp := mload(0x40)
mstore(fmp, domainTypeHash)
let ptr := add(fmp, 0x20)
if and(fields, 0x01) {
mstore(ptr, nameHash)
ptr := add(ptr, 0x20)
}
if and(fields, 0x02) {
mstore(ptr, versionHash)
ptr := add(ptr, 0x20)
}
if and(fields, 0x04) {
mstore(ptr, chainId)
ptr := add(ptr, 0x20)
}
if and(fields, 0x08) {
mstore(ptr, verifyingContract)
ptr := add(ptr, 0x20)
}
if and(fields, 0x10) {
mstore(ptr, salt)
ptr := add(ptr, 0x20)
}
hash := keccak256(fmp, sub(ptr, fmp))
}
}
/// @dev Builds an EIP-712 domain type hash depending on the `fields` provided, following https://eips.ethereum.org/EIPS/eip-5267[ERC-5267]
function toDomainTypeHash(bytes1 fields) internal pure returns (bytes32 hash) {
if (fields & 0x20 == 0x20) revert ERC5267ExtensionsNotSupported();
assembly ("memory-safe") {
// align fields to the right for easy processing
fields := shr(248, fields)
// FMP used as scratch space
let fmp := mload(0x40)
mstore(fmp, "EIP712Domain(")
let ptr := add(fmp, 0x0d)
// name field
if and(fields, 0x01) {
mstore(ptr, "string name,")
ptr := add(ptr, 0x0c)
}
// version field
if and(fields, 0x02) {
mstore(ptr, "string version,")
ptr := add(ptr, 0x0f)
}
// chainId field
if and(fields, 0x04) {
mstore(ptr, "uint256 chainId,")
ptr := add(ptr, 0x10)
}
// verifyingContract field
if and(fields, 0x08) {
mstore(ptr, "address verifyingContract,")
ptr := add(ptr, 0x1a)
}
// salt field
if and(fields, 0x10) {
mstore(ptr, "bytes32 salt,")
ptr := add(ptr, 0x0d)
}
// if any field is enabled, remove the trailing comma
ptr := sub(ptr, iszero(iszero(and(fields, 0x1f))))
// add the closing brace
mstore8(ptr, 0x29) // add closing brace
ptr := add(ptr, 1)
hash := keccak256(fmp, sub(ptr, fmp))
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/SlotDerivation.sol)
// This file was procedurally generated from scripts/generate/templates/SlotDerivation.js.
pragma solidity ^0.8.20;
/**
* @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots
* corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by
* the solidity language / compiler.
*
* See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.].
*
* Example usage:
* ```solidity
* contract Example {
* // Add the library methods
* using StorageSlot for bytes32;
* using SlotDerivation for *;
*
* // Declare a namespace
* string private constant _NAMESPACE = "<namespace>"; // eg. OpenZeppelin.Slot
*
* function setValueInNamespace(uint256 key, address newValue) internal {
* _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue;
* }
*
* function getValueInNamespace(uint256 key) internal view returns (address) {
* return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value;
* }
* }
* ```
*
* TIP: Consider using this library along with {StorageSlot}.
*
* NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking
* upgrade safety will ignore the slots accessed through this library.
*
* _Available since v5.1._
*/
library SlotDerivation {
/**
* @dev Derive an ERC-7201 slot from a string (namespace).
*/
function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) {
assembly ("memory-safe") {
mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1))
slot := and(keccak256(0x00, 0x20), not(0xff))
}
}
/**
* @dev Add an offset to a slot to get the n-th element of a structure or an array.
*/
function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) {
unchecked {
return bytes32(uint256(slot) + pos);
}
}
/**
* @dev Derive the location of the first element in an array from the slot where the length is stored.
*/
function deriveArray(bytes32 slot) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, slot)
result := keccak256(0x00, 0x20)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, and(key, shr(96, not(0))))
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, iszero(iszero(key)))
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
let length := mload(key)
let begin := add(key, 0x20)
let end := add(begin, length)
let cache := mload(end)
mstore(end, slot)
result := keccak256(begin, add(length, 0x20))
mstore(end, cache)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
let length := mload(key)
let begin := add(key, 0x20)
let end := add(begin, length)
let cache := mload(end)
mstore(end, slot)
result := keccak256(begin, add(length, 0x20))
mstore(end, cache)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.24;
import {Arrays} from "../Arrays.sol";
import {Math} from "../math/Math.sol";
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
* - Set can be cleared (all elements removed) in O(n).
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* The following types are supported:
*
* - `bytes32` (`Bytes32Set`) since v3.3.0
* - `address` (`AddressSet`) since v3.3.0
* - `uint256` (`UintSet`) since v3.3.0
* - `string` (`StringSet`) since v5.4.0
* - `bytes` (`BytesSet`) since v5.4.0
* - `bytes4` (`Bytes4Set`) since v5.6.0
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: This function has an unbounded cost that scales with set size. Developers should keep in mind that
* using it may render the function uncallable if the set grows to the point where clearing it consumes too much
* gas to fit in a block.
*/
function _clear(Set storage set) private {
uint256 len = _length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set, uint256 start, uint256 end) private view returns (bytes32[] memory) {
unchecked {
end = Math.min(end, _length(set));
start = Math.min(start, end);
uint256 len = end - start;
bytes32[] memory result = new bytes32[](len);
for (uint256 i = 0; i < len; ++i) {
result[i] = Arrays.unsafeAccess(set._values, start + i).value;
}
return result;
}
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(Bytes32Set storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set, uint256 start, uint256 end) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner, start, end);
bytes32[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// Bytes4Set
struct Bytes4Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes4Set storage set, bytes4 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes4Set storage set, bytes4 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(Bytes4Set storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes4Set storage set, bytes4 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes4Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes4Set storage set, uint256 index) internal view returns (bytes4) {
return bytes4(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes4Set storage set) internal view returns (bytes4[] memory) {
bytes32[] memory store = _values(set._inner);
bytes4[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes4Set storage set, uint256 start, uint256 end) internal view returns (bytes4[] memory) {
bytes32[] memory store = _values(set._inner, start, end);
bytes4[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(AddressSet storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set, uint256 start, uint256 end) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner, start, end);
address[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(UintSet storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set, uint256 start, uint256 end) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner, start, end);
uint256[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
struct StringSet {
// Storage of set values
string[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(string value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(StringSet storage set, string memory value) internal returns (bool) {
if (!contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(StringSet storage set, string memory value) internal returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
string memory lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(StringSet storage set) internal {
uint256 len = length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(StringSet storage set, string memory value) internal view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(StringSet storage set) internal view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(StringSet storage set, uint256 index) internal view returns (string memory) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(StringSet storage set) internal view returns (string[] memory) {
return set._values;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(StringSet storage set, uint256 start, uint256 end) internal view returns (string[] memory) {
unchecked {
end = Math.min(end, length(set));
start = Math.min(start, end);
uint256 len = end - start;
string[] memory result = new string[](len);
for (uint256 i = 0; i < len; ++i) {
result[i] = Arrays.unsafeAccess(set._values, start + i).value;
}
return result;
}
}
struct BytesSet {
// Storage of set values
bytes[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(BytesSet storage set, bytes memory value) internal returns (bool) {
if (!contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(BytesSet storage set, bytes memory value) internal returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes memory lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(BytesSet storage set) internal {
uint256 len = length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(BytesSet storage set, bytes memory value) internal view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(BytesSet storage set) internal view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(BytesSet storage set, uint256 index) internal view returns (bytes memory) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(BytesSet storage set) internal view returns (bytes[] memory) {
return set._values;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(BytesSet storage set, uint256 start, uint256 end) internal view returns (bytes[] memory) {
unchecked {
end = Math.min(end, length(set));
start = Math.min(start, end);
uint256 len = end - start;
bytes[] memory result = new bytes[](len);
for (uint256 i = 0; i < len; ++i) {
result[i] = Arrays.unsafeAccess(set._values, start + i).value;
}
return result;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/TransientSlot.sol)
// This file was procedurally generated from scripts/generate/templates/TransientSlot.js.
pragma solidity ^0.8.24;
/**
* @dev Library for reading and writing value-types to specific transient storage slots.
*
* Transient slots are often used to store temporary values that are removed after the current transaction.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* * Example reading and writing values using transient storage:
* ```solidity
* contract Lock {
* using TransientSlot for *;
*
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;
*
* modifier locked() {
* require(!_LOCK_SLOT.asBoolean().tload());
*
* _LOCK_SLOT.asBoolean().tstore(true);
* _;
* _LOCK_SLOT.asBoolean().tstore(false);
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library TransientSlot {
/**
* @dev UDVT that represents a slot holding an address.
*/
type AddressSlot is bytes32;
/**
* @dev Cast an arbitrary slot to a AddressSlot.
*/
function asAddress(bytes32 slot) internal pure returns (AddressSlot) {
return AddressSlot.wrap(slot);
}
/**
* @dev UDVT that represents a slot holding a bool.
*/
type BooleanSlot is bytes32;
/**
* @dev Cast an arbitrary slot to a BooleanSlot.
*/
function asBoolean(bytes32 slot) internal pure returns (BooleanSlot) {
return BooleanSlot.wrap(slot);
}
/**
* @dev UDVT that represents a slot holding a bytes32.
*/
type Bytes32Slot is bytes32;
/**
* @dev Cast an arbitrary slot to a Bytes32Slot.
*/
function asBytes32(bytes32 slot) internal pure returns (Bytes32Slot) {
return Bytes32Slot.wrap(slot);
}
/**
* @dev UDVT that represents a slot holding a uint256.
*/
type Uint256Slot is bytes32;
/**
* @dev Cast an arbitrary slot to a Uint256Slot.
*/
function asUint256(bytes32 slot) internal pure returns (Uint256Slot) {
return Uint256Slot.wrap(slot);
}
/**
* @dev UDVT that represents a slot holding a int256.
*/
type Int256Slot is bytes32;
/**
* @dev Cast an arbitrary slot to a Int256Slot.
*/
function asInt256(bytes32 slot) internal pure returns (Int256Slot) {
return Int256Slot.wrap(slot);
}
/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(AddressSlot slot) internal view returns (address value) {
assembly ("memory-safe") {
value := tload(slot)
}
}
/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(AddressSlot slot, address value) internal {
assembly ("memory-safe") {
tstore(slot, value)
}
}
/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(BooleanSlot slot) internal view returns (bool value) {
assembly ("memory-safe") {
value := tload(slot)
}
}
/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(BooleanSlot slot, bool value) internal {
assembly ("memory-safe") {
tstore(slot, value)
}
}
/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(Bytes32Slot slot) internal view returns (bytes32 value) {
assembly ("memory-safe") {
value := tload(slot)
}
}
/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(Bytes32Slot slot, bytes32 value) internal {
assembly ("memory-safe") {
tstore(slot, value)
}
}
/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(Uint256Slot slot) internal view returns (uint256 value) {
assembly ("memory-safe") {
value := tload(slot)
}
}
/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(Uint256Slot slot, uint256 value) internal {
assembly ("memory-safe") {
tstore(slot, value)
}
}
/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(Int256Slot slot) internal view returns (int256 value) {
assembly ("memory-safe") {
value := tload(slot)
}
}
/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(Int256Slot slot, int256 value) internal {
assembly ("memory-safe") {
tstore(slot, value)
}
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
// solhint-disable imports-order
pragma solidity >=0.8.27;
import {Slot} from "@aztec/shared/libraries/TimeMath.sol";
import {Signature} from "@aztec/shared/libraries/SignatureLib.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
interface IEmperor {
// Not view because it might rely on transient storage.
// Calls are essentially trusted
function getCurrentProposer() external returns (address);
function getCurrentSlot() external view returns (Slot);
}
interface IEmpire {
event SignalCast(IPayload indexed payload, uint256 indexed round, address indexed signaler);
event PayloadSubmittable(IPayload indexed payload, uint256 indexed round);
event PayloadSubmitted(IPayload indexed payload, uint256 indexed round);
function signal(IPayload _payload) external returns (bool);
function signalWithSig(IPayload _payload, Signature memory _sig) external returns (bool);
function submitRoundWinner(uint256 _roundNumber) external returns (bool);
function signalCount(address _instance, uint256 _round, IPayload _payload) external view returns (uint256);
function computeRound(Slot _slot) external view returns (uint256);
function getInstance() external view returns (address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)
pragma solidity >=0.4.16;
import {IERC20} from "../token/ERC20/IERC20.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)
pragma solidity >=0.4.16;
import {IERC165} from "../utils/introspection/IERC165.sol";// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
/**
* @title Modified minimal proxy
* @author Splits
* @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibClone.sol)
* @dev Modified minimal proxy includes a `receive()` method that emits the
* `ReceiveETH(uint256)` event to skip `DELEGATECALL` when there is no calldata.
* Enables us to accept hard gas-capped `sends` & `transfers` for maximum backwards
* composability.
*/
// solhint-disable no-inline-assembly
library Clone {
error DeploymentFailed();
uint256 private constant FREE_PTR = 0x40;
uint256 private constant ZERO_PTR = 0x60;
/// @dev Deploys a modified minimal proxy of `implementation`
function cloneDeterministic(address _implementation, bytes32 _salt) internal returns (address instance) {
assembly ("memory-safe") {
/**
* --------------------------------------------------------------------------+
* CREATION (9 bytes - 0x09) |
* --------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* --------------------------------------------------------------------------|
* 60 runSize | PUSH1 runSize | r | |
* 3d | RETURNDATASIZE | 0 r | |
* 81 | DUP2 | r 0 r | |
* 60 offset | PUSH1 offset | o r 0 r | |
* 3d | RETURNDATASIZE | 0 o r 0 r | |
* 39 | CODECOPY | 0 r | [0..runSize): runtime code |
* f3 | RETURN | | [0..runSize): runtime code |
* --------------------------------------------------------------------------|
* RUNTIME (89 bytes - 0x59) |
* --------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* --------------------------------------------------------------------------|
* |
* 36 | CALLDATASIZE | cds | |
* 60 0x2c | PUSH1 0x2c | 0x2c cds | |
* 57 | JUMPI | | |
* 34 | CALLVALUE | cv | |
* 3d | RETURNDATASIZE | 0 cv | |
* 52 | MSTORE | | [0..0x20): callvalue |
* 7f sig | PUSH32 0x9e.. | sig | [0..0x20): callvalue |
* 59 | MSIZE | 0x20 sig | [0..0x20): callvalue |
* 3d | RETURNDATASIZE | 0 0x20 sig | [0..0x20): callvalue |
* a1 | LOG1 | | [0..0x20): callvalue |
* 00 | STOP | | [0..0x20): callvalue |
* 5b | JUMPDEST | | |
* |
* ::: keep some values in stack ::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | 0 | |
* 3d | RETURNDATASIZE | 0 0 | |
* 3d | RETURNDATASIZE | 0 0 0 | |
* 3d | RETURNDATASIZE | 0 0 0 0 | |
* |
* ::: copy calldata to memory ::::::::::::::::::::::::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds 0 0 0 0 | |
* 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | |
* 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | |
* 37 | CALLDATACOPY | 0 0 0 0 | [0..cds): calldata |
* |
* ::: delegate call to the implementation contract :::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds 0 0 0 0 | [0..cds): calldata |
* 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0..cds): calldata |
* 73 addr | PUSH20 addr | addr 0 cds 0 0 0 0 | [0..cds): calldata |
* 5a | GAS | gas addr 0 cds 0 0 0 0 | [0..cds): calldata |
* f4 | DELEGATECALL | success 0 0 | [0..cds): calldata |
* |
* ::: copy return data to memory :::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds success 0 0 | [0..cds): calldata |
* 3d | RETURNDATASIZE | rds rds success 0 0 | [0..cds): calldata |
* 93 | SWAP4 | 0 rds success 0 rds | [0..cds): calldata |
* 80 | DUP1 | 0 0 rds success 0 rds | [0..cds): calldata |
* 3e | RETURNDATACOPY | success 0 rds | [0..rds): returndata |
* |
* 60 0x57 | PUSH1 0x57 | 0x57 success 0 rds | [0..rds): returndata |
* 57 | JUMPI | 0 rds | [0..rds): returndata |
* |
* ::: revert :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* fd | REVERT | | [0..rds): returndata |
* |
* ::: return :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 5b | JUMPDEST | 0 rds | [0..rds): returndata |
* f3 | RETURN | | [0..rds): returndata |
* --------------------------------------------------------------------------+
* TOTAL INIT (98 bytes - 0x62) |
* --------------------------------------------------------------------------|
*/
// save free pointer
let fp := mload(FREE_PTR)
mstore(0x51, 0x5af43d3d93803e605757fd5bf3) // 13 bytes
mstore(0x44, _implementation) // 20 bytes
mstore(0x30, 0x593da1005b3d3d3d3d363d3d37363d73) // 16 bytes
// `keccak256("ReceiveETH(uint256)")`
mstore(0x20, 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff) // 32 bytes
mstore(0x00, 0x60593d8160093d39f336602c57343d527f) // 17 bytes
// total: 113 bytes = 0x71
// offset: 15 bytes = 0x0f
// data: 98 bytes = 0x62
instance := create2(0, 0x0f, 0x71, _salt)
// restore free pointer, zero slot
mstore(FREE_PTR, fp)
mstore(ZERO_PTR, 0)
// If `instance` is zero, revert.
if iszero(instance) {
// Store the function selector of `DeploymentFailed()`.
mstore(0x00, 0x30116425)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
}
function initCodeHash(address _implementation) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
// save free pointer
let fp := mload(FREE_PTR)
mstore(0x51, 0x5af43d3d93803e605757fd5bf3) // 13 bytes
mstore(0x44, _implementation) // 20 bytes
mstore(0x30, 0x593da1005b3d3d3d3d363d3d37363d73) // 16 bytes
// `keccak256("ReceiveETH(uint256)")`
mstore(0x20, 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff) // 32 bytes
mstore(0x00, 0x60593d8160093d39f336602c57343d527f) // 17 bytes
hash := keccak256(0x0f, 0x71)
// restore free pointer, zero slot
mstore(FREE_PTR, fp)
mstore(ZERO_PTR, 0)
}
}
function predictDeterministicAddress(
address _implementation,
bytes32 _salt,
address _deployer
)
internal
pure
returns (address predicted)
{
bytes32 hash = initCodeHash(_implementation);
predicted = predictDeterministicAddress({ _hash: hash, _salt: _salt, _deployer: _deployer });
}
function predictDeterministicAddress(
bytes32 _hash,
bytes32 _salt,
address _deployer
)
internal
pure
returns (address predicted)
{
/// @solidity memory-safe-assembly
assembly {
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, _hash)
mstore(0x01, shl(96, _deployer))
mstore(0x15, _salt)
predicted := keccak256(0x00, 0x55)
mstore(0x35, 0) // Restore the overwritten part of the free memory pointer.
}
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
/**
* @title Track hash Nonces
* @dev Inspired by OpenZeppelin's Nonces.sol
*/
abstract contract Nonces {
mapping(bytes32 hash => uint256) private _nonces;
/**
* @dev Returns the next unused nonce for a hash.
*/
function nonces(bytes32 _hash) public view virtual returns (uint256) {
return _nonces[_hash];
}
/**
* @dev Consumes a nonce.
*
* Returns the current value and increments nonce.
*/
function useNonce(bytes32 _hash) internal virtual returns (uint256) {
// For each hash, the nonce has an initial value of 0, can only be incremented by one, and cannot be
// decremented or reset. This guarantees that the nonce never overflows.
unchecked {
// It is important to do x++ and not ++x here.
return _nonces[_hash]++;
}
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
import { ISplitsWarehouse } from "../interfaces/ISplitsWarehouse.sol";
import { Cast } from "../libraries/Cast.sol";
import { SplitV2Lib } from "../libraries/SplitV2.sol";
import { ERC1271 } from "../utils/ERC1271.sol";
import { Wallet } from "../utils/Wallet.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title Split Wallet V2
* @author Splits
* @notice Base splitter contract.
* @dev `SplitProxy` handles `receive()` itself to avoid the gas cost with `DELEGATECALL`.
*/
abstract contract SplitWalletV2 is Wallet, ERC1271 {
using SplitV2Lib for SplitV2Lib.Split;
using Cast for address;
/* -------------------------------------------------------------------------- */
/* ERRORS */
/* -------------------------------------------------------------------------- */
error UnauthorizedInitializer();
error InvalidSplit();
/* -------------------------------------------------------------------------- */
/* EVENTS */
/* -------------------------------------------------------------------------- */
event SplitUpdated(SplitV2Lib.Split _split);
event SplitDistributed(address indexed token, address indexed distributor, uint256 amount);
/* -------------------------------------------------------------------------- */
/* CONSTANTS/IMMUTABLES */
/* -------------------------------------------------------------------------- */
/// @notice address of Splits Warehouse
ISplitsWarehouse public immutable SPLITS_WAREHOUSE;
/// @notice address of Split Wallet V2 factory
address public immutable FACTORY;
/// @notice address of native token
address public immutable NATIVE_TOKEN;
/* -------------------------------------------------------------------------- */
/* STORAGE */
/* -------------------------------------------------------------------------- */
/// @notice the split hash - Keccak256 hash of the split struct
bytes32 public splitHash;
/// @notice the block number at which the split was last updated
uint256 public updateBlockNumber;
/* -------------------------------------------------------------------------- */
/* CONSTRUCTOR & INITIALIZER */
/* -------------------------------------------------------------------------- */
constructor(address _splitWarehouse, string memory _name) ERC1271(_name, "2.2") {
SPLITS_WAREHOUSE = ISplitsWarehouse(_splitWarehouse);
NATIVE_TOKEN = SPLITS_WAREHOUSE.NATIVE_TOKEN();
FACTORY = msg.sender;
}
/**
* @notice Initializes the split wallet with a split and its corresponding data.
* @dev Only the factory can call this function.
* @param _split The split struct containing the split data that gets initialized.
*/
function initialize(SplitV2Lib.Split calldata _split, address _owner) external {
if (msg.sender != FACTORY) revert UnauthorizedInitializer();
_split.validate();
splitHash = _split.getHash();
updateBlockNumber = block.number;
emit SplitUpdated(_split);
Wallet.__initWallet(_owner);
}
/* -------------------------------------------------------------------------- */
/* PUBLIC/EXTERNAL FUNCTIONS */
/* -------------------------------------------------------------------------- */
function distribute(SplitV2Lib.Split calldata _split, address _token, address _distributor) external virtual;
function distribute(
SplitV2Lib.Split calldata _split,
address _token,
uint256 _distributeAmount,
bool _performWarehouseTransfer,
address _distributor
)
external
virtual;
/**
* @notice Gets the total token balance of the split wallet and the warehouse.
* @param _token The token to get the balance of.
* @return splitBalance The token balance in the split wallet.
* @return warehouseBalance The token balance in the warehouse of the split wallet.
*/
function getSplitBalance(address _token) public view returns (uint256 splitBalance, uint256 warehouseBalance) {
splitBalance = (_token == NATIVE_TOKEN) ? address(this).balance : IERC20(_token).balanceOf(address(this));
warehouseBalance = SPLITS_WAREHOUSE.balanceOf(address(this), _token.toUint256());
}
/**
* @notice Updates the split.
* @dev Only the owner can call this function.
* @param _split The new split struct.
*/
function updateSplit(SplitV2Lib.Split calldata _split) external onlyOwner {
// throws error if invalid
_split.validate();
splitHash = _split.getHash();
updateBlockNumber = block.number;
emit SplitUpdated(_split);
}
/* -------------------------------------------------------------------------- */
/* INTERNAL FUNCTIONS */
/* -------------------------------------------------------------------------- */
function getSigner() internal view override returns (address) {
return owner;
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
library Cast {
error Overflow();
function toAddress(uint256 _value) internal pure returns (address) {
return address(toUint160(_value));
}
function toUint256(address _value) internal pure returns (uint256) {
return uint256(uint160(_value));
}
function toUint160(uint256 _x) internal pure returns (uint160 y) {
if (_x >> 160 != 0) revert Overflow();
// solhint-disable-next-line no-inline-assembly
assembly {
y := _x
}
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {CompressedProposal, CompressedProposalLib} from "@aztec/governance/libraries/compressed-data/Proposal.sol";
import {CompressedTimestamp, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
import {Math} from "@oz/utils/math/Math.sol";
enum VoteTabulationReturn {
Accepted,
Rejected,
Invalid
}
enum VoteTabulationInfo {
TotalPowerLtMinimum,
VotesNeededEqZero,
VotesNeededGtTotalPower,
VotesCastLtVotesNeeded,
YeaLimitEqZero,
YeaLimitGtVotesCast,
YeaLimitEqVotesCast,
YeaVotesEqVotesCast,
YeaVotesLeYeaLimit,
YeaVotesGtYeaLimit
}
/**
* @notice Library for governance proposal evaluation and lifecycle management
*
* This library implements the core vote tabulation logic, and has helpers for getting timestamps
* for the proposal lifecycle.
*
* @dev VOTING MECHANICS:
*
* The voting system uses three key parameters that interact to determine proposal outcomes:
*
* 1. **minimumVotes**: Absolute minimum voting power required in the system
* - Prevents proposals when total power is too low for meaningful governance
* - Must be > 0 and <= totalPower for valid proposals
*
* 2. **quorum**: Percentage of total power that must participate (in 1e18 precision)
* - votesNeeded = ceil(totalPower * quorum / 1e18)
* - Ensures sufficient community participation before decisions are made
* - Example: 30% quorum (0.3e18) with 1000 total power requires ≥300 votes
*
* 3. **requiredYeaMargin**: the required minimum difference between the percentage of yea votes,
* and the percentage of nay votes, in 1e18 precision
* - requiredYeaVotesFraction = ceil((1e18 + requiredYeaMargin) / 2)
* - requiredYeaVotes = ceil(votesCast * requiredYeaVotesFraction / 1e18)
* - Yea votes must be > requiredYeaVotes to pass (strict inequality to avoid ties)
* - Example: 20% requiredYeaMargin (0.2e18) means yea needs >60% of cast votes
* - Example: 0% requiredYeaMargin means yea needs >50% of cast votes
*
* To see why this is the case, let `y` be the percentage of yea votes,
* and `n` be the percentage of nay votes, and `m` be the requiredYeaMargin.
*
* The condition for the proposal to pass is `y - n > m`.
* Thus, `y > m + n`, which is equivalent to `y > m + (1 - y)` => `2y > m + 1` => `y > (m + 1) / 2`.
*
* These parameters are included on the proposal itself, which are copied from Governance at the
* time the proposal is created.
*
* @dev EXAMPLE SCENARIO:
* - Total power: 1000 tokens
* - Minimum votes: 100 tokens
* - Quorum: 40% (0.4e18)
* - Required yea margin: 10% (0.1e18)
*
* For a proposal to pass:
* 1. Total power (1000) must be ≥ minimum votes (100) ✓
* 2. Votes needed = ceil(1000 * 0.4) = 400 votes minimum
* 3. If 500 votes cast (300 yea, 200 nay):
* - Quorum met: 500 ≥ 400 ✓
* - Required yea votes = ceil(500 * ceil(1.1e18/2) / 1e18) = ceil(500 * 0.55) = 275
* - Proposal passes: 300 yea > 275 required yea votes ✓
*
* @dev ROUNDING STRATEGY:
* All calculations use ceiling rounding to ensure the protocol is never "underpaid"
* in terms of required votes. This prevents edge cases where fractional vote
* requirements could round down to zero or insufficient thresholds.
*
* @dev PROPOSAL LIFECYCLE:
* The library also manages proposal timing through four phases:
* 1. Pending: creation → creation + votingDelay
* 2. Active: pending end → pending end + votingDuration
* 3. Queued: active end → active end + executionDelay
* 4. Executable: queued end → queued end + gracePeriod
*/
library ProposalLib {
using CompressedTimeMath for CompressedTimestamp;
using CompressedProposalLib for CompressedProposal;
/**
* @notice Tabulate the votes for a proposal.
* @dev This function is used to determine if a proposal has met the acceptance criteria.
*
* @param _self The proposal to tabulate the votes for.
* @param _totalPower The total power (in Governance) at proposal.pendingThrough().
* @return The vote tabulation result, and additional information.
*/
function voteTabulation(CompressedProposal storage _self, uint256 _totalPower)
internal
view
returns (VoteTabulationReturn, VoteTabulationInfo)
{
if (_totalPower < _self.minimumVotes) {
return (VoteTabulationReturn.Rejected, VoteTabulationInfo.TotalPowerLtMinimum);
}
uint256 votesNeeded = Math.mulDiv(_totalPower, _self.quorum, 1e18, Math.Rounding.Ceil);
if (votesNeeded == 0) {
return (VoteTabulationReturn.Invalid, VoteTabulationInfo.VotesNeededEqZero);
}
if (votesNeeded > _totalPower) {
return (VoteTabulationReturn.Invalid, VoteTabulationInfo.VotesNeededGtTotalPower);
}
(uint256 yea, uint256 nay) = _self.getVotes();
uint256 votesCast = nay + yea;
if (votesCast < votesNeeded) {
return (VoteTabulationReturn.Rejected, VoteTabulationInfo.VotesCastLtVotesNeeded);
}
// Edge case where all the votes are yea, no need to compute requiredApprovalVotes.
// ConfigurationLib enforces that requiredYeaMargin is <= 1e18,
// i.e. we cannot require more votes to be yes than total votes.
if (yea == votesCast) {
return (VoteTabulationReturn.Accepted, VoteTabulationInfo.YeaVotesEqVotesCast);
}
uint256 requiredApprovalVotesFraction = Math.ceilDiv(1e18 + _self.requiredYeaMargin, 2);
uint256 requiredApprovalVotes = Math.mulDiv(votesCast, requiredApprovalVotesFraction, 1e18, Math.Rounding.Ceil);
/*if (requiredApprovalVotes == 0) {
// It should be impossible to hit this case as `requiredApprovalVotesFraction` cannot be 0,
// and due to rounding up, only way to hit this would be if `votesCast = 0`,
// which is already handled as `votesCast >= votesNeeded` and `votesNeeded > 0`.
return (VoteTabulationReturn.Invalid, VoteTabulationInfo.YeaLimitEqZero);
}*/
if (requiredApprovalVotes > votesCast) {
return (VoteTabulationReturn.Invalid, VoteTabulationInfo.YeaLimitGtVotesCast);
}
// We want to see that there are MORE votes on yea than needed
// We explicitly need MORE to ensure we don't "tie".
// If we need as many yea as there are votes, we know it is impossible already.
// due to the check earlier, that summedBallot.yea == votesCast.
if (yea <= requiredApprovalVotes) {
return (VoteTabulationReturn.Rejected, VoteTabulationInfo.YeaVotesLeYeaLimit);
}
return (VoteTabulationReturn.Accepted, VoteTabulationInfo.YeaVotesGtYeaLimit);
}
/**
* @notice Get when the pending phase ends
* @param _compressed Storage pointer to compressed proposal
* @return The timestamp when pending phase ends
*/
function pendingThrough(CompressedProposal storage _compressed) internal view returns (Timestamp) {
return _compressed.creation.decompress() + _compressed.votingDelay.decompress();
}
/**
* @notice Get when the active phase ends
* @param _compressed Storage pointer to compressed proposal
* @return The timestamp when active phase ends
*/
function activeThrough(CompressedProposal storage _compressed) internal view returns (Timestamp) {
return pendingThrough(_compressed) + _compressed.votingDuration.decompress();
}
/**
* @notice Get when the queued phase ends
* @param _compressed Storage pointer to compressed proposal
* @return The timestamp when queued phase ends
*/
function queuedThrough(CompressedProposal storage _compressed) internal view returns (Timestamp) {
return activeThrough(_compressed) + _compressed.executionDelay.decompress();
}
/**
* @notice Get when the executable phase ends
* @param _compressed Storage pointer to compressed proposal
* @return The timestamp when executable phase ends
*/
function executableThrough(CompressedProposal storage _compressed) internal view returns (Timestamp) {
return queuedThrough(_compressed) + _compressed.gracePeriod.decompress();
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IGSE} from "@aztec/governance/GSE.sol";
import {GSEPayload} from "@aztec/governance/GSEPayload.sol";
import {IEmpire} from "@aztec/governance/interfaces/IEmpire.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 {EmpireBase} from "./EmpireBase.sol";
/**
* @title GovernanceProposer
* An implementation of EmpireBase, used to propose payloads to governance from sequencers on the canonical rollup.
*
* Note: any payload which passes through this contract will have a call to GSEPayload.amIValid appended to the
* list of actions before it is proposed to Governance.
* This will cause the proposal to revert if >2/3 of all stake in the GSE are not staked on the latest rollup after
* the *original* payload is executed by Governance. Unless the latest and canonical rollup diverge, as that indicates
* a misconfiguration issue (see GSEPayload for more details).
*/
contract GovernanceProposer is IGovernanceProposer, EmpireBase {
IRegistry public immutable REGISTRY;
IGSE public immutable GSE;
/**
* @dev Mapping of proposal ID to the proposer address.
* This allows instances to see if they were the proposer of a proposal
* after the payload is `propose`ed to Governance.
* Instances that *did* propose a proposal are willing to vote on it in Governance.
* See `StakingLib.vote` for more details.
*/
mapping(uint256 proposalId => address proposer) internal proposalProposer;
/**
* @notice Constructor for the GovernanceProposer contract.
*
* @dev The _executionDelayInRounds are set to 0, as there already is a delay in the governance contract.
* If this was not the case, the delay could be applied here.
*
* @param _registry The registry contract address.
* @param _gse The GSE contract address.
* @param _quorumSize The number of signals needed in a round for a payload to pass.
* @param _roundSize The number of signals that can be cast in a round.
*/
constructor(IRegistry _registry, IGSE _gse, uint256 _quorumSize, uint256 _roundSize)
EmpireBase(_quorumSize, _roundSize, 5, 0)
{
REGISTRY = _registry;
GSE = _gse;
}
function getProposalProposer(uint256 _proposalId) external view override(IGovernanceProposer) returns (address) {
return proposalProposer[_proposalId];
}
/**
* @dev Returns the address of the Governance contract, i.e. the contract at which
* we will `propose` a winning proposal.
*/
function getGovernance() public view override(IGovernanceProposer) returns (address) {
return REGISTRY.getGovernance();
}
/**
* @dev A hook used by the EmpireBase to determine who is the current block builder (block "proposer"),
* and thus may signal.
*
* This contract only respects the canonical rollup.
*/
function getInstance() public view override(EmpireBase, IEmpire) returns (address) {
return address(REGISTRY.getCanonicalRollup());
}
/**
* @dev Called by the EmpireBase contract in `submitRoundWinner`, which asserts that the payload
* has enough support to be proposed to Governance.
*
* Note that it wraps the original payload in a GSEPayload before pushing into the Governance contract.
*
* This creates additional checks, namely that *after* the original payload is executed,
* the canonical rollup (both the instance and the "magical address") has at least 2/3 of the total stake.
*
* @param _payload The payload to propose to the governance contract.
* @return true if the proposal was proposed successfully, reverts otherwise.
*/
function _handleRoundWinner(IPayload _payload) internal override(EmpireBase) returns (bool) {
GSEPayload extendedPayload = new GSEPayload(_payload, GSE, REGISTRY);
uint256 proposalId = IGovernance(getGovernance()).propose(IPayload(address(extendedPayload)));
proposalProposer[proposalId] = getInstance();
return true;
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {G1Point, G2Point} from "@aztec/shared/libraries/BN254Lib.sol";
interface IBn254LibWrapper {
function proofOfPossession(
G1Point memory _publicKeyInG1,
G2Point memory _publicKeyInG2,
G1Point memory _proofOfPossession
) external view returns (bool);
function g1ToDigestPoint(G1Point memory pk1) external view returns (G1Point memory);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {Errors} from "@aztec/governance/libraries/Errors.sol";
import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol";
/**
* @title CheckpointedUintLib
* @notice Library for managing Trace224 using a timestamp as key,
* Provides helper functions to `add` to or `sub` from the current value.
*/
library CheckpointedUintLib {
using Checkpoints for Checkpoints.Trace224;
using SafeCast for uint256;
/**
* @notice Add `_amount` to the current value
*
* @dev The amounts are cast to uint224 before storing such that the (key: value) fits in a single slot
*
* @param _self - The Trace224 to add to
* @param _amount - The amount to add
*
* @return - The current value and the new value
*/
function add(Checkpoints.Trace224 storage _self, uint256 _amount) internal returns (uint256, uint256) {
uint224 current = _self.latest();
if (_amount == 0) {
return (current, current);
}
uint224 amount = _amount.toUint224();
_self.push(block.timestamp.toUint32(), current + amount);
return (current, current + amount);
}
/**
* @notice Subtract `_amount` from the current value
*
* @param _self - The Trace224 to subtract from
* @param _amount - The amount to subtract
* @return - The current value and the new value
*/
function sub(Checkpoints.Trace224 storage _self, uint256 _amount) internal returns (uint256, uint256) {
uint224 current = _self.latest();
if (_amount == 0) {
return (current, current);
}
uint224 amount = _amount.toUint224();
require(current >= amount, Errors.Governance__CheckpointedUintLib__InsufficientValue(msg.sender, current, amount));
_self.push(block.timestamp.toUint32(), current - amount);
return (current, current - amount);
}
/**
* @notice Get the current value
*
* @param _self - The Trace224 to get the value of
* @return - The current value
*/
function valueNow(Checkpoints.Trace224 storage _self) internal view returns (uint256) {
return _self.latest();
}
/**
* @notice Get the value at a given timestamp
* The timestamp MUST be in the past to guarantee it is stable
*
* @dev Uses `upperLookupRecent` instead of just `upperLookup` as it will most
* likely be a recent value when looked up as part of governance.
*
* @param _self - The Trace224 to get the value of
* @param _time - The timestamp to get the value at
* @return - The value at the given timestamp
*/
function valueAt(Checkpoints.Trace224 storage _self, Timestamp _time) internal view returns (uint256) {
require(_time < Timestamp.wrap(block.timestamp), Errors.Governance__CheckpointedUintLib__NotInPast());
return _self.upperLookupRecent(Timestamp.unwrap(_time).toUint32());
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
struct Ballot {
uint256 yea;
uint256 nay;
}
type CompressedBallot is uint256;
library BallotLib {
using SafeCast for uint256;
uint256 internal constant YEA_MASK = 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000;
uint256 internal constant NAY_MASK = 0xffffffffffffffffffffffffffffffff;
function getYea(CompressedBallot _compressedBallot) internal pure returns (uint256) {
return CompressedBallot.unwrap(_compressedBallot) >> 128;
}
function getNay(CompressedBallot _compressedBallot) internal pure returns (uint256) {
return CompressedBallot.unwrap(_compressedBallot) & NAY_MASK;
}
function updateYea(CompressedBallot _compressedBallot, uint256 _yea) internal pure returns (CompressedBallot) {
uint256 value = CompressedBallot.unwrap(_compressedBallot) & ~YEA_MASK;
return CompressedBallot.wrap(value | (_yea << 128));
}
function updateNay(CompressedBallot _compressedBallot, uint256 _nay) internal pure returns (CompressedBallot) {
uint256 value = CompressedBallot.unwrap(_compressedBallot) & ~NAY_MASK;
return CompressedBallot.wrap(value | _nay);
}
function addYea(CompressedBallot _compressedBallot, uint256 _amount) internal pure returns (CompressedBallot) {
uint256 currentYea = getYea(_compressedBallot);
uint256 newYea = currentYea + _amount;
return updateYea(_compressedBallot, newYea.toUint128());
}
function addNay(CompressedBallot _compressedBallot, uint256 _amount) internal pure returns (CompressedBallot) {
uint256 currentNay = getNay(_compressedBallot);
uint256 newNay = currentNay + _amount;
return updateNay(_compressedBallot, newNay.toUint128());
}
function compress(Ballot memory _ballot) internal pure returns (CompressedBallot) {
// We are doing cast to uint128 but inside a uint256 to not wreck the shifting.
uint256 yea = _ballot.yea.toUint128();
uint256 nay = _ballot.nay.toUint128();
return CompressedBallot.wrap((yea << 128) | nay);
}
function decompress(CompressedBallot _compressedBallot) internal pure returns (Ballot memory) {
return Ballot({yea: getYea(_compressedBallot), nay: getNay(_compressedBallot)});
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {Configuration, ProposeWithLockConfiguration} from "@aztec/governance/interfaces/IGovernance.sol";
import {CompressedTimestamp, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
/**
* @title CompressedConfiguration
* @notice Compressed storage representation of governance configuration
* @dev Packs configuration into minimal storage slots:
* Slot 1: Timing & percentages - votingDelay (32), votingDuration (32), executionDelay (32), gracePeriod (32),
* quorum (64), requiredYeaMargin (64)
* Slot 2: Amounts & proposeConfig - minimumVotes (96), lockAmount (96), lockDelay (32), unused (32)
*
* This packing reduces storage from ~8 slots to 2 slots.
* All timestamps use CompressedTimestamp (uint32, valid until year 2106).
* Percentages (quorum, requiredYeaMargin) use uint64 (max 1e18).
* Amounts use uint96 for realistic token amounts.
* ProposeConfig fields are kept together in Slot 2.
*/
struct CompressedConfiguration {
// Slot 1: Timing and percentages - 32*4 + 64*2 = 256 bits
CompressedTimestamp votingDelay;
CompressedTimestamp votingDuration;
CompressedTimestamp executionDelay;
CompressedTimestamp gracePeriod;
uint64 quorum;
uint64 requiredYeaMargin;
// Slot 2: Amounts and proposeConfig - 96 + 96 + 32 = 224 bits (32 bits unused)
uint96 minimumVotes;
uint96 lockAmount;
CompressedTimestamp lockDelay;
}
library CompressedConfigurationLib {
using SafeCast for uint256;
using CompressedTimeMath for Timestamp;
using CompressedTimeMath for CompressedTimestamp;
/**
* @notice Get the propose configuration directly from storage
* @param _compressed Storage pointer to compressed configuration
* @return The propose configuration
*/
function getProposeConfig(CompressedConfiguration storage _compressed)
internal
view
returns (ProposeWithLockConfiguration memory)
{
return
ProposeWithLockConfiguration({lockDelay: _compressed.lockDelay.decompress(), lockAmount: _compressed.lockAmount});
}
/**
* @notice Compress a Configuration struct into CompressedConfiguration
* @param _config The uncompressed configuration
* @return The compressed configuration
* @dev Values that exceed the compressed type limits will cause a revert.
* This is intentional to prevent storing invalid configurations.
*/
function compress(Configuration memory _config) internal pure returns (CompressedConfiguration memory) {
// Validate that amounts fit in their compressed types
require(_config.proposeConfig.lockAmount <= type(uint96).max, "lockAmount exceeds uint96");
require(_config.minimumVotes <= type(uint96).max, "minimumVotes exceeds uint96");
require(_config.quorum <= type(uint64).max, "quorum exceeds uint64");
require(_config.requiredYeaMargin <= type(uint64).max, "requiredYeaMargin exceeds uint64");
return CompressedConfiguration({
votingDelay: _config.votingDelay.compress(),
votingDuration: _config.votingDuration.compress(),
executionDelay: _config.executionDelay.compress(),
gracePeriod: _config.gracePeriod.compress(),
quorum: _config.quorum.toUint64(),
requiredYeaMargin: _config.requiredYeaMargin.toUint64(),
minimumVotes: _config.minimumVotes.toUint96(),
lockAmount: _config.proposeConfig.lockAmount.toUint96(),
lockDelay: _config.proposeConfig.lockDelay.compress()
});
}
/**
* @notice Decompress a CompressedConfiguration into Configuration
* @param _compressed The compressed configuration
* @return The uncompressed configuration
*/
function decompress(CompressedConfiguration memory _compressed) internal pure returns (Configuration memory) {
return Configuration({
proposeConfig: ProposeWithLockConfiguration({
lockDelay: _compressed.lockDelay.decompress(),
lockAmount: _compressed.lockAmount
}),
votingDelay: _compressed.votingDelay.decompress(),
votingDuration: _compressed.votingDuration.decompress(),
executionDelay: _compressed.executionDelay.decompress(),
gracePeriod: _compressed.gracePeriod.decompress(),
quorum: _compressed.quorum,
requiredYeaMargin: _compressed.requiredYeaMargin,
minimumVotes: _compressed.minimumVotes
});
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {Proposal, ProposalState, ProposalConfiguration} from "@aztec/governance/interfaces/IGovernance.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
import {CompressedBallot, BallotLib} from "@aztec/governance/libraries/compressed-data/Ballot.sol";
import {CompressedConfiguration} from "@aztec/governance/libraries/compressed-data/Configuration.sol";
import {CompressedTimestamp, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
/**
* @title CompressedProposal
* @notice Compressed storage representation of governance proposals
* @dev Packs proposal data with embedded config values into 4 storage slots:
* Slot 1: proposer (160) + minimumVotes (96) = 256 bits
* Slot 2: cachedState (8) + creation (32) + timing fields (32*4) + quorum (64) = 232 bits
* Slot 3: summedBallot (256 bits as CompressedBallot)
* Slot 4: payload (160) + requiredYeaMargin (64) = 224 bits
*
* This packing reduces storage from ~10 slots to 4 slots by embedding config values
* directly instead of storing the entire configuration struct.
*/
struct CompressedProposal {
// Slot 1: Core Identity (256 bits)
address proposer; // 160 bits
uint96 minimumVotes; // 96 bits - from config
// Slot 2: Timing (232 bits used, 24 bits padding)
ProposalState cachedState; // 8 bits
CompressedTimestamp creation; // 32 bits
CompressedTimestamp votingDelay; // 32 bits - from config
CompressedTimestamp votingDuration; // 32 bits - from config
CompressedTimestamp executionDelay; // 32 bits - from config
CompressedTimestamp gracePeriod; // 32 bits - from config
uint64 quorum; // 64 bits - from config
// Slot 3: Votes (256 bits)
CompressedBallot summedBallot; // 256 bits (128 yea + 128 nay)
// Slot 4: References (224 bits used, 32 bits padding)
IPayload payload; // 160 bits
uint64 requiredYeaMargin; // 64 bits - from config
}
library CompressedProposalLib {
using SafeCast for uint256;
using CompressedTimeMath for Timestamp;
using CompressedTimeMath for CompressedTimestamp;
using BallotLib for CompressedBallot;
/**
* @notice Add yea votes to the proposal
* @param _compressed Storage pointer to compressed proposal
* @param _amount The amount of yea votes to add
*/
function addYea(CompressedProposal storage _compressed, uint256 _amount) internal {
_compressed.summedBallot = _compressed.summedBallot.addYea(_amount);
}
/**
* @notice Add nay votes to the proposal
* @param _compressed Storage pointer to compressed proposal
* @param _amount The amount of nay votes to add
*/
function addNay(CompressedProposal storage _compressed, uint256 _amount) internal {
_compressed.summedBallot = _compressed.summedBallot.addNay(_amount);
}
/**
* @notice Get yea and nay votes
* @param _compressed Storage pointer to compressed proposal
* @return yea The yea votes
* @return nay The nay votes
*/
function getVotes(CompressedProposal storage _compressed) internal view returns (uint256 yea, uint256 nay) {
yea = _compressed.summedBallot.getYea();
nay = _compressed.summedBallot.getNay();
}
/**
* @notice Create a compressed proposal from uncompressed data and config
* @param _proposer The proposal creator
* @param _payload The payload to execute
* @param _creation The creation timestamp
* @param _config The compressed configuration to embed
* @return The compressed proposal
*/
function create(address _proposer, IPayload _payload, Timestamp _creation, CompressedConfiguration memory _config)
internal
pure
returns (CompressedProposal memory)
{
return CompressedProposal({
proposer: _proposer,
minimumVotes: _config.minimumVotes,
cachedState: ProposalState.Pending,
creation: _creation.compress(),
votingDelay: _config.votingDelay,
votingDuration: _config.votingDuration,
executionDelay: _config.executionDelay,
gracePeriod: _config.gracePeriod,
quorum: _config.quorum,
summedBallot: CompressedBallot.wrap(0),
payload: _payload,
requiredYeaMargin: _config.requiredYeaMargin
});
}
/**
* @notice Compress an uncompressed Proposal into a CompressedProposal
* @param _proposal The uncompressed proposal to compress
* @return The compressed proposal
*/
function compress(Proposal memory _proposal) internal pure returns (CompressedProposal memory) {
return CompressedProposal({
proposer: _proposal.proposer,
minimumVotes: _proposal.config.minimumVotes.toUint96(),
cachedState: _proposal.cachedState,
creation: _proposal.creation.compress(),
votingDelay: _proposal.config.votingDelay.compress(),
votingDuration: _proposal.config.votingDuration.compress(),
executionDelay: _proposal.config.executionDelay.compress(),
gracePeriod: _proposal.config.gracePeriod.compress(),
quorum: _proposal.config.quorum.toUint64(),
summedBallot: BallotLib.compress(_proposal.summedBallot),
payload: _proposal.payload,
requiredYeaMargin: _proposal.config.requiredYeaMargin.toUint64()
});
}
/**
* @notice Decompress a CompressedProposal into a standard Proposal
* @param _compressed The compressed proposal
* @return The uncompressed proposal
*/
function decompress(CompressedProposal memory _compressed) internal pure returns (Proposal memory) {
return Proposal({
config: ProposalConfiguration({
votingDelay: _compressed.votingDelay.decompress(),
votingDuration: _compressed.votingDuration.decompress(),
executionDelay: _compressed.executionDelay.decompress(),
gracePeriod: _compressed.gracePeriod.decompress(),
quorum: _compressed.quorum,
requiredYeaMargin: _compressed.requiredYeaMargin,
minimumVotes: _compressed.minimumVotes
}),
cachedState: _compressed.cachedState,
payload: _compressed.payload,
proposer: _compressed.proposer,
creation: _compressed.creation.decompress(),
summedBallot: _compressed.summedBallot.decompress()
});
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {Configuration} from "@aztec/governance/interfaces/IGovernance.sol";
import {CompressedConfiguration} from "@aztec/governance/libraries/compressed-data/Configuration.sol";
import {Errors} from "@aztec/governance/libraries/Errors.sol";
import {CompressedTimeMath, CompressedTimestamp} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
library ConfigurationLib {
using CompressedTimeMath for CompressedTimestamp;
uint256 internal constant QUORUM_LOWER = 1;
uint256 internal constant QUORUM_UPPER = 1e18;
uint256 internal constant REQUIRED_YEA_MARGIN_UPPER = 1e18;
uint256 internal constant VOTES_LOWER = 1;
uint256 internal constant VOTES_UPPER = type(uint96).max; // Maximum for compressed storage (uint96)
uint256 internal constant LOCK_AMOUNT_LOWER = 2;
uint256 internal constant LOCK_AMOUNT_UPPER = type(uint96).max; // Maximum for compressed storage (uint96)
Timestamp internal constant TIME_LOWER = Timestamp.wrap(60);
Timestamp internal constant TIME_UPPER = Timestamp.wrap(90 * 24 * 3600);
/**
* @notice The delay after which a withdrawal can be finalized.
* @dev This applies to the "normal" withdrawal, not one induced by proposeWithLock.
* @dev Making the delay equal to the voting duration + execution delay + a "small buffer"
* ensures that if you were able to vote on a proposal, someone may execute it before you can exit.
*
* The "small buffer" is somewhat arbitrarily set to the votingDelay / 5.
*/
function getWithdrawalDelay(CompressedConfiguration storage _self) internal view returns (Timestamp) {
Timestamp votingDelay = _self.votingDelay.decompress();
Timestamp votingDuration = _self.votingDuration.decompress();
Timestamp executionDelay = _self.executionDelay.decompress();
return Timestamp.wrap(Timestamp.unwrap(votingDelay) / 5) + votingDuration + executionDelay;
}
/**
* @notice
* @dev We specify `memory` here since it is called on outside import for validation
* before writing it to state.
*/
function assertValid(Configuration memory _self) internal pure {
require(_self.quorum >= QUORUM_LOWER, Errors.Governance__ConfigurationLib__QuorumTooSmall());
require(_self.quorum <= QUORUM_UPPER, Errors.Governance__ConfigurationLib__QuorumTooBig());
require(
_self.requiredYeaMargin <= REQUIRED_YEA_MARGIN_UPPER,
Errors.Governance__ConfigurationLib__RequiredYeaMarginTooBig()
);
require(_self.minimumVotes >= VOTES_LOWER, Errors.Governance__ConfigurationLib__InvalidMinimumVotes());
require(_self.minimumVotes <= VOTES_UPPER, Errors.Governance__ConfigurationLib__InvalidMinimumVotes());
require(
_self.proposeConfig.lockAmount >= LOCK_AMOUNT_LOWER, Errors.Governance__ConfigurationLib__LockAmountTooSmall()
);
require(
_self.proposeConfig.lockAmount <= LOCK_AMOUNT_UPPER, Errors.Governance__ConfigurationLib__LockAmountTooBig()
);
// Beyond checking the bounds like this, it might be useful to ensure that the value is larger than the withdrawal
// delay. this, can be useful if one want to ensure that the "locker" cannot himself vote in the proposal, but as
// it is unclear if this is a useful property, it is not enforced.
require(_self.proposeConfig.lockDelay >= TIME_LOWER, Errors.Governance__ConfigurationLib__TimeTooSmall("LockDelay"));
require(
_self.proposeConfig.lockDelay <= Timestamp.wrap(type(uint32).max),
Errors.Governance__ConfigurationLib__TimeTooBig("LockDelay")
);
require(_self.votingDelay >= TIME_LOWER, Errors.Governance__ConfigurationLib__TimeTooSmall("VotingDelay"));
require(_self.votingDelay <= TIME_UPPER, Errors.Governance__ConfigurationLib__TimeTooBig("VotingDelay"));
require(_self.votingDuration >= TIME_LOWER, Errors.Governance__ConfigurationLib__TimeTooSmall("VotingDuration"));
require(_self.votingDuration <= TIME_UPPER, Errors.Governance__ConfigurationLib__TimeTooBig("VotingDuration"));
require(_self.executionDelay >= TIME_LOWER, Errors.Governance__ConfigurationLib__TimeTooSmall("ExecutionDelay"));
require(_self.executionDelay <= TIME_UPPER, Errors.Governance__ConfigurationLib__TimeTooBig("ExecutionDelay"));
require(_self.gracePeriod >= TIME_LOWER, Errors.Governance__ConfigurationLib__TimeTooSmall("GracePeriod"));
require(_self.gracePeriod <= TIME_UPPER, Errors.Governance__ConfigurationLib__TimeTooBig("GracePeriod"));
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {ATPType} from "./../base/IATP.sol";
import {ILATP, ILATPPeriphery, IATPPeriphery, LATPStorage} from "./ILATP.sol";
import {LATPCore, IERC20, IRegistry, IBaseStaker} from "./LATPCore.sol";
/**
* @title Linear Aztec Token Position
* @notice Linear Aztec Token Position with additional helper view functions
* This is a helper contract to make it easier to use the LATP contract
* Will not include any state mutating extensions, just easier access to the data
* I might be kinda strange doing this, but I just find it simpler when looking at the state mutating
* functions, as I don't need to skip functions etc.
*
* It is also a neat way to make sure that all of the getters follow a similar pattern, as we like using
* different naming conventions for different types of data, e.g., constant vs mutable.
*/
contract LATP is ILATP, LATPCore {
constructor(IRegistry _registry, IERC20 _token) LATPCore(_registry, _token) {}
function getToken() external view override(IATPPeriphery) returns (IERC20) {
return TOKEN;
}
function getRegistry() external view override(IATPPeriphery) returns (IRegistry) {
return REGISTRY;
}
function getStaker() external view override(IATPPeriphery) returns (IBaseStaker) {
return staker;
}
function getExecuteAllowedAt() external view override(IATPPeriphery) returns (uint256) {
return REGISTRY.getExecuteAllowedAt();
}
function getClaimed() external view override(IATPPeriphery) returns (uint256) {
return claimed;
}
function getRevoker() external view override(IATPPeriphery) returns (address) {
return REGISTRY.getRevoker();
}
function getIsRevokable() external view override(IATPPeriphery) returns (bool) {
return store.isRevokable;
}
function getAllocation() external view override(IATPPeriphery) returns (uint256) {
return allocation;
}
function getStore() external view override(ILATPPeriphery) returns (LATPStorage memory) {
return store;
}
function getRevokeBeneficiary() external view override(ILATPPeriphery) returns (address) {
return store.revokeBeneficiary;
}
function getType() external pure virtual override(IATPPeriphery) returns (ATPType) {
return ATPType.Linear;
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {ERC1967Proxy} from "@oz/proxy/ERC1967/ERC1967Proxy.sol";
import {UUPSUpgradeable} from "@oz/proxy/utils/UUPSUpgradeable.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@oz/utils/math/Math.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {LockParams, Lock, LockLib} from "./../../libraries/LockLib.sol";
import {IRegistry, StakerVersion} from "./../../Registry.sol";
import {IBaseStaker} from "./../../staker/BaseStaker.sol";
import {ILATPCore, IATPCore, LATPStorage, RevokableParams} from "./ILATP.sol";
/**
* @title Linear Aztec Token Position Core
* @notice The core logic of the Linear Aztec Token Position
* @dev This contract is abstract and cannot be deployed on its own.
* It is meant to be inherited by the `LATP` contract.
* MUST be deployed using the `ATPFactory` contract.
*/
abstract contract LATPCore is ILATPCore {
using SafeCast for uint256;
using SafeERC20 for IERC20;
using LockLib for Lock;
IERC20 internal immutable TOKEN;
IRegistry internal immutable REGISTRY;
uint256 internal allocation;
address internal beneficiary;
IBaseStaker internal staker;
address internal operator;
uint256 internal claimed = 0;
LATPStorage internal store;
/**
* @dev The caller must be the beneficiary
*/
modifier onlyBeneficiary() {
require(msg.sender == beneficiary, NotBeneficiary(msg.sender, beneficiary));
_;
}
/**
* @dev Since we are using the `Clones` library to create the LATP's to use
* we can't use the constructor to initialize the individual ones, but
* we can use it to initialize values that will be shared across all the clones.
*
* @param _registry The registry
* @param _token The token
*/
constructor(IRegistry _registry, IERC20 _token) {
require(address(_registry) != address(0), InvalidRegistry(address(_registry)));
require(address(_token) != address(0), InvalidTokenAddress(address(_token)));
TOKEN = _token;
REGISTRY = _registry;
staker = IBaseStaker(address(0xdead));
}
/**
* @notice Initialize the Aztec Token Position
* Creates a `Staker`, sets the `beneficiary` and `allocation`
* If the LATP is revokable, it will set the `accumulation` lock as well
*
* @dev If run twice, the `staker` will already be set and this will revert
* with the `AlreadyInitialized` error
*
* @dev When done by the `ATPFactory` this will happen in the same transaction as LATP creation
*
* @param _beneficiary The address of the beneficiary
* @param _allocation The amount of tokens to allocate to the LATP
* @param _revokableParams The parameters for the accumulation lock and revoke beneficiary, if the LATP is revokable
*/
function initialize(address _beneficiary, uint256 _allocation, RevokableParams memory _revokableParams)
external
override(ILATPCore)
{
require(address(staker) == address(0), AlreadyInitialized());
require(_beneficiary != address(0), InvalidBeneficiary(address(0)));
require(_allocation > 0, AllocationMustBeGreaterThanZero());
beneficiary = _beneficiary;
allocation = _allocation;
staker = createStaker();
if (_revokableParams.revokeBeneficiary != address(0)) {
LockLib.assertValid(_revokableParams.lockParams);
store = LATPStorage({
isRevokable: true,
accumulationStartTime: _revokableParams.lockParams.startTime.toUint32(),
accumulationCliffDuration: _revokableParams.lockParams.cliffDuration.toUint32(),
accumulationLockDuration: _revokableParams.lockParams.lockDuration.toUint32(),
revokeBeneficiary: _revokableParams.revokeBeneficiary
});
} else {
// If the LATP is non-revokable, the store will be all 0, so we do not need to set storage
// We will however check that the lock params are empty, to reduce potential for confusion
require(LockLib.isEmpty(_revokableParams.lockParams), LockParamsMustBeEmpty());
}
}
/**
* @notice Upgrade the staker contract to a new version
*
* @param _version The version of the staker to upgrade to
*/
function upgradeStaker(StakerVersion _version) external override(IATPCore) onlyBeneficiary {
address impl = REGISTRY.getStakerImplementation(_version);
UUPSUpgradeable(address(staker)).upgradeToAndCall(impl, "");
require(staker.getATP() == address(this), InvalidUpgrade());
emit StakerUpgraded(_version);
}
/**
* @notice Update the operator of the staker contract
*
* @param _operator The address of the new operator
*/
function updateStakerOperator(address _operator) external override(IATPCore) onlyBeneficiary {
operator = _operator;
emit StakerOperatorUpdated(_operator);
}
/**
* @notice Cancel the accumulation of assets
*
* @return The amount of tokens revoked
*/
function revoke() external override(IATPCore) returns (uint256) {
require(store.isRevokable, NotRevokable());
address revoker = REGISTRY.getRevoker();
require(msg.sender == revoker, NotRevoker(msg.sender, revoker));
Lock memory accumulationLock = getAccumulationLock();
require(!accumulationLock.hasEnded(block.timestamp), LockHasEnded());
uint256 debt = getRevokableAmount();
store.isRevokable = false;
TOKEN.safeTransfer(store.revokeBeneficiary, debt);
emit Revoked(debt);
return debt;
}
/**
* @notice Rescue funds that have been sent to the contract by mistake
* Allows the beneficiary to transfer funds that are not unlock token from the contract.
*
* @param _asset The asset to rescue
* @param _to The address to send the assets to
*/
function rescueFunds(address _asset, address _to) external override(IATPCore) onlyBeneficiary {
require(_asset != address(TOKEN), InvalidAsset(_asset));
IERC20 asset = IERC20(_asset);
uint256 amount = asset.balanceOf(address(this));
asset.safeTransfer(_to, amount);
emit Rescued(_asset, _to, amount);
}
/**
* @notice Authorizes the staker contract for the specified amount.
*
* @param _allowance The amount of tokens to authorize the staker contract for
*/
function approveStaker(uint256 _allowance) external override(IATPCore) onlyBeneficiary {
// slither-disable-start block-timestamp
// As we are not relying on block.timestamp for randomness but merely for when we will toggle
// the EXECUTE_ALLOWED_AT flag, and time will only ever increase, we can safely ignore the warning.
uint256 executeAllowedAt = REGISTRY.getExecuteAllowedAt();
require(block.timestamp >= executeAllowedAt, ExecutionNotAllowedYet(block.timestamp, executeAllowedAt));
// slither-disable-end block-timestamp
uint256 stakeable = getStakeableAmount();
require(stakeable >= _allowance, InsufficientStakeable(stakeable, _allowance));
TOKEN.approve(address(staker), _allowance);
emit ApprovedStaker(_allowance);
}
/**
* @notice Claim the amount of tokens that are available for the owner to claim.
*
* @dev The `caller` must be the `beneficiary`
*
* @return The amount of tokens claimed
*/
function claim() external virtual override(IATPCore) onlyBeneficiary returns (uint256) {
uint256 amount = getClaimable();
require(amount > 0, NoClaimable());
claimed += amount;
TOKEN.safeTransfer(msg.sender, amount);
// @note After the transfer, we need to ensure that the allowance is not too high.
// Namely, if the allowance is larger than the stakeable amount it should be reduced.
uint256 stakeable = getStakeableAmount();
uint256 allowance = TOKEN.allowance(address(this), address(staker));
if (stakeable < allowance) {
TOKEN.approve(address(staker), stakeable);
}
emit Claimed(amount);
return amount;
}
function getOperator() public view override(IATPCore) returns (address) {
return operator;
}
function getBeneficiary() public view override(IATPCore) returns (address) {
return beneficiary;
}
/**
* @notice Compute the amount of tokens that can be claimed.
*
* @return The amount of tokens that can be claimed
*/
function getClaimable() public view override(IATPCore) returns (uint256) {
Lock memory globalLock = getGlobalLock();
uint256 unlocked = globalLock.hasEnded(block.timestamp)
? type(uint256).max
: (globalLock.unlockedAt(block.timestamp) - claimed);
return Math.min(TOKEN.balanceOf(address(this)) - getRevokableAmount(), unlocked);
}
/**
* @notice Get the global unlock schedule lock
*
* @return The global lock
*/
function getGlobalLock() public view override(IATPCore) returns (Lock memory) {
return LockLib.createLock(REGISTRY.getGlobalLockParams(), allocation);
}
/**
* @notice Get the accumulation lock
*
* @return The accumulation lock or empty if not revokable
*/
function getAccumulationLock() public view override(ILATPCore) returns (Lock memory) {
require(store.isRevokable, NotRevokable());
return LockLib.createLock(
LockParams({
startTime: store.accumulationStartTime,
cliffDuration: store.accumulationCliffDuration,
lockDuration: store.accumulationLockDuration
}),
allocation
);
}
/**
* @notice Get the amount of tokens that can be revoked
*
* @return The amount of tokens that can be revoked
*/
function getRevokableAmount() public view override(ILATPCore) returns (uint256) {
if (!store.isRevokable) {
return 0;
}
return allocation - getAccumulationLock().unlockedAt(block.timestamp);
}
/**
* @notice Get the amount of tokens that can be staked
*
* @return The amount of tokens that can be staked
*/
function getStakeableAmount() public view override(ILATPCore) returns (uint256) {
if (!store.isRevokable) {
return type(uint256).max;
}
return TOKEN.balanceOf(address(this)) - getRevokableAmount();
}
/**
* @notice Create a new staker contract with the `ERC1967Proxy`
* the initial implementation used will the be `BaseStaker`
*
* @return The new staker contract
*/
function createStaker() private returns (IBaseStaker) {
address impl = REGISTRY.getStakerImplementation(StakerVersion.wrap(0));
ERC1967Proxy proxy = new ERC1967Proxy(impl, abi.encodeCall(IBaseStaker.initialize, address(this)));
IBaseStaker _staker = IBaseStaker(address(proxy));
emit StakerInitialized(_staker);
return _staker;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {PositionInfo} from "../libraries/PositionInfoLibrary.sol";
/// @title ISubscriber
/// @notice Interface that a Subscriber contract should implement to receive updates from the v4 position manager
interface ISubscriber {
/// @notice Called when a position subscribes to this subscriber contract
/// @param tokenId the token ID of the position
/// @param data additional data passed in by the caller
function notifySubscribe(uint256 tokenId, bytes memory data) external;
/// @notice Called when a position unsubscribes from the subscriber
/// @dev This call's gas is capped at `unsubscribeGasLimit` (set at deployment)
/// @dev Because of EIP-150, solidity may only allocate 63/64 of gasleft()
/// @param tokenId the token ID of the position
function notifyUnsubscribe(uint256 tokenId) external;
/// @notice Called when a position is burned
/// @param tokenId the token ID of the position
/// @param owner the current owner of the tokenId
/// @param info information about the position
/// @param liquidity the amount of liquidity decreased in the position, may be 0
/// @param feesAccrued the fees accrued by the position if liquidity was decreased
function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued)
external;
/// @notice Called when a position modifies its liquidity or collects fees
/// @param tokenId the token ID of the position
/// @param liquidityChange the change in liquidity on the underlying position
/// @param feesAccrued the fees to be collected from the position as a result of the modifyLiquidity call
/// @dev Note that feesAccrued can be artificially inflated by a malicious user
/// Pools with a single liquidity position can inflate feeGrowthGlobal (and consequently feesAccrued) by donating to themselves;
/// atomically donating and collecting fees within the same unlockCallback may further inflate feeGrowthGlobal/feesAccrued
function notifyModifyLiquidity(uint256 tokenId, int256 liquidityChange, BalanceDelta feesAccrued) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IEIP712} from "./IEIP712.sol";
/// @title AllowanceTransfer
/// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts
/// @dev Requires user's token approval on the Permit2 contract
interface IAllowanceTransfer is IEIP712 {
/// @notice Thrown when an allowance on a token has expired.
/// @param deadline The timestamp at which the allowed amount is no longer valid
error AllowanceExpired(uint256 deadline);
/// @notice Thrown when an allowance on a token has been depleted.
/// @param amount The maximum amount allowed
error InsufficientAllowance(uint256 amount);
/// @notice Thrown when too many nonces are invalidated.
error ExcessiveInvalidation();
/// @notice Emits an event when the owner successfully invalidates an ordered nonce.
event NonceInvalidation(
address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce
);
/// @notice Emits an event when the owner successfully sets permissions on a token for the spender.
event Approval(
address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration
);
/// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.
event Permit(
address indexed owner,
address indexed token,
address indexed spender,
uint160 amount,
uint48 expiration,
uint48 nonce
);
/// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function.
event Lockdown(address indexed owner, address token, address spender);
/// @notice The permit data for a token
struct PermitDetails {
// ERC20 token address
address token;
// the maximum amount allowed to spend
uint160 amount;
// timestamp at which a spender's token allowances become invalid
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
/// @notice The permit message signed for a single token allowance
struct PermitSingle {
// the permit data for a single token alownce
PermitDetails details;
// address permissioned on the allowed tokens
address spender;
// deadline on the permit signature
uint256 sigDeadline;
}
/// @notice The permit message signed for multiple token allowances
struct PermitBatch {
// the permit data for multiple token allowances
PermitDetails[] details;
// address permissioned on the allowed tokens
address spender;
// deadline on the permit signature
uint256 sigDeadline;
}
/// @notice The saved permissions
/// @dev This info is saved per owner, per token, per spender and all signed over in the permit message
/// @dev Setting amount to type(uint160).max sets an unlimited approval
struct PackedAllowance {
// amount allowed
uint160 amount;
// permission expiry
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
/// @notice A token spender pair.
struct TokenSpenderPair {
// the token the spender is approved
address token;
// the spender address
address spender;
}
/// @notice Details for a token transfer.
struct AllowanceTransferDetails {
// the owner of the token
address from;
// the recipient of the token
address to;
// the amount of the token
uint160 amount;
// the token to be transferred
address token;
}
/// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval.
/// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]
/// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals.
function allowance(address user, address token, address spender)
external
view
returns (uint160 amount, uint48 expiration, uint48 nonce);
/// @notice Approves the spender to use up to amount of the specified token up until the expiration
/// @param token The token to approve
/// @param spender The spender address to approve
/// @param amount The approved amount of the token
/// @param expiration The timestamp at which the approval is no longer valid
/// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
/// @dev Setting amount to type(uint160).max sets an unlimited approval
function approve(address token, address spender, uint160 amount, uint48 expiration) external;
/// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature
/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
/// @param owner The owner of the tokens being approved
/// @param permitSingle Data signed over by the owner specifying the terms of approval
/// @param signature The owner's signature over the permit data
function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;
/// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature
/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
/// @param owner The owner of the tokens being approved
/// @param permitBatch Data signed over by the owner specifying the terms of approval
/// @param signature The owner's signature over the permit data
function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external;
/// @notice Transfer approved tokens from one address to another
/// @param from The address to transfer from
/// @param to The address of the recipient
/// @param amount The amount of the token to transfer
/// @param token The token address to transfer
/// @dev Requires the from address to have approved at least the desired amount
/// of tokens to msg.sender.
function transferFrom(address from, address to, uint160 amount, address token) external;
/// @notice Transfer approved tokens in a batch
/// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers
/// @dev Requires the from addresses to have approved at least the desired amount
/// of tokens to msg.sender.
function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external;
/// @notice Enables performing a "lockdown" of the sender's Permit2 identity
/// by batch revoking approvals
/// @param approvals Array of approvals to revoke.
function lockdown(TokenSpenderPair[] calldata approvals) external;
/// @notice Invalidate nonces for a given (token, spender) pair
/// @param token The token to invalidate nonces for
/// @param spender The spender to invalidate nonces for
/// @param newNonce The new nonce to set. Invalidates all nonces less than it.
/// @dev Can't invalidate more than 2**16 nonces per transaction.
function invalidateNonces(address token, address spender, uint48 newNonce) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Minimal ERC20 interface for Uniswap
/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3
interface IERC20Minimal {
/// @notice Returns an account's balance in the token
/// @param account The account for which to look up the number of tokens it has, i.e. its balance
/// @return The number of tokens held by the account
function balanceOf(address account) external view returns (uint256);
/// @notice Transfers the amount of token from the `msg.sender` to the recipient
/// @param recipient The account that will receive the amount transferred
/// @param amount The number of tokens to send from the sender to the recipient
/// @return Returns true for a successful transfer, false for an unsuccessful transfer
function transfer(address recipient, uint256 amount) external returns (bool);
/// @notice Returns the current allowance given to a spender by an owner
/// @param owner The account of the token owner
/// @param spender The account of the token spender
/// @return The current allowance granted by `owner` to `spender`
function allowance(address owner, address spender) external view returns (uint256);
/// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount`
/// @param spender The account which will be allowed to spend a given amount of the owners tokens
/// @param amount The amount of tokens allowed to be used by `spender`
/// @return Returns true for a successful approval, false for unsuccessful
function approve(address spender, uint256 amount) external returns (bool);
/// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender`
/// @param sender The account from which the transfer will be initiated
/// @param recipient The recipient of the transfer
/// @param amount The amount of the transfer
/// @return Returns true for a successful transfer, false for unsuccessful
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`.
/// @param from The account from which the tokens were sent, i.e. the balance decreased
/// @param to The account to which the tokens were sent, i.e. the balance increased
/// @param value The amount of tokens that were transferred
event Transfer(address indexed from, address indexed to, uint256 value);
/// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes.
/// @param owner The account that approved spending of its tokens
/// @param spender The account for which the spending allowance was modified
/// @param value The new allowance from the owner to the spender
event Approval(address indexed owner, address indexed spender, uint256 value);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Library for reverting with custom errors efficiently
/// @notice Contains functions for reverting with custom errors with different argument types efficiently
/// @dev To use this library, declare `using CustomRevert for bytes4;` and replace `revert CustomError()` with
/// `CustomError.selector.revertWith()`
/// @dev The functions may tamper with the free memory pointer but it is fine since the call context is exited immediately
library CustomRevert {
/// @dev ERC-7751 error for wrapping bubbled up reverts
error WrappedError(address target, bytes4 selector, bytes reason, bytes details);
/// @dev Reverts with the selector of a custom error in the scratch space
function revertWith(bytes4 selector) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
revert(0, 0x04)
}
}
/// @dev Reverts with a custom error with an address argument in the scratch space
function revertWith(bytes4 selector, address addr) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
mstore(0x04, and(addr, 0xffffffffffffffffffffffffffffffffffffffff))
revert(0, 0x24)
}
}
/// @dev Reverts with a custom error with an int24 argument in the scratch space
function revertWith(bytes4 selector, int24 value) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
mstore(0x04, signextend(2, value))
revert(0, 0x24)
}
}
/// @dev Reverts with a custom error with a uint160 argument in the scratch space
function revertWith(bytes4 selector, uint160 value) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
mstore(0x04, and(value, 0xffffffffffffffffffffffffffffffffffffffff))
revert(0, 0x24)
}
}
/// @dev Reverts with a custom error with two int24 arguments
function revertWith(bytes4 selector, int24 value1, int24 value2) internal pure {
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(fmp, selector)
mstore(add(fmp, 0x04), signextend(2, value1))
mstore(add(fmp, 0x24), signextend(2, value2))
revert(fmp, 0x44)
}
}
/// @dev Reverts with a custom error with two uint160 arguments
function revertWith(bytes4 selector, uint160 value1, uint160 value2) internal pure {
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(fmp, selector)
mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
revert(fmp, 0x44)
}
}
/// @dev Reverts with a custom error with two address arguments
function revertWith(bytes4 selector, address value1, address value2) internal pure {
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(fmp, selector)
mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
revert(fmp, 0x44)
}
}
/// @notice bubble up the revert message returned by a call and revert with a wrapped ERC-7751 error
/// @dev this method can be vulnerable to revert data bombs
function bubbleUpAndRevertWith(
address revertingContract,
bytes4 revertingFunctionSelector,
bytes4 additionalContext
) internal pure {
bytes4 wrappedErrorSelector = WrappedError.selector;
assembly ("memory-safe") {
// Ensure the size of the revert data is a multiple of 32 bytes
let encodedDataSize := mul(div(add(returndatasize(), 31), 32), 32)
let fmp := mload(0x40)
// Encode wrapped error selector, address, function selector, offset, additional context, size, revert reason
mstore(fmp, wrappedErrorSelector)
mstore(add(fmp, 0x04), and(revertingContract, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(
add(fmp, 0x24),
and(revertingFunctionSelector, 0xffffffff00000000000000000000000000000000000000000000000000000000)
)
// offset revert reason
mstore(add(fmp, 0x44), 0x80)
// offset additional context
mstore(add(fmp, 0x64), add(0xa0, encodedDataSize))
// size revert reason
mstore(add(fmp, 0x84), returndatasize())
// revert reason
returndatacopy(add(fmp, 0xa4), 0, returndatasize())
// size additional context
mstore(add(fmp, add(0xa4, encodedDataSize)), 0x04)
// additional context
mstore(
add(fmp, add(0xc4, encodedDataSize)),
and(additionalContext, 0xffffffff00000000000000000000000000000000000000000000000000000000)
)
revert(fmp, add(0xe4, encodedDataSize))
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Return type of the beforeSwap hook.
// Upper 128 bits is the delta in specified tokens. Lower 128 bits is delta in unspecified tokens (to match the afterSwap hook)
type BeforeSwapDelta is int256;
// Creates a BeforeSwapDelta from specified and unspecified
function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified)
pure
returns (BeforeSwapDelta beforeSwapDelta)
{
assembly ("memory-safe") {
beforeSwapDelta := or(shl(128, deltaSpecified), and(sub(shl(128, 1), 1), deltaUnspecified))
}
}
/// @notice Library for getting the specified and unspecified deltas from the BeforeSwapDelta type
library BeforeSwapDeltaLibrary {
/// @notice A BeforeSwapDelta of 0
BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0);
/// extracts int128 from the upper 128 bits of the BeforeSwapDelta
/// returned by beforeSwap
function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaSpecified) {
assembly ("memory-safe") {
deltaSpecified := sar(128, delta)
}
}
/// extracts int128 from the lower 128 bits of the BeforeSwapDelta
/// returned by beforeSwap and afterSwap
function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaUnspecified) {
assembly ("memory-safe") {
deltaUnspecified := signextend(15, delta)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {CustomRevert} from "./CustomRevert.sol";
/// @title Safe casting methods
/// @notice Contains methods for safely casting between types
library SafeCast {
using CustomRevert for bytes4;
error SafeCastOverflow();
/// @notice Cast a uint256 to a uint160, revert on overflow
/// @param x The uint256 to be downcasted
/// @return y The downcasted integer, now type uint160
function toUint160(uint256 x) internal pure returns (uint160 y) {
y = uint160(x);
if (y != x) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a uint256 to a uint128, revert on overflow
/// @param x The uint256 to be downcasted
/// @return y The downcasted integer, now type uint128
function toUint128(uint256 x) internal pure returns (uint128 y) {
y = uint128(x);
if (x != y) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a int128 to a uint128, revert on overflow or underflow
/// @param x The int128 to be casted
/// @return y The casted integer, now type uint128
function toUint128(int128 x) internal pure returns (uint128 y) {
if (x < 0) SafeCastOverflow.selector.revertWith();
y = uint128(x);
}
/// @notice Cast a int256 to a int128, revert on overflow or underflow
/// @param x The int256 to be downcasted
/// @return y The downcasted integer, now type int128
function toInt128(int256 x) internal pure returns (int128 y) {
y = int128(x);
if (y != x) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a uint256 to a int256, revert on overflow
/// @param x The uint256 to be casted
/// @return y The casted integer, now type int256
function toInt256(uint256 x) internal pure returns (int256 y) {
y = int256(x);
if (y < 0) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a uint256 to a int128, revert on overflow
/// @param x The uint256 to be downcasted
/// @return The downcasted integer, now type int128
function toInt128(uint256 x) internal pure returns (int128) {
if (x >= 1 << 127) SafeCastOverflow.selector.revertWith();
return int128(int256(x));
}
}// GENERATED FILE - DO NOT EDIT, RUN yarn remake-constants in yarn-project/constants
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Aztec Labs.
pragma solidity >=0.8.27;
/**
* @title Constants Library
* @author Aztec Labs
* @notice Library that contains constants used throughout the Aztec protocol
*/
library Constants {
// Prime field modulus
uint256 internal constant P =
21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617;
uint256 internal constant MAX_FIELD_VALUE =
21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_616;
uint256 internal constant L1_TO_L2_MSG_SUBTREE_HEIGHT = 4;
uint256 internal constant MAX_L2_TO_L1_MSGS_PER_TX = 8;
uint256 internal constant INITIAL_L2_BLOCK_NUM = 1;
uint256 internal constant BLOBS_PER_BLOCK = 3;
uint256 internal constant AZTEC_MAX_EPOCH_DURATION = 48;
uint256 internal constant GENESIS_ARCHIVE_ROOT =
14_298_165_331_316_638_916_453_567_345_577_793_920_283_466_066_305_521_584_041_971_978_819_102_601_406;
uint256 internal constant FEE_JUICE_ADDRESS = 5;
uint256 internal constant BLS12_POINT_COMPRESSED_BYTES = 48;
uint256 internal constant PROPOSED_BLOCK_HEADER_LENGTH_BYTES = 284;
uint256 internal constant ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH = 158;
uint256 internal constant NUM_MSGS_PER_BASE_PARITY = 4;
uint256 internal constant NUM_BASE_PARITY_PER_ROOT_PARITY = 4;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/Strings.sol)
pragma solidity ^0.8.24;
import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";
import {Bytes} from "./Bytes.sol";
/**
* @dev String operations.
*/
library Strings {
using SafeCast for *;
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
uint256 private constant SPECIAL_CHARS_LOOKUP =
(1 << 0x08) | // backspace
(1 << 0x09) | // tab
(1 << 0x0a) | // newline
(1 << 0x0c) | // form feed
(1 << 0x0d) | // carriage return
(1 << 0x22) | // double quote
(1 << 0x5c); // backslash
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev The string being parsed contains characters that are not in scope of the given base.
*/
error StringsInvalidChar();
/**
* @dev The string being parsed is not a properly formatted address.
*/
error StringsInvalidAddressFormat();
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly ("memory-safe") {
ptr := add(add(buffer, 0x20), length)
}
while (true) {
ptr--;
assembly ("memory-safe") {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
* representation, according to EIP-55.
*/
function toChecksumHexString(address addr) internal pure returns (string memory) {
bytes memory buffer = bytes(toHexString(addr));
// hash the hex part of buffer (skip length + 2 bytes, length 40)
uint256 hashValue;
assembly ("memory-safe") {
hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
}
for (uint256 i = 41; i > 1; --i) {
// possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
// case shift by xoring with 0x20
buffer[i] ^= 0x20;
}
hashValue >>= 4;
}
return string(buffer);
}
/**
* @dev Converts a `bytes` buffer to its ASCII `string` hexadecimal representation.
*/
function toHexString(bytes memory input) internal pure returns (string memory) {
unchecked {
bytes memory buffer = new bytes(2 * input.length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 0; i < input.length; ++i) {
uint8 v = uint8(input[i]);
buffer[2 * i + 2] = HEX_DIGITS[v >> 4];
buffer[2 * i + 3] = HEX_DIGITS[v & 0xf];
}
return string(buffer);
}
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return Bytes.equal(bytes(a), bytes(b));
}
/**
* @dev Parse a decimal string and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input) internal pure returns (uint256) {
return parseUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
uint256 result = 0;
for (uint256 i = begin; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 9) return (false, 0);
result *= 10;
result += chr;
}
return (true, result);
}
/**
* @dev Parse a decimal string and returns the value as a `int256`.
*
* Requirements:
* - The string must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input) internal pure returns (int256) {
return parseInt(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
(bool success, int256 value) = tryParseInt(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
* the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
}
uint256 private constant ABS_MIN_INT256 = 2 ** 255;
/**
* @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character or if the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, int256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseIntUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseIntUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, int256 value) {
bytes memory buffer = bytes(input);
// Check presence of a negative sign.
bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
bool positiveSign = sign == bytes1("+");
bool negativeSign = sign == bytes1("-");
uint256 offset = (positiveSign || negativeSign).toUint();
(bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);
if (absSuccess && absValue < ABS_MIN_INT256) {
return (true, negativeSign ? -int256(absValue) : int256(absValue));
} else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
return (true, type(int256).min);
} else return (false, 0);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input) internal pure returns (uint256) {
return parseHexUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseHexUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
* invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseHexUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseHexUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
// skip 0x prefix if present
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 offset = hasPrefix.toUint() * 2;
uint256 result = 0;
for (uint256 i = begin + offset; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 15) return (false, 0);
result *= 16;
unchecked {
// Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
// This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.
result += chr;
}
}
return (true, result);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input) internal pure returns (address) {
return parseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
(bool success, address value) = tryParseAddress(input, begin, end);
if (!success) revert StringsInvalidAddressFormat();
return value;
}
/**
* @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
* formatted address. See {parseAddress-string} requirements.
*/
function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
return tryParseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
* formatted address. See {parseAddress-string-uint256-uint256} requirements.
*/
function tryParseAddress(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, address value) {
if (end > bytes(input).length || begin > end) return (false, address(0));
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
// check that input is the correct length
if (end - begin == expectedLength) {
// length guarantees that this does not overflow, and value is at most type(uint160).max
(bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
return (s, address(uint160(v)));
} else {
return (false, address(0));
}
}
function _tryParseChr(bytes1 chr) private pure returns (uint8) {
uint8 value = uint8(chr);
// Try to parse `chr`:
// - Case 1: [0-9]
// - Case 2: [a-f]
// - Case 3: [A-F]
// - otherwise not supported
unchecked {
if (value > 47 && value < 58) value -= 48;
else if (value > 96 && value < 103) value -= 87;
else if (value > 64 && value < 71) value -= 55;
else return type(uint8).max;
}
return value;
}
/**
* @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.
*
* WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.
*
* NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of
* RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode
* characters that are not in this range, but other tooling may provide different results.
*/
function escapeJSON(string memory input) internal pure returns (string memory) {
bytes memory buffer = bytes(input);
bytes memory output = new bytes(2 * buffer.length); // worst case scenario
uint256 outputLength = 0;
for (uint256 i = 0; i < buffer.length; ++i) {
bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));
if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {
output[outputLength++] = "\\";
if (char == 0x08) output[outputLength++] = "b";
else if (char == 0x09) output[outputLength++] = "t";
else if (char == 0x0a) output[outputLength++] = "n";
else if (char == 0x0c) output[outputLength++] = "f";
else if (char == 0x0d) output[outputLength++] = "r";
else if (char == 0x5c) output[outputLength++] = "\\";
else if (char == 0x22) {
// solhint-disable-next-line quotes
output[outputLength++] = '"';
}
} else {
output[outputLength++] = char;
}
}
// write the actual length and deallocate unused memory
assembly ("memory-safe") {
mstore(output, outputLength)
mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))
}
return string(output);
}
/**
* @dev Reads a bytes32 from a bytes array without bounds checking.
*
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
* assembly block as such would prevent some optimizations.
*/
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
// This is not memory safe in the general case, but all calls to this private function are within bounds.
assembly ("memory-safe") {
value := mload(add(add(buffer, 0x20), offset))
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/Arrays.sol)
// This file was procedurally generated from scripts/generate/templates/Arrays.js.
pragma solidity ^0.8.24;
import {Comparators} from "./Comparators.sol";
import {SlotDerivation} from "./SlotDerivation.sol";
import {StorageSlot} from "./StorageSlot.sol";
import {Math} from "./math/Math.sol";
/**
* @dev Collection of functions related to array types.
*/
library Arrays {
using SlotDerivation for bytes32;
using StorageSlot for bytes32;
/**
* @dev Sort an array of uint256 (in memory) following the provided comparator function.
*
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
*
* NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
* array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
* consume more gas than is available in a block, leading to potential DoS.
*
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
*/
function sort(
uint256[] memory array,
function(uint256, uint256) pure returns (bool) comp
) internal pure returns (uint256[] memory) {
_quickSort(_begin(array), _end(array), comp);
return array;
}
/**
* @dev Variant of {sort} that sorts an array of uint256 in increasing order.
*/
function sort(uint256[] memory array) internal pure returns (uint256[] memory) {
sort(array, Comparators.lt);
return array;
}
/**
* @dev Sort an array of address (in memory) following the provided comparator function.
*
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
*
* NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
* array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
* consume more gas than is available in a block, leading to potential DoS.
*
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
*/
function sort(
address[] memory array,
function(address, address) pure returns (bool) comp
) internal pure returns (address[] memory) {
sort(_castToUint256Array(array), _castToUint256Comp(comp));
return array;
}
/**
* @dev Variant of {sort} that sorts an array of address in increasing order.
*/
function sort(address[] memory array) internal pure returns (address[] memory) {
sort(_castToUint256Array(array), Comparators.lt);
return array;
}
/**
* @dev Sort an array of bytes32 (in memory) following the provided comparator function.
*
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
*
* NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
* array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
* consume more gas than is available in a block, leading to potential DoS.
*
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
*/
function sort(
bytes32[] memory array,
function(bytes32, bytes32) pure returns (bool) comp
) internal pure returns (bytes32[] memory) {
sort(_castToUint256Array(array), _castToUint256Comp(comp));
return array;
}
/**
* @dev Variant of {sort} that sorts an array of bytes32 in increasing order.
*/
function sort(bytes32[] memory array) internal pure returns (bytes32[] memory) {
sort(_castToUint256Array(array), Comparators.lt);
return array;
}
/**
* @dev Performs a quick sort of a segment of memory. The segment sorted starts at `begin` (inclusive), and stops
* at end (exclusive). Sorting follows the `comp` comparator.
*
* Invariant: `begin <= end`. This is the case when initially called by {sort} and is preserved in subcalls.
*
* IMPORTANT: Memory locations between `begin` and `end` are not validated/zeroed. This function should
* be used only if the limits are within a memory array.
*/
function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure returns (bool) comp) private pure {
unchecked {
if (end - begin < 0x40) return;
// Use first element as pivot
uint256 pivot = _mload(begin);
// Position where the pivot should be at the end of the loop
uint256 pos = begin;
for (uint256 it = begin + 0x20; it < end; it += 0x20) {
if (comp(_mload(it), pivot)) {
// If the value stored at the iterator's position comes before the pivot, we increment the
// position of the pivot and move the value there.
pos += 0x20;
_swap(pos, it);
}
}
_swap(begin, pos); // Swap pivot into place
_quickSort(begin, pos, comp); // Sort the left side of the pivot
_quickSort(pos + 0x20, end, comp); // Sort the right side of the pivot
}
}
/**
* @dev Pointer to the memory location of the first element of `array`.
*/
function _begin(uint256[] memory array) private pure returns (uint256 ptr) {
assembly ("memory-safe") {
ptr := add(array, 0x20)
}
}
/**
* @dev Pointer to the memory location of the first memory word (32bytes) after `array`. This is the memory word
* that comes just after the last element of the array.
*/
function _end(uint256[] memory array) private pure returns (uint256 ptr) {
unchecked {
return _begin(array) + array.length * 0x20;
}
}
/**
* @dev Load memory word (as a uint256) at location `ptr`.
*/
function _mload(uint256 ptr) private pure returns (uint256 value) {
assembly {
value := mload(ptr)
}
}
/**
* @dev Swaps the elements memory location `ptr1` and `ptr2`.
*/
function _swap(uint256 ptr1, uint256 ptr2) private pure {
assembly {
let value1 := mload(ptr1)
let value2 := mload(ptr2)
mstore(ptr1, value2)
mstore(ptr2, value1)
}
}
/// @dev Helper: low level cast address memory array to uint256 memory array
function _castToUint256Array(address[] memory input) private pure returns (uint256[] memory output) {
assembly {
output := input
}
}
/// @dev Helper: low level cast bytes32 memory array to uint256 memory array
function _castToUint256Array(bytes32[] memory input) private pure returns (uint256[] memory output) {
assembly {
output := input
}
}
/// @dev Helper: low level cast address comp function to uint256 comp function
function _castToUint256Comp(
function(address, address) pure returns (bool) input
) private pure returns (function(uint256, uint256) pure returns (bool) output) {
assembly {
output := input
}
}
/// @dev Helper: low level cast bytes32 comp function to uint256 comp function
function _castToUint256Comp(
function(bytes32, bytes32) pure returns (bool) input
) private pure returns (function(uint256, uint256) pure returns (bool) output) {
assembly {
output := input
}
}
/**
* @dev Searches a sorted `array` and returns the first index that contains
* a value greater or equal to `element`. If no such index exists (i.e. all
* values in the array are strictly less than `element`), the array length is
* returned. Time complexity O(log n).
*
* NOTE: The `array` is expected to be sorted in ascending order, and to
* contain no repeated elements.
*
* IMPORTANT: Deprecated. This implementation behaves as {lowerBound} but lacks
* support for repeated elements in the array. The {lowerBound} function should
* be used instead.
*/
function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value > element) {
high = mid;
} else {
low = mid + 1;
}
}
// At this point `low` is the exclusive upper bound. We will return the inclusive upper bound.
if (low > 0 && unsafeAccess(array, low - 1).value == element) {
return low - 1;
} else {
return low;
}
}
/**
* @dev Searches an `array` sorted in ascending order and returns the first
* index that contains a value greater or equal than `element`. If no such index
* exists (i.e. all values in the array are strictly less than `element`), the array
* length is returned. Time complexity O(log n).
*
* See C++'s https://en.cppreference.com/w/cpp/algorithm/lower_bound[lower_bound].
*/
function lowerBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value < element) {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
} else {
high = mid;
}
}
return low;
}
/**
* @dev Searches an `array` sorted in ascending order and returns the first
* index that contains a value strictly greater than `element`. If no such index
* exists (i.e. all values in the array are strictly less than `element`), the array
* length is returned. Time complexity O(log n).
*
* See C++'s https://en.cppreference.com/w/cpp/algorithm/upper_bound[upper_bound].
*/
function upperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value > element) {
high = mid;
} else {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
}
}
return low;
}
/**
* @dev Same as {lowerBound}, but with an array in memory.
*/
function lowerBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeMemoryAccess(array, mid) < element) {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
} else {
high = mid;
}
}
return low;
}
/**
* @dev Same as {upperBound}, but with an array in memory.
*/
function upperBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeMemoryAccess(array, mid) > element) {
high = mid;
} else {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
}
}
return low;
}
/**
* @dev Copies the content of `array`, from `start` (included) to the end of `array` into a new address array in
* memory.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
*/
function slice(address[] memory array, uint256 start) internal pure returns (address[] memory) {
return slice(array, start, array.length);
}
/**
* @dev Copies the content of `array`, from `start` (included) to `end` (excluded) into a new address array in
* memory. The `end` argument is truncated to the length of the `array`.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
*/
function slice(address[] memory array, uint256 start, uint256 end) internal pure returns (address[] memory) {
// sanitize
end = Math.min(end, array.length);
start = Math.min(start, end);
// allocate and copy
address[] memory result = new address[](end - start);
assembly ("memory-safe") {
mcopy(add(result, 0x20), add(add(array, 0x20), mul(start, 0x20)), mul(sub(end, start), 0x20))
}
return result;
}
/**
* @dev Copies the content of `array`, from `start` (included) to the end of `array` into a new bytes32 array in
* memory.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
*/
function slice(bytes32[] memory array, uint256 start) internal pure returns (bytes32[] memory) {
return slice(array, start, array.length);
}
/**
* @dev Copies the content of `array`, from `start` (included) to `end` (excluded) into a new bytes32 array in
* memory. The `end` argument is truncated to the length of the `array`.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
*/
function slice(bytes32[] memory array, uint256 start, uint256 end) internal pure returns (bytes32[] memory) {
// sanitize
end = Math.min(end, array.length);
start = Math.min(start, end);
// allocate and copy
bytes32[] memory result = new bytes32[](end - start);
assembly ("memory-safe") {
mcopy(add(result, 0x20), add(add(array, 0x20), mul(start, 0x20)), mul(sub(end, start), 0x20))
}
return result;
}
/**
* @dev Copies the content of `array`, from `start` (included) to the end of `array` into a new uint256 array in
* memory.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
*/
function slice(uint256[] memory array, uint256 start) internal pure returns (uint256[] memory) {
return slice(array, start, array.length);
}
/**
* @dev Copies the content of `array`, from `start` (included) to `end` (excluded) into a new uint256 array in
* memory. The `end` argument is truncated to the length of the `array`.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
*/
function slice(uint256[] memory array, uint256 start, uint256 end) internal pure returns (uint256[] memory) {
// sanitize
end = Math.min(end, array.length);
start = Math.min(start, end);
// allocate and copy
uint256[] memory result = new uint256[](end - start);
assembly ("memory-safe") {
mcopy(add(result, 0x20), add(add(array, 0x20), mul(start, 0x20)), mul(sub(end, start), 0x20))
}
return result;
}
/**
* @dev Moves the content of `array`, from `start` (included) to the end of `array` to the start of that array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead.
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(address[] memory array, uint256 start) internal pure returns (address[] memory) {
return splice(array, start, array.length);
}
/**
* @dev Moves the content of `array`, from `start` (included) to `end` (excluded) to the start of that array. The
* `end` argument is truncated to the length of the `array`.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead.
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(address[] memory array, uint256 start, uint256 end) internal pure returns (address[] memory) {
// sanitize
end = Math.min(end, array.length);
start = Math.min(start, end);
// move and resize
assembly ("memory-safe") {
mcopy(add(array, 0x20), add(add(array, 0x20), mul(start, 0x20)), mul(sub(end, start), 0x20))
mstore(array, sub(end, start))
}
return array;
}
/**
* @dev Replaces elements in `array` starting at `pos` with all elements from `replacement`.
*
* Parameters are clamped to valid ranges (i.e. `pos` is clamped to `[0, array.length]`).
* If `pos >= array.length`, no replacement occurs and the array is returned unchanged.
*
* NOTE: This function modifies the provided array in place.
*/
function replace(
address[] memory array,
uint256 pos,
address[] memory replacement
) internal pure returns (address[] memory) {
return replace(array, pos, replacement, 0, replacement.length);
}
/**
* @dev Replaces elements in `array` starting at `pos` with elements from `replacement` starting at `offset`.
* Copies at most `length` elements from `replacement` to `array`.
*
* Parameters are clamped to valid ranges (i.e. `pos` is clamped to `[0, array.length]`, `offset` is
* clamped to `[0, replacement.length]`, and `length` is clamped to `min(length, replacement.length - offset,
* array.length - pos)`). If `pos >= array.length` or `offset >= replacement.length`, no replacement occurs
* and the array is returned unchanged.
*
* NOTE: This function modifies the provided array in place.
*/
function replace(
address[] memory array,
uint256 pos,
address[] memory replacement,
uint256 offset,
uint256 length
) internal pure returns (address[] memory) {
// sanitize
pos = Math.min(pos, array.length);
offset = Math.min(offset, replacement.length);
length = Math.min(length, Math.min(replacement.length - offset, array.length - pos));
// allocate and copy
assembly ("memory-safe") {
mcopy(
add(add(array, 0x20), mul(pos, 0x20)),
add(add(replacement, 0x20), mul(offset, 0x20)),
mul(length, 0x20)
)
}
return array;
}
/**
* @dev Moves the content of `array`, from `start` (included) to the end of `array` to the start of that array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead.
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(bytes32[] memory array, uint256 start) internal pure returns (bytes32[] memory) {
return splice(array, start, array.length);
}
/**
* @dev Moves the content of `array`, from `start` (included) to `end` (excluded) to the start of that array. The
* `end` argument is truncated to the length of the `array`.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead.
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(bytes32[] memory array, uint256 start, uint256 end) internal pure returns (bytes32[] memory) {
// sanitize
end = Math.min(end, array.length);
start = Math.min(start, end);
// move and resize
assembly ("memory-safe") {
mcopy(add(array, 0x20), add(add(array, 0x20), mul(start, 0x20)), mul(sub(end, start), 0x20))
mstore(array, sub(end, start))
}
return array;
}
/**
* @dev Replaces elements in `array` starting at `pos` with all elements from `replacement`.
*
* Parameters are clamped to valid ranges (i.e. `pos` is clamped to `[0, array.length]`).
* If `pos >= array.length`, no replacement occurs and the array is returned unchanged.
*
* NOTE: This function modifies the provided array in place.
*/
function replace(
bytes32[] memory array,
uint256 pos,
bytes32[] memory replacement
) internal pure returns (bytes32[] memory) {
return replace(array, pos, replacement, 0, replacement.length);
}
/**
* @dev Replaces elements in `array` starting at `pos` with elements from `replacement` starting at `offset`.
* Copies at most `length` elements from `replacement` to `array`.
*
* Parameters are clamped to valid ranges (i.e. `pos` is clamped to `[0, array.length]`, `offset` is
* clamped to `[0, replacement.length]`, and `length` is clamped to `min(length, replacement.length - offset,
* array.length - pos)`). If `pos >= array.length` or `offset >= replacement.length`, no replacement occurs
* and the array is returned unchanged.
*
* NOTE: This function modifies the provided array in place.
*/
function replace(
bytes32[] memory array,
uint256 pos,
bytes32[] memory replacement,
uint256 offset,
uint256 length
) internal pure returns (bytes32[] memory) {
// sanitize
pos = Math.min(pos, array.length);
offset = Math.min(offset, replacement.length);
length = Math.min(length, Math.min(replacement.length - offset, array.length - pos));
// allocate and copy
assembly ("memory-safe") {
mcopy(
add(add(array, 0x20), mul(pos, 0x20)),
add(add(replacement, 0x20), mul(offset, 0x20)),
mul(length, 0x20)
)
}
return array;
}
/**
* @dev Moves the content of `array`, from `start` (included) to the end of `array` to the start of that array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead.
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(uint256[] memory array, uint256 start) internal pure returns (uint256[] memory) {
return splice(array, start, array.length);
}
/**
* @dev Moves the content of `array`, from `start` (included) to `end` (excluded) to the start of that array. The
* `end` argument is truncated to the length of the `array`.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead.
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(uint256[] memory array, uint256 start, uint256 end) internal pure returns (uint256[] memory) {
// sanitize
end = Math.min(end, array.length);
start = Math.min(start, end);
// move and resize
assembly ("memory-safe") {
mcopy(add(array, 0x20), add(add(array, 0x20), mul(start, 0x20)), mul(sub(end, start), 0x20))
mstore(array, sub(end, start))
}
return array;
}
/**
* @dev Replaces elements in `array` starting at `pos` with all elements from `replacement`.
*
* Parameters are clamped to valid ranges (i.e. `pos` is clamped to `[0, array.length]`).
* If `pos >= array.length`, no replacement occurs and the array is returned unchanged.
*
* NOTE: This function modifies the provided array in place.
*/
function replace(
uint256[] memory array,
uint256 pos,
uint256[] memory replacement
) internal pure returns (uint256[] memory) {
return replace(array, pos, replacement, 0, replacement.length);
}
/**
* @dev Replaces elements in `array` starting at `pos` with elements from `replacement` starting at `offset`.
* Copies at most `length` elements from `replacement` to `array`.
*
* Parameters are clamped to valid ranges (i.e. `pos` is clamped to `[0, array.length]`, `offset` is
* clamped to `[0, replacement.length]`, and `length` is clamped to `min(length, replacement.length - offset,
* array.length - pos)`). If `pos >= array.length` or `offset >= replacement.length`, no replacement occurs
* and the array is returned unchanged.
*
* NOTE: This function modifies the provided array in place.
*/
function replace(
uint256[] memory array,
uint256 pos,
uint256[] memory replacement,
uint256 offset,
uint256 length
) internal pure returns (uint256[] memory) {
// sanitize
pos = Math.min(pos, array.length);
offset = Math.min(offset, replacement.length);
length = Math.min(length, Math.min(replacement.length - offset, array.length - pos));
// allocate and copy
assembly ("memory-safe") {
mcopy(
add(add(array, 0x20), mul(pos, 0x20)),
add(add(replacement, 0x20), mul(offset, 0x20)),
mul(length, 0x20)
)
}
return array;
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).getAddressSlot();
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).getBytes32Slot();
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).getUint256Slot();
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeAccess(bytes[] storage arr, uint256 pos) internal pure returns (StorageSlot.BytesSlot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).getBytesSlot();
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeAccess(string[] storage arr, uint256 pos) internal pure returns (StorageSlot.StringSlot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).getStringSlot();
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(bytes32[] memory arr, uint256 pos) internal pure returns (bytes32 res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(bytes[] memory arr, uint256 pos) internal pure returns (bytes memory res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(string[] memory arr, uint256 pos) internal pure returns (string memory res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, or initialize elements if length is increased.
*/
function unsafeSetLength(address[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
/**
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, or initialize elements if length is increased.
*/
function unsafeSetLength(bytes32[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
/**
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, or initialize elements if length is increased.
*/
function unsafeSetLength(uint256[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
/**
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, or initialize elements if length is increased.
*/
function unsafeSetLength(bytes[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
/**
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, or initialize elements if length is increased.
*/
function unsafeSetLength(string[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
import { IERC6909 } from "./IERC6909.sol";
interface ISplitsWarehouse is IERC6909 {
function NATIVE_TOKEN() external view returns (address);
function deposit(address receiver, address token, uint256 amount) external payable;
function batchDeposit(address[] calldata receivers, address token, uint256[] calldata amounts) external;
function batchTransfer(address[] calldata receivers, address token, uint256[] calldata amounts) external;
function withdraw(address owner, address token) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
/**
* @notice ERC-1271 with guards for same signer being used on multiple splits
* @author Splits
* Based on coinbase (https://github.com/coinbase/smart-wallet/blob/main/src/ERC1271.sol)
*/
abstract contract ERC1271 is EIP712 {
/* -------------------------------------------------------------------------- */
/* CONSTANTS */
/* -------------------------------------------------------------------------- */
/**
* @dev We use `bytes32 hash` rather than `bytes message`
* In the EIP-712 context, `bytes message` would be useful for showing users a full message
* they are signing in some wallet preview. But in this case, to prevent replay
* across accounts, we are always dealing with nested messages, and so the
* input should be a EIP-191 or EIP-712 output hash.
* E.g. The input hash would be result of
*
* keccak256("\x19\x01" || someDomainSeparator || hashStruct(someStruct))
*
* OR
*
* keccak256("\x19Ethereum Signed Message:\n" || len(someMessage) || someMessage),
*/
bytes32 private constant _MESSAGE_TYPEHASH = keccak256("SplitWalletMessage(bytes32 hash)");
/* -------------------------------------------------------------------------- */
/* CONSTRUCTOR */
/* -------------------------------------------------------------------------- */
/**
* @dev Initializes the {EIP712} domain separator.
*/
constructor(string memory _name, string memory _version) EIP712(_name, _version) { }
/* -------------------------------------------------------------------------- */
/* PUBLIC FUNCTIONS */
/* -------------------------------------------------------------------------- */
/**
* @notice Validates the signature with ERC1271 return, so that this account can also be used as a signer.
*/
function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4 result) {
if (
SignatureChecker.isValidSignatureNow({
signer: getSigner(),
hash: replaySafeHash(hash),
signature: signature
})
) {
// bytes4(keccak256("isValidSignature(bytes32,bytes)"))
return 0x1626ba7e;
}
return 0xffffffff;
}
/**
* @dev Returns an EIP-712-compliant hash of `hash`,
* where the domainSeparator includes address(this) and block.chainId
* to protect against the same signature being used for many accounts.
* @return
* keccak256(\x19\x01 || this.domainSeparator ||
* hashStruct(SplitWalletMessage({
* hash: `hash`
* }))
* )
*/
function replaySafeHash(bytes32 hash) public view virtual returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(_MESSAGE_TYPEHASH, hash)));
}
/// @dev returns the ERC1271 signer.
function getSigner() internal view virtual returns (address);
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
import { Pausable } from "./Pausable.sol";
import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
/**
* @title Wallet Implementation
* @author Splits
* @notice Minimal smart wallet clone-implementation.
*/
abstract contract Wallet is Pausable, ERC721Holder, ERC1155Holder {
/* -------------------------------------------------------------------------- */
/* ERRORS */
/* -------------------------------------------------------------------------- */
error InvalidCalldataForEOA(Call call);
/* -------------------------------------------------------------------------- */
/* STRUCTS */
/* -------------------------------------------------------------------------- */
struct Call {
address to;
uint256 value;
bytes data;
}
/* -------------------------------------------------------------------------- */
/* EVENTS */
/* -------------------------------------------------------------------------- */
event ExecCalls(Call[] calls);
/* -------------------------------------------------------------------------- */
/* CONSTRUCTOR & INITIALIZER */
/* -------------------------------------------------------------------------- */
function __initWallet(address _owner) internal {
__initPausable(_owner, false);
}
/* -------------------------------------------------------------------------- */
/* FUNCTONS */
/* -------------------------------------------------------------------------- */
/**
* @notice Execute a batch of calls.
* @dev The calls are executed in order, reverting if any of them fails. Can
* only be called by the owner.
* @param _calls The calls to execute
*/
function execCalls(Call[] calldata _calls)
external
payable
returns (uint256 blockNumber, bytes[] memory returnData)
{
address caller = msg.sender;
blockNumber = block.number;
uint256 length = _calls.length;
returnData = new bytes[](length);
bool success;
for (uint256 i; i < length; ++i) {
// prevent user from executing calls after transferring ownership.
if (caller != owner) revert Unauthorized();
Call calldata calli = _calls[i];
if (calli.to.code.length == 0) {
// When the call is to an EOA, the calldata must be empty.
if (calli.data.length > 0) revert InvalidCalldataForEOA({ call: calli });
}
(success, returnData[i]) = calli.to.call{ value: calli.value }(calli.data);
// solhint-disable-next-line
require(success, string(returnData[i]));
}
emit ExecCalls({ calls: _calls });
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IGSE} from "@aztec/governance/GSE.sol";
import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol";
import {Errors} from "@aztec/governance/libraries/Errors.sol";
import {IPayload} from "./interfaces/IPayload.sol";
import {IProposerPayload} from "./interfaces/IProposerPayload.sol";
/**
* @title GSEPayload
*
* @notice This contract is used by the GovernanceProposer to enforce checks on an existing payload.
*
* In the GovernanceProposer, support for payloads may be signalled by the current block proposer of the
* current canonical rollup according to the Registry. Once a payload receives enough support,
* it may be submitted by the GovernanceProposer.
*
* Instead of proposing the original payload to Governance, the GovernanceProposer creates a new GSEPayload,
* referencing the original payload. It is this new GSEPayload which is proposed via Governance.propose.
* If/when the GSE payload is executed, Governance calls `getActions`, which copies the actions of the original
* payload, and appends a call to `amIValid` to it.
*
* NB `amIValid` will fail if the 2/3 of the total stake is not "following latest", irrespective
* of what the original proposal does.
* Note that the GSE is used to perform these checks, hence the name.
* Note this check is skipped if the canonical rollup does not match the latest to avoid livelock cases.
*
* For example, if the original proposal is just to update a configuration parameter, but in the meantime
* half of the stake has exited the latest rollup in the GSE, `amIValid` will fail.
*
* In such an event, your recourse is either:
* - wait for the latest rollup to have at least 2/3 of the total stake
* - `GSE.proposeWithLock`, which bypasses the GovernanceProposer
*/
contract GSEPayload is IProposerPayload {
IPayload public immutable ORIGINAL;
IGSE public immutable GSE;
IRegistry public immutable REGISTRY;
constructor(IPayload _originalPayloadProposal, IGSE _gse, IRegistry _registry) {
ORIGINAL = _originalPayloadProposal;
GSE = _gse;
REGISTRY = _registry;
}
function getOriginalPayload() external view override(IProposerPayload) returns (IPayload) {
return ORIGINAL;
}
function getURI() external view override(IPayload) returns (string memory) {
return ORIGINAL.getURI();
}
/**
* @notice called by the Governance contract when executing the proposal.
*
* Note that this contract simply appends a call to `amIValid` to the original actions.
*/
function getActions() external view override(IPayload) returns (IPayload.Action[] memory) {
IPayload.Action[] memory originalActions = ORIGINAL.getActions();
IPayload.Action[] memory actions = new IPayload.Action[](originalActions.length + 1);
for (uint256 i = 0; i < originalActions.length; i++) {
actions[i] = originalActions[i];
}
actions[originalActions.length] =
IPayload.Action({target: address(this), data: abi.encodeWithSelector(GSEPayload.amIValid.selector)});
return actions;
}
/**
* @notice Validates that the proposal maintains governance system integrity by ensuring
* sufficient stake remains on the active rollup after execution.
*
* The validation passes when EITHER:
* 1. The latest rollup (plus bonus instance) has >2/3 of total stake, OR
* 2. A Registry/GSE mismatch is detected (fail-open to prevent governance livelock)
*
* @dev Beware that the >2/3 support means that 1/3 of the stake can be used to reject proposals.
*
* @dev The "bonus instance" is a special GSE mechanism where attesters automatically
* follow the latest rollup without re-depositing. Their stake counts toward
* the latest rollup's total for this validation.
*
* @dev LIVELOCK PREVENTION: When canonical != latest, we intentionally return true
* to bypass validation. This mismatch typically indicates the GovernanceProposer
* is still pointing to a stale GSE contract after a rollup upgrade.
*
* Why this creates a livelock:
* - The stale GSE tracks an outdated rollup as "latest"
* - The Registry correctly identifies the new rollup as canonical
* - Economic incentives drive attesters to follow the canonical (where rewards are)
* - The stale GSE's "latest" gradually bleeds stake as rational actors exit
* - While theoretically possible to maintain >2/3 stake, it becomes increasingly
* unlikely as only inattentive or non-reward-seeking attesters remain
* - Proposals keep failing validation, creating a probabilistic livelock where
* progress is technically possible but economically improbable
*
* By returning true, we provide an escape hatch that allows governance to
* continue functioning despite the misconfiguration, enabling corrective
* proposals to update the GovernanceProposer's GSE reference.
*
* @dev This function executes as the final action of the proposal (see getActions).
* It either reverts with an error (proposal invalid) or returns true (proposal valid).
* The boolean return value is effectively ceremonial - only the revert matters.
*
* @return Always returns true if the proposal is valid; reverts otherwise
*/
function amIValid() external view override(IProposerPayload) returns (bool) {
address canonicalRollup = address(REGISTRY.getCanonicalRollup());
address latestRollup = GSE.getLatestRollup();
// Bypass validation on mismatch to prevent economically-driven livelock
// In theory, >2/3 stake could remain on the stale rollup, but economic
// incentives make this highly unlikely
if (canonicalRollup != latestRollup) {
return true;
}
// Standard validation: ensure >2/3 of stake remains with the latest rollup
uint256 totalSupply = GSE.totalSupply();
address bonusInstance = GSE.getBonusInstanceAddress();
uint256 effectiveSupplyOfLatestRollup = GSE.supplyOf(latestRollup) + GSE.supplyOf(bonusInstance);
require(effectiveSupplyOfLatestRollup > totalSupply * 2 / 3, Errors.GovernanceProposer__GSEPayloadInvalid());
return true;
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
// solhint-disable imports-order
pragma solidity >=0.8.27;
import {IEmpire} from "./IEmpire.sol";
interface IGovernanceProposer is IEmpire {
function getProposalProposer(uint256 _proposalId) external view returns (address);
function getGovernance() external view returns (address);
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
// solhint-disable imports-order
pragma solidity >=0.8.27;
import {SignatureLib, Signature} from "@aztec/shared/libraries/SignatureLib.sol";
import {IEmpire, IEmperor} from "@aztec/governance/interfaces/IEmpire.sol";
import {Slot} from "@aztec/shared/libraries/TimeMath.sol";
import {Errors} from "@aztec/governance/libraries/Errors.sol";
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
import {EIP712} from "@oz/utils/cryptography/EIP712.sol";
import {CompressedTimeMath, CompressedSlot} from "@aztec/shared/libraries/CompressedTimeMath.sol";
struct RoundAccounting {
Slot lastSignalSlot;
IPayload payloadWithMostSignals;
bool executed;
}
struct CompressedRoundAccounting {
CompressedSlot lastSignalSlot;
IPayload payloadWithMostSignals;
bool executed;
mapping(IPayload payload => uint256 count) signalCount;
}
/**
* @title EmpireBase
* @author Aztec Labs
* @notice Abstract base contract for a round-based signaling system where designated entities
* signal support for payloads before they are submitted elsewhere.
* Works with an IEmperor (i.e. a Rollup) contract to determine the entity that may signal for a given slot.
*
* @dev PURPOSE:
* This contract allows validators to signal their support for payloads.
*
* There are two primary implementations of this contract:
* - The GovernanceProposer
* - The EmpireSlashingProposer
*
* The GovernanceProposer is used to signal support for payloads before they are submitted to the main Governance
* contract,
* resulting in a two-stage governance process:
* 1. Signal gathering (GovernanceProposer contract) - validators indicate support
* 2. Formal governance (Governance contract) - actual voting and execution
*
* The EmpireSlashingProposer is used to signal support for payloads before they are submitted to a Rollup instance's
* Slasher,
* resulting in a one-stage slashing process:
* 1. Signal gathering (EmpireSlashingProposer contract) - validators indicate support
*
* @dev KEY CONCEPTS:
* **Payload**: A contract with a list of actions (contract calls) to perform.
*
* **Rounds**: Time is divided into rounds of ROUND_SIZE slots. Payloads compete for support
* within a round.
*
* **Instances**: Refers to an instance of the rollup contract, which in this case is exposed via a simplified IEmperor
* interface.
* This contract only needs the instance to determine the current slot (to compute the round), and the current block
* proposer.
*
* **Signalers**: Each slot has a designated signaler (determined by IEmperor).
* Only the current slot's signaler can signal support, either directly or via signature.
* In the current implementation, the entity that may propose a block (i.e. the "proposer") is the signaler.
*
* **Signaling**
* - One signal per slot (enforced by tracking lastSignalSlot)
* - Signals accumulate for payloads within a round
* - First payload to reach QUORUM_SIZE becomes submittable
*
* **Submission**
* - Payloads can be submitted after their round ends with `submitRoundWinner(uint256 _roundNumber)`
* - Round winner must have received at least QUORUM_SIZE signals
* - Submission window: LIFETIME_IN_ROUNDS (5 rounds)
* - Each round's leading payload can only be submitted once
* - `_handleRoundWinner(IPayload _payload)` on the implementing contract is called to handle the winner
*
* @dev SYSTEM PARAMETERS:
* - QUORUM_SIZE: Minimum signals needed for submission
* - ROUND_SIZE: Slots per round
* - Constraint: QUORUM_SIZE > ROUND_SIZE/2 and QUORUM_SIZE ≤ ROUND_SIZE
* Note that it it possible to have QUORUM_SIZE = 1 for ROUND_SIZE = 1, which effectively give all the
* power to the first signal.
*
* @dev SIGNALING METHODS:
* 1. Direct signal: Current signaler calls `signal()`
* 2. Delegated signal: Anyone submits with signaler's signature via `signalWithSig()`
* - Uses EIP-712 for signature verification
* - Includes slot and instance to prevent replay attacks
*
* @dev ABSTRACT FUNCTIONS:
* Implementing contracts must provide:
* - `getInstance()`: Returns the IEmperor instance for slot/signaler info
* - `_handleRoundWinner(IPayload _payload)`: Called during `submitRoundWinner`
*
* Note this contract can support multiple instances/rollups. This is because the instance is retrieved dynamically from
* the
* underlying implementation. For example, when the GovernanceProposer is used, the instance is the canonical rollup,
* which will change whenever there is a new canonical rollup.
*
* This also means that if the new canonical rollup does not support the IEmperor interface, this contract will not
* work,
* and a different implementation will need to be specified as part of the payload which deploys the new canonical
* instance.
*/
abstract contract EmpireBase is EIP712, IEmpire {
using SignatureLib for Signature;
using CompressedTimeMath for Slot;
using CompressedTimeMath for CompressedSlot;
// EIP-712 type hash for the Signal struct
bytes32 public constant SIGNAL_TYPEHASH = keccak256("Signal(address payload,uint256 slot,address instance)");
// The number of signals needed for a payload to be considered submittable.
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, 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;
// Mapping of instance to round number to round accounting.
mapping(address instance => mapping(uint256 roundNumber => CompressedRoundAccounting)) internal rounds;
constructor(uint256 _quorumSize, uint256 _roundSize, uint256 _lifetimeInRounds, uint256 _executionDelayInRounds)
EIP712("EmpireBase", "1")
{
QUORUM_SIZE = _quorumSize;
ROUND_SIZE = _roundSize;
LIFETIME_IN_ROUNDS = _lifetimeInRounds;
EXECUTION_DELAY_IN_ROUNDS = _executionDelayInRounds;
require(QUORUM_SIZE > ROUND_SIZE / 2, Errors.EmpireBase__InvalidQuorumAndRoundSize(QUORUM_SIZE, ROUND_SIZE));
require(QUORUM_SIZE <= ROUND_SIZE, Errors.EmpireBase__QuorumCannotBeLargerThanRoundSize(QUORUM_SIZE, ROUND_SIZE));
require(
LIFETIME_IN_ROUNDS > EXECUTION_DELAY_IN_ROUNDS,
Errors.EmpireBase__InvalidLifetimeAndExecutionDelay(LIFETIME_IN_ROUNDS, EXECUTION_DELAY_IN_ROUNDS)
);
}
/**
* @notice Signal support for a payload
*
* @dev this only works if msg.sender is the current signaler
*
* @param _payload - The address of the IPayload to signal support for
*
* @return True if executed successfully, false otherwise
*/
function signal(IPayload _payload) external override(IEmpire) returns (bool) {
return _internalSignal(_payload, Signature({v: 0, r: bytes32(0), s: bytes32(0)}));
}
/**
* @notice Signal support for a payload with a signature from the current signaler
*
* @param _payload - The payload to signal support for
* @param _sig - A signature from the signaler
*
* @return True if executed successfully, false otherwise
*/
function signalWithSig(IPayload _payload, Signature memory _sig) external override(IEmpire) returns (bool) {
return _internalSignal(_payload, _sig);
}
/**
* @notice Submit the round winner to the implementation's `_handleRoundWinner` function
*
* @dev calls `_handleRoundWinner` on the implementing contract with the winning payload, if applicable.
*
* @param _roundNumber - The round number to execute
*
* @return True if executed successfully, false otherwise
*/
function submitRoundWinner(uint256 _roundNumber) external override(IEmpire) returns (bool) {
// Need to ensure that the round is not active.
address instance = getInstance();
require(instance.code.length > 0, Errors.EmpireBase__InstanceHaveNoCode(instance));
IEmperor selection = IEmperor(instance);
Slot currentSlot = selection.getCurrentSlot();
uint256 currentRound = computeRound(currentSlot);
require(
currentRound > _roundNumber + EXECUTION_DELAY_IN_ROUNDS,
Errors.EmpireBase__RoundTooNew(_roundNumber, currentRound)
);
require(
currentRound <= _roundNumber + LIFETIME_IN_ROUNDS, Errors.EmpireBase__RoundTooOld(_roundNumber, currentRound)
);
CompressedRoundAccounting storage round = rounds[instance][_roundNumber];
require(!round.executed, Errors.EmpireBase__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.EmpireBase__PayloadCannotBeAddressZero());
uint256 signalsCast = round.signalCount[round.payloadWithMostSignals];
require(signalsCast >= QUORUM_SIZE, Errors.EmpireBase__InsufficientSignals(signalsCast, QUORUM_SIZE));
round.executed = true;
emit PayloadSubmitted(round.payloadWithMostSignals, _roundNumber);
require(
_handleRoundWinner(round.payloadWithMostSignals),
Errors.EmpireBase__FailedToSubmitRoundWinner(round.payloadWithMostSignals)
);
return true;
}
/**
* @notice Fetch the signal count for a specific payload in a specific round on a specific instance
*
* @param _instance - The address of the instance
* @param _round - The round to lookup
* @param _payload - The payload to lookup
*
* @return The number of signals
*/
function signalCount(address _instance, uint256 _round, IPayload _payload)
external
view
override(IEmpire)
returns (uint256)
{
return rounds[_instance][_round].signalCount[_payload];
}
/**
* @notice Computes the round at the current slot
*
* @return The round number
*/
function getCurrentRound() external view returns (uint256) {
IEmperor selection = IEmperor(getInstance());
Slot currentSlot = selection.getCurrentSlot();
return computeRound(currentSlot);
}
function getRoundData(address _instance, uint256 _round) external view returns (RoundAccounting memory) {
CompressedRoundAccounting storage compressedRound = rounds[_instance][_round];
return RoundAccounting({
lastSignalSlot: compressedRound.lastSignalSlot.decompress(),
payloadWithMostSignals: compressedRound.payloadWithMostSignals,
executed: compressedRound.executed
});
}
/**
* @notice Computes the round at the given slot
*
* @param _slot - The slot to compute round for
*
* @return The round number
*/
function computeRound(Slot _slot) public view override(IEmpire) returns (uint256) {
return Slot.unwrap(_slot) / ROUND_SIZE;
}
function getSignalSignatureDigest(IPayload _payload, Slot _slot) public view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(SIGNAL_TYPEHASH, _payload, _slot, getInstance())));
}
// Virtual functions
function getInstance() public view virtual override(IEmpire) returns (address);
function _handleRoundWinner(IPayload _payload) internal virtual returns (bool);
function _internalSignal(IPayload _payload, Signature memory _sig) internal returns (bool) {
address instance = getInstance();
require(instance.code.length > 0, Errors.EmpireBase__InstanceHaveNoCode(instance));
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.EmpireBase__SignalAlreadyCastForSlot(currentSlot));
round.lastSignalSlot = currentSlot.compress();
address signaler = selection.getCurrentProposer();
if (_sig.isEmpty()) {
require(msg.sender == signaler, Errors.EmpireBase__OnlyProposerCanSignal(msg.sender, signaler));
} else {
bytes32 digest = getSignalSignatureDigest(_payload, currentSlot);
// _sig.verify will throw if invalid, it is more my sanity that I am doing this for.
require(_sig.verify(signaler, digest), Errors.EmpireBase__OnlyProposerCanSignal(msg.sender, signaler));
}
round.signalCount[_payload] += 1;
if (
round.payloadWithMostSignals != _payload
&& round.signalCount[_payload] > round.signalCount[round.payloadWithMostSignals]
) {
round.payloadWithMostSignals = _payload;
}
emit SignalCast(_payload, roundNumber, signaler);
if (round.signalCount[_payload] == QUORUM_SIZE) {
emit PayloadSubmittable(_payload, roundNumber);
}
return true;
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {Lock, LockParams} from "./../../libraries/LockLib.sol";
import {IATPCore, IATPPeriphery} from "./../base/IATP.sol";
struct LATPStorage {
uint32 accumulationStartTime;
uint32 accumulationCliffDuration;
uint32 accumulationLockDuration;
bool isRevokable;
address revokeBeneficiary;
}
struct RevokableParams {
address revokeBeneficiary;
LockParams lockParams;
}
interface ILATPCore is IATPCore {
error InsufficientStakeable(uint256 stakeable, uint256 allowance);
error LockParamsMustBeEmpty();
function initialize(address _beneficiary, uint256 _allocation, RevokableParams memory _revokableParams) external;
function getAccumulationLock() external view returns (Lock memory);
function getRevokableAmount() external view returns (uint256);
function getStakeableAmount() external view returns (uint256);
}
interface ILATPPeriphery is IATPPeriphery {
function getStore() external view returns (LATPStorage memory);
function getRevokeBeneficiary() external view returns (address);
}
interface ILATP is ILATPCore, ILATPPeriphery {}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/ERC1967/ERC1967Proxy.sol)
pragma solidity ^0.8.22;
import {Proxy} from "../Proxy.sol";
import {ERC1967Utils} from "./ERC1967Utils.sol";
/**
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
* implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy.
*/
contract ERC1967Proxy is Proxy {
/**
* @dev The proxy is left uninitialized.
*/
error ERC1967ProxyUninitialized();
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.
*
* Provided `_data` is passed in a delegate call to `implementation`. This will typically be an encoded function
* call, and allows initializing the storage of the proxy like a Solidity constructor. By default construction
* will fail if `_data` is empty. This behavior can be overridden using a custom {_unsafeAllowUninitialized} that
* returns true. In that case, empty `_data` is ignored and no delegate call to the implementation is performed
* during construction.
*
* Requirements:
*
* - If `data` is empty, `msg.value` must be zero.
*/
constructor(address implementation, bytes memory _data) payable {
if (!_unsafeAllowUninitialized() && _data.length == 0) {
revert ERC1967ProxyUninitialized();
}
ERC1967Utils.upgradeToAndCall(implementation, _data);
}
/**
* @dev Returns the current implementation address.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
*/
function _implementation() internal view virtual override returns (address) {
return ERC1967Utils.getImplementation();
}
/**
* @dev Returns whether the proxy can be left uninitialized.
*
* NOTE: Override this function to allow the proxy to be left uninitialized.
* Consider uninitialized proxies might be susceptible to man-in-the-middle threats
* where the proxy is replaced with a malicious one.
*/
function _unsafeAllowUninitialized() internal pure virtual returns (bool) {
return false;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IEIP712 {
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/Bytes.sol)
pragma solidity ^0.8.24;
import {Math} from "./math/Math.sol";
/**
* @dev Bytes operations.
*/
library Bytes {
/**
* @dev Forward search for `s` in `buffer`
* * If `s` is present in the buffer, returns the index of the first instance
* * If `s` is not present in the buffer, returns type(uint256).max
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`]
*/
function indexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) {
return indexOf(buffer, s, 0);
}
/**
* @dev Forward search for `s` in `buffer` starting at position `pos`
* * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance
* * If `s` is not present in the buffer (at or after `pos`), returns type(uint256).max
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`]
*/
function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) {
uint256 length = buffer.length;
for (uint256 i = pos; i < length; ++i) {
if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) {
return i;
}
}
return type(uint256).max;
}
/**
* @dev Backward search for `s` in `buffer`
* * If `s` is present in the buffer, returns the index of the last instance
* * If `s` is not present in the buffer, returns type(uint256).max
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`]
*/
function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) {
return lastIndexOf(buffer, s, type(uint256).max);
}
/**
* @dev Backward search for `s` in `buffer` starting at position `pos`
* * If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance
* * If `s` is not present in the buffer (at or before `pos`), returns type(uint256).max
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`]
*/
function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) {
unchecked {
uint256 length = buffer.length;
for (uint256 i = Math.min(Math.saturatingAdd(pos, 1), length); i > 0; --i) {
if (bytes1(_unsafeReadBytesOffset(buffer, i - 1)) == s) {
return i - 1;
}
}
return type(uint256).max;
}
}
/**
* @dev Copies the content of `buffer`, from `start` (included) to the end of `buffer` into a new bytes object in
* memory.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
*/
function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) {
return slice(buffer, start, buffer.length);
}
/**
* @dev Copies the content of `buffer`, from `start` (included) to `end` (excluded) into a new bytes object in
* memory. The `end` argument is truncated to the length of the `buffer`.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
*/
function slice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) {
// sanitize
end = Math.min(end, buffer.length);
start = Math.min(start, end);
// allocate and copy
bytes memory result = new bytes(end - start);
assembly ("memory-safe") {
mcopy(add(result, 0x20), add(add(buffer, 0x20), start), sub(end, start))
}
return result;
}
/**
* @dev Moves the content of `buffer`, from `start` (included) to the end of `buffer` to the start of that buffer.
*
* NOTE: This function modifies the provided buffer in place. If you need to preserve the original buffer, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) {
return splice(buffer, start, buffer.length);
}
/**
* @dev Moves the content of `buffer`, from `start` (included) to end (excluded) to the start of that buffer. The
* `end` argument is truncated to the length of the `buffer`.
*
* NOTE: This function modifies the provided buffer in place. If you need to preserve the original buffer, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) {
// sanitize
end = Math.min(end, buffer.length);
start = Math.min(start, end);
// allocate and copy
assembly ("memory-safe") {
mcopy(add(buffer, 0x20), add(add(buffer, 0x20), start), sub(end, start))
mstore(buffer, sub(end, start))
}
return buffer;
}
/**
* @dev Replaces bytes in `buffer` starting at `pos` with all bytes from `replacement`.
*
* Parameters are clamped to valid ranges (i.e. `pos` is clamped to `[0, buffer.length]`).
* If `pos >= buffer.length`, no replacement occurs and the buffer is returned unchanged.
*
* NOTE: This function modifies the provided buffer in place.
*/
function replace(bytes memory buffer, uint256 pos, bytes memory replacement) internal pure returns (bytes memory) {
return replace(buffer, pos, replacement, 0, replacement.length);
}
/**
* @dev Replaces bytes in `buffer` starting at `pos` with bytes from `replacement` starting at `offset`.
* Copies at most `length` bytes from `replacement` to `buffer`.
*
* Parameters are clamped to valid ranges (i.e. `pos` is clamped to `[0, buffer.length]`, `offset` is
* clamped to `[0, replacement.length]`, and `length` is clamped to `min(length, replacement.length - offset,
* buffer.length - pos))`. If `pos >= buffer.length` or `offset >= replacement.length`, no replacement occurs
* and the buffer is returned unchanged.
*
* NOTE: This function modifies the provided buffer in place.
*/
function replace(
bytes memory buffer,
uint256 pos,
bytes memory replacement,
uint256 offset,
uint256 length
) internal pure returns (bytes memory) {
// sanitize
pos = Math.min(pos, buffer.length);
offset = Math.min(offset, replacement.length);
length = Math.min(length, Math.min(replacement.length - offset, buffer.length - pos));
// allocate and copy
assembly ("memory-safe") {
mcopy(add(add(buffer, 0x20), pos), add(add(replacement, 0x20), offset), length)
}
return buffer;
}
/**
* @dev Concatenate an array of bytes into a single bytes object.
*
* For fixed bytes types, we recommend using the solidity built-in `bytes.concat` or (equivalent)
* `abi.encodePacked`.
*
* NOTE: this could be done in assembly with a single loop that expands starting at the FMP, but that would be
* significantly less readable. It might be worth benchmarking the savings of the full-assembly approach.
*/
function concat(bytes[] memory buffers) internal pure returns (bytes memory) {
uint256 length = 0;
for (uint256 i = 0; i < buffers.length; ++i) {
length += buffers[i].length;
}
bytes memory result = new bytes(length);
uint256 offset = 0x20;
for (uint256 i = 0; i < buffers.length; ++i) {
bytes memory input = buffers[i];
assembly ("memory-safe") {
mcopy(add(result, offset), add(input, 0x20), mload(input))
}
unchecked {
offset += input.length;
}
}
return result;
}
/**
* @dev Split each byte in `input` into two nibbles (4 bits each)
*
* Example: hex"01234567" → hex"0001020304050607"
*/
function toNibbles(bytes memory input) internal pure returns (bytes memory output) {
assembly ("memory-safe") {
let length := mload(input)
output := mload(0x40)
mstore(0x40, add(add(output, 0x20), mul(length, 2)))
mstore(output, mul(length, 2))
for {
let i := 0
} lt(i, length) {
i := add(i, 0x10)
} {
let chunk := shr(128, mload(add(add(input, 0x20), i)))
chunk := and(
0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff,
or(shl(64, chunk), chunk)
)
chunk := and(
0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff,
or(shl(32, chunk), chunk)
)
chunk := and(
0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff,
or(shl(16, chunk), chunk)
)
chunk := and(
0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff,
or(shl(8, chunk), chunk)
)
chunk := and(
0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f,
or(shl(4, chunk), chunk)
)
mstore(add(add(output, 0x20), mul(i, 2)), chunk)
}
}
}
/**
* @dev Returns true if the two byte buffers are equal.
*/
function equal(bytes memory a, bytes memory b) internal pure returns (bool) {
return a.length == b.length && keccak256(a) == keccak256(b);
}
/**
* @dev Reverses the byte order of a bytes32 value, converting between little-endian and big-endian.
* Inspired by https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel[Reverse Parallel]
*/
function reverseBytes32(bytes32 value) internal pure returns (bytes32) {
value = // swap bytes
((value >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |
((value & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
value = // swap 2-byte long pairs
((value >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |
((value & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
value = // swap 4-byte long pairs
((value >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |
((value & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);
value = // swap 8-byte long pairs
((value >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |
((value & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);
return (value >> 128) | (value << 128); // swap 16-byte long pairs
}
/// @dev Same as {reverseBytes32} but optimized for 128-bit values.
function reverseBytes16(bytes16 value) internal pure returns (bytes16) {
value = // swap bytes
((value & 0xFF00FF00FF00FF00FF00FF00FF00FF00) >> 8) |
((value & 0x00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
value = // swap 2-byte long pairs
((value & 0xFFFF0000FFFF0000FFFF0000FFFF0000) >> 16) |
((value & 0x0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
value = // swap 4-byte long pairs
((value & 0xFFFFFFFF00000000FFFFFFFF00000000) >> 32) |
((value & 0x00000000FFFFFFFF00000000FFFFFFFF) << 32);
return (value >> 64) | (value << 64); // swap 8-byte long pairs
}
/// @dev Same as {reverseBytes32} but optimized for 64-bit values.
function reverseBytes8(bytes8 value) internal pure returns (bytes8) {
value = ((value & 0xFF00FF00FF00FF00) >> 8) | ((value & 0x00FF00FF00FF00FF) << 8); // swap bytes
value = ((value & 0xFFFF0000FFFF0000) >> 16) | ((value & 0x0000FFFF0000FFFF) << 16); // swap 2-byte long pairs
return (value >> 32) | (value << 32); // swap 4-byte long pairs
}
/// @dev Same as {reverseBytes32} but optimized for 32-bit values.
function reverseBytes4(bytes4 value) internal pure returns (bytes4) {
value = ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8); // swap bytes
return (value >> 16) | (value << 16); // swap 2-byte long pairs
}
/// @dev Same as {reverseBytes32} but optimized for 16-bit values.
function reverseBytes2(bytes2 value) internal pure returns (bytes2) {
return (value >> 8) | (value << 8);
}
/**
* @dev Counts the number of leading zero bits a bytes array. Returns `8 * buffer.length`
* if the buffer is all zeros.
*/
function clz(bytes memory buffer) internal pure returns (uint256) {
for (uint256 i = 0; i < buffer.length; i += 0x20) {
bytes32 chunk = _unsafeReadBytesOffset(buffer, i);
if (chunk != bytes32(0)) {
return Math.min(8 * i + Math.clz(uint256(chunk)), 8 * buffer.length);
}
}
return 8 * buffer.length;
}
/**
* @dev Reads a bytes32 from a bytes array without bounds checking.
*
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
* assembly block as such would prevent some optimizations.
*/
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
// This is not memory safe in the general case, but all calls to this private function are within bounds.
assembly ("memory-safe") {
value := mload(add(add(buffer, 0x20), offset))
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Comparators.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides a set of functions to compare values.
*
* _Available since v5.1._
*/
library Comparators {
function lt(uint256 a, uint256 b) internal pure returns (bool) {
return a < b;
}
function gt(uint256 a, uint256 b) internal pure returns (bool) {
return a > b;
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
import { IERC165 } from "./IERC165.sol";
/// @title ERC6909 Core Interface
/// @author jtriley.eth
interface IERC6909 is IERC165 {
/// @notice The event emitted when a transfer occurs.
/// @param caller The caller of the transfer.
/// @param sender The address of the sender.
/// @param receiver The address of the receiver.
/// @param id The id of the token.
/// @param amount The amount of the token.
event Transfer(
address caller, address indexed sender, address indexed receiver, uint256 indexed id, uint256 amount
);
/// @notice The event emitted when an operator is set.
/// @param owner The address of the owner.
/// @param spender The address of the spender.
/// @param approved The approval status.
event OperatorSet(address indexed owner, address indexed spender, bool approved);
/// @notice The event emitted when an approval occurs.
/// @param owner The address of the owner.
/// @param spender The address of the spender.
/// @param id The id of the token.
/// @param amount The amount of the token.
event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
/// @notice Owner balance of an id.
/// @param owner The address of the owner.
/// @param id The id of the token.
/// @return amount The balance of the token.
function balanceOf(address owner, uint256 id) external view returns (uint256 amount);
/// @notice Spender allowance of an id.
/// @param owner The address of the owner.
/// @param spender The address of the spender.
/// @param id The id of the token.
/// @return amount The allowance of the token.
function allowance(address owner, address spender, uint256 id) external view returns (uint256 amount);
/// @notice Checks if a spender is approved by an owner as an operator
/// @param owner The address of the owner.
/// @param spender The address of the spender.
/// @return approved The approval status.
function isOperator(address owner, address spender) external view returns (bool approved);
/// @notice Transfers an amount of an id from the caller to a receiver.
/// @param receiver The address of the receiver.
/// @param id The id of the token.
/// @param amount The amount of the token.
function transfer(address receiver, uint256 id, uint256 amount) external returns (bool);
/// @notice Transfers an amount of an id from a sender to a receiver.
/// @param sender The address of the sender.
/// @param receiver The address of the receiver.
/// @param id The id of the token.
/// @param amount The amount of the token.
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external returns (bool);
/// @notice Approves an amount of an id to a spender.
/// @param spender The address of the spender.
/// @param id The id of the token.
/// @param amount The amount of the token.
function approve(address spender, uint256 id, uint256 amount) external returns (bool);
/// @notice Sets or removes a spender as an operator for the caller.
/// @param spender The address of the spender.
/// @param approved The approval status.
function setOperator(address spender, bool approved) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.24;
import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.
*
* The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
* encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
* does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
* produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
* separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
* separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
*
* @custom:oz-upgrades-unsafe-allow state-variable-immutable
*/
abstract contract EIP712 is IERC5267 {
using ShortStrings for *;
bytes32 private constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _cachedDomainSeparator;
uint256 private immutable _cachedChainId;
address private immutable _cachedThis;
bytes32 private immutable _hashedName;
bytes32 private immutable _hashedVersion;
ShortString private immutable _name;
ShortString private immutable _version;
// slither-disable-next-line constable-states
string private _nameFallback;
// slither-disable-next-line constable-states
string private _versionFallback;
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) {
_name = name.toShortStringWithFallback(_nameFallback);
_version = version.toShortStringWithFallback(_versionFallback);
_hashedName = keccak256(bytes(name));
_hashedVersion = keccak256(bytes(version));
_cachedChainId = block.chainid;
_cachedDomainSeparator = _buildDomainSeparator();
_cachedThis = address(this);
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
return _cachedDomainSeparator;
} else {
return _buildDomainSeparator();
}
}
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
}
/// @inheritdoc IERC5267
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
return (
hex"0f", // 01111
_EIP712Name(),
_EIP712Version(),
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
/**
* @dev The name parameter for the EIP712 domain.
*
* NOTE: By default this function reads _name which is an immutable value.
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
*/
// solhint-disable-next-line func-name-mixedcase
function _EIP712Name() internal view returns (string memory) {
return _name.toStringWithFallback(_nameFallback);
}
/**
* @dev The version parameter for the EIP712 domain.
*
* NOTE: By default this function reads _version which is an immutable value.
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
*/
// solhint-disable-next-line func-name-mixedcase
function _EIP712Version() internal view returns (string memory) {
return _version.toStringWithFallback(_versionFallback);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/cryptography/SignatureChecker.sol)
pragma solidity ^0.8.24;
import {ECDSA} from "./ECDSA.sol";
import {IERC1271} from "../../interfaces/IERC1271.sol";
import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";
import {Bytes} from "../Bytes.sol";
/**
* @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support:
*
* * ECDSA signatures from externally owned accounts (EOAs)
* * ERC-1271 signatures from smart contract wallets like Argent and Safe Wallet (previously Gnosis Safe)
* * ERC-7913 signatures from keys that do not have an Ethereum address of their own
*
* See https://eips.ethereum.org/EIPS/eip-1271[ERC-1271] and https://eips.ethereum.org/EIPS/eip-7913[ERC-7913].
*/
library SignatureChecker {
using Bytes for bytes;
/**
* @dev Checks if a signature is valid for a given signer and data hash. If the signer has code, the
* signature is validated against it using ERC-1271, otherwise it's validated using `ECDSA.recover`.
*
* NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
* change through time. It could return true at block N and false at block N+1 (or the opposite).
*
* NOTE: For an extended version of this function that supports ERC-7913 signatures, see {isValidSignatureNow-bytes-bytes32-bytes-}.
*/
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
if (signer.code.length == 0) {
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature);
return err == ECDSA.RecoverError.NoError && recovered == signer;
} else {
return isValidERC1271SignatureNow(signer, hash, signature);
}
}
/**
* @dev Variant of {isValidSignatureNow} that takes a signature in calldata
*/
function isValidSignatureNowCalldata(
address signer,
bytes32 hash,
bytes calldata signature
) internal view returns (bool) {
if (signer.code.length == 0) {
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecoverCalldata(hash, signature);
return err == ECDSA.RecoverError.NoError && recovered == signer;
} else {
return isValidERC1271SignatureNowCalldata(signer, hash, signature);
}
}
/**
* @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
* against the signer smart contract using ERC-1271.
*
* NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
* change through time. It could return true at block N and false at block N+1 (or the opposite).
*/
function isValidERC1271SignatureNow(
address signer,
bytes32 hash,
bytes memory signature
) internal view returns (bool result) {
bytes4 selector = IERC1271.isValidSignature.selector;
uint256 length = signature.length;
assembly ("memory-safe") {
// Encoded calldata is :
// [ 0x00 - 0x03 ] <selector>
// [ 0x04 - 0x23 ] <hash>
// [ 0x24 - 0x43 ] <signature offset> (0x40)
// [ 0x44 - 0x63 ] <signature length>
// [ 0x64 - ... ] <signature data>
let ptr := mload(0x40)
mstore(ptr, selector)
mstore(add(ptr, 0x04), hash)
mstore(add(ptr, 0x24), 0x40)
mcopy(add(ptr, 0x44), signature, add(length, 0x20))
let success := staticcall(gas(), signer, ptr, add(length, 0x64), 0x00, 0x20)
result := and(success, and(gt(returndatasize(), 0x1f), eq(mload(0x00), selector)))
}
}
function isValidERC1271SignatureNowCalldata(
address signer,
bytes32 hash,
bytes calldata signature
) internal view returns (bool result) {
bytes4 selector = IERC1271.isValidSignature.selector;
uint256 length = signature.length;
assembly ("memory-safe") {
// Encoded calldata is :
// [ 0x00 - 0x03 ] <selector>
// [ 0x04 - 0x23 ] <hash>
// [ 0x24 - 0x43 ] <signature offset> (0x40)
// [ 0x44 - 0x63 ] <signature length>
// [ 0x64 - ... ] <signature data>
let ptr := mload(0x40)
mstore(ptr, selector)
mstore(add(ptr, 0x04), hash)
mstore(add(ptr, 0x24), 0x40)
mstore(add(ptr, 0x44), length)
calldatacopy(add(ptr, 0x64), signature.offset, length)
let success := staticcall(gas(), signer, ptr, add(length, 0x64), 0x00, 0x20)
result := and(success, and(gt(returndatasize(), 0x1f), eq(mload(0x00), selector)))
}
}
/**
* @dev Verifies a signature for a given ERC-7913 signer and hash.
*
* The signer is a `bytes` object that is the concatenation of an address and optionally a key:
* `verifier || key`. A signer must be at least 20 bytes long.
*
* Verification is done as follows:
*
* * If `signer.length < 20`: verification fails
* * If `signer.length == 20`: verification is done using {isValidSignatureNow}
* * Otherwise: verification is done using {IERC7913SignatureVerifier}
*
* NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
* change through time. It could return true at block N and false at block N+1 (or the opposite).
*/
function isValidSignatureNow(
bytes memory signer,
bytes32 hash,
bytes memory signature
) internal view returns (bool) {
if (signer.length < 20) {
return false;
} else if (signer.length == 20) {
return isValidSignatureNow(address(bytes20(signer)), hash, signature);
} else {
(bool success, bytes memory result) = address(bytes20(signer)).staticcall(
abi.encodeCall(IERC7913SignatureVerifier.verify, (signer.slice(20), hash, signature))
);
return (success &&
result.length >= 32 &&
abi.decode(result, (bytes32)) == bytes32(IERC7913SignatureVerifier.verify.selector));
}
}
/**
* @dev Verifies multiple ERC-7913 `signatures` for a given `hash` using a set of `signers`.
* Returns `false` if the number of signers and signatures is not the same.
*
* The signers should be ordered by their `keccak256` hash to ensure efficient duplication check. Unordered
* signers are supported, but the uniqueness check will be more expensive.
*
* NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
* change through time. It could return true at block N and false at block N+1 (or the opposite).
*/
function areValidSignaturesNow(
bytes32 hash,
bytes[] memory signers,
bytes[] memory signatures
) internal view returns (bool) {
if (signers.length != signatures.length) return false;
bytes32 lastId = bytes32(0);
for (uint256 i = 0; i < signers.length; ++i) {
bytes memory signer = signers[i];
// If one of the signatures is invalid, reject the batch
if (!isValidSignatureNow(signer, hash, signatures[i])) return false;
bytes32 id = keccak256(signer);
// If the current signer ID is greater than all previous IDs, then this is a new signer.
if (lastId < id) {
lastId = id;
} else {
// If this signer id is not greater than all the previous ones, verify that it is not a duplicate of a previous one
// This loop is never executed if the signers are ordered by id.
for (uint256 j = 0; j < i; ++j) {
if (id == keccak256(signers[j])) return false;
}
}
}
return true;
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
import { Ownable } from "./Ownable.sol";
/**
* @title Pausable Implementation
* @author Splits
* @notice Pausable clone-implementation
*/
abstract contract Pausable is Ownable {
/* -------------------------------------------------------------------------- */
/* ERRORS */
/* -------------------------------------------------------------------------- */
error Paused();
/* -------------------------------------------------------------------------- */
/* EVENTS */
/* -------------------------------------------------------------------------- */
event SetPaused(bool paused);
/* -------------------------------------------------------------------------- */
/* STORAGE */
/* -------------------------------------------------------------------------- */
bool public paused;
/* -------------------------------------------------------------------------- */
/* CONSTRUCTOR & INITIALIZER */
/* -------------------------------------------------------------------------- */
function __initPausable(address _owner, bool _paused) internal virtual {
__initOwnable(_owner);
paused = _paused;
}
/* -------------------------------------------------------------------------- */
/* MODIFIERS */
/* -------------------------------------------------------------------------- */
modifier pausable() virtual {
address owner_ = owner;
if (paused) {
// solhint-disable-next-line avoid-tx-origin
if (msg.sender != owner_ && tx.origin != owner_ && msg.sender != address(this)) {
revert Paused();
}
}
_;
}
/* -------------------------------------------------------------------------- */
/* PUBLIC/EXTERNAL FUNCTIONS */
/* -------------------------------------------------------------------------- */
function setPaused(bool _paused) public virtual onlyOwner {
paused = _paused;
emit SetPaused({ paused: _paused });
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC1155/utils/ERC1155Holder.sol)
pragma solidity ^0.8.20;
import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol";
import {IERC1155Receiver} from "../IERC1155Receiver.sol";
/**
* @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC-1155 tokens.
*
* IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
* stuck.
*
* @custom:stateless
*/
abstract contract ERC1155Holder is ERC165, IERC1155Receiver {
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
}
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC721/utils/ERC721Holder.sol)
pragma solidity ^0.8.20;
import {IERC721Receiver} from "../IERC721Receiver.sol";
/**
* @dev Implementation of the {IERC721Receiver} interface.
*
* Accepts all token transfers.
* Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or
* {IERC721-setApprovalForAll}.
*
* @custom:stateless
*/
abstract contract ERC721Holder is IERC721Receiver {
/**
* @dev See {IERC721Receiver-onERC721Received}.
*
* Always returns `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) {
return this.onERC721Received.selector;
}
}// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;
import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
interface IProposerPayload is IPayload {
function getOriginalPayload() external view returns (IPayload);
function amIValid() external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (proxy/Proxy.sol)
pragma solidity ^0.8.20;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0x00, 0x00, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0x00, calldatasize(), 0x00, 0x00)
// Copy the returned data.
returndatacopy(0x00, 0x00, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0x00, returndatasize())
}
default {
return(0x00, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback
* function and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
interface IERC165 {
/// @notice Checks if a contract implements an interface.
/// @param interfaceId The interface identifier, as specified in ERC-165.
/// @return supported True if the contract implements `interfaceId` and
/// `interfaceId` is not 0xffffffff, false otherwise.
function supportsInterface(bytes4 interfaceId) external view returns (bool supported);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/ShortStrings.sol)
pragma solidity ^0.8.20;
import {StorageSlot} from "./StorageSlot.sol";
// | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
// | length | 0x BB |
type ShortString is bytes32;
/**
* @dev This library provides functions to convert short memory strings
* into a `ShortString` type that can be used as an immutable variable.
*
* Strings of arbitrary length can be optimized using this library if
* they are short enough (up to 31 bytes) by packing them with their
* length (1 byte) in a single EVM word (32 bytes). Additionally, a
* fallback mechanism can be used for every other case.
*
* Usage example:
*
* ```solidity
* contract Named {
* using ShortStrings for *;
*
* ShortString private immutable _name;
* string private _nameFallback;
*
* constructor(string memory contractName) {
* _name = contractName.toShortStringWithFallback(_nameFallback);
* }
*
* function name() external view returns (string memory) {
* return _name.toStringWithFallback(_nameFallback);
* }
* }
* ```
*/
library ShortStrings {
// Used as an identifier for strings longer than 31 bytes.
bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;
error StringTooLong(string str);
error InvalidShortString();
/**
* @dev Encode a string of at most 31 chars into a `ShortString`.
*
* This will trigger a `StringTooLong` error is the input string is too long.
*/
function toShortString(string memory str) internal pure returns (ShortString) {
bytes memory bstr = bytes(str);
if (bstr.length > 0x1f) {
revert StringTooLong(str);
}
return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
}
/**
* @dev Decode a `ShortString` back to a "normal" string.
*/
function toString(ShortString sstr) internal pure returns (string memory) {
uint256 len = byteLength(sstr);
// using `new string(len)` would work locally but is not memory safe.
string memory str = new string(0x20);
assembly ("memory-safe") {
mstore(str, len)
mstore(add(str, 0x20), sstr)
}
return str;
}
/**
* @dev Return the length of a `ShortString`.
*/
function byteLength(ShortString sstr) internal pure returns (uint256) {
uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
if (result > 0x1f) {
revert InvalidShortString();
}
return result;
}
/**
* @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
*/
function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
if (bytes(value).length < 0x20) {
return toShortString(value);
} else {
StorageSlot.getStringSlot(store).value = value;
return ShortString.wrap(FALLBACK_SENTINEL);
}
}
/**
* @dev Decode a string that was encoded to `ShortString` or written to storage using {toShortStringWithFallback}.
*/
function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
return toString(value);
} else {
return store;
}
}
/**
* @dev Return the length of a string that was encoded to `ShortString` or written to storage using
* {toShortStringWithFallback}.
*
* WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
* actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
*/
function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
return byteLength(value);
} else {
return bytes(store).length;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC5267.sol)
pragma solidity >=0.4.16;
interface IERC5267 {
/**
* @dev MAY be emitted to signal that the domain could have changed.
*/
event EIP712DomainChanged();
/**
* @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
* signature.
*/
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1271.sol)
pragma solidity >=0.5.0;
/**
* @dev Interface of the ERC-1271 standard signature validation method for
* contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
*/
interface IERC1271 {
/**
* @dev Should return whether the signature provided is valid for the provided data
* @param hash Hash of the data to be signed
* @param signature Signature byte array associated with `hash`
*/
function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC7913.sol)
pragma solidity >=0.5.0;
/**
* @dev Signature verifier interface.
*/
interface IERC7913SignatureVerifier {
/**
* @dev Verifies `signature` as a valid signature of `hash` by `key`.
*
* MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid.
* SHOULD return 0xffffffff or revert if the signature is not valid.
* SHOULD return 0xffffffff or revert if the key is empty
*/
function verify(bytes calldata key, bytes32 hash, bytes calldata signature) external view returns (bytes4);
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
/// @title Ownable Implementation
/// @author Splits
/// @notice Ownable clone-implementation
abstract contract Ownable {
/* -------------------------------------------------------------------------- */
/* ERRORS */
/* -------------------------------------------------------------------------- */
error Unauthorized();
/* -------------------------------------------------------------------------- */
/* EVENTS */
/* -------------------------------------------------------------------------- */
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/* -------------------------------------------------------------------------- */
/* STORAGE */
/* -------------------------------------------------------------------------- */
address public owner;
/* -------------------------------------------------------------------------- */
/* CONSTRUCTOR & INITIALIZER */
/* -------------------------------------------------------------------------- */
function __initOwnable(address _owner) internal virtual {
emit OwnershipTransferred({ oldOwner: address(0), newOwner: _owner });
owner = _owner;
}
/* -------------------------------------------------------------------------- */
/* MODIFIERS */
/* -------------------------------------------------------------------------- */
modifier onlyOwner() virtual {
if (msg.sender != owner && msg.sender != address(this)) revert Unauthorized();
_;
}
/* -------------------------------------------------------------------------- */
/* FUNCTIONS */
/* -------------------------------------------------------------------------- */
function transferOwnership(address _owner) public virtual onlyOwner {
emit OwnershipTransferred({ oldOwner: owner, newOwner: _owner });
owner = _owner;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC1155/IERC1155Receiver.sol)
pragma solidity >=0.6.2;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Interface that must be implemented by smart contracts in order to receive
* ERC-1155 token transfers.
*/
interface IERC1155Receiver is IERC165 {
/**
* @dev Handles the receipt of a single ERC-1155 token type. This function is
* called at the end of a `safeTransferFrom` after the balance has been updated.
*
* NOTE: To accept the transfer, this must return
* `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
* (i.e. 0xf23a6e61, or its own function selector).
*
* @param operator The address which initiated the transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param id The ID of the token being transferred
* @param value The amount of tokens being transferred
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
*/
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
/**
* @dev Handles the receipt of a multiple ERC-1155 token types. This function
* is called at the end of a `safeBatchTransferFrom` after the balances have
* been updated.
*
* NOTE: To accept the transfer(s), this must return
* `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
* (i.e. 0xbc197c81, or its own function selector).
*
* @param operator The address which initiated the batch transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param ids An array containing ids of each token being transferred (order and length must match values array)
* @param values An array containing amounts of each token being transferred (order and length must match ids array)
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity >=0.5.0;
/**
* @title ERC-721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC-721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be
* reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}{
"remappings": [
"src/=src/",
"test/=test/",
"@aztec/=lib/l1-contracts/src/",
"@aztec-test/=lib/l1-contracts/test/",
"@openzeppelin/=lib/openzeppelin-contracts/",
"@oz/=lib/openzeppelin-contracts/contracts/",
"forge-std/=lib/forge-std/src/",
"@atp/=src/token-vaults/",
"@atp-mock/=test/token-vaults/mocks/",
"@zkpassport/=lib/circuits/src/solidity/src/",
"@splits/=lib/splits-contracts-monorepo/packages/splits-v2/src/",
"@predicate/=lib/predicate-contracts/src/",
"@teegeeee/=src/token-vaults/",
"@twap-auction/=lib/liquidity-launcher/lib/continuous-clearing-auction/src/",
"@twap-auction-test/=lib/liquidity-launcher/lib/continuous-clearing-auction/test/",
"@launcher/=lib/liquidity-launcher/src/",
"@v4c/=lib/liquidity-launcher/lib/v4-core/src/",
"@v4p/=lib/liquidity-launcher/lib/v4-periphery/src/",
"@aztec-blob-lib/=lib/l1-contracts/src/mock/libraries/",
"@ensdomains/=lib/liquidity-launcher/lib/v4-core/node_modules/@ensdomains/",
"@openzeppelin-latest/=lib/liquidity-launcher/lib/openzeppelin-contracts/",
"@openzeppelin-upgrades-v4.9.0/=lib/predicate-contracts/lib/eigenlayer-contracts/lib/openzeppelin-contracts-upgradeable-v4.9.0/",
"@openzeppelin-upgrades/=lib/predicate-contracts/lib/eigenlayer-contracts/lib/openzeppelin-contracts-upgradeable/",
"@openzeppelin-v4.9.0/=lib/predicate-contracts/lib/eigenlayer-contracts/lib/openzeppelin-contracts-v4.9.0/",
"@optimism/=lib/liquidity-launcher/lib/optimism/packages/contracts-bedrock/",
"@solady/=lib/liquidity-launcher/lib/solady/",
"@test/=lib/l1-contracts/test/",
"@uniswap/v4-core/=lib/liquidity-launcher/lib/v4-core/",
"@uniswap/v4-periphery/=lib/liquidity-launcher/lib/v4-periphery/",
"@zkpassport-test/=lib/l1-contracts/lib/circuits/src/solidity/test/",
"btt/=lib/liquidity-launcher/lib/continuous-clearing-auction/test/btt/",
"circuits/=lib/circuits/src/",
"continuous-clearing-auction/=lib/liquidity-launcher/lib/continuous-clearing-auction/",
"ds-test/=lib/predicate-contracts/lib/forge-std/lib/ds-test/src/",
"eigenlayer-contracts/=lib/predicate-contracts/lib/eigenlayer-contracts/",
"eigenlayer-middleware/=lib/predicate-contracts/lib/eigenlayer-middleware/",
"erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
"forge-gas-snapshot/=lib/liquidity-launcher/lib/continuous-clearing-auction/lib/forge-gas-snapshot/src/",
"halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
"hardhat/=lib/liquidity-launcher/lib/v4-core/node_modules/hardhat/",
"kontrol-cheatcodes/=lib/liquidity-launcher/lib/optimism/packages/contracts-bedrock/lib/kontrol-cheatcodes/src/",
"l1-contracts/=lib/l1-contracts/src/",
"lib-keccak/=lib/liquidity-launcher/lib/optimism/packages/contracts-bedrock/lib/lib-keccak/contracts/",
"liquidity-launcher/=lib/liquidity-launcher/",
"merkle-distributor/=lib/liquidity-launcher/lib/merkle-distributor/",
"openzeppelin-contracts-4.7/=lib/liquidity-launcher/lib/openzeppelin-contracts-4.7/",
"openzeppelin-contracts-upgradeable-v4.9.0/=lib/predicate-contracts/lib/eigenlayer-contracts/lib/openzeppelin-contracts-upgradeable-v4.9.0/",
"openzeppelin-contracts-upgradeable/=lib/predicate-contracts/lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts-v4.9.0/=lib/predicate-contracts/lib/eigenlayer-contracts/lib/openzeppelin-contracts-v4.9.0/",
"openzeppelin-contracts-v5/=lib/liquidity-launcher/lib/optimism/packages/contracts-bedrock/lib/openzeppelin-contracts-v5/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"openzeppelin-foundry-upgrades/=lib/predicate-contracts/lib/openzeppelin-foundry-upgrades/src/",
"openzeppelin-upgradeable/=lib/predicate-contracts/lib/openzeppelin-contracts-upgradeable/contracts/",
"openzeppelin/=lib/predicate-contracts/lib/eigenlayer-contracts/lib/openzeppelin-contracts-upgradeable-v4.9.0/contracts/",
"optimism/=lib/liquidity-launcher/lib/optimism/",
"permit2/=lib/liquidity-launcher/lib/permit2/",
"predicate-contracts/=lib/predicate-contracts/src/",
"safe-contracts/=lib/liquidity-launcher/lib/optimism/packages/contracts-bedrock/lib/safe-contracts/contracts/",
"solady-v0.0.245/=lib/liquidity-launcher/lib/optimism/packages/contracts-bedrock/lib/solady-v0.0.245/src/",
"solady/=lib/liquidity-launcher/lib/solady/src/",
"solmate/=lib/predicate-contracts/lib/solmate/src/",
"splits-contracts-monorepo/=lib/splits-contracts-monorepo/",
"utils/=lib/predicate-contracts/lib/utils/",
"v4-core/=lib/liquidity-launcher/lib/v4-core/src/",
"v4-periphery/=lib/liquidity-launcher/lib/v4-periphery/",
"zkpassport-packages/=lib/zkpassport-packages/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "prague",
"viaIR": false
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"secondsSinceMidnightCET","type":"uint256"},{"internalType":"uint256","name":"dayOfWeek","type":"uint256"}],"name":"OutsideBusinessHours","type":"error"},{"inputs":[],"name":"ATP_REGISTRY","outputs":[{"internalType":"contract IRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"AZTEC_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DATE_GATED_RELAYER_SHORT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"END_DAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"END_OF_WORKDAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"JAN_1_2026_CET","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROLLUP","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROLLUP_REGISTRY","outputs":[{"internalType":"contract IRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKER","outputs":[{"internalType":"contract ATPWithdrawableAndClaimableStakerV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_REGISTRY","outputs":[{"internalType":"contract StakingRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"START_DAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"START_OF_WORKDAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VIRTUAL_LBP_STRATEGY","outputs":[{"internalType":"contract IVirtualLBPStrategyBasic","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getActions","outputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct IPayload.Action[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"}]Contract Creation Code
60a060405234801561000f575f5ffd5b5073a27ec0006e59f245217ff08cd52a7e8b169e62d27335b22e09ee0390539439e24f06da43d83f90e29873042df8f42790d6943f41c25c2132400fd727f4524260405161005c906100ae565b6001600160a01b0394851681529284166020840152921660408201526060810191909152608001604051809103905ff08015801561009c573d5f5f3e3d5ffd5b506001600160a01b03166080526100bb565b6125e680610aec83390190565b608051610a126100da5f395f818161023001526105b40152610a125ff3fe608060405234801561000f575f5ffd5b50600436106100f0575f3560e01c80637749299311610093578063a01b364d11610063578063a01b364d14610207578063ac8e7dc914610222578063b0df4cab1461022b578063fcd8e61a14610252575f5ffd5b806377492993146101c45780637754305c146101cc57806394dd95b9146101e15780639611c5c2146101ec575f5ffd5b80633d1e54be116100ce5780633d1e54be14610162578063450cc1b014610179578063504d080b146101945780635506648f146101a9575f5ffd5b80631317d1b8146100f45780631f998bb11461012c5780633a79fc9c14610147575b5f5ffd5b61010f7335b22e09ee0390539439e24f06da43d83f90e29881565b6040516001600160a01b0390911681526020015b60405180910390f35b61010f73a27ec0006e59f245217ff08cd52a7e8b169e62d281565b61010f7363841bad6b35b6419e15ca9bbbbdf446d4dc3dde81565b61016b61d2f081565b604051908152602001610123565b61010f73042df8f42790d6943f41c25c2132400fd727f45281565b61019c61025a565b6040516101239190610853565b61010f73d53006d1e3110fd319a79aeec4c527a0d265e08081565b61016b600381565b6101d4610805565b60405161012391906108d6565b61016b636955aaf081565b61010f73603bb2c05d474794ea97805e8de69bccfb3bca1281565b61010f737d6decf157e1329a20c4596eaf78d387e896aa4e81565b61016b61708081565b61010f7f000000000000000000000000000000000000000000000000000000000000000081565b61016b600181565b60605f61026b636955aaf042610903565b90505f61027b6201518083610930565b90505f61028b6201518084610943565b90505f600761029b836003610956565b6102a59190610930565b905061708083101580156102ba575061d2f083105b80156102c7575060018110155b80156102d4575060038111155b838290916103025760405163e7a31ae160e01b81526004810192909252602482015260440160405180910390fd5b505060408051600680825260e082019092525f91816020015b604080518082019091525f81526060602082015281526020019060019003908161031b57905050604080518082018252737d6decf157e1329a20c4596eaf78d387e896aa4e815281516004815260248101909252602082810180516001600160e01b0316634d674d0760e01b17905281019190915281519192509082905f906103a6576103a6610969565b602090810291909101810191909152604080518082018252737d6decf157e1329a20c4596eaf78d387e896aa4e81528151600481526024808201845281850180516001600160e01b03166379ba509760e01b1790529251919384019263c28e83fd60e01b9261042d927363841bad6b35b6419e15ca9bbbbdf446d4dc3dde9290910161097d565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152905281518290600190811061047657610476610969565b60209081029190910181019190915260408051808201909152737d6decf157e1329a20c4596eaf78d387e896aa4e815290810163c28e83fd60e01b7363841bad6b35b6419e15ca9bbbbdf446d4dc3dde635661687b60e11b6104dc6301e1338042610903565b6040516024016104ee91815260200190565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161053092919060240161097d565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152905281518290600290811061057957610579610969565b602090810291909101810191909152604080518082018252737d6decf157e1329a20c4596eaf78d387e896aa4e815281516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000166024808301919091528351808303820181526044909201845281850180516001600160e01b0316632b00162b60e21b1790529251919384019263c28e83fd60e01b92610638927363841bad6b35b6419e15ca9bbbbdf446d4dc3dde9290910161097d565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152905281518290600390811061068157610681610969565b602090810291909101810191909152604080518082018252737d6decf157e1329a20c4596eaf78d387e896aa4e81528151600481526024808201845281850180516001600160e01b0316630c95593b60e21b1790529251919384019263c28e83fd60e01b926107089273d53006d1e3110fd319a79aeec4c527a0d265e0809290910161097d565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152905281518290600490811061075157610751610969565b6020026020010181905250604051806040016040528073603bb2c05d474794ea97805e8de69bccfb3bca126001600160a01b03168152602001638fcc697a60e01b60016040516024016107a8911515815260200190565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915290528151829060059081106107f1576107f1610969565b602090810291909101015294505050505090565b60606040518060600160405280603481526020016109a960349139905090565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b828110156108ca57868503603f19018452815180516001600160a01b031686526020908101516040918701829052906108b490870182610825565b9550506020938401939190910190600101610879565b50929695505050505050565b602081525f6108e86020830184610825565b9392505050565b634e487b7160e01b5f52601160045260245ffd5b81810381811115610916576109166108ef565b92915050565b634e487b7160e01b5f52601260045260245ffd5b5f8261093e5761093e61091c565b500690565b5f826109515761095161091c565b500490565b80820180821115610916576109166108ef565b634e487b7160e01b5f52603260045260245ffd5b6001600160a01b03831681526040602082018190525f906109a090830184610825565b94935050505056fe68747470733a2f2f6769746875622e636f6d2f417a74656350726f746f636f6c2f69676e6974696f6e2d636f6e7472616374732fa2646970667358221220220530146f4bb176e232fff60e2f229b7e0f8631a21d045c229a239eb119924564736f6c634300081e003361012060405230608052348015610014575f5ffd5b506040516125e63803806125e683398101604081905261003391610081565b5f80546001600160a01b03191661dead1790556001600160a01b0393841660a05291831660c0529190911660e052610100526100d1565b6001600160a01b038116811461007e575f5ffd5b50565b5f5f5f5f60808587031215610094575f5ffd5b845161009f8161006a565b60208601519094506100b08161006a565b60408601519093506100c18161006a565b6060959095015193969295505050565b60805160a05160c05160e051610100516124296101bd5f395f81816104750152610db901525f818161028a01528181611692015261177101525f81816101cd015281816104f201528181610615015281816107d8015281816108dd01528181610a1c01528181610c7a01528181611030015281816112fd0152818161156a0152611a6901525f818161015c01528181610b5901528181610bd101528181610e1901528181610efc0152818161138e015281816113de01528181611651015281816116c101528181611b500152611ba001525f81816117dd0152818161180601526119a301526124295ff3fe608060405260043610610147575f3560e01c806352d1902d116100b3578063c4d66de81161006d578063c4d66de8146103d4578063d0e48cad146103f3578063e7f43c6814610412578063eeff76dd14610426578063f1048aa814610445578063f8dc4f9614610464575f5ffd5b806352d1902d14610320578063aaf10f4214610334578063ad3cb1cc14610348578063af43ecb614610385578063bbffb4f5146103a1578063bc1bea84146103c0575f5ffd5b8063425ca87011610104578063425ca8701461025a578063450cc1b0146102795780634ac3a713146102ac5780634ef0a2df146102cb5780634f1ef286146102f95780634f3851311461030c575f5ffd5b806303c230e31461014b5780630962ef791461019b5780631317d1b8146101bc57806332367ba0146101ef57806334ac94a21461021c578063362bd7bd1461023b575b5f5ffd5b348015610156575f5ffd5b5061017e7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156101a6575f5ffd5b506101ba6101b5366004611f30565b610497565b005b3480156101c7575f5ffd5b5061017e7f000000000000000000000000000000000000000000000000000000000000000081565b3480156101fa575f5ffd5b5061020e610209366004611f30565b6105d7565b604051908152602001610192565b348015610227575f5ffd5b506101ba610236366004611f7b565b610725565b348015610246575f5ffd5b506101ba610255366004611fdc565b6107c0565b348015610265575f5ffd5b506101ba61027436600461200a565b6108a0565b348015610284575f5ffd5b5061017e7f000000000000000000000000000000000000000000000000000000000000000081565b3480156102b7575f5ffd5b506101ba6102c6366004611fdc565b6109ca565b3480156102d6575f5ffd5b505f5160206123d45f395f51905f525460ff166040519015158152602001610192565b6101ba6103073660046120ae565b610ad2565b348015610317575f5ffd5b506101ba610af1565b34801561032b575f5ffd5b5061020e610bfd565b34801561033f575f5ffd5b5061017e610c18565b348015610353575f5ffd5b50610378604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516101929190612155565b348015610390575f5ffd5b505f546001600160a01b031661017e565b3480156103ac575f5ffd5b5061020e6103bb36600461218a565b610c3c565b3480156103cb575f5ffd5b506101ba610d3d565b3480156103df575f5ffd5b506101ba6103ee36600461218a565b610f6e565b3480156103fe575f5ffd5b506101ba61040d3660046121ac565b610fde565b34801561041d575f5ffd5b5061017e61124c565b348015610431575f5ffd5b506101ba610440366004611f30565b6112c0565b348015610450575f5ffd5b506101ba61045f36600461222d565b61147c565b34801561046f575f5ffd5b5061020e7f000000000000000000000000000000000000000000000000000000000000000081565b5f6104a061124c565b905033816001600160a01b03811682146104d8576040516311ce341560e21b81526004016104cf9291906122d1565b60405180910390fd5b5050604051636e8ac1e160e11b8152600481018390525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063dd1583c290602401602060405180830381865afa15801561053f573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061056391906122eb565b90505f6105775f546001600160a01b031690565b60405163c7523d7960e01b81526001600160a01b0380831660048301529192509083169063c7523d79906024015f604051808303815f87803b1580156105bb575f5ffd5b505af11580156105cd573d5f5f3e3d5ffd5b5050505050505050565b5f5f6105e161124c565b905033816001600160a01b0381168214610610576040516311ce341560e21b81526004016104cf9291906122d1565b50505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663289b3c0d6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561066f573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061069391906122eb565b90505f6106a75f546001600160a01b031690565b60405163625f849b60e11b81526001600160a01b038083166004830152602482018890529192509083169063c4bf0936906044015b6020604051808303815f875af11580156106f8573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061071c9190612306565b95945050505050565b5f61072e61124c565b905033816001600160a01b038116821461075d576040516311ce341560e21b81526004016104cf9291906122d1565b505061076c8686868686611518565b5f5160206123d45f395f51905f52805460ff166107b757805460ff191660011781556040517f4a127bffecc76f032d16b6fd22e796c618e3b2a48448f86852eaafd57f2ac0bc905f90a15b50505050505050565b604051636e8ac1e160e11b8152600481018390525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063dd1583c290602401602060405180830381865afa158015610825573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061084991906122eb565b604051637c2a4a6f60e11b81526001600160a01b0384811660048301529192509082169063f85494de906024015b5f604051808303815f87803b15801561088e575f5ffd5b505af11580156107b7573d5f5f3e3d5ffd5b5f6108a961124c565b905033816001600160a01b03811682146108d8576040516311ce341560e21b81526004016104cf9291906122d1565b50505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663289b3c0d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610937573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061095b91906122eb565b60405163350c7fbd60e11b8152600481018790526024810186905284151560448201529091506001600160a01b03821690636a18ff7a906064015b5f604051808303815f87803b1580156109ad575f5ffd5b505af11580156109bf573d5f5f3e3d5ffd5b505050505050505050565b5f6109d361124c565b905033816001600160a01b0381168214610a02576040516311ce341560e21b81526004016104cf9291906122d1565b5050604051636e8ac1e160e11b8152600481018490525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063dd1583c290602401602060405180830381865afa158015610a69573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a8d91906122eb565b90505f610aa15f546001600160a01b031690565b60405163771dc6e160e11b81529091506001600160a01b0383169063ee3b8dc29061099690879085906004016122d1565b610ada6117d2565b610ae382611878565b610aed82826118a7565b5050565b5f610afa61124c565b905033816001600160a01b0381168214610b29576040516311ce341560e21b81526004016104cf9291906122d1565b50505f80546001600160a01b03166040516370a0823160e01b81523060048201529091505f906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015610b9e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bc29190612306565b9050610bf86001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168383611963565b505050565b5f610c06611998565b505f5160206123b45f395f51905f5290565b5f610c375f5160206123b45f395f51905f52546001600160a01b031690565b905090565b5f5f610c4661124c565b905033816001600160a01b0381168214610c75576040516311ce341560e21b81526004016104cf9291906122d1565b50505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663289b3c0d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cd4573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cf891906122eb565b90505f610d0c5f546001600160a01b031690565b6040516330eb4bdd60e01b81529091506001600160a01b038316906330eb4bdd906106dc90889085906004016122d1565b5f610d4661124c565b905033816001600160a01b0381168214610d75576040516311ce341560e21b81526004016104cf9291906122d1565b50505f546001600160a01b0316610d9a5f5160206123d45f395f51905f525460ff1690565b610db75760405163ab9d5e2b60e01b815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000000421015610df8576040516303ec244760e51b815260040160405180910390fd5b6040516370a0823160e01b81526001600160a01b0382811660048301525f917f0000000000000000000000000000000000000000000000000000000000000000909116906370a0823190602401602060405180830381865afa158015610e60573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e849190612306565b90505f826001600160a01b031663565a2e2c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ec3573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ee791906122eb565b90508115610f6857610f246001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168483856119e1565b806001600160a01b03167fdf800f68559bc54bc11df67fe8f6c8130a869bd79a4d8a6805f8ca38ae9b7b1a83604051610f5f91815260200190565b60405180910390a25b50505050565b6001600160a01b038116610f9557604051632a68e88960e21b815260040160405180910390fd5b5f546001600160a01b031615610fbd5760405162dc149f60e41b815260040160405180910390fd5b5f80546001600160a01b0319166001600160a01b0392909216919091179055565b5f610fe761124c565b905033816001600160a01b0381168214611016576040516311ce341560e21b81526004016104cf9291906122d1565b5050604051636e8ac1e160e11b8152600481018590525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063dd1583c290602401602060405180830381865afa15801561107d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110a191906122eb565b90505f816001600160a01b031663ed9187b76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156110e0573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061110491906122eb565b60405163d3da927f60e01b815290915082906001600160a01b0383169063d3da927f906111379084908a906004016122d1565b602060405180830381865afa158015611152573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611176919061231d565b6111dd57816001600160a01b0316639f6bc70c6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156111b6573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111da91906122eb565b90505b604051630171a0e760e51b81526001600160a01b03828116600483015287811660248301528681166044830152831690632e341ce0906064015f604051808303815f87803b15801561122d575f5ffd5b505af115801561123f573d5f5f3e3d5ffd5b5050505050505050505050565b5f5f5f9054906101000a90046001600160a01b03166001600160a01b031663e7f43c686040518163ffffffff1660e01b8152600401602060405180830381865afa15801561129c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c3791906122eb565b5f6112c961124c565b905033816001600160a01b03811682146112f8576040516311ce341560e21b81526004016104cf9291906122d1565b50505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663289b3c0d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611357573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061137b91906122eb565b5f549091506113b8906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116911630866119e1565b60405163095ea7b360e01b81526001600160a01b038281166004830152602482018590527f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906044016020604051808303815f875af1158015611424573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611448919061231d565b506040516311f9fbc960e21b8152306004820152602481018490526001600160a01b038216906347e7ef2490604401610877565b5f61148561124c565b905033816001600160a01b03811682146114b4576040516311ce341560e21b81526004016104cf9291906122d1565b50506114c4878787878787611a17565b5f5160206123d45f395f51905f52805460ff166105cd57805460ff191660011781556040517f4a127bffecc76f032d16b6fd22e796c618e3b2a48448f86852eaafd57f2ac0bc905f90a15050505050505050565b5f61152161124c565b905033816001600160a01b0381168214611550576040516311ce341560e21b81526004016104cf9291906122d1565b5050604051636e8ac1e160e11b8152600481018790525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063dd1583c290602401602060405180830381865afa1580156115b7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115db91906122eb565b90505f816001600160a01b031663aa10df4c6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561161a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061163e9190612306565b5f5490915061167b906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116911630846119e1565b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906044016020604051808303815f875af1158015611707573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061172b919061231d565b50604051636fde931560e01b8152600481018890526024810189905230604482015261ffff871660648201526001600160a01b03868116608483015285151560a48301527f00000000000000000000000000000000000000000000000000000000000000001690636fde93159060c4015f604051808303815f87803b1580156117b2575f5ffd5b505af11580156117c4573d5f5f3e3d5ffd5b505050505050505050505050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061185857507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661184c5f5160206123b45f395f51905f52546001600160a01b031690565b6001600160a01b031614155b156118765760405163703e46dd60e11b815260040160405180910390fd5b565b5f5433906001600160a01b0316818114610bf85760405163f5f64b6760e01b81526004016104cf9291906122d1565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611901575060408051601f3d908101601f191682019092526118fe91810190612306565b60015b61192957604051634c9c8ce360e01b81526001600160a01b03831660048201526024016104cf565b5f5160206123b45f395f51905f52811461195957604051632a87526960e21b8152600481018290526024016104cf565b610bf88383611cb2565b6119708383836001611d07565b610bf857604051635274afe760e01b81526001600160a01b03841660048201526024016104cf565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146118765760405163703e46dd60e11b815260040160405180910390fd5b6119ef848484846001611d69565b610f6857604051635274afe760e01b81526001600160a01b03851660048201526024016104cf565b5f611a2061124c565b905033816001600160a01b0381168214611a4f576040516311ce341560e21b81526004016104cf9291906122d1565b5050604051636e8ac1e160e11b8152600481018890525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063dd1583c290602401602060405180830381865afa158015611ab6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ada91906122eb565b90505f816001600160a01b031663aa10df4c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611b19573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b3d9190612306565b5f54909150611b7a906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116911630846119e1565b60405163095ea7b360e01b81526001600160a01b038381166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906044016020604051808303815f875af1158015611be6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c0a919061231d565b50604051636902c67b60e01b81526001600160a01b03831690636902c67b90611c41908b9030908c908c908c908c90600401612338565b5f604051808303815f87803b158015611c58575f5ffd5b505af1158015611c6a573d5f5f3e3d5ffd5b50506040516001600160a01b0380861693508b16915030907fb2718d32c10b5a7d94c75d01205a0b819d5fdb7854640c8e9b94e647fedc6e17905f90a4505050505050505050565b611cbb82611dd6565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115611cff57610bf88282611e39565b610aed611eda565b60405163a9059cbb60e01b5f8181526001600160a01b038616600452602485905291602083604481808b5af1925060015f51148316611d5d578383151615611d51573d5f823e3d81fd5b5f873b113d1516831692505b60405250949350505050565b6040516323b872dd60e01b5f8181526001600160a01b038781166004528616602452604485905291602083606481808c5af1925060015f51148316611dc5578383151615611db9573d5f823e3d81fd5b5f883b113d1516831692505b604052505f60605295945050505050565b806001600160a01b03163b5f03611e0b57604051634c9c8ce360e01b81526001600160a01b03821660048201526024016104cf565b5f5160206123b45f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f611e468484611ef9565b9050808015611e6757505f3d1180611e6757505f846001600160a01b03163b115b15611e7c57611e74611f0c565b915050611ed4565b8015611ea657604051639996b31560e01b81526001600160a01b03851660048201526024016104cf565b3d15611eb957611eb4611f25565b611ed2565b60405163d6bda27560e01b815260040160405180910390fd5b505b92915050565b34156118765760405163b398979f60e01b815260040160405180910390fd5b5f5f5f835160208501865af49392505050565b6040513d81523d5f602083013e3d602001810160405290565b6040513d5f823e3d81fd5b5f60208284031215611f40575f5ffd5b5035919050565b6001600160a01b0381168114611f5b575f5ffd5b50565b8015158114611f5b575f5ffd5b8035611f7681611f5e565b919050565b5f5f5f5f5f60a08688031215611f8f575f5ffd5b8535945060208601359350604086013561ffff81168114611fae575f5ffd5b92506060860135611fbe81611f47565b91506080860135611fce81611f5e565b809150509295509295909350565b5f5f60408385031215611fed575f5ffd5b823591506020830135611fff81611f47565b809150509250929050565b5f5f5f6060848603121561201c575f5ffd5b8335925060208401359150604084013561203581611f5e565b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b6040516080810167ffffffffffffffff8111828210171561207757612077612040565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156120a6576120a6612040565b604052919050565b5f5f604083850312156120bf575f5ffd5b82356120ca81611f47565b9150602083013567ffffffffffffffff8111156120e5575f5ffd5b8301601f810185136120f5575f5ffd5b803567ffffffffffffffff81111561210f5761210f612040565b612122601f8201601f191660200161207d565b818152866020838501011115612136575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b5f6020828403121561219a575f5ffd5b81356121a581611f47565b9392505050565b5f5f5f606084860312156121be575f5ffd5b8335925060208401356121d081611f47565b9150604084013561203581611f47565b5f604082840312156121f0575f5ffd5b6040805190810167ffffffffffffffff8111828210171561221357612213612040565b604052823581526020928301359281019290925250919050565b5f5f5f5f5f5f868803610160811215612244575f5ffd5b87359650602088013561225681611f47565b95506122658960408a016121e0565b94506080607f1982011215612278575f5ffd5b50612281612054565b6080880135815260a0880135602082015260c0880135604082015260e0880135606082015292506122b68861010089016121e0565b91506122c56101408801611f6b565b90509295509295509295565b6001600160a01b0392831681529116602082015260400190565b5f602082840312156122fb575f5ffd5b81516121a581611f47565b5f60208284031215612316575f5ffd5b5051919050565b5f6020828403121561232d575f5ffd5b81516121a581611f5e565b6001600160a01b038781168252861660208201526101608101612368604083018780518252602090810151910152565b8451608083015260208086015160a0840152604086015160c0840152606086015160e0840152845161010084015284015161012083015282151561014083015297965050505050505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc2527cfd5830db0c841b72084c1ec066be32b8320e2ee2b8cb438bb32af8d8500a264697066735822122032e7281002eca6b4522b7f7f32e38b38f508848d9e7a1605dd49869a5fdf40dd64736f6c634300081e0033
Deployed Bytecode
0x608060405234801561000f575f5ffd5b50600436106100f0575f3560e01c80637749299311610093578063a01b364d11610063578063a01b364d14610207578063ac8e7dc914610222578063b0df4cab1461022b578063fcd8e61a14610252575f5ffd5b806377492993146101c45780637754305c146101cc57806394dd95b9146101e15780639611c5c2146101ec575f5ffd5b80633d1e54be116100ce5780633d1e54be14610162578063450cc1b014610179578063504d080b146101945780635506648f146101a9575f5ffd5b80631317d1b8146100f45780631f998bb11461012c5780633a79fc9c14610147575b5f5ffd5b61010f7335b22e09ee0390539439e24f06da43d83f90e29881565b6040516001600160a01b0390911681526020015b60405180910390f35b61010f73a27ec0006e59f245217ff08cd52a7e8b169e62d281565b61010f7363841bad6b35b6419e15ca9bbbbdf446d4dc3dde81565b61016b61d2f081565b604051908152602001610123565b61010f73042df8f42790d6943f41c25c2132400fd727f45281565b61019c61025a565b6040516101239190610853565b61010f73d53006d1e3110fd319a79aeec4c527a0d265e08081565b61016b600381565b6101d4610805565b60405161012391906108d6565b61016b636955aaf081565b61010f73603bb2c05d474794ea97805e8de69bccfb3bca1281565b61010f737d6decf157e1329a20c4596eaf78d387e896aa4e81565b61016b61708081565b61010f7f00000000000000000000000011ed6b4a9d44cf8bc4e1763d08304ef20c998c9581565b61016b600181565b60605f61026b636955aaf042610903565b90505f61027b6201518083610930565b90505f61028b6201518084610943565b90505f600761029b836003610956565b6102a59190610930565b905061708083101580156102ba575061d2f083105b80156102c7575060018110155b80156102d4575060038111155b838290916103025760405163e7a31ae160e01b81526004810192909252602482015260440160405180910390fd5b505060408051600680825260e082019092525f91816020015b604080518082019091525f81526060602082015281526020019060019003908161031b57905050604080518082018252737d6decf157e1329a20c4596eaf78d387e896aa4e815281516004815260248101909252602082810180516001600160e01b0316634d674d0760e01b17905281019190915281519192509082905f906103a6576103a6610969565b602090810291909101810191909152604080518082018252737d6decf157e1329a20c4596eaf78d387e896aa4e81528151600481526024808201845281850180516001600160e01b03166379ba509760e01b1790529251919384019263c28e83fd60e01b9261042d927363841bad6b35b6419e15ca9bbbbdf446d4dc3dde9290910161097d565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152905281518290600190811061047657610476610969565b60209081029190910181019190915260408051808201909152737d6decf157e1329a20c4596eaf78d387e896aa4e815290810163c28e83fd60e01b7363841bad6b35b6419e15ca9bbbbdf446d4dc3dde635661687b60e11b6104dc6301e1338042610903565b6040516024016104ee91815260200190565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161053092919060240161097d565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152905281518290600290811061057957610579610969565b602090810291909101810191909152604080518082018252737d6decf157e1329a20c4596eaf78d387e896aa4e815281516001600160a01b037f00000000000000000000000011ed6b4a9d44cf8bc4e1763d08304ef20c998c95166024808301919091528351808303820181526044909201845281850180516001600160e01b0316632b00162b60e21b1790529251919384019263c28e83fd60e01b92610638927363841bad6b35b6419e15ca9bbbbdf446d4dc3dde9290910161097d565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152905281518290600390811061068157610681610969565b602090810291909101810191909152604080518082018252737d6decf157e1329a20c4596eaf78d387e896aa4e81528151600481526024808201845281850180516001600160e01b0316630c95593b60e21b1790529251919384019263c28e83fd60e01b926107089273d53006d1e3110fd319a79aeec4c527a0d265e0809290910161097d565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152905281518290600490811061075157610751610969565b6020026020010181905250604051806040016040528073603bb2c05d474794ea97805e8de69bccfb3bca126001600160a01b03168152602001638fcc697a60e01b60016040516024016107a8911515815260200190565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915290528151829060059081106107f1576107f1610969565b602090810291909101015294505050505090565b60606040518060600160405280603481526020016109a960349139905090565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b828110156108ca57868503603f19018452815180516001600160a01b031686526020908101516040918701829052906108b490870182610825565b9550506020938401939190910190600101610879565b50929695505050505050565b602081525f6108e86020830184610825565b9392505050565b634e487b7160e01b5f52601160045260245ffd5b81810381811115610916576109166108ef565b92915050565b634e487b7160e01b5f52601260045260245ffd5b5f8261093e5761093e61091c565b500690565b5f826109515761095161091c565b500490565b80820180821115610916576109166108ef565b634e487b7160e01b5f52603260045260245ffd5b6001600160a01b03831681526040602082018190525f906109a090830184610825565b94935050505056fe68747470733a2f2f6769746875622e636f6d2f417a74656350726f746f636f6c2f69676e6974696f6e2d636f6e7472616374732fa2646970667358221220220530146f4bb176e232fff60e2f229b7e0f8631a21d045c229a239eb119924564736f6c634300081e0033
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.