Source Code
Overview
AVAX Balance
Token Holdings
More Info
ContractCreator
Multichain Info
N/A
Latest 21 from a total of 21 transactions
| Transaction Hash |
Method
|
Block
|
From
|
To
|
Amount
|
||||
|---|---|---|---|---|---|---|---|---|---|
| Claim Rewards | 40774384 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40774361 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40774355 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40774347 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40774339 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40774327 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40774071 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40774062 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40774058 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40773749 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40773745 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40773737 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Claim Protocol F... | 40772942 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Claim Curator Fe... | 40772938 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Claim Operator F... | 40772929 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Distribute Rewar... | 40772891 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Amou... | 40772879 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Shar... | 40772602 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Dist... | 40772582 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Set Rewards Mana... | 40772574 | 290 days ago | IN | 0 AVAX | 0 | ||||
| Initialize | 40764611 | 290 days ago | IN | 0 AVAX | 0 |
Latest 25 internal transactions (View All)
| Parent Transaction Hash | Block | From | To | Amount | ||
|---|---|---|---|---|---|---|
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX | ||||
| 40774384 | 290 days ago | 0 AVAX |
Loading...
Loading
Contract Name:
Rewards
Compiler Version
v0.8.25+commit.b61c2a91
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
import {AvalancheL1Middleware} from "../middleware/AvalancheL1Middleware.sol";
import {MiddlewareVaultManager} from "../middleware/MiddlewareVaultManager.sol";
import {UptimeTracker} from "./UptimeTracker.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {BaseDelegator} from "../delegator/BaseDelegator.sol";
import {IVaultTokenized} from "../../interfaces/vault/IVaultTokenized.sol";
import {VaultTokenized} from "../vault/VaultTokenized.sol";
import {IRewards, DistributionBatch} from "../../interfaces/rewards/IRewards.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
contract Rewards is AccessControlUpgradeable, IRewards {
using SafeERC20 for IERC20;
using EnumerableMap for EnumerableMap.AddressToUintMap;
using EnumerableSet for EnumerableSet.AddressSet;
// Constants
uint16 public constant BASIS_POINTS_DENOMINATOR = 10_000;
bytes32 public constant REWARDS_MANAGER_ROLE = keccak256("REWARDS_MANAGER_ROLE");
bytes32 public constant REWARDS_DISTRIBUTOR_ROLE = keccak256("REWARDS_DISTRIBUTOR_ROLE");
bytes32 public constant PROTOCOL_OWNER_ROLE = keccak256("PROTOCOL_OWNER_ROLE");
// STATE VARIABLES
// Fee configuration
uint16 public protocolFee;
uint16 public operatorFee;
uint16 public curatorFee;
// External contracts
AvalancheL1Middleware public l1Middleware;
MiddlewareVaultManager public middlewareVaultManager;
UptimeTracker public uptimeTracker;
uint48 public epochDuration;
uint256 public minRequiredUptime;
// Batch tracking
mapping(uint48 epoch => DistributionBatch) public distributionBatches;
// Share tracking
mapping(uint48 epoch => mapping(address operator => uint256 share)) public operatorBeneficiariesShares;
mapping(uint48 epoch => mapping(address operator => uint256 share)) public operatorShares;
mapping(uint48 epoch => mapping(address vault => uint256 share)) public vaultShares; // vault stakes owners shares
mapping(uint48 epoch => mapping(address curator => uint256 share)) public curatorShares; // vault owner shares
// Protocol rewards
mapping(address rewardsToken => uint256 rewardsAmount) public protocolRewards;
// Reward token amounts per epoch
mapping(uint48 epoch => EnumerableMap.AddressToUintMap rewardsTokenToAmount) private rewardsAmountPerTokenFromEpoch;
// Last claimed epoch tracking
mapping(address staker => uint48 epoch) public lastEpochClaimedStaker;
mapping(address curator => uint48 epoch) public lastEpochClaimedCurator;
mapping(address protocolOwner => uint48 epoch) public lastEpochClaimedProtocol;
mapping(address operator => uint48 epoch) public lastEpochClaimedOperator;
// Asset class configuration
mapping(uint96 assetClass => uint16 rewardsShare) public rewardsSharePerAssetClass;
// INITIALIZER
function initialize(
address admin_,
address protocolOwner_,
address payable l1Middleware_,
address uptimeTracker_,
uint16 protocolFee_,
uint16 operatorFee_,
uint16 curatorFee_,
uint256 minRequiredUptime_
) public initializer {
if (l1Middleware_ == address(0)) revert InvalidL1Middleware(l1Middleware_);
if (uptimeTracker_ == address(0)) revert InvalidUptimeTracker(uptimeTracker_);
if (admin_ == address(0)) revert InvalidAdmin(admin_);
if (protocolOwner_ == address(0)) revert InvalidProtocolOwner(protocolOwner_);
_grantRole(DEFAULT_ADMIN_ROLE, admin_);
_grantRole(REWARDS_MANAGER_ROLE, admin_);
_grantRole(REWARDS_DISTRIBUTOR_ROLE, admin_);
_grantRole(PROTOCOL_OWNER_ROLE, protocolOwner_);
l1Middleware = AvalancheL1Middleware(l1Middleware_);
middlewareVaultManager = MiddlewareVaultManager(l1Middleware.getVaultManager());
uptimeTracker = UptimeTracker(uptimeTracker_);
epochDuration = l1Middleware.EPOCH_DURATION();
protocolFee = protocolFee_;
operatorFee = operatorFee_;
curatorFee = curatorFee_;
minRequiredUptime = minRequiredUptime_;
}
// EXTERNAL FUNCTIONS
// Distribution
/// @inheritdoc IRewards
function distributeRewards(uint48 epoch, uint48 batchSize) external onlyRole(REWARDS_DISTRIBUTOR_ROLE) {
DistributionBatch storage batch = distributionBatches[epoch];
uint48 currentEpoch = l1Middleware.getCurrentEpoch();
if (batch.isComplete) revert AlreadyCompleted(epoch);
// We need to wait for 2 epochs before we can distribute rewards
if (epoch >= currentEpoch - 2) revert RewardsDistributionTooEarly(epoch, currentEpoch - 2);
address[] memory operators = l1Middleware.getAllOperators();
uint256 operatorCount = 0;
for (uint256 i = batch.lastProcessedOperator; i < operators.length && operatorCount < batchSize; i++) {
// Calculate operator's total share based on stake and uptime
_calculateOperatorShare(epoch, operators[i]);
// Calculate and store vault shares
_calculateAndStoreVaultShares(epoch, operators[i]);
batch.lastProcessedOperator = i + 1;
operatorCount++;
}
if (batch.lastProcessedOperator >= operators.length) {
batch.isComplete = true;
}
}
// Claiming functions
/// @inheritdoc IRewards
function claimRewards(address rewardsToken, address recipient) external {
if (recipient == address(0)) revert InvalidRecipient(recipient);
uint48 lastClaimedEpoch = lastEpochClaimedStaker[msg.sender];
uint48 currentEpoch = l1Middleware.getCurrentEpoch();
if (currentEpoch > 0 && lastClaimedEpoch >= currentEpoch - 1) {
revert AlreadyClaimedForLatestEpoch(msg.sender, lastClaimedEpoch);
}
uint256 totalRewards = 0;
for (uint48 epoch = lastClaimedEpoch + 1; epoch < currentEpoch; epoch++) {
address[] memory vaults = _getStakerVaults(msg.sender, epoch);
uint48 epochTs = l1Middleware.getEpochStartTs(epoch);
uint256 epochRewards = rewardsAmountPerTokenFromEpoch[epoch].get(rewardsToken);
if (epochRewards == 0) continue;
for (uint256 i = 0; i < vaults.length; i++) {
address vault = vaults[i];
uint256 vaultShare = vaultShares[epoch][vault];
if (vaultShare == 0) continue;
uint256 stakerVaultShare = IVaultTokenized(vault).activeSharesOfAt(msg.sender, epochTs, new bytes(0));
if (stakerVaultShare == 0) continue;
// Get total raw shares in this specific vault at that time
uint256 totalRawSharesInVault = IVaultTokenized(vault).activeSharesAt(epochTs, new bytes(0));
if (totalRawSharesInVault == 0) continue;
uint256 tokensForVault = Math.mulDiv(
epochRewards,
vaultShare,
BASIS_POINTS_DENOMINATOR
);
uint256 rewards = Math.mulDiv(
tokensForVault,
stakerVaultShare,
totalRawSharesInVault
);
totalRewards += rewards;
}
}
if (totalRewards == 0) revert NoRewardsToClaim(msg.sender);
IERC20(rewardsToken).safeTransfer(recipient, totalRewards);
lastEpochClaimedStaker[msg.sender] = currentEpoch - 1;
}
/// @inheritdoc IRewards
function claimOperatorFee(address rewardsToken, address recipient) external {
if (recipient == address(0)) revert InvalidRecipient(recipient);
uint48 currentEpoch = l1Middleware.getCurrentEpoch();
uint48 lastClaimedEpoch = lastEpochClaimedOperator[msg.sender];
if (currentEpoch > 0 && lastClaimedEpoch >= currentEpoch - 1) {
revert AlreadyClaimedForLatestEpoch(msg.sender, lastClaimedEpoch);
}
uint256 totalRewards = 0;
for (uint48 epoch = lastClaimedEpoch + 1; epoch < currentEpoch; epoch++) {
uint256 operatorShare = operatorShares[epoch][msg.sender];
if (operatorShare == 0) continue;
// get rewards amount per token for epoch
uint256 rewardsAmount = rewardsAmountPerTokenFromEpoch[epoch].get(rewardsToken);
if (rewardsAmount == 0) continue;
uint256 operatorRewards = Math.mulDiv(rewardsAmount, operatorShare, BASIS_POINTS_DENOMINATOR);
totalRewards += operatorRewards;
}
if (totalRewards == 0) revert NoRewardsToClaim(msg.sender);
IERC20(rewardsToken).safeTransfer(recipient, totalRewards);
lastEpochClaimedOperator[msg.sender] = currentEpoch - 1;
}
/// @inheritdoc IRewards
function claimCuratorFee(address rewardsToken, address recipient) external {
if (recipient == address(0)) revert InvalidRecipient(recipient);
uint48 currentEpoch = l1Middleware.getCurrentEpoch();
uint48 lastClaimedEpoch = lastEpochClaimedCurator[msg.sender];
if (currentEpoch > 0 && lastClaimedEpoch >= currentEpoch - 1) {
revert AlreadyClaimedForLatestEpoch(msg.sender, lastClaimedEpoch);
}
uint256 totalCuratorRewards = 0;
for (uint48 epoch = lastClaimedEpoch + 1; epoch < currentEpoch; epoch++) {
uint256 curatorShare = curatorShares[epoch][msg.sender];
if (curatorShare == 0) continue;
uint256 rewardsAmount = rewardsAmountPerTokenFromEpoch[epoch].get(rewardsToken);
if (rewardsAmount == 0) continue;
uint256 curatorRewards = Math.mulDiv(rewardsAmount, curatorShare, BASIS_POINTS_DENOMINATOR);
totalCuratorRewards += curatorRewards;
}
if (totalCuratorRewards == 0) revert NoRewardsToClaim(msg.sender);
IERC20(rewardsToken).safeTransfer(recipient, totalCuratorRewards);
lastEpochClaimedCurator[msg.sender] = currentEpoch - 1;
}
/// @inheritdoc IRewards
function claimProtocolFee(address rewardsToken, address recipient) external onlyRole(PROTOCOL_OWNER_ROLE) {
if (recipient == address(0)) revert InvalidRecipient(recipient);
uint256 rewards = protocolRewards[rewardsToken];
if (rewards == 0) revert NoRewardsToClaim(msg.sender);
IERC20(rewardsToken).safeTransfer(recipient, rewards);
protocolRewards[rewardsToken] = 0;
}
/// @inheritdoc IRewards
function claimUndistributedRewards(
uint48 epoch,
address rewardsToken,
address recipient
) external onlyRole(REWARDS_DISTRIBUTOR_ROLE) {
if (recipient == address(0)) revert InvalidRecipient(recipient);
// Check if epoch distribution is complete
DistributionBatch storage batch = distributionBatches[epoch];
if (!batch.isComplete) revert DistributionNotComplete(epoch);
// Check if current epoch is at least 2 epochs ahead (to ensure all claims are done)
uint48 currentEpoch = l1Middleware.getCurrentEpoch();
if (currentEpoch < epoch + 2) revert EpochStillClaimable(epoch);
// Get total rewards for the epoch
uint256 totalRewardsForEpoch = rewardsAmountPerTokenFromEpoch[epoch].get(rewardsToken);
if (totalRewardsForEpoch == 0) revert NoRewardsToClaim(msg.sender);
// Calculate total distributed shares for the epoch
uint256 totalDistributedShares = 0;
// Sum operator shares
address[] memory operators = l1Middleware.getAllOperators();
for (uint256 i = 0; i < operators.length; i++) {
totalDistributedShares += operatorShares[epoch][operators[i]];
}
// Sum vault shares
address[] memory vaults = middlewareVaultManager.getVaults(epoch);
for (uint256 i = 0; i < vaults.length; i++) {
totalDistributedShares += vaultShares[epoch][vaults[i]];
}
// Sum curator shares
for (uint256 i = 0; i < vaults.length; i++) {
address curator = VaultTokenized(vaults[i]).owner();
totalDistributedShares += curatorShares[epoch][curator];
}
// Calculate and transfer undistributed rewards
uint256 undistributedRewards =
totalRewardsForEpoch - Math.mulDiv(totalRewardsForEpoch, totalDistributedShares, BASIS_POINTS_DENOMINATOR);
if (undistributedRewards == 0) revert NoRewardsToClaim(msg.sender);
// Clear the rewards amount to prevent double claiming
rewardsAmountPerTokenFromEpoch[epoch].set(rewardsToken, 0);
IERC20(rewardsToken).safeTransfer(recipient, undistributedRewards);
emit UndistributedRewardsClaimed(epoch, rewardsToken, recipient, undistributedRewards);
}
// Admin configuration functions
/// @inheritdoc IRewards
function setRewardsAmountForEpochs(
uint48 startEpoch,
uint48 numberOfEpochs,
address rewardsToken,
uint256 rewardsAmount
) external onlyRole(REWARDS_DISTRIBUTOR_ROLE) {
if (rewardsToken == address(0)) {
revert InvalidRewardsToken(rewardsToken);
}
if (rewardsAmount == 0) revert InvalidRewardsAmount(rewardsAmount);
if (numberOfEpochs == 0) revert InvalidNumberOfEpochs(numberOfEpochs);
uint256 totalRewards = rewardsAmount * numberOfEpochs;
IERC20(rewardsToken).safeTransferFrom(msg.sender, address(this), totalRewards);
uint256 protocolRewardsAmount = Math.mulDiv(totalRewards, protocolFee, BASIS_POINTS_DENOMINATOR);
protocolRewards[rewardsToken] += protocolRewardsAmount;
rewardsAmount -= Math.mulDiv(rewardsAmount, protocolFee, BASIS_POINTS_DENOMINATOR);
for (uint48 i = 0; i < numberOfEpochs; i++) {
rewardsAmountPerTokenFromEpoch[startEpoch + i].set(rewardsToken, rewardsAmount);
}
emit RewardsAmountSet(startEpoch, numberOfEpochs, rewardsToken, rewardsAmount);
}
/// @inheritdoc IRewards
function setRewardsShareForAssetClass(uint96 assetClass, uint16 share) external onlyRole(REWARDS_MANAGER_ROLE) {
if (share > BASIS_POINTS_DENOMINATOR) revert InvalidShare(share);
rewardsSharePerAssetClass[assetClass] = share;
emit RewardsShareUpdated(assetClass, share);
}
/// @inheritdoc IRewards
function setMinRequiredUptime(
uint256 newMinUptime
) external onlyRole(REWARDS_MANAGER_ROLE) {
if (newMinUptime > epochDuration) revert InvalidMinUptime(newMinUptime);
minRequiredUptime = newMinUptime;
}
/// @inheritdoc IRewards
function setRewardsDistributorRole(
address newRewardsDistributor
) external onlyRole(REWARDS_MANAGER_ROLE) {
if (newRewardsDistributor == address(0)) revert InvalidRecipient(newRewardsDistributor);
_grantRole(REWARDS_DISTRIBUTOR_ROLE, newRewardsDistributor);
emit RewardsDistributorRoleAssigned(newRewardsDistributor);
}
/// @inheritdoc IRewards
function setRewardsManagerRole(
address newRewardsManager
) external override onlyRole(DEFAULT_ADMIN_ROLE) {
if (newRewardsManager == address(0)) revert InvalidRecipient(newRewardsManager);
_grantRole(REWARDS_MANAGER_ROLE, newRewardsManager);
emit RewardsManagerRoleAssigned(newRewardsManager);
}
/// @inheritdoc IRewards
function setProtocolOwner(
address newProtocolOwner
) external override onlyRole(DEFAULT_ADMIN_ROLE) {
if (newProtocolOwner == address(0)) revert InvalidRecipient(newProtocolOwner);
_grantRole(PROTOCOL_OWNER_ROLE, newProtocolOwner);
emit ProtocolOwnerUpdated(newProtocolOwner);
}
/// @inheritdoc IRewards
function updateProtocolFee(
uint16 newFee
) external override onlyRole(REWARDS_MANAGER_ROLE) {
if (newFee > BASIS_POINTS_DENOMINATOR) revert InvalidFee(newFee);
protocolFee = newFee;
emit ProtocolFeeUpdated(newFee);
}
/// @inheritdoc IRewards
function updateOperatorFee(
uint16 newFee
) external override onlyRole(REWARDS_MANAGER_ROLE) {
if (newFee > BASIS_POINTS_DENOMINATOR) revert InvalidFee(newFee);
operatorFee = newFee;
emit OperatorFeeUpdated(newFee);
}
/// @inheritdoc IRewards
function updateCuratorFee(
uint16 newFee
) external override onlyRole(REWARDS_MANAGER_ROLE) {
if (newFee > BASIS_POINTS_DENOMINATOR) revert InvalidFee(newFee);
curatorFee = newFee;
emit CuratorFeeUpdated(newFee);
}
// Getter functions
/// @inheritdoc IRewards
function getRewardsAmountPerTokenFromEpoch(
uint48 epoch
) external view override returns (address[] memory tokens, uint256[] memory amounts) {
tokens = rewardsAmountPerTokenFromEpoch[epoch].keys();
amounts = new uint256[](tokens.length);
for (uint256 i = 0; i < tokens.length; i++) {
amounts[i] = rewardsAmountPerTokenFromEpoch[epoch].get(tokens[i]);
}
}
function getRewardsAmountPerTokenFromEpoch(uint48 epoch, address token) external view returns (uint256) {
return rewardsAmountPerTokenFromEpoch[epoch].get(token);
}
// INTERNAL FUNCTIONS
// Calculation functions
/**
* @dev Calculates the operator share for a given epoch and operator
* @param epoch The epoch to calculate the operator share for
* @param operator The operator to calculate the share for
*/
function _calculateOperatorShare(uint48 epoch, address operator) internal {
uint256 uptime = uptimeTracker.operatorUptimePerEpoch(epoch, operator);
if (uptime < minRequiredUptime) {
operatorBeneficiariesShares[epoch][operator] = 0;
operatorShares[epoch][operator] = 0;
return;
}
uint256 operatorUptime = Math.mulDiv(uptime, BASIS_POINTS_DENOMINATOR, epochDuration);
uint256 totalShare = 0;
uint96[] memory assetClasses = l1Middleware.getAssetClassIds();
for (uint256 i = 0; i < assetClasses.length; i++) {
uint256 operatorStake = l1Middleware.getOperatorUsedStakeCachedPerEpoch(epoch, operator, assetClasses[i]);
uint256 totalStake = l1Middleware.totalStakeCache(epoch, assetClasses[i]);
uint16 assetClassShare = rewardsSharePerAssetClass[assetClasses[i]];
uint256 shareForClass = Math.mulDiv(
Math.mulDiv(operatorStake, BASIS_POINTS_DENOMINATOR, totalStake),
assetClassShare,
BASIS_POINTS_DENOMINATOR
);
totalShare += shareForClass;
}
totalShare = Math.mulDiv(totalShare, operatorUptime, BASIS_POINTS_DENOMINATOR);
// Calculate operator fee share and store it
uint256 operatorFeeShare = Math.mulDiv(totalShare, operatorFee, BASIS_POINTS_DENOMINATOR);
operatorShares[epoch][operator] = operatorFeeShare;
// Remove operator fee share from total share
totalShare -= operatorFeeShare;
operatorBeneficiariesShares[epoch][operator] = totalShare;
}
/**
* @dev Calculates and stores the vault shares for a given epoch and operator
* @param epoch The epoch to calculate the vault shares for
* @param operator The operator to calculate the vault shares for
*/
function _calculateAndStoreVaultShares(uint48 epoch, address operator) internal {
uint256 operatorShare = operatorBeneficiariesShares[epoch][operator];
if (operatorShare == 0) return;
address[] memory vaults = middlewareVaultManager.getVaults(epoch);
uint48 epochTs = l1Middleware.getEpochStartTs(epoch);
// First pass: calculate raw shares and total
for (uint256 i = 0; i < vaults.length; i++) {
address vault = vaults[i];
uint96 vaultAssetClass = middlewareVaultManager.getVaultAssetClass(vault);
uint256 vaultStake = BaseDelegator(IVaultTokenized(vault).delegator()).stakeAt(
l1Middleware.L1_VALIDATOR_MANAGER(), vaultAssetClass, operator, epochTs, new bytes(0)
);
if (vaultStake > 0) {
uint256 operatorActiveStake =
l1Middleware.getOperatorUsedStakeCachedPerEpoch(epoch, operator, vaultAssetClass);
uint256 vaultShare = Math.mulDiv(vaultStake, BASIS_POINTS_DENOMINATOR, operatorActiveStake);
vaultShare =
Math.mulDiv(vaultShare, rewardsSharePerAssetClass[vaultAssetClass], BASIS_POINTS_DENOMINATOR);
vaultShare = Math.mulDiv(vaultShare, operatorShare, BASIS_POINTS_DENOMINATOR);
uint256 operatorTotalStake = l1Middleware.getOperatorStake(operator, epoch, vaultAssetClass);
if (operatorTotalStake > 0) {
uint256 operatorStakeRatio =
Math.mulDiv(operatorActiveStake, BASIS_POINTS_DENOMINATOR, operatorTotalStake);
vaultShare = Math.mulDiv(vaultShare, operatorStakeRatio, BASIS_POINTS_DENOMINATOR);
}
// Calculate curator share
uint256 curatorShare = Math.mulDiv(vaultShare, curatorFee, BASIS_POINTS_DENOMINATOR);
curatorShares[epoch][VaultTokenized(vault).owner()] += curatorShare;
// Store vault share after removing curator share
vaultShares[epoch][vault] += vaultShare - curatorShare;
}
}
}
// Getter functions
/**
* @dev Gets the vaults for a given staker and epoch
* @param staker The staker to get the vaults for
* @param epoch The epoch to get the vaults for
* @return The vaults for the given staker and epoch
*/
function _getStakerVaults(address staker, uint48 epoch) internal view returns (address[] memory) {
address[] memory vaults = middlewareVaultManager.getVaults(epoch);
uint48 epochStart = l1Middleware.getEpochStartTs(epoch);
uint256 count = 0;
// First pass: Count non-zero balance vaults
for (uint256 i = 0; i < vaults.length; i++) {
uint256 balance = IVaultTokenized(vaults[i]).activeBalanceOfAt(staker, epochStart, new bytes(0));
if (balance > 0) {
count++;
}
}
// Create a new array with the exact number of valid vaults
address[] memory validVaults = new address[](count);
uint256 index = 0;
// Second pass: Populate the new array
for (uint256 i = 0; i < vaults.length; i++) {
uint256 balance = IVaultTokenized(vaults[i]).activeBalanceOfAt(staker, epochStart, new bytes(0));
if (balance > 0) {
validVaults[index] = vaults[i];
index++;
}
}
return validVaults;
}
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
import {Time} from "@openzeppelin/contracts/utils/types/Time.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {
Validator,
ValidatorStatus,
ValidatorRegistrationInput,
PChainOwner
} from "@avalabs/teleporter/validator-manager/interfaces/IValidatorManager.sol";
import {BalancerValidatorManager} from
"@suzaku/contracts-library/contracts/ValidatorManager/BalancerValidatorManager.sol";
import {IOperatorRegistry} from "../../interfaces/IOperatorRegistry.sol";
import {IVaultTokenized} from "../../interfaces/vault/IVaultTokenized.sol";
import {IAvalancheL1Middleware} from "../../interfaces/middleware/IAvalancheL1Middleware.sol";
import {IOptInService} from "../../interfaces/service/IOptInService.sol";
import {AssetClassRegistry} from "./AssetClassRegistry.sol";
import {MiddlewareVaultManager} from "./MiddlewareVaultManager.sol";
import {MapWithTimeData} from "./libraries/MapWithTimeData.sol";
import {StakeConversion} from "./libraries/StakeConversion.sol";
import {BaseDelegator} from "../../contracts/delegator/BaseDelegator.sol";
struct AvalancheL1MiddlewareSettings {
address l1ValidatorManager;
address operatorRegistry;
address vaultRegistry;
address operatorL1Optin;
uint48 epochDuration;
uint48 slashingWindow;
uint48 stakeUpdateWindow;
}
/**
* @title AvalancheL1Middleware
* @notice Manages operator registration, vault registration, stake accounting, and slashing for Avalanche L1
*/
contract AvalancheL1Middleware is IAvalancheL1Middleware, AssetClassRegistry {
using EnumerableMap for EnumerableMap.AddressToUintMap;
using EnumerableSet for EnumerableSet.UintSet;
using MapWithTimeData for EnumerableMap.AddressToUintMap;
using EnumerableMap for EnumerableMap.Bytes32ToUintMap;
using EnumerableSet for EnumerableSet.Bytes32Set;
address public immutable L1_VALIDATOR_MANAGER;
address public immutable OPERATOR_REGISTRY;
address public immutable OPERATOR_L1_OPTIN;
address public immutable PRIMARY_ASSET;
uint48 public immutable EPOCH_DURATION;
uint48 public immutable SLASHING_WINDOW;
uint48 public immutable START_TIME;
uint48 public immutable UPDATE_WINDOW;
uint256 public immutable WEIGHT_SCALE_FACTOR;
uint48 public lastGlobalNodeStakeUpdateEpoch;
uint96 public constant PRIMARY_ASSET_CLASS = 1;
uint48 public constant MAX_AUTO_EPOCH_UPDATES = 1;
MiddlewareVaultManager private vaultManager;
EnumerableMap.AddressToUintMap private operators;
EnumerableSet.UintSet private secondaryAssetClasses;
BalancerValidatorManager public balancerValidatorManager;
mapping(address => mapping(uint48 => bool)) public rebalancedThisEpoch;
mapping(uint48 => mapping(uint96 => uint256)) public totalStakeCache;
mapping(address => bytes32[]) public operatorNodesArray;
mapping(uint48 => mapping(uint96 => mapping(address => uint256))) public operatorStakeCache;
mapping(uint48 => mapping(bytes32 => uint256)) public nodeStakeCache;
mapping(bytes32 => bool) public nodePendingUpdate;
mapping(bytes32 => bool) public nodePendingRemoval;
mapping(address => uint256) public operatorLockedStake;
mapping(uint48 => mapping(uint96 => bool)) public totalStakeCached;
// operatorNodesArray[operator] is used for iteration during certain
// rebalancing or node-update operations, and has nodes removed once
// they are effectively retired. This means a node can remain in
// operatorNodes while it is removed from operatorNodesArray.
// operatorNodes[operator] is intended as a permanent record of all nodes
// ever registered by the operator, used for historical/epoch-based queries.
// We do *not* remove nodes from this set when they are "retired" so
// getActiveNodesForEpoch(...) can still detect them for past epochs.
mapping(address => EnumerableSet.Bytes32Set) private operatorNodes;
/**
* @notice Initializes contract settings
* @param settings General contract parameters
* @param owner Owner address
* @param primaryAsset The primary asset address
* @param primaryAssetMaxStake Max stake for the primary asset class
* @param primaryAssetMinStake Min stake for the primary asset class
*/
constructor(
AvalancheL1MiddlewareSettings memory settings,
address owner,
address primaryAsset,
uint256 primaryAssetMaxStake,
uint256 primaryAssetMinStake,
uint256 primaryAssetWeightScaleFactor
) AssetClassRegistry(owner) {
if (settings.l1ValidatorManager == address(0)) {
revert AvalancheL1Middleware__ZeroAddress("l1ValidatorManager");
}
if (settings.operatorRegistry == address(0)) {
revert AvalancheL1Middleware__ZeroAddress("operatorRegistry");
}
if (settings.vaultRegistry == address(0)) {
revert AvalancheL1Middleware__ZeroAddress("vaultRegistry");
}
if (settings.operatorL1Optin == address(0)) {
revert AvalancheL1Middleware__ZeroAddress("operatorL1Optin");
}
if (owner == address(0)) {
revert AvalancheL1Middleware__ZeroAddress("owner");
}
if (primaryAsset == address(0)) {
revert AvalancheL1Middleware__ZeroAddress("primaryAsset");
}
if (settings.slashingWindow < settings.epochDuration) {
revert AvalancheL1Middleware__SlashingWindowTooShort(settings.slashingWindow, settings.epochDuration);
}
if (primaryAssetWeightScaleFactor == 0) {
revert AvalancheL1Middleware__InvalidScaleFactor();
}
START_TIME = Time.timestamp();
EPOCH_DURATION = settings.epochDuration;
L1_VALIDATOR_MANAGER = settings.l1ValidatorManager;
OPERATOR_REGISTRY = settings.operatorRegistry;
OPERATOR_L1_OPTIN = settings.operatorL1Optin;
SLASHING_WINDOW = settings.slashingWindow;
PRIMARY_ASSET = primaryAsset;
UPDATE_WINDOW = settings.stakeUpdateWindow;
WEIGHT_SCALE_FACTOR = primaryAssetWeightScaleFactor;
balancerValidatorManager = BalancerValidatorManager(settings.l1ValidatorManager);
_addAssetClass(PRIMARY_ASSET_CLASS, primaryAssetMinStake, primaryAssetMaxStake, PRIMARY_ASSET);
}
/**
* @notice Updates stake cache before function execution
* @param epoch The epoch to update
* @param assetClassId The asset class ID
*/
modifier updateStakeCache(uint48 epoch, uint96 assetClassId) {
if (!totalStakeCached[epoch][assetClassId]) {
calcAndCacheStakes(epoch, assetClassId);
}
_;
}
/**
* @notice Window where a node update can be done manually, before the force update can be applied
*/
modifier onlyDuringFinalWindowOfEpoch() {
uint48 currentEpoch = getCurrentEpoch();
uint48 epochStartTs = getEpochStartTs(currentEpoch);
uint48 timeNow = Time.timestamp();
uint48 epochUpdatePeriod = epochStartTs + UPDATE_WINDOW;
if (timeNow < epochUpdatePeriod || timeNow > epochStartTs + EPOCH_DURATION) {
revert AvalancheL1Middleware__NotEpochUpdatePeriod(timeNow, epochUpdatePeriod);
}
_;
}
modifier onlyRegisteredOperatorNode(address operator, bytes32 nodeId) {
if (!operators.contains(operator)) {
revert AvalancheL1Middleware__OperatorNotRegistered(operator);
}
if (!operatorNodes[operator].contains(nodeId)) {
revert AvalancheL1Middleware__NodeNotFound(nodeId);
}
_;
}
modifier updateGlobalNodeStakeOncePerEpoch() {
uint48 current = getCurrentEpoch();
if (current > lastGlobalNodeStakeUpdateEpoch) {
calcAndCacheNodeStakeForAllOperators();
lastGlobalNodeStakeUpdateEpoch = current;
}
_;
}
function setVaultManager(
address vaultManager_
) external onlyOwner {
if (vaultManager_ == address(0)) {
revert AvalancheL1Middleware__ZeroAddress("vaultManager");
}
emit VaultManagerUpdated(address(vaultManager), vaultManager_);
vaultManager = MiddlewareVaultManager(vaultManager_);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function activateSecondaryAssetClass(
uint256 assetClassId
) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
if (!assetClassIds.contains(assetClassId)) {
revert AssetClassRegistry__AssetClassNotFound();
}
if (assetClassId == PRIMARY_ASSET_CLASS) {
revert AssetClassRegistry__AssetClassAlreadyExists();
}
bool added = secondaryAssetClasses.add(assetClassId);
if (!added) {
revert AssetClassRegistry__AssetClassAlreadyExists();
}
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function deactivateSecondaryAssetClass(
uint256 assetClassId
) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
if (_isUsedAssetClass(assetClassId)) {
revert AvalancheL1Middleware__AssetStillInUse(assetClassId);
}
bool removed = secondaryAssetClasses.remove(assetClassId);
if (!removed) {
revert AssetClassRegistry__AssetClassNotFound();
}
}
/**
* @notice Removes an asset from an asset class, except primary asset
* @param assetClassId The ID of the asset class
* @param asset The address of the asset to remove
*/
function removeAssetFromClass(
uint256 assetClassId,
address asset
) public override onlyOwner updateGlobalNodeStakeOncePerEpoch {
if (assetClassId == 1 && asset == PRIMARY_ASSET) {
revert AssetClassRegistry__AssetIsPrimaryAssetClass(assetClassId);
}
if (_isUsedAsset(assetClassId, asset)) {
revert AvalancheL1Middleware__AssetStillInUse(assetClassId);
}
super.removeAssetFromClass(assetClassId, asset);
}
/**
* @notice Removes an asset class
* @param assetClassId The asset class ID
*/
function removeAssetClass(
uint256 assetClassId
) public override updateGlobalNodeStakeOncePerEpoch {
if (secondaryAssetClasses.contains(assetClassId)) {
revert AvalancheL1Middleware__ActiveSecondaryAssetCLass(assetClassId);
}
super.removeAssetClass(assetClassId);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function registerOperator(
address operator
) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
if (operators.contains(operator)) {
revert AvalancheL1Middleware__OperatorAlreadyRegistered(operator);
}
if (!IOperatorRegistry(OPERATOR_REGISTRY).isRegistered(operator)) {
revert AvalancheL1Middleware__OperatorNotRegistered(operator);
}
if (!IOptInService(OPERATOR_L1_OPTIN).isOptedIn(operator, L1_VALIDATOR_MANAGER)) {
revert AvalancheL1Middleware__OperatorNotOptedIn(operator, L1_VALIDATOR_MANAGER);
}
operators.add(operator);
operators.enable(operator);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function disableOperator(
address operator
) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
operators.disable(operator);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function enableOperator(
address operator
) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
operators.enable(operator);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function removeOperator(
address operator
) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
(, uint48 disabledTime) = operators.getTimes(operator);
if (disabledTime == 0 || disabledTime + SLASHING_WINDOW > Time.timestamp()) {
revert AvalancheL1Middleware__OperatorGracePeriodNotPassed(disabledTime, SLASHING_WINDOW);
}
operators.remove(operator);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function addNode(
bytes32 nodeId,
bytes calldata blsKey,
uint64 registrationExpiry,
PChainOwner calldata remainingBalanceOwner,
PChainOwner calldata disableOwner,
uint256 stakeAmount // optional
) external updateStakeCache(getCurrentEpoch(), PRIMARY_ASSET_CLASS) updateGlobalNodeStakeOncePerEpoch {
address operator = msg.sender;
if (!operators.contains(operator)) {
revert AvalancheL1Middleware__OperatorNotRegistered(operator);
}
if (!_requireMinSecondaryAssetClasses(1, operator)) {
revert AvalancheL1Middleware__NotEnoughFreeStakeSecondaryAssetClasses();
}
bytes32 valId = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
uint256 available = _getOperatorAvailableStake(operator);
if (nodePendingRemoval[valId]) revert AvalancheL1Middleware__NodePendingRemoval(nodeId);
if (nodePendingUpdate[valId]) revert AvalancheL1Middleware__NodePendingUpdate(nodeId);
uint256 minStake = assetClasses[PRIMARY_ASSET_CLASS].minValidatorStake;
uint256 maxStake = assetClasses[PRIMARY_ASSET_CLASS].maxValidatorStake;
uint256 newStake = (stakeAmount != 0) ? stakeAmount : available;
newStake = (newStake > maxStake) ? maxStake : newStake;
if (newStake < minStake || newStake > available) {
revert AvalancheL1Middleware__NotEnoughFreeStake(newStake);
}
ValidatorRegistrationInput memory input = ValidatorRegistrationInput({
nodeID: abi.encodePacked(uint160(uint256(nodeId))),
blsPublicKey: blsKey,
registrationExpiry: registrationExpiry,
remainingBalanceOwner: remainingBalanceOwner,
disableOwner: disableOwner
});
// Track node in our time-based map and dynamic array.
operatorNodes[operator].add(nodeId);
operatorNodesArray[operator].push(nodeId);
uint48 epoch = getCurrentEpoch();
bytes32 validationID = balancerValidatorManager.initializeValidatorRegistration(
input, StakeConversion.stakeToWeight(newStake, WEIGHT_SCALE_FACTOR)
);
nodeStakeCache[epoch][validationID] = newStake;
nodeStakeCache[epoch + 1][validationID] = newStake;
nodePendingUpdate[validationID] = true;
emit NodeAdded(operator, nodeId, newStake, validationID);
}
function removeNode(
bytes32 nodeId
) external updateGlobalNodeStakeOncePerEpoch onlyRegisteredOperatorNode(msg.sender, nodeId) {
_removeNode(msg.sender, nodeId);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function forceUpdateNodes(
address operator,
uint256 limitStake
)
external
updateStakeCache(getCurrentEpoch(), PRIMARY_ASSET_CLASS)
onlyDuringFinalWindowOfEpoch
updateGlobalNodeStakeOncePerEpoch
{
uint48 currentEpoch = getCurrentEpoch();
if (rebalancedThisEpoch[operator][currentEpoch]) {
revert AvalancheL1Middleware__AlreadyRebalanced(operator, currentEpoch);
}
rebalancedThisEpoch[operator][currentEpoch] = true;
if (!operators.contains(operator)) {
revert AvalancheL1Middleware__OperatorNotRegistered(operator);
}
// Calculate the new total stake for the operator and compare it to the registered stake
uint256 newTotalStake = _getOperatorAvailableStake(operator);
uint256 registeredStake = getOperatorUsedStakeCached(operator);
uint256 leftoverStake;
bytes32[] storage nodesArr = operatorNodesArray[operator];
uint256 length = nodesArr.length;
// If nothing changed, do nothing
if (newTotalStake == registeredStake) {
return;
}
if (newTotalStake > registeredStake) {
leftoverStake = newTotalStake - registeredStake;
emit OperatorHasLeftoverStake(operator, leftoverStake);
emit AllNodeStakesUpdated(operator, newTotalStake);
return;
}
// We only handle the scenario newTotalStake < registeredStake, when removing stake
leftoverStake = registeredStake - newTotalStake;
for (uint256 i = length; i > 0 && leftoverStake > 0;) {
i--;
bytes32 nodeId = nodesArr[i];
bytes32 valID = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
if (balancerValidatorManager.isValidatorPendingWeightUpdate(valID)) {
continue;
}
Validator memory validator = balancerValidatorManager.getValidator(valID);
if (validator.status != ValidatorStatus.Active) {
continue;
}
uint256 previousStake = getEffectiveNodeStake(currentEpoch, valID);
// Remove stake
if (previousStake == 0) {
continue;
}
uint256 stakeToRemove = leftoverStake < previousStake ? leftoverStake : previousStake;
if (limitStake > 0 && stakeToRemove > limitStake) {
stakeToRemove = limitStake;
}
uint256 newStake = previousStake - stakeToRemove;
leftoverStake -= stakeToRemove;
if (
(newStake < assetClasses[PRIMARY_ASSET_CLASS].minValidatorStake)
|| !_requireMinSecondaryAssetClasses(0, operator)
) {
newStake = 0;
_initializeEndValidationAndFlag(operator, valID, nodeId);
} else {
_initializeValidatorStakeUpdate(operator, valID, newStake);
emit NodeStakeUpdated(operator, nodeId, newStake, valID);
}
}
// Finally emit updated stake
emit AllNodeStakesUpdated(operator, newTotalStake);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function initializeValidatorStakeUpdate(
bytes32 nodeId,
uint256 stakeAmount
) external updateGlobalNodeStakeOncePerEpoch {
if (!operatorNodes[msg.sender].contains(nodeId)) {
revert AvalancheL1Middleware__NodeNotFound(nodeId);
}
uint256 minStake = assetClasses[PRIMARY_ASSET_CLASS].minValidatorStake;
uint256 maxStake = assetClasses[PRIMARY_ASSET_CLASS].maxValidatorStake;
if (stakeAmount > maxStake) {
revert AvalancheL1Middleware__StakeTooHigh(stakeAmount, maxStake);
}
if (stakeAmount < minStake) {
revert AvalancheL1Middleware__StakeTooLow(stakeAmount, minStake);
}
bytes32 validationID = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
_initializeValidatorStakeUpdate(msg.sender, validationID, stakeAmount);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function completeValidatorRegistration(
address operator,
bytes32 nodeId,
uint32 messageIndex
) external updateGlobalNodeStakeOncePerEpoch {
_completeValidatorRegistration(operator, nodeId, messageIndex);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function completeStakeUpdate(
bytes32 nodeId,
uint32 messageIndex
) external onlyRegisteredOperatorNode(msg.sender, nodeId) updateGlobalNodeStakeOncePerEpoch {
_completeStakeUpdate(msg.sender, nodeId, messageIndex);
}
function completeValidatorRemoval(
uint32 messageIndex
) external updateGlobalNodeStakeOncePerEpoch {
_completeValidatorRemoval(messageIndex);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function slash(
uint48 epoch,
address, /* operator */
uint256, /* amount */
uint96 assetClassId
) public onlyOwner updateStakeCache(epoch, assetClassId) updateGlobalNodeStakeOncePerEpoch {
revert AvalancheL1Middleware__NotImplemented();
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function calcAndCacheStakes(uint48 epoch, uint96 assetClassId) public returns (uint256 totalStake) {
uint48 epochStartTs = getEpochStartTs(epoch);
uint256 length = operators.length();
for (uint256 i; i < length; ++i) {
(address operator, uint48 enabledTime, uint48 disabledTime) = operators.atWithTimes(i);
if (!_wasActiveAt(enabledTime, disabledTime, epochStartTs)) {
continue;
}
uint256 operatorStake = getOperatorStake(operator, epoch, assetClassId);
operatorStakeCache[epoch][assetClassId][operator] = operatorStake;
totalStake += operatorStake;
}
totalStakeCache[epoch][assetClassId] = totalStake;
totalStakeCached[epoch][assetClassId] = true;
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function calcAndCacheNodeStakeForAllOperators() public {
uint48 current = getCurrentEpoch();
if (current <= lastGlobalNodeStakeUpdateEpoch) {
return; // Already up-to-date
}
uint48 epochsPending = current - lastGlobalNodeStakeUpdateEpoch;
if (epochsPending > MAX_AUTO_EPOCH_UPDATES) {
revert AvalancheL1Middleware__ManualEpochUpdateRequired(epochsPending, MAX_AUTO_EPOCH_UPDATES);
}
// Process pending epochs up to MAX_AUTO_EPOCH_UPDATES
for (uint48 i = 0; i < epochsPending; i++) {
bool processed = _processSingleEpochNodeStakeCacheUpdate();
if (!processed) break;
}
}
/**
* @notice Processes node stake cache updates for the next pending epoch.
* @dev Updates lastGlobalNodeStakeUpdateEpoch if an epoch is processed.
* @return processed True if an epoch was processed, false if already up-to-date.
*/
function _processSingleEpochNodeStakeCacheUpdate() internal returns (bool) {
uint48 current = getCurrentEpoch();
if (current <= lastGlobalNodeStakeUpdateEpoch) {
return false; // Already up-to-date
}
uint48 epochToProcess = lastGlobalNodeStakeUpdateEpoch + 1;
// Process this single epochToProcess
for (uint256 i = 0; i < operators.length(); i++) {
(address operator,,) = operators.atWithTimes(i);
// _calcAndCacheNodeStakeForOperatorAtEpoch itself handles carry-over from epochToProcess - 1
_calcAndCacheNodeStakeForOperatorAtEpoch(operator, epochToProcess);
}
lastGlobalNodeStakeUpdateEpoch = epochToProcess;
return true;
}
/**
* @notice Manually processes node stake cache updates for a specified number of epochs.
* @dev Useful if automatic updates via modifier fail due to too many pending epochs.
* @param numEpochsToProcess The number of pending epochs to process in this call.
*/
function manualProcessNodeStakeCache(uint48 numEpochsToProcess) external {
if (numEpochsToProcess == 0) {
revert AvalancheL1Middleware__NoEpochsToProcess();
}
uint48 currentEpoch = getCurrentEpoch();
uint48 epochsActuallyPending = 0;
if (currentEpoch > lastGlobalNodeStakeUpdateEpoch) {
epochsActuallyPending = currentEpoch - lastGlobalNodeStakeUpdateEpoch;
}
if (numEpochsToProcess > epochsActuallyPending) {
// Cap processing at what's actually pending to avoid processing non-existent future states.
if (epochsActuallyPending == 0) {
// Effectively, nothing to do, could emit an event or just succeed.
emit NodeStakeCacheManuallyProcessed(lastGlobalNodeStakeUpdateEpoch, 0);
return;
}
numEpochsToProcess = epochsActuallyPending;
}
uint48 epochsProcessedCount = 0;
for (uint48 i = 0; i < numEpochsToProcess; i++) {
if (lastGlobalNodeStakeUpdateEpoch >= currentEpoch) {
break; // Caught up
}
bool processed = _processSingleEpochNodeStakeCacheUpdate();
if (processed) {
epochsProcessedCount++;
} else {
// Should not happen if currentEpoch > lastGlobalNodeStakeUpdateEpoch initially
// and numEpochsToProcess is positive.
break;
}
}
emit NodeStakeCacheManuallyProcessed(lastGlobalNodeStakeUpdateEpoch, epochsProcessedCount);
}
/**
* @notice Caches manager-based stake for each node of `operator` in epoch `currentEpoch`.
* @param operator The operator address
*/
function _calcAndCacheNodeStakeForOperatorAtEpoch(address operator, uint48 epoch) internal {
uint48 prevEpoch = (epoch == 0) ? 0 : epoch - 1;
bytes32[] storage nodeArray = operatorNodesArray[operator];
for (uint256 i = nodeArray.length; i > 0;) {
i--;
bytes32 nodeId = nodeArray[i];
bytes32 valID = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
// If no removal/update, just carry over from prevEpoch (only if we haven’t set it yet)
if (!nodePendingRemoval[valID] && !nodePendingUpdate[valID]) {
if (nodeStakeCache[epoch][valID] == 0) {
nodeStakeCache[epoch][valID] = nodeStakeCache[prevEpoch][valID];
}
continue;
}
if (nodePendingRemoval[valID] && nodeStakeCache[epoch][valID] == 0 && nodeStakeCache[prevEpoch][valID] != 0)
{
_removeNodeFromArray(operator, nodeId);
nodePendingRemoval[valID] = false;
}
// If there was a pending update, finalize and clear the pending markers
if (nodePendingUpdate[valID]) {
nodePendingUpdate[valID] = false;
}
}
// Reset operator locked stake once per epoch
if (operatorLockedStake[operator] > 0) {
operatorLockedStake[operator] = 0;
}
}
/**
* @notice Remove a node => end its validator. Checks still to be done.
* @param nodeId The node ID
*/
function _removeNode(address operator, bytes32 nodeId) internal {
bytes32 validationID = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
_initializeEndValidationAndFlag(operator, validationID, nodeId);
}
function _initializeEndValidationAndFlag(address operator, bytes32 validationID, bytes32 nodeId) internal {
uint48 nextEpoch = getCurrentEpoch() + 1;
nodeStakeCache[nextEpoch][validationID] = 0;
nodePendingRemoval[validationID] = true;
balancerValidatorManager.initializeEndValidation(validationID);
emit NodeRemoved(operator, nodeId, validationID);
}
/**
* @notice Remove the node from the dynamic array (swap and pop).
* @param nodeId The node ID.
*/
function _removeNodeFromArray(address operator, bytes32 nodeId) internal {
bytes32[] storage nodesArr = operatorNodesArray[operator];
// Find the node index by looping (O(n)), then swap+pop
uint256 length = nodesArr.length;
for (uint256 i = 0; i < length; i++) {
if (nodesArr[i] == nodeId) {
uint256 lastIndex = length - 1;
if (i != lastIndex) {
nodesArr[i] = nodesArr[lastIndex];
}
nodesArr.pop();
break;
}
}
}
/**
* @notice Completes a validator's registration.
* @param operator The operator who owns the validator
* @param nodeId The unique ID of the validator whose registration is being finalized
* @param messageIndex The message index from the BalancerValidatorManager (used for ordering/verification)
*/
function _completeValidatorRegistration(
address operator,
bytes32 nodeId,
uint32 messageIndex
) internal onlyRegisteredOperatorNode(operator, nodeId) {
balancerValidatorManager.completeValidatorRegistration(messageIndex);
}
/**
* @notice Completes a validator's removal.
* @param messageIndex The message index from the BalancerValidatorManager (used for ordering/verification)
*/
function _completeValidatorRemoval(
uint32 messageIndex
) internal {
balancerValidatorManager.completeEndValidation(messageIndex);
}
/**
* @notice Completes a validator's stake update
* @param operator The operator who owns the validator
* @param nodeId The unique ID of the validator whose relative weight update is being finalized
* @param messageIndex The message index from the BalancerValidatorManager (used for ordering/verification)
*/
function _completeStakeUpdate(
address operator,
bytes32 nodeId,
uint32 messageIndex
) internal onlyRegisteredOperatorNode(operator, nodeId) {
bytes32 validationID = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
if (!balancerValidatorManager.isValidatorPendingWeightUpdate(validationID)) {
revert AvalancheL1Middleware__WeightUpdateNotPending(validationID);
}
// if the completeValidatorWeightUpdate fails, not sure if the previous bool is secure.
balancerValidatorManager.completeValidatorWeightUpdate(validationID, messageIndex);
}
/**
* @notice Sets the stake of a validator and updates the operator's locked stake accordingly.
* @param operator The operator who owns the validator
* @param validationID The unique ID of the validator whose stake is being updated
* @param newStake The new stake for the validator
* @dev When updating the relative weight of a validator, the operator's locked stake is increased or decreased
*/
function _initializeValidatorStakeUpdate(address operator, bytes32 validationID, uint256 newStake) internal {
uint48 currentEpoch = getCurrentEpoch();
uint256 cachedStake = getEffectiveNodeStake(currentEpoch, validationID);
if (balancerValidatorManager.isValidatorPendingWeightUpdate(validationID)) {
revert AvalancheL1Middleware__WeightUpdatePending(validationID);
}
uint256 delta;
if (newStake > cachedStake) {
delta = newStake - cachedStake;
if (delta > _getOperatorAvailableStake(operator)) {
revert AvalancheL1Middleware__NotEnoughFreeStake(newStake);
}
}
operatorLockedStake[operator] += delta;
nodePendingUpdate[validationID] = true;
nodeStakeCache[currentEpoch + 1][validationID] = newStake;
// if newStake < cachedStake, no lock should happen, it's locked in the cache
uint64 scaledWeight = StakeConversion.stakeToWeight(newStake, WEIGHT_SCALE_FACTOR);
balancerValidatorManager.initializeValidatorWeightUpdate(validationID, scaledWeight);
}
function _requireMinSecondaryAssetClasses(uint256 extraNode, address operator) internal view returns (bool) {
uint48 epoch = getCurrentEpoch();
uint256 nodeCount = operatorNodesArray[operator].length; // existing nodes
uint256 secCount = secondaryAssetClasses.length();
if (secCount == 0) {
return true;
}
for (uint256 i = 0; i < secCount; i++) {
uint256 classId = secondaryAssetClasses.at(i);
uint256 stake = getOperatorStake(operator, epoch, uint96(classId));
// Check ratio vs. class's min stake, could add an emit here to debug
if (stake / (nodeCount + extraNode) < assetClasses[classId].minValidatorStake) {
return false;
}
}
return true;
}
/**
* @notice Checks if the classId is active
* @param assetClassId The asset class ID
* @return bool True if active
*/
function _isActiveAssetClass(
uint256 assetClassId
) internal view returns (bool) {
return (assetClassId == PRIMARY_ASSET_CLASS || secondaryAssetClasses.contains(assetClassId));
}
/**
* @notice Checks if the asset is still in use by a vault
* @param assetClassId The asset class ID
* @param asset The asset address
* @return bool True if in use by any vault
*/
function _isUsedAsset(uint256 assetClassId, address asset) internal view returns (bool) {
for (uint256 i; i < vaultManager.getVaultCount(); ++i) {
(address vault,,) = vaultManager.getVaultAtWithTimes(i);
if (vaultManager.vaultToAssetClass(vault) == assetClassId && IVaultTokenized(vault).collateral() == asset) {
return true;
}
}
return false;
}
/**
* @notice Checks if the asset class is still in use by a vault
* @param assetClassId The asset class ID
* @return bool True if in use by any vault
*/
function _isUsedAssetClass(
uint256 assetClassId
) internal view returns (bool) {
for (uint256 i; i < vaultManager.getVaultCount(); ++i) {
(address vault,,) = vaultManager.getVaultAtWithTimes(i);
if (vaultManager.vaultToAssetClass(vault) == assetClassId) {
return true;
}
}
return false;
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getActiveAssetClasses() external view returns (uint256 primary, uint256[] memory secondaries) {
primary = PRIMARY_ASSET_CLASS;
secondaries = secondaryAssetClasses.values();
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getEpochStartTs(
uint48 epoch
) public view returns (uint48 timestamp) {
return START_TIME + epoch * EPOCH_DURATION;
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getEpochAtTs(
uint48 timestamp
) public view returns (uint48 epoch) {
return (timestamp - START_TIME) / EPOCH_DURATION;
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getCurrentEpoch() public view returns (uint48 epoch) {
return getEpochAtTs(Time.timestamp());
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getOperatorStake(
address operator,
uint48 epoch,
uint96 assetClassId
) public view returns (uint256 stake) {
if (totalStakeCached[epoch][assetClassId]) {
uint256 cachedStake = operatorStakeCache[epoch][assetClassId][operator];
return cachedStake;
}
uint48 epochStartTs = getEpochStartTs(epoch);
uint256 totalVaults = vaultManager.getVaultCount();
for (uint256 i; i < totalVaults; ++i) {
(address vault, uint48 enabledTime, uint48 disabledTime) = vaultManager.getVaultAtWithTimes(i);
// Skip if vault not active in the target epoch
if (!_wasActiveAt(enabledTime, disabledTime, epochStartTs)) {
continue;
}
// Skip if vault asset not in AssetClassID
if (vaultManager.getVaultAssetClass(vault) != assetClassId) {
continue;
}
uint256 vaultStake = BaseDelegator(IVaultTokenized(vault).delegator()).stakeAt(
L1_VALIDATOR_MANAGER, assetClassId, operator, epochStartTs, new bytes(0)
);
stake += vaultStake;
}
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getTotalStake(uint48 epoch, uint96 assetClassId) public view returns (uint256) {
if (totalStakeCached[epoch][assetClassId]) {
return totalStakeCache[epoch][assetClassId];
}
return _calcTotalStake(epoch, assetClassId);
}
function getOperatorNodesLength(
address operator
) public view returns (uint256) {
return operatorNodesArray[operator].length;
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getAllOperators() external view returns (address[] memory) {
uint256 length = operators.length();
address[] memory result = new address[](length);
for (uint256 i; i < length; i++) {
(address operator,,) = operators.atWithTimes(i);
result[i] = operator;
}
return result;
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getNodeStake(uint48 epoch, bytes32 validationID) external view returns (uint256) {
return nodeStakeCache[epoch][validationID];
}
function isActiveAssetClass(
uint96 assetClassId
) external view returns (bool) {
return _isActiveAssetClass(assetClassId);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getActiveNodesForEpoch(
address operator,
uint48 epoch
) external view returns (bytes32[] memory activeNodeIds) {
uint48 epochStartTs = getEpochStartTs(epoch);
// Gather all nodes from the never-removed set
bytes32[] memory allNodeIds = operatorNodes[operator].values();
bytes32[] memory temp = new bytes32[](allNodeIds.length);
uint256 activeCount;
for (uint256 i = 0; i < allNodeIds.length; i++) {
bytes32 nodeId = allNodeIds[i];
bytes32 validationID =
balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
Validator memory validator = balancerValidatorManager.getValidator(validationID);
if (_wasActiveAt(uint48(validator.startedAt), uint48(validator.endedAt), epochStartTs)) {
temp[activeCount++] = nodeId;
}
}
activeNodeIds = new bytes32[](activeCount);
for (uint256 j = 0; j < activeCount; j++) {
activeNodeIds[j] = temp[j];
}
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getOperatorAvailableStake(
address operator
) external view returns (uint256) {
return _getOperatorAvailableStake(operator);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getVaultManager() external view returns (address) {
return address(vaultManager);
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getOperatorUsedStakeCached(
address operator
) public view returns (uint256 registeredStake) {
bytes32[] storage nodesArr = operatorNodesArray[operator];
for (uint256 i = 0; i < nodesArr.length; i++) {
bytes32 nodeId = nodesArr[i];
bytes32 validationID =
balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
registeredStake += getEffectiveNodeStake(getCurrentEpoch(), validationID);
}
}
/**
* @notice Gets the effective stake for a specific ValidationID.
* @param epoch The epoch number
* @param validationID The validation ID
*/
function getEffectiveNodeStake(uint48 epoch, bytes32 validationID) internal view returns (uint256) {
return nodeStakeCache[epoch][validationID];
}
/**
* @inheritdoc IAvalancheL1Middleware
*/
function getOperatorUsedStakeCachedPerEpoch(
uint48 epoch,
address operator,
uint96 assetClass
) external view returns (uint256) {
if (assetClass == PRIMARY_ASSET_CLASS) {
bytes32[] memory nodesArr = this.getActiveNodesForEpoch(operator, epoch);
uint256 operatorStake = 0;
for (uint256 i = 0; i < nodesArr.length; i++) {
bytes32 nodeId = nodesArr[i];
bytes32 validationID =
balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
operatorStake += getEffectiveNodeStake(epoch, validationID);
}
return operatorStake;
} else {
return getOperatorStake(operator, epoch, assetClass);
}
}
/**
* @notice Get the validator per ValidationID.
* @param validationID The validation ID.
*/
function _getValidator(
bytes32 validationID
) internal view returns (Validator memory) {
return balancerValidatorManager.getValidator(validationID);
}
/**
* @notice Returns the available stake for an operator
* @param operator The operator address
* @return The available stake
*/
function _getOperatorAvailableStake(
address operator
) internal view returns (uint256) {
uint48 epoch = getCurrentEpoch();
uint256 totalStake = getOperatorStake(operator, epoch, PRIMARY_ASSET_CLASS);
// Enforce max security module weight
(, uint64 securityModuleMaxWeight) = balancerValidatorManager.getSecurityModuleWeights(address(this));
uint256 convertedSecurityModuleMaxWeight =
StakeConversion.weightToStake(securityModuleMaxWeight, WEIGHT_SCALE_FACTOR);
if (totalStake > convertedSecurityModuleMaxWeight) {
totalStake = convertedSecurityModuleMaxWeight;
}
uint256 lockedStake = operatorLockedStake[operator];
if (totalStake <= lockedStake) {
return 0;
}
return totalStake - lockedStake;
}
/**
* @notice Helper to calculate total stake for an epoch
* @param epoch The epoch number
* @param assetClassId The asset class ID
* @return totalStake The total stake across all operators
*/
function _calcTotalStake(uint48 epoch, uint96 assetClassId) private view returns (uint256 totalStake) {
uint48 epochStartTs = getEpochStartTs(epoch);
// for epoch older than SLASHING_WINDOW total stake can be invalidated (use cache)
if (epochStartTs > Time.timestamp() || epochStartTs < Time.timestamp() - SLASHING_WINDOW) {
revert AvalancheL1Middleware__EpochError(epochStartTs);
}
uint256 length = operators.length();
for (uint256 i; i < length; ++i) {
(address operator, uint48 enabledTime, uint48 disabledTime) = operators.atWithTimes(i);
// just skip operator if it was added after the target epoch or paused
if (!_wasActiveAt(enabledTime, disabledTime, epochStartTs)) {
continue;
}
uint256 operatorStake = getOperatorStake(operator, epoch, assetClassId);
totalStake += operatorStake;
}
}
/**
* @notice Checks if an operator or vault was active at a specific timestamp
* @param enabledTime The time it was enabled
* @param disabledTime The time it was disabled
* @param timestamp The timestamp to check
* @return bool True if active
*/
function _wasActiveAt(uint48 enabledTime, uint48 disabledTime, uint48 timestamp) private pure returns (bool) {
return enabledTime != 0 && enabledTime <= timestamp && (disabledTime == 0 || disabledTime >= timestamp);
}
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
import {Time} from "@openzeppelin/contracts/utils/types/Time.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import {IMiddlewareVaultManager} from "../../interfaces/middleware/IMiddlewareVaultManager.sol";
import {IAvalancheL1Middleware} from "../../interfaces/middleware/IAvalancheL1Middleware.sol";
import {IRegistry} from "../../interfaces/common/IRegistry.sol";
import {IEntity} from "../../interfaces/common/IEntity.sol";
import {IVaultTokenized} from "../../interfaces/vault/IVaultTokenized.sol";
import {BaseDelegator} from "../../contracts/delegator/BaseDelegator.sol";
import {ISlasher} from "../../interfaces/slasher/ISlasher.sol";
import {IVetoSlasher} from "../../interfaces/slasher/IVetoSlasher.sol";
import {MapWithTimeData} from "./libraries/MapWithTimeData.sol";
import {AvalancheL1Middleware} from "./AvalancheL1Middleware.sol";
contract MiddlewareVaultManager is IMiddlewareVaultManager, Ownable {
using EnumerableMap for EnumerableMap.AddressToUintMap;
using MapWithTimeData for EnumerableMap.AddressToUintMap;
mapping(address => uint96) public vaultToAssetClass;
EnumerableMap.AddressToUintMap private vaults;
address public immutable VAULT_REGISTRY;
AvalancheL1Middleware public immutable middleware;
uint48 private constant INSTANT_SLASHER_TYPE = 0;
uint48 private constant VETO_SLASHER_TYPE = 1;
constructor(address vaultRegistry, address owner, address middlewareAddress) Ownable(owner) {
if (vaultRegistry == address(0)) {
revert AvalancheL1Middleware__ZeroAddress("vaultRegistry");
}
if (middlewareAddress == address(0)) {
revert AvalancheL1Middleware__ZeroAddress("middlewareAddress");
}
VAULT_REGISTRY = vaultRegistry;
middleware = AvalancheL1Middleware(payable(middlewareAddress));
}
/**
* @notice Registers a vault to a specific asset class, sets the max stake.
* @param vault The vault address
* @param assetClassId The asset class ID for that vault
* @param vaultMaxL1Limit The maximum stake allowed for this vault
*/
function registerVault(address vault, uint96 assetClassId, uint256 vaultMaxL1Limit) external onlyOwner {
if (vaultMaxL1Limit == 0) {
revert AvalancheL1Middleware__ZeroVaultMaxL1Limit();
}
if (vaults.contains(vault)) {
revert AvalancheL1Middleware__VaultAlreadyRegistered();
}
uint48 vaultEpoch = IVaultTokenized(vault).epochDuration();
address slasher = IVaultTokenized(vault).slasher();
if (slasher != address(0) && IEntity(slasher).TYPE() == VETO_SLASHER_TYPE) {
vaultEpoch -= IVetoSlasher(slasher).vetoDuration();
}
if (vaultEpoch < middleware.SLASHING_WINDOW()) {
revert AvalancheL1Middleware__VaultEpochTooShort();
}
vaultToAssetClass[vault] = assetClassId;
_setVaultMaxL1Limit(vault, assetClassId, vaultMaxL1Limit);
vaults.add(vault);
vaults.enable(vault);
}
/**
* @notice Updates a vault's max L1 stake limit. Disables or enables the vault based on the new limit
* @param vault The vault address
* @param assetClassId The asset class ID
* @param vaultMaxL1Limit The new maximum stake
*/
function updateVaultMaxL1Limit(address vault, uint96 assetClassId, uint256 vaultMaxL1Limit) external onlyOwner {
if (!vaults.contains(vault)) {
revert AvalancheL1Middleware__NotVault(vault);
}
if (vaultToAssetClass[vault] != assetClassId) {
revert AvalancheL1Middleware__WrongVaultAssetClass();
}
_setVaultMaxL1Limit(vault, assetClassId, vaultMaxL1Limit);
if (vaultMaxL1Limit == 0) {
vaults.disable(vault);
} else {
vaults.enable(vault);
}
}
/**
* @notice Removes a vault if the grace period has passed
* @param vault The vault address
*/
function removeVault(
address vault
) external onlyOwner {
if (!vaults.contains(vault)) {
revert AvalancheL1Middleware__NotVault(vault);
}
(, uint48 disabledTime) = vaults.getTimes(vault);
if (disabledTime == 0) {
revert AvalancheL1Middleware__VaultNotDisabled();
}
if (disabledTime + middleware.SLASHING_WINDOW() > Time.timestamp()) {
revert AvalancheL1Middleware__VaultGracePeriodNotPassed();
}
// Remove from vaults and clear mapping
vaults.remove(vault);
delete vaultToAssetClass[vault];
}
/**
* @notice Sets a vault's max L1 stake limit
* @param vault The vault address
* @param assetClassId The asset class ID
* @param amount The new maximum stake
*/
function _setVaultMaxL1Limit(address vault, uint96 assetClassId, uint256 amount) internal onlyOwner {
if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) {
revert AvalancheL1Middleware__NotVault(vault);
}
if (!middleware.isActiveAssetClass(assetClassId)) {
revert IAvalancheL1Middleware.AvalancheL1Middleware__AssetClassNotActive(assetClassId);
}
address vaultCollateral = IVaultTokenized(vault).collateral();
if (!middleware.isAssetInClass(assetClassId, vaultCollateral)) {
revert IAvalancheL1Middleware.AvalancheL1Middleware__CollateralNotInAssetClass(
vaultCollateral, assetClassId
);
}
address delegator = IVaultTokenized(vault).delegator();
BaseDelegator(delegator).setMaxL1Limit(middleware.L1_VALIDATOR_MANAGER(), assetClassId, amount);
}
function slashVault() external pure {
revert AvalancheL1Middleware__SlasherNotImplemented();
}
function getVaultCount() external view returns (uint256) {
return vaults.length();
}
function getVaultAtWithTimes(
uint256 index
) external view returns (address vault, uint48 enabledTime, uint48 disabledTime) {
return vaults.atWithTimes(index);
}
function getVaultAssetClass(
address vault
) external view returns (uint96) {
return vaultToAssetClass[vault];
}
function getVaults(
uint48 epoch
) external view returns (address[] memory) {
uint256 vaultCount = vaults.length();
uint48 epochStart = middleware.getEpochStartTs(epoch);
uint256 activeCount = 0;
for (uint256 i = 0; i < vaultCount; i++) {
(address vault, uint48 enabledTime, uint48 disabledTime) = vaults.atWithTimes(i);
if (_wasActiveAt(enabledTime, disabledTime, epochStart)) {
activeCount++;
}
}
address[] memory activeVaults = new address[](activeCount);
uint256 activeIndex = 0;
for (uint256 i = 0; i < vaultCount; i++) {
(address vault, uint48 enabledTime, uint48 disabledTime) = vaults.atWithTimes(i);
if (_wasActiveAt(enabledTime, disabledTime, epochStart)) {
activeVaults[activeIndex] = vault;
activeIndex++;
}
}
return activeVaults;
}
function _wasActiveAt(uint48 enabledTime, uint48 disabledTime, uint48 timestamp) private pure returns (bool) {
return enabledTime != 0 && enabledTime <= timestamp && (disabledTime == 0 || disabledTime >= timestamp);
}
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
import {AvalancheL1Middleware} from "../middleware/AvalancheL1Middleware.sol";
import {IUptimeTracker, LastUptimeCheckpoint} from "../../interfaces/rewards/IUptimeTracker.sol";
import {BalancerValidatorManager} from
"@suzaku/contracts-library/contracts/ValidatorManager/BalancerValidatorManager.sol";
import {Validator} from "@avalabs/icm-contracts/validator-manager/interfaces/IValidatorManager.sol";
import {ValidatorMessages} from "@avalabs/icm-contracts/validator-manager/ValidatorMessages.sol";
import {
IWarpMessenger, WarpMessage
} from "@avalabs/[email protected]/contracts/interfaces/IWarpMessenger.sol";
/**
* @title UptimeTracker
* @dev Tracks validator uptime and calculates uptime percentages per epoch.
* Used to monitor validator and operator performance.
*/
contract UptimeTracker is IUptimeTracker {
uint48 private immutable epochDuration;
AvalancheL1Middleware private immutable l1Middleware;
BalancerValidatorManager private immutable validatorManager;
bytes32 private immutable l1ChainID;
IWarpMessenger public constant WARP_MESSENGER = IWarpMessenger(0x0200000000000000000000000000000000000005);
/// @notice Mapping of validation ID to the last recorded uptime checkpoint.
mapping(bytes32 validationID => LastUptimeCheckpoint lastUptimeCheckpoint) public validatorLastUptimeCheckpoint;
/// @notice Mapping of epoch to validator uptime (in seconds).
mapping(uint48 epoch => mapping(bytes32 validationID => uint256 uptime)) public validatorUptimePerEpoch;
/// @notice Mapping of epoch to validator uptime set.
mapping(uint48 epoch => mapping(bytes32 validationID => bool isSet)) public isValidatorUptimeSet;
/// @notice Mapping of epoch to operator uptime (in seconds).
mapping(uint48 epoch => mapping(address operator => uint256 uptime)) public operatorUptimePerEpoch;
/// @notice Mapping of epoch to operator uptime set.
mapping(uint48 epoch => mapping(address operator => bool isSet)) public isOperatorUptimeSet;
constructor(
address payable l1Middleware_,
bytes32 l1ChainID_
) {
l1Middleware = AvalancheL1Middleware(l1Middleware_);
epochDuration = l1Middleware.EPOCH_DURATION();
validatorManager = BalancerValidatorManager(l1Middleware.L1_VALIDATOR_MANAGER());
l1ChainID = l1ChainID_;
}
/**
* @inheritdoc IUptimeTracker
*/
function computeValidatorUptime(
uint32 messageIndex
) external {
// Get warp message directly
(WarpMessage memory warpMessage, bool valid) = WARP_MESSENGER.getVerifiedWarpMessage(messageIndex);
if (!valid) {
revert InvalidWarpMessage();
}
// Must match to P-Chain blockchain id
if (warpMessage.sourceChainID != l1ChainID) {
revert InvalidWarpSourceChainID(warpMessage.sourceChainID);
}
if (warpMessage.originSenderAddress != address(0)) {
revert InvalidWarpOriginSenderAddress(warpMessage.originSenderAddress);
}
// Unpack the uptime message
(bytes32 validationID, uint256 uptime) = ValidatorMessages.unpackValidationUptimeMessage(warpMessage.payload);
LastUptimeCheckpoint storage lastUptimeCheckpoint = validatorLastUptimeCheckpoint[validationID];
// No timestamp means no initial checkpoint
if (lastUptimeCheckpoint.timestamp == 0) {
// Get validator details
Validator memory validator = validatorManager.getValidator(validationID);
validatorLastUptimeCheckpoint[validationID] =
LastUptimeCheckpoint({remainingUptime: 0, attributedUptime: 0, timestamp: validator.startedAt});
// Refresh the reference to the updated struct
lastUptimeCheckpoint = validatorLastUptimeCheckpoint[validationID];
}
// Get last checkpoint epoch start
uint48 lastUptimeEpoch = l1Middleware.getEpochAtTs(uint48(lastUptimeCheckpoint.timestamp));
uint256 lastUptimeEpochStart = l1Middleware.getEpochStartTs(lastUptimeEpoch);
// Get current epoch start
uint48 currentEpoch = l1Middleware.getEpochAtTs(uint48(block.timestamp));
uint256 currentEpochStart = l1Middleware.getEpochStartTs(currentEpoch);
// Calculate the recorded uptime since the last checkpoint
uint256 recordedUptime = lastUptimeCheckpoint.remainingUptime + (uptime - lastUptimeCheckpoint.attributedUptime);
// Calculate the elapsed time between the last recorded epoch and the current epoch
uint256 elapsedTime = currentEpochStart - lastUptimeEpochStart;
// Determine how many full epochs have passed
uint256 elapsedEpochs = elapsedTime / epochDuration;
uint256 remainingUptime = recordedUptime > elapsedTime ? recordedUptime - elapsedTime : 0;
// The uptime to distribute across the elapsed epochs
uint256 uptimeToDistribute = recordedUptime - remainingUptime;
// If the recorded uptime is greater than the elapsed time, carry over the excess uptime else reset it
validatorLastUptimeCheckpoint[validationID] = LastUptimeCheckpoint({
remainingUptime: remainingUptime, // Store the leftover uptime for future epochs if any
attributedUptime: uptime, // Update the last recorded uptime
timestamp: currentEpochStart // Move the checkpoint forward
});
// Distribute the recorded uptime across multiple epochs
if (elapsedEpochs >= 1) {
uint256 uptimePerEpoch = uptimeToDistribute / elapsedEpochs;
for (uint48 i = 0; i < elapsedEpochs; i++) {
uint48 epoch = lastUptimeEpoch + i;
if (isValidatorUptimeSet[epoch][validationID] == true) {
break;
}
validatorUptimePerEpoch[epoch][validationID] = uptimePerEpoch; // Assign uptime to each epoch
isValidatorUptimeSet[epoch][validationID] = true; // Mark uptime as set for the epoch
}
}
emit ValidatorUptimeComputed(validationID, lastUptimeEpoch, uptimeToDistribute, elapsedEpochs);
}
/**
* @inheritdoc IUptimeTracker
*/
function computeOperatorUptimeAt(address operator, uint48 epoch) external {
bytes32[] memory operatorNodes = l1Middleware.getActiveNodesForEpoch(operator, epoch);
uint256 numberOfValidators = operatorNodes.length;
if (numberOfValidators == 0) revert UptimeTracker__NoValidators(operator, epoch);
uint256 sumValidatorsUptime = 0;
for (uint256 i = 0; i < numberOfValidators; i++) {
bytes32 validationID = validatorManager.registeredValidators(abi.encodePacked(uint160(uint256(operatorNodes[i]))));
if (isValidatorUptimeSet[epoch][validationID] == false) {
revert UptimeTracker__ValidatorUptimeNotRecorded(epoch, validationID);
}
uint256 uptimeValidator = validatorUptimePerEpoch[epoch][validationID];
sumValidatorsUptime += uptimeValidator;
}
operatorUptimePerEpoch[epoch][operator] = sumValidatorsUptime / numberOfValidators;
isOperatorUptimeSet[epoch][operator] = true;
emit OperatorUptimeComputed(operator, epoch, sumValidatorsUptime / numberOfValidators);
}
/**
* @inheritdoc IUptimeTracker
*/
function getLastUptimeCheckpoint(
bytes32 validationID
) external view returns (LastUptimeCheckpoint memory) {
return validatorLastUptimeCheckpoint[validationID];
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js.
pragma solidity ^0.8.20;
import {EnumerableSet} from "./EnumerableSet.sol";
/**
* @dev Library for managing an enumerable variant of Solidity's
* https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
* type.
*
* Maps have the following properties:
*
* - Entries are added, removed, and checked for existence in constant time
* (O(1)).
* - Entries are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableMap for EnumerableMap.UintToAddressMap;
*
* // Declare a set state variable
* EnumerableMap.UintToAddressMap private myMap;
* }
* ```
*
* The following map types are supported:
*
* - `uint256 -> address` (`UintToAddressMap`) since v3.0.0
* - `address -> uint256` (`AddressToUintMap`) since v4.6.0
* - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0
* - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
* - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.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 EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableMap.
* ====
*/
library EnumerableMap {
using EnumerableSet for EnumerableSet.Bytes32Set;
// To implement this library for multiple types with as little code repetition as possible, we write it in
// terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
// and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map.
// This means that we can only create new EnumerableMaps for types that fit in bytes32.
/**
* @dev Query for a nonexistent map key.
*/
error EnumerableMapNonexistentKey(bytes32 key);
struct Bytes32ToBytes32Map {
// Storage of keys
EnumerableSet.Bytes32Set _keys;
mapping(bytes32 key => bytes32) _values;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) {
map._values[key] = value;
return map._keys.add(key);
}
/**
* @dev Removes a key-value pair from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) {
delete map._values[key];
return map._keys.remove(key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) {
return map._keys.contains(key);
}
/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) {
return map._keys.length();
}
/**
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) {
bytes32 key = map._keys.at(index);
return (key, map._values[key]);
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) {
bytes32 value = map._values[key];
if (value == bytes32(0)) {
return (contains(map, key), bytes32(0));
} else {
return (true, value);
}
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) {
bytes32 value = map._values[key];
if (value == 0 && !contains(map, key)) {
revert EnumerableMapNonexistentKey(key);
}
return value;
}
/**
* @dev Return the an array containing all the keys
*
* 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 map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
return map._keys.values();
}
// UintToUintMap
struct UintToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) {
return set(map._inner, bytes32(key), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToUintMap storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. 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(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(key)));
}
/**
* @dev Return the an array containing all the keys
*
* 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 map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToUintMap storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintToAddressMap
struct UintToAddressMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
return set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToAddressMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. 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(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), address(uint160(uint256(value))));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, address(uint160(uint256(value))));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
return address(uint160(uint256(get(map._inner, bytes32(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* 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 map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressToUintMap
struct AddressToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) {
return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(AddressToUintMap storage map, address key) internal returns (bool) {
return remove(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(AddressToUintMap storage map, address key) internal view returns (bool) {
return contains(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(AddressToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. 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(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (address(uint160(uint256(key))), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(AddressToUintMap storage map, address key) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(uint256(uint160(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* 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 map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(AddressToUintMap storage map) internal view returns (address[] memory) {
bytes32[] memory store = keys(map._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// Bytes32ToUintMap
struct Bytes32ToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) {
return set(map._inner, key, bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) {
return remove(map._inner, key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) {
return contains(map._inner, key);
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(Bytes32ToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. 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(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (key, uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, key);
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) {
return uint256(get(map._inner, key));
}
/**
* @dev Return the an array containing all the keys
*
* 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 map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) {
bytes32[] memory store = keys(map._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @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.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [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 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;
}
// 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 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;
/// @solidity memory-safe-assembly
assembly {
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 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;
/// @solidity memory-safe-assembly
assembly {
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 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;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 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 {
using Address for address;
/**
* @dev An operation with an ERC20 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 {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @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 {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
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.
*/
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.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), 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 data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), 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 data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControl, ERC165Upgradeable {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControl
struct AccessControlStorage {
mapping(bytes32 role => RoleData) _roles;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;
function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
assembly {
$.slot := AccessControlStorageLocation
}
}
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
function __AccessControl_init() internal onlyInitializing {
}
function __AccessControl_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
return $._roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
AccessControlStorage storage $ = _getAccessControlStorage();
return $._roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
AccessControlStorage storage $ = _getAccessControlStorage();
bytes32 previousAdminRole = getRoleAdmin(role);
$._roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
if (!hasRole(role, account)) {
$._roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
if (hasRole(role, account)) {
$._roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {IBaseDelegator} from "../../interfaces/delegator/IBaseDelegator.sol";
import {IDelegatorHook} from "../../interfaces/delegator/IDelegatorHook.sol";
import {IOptInService} from "../../interfaces/service/IOptInService.sol";
import {IRegistry} from "../../interfaces/common/IRegistry.sol";
import {IVaultTokenized} from "../../interfaces/vault/IVaultTokenized.sol";
import {IVaultFactory} from "../../interfaces/IVaultFactory.sol";
import {IL1Registry} from "../../interfaces/IL1Registry.sol";
import {IEntity} from "../../interfaces/common/IEntity.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
abstract contract BaseDelegator is AccessControlUpgradeable, ReentrancyGuardUpgradeable, IBaseDelegator, ERC165 {
using ERC165Checker for address;
/**
* @inheritdoc IBaseDelegator
*/
address public immutable FACTORY;
/**
* @inheritdoc IBaseDelegator
*/
uint64 public immutable TYPE;
/**
* @inheritdoc IBaseDelegator
*/
uint256 public constant HOOK_GAS_LIMIT = 250_000;
/**
* @inheritdoc IBaseDelegator
*/
uint256 public constant HOOK_RESERVE = 20_000;
/**
* @inheritdoc IBaseDelegator
*/
bytes32 public constant HOOK_SET_ROLE = keccak256("HOOK_SET_ROLE");
/**
* @inheritdoc IBaseDelegator
*/
address public immutable L1_REGISTRY;
/**
* @inheritdoc IBaseDelegator
*/
address public immutable VAULT_FACTORY;
/**
* @inheritdoc IBaseDelegator
*/
address public immutable OPERATOR_VAULT_OPT_IN_SERVICE;
/**
* @inheritdoc IBaseDelegator
*/
address public immutable OPERATOR_L1_OPT_IN_SERVICE;
/**
* @inheritdoc IBaseDelegator
*/
address public vault;
/**
* @inheritdoc IBaseDelegator
*/
address public hook;
/**
* @inheritdoc IBaseDelegator
*/
mapping(address => mapping(uint96 => uint256)) public maxL1Limit;
constructor(
address l1Registry,
address vaultFactory,
address operatorVaultOptInService,
address operatorL1OptInService,
address delegatorFactory,
uint64 entityType
) {
if (l1Registry == address(0)) {
revert BaseDelegator__ZeroAddress("l1Registry");
}
if (vaultFactory == address(0)) {
revert BaseDelegator__ZeroAddress("vaultFactory");
}
if (operatorVaultOptInService == address(0)) {
revert BaseDelegator__ZeroAddress("operatorVaultOptInService");
}
if (operatorL1OptInService == address(0)) {
revert BaseDelegator__ZeroAddress("operatorL1OptInService");
}
if (delegatorFactory == address(0)) {
revert BaseDelegator__ZeroAddress("delegatorFactory");
}
_disableInitializers();
L1_REGISTRY = l1Registry;
VAULT_FACTORY = vaultFactory;
OPERATOR_VAULT_OPT_IN_SERVICE = operatorVaultOptInService;
OPERATOR_L1_OPT_IN_SERVICE = operatorL1OptInService;
FACTORY = delegatorFactory;
TYPE = entityType;
}
/**
* @inheritdoc IBaseDelegator
*/
function VERSION() external pure returns (uint64) {
return 1;
}
/**
* @inheritdoc IBaseDelegator
*/
function stakeAt(
address l1,
uint96 assetClass,
address operator,
uint48 timestamp,
bytes memory hints
) public view returns (uint256) {
(uint256 stake_, bytes memory baseHints) = _stakeAt(l1, assetClass, operator, timestamp, hints);
StakeBaseHints memory stakeBaseHints;
if (baseHints.length > 0) {
stakeBaseHints = abi.decode(baseHints, (StakeBaseHints));
}
if (
stake_ == 0
|| !IOptInService(OPERATOR_VAULT_OPT_IN_SERVICE).isOptedInAt(
operator, vault, timestamp, stakeBaseHints.operatorVaultOptInHint
)
|| !IOptInService(OPERATOR_L1_OPT_IN_SERVICE).isOptedInAt(
operator, l1, timestamp, stakeBaseHints.operatorL1OptInHint
)
) {
return 0;
}
return stake_;
}
/**
* @inheritdoc IBaseDelegator
*/
function stake(address l1, uint96 assetClass, address operator) external view returns (uint256) {
if (
!IOptInService(OPERATOR_VAULT_OPT_IN_SERVICE).isOptedIn(operator, vault)
|| !IOptInService(OPERATOR_L1_OPT_IN_SERVICE).isOptedIn(operator, l1)
) {
return 0;
}
return _stake(l1, assetClass, operator);
}
/**
* @inheritdoc IBaseDelegator
*/
function setMaxL1Limit(address l1, uint96 assetClass, uint256 amount) external nonReentrant {
if (!IL1Registry(L1_REGISTRY).isRegistered(l1)) {
revert BaseDelegator__NotL1();
}
// Check if the middleware is registered and matches the caller
bool isRegisteredWithMiddleware = IL1Registry(L1_REGISTRY).isRegisteredWithMiddleware(l1, msg.sender);
if (!isRegisteredWithMiddleware) {
revert BaseDelegator__NotAuthorizedMiddleware();
}
uint256 currentLimit = maxL1Limit[l1][assetClass];
if (currentLimit == amount) {
revert BaseDelegator__AlreadySet();
}
maxL1Limit[l1][assetClass] = amount;
_setMaxL1Limit(l1, assetClass, amount);
emit SetMaxL1Limit(l1, assetClass, amount);
}
/**
* @inheritdoc IBaseDelegator
*/
function setHook(
address hook_
) external nonReentrant onlyRole(HOOK_SET_ROLE) {
if (hook == hook_) {
revert BaseDelegator__AlreadySet();
}
hook = hook_;
emit SetHook(hook_);
}
/**
* @inheritdoc IBaseDelegator
*/
function onSlash(
address l1,
uint96 assetClass,
address operator,
uint256 amount,
uint48 captureTimestamp,
bytes memory data
) external nonReentrant {
if (msg.sender != IVaultTokenized(vault).slasher()) {
revert IVaultTokenized.Vault__NotSlasher();
}
address hook_ = hook;
if (hook_ != address(0)) {
bytes memory calldata_ =
abi.encodeCall(IDelegatorHook.onSlash, (l1, assetClass, operator, amount, captureTimestamp, data));
if (gasleft() < HOOK_RESERVE + HOOK_GAS_LIMIT * 64 / 63) {
revert BaseDelegator__InsufficientHookGas();
}
assembly ("memory-safe") {
pop(call(HOOK_GAS_LIMIT, hook_, 0, add(calldata_, 0x20), mload(calldata_), 0, 0))
}
}
emit OnSlash(l1, assetClass, operator, amount, captureTimestamp);
}
/**
* @inheritdoc IBaseDelegator
*/
function initialize(
bytes calldata data
) external initializer {
_initialize(data);
}
function _initialize(
bytes calldata data
) internal {
(address vault_, bytes memory data_) = abi.decode(data, (address, bytes));
if (!IRegistry(VAULT_FACTORY).isEntity(vault_)) {
revert BaseDelegator__NotVault();
}
__AccessControl_init();
__ReentrancyGuard_init();
vault = vault_;
BaseParams memory baseParams = __initialize(vault_, data_);
hook = baseParams.hook;
if (baseParams.defaultAdminRoleHolder != address(0)) {
_grantRole(DEFAULT_ADMIN_ROLE, baseParams.defaultAdminRoleHolder);
}
if (baseParams.hookSetRoleHolder != address(0)) {
_grantRole(HOOK_SET_ROLE, baseParams.hookSetRoleHolder);
}
}
function _stakeAt(
address l1,
uint96 assetClass,
address operator,
uint48 timestamp,
bytes memory hints
) internal view virtual returns (uint256, bytes memory);
function _stake(address l1, uint96 assetClass, address operator) internal view virtual returns (uint256);
function _setMaxL1Limit(address l1, uint96 assetClass, uint256 amount) internal virtual;
function __initialize(address vault_, bytes memory data) internal virtual returns (BaseParams memory);
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC165, AccessControlUpgradeable) returns (bool) {
return interfaceId == type(IEntity).interfaceId || super.supportsInterface(interfaceId);
}
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
// import {IMigratableEntity} from "../common/IMigratableEntity.sol";
interface IVaultTokenized {
error Vault__AlreadyClaimed();
error Vault__AlreadySet();
error Vault__DelegatorAlreadyInitialized();
error Vault__DepositLimitReached();
error Vault__InsufficientClaim();
error Vault__InsufficientDeposit();
error Vault__InsufficientRedemption();
error Vault__InsufficientWithdrawal();
error Vault__InvalidAccount();
error Vault__InvalidCaptureEpoch();
error Vault__InvalidClaimer();
error Vault__InvalidCollateral();
error Vault__InvalidDelegator();
error Vault__InvalidEpoch();
error Vault__InvalidEpochDuration();
error Vault__InvalidLengthEpochs();
error Vault__InvalidOnBehalfOf();
error Vault__InvalidRecipient();
error Vault__InvalidSlasher();
error Vault__MissingRoles();
error Vault__InconsistentRoles();
error Vault__NotDelegator();
error Vault__NotSlasher();
error Vault__NotWhitelistedDepositor();
error Vault__SlasherAlreadyInitialized();
error Vault__TooMuchRedeem();
error Vault__TooMuchWithdraw();
error Vault__InvalidTimestamp();
error Vault__NoPreviousEpoch();
error Vault__MigrationNotImplemented();
error Vault__InvalidFactory();
// Structs
/**
* @notice Initial parameters needed for a vault deployment.
* @param collateral vault's underlying collateral
* @param burner vault's burner to issue debt to (e.g., 0xdEaD or some unwrapper contract)
* @param epochDuration duration of the vault epoch (it determines sync points for withdrawals)
* @param depositWhitelist if enabling deposit whitelist
* @param isDepositLimit if enabling deposit limit
* @param depositLimit deposit limit (maximum amount of the collateral that can be in the vault simultaneously)
* @param defaultAdminRoleHolder address of the initial DEFAULT_ADMIN_ROLE holder
* @param depositWhitelistSetRoleHolder address of the initial DEPOSIT_WHITELIST_SET_ROLE holder
* @param depositorWhitelistRoleHolder address of the initial DEPOSITOR_WHITELIST_ROLE holder
* @param isDepositLimitSetRoleHolder address of the initial IS_DEPOSIT_LIMIT_SET_ROLE holder
* @param depositLimitSetRoleHolder address of the initial DEPOSIT_LIMIT_SET_ROLE holder
* @param name name for the ERC20 tokenized vault
* @param symbol symbol for the ERC20 tokenized vault
*/
struct InitParams {
address collateral;
address burner;
uint48 epochDuration;
bool depositWhitelist;
bool isDepositLimit;
uint256 depositLimit;
address defaultAdminRoleHolder;
address depositWhitelistSetRoleHolder;
address depositorWhitelistRoleHolder;
address isDepositLimitSetRoleHolder;
address depositLimitSetRoleHolder;
string name;
string symbol;
}
/**
* @notice Get the factory's address.
* @return address of the factory
*/
function FACTORY() external view returns (address);
/**
* @notice Get the entity's version.
* @return version of the entity
* @dev Starts from 1.
*/
function version() external view returns (uint64);
/**
* @notice Initialize this entity contract by using a given data and setting a particular version and owner.
* @param initialVersion initial version of the entity
* @param owner initial owner of the entity
* @param data some data to use
* @param delegatorFactory Address of the delegator factory.
* @param slasherFactory Address of the slasher factory.
*
*/
function initialize(
uint64 initialVersion,
address owner,
bytes calldata data,
address delegatorFactory,
address slasherFactory
) external;
/**
* @notice Migrate this entity to a particular newer version using a given data.
* @param newVersion new version of the entity
* @param data some data to use
*/
function migrate(uint64 newVersion, bytes calldata data) external;
/**
* @notice Hints for an active balance.
* @param activeSharesOfHint hint for the active shares of checkpoint
* @param activeStakeHint hint for the active stake checkpoint
* @param activeSharesHint hint for the active shares checkpoint
*/
struct ActiveBalanceOfHints {
bytes activeSharesOfHint;
bytes activeStakeHint;
bytes activeSharesHint;
}
// Events
/**
* @notice Emitted when a deposit is made.
* @param depositor account that made the deposit
* @param onBehalfOf account the deposit was made on behalf of
* @param amount amount of the collateral deposited
* @param shares amount of the active shares minted
*/
event Deposit(address indexed depositor, address indexed onBehalfOf, uint256 amount, uint256 shares);
/**
* @notice Emitted when a withdrawal is made.
* @param withdrawer account that made the withdrawal
* @param claimer account that needs to claim the withdrawal
* @param amount amount of the collateral withdrawn
* @param burnedShares amount of the active shares burned
* @param mintedShares amount of the epoch withdrawal shares minted
*/
event Withdraw(
address indexed withdrawer, address indexed claimer, uint256 amount, uint256 burnedShares, uint256 mintedShares
);
/**
* @notice Emitted when a claim is made.
* @param claimer account that claimed
* @param recipient account that received the collateral
* @param epoch epoch the collateral was claimed for
* @param amount amount of the collateral claimed
*/
event Claim(address indexed claimer, address indexed recipient, uint256 epoch, uint256 amount);
/**
* @notice Emitted when a batch claim is made.
* @param claimer account that claimed
* @param recipient account that received the collateral
* @param epochs epochs the collateral was claimed for
* @param amount amount of the collateral claimed
*/
event ClaimBatch(address indexed claimer, address indexed recipient, uint256[] epochs, uint256 amount);
/**
* @notice Emitted when a slash happens.
* @param amount amount of the collateral to slash
* @param captureTimestamp time point when the stake was captured
* @param slashedAmount real amount of the collateral slashed
*/
event OnSlash(uint256 amount, uint48 captureTimestamp, uint256 slashedAmount);
/**
* @notice Emitted when a deposit whitelist status is enabled/disabled.
* @param status if enabled deposit whitelist
*/
event SetDepositWhitelist(bool status);
/**
* @notice Emitted when a depositor whitelist status is set.
* @param account account for which the whitelist status is set
* @param status if whitelisted the account
*/
event SetDepositorWhitelistStatus(address indexed account, bool status);
/**
* @notice Emitted when a deposit limit status is enabled/disabled.
* @param status if enabled deposit limit
*/
event SetIsDepositLimit(bool status);
/**
* @notice Emitted when a deposit limit is set.
* @param limit deposit limit (maximum amount of the collateral that can be in the vault simultaneously)
*/
event SetDepositLimit(uint256 limit);
/**
* @notice Emitted when a delegator is set.
* @param delegator vault's delegator to delegate the stake to networks and operators
* @dev Can be set only once.
*/
event SetDelegator(address indexed delegator);
/**
* @notice Emitted when a slasher is set.
* @param slasher vault's slasher to provide a slashing mechanism to networks
* @dev Can be set only once.
*/
event SetSlasher(address indexed slasher);
// Functions
// Ex IVaultStorage
/**
* @notice Get the delegator factory's address.
* @return address of the delegator factory
*/
function DELEGATOR_FACTORY() external view returns (address);
/**
* @notice Get the slasher factory's address.
* @return address of the slasher factory
*/
function SLASHER_FACTORY() external view returns (address);
/**
* @notice Get a vault collateral.
* @return address of the underlying collateral
*/
function collateral() external view returns (address);
/**
* @notice Get a burner to issue debt to (e.g., 0xdEaD or some unwrapper contract).
* @return address of the burner
*/
function burner() external view returns (address);
/**
* @notice Get a delegator (it delegates the vault's stake to networks and operators).
* @return address of the delegator
*/
function delegator() external view returns (address);
/**
* @notice Get if the delegator is initialized.
* @return if the delegator is initialized
*/
function isDelegatorInitialized() external view returns (bool);
/**
* @notice Get a slasher (it provides networks a slashing mechanism).
* @return address of the slasher
*/
function slasher() external view returns (address);
/**
* @notice Get if the slasher is initialized.
* @return if the slasher is initialized
*/
function isSlasherInitialized() external view returns (bool);
/**
* @notice Get a time point of the epoch duration set.
* @return time point of the epoch duration set
*/
function epochDurationInit() external view returns (uint48);
/**
* @notice Get a duration of the vault epoch.
* @return duration of the epoch
*/
function epochDuration() external view returns (uint48);
/**
* @notice Get an epoch at a given timestamp.
* @param timestamp time point to get the epoch at
* @return epoch at the timestamp
* @dev Reverts if the timestamp is less than the start of the epoch 0.
*/
function epochAt(
uint48 timestamp
) external view returns (uint256);
/**
* @notice Get a current vault epoch.
* @return current epoch
*/
function currentEpoch() external view returns (uint256);
/**
* @notice Get a start of the current vault epoch.
* @return start of the current epoch
*/
function currentEpochStart() external view returns (uint48);
/**
* @notice Get a start of the previous vault epoch.
* @return start of the previous epoch
* @dev Reverts if the current epoch is 0.
*/
function previousEpochStart() external view returns (uint48);
/**
* @notice Get a start of the next vault epoch.
* @return start of the next epoch
*/
function nextEpochStart() external view returns (uint48);
/**
* @notice Get if the deposit whitelist is enabled.
* @return if the deposit whitelist is enabled
*/
function depositWhitelist() external view returns (bool);
/**
* @notice Get if a given account is whitelisted as a depositor.
* @param account address to check
* @return if the account is whitelisted as a depositor
*/
function isDepositorWhitelisted(
address account
) external view returns (bool);
/**
* @notice Get if the deposit limit is set.
* @return if the deposit limit is set
*/
function isDepositLimit() external view returns (bool);
/**
* @notice Get a deposit limit (maximum amount of the active stake that can be in the vault simultaneously).
* @return deposit limit
*/
function depositLimit() external view returns (uint256);
/**
* @notice Get a total number of active shares in the vault at a given timestamp using a hint.
* @param timestamp time point to get the total number of active shares at
* @param hint hint for the checkpoint index
* @return total number of active shares at the timestamp
*/
function activeSharesAt(uint48 timestamp, bytes memory hint) external view returns (uint256);
/**
* @notice Get a total number of active shares in the vault.
* @return total number of active shares
*/
function activeShares() external view returns (uint256);
/**
* @notice Get a total amount of active stake in the vault at a given timestamp using a hint.
* @param timestamp time point to get the total active stake at
* @param hint hint for the checkpoint index
* @return total amount of active stake at the timestamp
*/
function activeStakeAt(uint48 timestamp, bytes memory hint) external view returns (uint256);
/**
* @notice Get a total amount of active stake in the vault.
* @return total amount of active stake
*/
function activeStake() external view returns (uint256);
/**
* @notice Get a total number of active shares for a particular account at a given timestamp using a hint.
* @param account account to get the number of active shares for
* @param timestamp time point to get the number of active shares for the account at
* @param hint hint for the checkpoint index
* @return number of active shares for the account at the timestamp
*/
function activeSharesOfAt(address account, uint48 timestamp, bytes memory hint) external view returns (uint256);
/**
* @notice Get a number of active shares for a particular account.
* @param account account to get the number of active shares for
* @return number of active shares for the account
*/
function activeSharesOf(
address account
) external view returns (uint256);
/**
* @notice Get a total amount of the withdrawals at a given epoch.
* @param epoch epoch to get the total amount of the withdrawals at
* @return total amount of the withdrawals at the epoch
*/
function withdrawals(
uint256 epoch
) external view returns (uint256);
/**
* @notice Get a total number of withdrawal shares at a given epoch.
* @param epoch epoch to get the total number of withdrawal shares at
* @return total number of withdrawal shares at the epoch
*/
function withdrawalShares(
uint256 epoch
) external view returns (uint256);
/**
* @notice Get a number of withdrawal shares for a particular account at a given epoch (zero if claimed).
* @param epoch epoch to get the number of withdrawal shares for the account at
* @param account account to get the number of withdrawal shares for
* @return number of withdrawal shares for the account at the epoch
*/
function withdrawalSharesOf(uint256 epoch, address account) external view returns (uint256);
/**
* @notice Get if the withdrawals are claimed for a particular account at a given epoch.
* @param epoch epoch to check the withdrawals for the account at
* @param account account to check the withdrawals for
* @return if the withdrawals are claimed for the account at the epoch
*/
function isWithdrawalsClaimed(uint256 epoch, address account) external view returns (bool);
//ex IVault
/**
* @notice Check if the vault is fully initialized (a delegator and a slasher are set).
* @return if the vault is fully initialized
*/
function isInitialized() external view returns (bool);
/**
* @notice Get a total amount of the collateral that can be slashed.
* @return total amount of the slashable collateral
*/
function totalStake() external view returns (uint256);
/**
* @notice Get an active balance for a particular account at a given timestamp using hints.
* @param account account to get the active balance for
* @param timestamp time point to get the active balance for the account at
* @param hints hints for checkpoints' indexes
* @return active balance for the account at the timestamp
*/
function activeBalanceOfAt(
address account,
uint48 timestamp,
bytes calldata hints
) external view returns (uint256);
/**
* @notice Get an active balance for a particular account.
* @param account account to get the active balance for
* @return active balance for the account
*/
function activeBalanceOf(
address account
) external view returns (uint256);
/**
* @notice Get withdrawals for a particular account at a given epoch (zero if claimed).
* @param epoch epoch to get the withdrawals for the account at
* @param account account to get the withdrawals for
* @return withdrawals for the account at the epoch
*/
function withdrawalsOf(uint256 epoch, address account) external view returns (uint256);
/**
* @notice Get a total amount of the collateral that can be slashed for a given account.
* @param account account to get the slashable collateral for
* @return total amount of the account's slashable collateral
*/
function slashableBalanceOf(
address account
) external view returns (uint256);
/**
* @notice Deposit collateral into the vault.
* @param onBehalfOf account the deposit is made on behalf of
* @param amount amount of the collateral to deposit
* @return depositedAmount real amount of the collateral deposited
* @return mintedShares amount of the active shares minted
*/
function deposit(
address onBehalfOf,
uint256 amount
) external returns (uint256 depositedAmount, uint256 mintedShares);
/**
* @notice Withdraw collateral from the vault (it will be claimable after the next epoch).
* @param claimer account that needs to claim the withdrawal
* @param amount amount of the collateral to withdraw
* @return burnedShares amount of the active shares burned
* @return mintedShares amount of the epoch withdrawal shares minted
*/
function withdraw(address claimer, uint256 amount) external returns (uint256 burnedShares, uint256 mintedShares);
/**
* @notice Redeem collateral from the vault (it will be claimable after the next epoch).
* @param claimer account that needs to claim the withdrawal
* @param shares amount of the active shares to redeem
* @return withdrawnAssets amount of the collateral withdrawn
* @return mintedShares amount of the epoch withdrawal shares minted
*/
function redeem(address claimer, uint256 shares) external returns (uint256 withdrawnAssets, uint256 mintedShares);
/**
* @notice Claim collateral from the vault.
* @param recipient account that receives the collateral
* @param epoch epoch to claim the collateral for
* @return amount amount of the collateral claimed
*/
function claim(address recipient, uint256 epoch) external returns (uint256 amount);
/**
* @notice Claim collateral from the vault for multiple epochs.
* @param recipient account that receives the collateral
* @param epochs epochs to claim the collateral for
* @return amount amount of the collateral claimed
*/
function claimBatch(address recipient, uint256[] calldata epochs) external returns (uint256 amount);
/**
* @notice Slash callback for burning collateral.
* @param amount amount to slash
* @param captureTimestamp time point when the stake was captured
* @return slashedAmount real amount of the collateral slashed
* @dev Only the slasher can call this function.
*/
function onSlash(uint256 amount, uint48 captureTimestamp) external returns (uint256 slashedAmount);
/**
* @notice Enable/disable deposit whitelist.
* @param status if enabling deposit whitelist
* @dev Only a DEPOSIT_WHITELIST_SET_ROLE holder can call this function.
*/
function setDepositWhitelist(
bool status
) external;
/**
* @notice Set a depositor whitelist status.
* @param account account for which the whitelist status is set
* @param status if whitelisting the account
* @dev Only a DEPOSITOR_WHITELIST_ROLE holder can call this function.
*/
function setDepositorWhitelistStatus(address account, bool status) external;
/**
* @notice Enable/disable deposit limit.
* @param status if enabling deposit limit
* @dev Only a IS_DEPOSIT_LIMIT_SET_ROLE holder can call this function.
*/
function setIsDepositLimit(
bool status
) external;
/**
* @notice Set a deposit limit.
* @param limit deposit limit (maximum amount of the collateral that can be in the vault simultaneously)
* @dev Only a DEPOSIT_LIMIT_SET_ROLE holder can call this function.
*/
function setDepositLimit(
uint256 limit
) external;
/**
* @notice Set a delegator.
* @param delegator vault's delegator to delegate the stake to networks and operators
* @dev Can be set only once.
*/
function setDelegator(
address delegator
) external;
/**
* @notice Set a slasher.
* @param slasher vault's slasher to provide a slashing mechanism to networks
* @dev Can be set only once.
*/
function setSlasher(
address slasher
) external;
/**
* @notice Make a delegatecall from this contract to a given target contract with a particular data (always reverts with a return data).
* @param target address of the contract to make a delegatecall to
* @param data data to make a delegatecall with
* @dev It allows to use this contract's storage on-chain.
*/
function staticDelegateCall(address target, bytes calldata data) external;
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
import {IVaultTokenized} from "../../interfaces/vault/IVaultTokenized.sol";
import {IBaseDelegator} from "../../interfaces/delegator/IBaseDelegator.sol";
import {IBaseSlasher} from "../../interfaces/slasher/IBaseSlasher.sol";
import {IRegistry} from "../../interfaces/common/IRegistry.sol";
import {IDelegatorFactory} from "../../interfaces/IDelegatorFactory.sol";
import {ExtendedCheckpoints} from "../libraries/Checkpoints.sol";
import {ERC4626Math} from "../libraries/ERC4626Math.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Time} from "@openzeppelin/contracts/utils/types/Time.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
contract VaultTokenized is
Initializable,
AccessControlUpgradeable,
ERC20Upgradeable,
IVaultTokenized,
OwnableUpgradeable,
ReentrancyGuardUpgradeable
{
using ExtendedCheckpoints for ExtendedCheckpoints.Trace256;
using Math for uint256;
using SafeCast for uint256;
using SafeERC20 for IERC20;
error AlreadyInitialized();
error NotFactory();
error NotInitialized();
/**
* @inheritdoc IVaultTokenized
*/
address public immutable FACTORY;
// Constants (roles)
bytes32 public constant DEPOSIT_WHITELIST_SET_ROLE = keccak256("DEPOSIT_WHITELIST_SET_ROLE");
bytes32 public constant DEPOSITOR_WHITELIST_ROLE = keccak256("DEPOSITOR_WHITELIST_ROLE");
bytes32 public constant IS_DEPOSIT_LIMIT_SET_ROLE = keccak256("IS_DEPOSIT_LIMIT_SET_ROLE");
bytes32 public constant DEPOSIT_LIMIT_SET_ROLE = keccak256("DEPOSIT_LIMIT_SET_ROLE");
/// @custom:storage-location erc7201:vault.storage
struct VaultStorageStruct {
// State variables
address DELEGATOR_FACTORY;
address SLASHER_FACTORY;
bool depositWhitelist;
bool isDepositLimit;
address collateral;
address burner;
uint48 epochDurationInit;
uint48 epochDuration;
address delegator;
bool isDelegatorInitialized;
address slasher;
bool isSlasherInitialized;
uint256 depositLimit;
mapping(address => bool) isDepositorWhitelisted;
mapping(uint256 => uint256) withdrawals;
mapping(uint256 => uint256) withdrawalShares;
mapping(uint256 => mapping(address => uint256)) withdrawalSharesOf;
mapping(uint256 => mapping(address => bool)) isWithdrawalsClaimed;
ExtendedCheckpoints.Trace256 _activeShares;
ExtendedCheckpoints.Trace256 _activeStake;
mapping(address => ExtendedCheckpoints.Trace256) _activeSharesOf;
}
// bytes32(uint256(keccak256(abi.encodePacked(uint256(keccak256("vault.storage")) - 1))) & ~uint256(0xff));
bytes32 public constant _VAULT_STORAGE_SLOT = 0x8b11a41397fd1980a1e0d979c37e7161d100e59fa63c611bcc37f4f3fcd7b600;
function _vaultStorage() internal pure returns (VaultStorageStruct storage vs) {
bytes32 slot = _VAULT_STORAGE_SLOT;
assembly {
vs.slot := slot
}
}
/**
*
* @param vaultFactory Address of the vault factory.
*/
constructor(
address vaultFactory
) {
_disableInitializers();
if (vaultFactory == address(0)) {
revert Vault__InvalidFactory();
}
FACTORY = vaultFactory;
}
/**
* @inheritdoc IVaultTokenized
*/
function initialize(
uint64 initialVersion,
address owner_,
bytes calldata data,
address delegatorFactory,
address slasherFactory
) external initializer {
VaultStorageStruct storage vs = _vaultStorage();
__Ownable_init(owner_);
__AccessControl_init();
__ReentrancyGuard_init();
vs.DELEGATOR_FACTORY = delegatorFactory;
vs.SLASHER_FACTORY = slasherFactory;
_initialize(initialVersion, owner_, data);
}
/**
* @dev Internal initialization function.
* @param data Initialization data.
*/
function _initialize(
uint64, /* initialVersion */
address, /* owner */
bytes memory data
) internal onlyInitializing {
VaultStorageStruct storage vs = _vaultStorage();
(InitParams memory params) = abi.decode(data, (InitParams));
__ERC20_init(params.name, params.symbol);
if (params.collateral == address(0)) {
revert Vault__InvalidCollateral();
}
if (params.epochDuration == 0) {
revert Vault__InvalidEpochDuration();
}
if (params.defaultAdminRoleHolder == address(0)) {
if (params.depositWhitelistSetRoleHolder == address(0)) {
if (params.depositWhitelist) {
if (params.depositorWhitelistRoleHolder == address(0)) {
revert Vault__MissingRoles();
}
} else if (params.depositorWhitelistRoleHolder != address(0)) {
revert Vault__InconsistentRoles();
}
}
if (params.isDepositLimitSetRoleHolder == address(0)) {
if (params.isDepositLimit) {
if (params.depositLimit == 0 && params.depositLimitSetRoleHolder == address(0)) {
revert Vault__MissingRoles();
}
} else if (params.depositLimit != 0 || params.depositLimitSetRoleHolder != address(0)) {
revert Vault__InconsistentRoles();
}
}
}
vs.collateral = params.collateral;
vs.burner = params.burner;
vs.epochDurationInit = Time.timestamp();
vs.epochDuration = params.epochDuration;
vs.depositWhitelist = params.depositWhitelist;
vs.isDepositLimit = params.isDepositLimit;
vs.depositLimit = params.depositLimit;
if (params.defaultAdminRoleHolder != address(0)) {
_grantRole(DEFAULT_ADMIN_ROLE, params.defaultAdminRoleHolder);
}
if (params.depositWhitelistSetRoleHolder != address(0)) {
_grantRole(DEPOSIT_WHITELIST_SET_ROLE, params.depositWhitelistSetRoleHolder);
}
if (params.depositorWhitelistRoleHolder != address(0)) {
_grantRole(DEPOSITOR_WHITELIST_ROLE, params.depositorWhitelistRoleHolder);
}
if (params.isDepositLimitSetRoleHolder != address(0)) {
_grantRole(IS_DEPOSIT_LIMIT_SET_ROLE, params.isDepositLimitSetRoleHolder);
}
if (params.depositLimitSetRoleHolder != address(0)) {
_grantRole(DEPOSIT_LIMIT_SET_ROLE, params.depositLimitSetRoleHolder);
}
}
/**
* @notice Returns the version of the contract.
*/
function version() external view returns (uint64) {
return _getInitializedVersion();
}
/**
* @inheritdoc IVaultTokenized
*/
function migrate(uint64 newVersion, bytes calldata data) external nonReentrant {
if (msg.sender != FACTORY) {
revert NotFactory();
}
_migrateInternal(newVersion, data);
}
function _migrateInternal(uint64 newVersion, bytes calldata data) private reinitializer(newVersion) {
_migrate(newVersion, data);
}
/**
* @dev Internal migration function. Can be overridden by child contracts.
*/
function _migrate(uint64, /* newVersion */ bytes calldata /* data */ ) internal virtual onlyInitializing {
// Implement migration logic here
revert Vault__MigrationNotImplemented();
}
/**
* @inheritdoc IVaultTokenized
*/
function isInitialized() external view returns (bool) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.isDelegatorInitialized && vs.isSlasherInitialized;
}
/**
* @inheritdoc IVaultTokenized
*/
function DELEGATOR_FACTORY() external view override returns (address) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.DELEGATOR_FACTORY;
}
/**
* @inheritdoc IVaultTokenized
*/
function SLASHER_FACTORY() external view override returns (address) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.SLASHER_FACTORY;
}
/**
* @inheritdoc IVaultTokenized
*/
function burner() external view override returns (address) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.burner;
}
/**
* @inheritdoc IVaultTokenized
*/
function collateral() external view override returns (address) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.collateral;
}
/**
* @inheritdoc IVaultTokenized
*/
function delegator() external view override returns (address) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.delegator;
}
/**
* @inheritdoc IVaultTokenized
*/
function depositLimit() external view override returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.depositLimit;
}
/**
* @inheritdoc IVaultTokenized
*/
function depositWhitelist() external view override returns (bool) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.depositWhitelist;
}
/**
* @inheritdoc IVaultTokenized
*/
function epochDuration() external view override returns (uint48) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.epochDuration;
}
/**
* @inheritdoc IVaultTokenized
*/
function epochDurationInit() external view override returns (uint48) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.epochDurationInit;
}
/**
* @inheritdoc IVaultTokenized
*/
function isDelegatorInitialized() external view override returns (bool) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.isDelegatorInitialized;
}
/**
* @inheritdoc IVaultTokenized
*/
function isDepositLimit() external view override returns (bool) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.isDepositLimit;
}
/**
* @inheritdoc IVaultTokenized
*/
function isDepositorWhitelisted(
address account
) external view override returns (bool) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.isDepositorWhitelisted[account];
}
/**
* @inheritdoc IVaultTokenized
*/
function isSlasherInitialized() external view override returns (bool) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.isSlasherInitialized;
}
/**
* @inheritdoc IVaultTokenized
*/
function isWithdrawalsClaimed(uint256 epoch, address account) external view override returns (bool) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.isWithdrawalsClaimed[epoch][account];
}
/**
* @inheritdoc IVaultTokenized
*/
function slasher() external view override returns (address) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.slasher;
}
/**
* @inheritdoc IVaultTokenized
*/
function withdrawalShares(
uint256 epoch
) external view override returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.withdrawalShares[epoch];
}
/**
* @inheritdoc IVaultTokenized
*/
function withdrawalSharesOf(uint256 epoch, address account) external view override returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.withdrawalSharesOf[epoch][account];
}
/**
* @inheritdoc IVaultTokenized
*/
function withdrawals(
uint256 epoch
) external view override returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return vs.withdrawals[epoch];
}
/**
* @inheritdoc IVaultTokenized
*/
function totalStake() external view override returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
uint256 epoch = currentEpoch();
return activeStake() + vs.withdrawals[epoch] + vs.withdrawals[epoch + 1];
}
/**
* @inheritdoc IVaultTokenized
*/
function activeBalanceOfAt(address account, uint48 timestamp, bytes calldata hints) public view returns (uint256) {
// VaultStorageStruct storage vs = _vaultStorage();
ActiveBalanceOfHints memory activeBalanceOfHints;
if (hints.length > 0) {
activeBalanceOfHints = abi.decode(hints, (ActiveBalanceOfHints));
}
return ERC4626Math.previewRedeem(
activeSharesOfAt(account, timestamp, activeBalanceOfHints.activeSharesOfHint),
activeStakeAt(timestamp, activeBalanceOfHints.activeStakeHint),
activeSharesAt(timestamp, activeBalanceOfHints.activeSharesHint)
);
}
/**
* @inheritdoc IVaultTokenized
*/
function activeBalanceOf(
address account
) public view returns (uint256) {
return ERC4626Math.previewRedeem(activeSharesOf(account), activeStake(), activeShares());
}
/**
* @inheritdoc IVaultTokenized
*/
function withdrawalsOf(uint256 epoch, address account) public view returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return ERC4626Math.previewRedeem(
vs.withdrawalSharesOf[epoch][account], vs.withdrawals[epoch], vs.withdrawalShares[epoch]
);
}
/**
* @inheritdoc IVaultTokenized
*/
function slashableBalanceOf(
address account
) external view returns (uint256) {
uint256 epoch = currentEpoch();
return activeBalanceOf(account) + withdrawalsOf(epoch, account) + withdrawalsOf(epoch + 1, account);
}
/**
* @inheritdoc IVaultTokenized
*/
function deposit(
address onBehalfOf,
uint256 amount
) public virtual nonReentrant returns (uint256 depositedAmount, uint256 mintedShares) {
VaultStorageStruct storage vs = _vaultStorage();
if (onBehalfOf == address(0)) {
revert Vault__InvalidOnBehalfOf();
}
if (vs.depositWhitelist && !vs.isDepositorWhitelisted[msg.sender]) {
revert Vault__NotWhitelistedDepositor();
}
uint256 balanceBefore = IERC20(vs.collateral).balanceOf(address(this));
IERC20(vs.collateral).safeTransferFrom(msg.sender, address(this), amount);
depositedAmount = IERC20(vs.collateral).balanceOf(address(this)) - balanceBefore;
if (depositedAmount == 0) {
revert Vault__InsufficientDeposit();
}
if (vs.isDepositLimit && activeStake() + depositedAmount > vs.depositLimit) {
revert Vault__DepositLimitReached();
}
uint256 activeStake_ = activeStake();
uint256 activeShares_ = activeShares();
mintedShares = ERC4626Math.previewDeposit(depositedAmount, activeShares_, activeStake_);
vs._activeStake.push(Time.timestamp(), activeStake_ + depositedAmount);
vs._activeShares.push(Time.timestamp(), activeShares_ + mintedShares);
vs._activeSharesOf[onBehalfOf].push(Time.timestamp(), activeSharesOf(onBehalfOf) + mintedShares);
emit Deposit(msg.sender, onBehalfOf, depositedAmount, mintedShares);
emit Transfer(address(0), onBehalfOf, mintedShares);
}
/**
* @inheritdoc IVaultTokenized
*/
function withdraw(
address claimer,
uint256 amount
) external nonReentrant returns (uint256 burnedShares, uint256 mintedShares) {
// VaultStorageStruct storage vs = _vaultStorage();
if (claimer == address(0)) {
revert Vault__InvalidClaimer();
}
if (amount == 0) {
revert Vault__InsufficientWithdrawal();
}
burnedShares = ERC4626Math.previewWithdraw(amount, activeShares(), activeStake());
if (burnedShares > activeSharesOf(msg.sender)) {
revert Vault__TooMuchWithdraw();
}
mintedShares = _withdraw(claimer, amount, burnedShares);
}
/**
* @inheritdoc IVaultTokenized
*/
function redeem(
address claimer,
uint256 shares
) external nonReentrant returns (uint256 withdrawnAssets, uint256 mintedShares) {
// VaultStorageStruct storage vs = _vaultStorage();
if (claimer == address(0)) {
revert Vault__InvalidClaimer();
}
if (shares > activeSharesOf(msg.sender)) {
revert Vault__TooMuchRedeem();
}
withdrawnAssets = ERC4626Math.previewRedeem(shares, activeStake(), activeShares());
if (withdrawnAssets == 0) {
revert Vault__InsufficientRedemption();
}
mintedShares = _withdraw(claimer, withdrawnAssets, shares);
}
/**
* @inheritdoc IVaultTokenized
*/
function claim(address recipient, uint256 epoch) external nonReentrant returns (uint256 amount) {
VaultStorageStruct storage vs = _vaultStorage();
if (recipient == address(0)) {
revert Vault__InvalidRecipient();
}
amount = _claim(epoch);
IERC20(vs.collateral).safeTransfer(recipient, amount);
emit Claim(msg.sender, recipient, epoch, amount);
}
/**
* @inheritdoc IVaultTokenized
*/
function claimBatch(address recipient, uint256[] calldata epochs) external nonReentrant returns (uint256 amount) {
VaultStorageStruct storage vs = _vaultStorage();
if (recipient == address(0)) {
revert Vault__InvalidRecipient();
}
uint256 length = epochs.length;
if (length == 0) {
revert Vault__InvalidLengthEpochs();
}
for (uint256 i; i < length; ++i) {
amount += _claim(epochs[i]);
}
IERC20(vs.collateral).safeTransfer(recipient, amount);
emit ClaimBatch(msg.sender, recipient, epochs, amount);
}
/**
* @inheritdoc IVaultTokenized
*/
function onSlash(uint256 amount, uint48 captureTimestamp) external nonReentrant returns (uint256 slashedAmount) {
VaultStorageStruct storage vs = _vaultStorage();
if (msg.sender != vs.slasher) {
revert Vault__NotSlasher();
}
uint256 currentEpoch_ = currentEpoch();
uint256 captureEpoch = epochAt(captureTimestamp);
if ((currentEpoch_ > 0 && captureEpoch < currentEpoch_ - 1) || captureEpoch > currentEpoch_) {
revert Vault__InvalidCaptureEpoch();
}
uint256 activeStake_ = activeStake();
uint256 nextWithdrawals = vs.withdrawals[currentEpoch_ + 1];
if (captureEpoch == currentEpoch_) {
uint256 slashableStake = activeStake_ + nextWithdrawals;
slashedAmount = Math.min(amount, slashableStake);
if (slashedAmount > 0) {
uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake);
uint256 nextWithdrawalsSlashed = slashedAmount - activeSlashed;
vs._activeStake.push(Time.timestamp(), activeStake_ - activeSlashed);
vs.withdrawals[captureEpoch + 1] = nextWithdrawals - nextWithdrawalsSlashed;
}
} else {
uint256 withdrawals_ = vs.withdrawals[currentEpoch_];
uint256 slashableStake = activeStake_ + withdrawals_ + nextWithdrawals;
slashedAmount = Math.min(amount, slashableStake);
if (slashedAmount > 0) {
uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake);
uint256 nextWithdrawalsSlashed = slashedAmount.mulDiv(nextWithdrawals, slashableStake);
uint256 withdrawalsSlashed = slashedAmount - activeSlashed - nextWithdrawalsSlashed;
if (withdrawals_ < withdrawalsSlashed) {
nextWithdrawalsSlashed += withdrawalsSlashed - withdrawals_;
withdrawalsSlashed = withdrawals_;
}
vs._activeStake.push(Time.timestamp(), activeStake_ - activeSlashed);
vs.withdrawals[currentEpoch_ + 1] = nextWithdrawals - nextWithdrawalsSlashed;
vs.withdrawals[currentEpoch_] = withdrawals_ - withdrawalsSlashed;
}
}
if (slashedAmount > 0) {
IERC20(vs.collateral).safeTransfer(vs.burner, slashedAmount);
}
emit OnSlash(amount, captureTimestamp, slashedAmount);
}
/**
* @inheritdoc IVaultTokenized
*/
function setDepositWhitelist(
bool status
) external nonReentrant onlyRole(DEPOSIT_WHITELIST_SET_ROLE) {
VaultStorageStruct storage vs = _vaultStorage();
if (vs.depositWhitelist == status) {
revert Vault__AlreadySet();
}
vs.depositWhitelist = status;
emit SetDepositWhitelist(status);
}
/**
* @inheritdoc IVaultTokenized
*/
function setDepositorWhitelistStatus(
address account,
bool status
) external nonReentrant onlyRole(DEPOSITOR_WHITELIST_ROLE) {
VaultStorageStruct storage vs = _vaultStorage();
if (account == address(0)) {
revert Vault__InvalidAccount();
}
if (vs.isDepositorWhitelisted[account] == status) {
revert Vault__AlreadySet();
}
vs.isDepositorWhitelisted[account] = status;
emit SetDepositorWhitelistStatus(account, status);
}
/**
* @inheritdoc IVaultTokenized
*/
function setIsDepositLimit(
bool status
) external nonReentrant onlyRole(IS_DEPOSIT_LIMIT_SET_ROLE) {
VaultStorageStruct storage vs = _vaultStorage();
if (vs.isDepositLimit == status) {
revert Vault__AlreadySet();
}
vs.isDepositLimit = status;
emit SetIsDepositLimit(status);
}
/**
* @inheritdoc IVaultTokenized
*/
function setDepositLimit(
uint256 limit
) external nonReentrant onlyRole(DEPOSIT_LIMIT_SET_ROLE) {
VaultStorageStruct storage vs = _vaultStorage();
if (vs.depositLimit == limit) {
revert Vault__AlreadySet();
}
vs.depositLimit = limit;
emit SetDepositLimit(limit);
}
function setDelegator(
address delegator_
) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
VaultStorageStruct storage vs = _vaultStorage();
if (vs.isDelegatorInitialized) {
revert Vault__DelegatorAlreadyInitialized();
}
// replace by IDelegatorFactory
if (!IDelegatorFactory(vs.DELEGATOR_FACTORY).isEntity(delegator_)) {
revert Vault__NotDelegator();
}
if (IBaseDelegator(delegator_).vault() != address(this)) {
revert Vault__InvalidDelegator();
}
vs.delegator = delegator_;
vs.isDelegatorInitialized = true;
emit SetDelegator(delegator_);
}
function setSlasher(
address slasher_
) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
VaultStorageStruct storage vs = _vaultStorage();
if (vs.isSlasherInitialized) {
revert Vault__SlasherAlreadyInitialized();
}
// replace by ISlasherFactory
if (slasher_ != address(0)) {
if (!IRegistry(vs.SLASHER_FACTORY).isEntity(slasher_)) {
revert Vault__NotSlasher();
}
if (IBaseSlasher(slasher_).vault() != address(this)) {
revert Vault__InvalidSlasher();
}
vs.slasher = slasher_;
}
vs.isSlasherInitialized = true;
emit SetSlasher(slasher_);
}
function _withdraw(
address claimer,
uint256 withdrawnAssets,
uint256 burnedShares
) internal virtual returns (uint256 mintedShares) {
VaultStorageStruct storage vs = _vaultStorage();
vs._activeSharesOf[msg.sender].push(Time.timestamp(), activeSharesOf(msg.sender) - burnedShares);
vs._activeShares.push(Time.timestamp(), activeShares() - burnedShares);
vs._activeStake.push(Time.timestamp(), activeStake() - withdrawnAssets);
uint256 epoch = currentEpoch() + 1;
uint256 withdrawals_ = vs.withdrawals[epoch];
uint256 withdrawalsShares_ = vs.withdrawalShares[epoch];
mintedShares = ERC4626Math.previewDeposit(withdrawnAssets, withdrawalsShares_, withdrawals_);
vs.withdrawals[epoch] = withdrawals_ + withdrawnAssets;
vs.withdrawalShares[epoch] = withdrawalsShares_ + mintedShares;
vs.withdrawalSharesOf[epoch][claimer] += mintedShares;
emit Withdraw(msg.sender, claimer, withdrawnAssets, burnedShares, mintedShares);
emit Transfer(msg.sender, address(0), burnedShares);
}
function _claim(
uint256 epoch
) internal returns (uint256 amount) {
VaultStorageStruct storage vs = _vaultStorage();
if (epoch >= currentEpoch()) {
revert Vault__InvalidEpoch();
}
// Cache the mapping lookup for the specific user and epoch
mapping(address => bool) storage epochClaimsMap = vs.isWithdrawalsClaimed[epoch];
if (epochClaimsMap[msg.sender]) {
revert Vault__AlreadyClaimed();
}
amount = withdrawalsOf(epoch, msg.sender);
if (amount == 0) {
revert Vault__InsufficientClaim();
}
epochClaimsMap[msg.sender] = true;
}
/**
* @inheritdoc IVaultTokenized
*/
function epochAt(
uint48 timestamp
) public view returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
if (timestamp < vs.epochDurationInit) {
revert Vault__InvalidTimestamp();
}
unchecked {
return (timestamp - vs.epochDurationInit) / vs.epochDuration;
}
}
/**
* @inheritdoc IVaultTokenized
*/
function currentEpoch() public view returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return (Time.timestamp() - vs.epochDurationInit) / vs.epochDuration;
}
/**
* @inheritdoc IVaultTokenized
*/
function currentEpochStart() public view returns (uint48) {
VaultStorageStruct storage vs = _vaultStorage();
return (vs.epochDurationInit + currentEpoch() * vs.epochDuration).toUint48();
}
/**
* @inheritdoc IVaultTokenized
*/
function previousEpochStart() public view returns (uint48) {
VaultStorageStruct storage vs = _vaultStorage();
uint256 epoch = currentEpoch();
if (epoch == 0) {
revert Vault__NoPreviousEpoch();
}
return (vs.epochDurationInit + (epoch - 1) * vs.epochDuration).toUint48();
}
/**
* @inheritdoc IVaultTokenized
*/
function nextEpochStart() public view returns (uint48) {
VaultStorageStruct storage vs = _vaultStorage();
return (vs.epochDurationInit + (currentEpoch() + 1) * vs.epochDuration).toUint48();
}
/**
* @inheritdoc IVaultTokenized
*/
function activeSharesAt(uint48 timestamp, bytes memory hint) public view returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return vs._activeShares.upperLookupRecent(timestamp, hint);
}
/**
* @inheritdoc IVaultTokenized
*/
function activeShares() public view returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return vs._activeShares.latest();
}
/**
* @inheritdoc IVaultTokenized
*/
function activeStakeAt(uint48 timestamp, bytes memory hint) public view returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return vs._activeStake.upperLookupRecent(timestamp, hint);
}
/**
* @inheritdoc IVaultTokenized
*/
function activeStake() public view returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return vs._activeStake.latest();
}
/**
* @inheritdoc IVaultTokenized
*/
function activeSharesOfAt(address account, uint48 timestamp, bytes memory hint) public view returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return vs._activeSharesOf[account].upperLookupRecent(timestamp, hint);
}
/**
* @inheritdoc IVaultTokenized
*/
function activeSharesOf(
address account
) public view returns (uint256) {
VaultStorageStruct storage vs = _vaultStorage();
return vs._activeSharesOf[account].latest();
}
/**
* @inheritdoc ERC20Upgradeable
*/
function decimals() public view override returns (uint8) {
VaultStorageStruct storage vs = _vaultStorage();
return IERC20Metadata(vs.collateral).decimals();
}
/**
* @inheritdoc ERC20Upgradeable
*/
function totalSupply() public view override returns (uint256) {
// VaultStorageStruct storage vs = _vaultStorage();
return activeShares();
}
/**
* @inheritdoc ERC20Upgradeable
*/
function balanceOf(
address account
) public view override returns (uint256) {
// VaultStorageStruct storage vs = _vaultStorage();
return activeSharesOf(account);
}
/**
* @inheritdoc ERC20Upgradeable
*/
function _update(address from, address to, uint256 value) internal override {
VaultStorageStruct storage vs = _vaultStorage();
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
vs._activeShares.push(Time.timestamp(), totalSupply() + value);
} else {
uint256 fromBalance = balanceOf(from);
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
vs._activeSharesOf[from].push(Time.timestamp(), fromBalance - value);
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
vs._activeShares.push(Time.timestamp(), totalSupply() - value);
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
vs._activeSharesOf[to].push(Time.timestamp(), balanceOf(to) + value);
}
}
emit Transfer(from, to, value);
}
/**
* @inheritdoc IVaultTokenized
*/
function staticDelegateCall(address target, bytes calldata data) external onlyOwner {
(bool success, bytes memory returndata) = target.delegatecall(data);
bytes memory revertData = abi.encode(success, returndata);
assembly {
revert(add(32, revertData), mload(revertData))
}
}
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
struct DistributionBatch {
uint256 lastProcessedOperator;
bool isComplete;
}
/**
* @title IRewards
* @notice Interface for managing distribution and claiming of staking rewards
*/
interface IRewards {
// ============================
// ERRORS
// ============================
/**
* @dev Error thrown when trying to distribute rewards for an epoch that has already been processed
* @param epoch The epoch for which rewards were already distributed
*/
error RewardsAlreadyDistributed(uint48 epoch);
/**
* @dev Error thrown when trying to distribute rewards too early
* @param epoch Current epoch
* @param requiredEpoch Minimum required epoch for distribution
*/
error RewardsDistributionTooEarly(uint48 epoch, uint48 requiredEpoch);
/**
* @dev Error thrown when a user attempts to claim rewards but has no claimable balance
* @param user Address of user attempting to claim
*/
error NoRewardsToClaim(address user);
/**
* @dev Error thrown when the recipient address is invalid (zero address)
* @param recipient Invalid recipient address
*/
error InvalidRecipient(address recipient);
/**
* @dev Error thrown when the user has already claimed all available rewards
* @param staker Address of the staker
* @param epoch Epoch at which they last claimed
*/
error AlreadyClaimedForLatestEpoch(address staker, uint48 epoch);
/**
* @dev Error thrown when an invalid rewards token address is provided
* @param rewardsToken Invalid rewards token address
*/
error InvalidRewardsToken(address rewardsToken);
/**
* @dev Error thrown when an invalid rewards amount is provided
* @param rewardsAmount Invalid rewards amount
*/
error InvalidRewardsAmount(uint256 rewardsAmount);
/**
* @dev Error thrown when an invalid number of epochs is provided
* @param numberOfEpochs Invalid number of epochs
*/
error InvalidNumberOfEpochs(uint48 numberOfEpochs);
/**
* @dev Error thrown when an invalid share percentage is provided
* @param share Invalid share percentage
*/
error InvalidShare(uint16 share);
/**
* @dev Error thrown when an invalid fee percentage is provided
* @param fee Invalid fee percentage
*/
error InvalidFee(uint16 fee);
/**
* @dev Error thrown when an invalid operator address is provided
* @param operator Invalid operator address
*/
error InvalidOperator(address operator);
/**
* @dev Error thrown when an invalid l1 middleware address is provided
* @param l1Middleware Invalid l1 middleware address
*/
error InvalidL1Middleware(address l1Middleware);
/**
* @dev Error thrown when an invalid uptime tracker address is provided
* @param uptimeTracker Invalid uptime tracker address
*/
error InvalidUptimeTracker(address uptimeTracker);
/**
* @dev Error thrown when an invalid admin address is provided
* @param admin Invalid admin address
*/
error InvalidAdmin(address admin);
/**
* @dev Error thrown when an invalid protocol owner address is provided
* @param protocolOwner Invalid protocol owner address
*/
error InvalidProtocolOwner(address protocolOwner);
/**
* @dev Error thrown when an invalid minimum uptime is provided
* @param minUptime Invalid minimum uptime
*/
error InvalidMinUptime(uint256 minUptime);
/**
* @dev Error thrown when a vault's asset class cannot be found
* @param vault Address of the vault
*/
error AssetClassNotFound(address vault);
/**
* @dev Error thrown when trying to operate on an unfinished epoch
* @param epoch Unfinished epoch
*/
error EpochNotFinished(uint48 epoch);
/**
* @dev Error thrown when the operator does not have uptime set for the epoch
* @param operator Address of the operator
* @param epoch Epoch at which the operator's uptime was checked
*/
error OperatorUptimeNotSet(address operator, uint48 epoch);
/**
* @dev Error thrown when the vault stake is zero, preventing reward computation
* @param vault Address of the vault with zero stake
* @param epoch Epoch for which the stake was checked
*/
error ZeroVaultStake(address vault, uint48 epoch);
/**
* @notice Error thrown when a fee percentage exceeds 100%
* @param fee Fee percentage that exceeds 100%
*/
error FeePercentageTooHigh(uint16 fee);
/**
* @notice Error thrown when the sum of all fees exceeds 100%
* @param totalFees Sum of all fees that exceeds 100%
*/
error TotalFeesExceed100(uint16 totalFees);
/**
* @notice Error thrown when trying to distribute rewards for an epoch that has already been completed
* @param epoch Epoch for which rewards were already distributed
*/
error AlreadyCompleted(uint48 epoch);
/**
* @notice Error thrown when trying to claim rewards for an epoch that has not been distributed
* @param epoch Epoch for which rewards are not distributed
*/
error DistributionNotComplete(uint48 epoch);
/**
* @notice Error thrown when trying to claim rewards for an epoch that is still claimable
* @param epoch Epoch for which rewards are still claimable
*/
error EpochStillClaimable(uint48 epoch);
// ============================
// EVENTS
// ============================
/**
* @notice Emitted when rewards are distributed for an epoch
* @param epoch Epoch for which rewards were distributed
*/
event RewardsDistributed(uint48 indexed epoch);
/**
* @notice Emitted when a user claims their staking rewards
* @param rewardsToken Address of the reward token
* @param recipient Address receiving the claimed rewards
* @param amount Amount of reward tokens claimed
*/
event RewardsClaimed(address indexed rewardsToken, address indexed recipient, uint256 amount);
/**
* @notice Emitted when a curator claims their fee
* @param rewardsToken Address of the reward token
* @param recipient Address receiving the curator fee
* @param amount Amount of reward tokens claimed
*/
event CuratorFeeClaimed(address indexed rewardsToken, address indexed recipient, uint256 amount);
/**
* @notice Emitted when an operator claims their fee
* @param rewardsToken Address of the reward token
* @param recipient Address receiving the operator fee
* @param amount Amount of reward tokens claimed
*/
event OperatorFeeClaimed(address indexed rewardsToken, address indexed recipient, uint256 amount);
/**
* @notice Emitted when the protocol owner claims fees
* @param rewardsToken Address of the reward token
* @param recipient Address receiving the protocol fee
* @param amount Amount of reward tokens claimed
*/
event ProtocolFeeClaimed(address indexed rewardsToken, address indexed recipient, uint256 amount);
/**
* @notice Emitted when a new admin role is assigned
* @param newAdmin Address of the new admin
*/
event AdminRoleAssigned(address indexed newAdmin);
/**
* @notice Emitted when a new rewards manager role is assigned
* @param rewardsManager Address of the new rewards manager
*/
event RewardsManagerRoleAssigned(address indexed rewardsManager);
/**
* @notice Emitted when a new rewards distributor role is assigned
* @param rewardsDistributor Address of the new rewards distributor
*/
event RewardsDistributorRoleAssigned(address indexed rewardsDistributor);
/**
* @notice Emitted when the protocol fee is updated
* @param newFee New protocol fee in basis points
*/
event ProtocolFeeUpdated(uint16 newFee);
/**
* @notice Emitted when the operator fee is updated
* @param newFee New operator fee in basis points
*/
event OperatorFeeUpdated(uint16 newFee);
/**
* @notice Emitted when the curator fee is updated
* @param newFee New curator fee in basis points
*/
event CuratorFeeUpdated(uint16 newFee);
/**
* @notice Emitted when the reward share percentage for an asset class is updated
* @param assetClassId ID of the asset class
* @param rewardsPercentage New reward percentage in basis points
*/
event RewardsShareUpdated(uint96 indexed assetClassId, uint16 rewardsPercentage);
/**
* @notice Emitted when rewards are set for a range of epochs
* @param startEpoch Starting epoch for which rewards were set
* @param numberOfEpochs Number of epochs affected
* @param rewardsToken Address of the reward token
* @param rewardsAmount Amount of rewards allocated per epoch
*/
event RewardsAmountSet(
uint48 indexed startEpoch, uint256 numberOfEpochs, address indexed rewardsToken, uint256 rewardsAmount
);
/**
* @notice Emitted when undistributed rewards are claimed
* @param epoch Epoch for which undistributed rewards were claimed
* @param rewardsToken Address of the reward token
* @param recipient Address receiving the undistributed rewards
* @param amount Amount of reward tokens claimed
*/
event UndistributedRewardsClaimed(
uint48 indexed epoch, address indexed rewardsToken, address indexed recipient, uint256 amount
);
/**
* @notice Emitted when a new protocol owner is set
* @param newProtocolOwner Address of the new protocol owner
*/
event ProtocolOwnerUpdated(address indexed newProtocolOwner);
// ============================
// FUNCTIONS
// ============================
/**
* @notice Distributes rewards for a given epoch
* @dev Rewards are allocated to operators, curators, and stakers based on predefined logic
* @param epoch Epoch for which rewards should be distributed
* @param batchSize Size of the batch to distribute rewards in
*/
function distributeRewards(uint48 epoch, uint48 batchSize) external;
/**
* @notice Claims staking rewards for a user
* @param rewardsToken Address of the reward token
* @param recipient Address receiving the claimed rewards
*/
function claimRewards(address rewardsToken, address recipient) external;
/**
* @notice Claims accumulated curator fees
* @param rewardsToken Address of the reward token
* @param recipient Address receiving the curator fee
*/
function claimCuratorFee(address rewardsToken, address recipient) external;
/**
* @notice Claims accumulated operator fees
* @param rewardsToken Address of the reward token
* @param recipient Address receiving the operator fee
*/
function claimOperatorFee(address rewardsToken, address recipient) external;
/**
* @notice Claims accumulated protocol fees
* @dev Only callable by an address with the PROTOCOL_OWNER_ROLE
* @param rewardsToken Address of the reward token
* @param recipient Address receiving the protocol fee
*/
function claimProtocolFee(address rewardsToken, address recipient) external;
/**
* @notice Claims undistributed rewards for a given epoch
* @dev Only callable by an address with the REWARDS_DISTRIBUTOR_ROLE role
* @param epoch Epoch for which undistributed rewards should be claimed
* @param rewardsToken Address of the reward token
* @param recipient Address receiving the undistributed rewards
*/
function claimUndistributedRewards(uint48 epoch, address rewardsToken, address recipient) external;
/**
* @notice Grants the rewards manager role to a new address
* @dev Only callable by an address with the DEFAULT_ADMIN_ROLE
* @param newRewardsManager Address to be granted the rewards manager role
*/
function setRewardsManagerRole(
address newRewardsManager
) external;
/**
* @notice Grants the rewards distributor role to a new address
* @dev Only callable by an address with the REWARDS_MANAGER_ROLE role
* @param newRewardsDistributor Address to be granted the rewards distributor role
*/
function setRewardsDistributorRole(
address newRewardsDistributor
) external;
/**
* @notice Sets a new protocol owner
* @dev Only callable by an address with the DEFAULT_ADMIN_ROLE
* @param newProtocolOwner Address of the new protocol owner
*/
function setProtocolOwner(
address newProtocolOwner
) external;
/**
* @notice Sets a new minimum required uptime
* @dev Only callable by an address with the REWARDS_MANAGER_ROLE role
* @param uptime Uptime for an epoch in seconds
*/
function setMinRequiredUptime(
uint256 uptime
) external;
/**
* @notice Updates the protocol fee percentage
* @dev Only callable by an address with the REWARDS_MANAGER_ROLE role
* @param newFee New protocol fee percentage in basis points
*/
function updateProtocolFee(
uint16 newFee
) external;
/**
* @notice Updates the operator fee percentage
* @dev Only callable by an address with the REWARDS_MANAGER_ROLE role
* @param newFee New operator fee percentage in basis points
*/
function updateOperatorFee(
uint16 newFee
) external;
/**
* @notice Updates the curator fee percentage
* @dev Only callable by an address with the REWARDS_MANAGER_ROLE role
* @param newFee New curator fee percentage in basis points
*/
function updateCuratorFee(
uint16 newFee
) external;
/**
* @notice Sets the rewards share percentage for a specific asset class
* @dev Only callable by an address with the REWARDS_MANAGER_ROLE role
* @param assetClassId ID of the asset class
* @param rewardsPercentage New reward percentage in basis points
*/
function setRewardsShareForAssetClass(uint96 assetClassId, uint16 rewardsPercentage) external;
/**
* @notice Sets the rewards amount for a range of epochs
* @dev Only callable by an address with the REWARDS_DISTRIBUTOR_ROLE role
* @param startEpoch The starting epoch for which rewards should be set
* @param numberOfEpochs The number of epochs for which the rewards should be applied
* @param rewardsToken The address of the reward token
* @param rewardsAmount The total reward amount for each epoch
*/
function setRewardsAmountForEpochs(
uint48 startEpoch,
uint48 numberOfEpochs,
address rewardsToken,
uint256 rewardsAmount
) external;
/**
* @notice Retrieves the rewards tokens and their respective amounts for a given epoch
* @dev This function allows external callers to view the rewards distributed per token for a specific epoch
* @param epoch The epoch for which to fetch reward token information
* @return tokens An array of reward token addresses
* @return amounts An array of reward amounts corresponding to each token in the `tokens` array
*/
function getRewardsAmountPerTokenFromEpoch(
uint48 epoch
) external view returns (address[] memory tokens, uint256[] memory amounts);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return 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) {
// (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.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev 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 {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 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 prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, 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 {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
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^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// 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^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice 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) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice 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 + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @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 + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @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 + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @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 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @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 + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @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;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/types/Time.sol)
pragma solidity ^0.8.20;
import {Math} from "../math/Math.sol";
import {SafeCast} from "../math/SafeCast.sol";
/**
* @dev This library provides helpers for manipulating time-related objects.
*
* It uses the following types:
* - `uint48` for timepoints
* - `uint32` for durations
*
* While the library doesn't provide specific types for timepoints and duration, it does provide:
* - a `Delay` type to represent duration that can be programmed to change value automatically at a given point
* - additional helper functions
*/
library Time {
using Time for *;
/**
* @dev Get the block timestamp as a Timepoint.
*/
function timestamp() internal view returns (uint48) {
return SafeCast.toUint48(block.timestamp);
}
/**
* @dev Get the block number as a Timepoint.
*/
function blockNumber() internal view returns (uint48) {
return SafeCast.toUint48(block.number);
}
// ==================================================== Delay =====================================================
/**
* @dev A `Delay` is a uint32 duration that can be programmed to change value automatically at a given point in the
* future. The "effect" timepoint describes when the transitions happens from the "old" value to the "new" value.
* This allows updating the delay applied to some operation while keeping some guarantees.
*
* In particular, the {update} function guarantees that if the delay is reduced, the old delay still applies for
* some time. For example if the delay is currently 7 days to do an upgrade, the admin should not be able to set
* the delay to 0 and upgrade immediately. If the admin wants to reduce the delay, the old delay (7 days) should
* still apply for some time.
*
*
* The `Delay` type is 112 bits long, and packs the following:
*
* ```
* | [uint48]: effect date (timepoint)
* | | [uint32]: value before (duration)
* ↓ ↓ ↓ [uint32]: value after (duration)
* 0xAAAAAAAAAAAABBBBBBBBCCCCCCCC
* ```
*
* NOTE: The {get} and {withUpdate} functions operate using timestamps. Block number based delays are not currently
* supported.
*/
type Delay is uint112;
/**
* @dev Wrap a duration into a Delay to add the one-step "update in the future" feature
*/
function toDelay(uint32 duration) internal pure returns (Delay) {
return Delay.wrap(duration);
}
/**
* @dev Get the value at a given timepoint plus the pending value and effect timepoint if there is a scheduled
* change after this timepoint. If the effect timepoint is 0, then the pending value should not be considered.
*/
function _getFullAt(Delay self, uint48 timepoint) private pure returns (uint32, uint32, uint48) {
(uint32 valueBefore, uint32 valueAfter, uint48 effect) = self.unpack();
return effect <= timepoint ? (valueAfter, 0, 0) : (valueBefore, valueAfter, effect);
}
/**
* @dev Get the current value plus the pending value and effect timepoint if there is a scheduled change. If the
* effect timepoint is 0, then the pending value should not be considered.
*/
function getFull(Delay self) internal view returns (uint32, uint32, uint48) {
return _getFullAt(self, timestamp());
}
/**
* @dev Get the current value.
*/
function get(Delay self) internal view returns (uint32) {
(uint32 delay, , ) = self.getFull();
return delay;
}
/**
* @dev Update a Delay object so that it takes a new duration after a timepoint that is automatically computed to
* enforce the old delay at the moment of the update. Returns the updated Delay object and the timestamp when the
* new delay becomes effective.
*/
function withUpdate(
Delay self,
uint32 newValue,
uint32 minSetback
) internal view returns (Delay updatedDelay, uint48 effect) {
uint32 value = self.get();
uint32 setback = uint32(Math.max(minSetback, value > newValue ? value - newValue : 0));
effect = timestamp() + setback;
return (pack(value, newValue, effect), effect);
}
/**
* @dev Split a delay into its components: valueBefore, valueAfter and effect (transition timepoint).
*/
function unpack(Delay self) internal pure returns (uint32 valueBefore, uint32 valueAfter, uint48 effect) {
uint112 raw = Delay.unwrap(self);
valueAfter = uint32(raw);
valueBefore = uint32(raw >> 32);
effect = uint48(raw >> 64);
return (valueBefore, valueAfter, effect);
}
/**
* @dev pack the components into a Delay object.
*/
function pack(uint32 valueBefore, uint32 valueAfter, uint48 effect) internal pure returns (Delay) {
return Delay.wrap((uint112(effect) << 64) | (uint112(valueBefore) << 32) | uint112(valueAfter));
}
}// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// SPDX-License-Identifier: Ecosystem
pragma solidity 0.8.25;
/**
* @dev Validator status
*/
enum ValidatorStatus {
Unknown,
PendingAdded,
Active,
PendingRemoved,
Completed,
Invalidated
}
/**
* @dev Specifies the owner of a validator's remaining balance or disable owner on the P-Chain.
* P-Chain addresses are also 20-bytes, so we use the address type to represent them.
*/
struct PChainOwner {
uint32 threshold;
address[] addresses;
}
/**
* @dev Contains the active state of a Validator
*/
struct Validator {
ValidatorStatus status;
bytes nodeID;
uint64 startingWeight;
uint64 messageNonce;
uint64 weight;
uint64 startedAt;
uint64 endedAt;
}
/**
* @dev Describes the current churn period
*/
struct ValidatorChurnPeriod {
uint256 startedAt;
uint64 initialWeight;
uint64 totalWeight;
uint64 churnAmount;
}
/**
* @notice Validator Manager settings, used to initialize the Validator Manager
* @notice The l1ID is the ID of the L1 that the Validator Manager is managing
* @notice The churnPeriodSeconds is the duration of the churn period in seconds
* @notice The maximumChurnPercentage is the maximum percentage of the total weight that can be added or removed in a single churn period
*/
struct ValidatorManagerSettings {
bytes32 l1ID;
uint64 churnPeriodSeconds;
uint8 maximumChurnPercentage;
}
/**
* @dev Description of the conversion data used to convert
* a subnet to an L1 on the P-Chain.
* This data is the pre-image of a hash that is authenticated by the P-Chain
* and verified by the Validator Manager.
*/
struct ConversionData {
bytes32 l1ID;
bytes32 validatorManagerBlockchainID;
address validatorManagerAddress;
InitialValidator[] initialValidators;
}
/**
* @dev Specifies an initial validator, used in the conversion data.
*/
struct InitialValidator {
bytes nodeID;
bytes blsPublicKey;
uint64 weight;
}
/**
* @dev Specifies a validator to register.
*/
struct ValidatorRegistrationInput {
bytes nodeID;
bytes blsPublicKey;
uint64 registrationExpiry;
PChainOwner remainingBalanceOwner;
PChainOwner disableOwner;
}
/**
* @notice Interface for Validator Manager contracts that implement Subnet-only Validator management.
*/
interface IValidatorManager {
/**
* @notice Emitted when a new validation period is created by locking stake in the manager contract.
* Note: This event does not mean that the validation period has been successfully registered on the P-Chain,
* and rewards for this validation period will not begin accruing until the {ValidationPeriodRegistered} event is
* emitted.
* @param validationID The ID of the validation period being created.
* @param nodeID The node ID of the validator being registered.
* @param registerValidationMessageID The ID of the ICM message that will be sent to the P-Chain to register the
* validation period.
* @param weight The weight of the validator being registered.
* @param registrationExpiry The Unix timestamp after which the reigistration is no longer valid on the P-Chain.
*/
event ValidationPeriodCreated(
bytes32 indexed validationID,
bytes indexed nodeID,
bytes32 indexed registerValidationMessageID,
uint64 weight,
uint64 registrationExpiry
);
event InitialValidatorCreated(
bytes32 indexed validationID, bytes indexed nodeID, uint64 weight
);
/**
* @notice Emitted when the staking manager learns that the validation period has been successfully registered
* on the P-Chain. Rewards for this validation period will begin accruing when this event is emitted.
* @param validationID The ID of the validation period being registered.
* @param weight The weight of the validator being registered.
* @param timestamp The time at which the validation period was registered with the contract.
*/
event ValidationPeriodRegistered(
bytes32 indexed validationID, uint64 weight, uint256 timestamp
);
/**
* @notice Emitted when the process of ending a registered validation period is started by calling
* {initializeEndValidation}.
* Note: The stake for this validation period remains locked until a {ValidationPeriodRemoved} event is emitted.
* @param validationID The ID of the validation period being removed.
* @param setWeightMessageID The ID of the ICM message that updates the validator's weight on the P-Chain.
* @param weight The weight of the validator being removed.
* @param endTime The time at which the removal was initiated.
*/
event ValidatorRemovalInitialized(
bytes32 indexed validationID,
bytes32 indexed setWeightMessageID,
uint64 weight,
uint256 endTime
);
/**
* @notice Emitted when the stake for a validation period is unlocked and returned to the staker.
* This is done by calling {completeEndValidation}, which provides proof from the P-Chain that the
* validation period is not active and will never be active in the future.
* @param validationID The ID of the validation period being removed.
*/
event ValidationPeriodEnded(bytes32 indexed validationID, ValidatorStatus indexed status);
/**
* @notice Event emitted when validator weight is updated.
* @param validationID The ID of the validation period being updated
* @param nonce The message nonce used to update the validator weight
* @param weight The updated validator weight that is sent to the P-Chain
* @param setWeightMessageID The ID of the ICM message that updates the validator's weight on the P-Chain
*/
event ValidatorWeightUpdate(
bytes32 indexed validationID,
uint64 indexed nonce,
uint64 weight,
bytes32 setWeightMessageID
);
/**
* @notice Verifies and sets the initial validator set for the chain through a P-Chain SubnetToL1ConversionMessage.
* @param conversionData The subnet conversion message data used to recompute and verify against the conversionID.
* @param messsageIndex The index that contains the SubnetToL1ConversionMessage ICM message containing the conversionID to be verified against the provided {ConversionData}
*/
function initializeValidatorSet(
ConversionData calldata conversionData,
uint32 messsageIndex
) external;
/**
* @notice Resubmits a validator registration message to be sent to the P-Chain.
* Only necessary if the original message can't be delivered due to validator churn.
* @param validationID The ID of the validation period being registered.
*/
function resendRegisterValidatorMessage(bytes32 validationID) external;
/**
* @notice Completes the validator registration process by returning an acknowledgement of the registration of a
* validationID from the P-Chain.
* @param messageIndex The index of the ICM message to be received providing the acknowledgement.
*/
function completeValidatorRegistration(uint32 messageIndex) external;
/**
* @notice Resubmits a validator end message to be sent to the P-Chain.
* Only necessary if the original message can't be delivered due to validator churn.
* @param validationID The ID of the validation period being ended.
*/
function resendEndValidatorMessage(bytes32 validationID) external;
/**
* @notice Completes the process of ending a validation period by receiving an acknowledgement from the P-Chain
* that the validation ID is not active and will never be active in the future. Returns the the stake associated
* with the validation.
* Note: This function can be used for successful validation periods that have been explicitly ended by calling
* {initializeEndValidation} or for validation periods that never began on the P-Chain due to the {registrationExpiry} being reached.
* @param messageIndex The index of the ICM message to be received providing the proof the validation is not active
* and never will be active on the P-Chain.
*/
function completeEndValidation(uint32 messageIndex) external;
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
import {
BalancerValidatorManagerSettings,
IBalancerValidatorManager
} from "../../interfaces/ValidatorManager/IBalancerValidatorManager.sol";
import {ValidatorManager} from "@avalabs/icm-contracts/validator-manager/ValidatorManager.sol";
import {ValidatorMessages} from "@avalabs/icm-contracts/validator-manager/ValidatorMessages.sol";
import {
IValidatorManager,
Validator,
ValidatorChurnPeriod,
ValidatorManagerSettings,
ValidatorRegistrationInput,
ValidatorStatus
} from "@avalabs/icm-contracts/validator-manager/interfaces/IValidatorManager.sol";
import {OwnableUpgradeable} from
"@openzeppelin/[email protected]/access/OwnableUpgradeable.sol";
import {EnumerableMap} from "@openzeppelin/[email protected]/utils/structs/EnumerableMap.sol";
/**
* @title BalancerValidatorManager
* @author ADDPHO
* @notice The Balancer Validator Manager contract allows to balance the weight of an L1 between multiple security modules.
* @custom:security-contact [email protected]
* @custom:oz-upgrades-unsafe-allow external-library-linking
* @custom:oz-upgrades-from PoAValidatorManager
*/
contract BalancerValidatorManager is
IBalancerValidatorManager,
ValidatorManager,
OwnableUpgradeable
{
using EnumerableMap for EnumerableMap.AddressToUintMap;
/// @custom:storage-location erc7201:suzaku.storage.BalancerValidatorManager
struct BalancerValidatorManagerStorage {
/// @notice The registered security modules along with their maximum weight
EnumerableMap.AddressToUintMap securityModules;
/// @notice The total weight of all validators for a given security module
mapping(address securityModule => uint64 weight) securityModuleWeight;
/// @notice The security module to which each validator belongs
mapping(bytes32 validationID => address securityModule) validatorSecurityModule;
/// @notice Validators pending weight updates
mapping(bytes32 validationID => bytes32 messageID) validatorPendingWeightUpdate;
}
// keccak256(abi.encode(uint256(keccak256("suzaku.storage.BalancerValidatorManager")) - 1)) & ~bytes32(uint256(0xff));
bytes32 public constant BALANCER_VALIDATOR_MANAGER_STORAGE_LOCATION =
0x9d2d7650aa35ca910e5b713f6b3de6524a06fbcb31ffc9811340c6f331a23400;
// solhint-disable func-name-mixedcase, ordering
function _getBalancerValidatorManagerStorage()
private
pure
returns (BalancerValidatorManagerStorage storage $)
{
// solhint-disable-next-line no-inline-assembly
assembly {
$.slot := BALANCER_VALIDATOR_MANAGER_STORAGE_LOCATION
}
}
modifier onlySecurityModule() {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
if (!$.securityModules.contains(msg.sender)) {
revert BalancerValidatorManager__SecurityModuleNotRegistered(msg.sender);
}
_;
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @notice Initialize the Balancer Validator Manager
* @dev This function is reinitializer(2) because it is upgrated from PoAValidatorManager
* https://github.com/ava-labs/icm-contracts/blob/validator-manager-v1.0.0/contracts/validator-manager/PoAValidatorManager.sol
* @param settings The settings for the Balancer Validator Manager
*/
function initialize(
BalancerValidatorManagerSettings calldata settings
) external reinitializer(2) {
__BalancerValidatorManager_init(settings);
}
function __BalancerValidatorManager_init(
BalancerValidatorManagerSettings calldata settings
) internal onlyInitializing {
__ValidatorManager_init(settings.baseSettings);
__Ownable_init(settings.initialOwner);
__BalancerValidatorManager_init_unchained(
settings.initialSecurityModule,
settings.initialSecurityModuleMaxWeight,
settings.migratedValidators
);
}
function __BalancerValidatorManager_init_unchained(
address initialSecurityModule,
uint64 initialSecurityModuleMaxWeight,
bytes[] calldata migratedValidators
) internal onlyInitializing {
ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
// Ensure initial security module max weight is sufficient
if (initialSecurityModuleMaxWeight < vms._churnTracker.totalWeight) {
revert BalancerValidatorManager__InitialSecurityModuleMaxWeightLowerThanTotalWeight(
initialSecurityModule, initialSecurityModuleMaxWeight, vms._churnTracker.totalWeight
);
}
_setUpSecurityModule(initialSecurityModule, initialSecurityModuleMaxWeight);
_migrateValidators(initialSecurityModule, migratedValidators);
}
// solhint-enable func-name-mixedcase
/// @inheritdoc IBalancerValidatorManager
function setUpSecurityModule(address securityModule, uint64 maxWeight) external onlyOwner {
_setUpSecurityModule(securityModule, maxWeight);
}
/// @inheritdoc IBalancerValidatorManager
function initializeValidatorRegistration(
ValidatorRegistrationInput calldata registrationInput,
uint64 weight
) external onlySecurityModule returns (bytes32 validationID) {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
validationID = _initializeValidatorRegistration(registrationInput, weight);
// Update the security module weight
uint64 newSecurityModuleWeight = $.securityModuleWeight[msg.sender] + weight;
_updateSecurityModuleWeight(msg.sender, newSecurityModuleWeight);
$.validatorSecurityModule[validationID] = msg.sender;
return validationID;
}
/// @inheritdoc IBalancerValidatorManager
function initializeEndValidation(
bytes32 validationID
) external onlySecurityModule returns (Validator memory validator) {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
// Ensure the validator weight is not being updated
if ($.validatorPendingWeightUpdate[validationID] != 0) {
revert BalancerValidatorManager__PendingWeightUpdate(validationID);
}
_checkValidatorSecurityModule(validationID, msg.sender);
validator = _initializeEndValidation(validationID);
// Update the security module weight
uint64 newSecurityModuleWeight = $.securityModuleWeight[msg.sender] - validator.weight;
_updateSecurityModuleWeight(msg.sender, newSecurityModuleWeight);
return validator;
}
/// @inheritdoc IValidatorManager
function completeEndValidation(
uint32 messageIndex
) external {
(bytes32 validationID, Validator memory validator) = _completeEndValidation(messageIndex);
// Update the security module weight only if the validation was invalidated
if (validator.status == ValidatorStatus.Invalidated) {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
address securityModule = $.validatorSecurityModule[validationID];
uint64 newSecurityModuleWeight =
$.securityModuleWeight[securityModule] - validator.weight;
_updateSecurityModuleWeight(securityModule, newSecurityModuleWeight);
}
}
/// @inheritdoc IBalancerValidatorManager
function initializeValidatorWeightUpdate(
bytes32 validationID,
uint64 newWeight
) external onlySecurityModule returns (Validator memory) {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
// Check that the newWeight is greater than zero
if (newWeight == 0) {
revert BalancerValidatorManager__NewWeightIsZero();
}
// Ensure the validation period is active and that the validator is not already being updated
// The initial validator set must have been set already to have active validators.
Validator storage validator = vms._validationPeriods[validationID];
if (validator.status != ValidatorStatus.Active) {
revert InvalidValidatorStatus(validator.status);
}
if ($.validatorPendingWeightUpdate[validationID] != 0) {
revert BalancerValidatorManager__PendingWeightUpdate(validationID);
}
_checkValidatorSecurityModule(validationID, msg.sender);
uint64 oldWeight = validator.weight;
(, bytes32 messageID) = _setValidatorWeight(validationID, newWeight);
// Update the security module weight
uint64 newSecurityModuleWeight = $.securityModuleWeight[msg.sender] + newWeight - oldWeight;
_updateSecurityModuleWeight(msg.sender, newSecurityModuleWeight);
$.validatorPendingWeightUpdate[validationID] = messageID;
return validator;
}
/// @inheritdoc IBalancerValidatorManager
function completeValidatorWeightUpdate(bytes32 validationID, uint32 messageIndex) external {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
Validator storage validator = vms._validationPeriods[validationID];
// Check that the validator is active and being updated
if (validator.status != ValidatorStatus.Active) {
revert InvalidValidatorStatus(validator.status);
}
if ($.validatorPendingWeightUpdate[validationID] == 0) {
revert BalancerValidatorManager__NoPendingWeightUpdate(validationID);
}
// Unpack the Warp message
(bytes32 messageValidationID, uint64 nonce,) = ValidatorMessages
.unpackL1ValidatorWeightMessage(_getPChainWarpMessage(messageIndex).payload);
if (validationID != messageValidationID) {
revert InvalidValidationID(validationID);
}
if (validator.messageNonce < nonce) {
revert BalancerValidatorManager__InvalidNonce(nonce);
}
delete $.validatorPendingWeightUpdate[validationID];
}
function resendValidatorWeightUpdate(
bytes32 validationID
) external {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
Validator storage validator = vms._validationPeriods[validationID];
if (validator.status != ValidatorStatus.Active) {
revert InvalidValidatorStatus(validator.status);
}
if ($.validatorPendingWeightUpdate[validationID] == 0) {
revert BalancerValidatorManager__NoPendingWeightUpdate(validationID);
}
if (validator.messageNonce == 0) {
revert InvalidValidationID(validationID);
}
// Submit the message to the Warp precompile.
WARP_MESSENGER.sendWarpMessage(
ValidatorMessages.packL1ValidatorWeightMessage(
validationID, validator.messageNonce, validator.weight
)
);
}
/// @inheritdoc IBalancerValidatorManager
function getChurnPeriodSeconds() external view returns (uint64 churnPeriodSeconds) {
return _getChurnPeriodSeconds();
}
/// @inheritdoc IBalancerValidatorManager
function getMaximumChurnPercentage() external view returns (uint64 maximumChurnPercentage) {
ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
return vms._maximumChurnPercentage;
}
/// @inheritdoc IBalancerValidatorManager
function getCurrentChurnPeriod()
external
view
returns (ValidatorChurnPeriod memory churnPeriod)
{
ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
return vms._churnTracker;
}
/// @inheritdoc IBalancerValidatorManager
function getSecurityModules() external view returns (address[] memory securityModules) {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
return $.securityModules.keys();
}
/// @inheritdoc IBalancerValidatorManager
function getSecurityModuleWeights(
address securityModule
) external view returns (uint64 weight, uint64 maxWeight) {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
weight = $.securityModuleWeight[securityModule];
maxWeight = uint64($.securityModules.get(securityModule));
return (weight, maxWeight);
}
/// @inheritdoc IBalancerValidatorManager
function getL1ID() external view returns (bytes32 l1ID) {
ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
return vms._l1ID;
}
/// @inheritdoc IBalancerValidatorManager
function isValidatorPendingWeightUpdate(
bytes32 validationID
) external view returns (bool) {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
return $.validatorPendingWeightUpdate[validationID] != 0;
}
function _checkValidatorSecurityModule(
bytes32 validationID,
address securityModule
) internal view {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
// If the validator has no associated security module, it is an initial validator
// and can be managed by any security module
if ($.validatorSecurityModule[validationID] == address(0)) {
return;
} else if ($.validatorSecurityModule[validationID] != securityModule) {
revert BalancerValidatorManager__ValidatorNotBelongingToSecurityModule(
validationID, securityModule
);
}
}
function _setUpSecurityModule(address securityModule, uint64 maxWeight) internal {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
uint64 currentWeight = $.securityModuleWeight[securityModule];
if (maxWeight < currentWeight) {
revert BalancerValidatorManager__SecurityModuleNewMaxWeightLowerThanCurrentWeight(
securityModule, maxWeight, currentWeight
);
}
if (maxWeight == 0) {
if (!$.securityModules.remove(securityModule)) {
revert BalancerValidatorManager__SecurityModuleNotRegistered(securityModule);
}
} else {
$.securityModules.set(securityModule, uint256(maxWeight));
}
emit SetUpSecurityModule(securityModule, maxWeight);
}
function _updateSecurityModuleWeight(address securityModule, uint64 weight) internal {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
uint64 maxWeight = uint64($.securityModules.get(securityModule));
if (weight > maxWeight) {
revert BalancerValidatorManager__SecurityModuleMaxWeightExceeded(
securityModule, weight, maxWeight
);
}
$.securityModuleWeight[securityModule] = weight;
}
function _migrateValidators(
address initialSecurityModule,
bytes[] calldata migratedValidators
) internal {
BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
// Add the migrated validators to the initial security module
uint64 migratedValidatorsTotalWeight = 0;
for (uint256 i = 0; i < migratedValidators.length; i++) {
bytes32 validationID = registeredValidators(migratedValidators[i]);
// Ensure validator hasn't already been migrated
if ($.validatorSecurityModule[validationID] != address(0)) {
revert BalancerValidatorManager__ValidatorAlreadyMigrated(validationID);
}
Validator storage validator = vms._validationPeriods[validationID];
$.validatorSecurityModule[validationID] = initialSecurityModule;
migratedValidatorsTotalWeight += validator.weight;
}
// Check that the migrated validators total weight equals the current L1 total weight
if (migratedValidatorsTotalWeight != vms._churnTracker.totalWeight) {
revert BalancerValidatorManager__MigratedValidatorsTotalWeightMismatch(
migratedValidatorsTotalWeight, vms._churnTracker.totalWeight
);
}
// Update the initial security module weight directly since we've already validated the max weight
$.securityModuleWeight[initialSecurityModule] = migratedValidatorsTotalWeight;
}
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
interface IOperatorRegistry {
event RegisterOperator(address indexed operator);
event SetMetadataURL(address indexed operator, string metadataURL);
error OperatorRegistry__OperatorAlreadyRegistered();
error OperatorRegistry__OperatorNotRegistered();
/// @notice Register an operator with its metadata URL
function registerOperator(
string calldata metadataURL
) external;
/**
* @notice Check if an address is registered as an operator
* @param operator The address to check
* @return True if the address is registered as an operator, false otherwise
*/
function isRegistered(
address operator
) external view returns (bool);
/**
* @notice Get the operator at a specific index
* @param index The index of the operator to get
* @return The address of the operator at the specified index
* @return The metadata URL of the operator at the specified index
*/
function getOperatorAt(
uint256 index
) external view returns (address, string memory);
/**
* @notice Get the total number of operators
* @return Total number of operators
*/
function totalOperators() external view returns (uint256);
/**
* @notice Get all operators
* @return Array of all operators
* @return Array of all operators' metadata URLs
*/
function getAllOperators() external view returns (address[] memory, string[] memory);
/**
* @notice Set the metadata URL of an operator
* @param metadataURL The new metadata URL
*/
function setMetadataURL(
string calldata metadataURL
) external;
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
import {
IValidatorManager,
Validator,
ValidatorStatus,
ValidatorRegistrationInput,
PChainOwner
} from "@avalabs/teleporter/validator-manager/interfaces/IValidatorManager.sol";
/**
* @title IAvalancheL1Middleware
* @notice Manages operator registration, asset classes, stake accounting, and slashing for Avalanche L1
*/
interface IAvalancheL1Middleware {
// Errors
error AvalancheL1Middleware__ActiveSecondaryAssetCLass(uint256 assetClassId);
error AvalancheL1Middleware__AssetClassNotActive(uint256 assetClassId);
error AvalancheL1Middleware__AssetStillInUse(uint256 assetClassId);
error AvalancheL1Middleware__AlreadyRebalanced(address operator, uint48 epoch);
error AvalancheL1Middleware__WeightUpdateNotPending(bytes32 validationId);
error AvalancheL1Middleware__CollateralNotInAssetClass(address collateral, uint96 assetClassId);
error AvalancheL1Middleware__EpochError(uint48 epochStartTs);
error AvalancheL1Middleware__MaxL1LimitZero();
error AvalancheL1Middleware__NoSlasher();
error AvalancheL1Middleware__NotEnoughFreeStakeSecondaryAssetClasses();
error AvalancheL1Middleware__NodeNotActive();
error AvalancheL1Middleware__NotEnoughFreeStake(uint256 newStake);
error AvalancheL1Middleware__StakeTooHigh(uint256 newStake, uint256 maxStake);
error AvalancheL1Middleware__StakeTooLow(uint256 newStake, uint256 minStake);
error AvalancheL1Middleware__OperatorGracePeriodNotPassed(uint48 disabledTime, uint48 slashingWindow);
error AvalancheL1Middleware__OperatorAlreadyRegistered(address operator);
error AvalancheL1Middleware__OperatorNotOptedIn(address operator, address l1ValidatorManager);
error AvalancheL1Middleware__OperatorNotRegistered(address operator);
error AvalancheL1Middleware__SlashingWindowTooShort(uint48 slashingWindow, uint48 epochDuration);
error AvalancheL1Middleware__TooBigSlashAmount();
error AvalancheL1Middleware__NodeNotFound(bytes32 nodeId);
error AvalancheL1Middleware__SecurityModuleCapacityNotEnough(uint256 securityModuleCapacity, uint256 minStake);
error AvalancheL1Middleware__WeightUpdatePending(bytes32 validationID);
error AvalancheL1Middleware__NodeStateNotUpdated(bytes32 validationID);
error AvalancheL1Middleware__NotEpochUpdatePeriod(uint48 timeNow, uint48 epochUpdatePeriod);
error AvalancheL1Middleware__NotImplemented();
error AvalancheL1Middleware__NodePendingRemoval(bytes32 nodeId);
error AvalancheL1Middleware__NodePendingUpdate(bytes32 nodeId);
error AvalancheL1Middleware__ZeroAddress(string name);
error AvalancheL1Middleware__InvalidScaleFactor();
error AvalancheL1Middleware__ManualEpochUpdateRequired(uint48 epochsPending, uint48 maxAutoUpdates);
error AvalancheL1Middleware__NoEpochsToProcess();
error AvalancheL1Middleware__TooManyEpochsRequested(uint48 requested, uint48 pending);
// Events
/**
* @notice Emitted when a node is added
* @param operator The operator who added the node
* @param nodeId The ID of the node
* @param stake The stake assigned to the node
* @param validationID The validation identifier from BalancerValidatorManager
*/
event NodeAdded(address indexed operator, bytes32 indexed nodeId, uint256 stake, bytes32 indexed validationID);
/**
* @notice Emitted when a node is removed
* @param operator The operator who removed the node
* @param nodeId The ID of the node
* @param validationID The validation identifier from BalancerValidatorManager
*/
event NodeRemoved(address indexed operator, bytes32 indexed nodeId, bytes32 indexed validationID);
/**
* @notice Emitted when a single node's stake is updated
* @param operator The operator who owns the node
* @param nodeId The ID of the node
* @param newStake The new stake of the node
* @param validationID The validation identifier from BalancerValidatorManager
*/
event NodeStakeUpdated(
address indexed operator, bytes32 indexed nodeId, uint256 newStake, bytes32 indexed validationID
);
/**
* @notice Emitted when the operator has leftover stake after rebalancing
* @param operator The operator who has leftover stake
* @param leftoverStake The amount of leftover stake
*/
event OperatorHasLeftoverStake(address indexed operator, uint256 leftoverStake);
/**
* @notice Emitted when all node stakes are updated for an operator
* @param operator The operator
* @param newStake The total new stake for the operator
*/
event AllNodeStakesUpdated(address indexed operator, uint256 newStake);
/**
* @notice Emitted when the Vault Manager is updated
* @param oldVaultManager The old Vault Manager address
* @param newVaultManager The new Vault Manager address
*/
event VaultManagerUpdated(address indexed oldVaultManager, address indexed newVaultManager);
/**
* @notice Emitted when the node stake cache is manually processed
* @param upToEpoch The epoch to process up to
* @param epochsProcessedCount The number of epochs processed
*/
event NodeStakeCacheManuallyProcessed(uint48 upToEpoch, uint48 epochsProcessedCount);
/**
* @dev Simple struct to return operator stake and key.
*/
struct OperatorData {
uint256 stake;
bytes32 key;
}
// External functions
/**
* @notice Activates a secondary asset class
* @param assetClassId The asset class ID to activate
*/
function activateSecondaryAssetClass(
uint256 assetClassId
) external;
/**
* @notice Deactivates a secondary asset class
* @param assetClassId The asset class ID to deactivate
*/
function deactivateSecondaryAssetClass(
uint256 assetClassId
) external;
/**
* @notice Registers a new operator and enables it
* @param operator The operator address
*/
function registerOperator(
address operator
) external;
/**
* @notice Disables an operator
* @param operator The operator address
*/
function disableOperator(
address operator
) external;
/**
* @notice Enables an operator
* @param operator The operator address
*/
function enableOperator(
address operator
) external;
/**
* @notice Removes an operator if grace period has passed
* @param operator The operator address
*/
function removeOperator(
address operator
) external;
/**
* @notice Add a new node => create a new validator.
* Check the new node stake also ensure security module capacity.
* @param nodeId The node ID
* @param blsKey The BLS key
* @param registrationExpiry The Unix timestamp after which the reigistration is no longer valid on the P-Chain
* @param remainingBalanceOwner The owner of a validator's remaining balance
* @param disableOwner The owner of a validator's disable owner on the P-Chain
* @param stakeAmount The initial stake of the node to be added(optional)
*/
function addNode(
bytes32 nodeId,
bytes calldata blsKey,
uint64 registrationExpiry,
PChainOwner calldata remainingBalanceOwner,
PChainOwner calldata disableOwner,
uint256 stakeAmount
) external;
/**
* @notice Remove a node => remove a validator.
* @param nodeId The node ID
*/
function removeNode(
bytes32 nodeId
) external;
/**
* @notice Rebalance node stakes once per epoch for an operator.
* @param operator The operator address
* @param limitStake The maximum stake adjustment (add or remove) allowed per node per call.
*/
function forceUpdateNodes(address operator, uint256 limitStake) external;
/**
* @notice Update the stake of a validator.
* @param nodeId The node ID.
* @param stakeAmount The new stake.
*/
function initializeValidatorStakeUpdate(bytes32 nodeId, uint256 stakeAmount) external;
/**
* @notice Finalize a pending validator registration
* @param operator The operator address
* @param nodeId The node ID
* @param messageIndex The message index
*/
function completeValidatorRegistration(address operator, bytes32 nodeId, uint32 messageIndex) external;
/**
* @notice Finalize a pending stake update
* @param nodeId The node ID
* @param messageIndex The message index
*/
function completeStakeUpdate(bytes32 nodeId, uint32 messageIndex) external;
/**
* @notice Finalize a pending validator removal
* @param messageIndex The message index
*/
function completeValidatorRemoval(
uint32 messageIndex
) external;
/**
* @notice Slashes an operator's stake
* @param epoch The epoch of the slash
* @param operator The operator being slashed
* @param amount The slash amount
* @param assetClassId The asset class ID
*/
function slash(uint48 epoch, address operator, uint256 amount, uint96 assetClassId) external;
/**
* @notice Calculates and caches total stake for an epoch
* @param epoch The epoch number
* @param assetClassId The asset class ID
* @return totalStake The total stake calculated and cached
*/
function calcAndCacheStakes(uint48 epoch, uint96 assetClassId) external returns (uint256);
/**
* @notice Calculates and caches node stakes for all operators retroactively for all epochs
*/
function calcAndCacheNodeStakeForAllOperators() external;
/**
* @notice Fetches the primary and secondary asset classes
* @return primary The primary asset class
* @return secondaries An array of secondary asset classes
*/
function getActiveAssetClasses() external view returns (uint256 primary, uint256[] memory secondaries);
/**
* @notice Checks if the classId is active
* @param assetClassId The asset class ID
* @return bool True if active
*/
function isActiveAssetClass(
uint96 assetClassId
) external view returns (bool);
/**
* @notice Gets the start timestamp for a given epoch
* @param epoch The epoch number
* @return timestamp The start time of that epoch
*/
function getEpochStartTs(
uint48 epoch
) external view returns (uint48);
/**
* @notice Gets the epoch number at a given timestamp
* @param timestamp The timestamp
* @return epoch The epoch at that time
*/
function getEpochAtTs(
uint48 timestamp
) external view returns (uint48);
/**
* @notice Gets the current epoch based on the current block time
* @return epoch The current epoch
*/
function getCurrentEpoch() external view returns (uint48);
/**
* @notice Returns an operator's stake at a given epoch for a specific asset class
* @param operator The operator address
* @param epoch The epoch number
* @param assetClassId The asset class ID
* @return stake The operator's stake
*/
function getOperatorStake(address operator, uint48 epoch, uint96 assetClassId) external view returns (uint256);
/**
* @notice Returns total stake across all operators in a specific epoch
* @param epoch The epoch number
* @param assetClassId The asset class ID
* @return The total stake in that epoch
*/
function getTotalStake(uint48 epoch, uint96 assetClassId) external view returns (uint256);
/**
* @notice Returns all operators
*/
function getAllOperators() external view returns (address[] memory);
/**
* @notice Returns the cached stake for a given node in the specified epoch, based on its Validation ID.
* @param epoch The target Not enough free stake to add nodeepoch.
* @param validationId The node ID.
* @return The node stake from the cache.
*/
function getNodeStake(uint48 epoch, bytes32 validationId) external view returns (uint256);
/**
* @notice Returns the current epoch number
* @param operator The operator address
* @param epoch The epoch number
* @return activeNodeIds The list of nodes
*/
function getActiveNodesForEpoch(address operator, uint48 epoch) external view returns (bytes32[] memory);
/**
* @notice Returns the available stake for an operator
* @param operator The operator address
* @return The available stake
*/
function getOperatorAvailableStake(
address operator
) external view returns (uint256);
/**
* @notice Summation of node stakes from the nodeStakeCache.
* @param operator The operator address.
* @return registeredStake The sum of node stakes.
*/
function getOperatorUsedStakeCached(
address operator
) external view returns (uint256);
/**
* @notice Returns the Vault Manager address associated to this middleware
* @return Address Vault Manager
*/
function getVaultManager() external view returns (address);
/**
* @notice Returns the true active stake for an operator in a given epoch and asset class
* @param epoch The epoch number
* @param operator The operator address
* @param assetClass The asset class ID
* @return The true active stake
*/
function getOperatorUsedStakeCachedPerEpoch(
uint48 epoch,
address operator,
uint96 assetClass
) external view returns (uint256);
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
interface IOptInService {
error OptInService__AlreadyOptedIn();
error OptInService__ExpiredSignature();
error OptInService__InvalidSignature();
error OptInService__NotOptedIn();
error OptInService__NotWhereRegistered();
error OptInService__NotWho();
error OptInService__OptOutCooldown();
error OptInService__NotWhereEntity();
/**
* @notice Emitted when a "who" opts into a "where" entity.
* @param who address of the "who"
* @param where address of the "where" entity
*/
event OptIn(address indexed who, address indexed where);
/**
* @notice Emitted when a "who" opts out from a "where" entity.
* @param who address of the "who"
* @param where address of the "where" entity
*/
event OptOut(address indexed who, address indexed where);
/**
* @notice Emitted when the nonce of a "who" to a "where" entity is increased.
* @param who address of the "who"
* @param where address of the "where" entity
*/
event IncreaseNonce(address indexed who, address indexed where);
/**
* @notice Get the "who" registry's address.
* @return address of the "who" registry
*/
function WHO_REGISTRY() external view returns (address);
/**
* @notice Get the address of the registry where to opt-in.
* @return address of the "where" registry
*/
function WHERE_REGISTRY() external view returns (address);
/**
* @notice Get if a given "who" is opted-in to a particular "where" entity at a given timestamp using a hint.
* @param who address of the "who"
* @param where address of the "where" entity
* @param timestamp time point to get if the "who" is opted-in at
* @param hint hint for the checkpoint index
* @return if the "who" is opted-in at the given timestamp
*/
function isOptedInAt(
address who,
address where,
uint48 timestamp,
bytes calldata hint
) external view returns (bool);
/**
* @notice Check if a given "who" is opted-in to a particular "where" entity.
* @param who address of the "who"
* @param where address of the "where" entity
* @return if the "who" is opted-in
*/
function isOptedIn(address who, address where) external view returns (bool);
/**
* @notice Get the nonce of a given "who" to a particular "where" entity.
* @param who address of the "who"
* @param where address of the "where" entity
* @return nonce
*/
function nonces(address who, address where) external view returns (uint256);
/**
* @notice Opt-in a calling "who" to a particular "where" entity.
* @param where address of the "where" entity
*/
function optIn(
address where
) external;
/**
* @notice Opt-in a "who" to a particular "where" entity with a signature.
* @param who address of the "who"
* @param where address of the "where" entity
* @param deadline time point until the signature is valid (inclusively)
* @param signature signature of the "who"
*/
function optIn(address who, address where, uint48 deadline, bytes calldata signature) external;
/**
* @notice Opt-out a calling "who" from a particular "where" entity.
* @param where address of the "where" entity
*/
function optOut(
address where
) external;
/**
* @notice Opt-out a "who" from a particular "where" entity with a signature.
* @param who address of the "who"
* @param where address of the "where" entity
* @param deadline time point until the signature is valid (inclusively)
* @param signature signature of the "who"
*/
function optOut(address who, address where, uint48 deadline, bytes calldata signature) external;
/**
* @notice Increase the nonce of a given "who" to a particular "where" entity.
* @param where address of the "where" entity
* @dev It can be used to invalidate a given signature.
*/
function increaseNonce(
address where
) external;
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import {IAssetClassRegistry} from "../../interfaces/middleware/IAssetClassRegistry.sol";
abstract contract AssetClassRegistry is IAssetClassRegistry, Ownable {
using EnumerableSet for EnumerableSet.AddressSet;
using EnumerableSet for EnumerableSet.UintSet;
struct AssetClass {
EnumerableSet.AddressSet assets;
uint256 minValidatorStake;
uint256 maxValidatorStake;
}
EnumerableSet.UintSet internal assetClassIds;
mapping(uint256 => AssetClass) internal assetClasses;
constructor(
address initialOwner
) Ownable(initialOwner) {}
/// @inheritdoc IAssetClassRegistry
function addAssetClass(
uint256 assetClassId,
uint256 minValidatorStake,
uint256 maxValidatorStake,
address initialAsset
) external onlyOwner {
_addAssetClass(assetClassId, minValidatorStake, maxValidatorStake, initialAsset);
}
/// @inheritdoc IAssetClassRegistry
function addAssetToClass(uint256 assetClassId, address asset) external onlyOwner {
if (!assetClassIds.contains(assetClassId)) {
revert AssetClassRegistry__AssetClassNotFound();
}
if (asset == address(0)) {
revert AssetClassRegistry__InvalidAsset();
}
_addAssetToClass(assetClassId, asset);
}
/// @inheritdoc IAssetClassRegistry
function removeAssetFromClass(uint256 assetClassId, address asset) public virtual onlyOwner {
_removeAssetFromClass(assetClassId, asset);
}
/// @inheritdoc IAssetClassRegistry
function removeAssetClass(
uint256 assetClassId
) public virtual onlyOwner {
_removeAssetClass(assetClassId);
}
/// @inheritdoc IAssetClassRegistry
function getClassAssets(
uint256 assetClassId
) external view returns (address[] memory) {
if (!assetClassIds.contains(assetClassId)) {
revert AssetClassRegistry__AssetClassNotFound();
}
return assetClasses[assetClassId].assets.values();
}
/// @inheritdoc IAssetClassRegistry
function getClassStakingRequirements(
uint256 assetClassId
) external view returns (uint256 minStake, uint256 maxStake) {
if (!assetClassIds.contains(assetClassId)) {
revert AssetClassRegistry__AssetClassNotFound();
}
AssetClass storage cls = assetClasses[assetClassId];
return (cls.minValidatorStake, cls.maxValidatorStake);
}
function _addAssetClass(
uint256 assetClassId,
uint256 minValidatorStake,
uint256 maxValidatorStake,
address initialAsset
) internal {
if (initialAsset == address(0)) {
revert AssetClassRegistry__InvalidAsset();
}
if (assetClassId == 1 && minValidatorStake > maxValidatorStake) {
revert AssetClassRegistry__InvalidStakingRequirements();
}
bool added = assetClassIds.add(assetClassId);
if (!added) {
revert AssetClassRegistry__AssetClassAlreadyExists();
}
AssetClass storage cls = assetClasses[assetClassId];
cls.minValidatorStake = minValidatorStake;
cls.maxValidatorStake = maxValidatorStake;
emit AssetClassAdded(assetClassId, minValidatorStake, maxValidatorStake);
_addAssetToClass(assetClassId, initialAsset);
}
function _addAssetToClass(uint256 assetClassId, address asset) internal {
AssetClass storage cls = assetClasses[assetClassId];
bool added = cls.assets.add(asset);
if (!added) {
revert AssetClassRegistry__AssetAlreadyRegistered();
}
emit AssetAdded(assetClassId, asset);
}
function _removeAssetFromClass(uint256 assetClassId, address asset) internal {
AssetClass storage cls = assetClasses[assetClassId];
bool assetFound = cls.assets.remove(asset);
if (!assetFound) {
if (!assetClassIds.contains(assetClassId)) {
revert AssetClassRegistry__AssetClassNotFound();
}
revert AssetClassRegistry__AssetNotFound();
}
emit AssetRemoved(assetClassId, asset);
}
function _removeAssetClass(
uint256 assetClassId
) internal {
if (assetClassId == 1) {
revert AssetClassRegistry__AssetIsPrimaryAssetClass(assetClassId);
}
if (assetClasses[assetClassId].assets.length() != 0) {
revert AssetClassRegistry__AssetsStillExist();
}
bool removed = assetClassIds.remove(assetClassId);
if (!removed) {
revert AssetClassRegistry__AssetClassNotFound();
}
delete assetClasses[assetClassId];
emit AssetClassRemoved(assetClassId);
}
function isAssetInClass(uint256 assetClassId, address asset) external view returns (bool) {
if (!assetClassIds.contains(assetClassId)) {
revert AssetClassRegistry__AssetClassNotFound();
}
return assetClasses[assetClassId].assets.contains(asset);
}
function getAssetClassIds() external view returns (uint96[] memory) {
uint256[] memory ids = assetClassIds.values();
uint96[] memory assetClassIDs = new uint96[](ids.length);
for (uint256 i = 0; i < ids.length; i++) {
assetClassIDs[i] = uint96(ids[i]);
}
return assetClassIDs;
}
receive() external payable {}
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.25;
import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
import {Time} from "@openzeppelin/contracts/utils/types/Time.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
library MapWithTimeData {
using EnumerableMap for EnumerableMap.AddressToUintMap;
error MapWithTimeData__AlreadyAdded();
error MapWithTimeData__NotEnabled();
error MapWithTimeData__AlreadyEnabled();
error MapWithTimeData__AlreadyDisabled();
function add(EnumerableMap.AddressToUintMap storage self, address addr) internal {
if (!self.set(addr, uint256(0))) {
revert MapWithTimeData__AlreadyAdded();
}
}
function disable(EnumerableMap.AddressToUintMap storage self, address addr) internal {
uint256 value = self.get(addr);
uint48 enabledTime = uint48(value);
uint48 disabledTime = uint48(value >> 48);
if (enabledTime == 0) {
revert MapWithTimeData__NotEnabled();
}
if (disabledTime != 0) {
revert MapWithTimeData__AlreadyDisabled();
}
value |= uint256(Time.timestamp()) << 48;
self.set(addr, value);
}
function enable(EnumerableMap.AddressToUintMap storage self, address addr) internal {
uint256 value = self.get(addr);
if (uint48(value) != 0 && uint48(value >> 48) == 0) {
revert MapWithTimeData__AlreadyEnabled();
}
value = uint256(Time.timestamp());
self.set(addr, value);
}
function atWithTimes(
EnumerableMap.AddressToUintMap storage self,
uint256 idx
) internal view returns (address key, uint48 enabledTime, uint48 disabledTime) {
uint256 value;
(key, value) = self.at(idx);
enabledTime = uint48(value);
disabledTime = uint48(value >> 48);
}
function getTimes(
EnumerableMap.AddressToUintMap storage self,
address addr
) internal view returns (uint48 enabledTime, uint48 disabledTime) {
uint256 value = self.get(addr);
enabledTime = uint48(value);
disabledTime = uint48(value >> 48);
}
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
library StakeConversion {
error MiddlewareUtils__OverflowInStakeToWeight();
/**
* @notice Convert a full 256-bit stake amount into a 64-bit weight
* @dev Anything < WEIGHT_SCALE_FACTOR becomes 0
*/
function stakeToWeight(uint256 stakeAmount, uint256 scaleFactor) internal pure returns (uint64) {
uint256 weight = stakeAmount / scaleFactor;
if (weight > type(uint64).max) {
revert MiddlewareUtils__OverflowInStakeToWeight();
}
return uint64(weight);
}
/**
* @notice Convert a 64-bit weight back into its 256-bit stake amount
*/
function weightToStake(uint64 weight, uint256 scaleFactor) internal pure returns (uint256) {
return uint256(weight) * scaleFactor;
}
/**
* @notice Remove the node from the dynamic array (swap and pop).
* @dev Matches logic from _removeNodeFromArray() unchanged.
*/
function removeNodeFromArray(bytes32[] storage arr, bytes32 nodeId) internal {
uint256 arrLength = arr.length;
for (uint256 i = 0; i < arrLength; i++) {
if (arr[i] == nodeId) {
uint256 lastIndex;
unchecked {
lastIndex = arrLength - 1;
}
if (i != lastIndex) {
arr[i] = arr[lastIndex];
}
arr.pop();
break;
}
}
}
}// 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
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
import {IAvalancheL1Middleware} from "../../interfaces/middleware/IAvalancheL1Middleware.sol";
/**
* @title IMiddlewareVaultManager
* @notice Manages vault registration, maximum L1 stake limits, and slash routing.
*/
interface IMiddlewareVaultManager {
// -----------------------------------------------------------------------
// Errors
// -----------------------------------------------------------------------
error AvalancheL1Middleware__VaultAlreadyRegistered();
error AvalancheL1Middleware__VaultEpochTooShort();
error AvalancheL1Middleware__NotVault(address vault);
error AvalancheL1Middleware__WrongVaultAssetClass();
error AvalancheL1Middleware__ZeroVaultMaxL1Limit();
error AvalancheL1Middleware__VaultGracePeriodNotPassed();
error AvalancheL1Middleware__VaultNotDisabled();
error AvalancheL1Middleware__ZeroAddress(string name);
error AvalancheL1Middleware__SlasherNotImplemented();
// -----------------------------------------------------------------------
// Public state variable getters
// -----------------------------------------------------------------------
/**
* @notice Returns the VAULT_REGISTRY address.
*/
function VAULT_REGISTRY() external view returns (address);
//
// Functions
//
/**
* @notice Registers a vault to a specific asset class with a given maximum L1 stake limit.
* @param vault The vault address
* @param assetClassId The asset class ID for the vault
* @param vaultMaxL1Limit The maximum stake allowed for this vault
*/
function registerVault(address vault, uint96 assetClassId, uint256 vaultMaxL1Limit) external;
/**
* @notice Updates a vault's max L1 stake limit.
* @param vault The vault address
* @param assetClassId The asset class ID
* @param vaultMaxL1Limit The new maximum stake
*/
function updateVaultMaxL1Limit(address vault, uint96 assetClassId, uint256 vaultMaxL1Limit) external;
/**
* @notice Removes a vault if the grace period has passed.
* @param vault The vault address
*/
function removeVault(
address vault
) external;
/**
* @notice Slashes a vault based on the operator’s share of stake.
*/
function slashVault() external;
/**
* @notice Returns the number of vaults registered.
* @return The count of vaults
*/
function getVaultCount() external view returns (uint256);
/**
* @notice Returns the vault and its enable/disable times at the given index.
* @param index The vault index
* @return vault The vault address
* @return enabledTime The time the vault was enabled
* @return disabledTime The time the vault was disabled
*/
function getVaultAtWithTimes(
uint256 index
) external view returns (address vault, uint48 enabledTime, uint48 disabledTime);
/**
* @notice Returns the asset class ID for a given vault.
* @param vault The vault address
* @return The asset class ID
*/
function getVaultAssetClass(
address vault
) external view returns (uint96);
/**
* @notice Fetches the active vaults for a given epoch
* @param epoch The epoch for which vaults are fetched
* @return An array of active vault addresses
*/
function getVaults(
uint48 epoch
) external view returns (address[] memory);
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
interface IRegistry {
error EntityNotExist();
/**
* @notice Emitted when an entity is added.
* @param entity address of the added entity
*/
event AddEntity(address indexed entity);
/**
* @notice Get if a given address is an entity.
* @param account address to check
* @return if the given address is an entity
*/
function isEntity(
address account
) external view returns (bool);
/**
* @notice Get a total number of entities.
* @return total number of entities added
*/
function totalEntities() external view returns (uint256);
/**
* @notice Get an entity given its index.
* @param index index of the entity to get
* @return address of the entity
*/
function entity(
uint256 index
) external view returns (address);
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface IEntity is IERC165 {
error Entity__NotInitialized();
error Entity__ZeroAddress(string name);
/**
* @notice Get the factory's address.
* @return address of the factory
*/
function FACTORY() external view returns (address);
/**
* @notice Get the entity's type.
* @return type of the entity
*/
function TYPE() external view returns (uint64);
/**
* @notice Initialize this entity contract by using given data.
* @param data some data to use
*/
function initialize(
bytes calldata data
) external;
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
import {IBaseSlasher} from "./IBaseSlasher.sol";
interface ISlasher is IBaseSlasher {
error InsufficientSlash();
error InvalidCaptureTimestamp();
/**
* @notice Initial parameters needed for a slasher deployment.
* @param baseParams base parameters for slashers' deployment
*/
struct InitParams {
IBaseSlasher.BaseParams baseParams;
}
/**
* @notice Hints for a slash.
* @param slashableStakeHints hints for the slashable stake checkpoints
*/
struct SlashHints {
bytes slashableStakeHints;
}
/**
* @notice Extra data for the delegator.
* @param slashableStake amount of the slashable stake before the slash (cache)
* @param stakeAt amount of the stake at the capture time (cache)
*/
struct DelegatorData {
uint256 slashableStake;
uint256 stakeAt;
}
/**
* @notice Emitted when a slash is performed.
* @param l1 address of the l1.
* @param assetClass the uint96 assetClass.
* @param operator operator that is slashed
* @param slashedAmount virtual amount of the collateral slashed
* @param captureTimestamp time point when the stake was captured
*/
event Slash(
address indexed l1,
uint96 indexed assetClass,
address indexed operator,
uint256 slashedAmount,
uint48 captureTimestamp
);
/**
* @notice Perform a slash using a subnetwork for a particular operator by a given amount using hints.
* @param l1 address of the l1
* @param assetClass the uint96 assetClass
* @param operator address of the operator
* @param amount maximum amount of the collateral to be slashed
* @param captureTimestamp time point when the stake was captured
* @param hints hints for checkpoints' indexes
* @return slashedAmount virtual amount of the collateral slashed
* @dev Only a network middleware can call this function.
*/
function slash(
address l1,
uint96 assetClass,
address operator,
uint256 amount,
uint48 captureTimestamp,
bytes calldata hints
) external returns (uint256 slashedAmount);
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
import {IBaseSlasher} from "./IBaseSlasher.sol";
interface IVetoSlasher is IBaseSlasher {
error AlreadySet();
error InsufficientSlash();
error InvalidCaptureTimestamp();
error InvalidResolverSetEpochsDelay();
error InvalidVetoDuration();
error NoResolver();
error NotNetwork();
error NotResolver();
error SlashPeriodEnded();
error SlashRequestCompleted();
error SlashRequestNotExist();
error VetoPeriodEnded();
error VetoPeriodNotEnded();
/**
* @notice Initial parameters needed for a slasher deployment.
* @param baseParams base parameters for slashers' deployment
* @param vetoDuration duration of the veto period for a slash request
* @param resolverSetEpochsDelay delay in epochs for a network to update a resolver
*/
struct InitParams {
IBaseSlasher.BaseParams baseParams;
uint48 vetoDuration;
uint256 resolverSetEpochsDelay;
}
/**
* @notice Structure for a slash request.
* @param l1 address of the l1
* @param assetClass the uint96 assetClass
* @param operator operator that could be slashed (if the request is not vetoed)
* @param amount maximum amount of the collateral to be slashed
* @param captureTimestamp time point when the stake was captured
* @param vetoDeadline deadline for the resolver to veto the slash (exclusively)
* @param completed if the slash was vetoed/executed
*/
struct SlashRequest {
address l1;
uint96 assetClass;
address operator;
uint256 amount;
uint48 captureTimestamp;
uint48 vetoDeadline;
bool completed;
}
/**
* @notice Hints for a slash request.
* @param slashableStakeHints hints for the slashable stake checkpoints
*/
struct RequestSlashHints {
bytes slashableStakeHints;
}
/**
* @notice Hints for a slash execute.
* @param captureResolverHint hint for the resolver checkpoint at the capture time
* @param currentResolverHint hint for the resolver checkpoint at the current time
* @param slashableStakeHints hints for the slashable stake checkpoints
*/
struct ExecuteSlashHints {
bytes captureResolverHint;
bytes currentResolverHint;
bytes slashableStakeHints;
}
/**
* @notice Hints for a slash veto.
* @param captureResolverHint hint for the resolver checkpoint at the capture time
* @param currentResolverHint hint for the resolver checkpoint at the current time
*/
struct VetoSlashHints {
bytes captureResolverHint;
bytes currentResolverHint;
}
/**
* @notice Hints for a resolver set.
* @param resolverHint hint for the resolver checkpoint
*/
struct SetResolverHints {
bytes resolverHint;
}
/**
* @notice Extra data for the delegator.
* @param slashableStake amount of the slashable stake before the slash (cache)
* @param stakeAt amount of the stake at the capture time (cache)
* @param slashIndex index of the slash request
*/
struct DelegatorData {
uint256 slashableStake;
uint256 stakeAt;
uint256 slashIndex;
}
/**
* @notice Emitted when a slash request is created.
* @param slashIndex index of the slash request
* @param l1 address of the l1
* @param assetClass the uint96 assetClass
* @param operator operator that could be slashed (if the request is not vetoed)
* @param slashAmount maximum amount of the collateral to be slashed
* @param captureTimestamp time point when the stake was captured
* @param vetoDeadline deadline for the resolver to veto the slash (exclusively)
*/
event RequestSlash(
uint256 indexed slashIndex,
address l1,
uint96 assetClass,
address indexed operator,
uint256 slashAmount,
uint48 captureTimestamp,
uint48 vetoDeadline
);
/**
* @notice Emitted when a slash request is executed.
* @param slashIndex index of the slash request
* @param slashedAmount virtual amount of the collateral slashed
*/
event ExecuteSlash(uint256 indexed slashIndex, uint256 slashedAmount);
/**
* @notice Emitted when a slash request is vetoed.
* @param slashIndex index of the slash request
* @param resolver address of the resolver that vetoed the slash
*/
event VetoSlash(uint256 indexed slashIndex, address indexed resolver);
/**
* @notice Emitted when a resolver is set.
* @param l1 address of the l1
* @param assetClass the uint96 assetClass
* @param resolver address of the resolver
*/
event SetResolver(address indexed l1, uint96 indexed assetClass, address resolver);
/**
* @notice Get the network registry's address.
* @return address of the network registry
*/
function NETWORK_REGISTRY() external view returns (address);
/**
* @notice Get a duration during which resolvers can veto slash requests.
* @return duration of the veto period
*/
function vetoDuration() external view returns (uint48);
/**
* @notice Get a total number of slash requests.
* @return total number of slash requests
*/
function slashRequestsLength() external view returns (uint256);
/**
* @notice Get a particular slash request.
* @param slashIndex index of the slash request
* @return l1 address of the l1
* @return assetClass the uint96 assetClass
* @return operator operator that could be slashed (if the request is not vetoed)
* @return amount maximum amount of the collateral to be slashed
* @return captureTimestamp time point when the stake was captured
* @return vetoDeadline deadline for the resolver to veto the slash (exclusively)
* @return completed if the slash was vetoed/executed
*/
function slashRequests(
uint256 slashIndex
)
external
view
returns (
address l1,
uint96 assetClass,
address operator,
uint256 amount,
uint48 captureTimestamp,
uint48 vetoDeadline,
bool completed
);
/**
* @notice Get a delay for networks in epochs to update a resolver.
* @return updating resolver delay in epochs
*/
function resolverSetEpochsDelay() external view returns (uint256);
/**
* @notice Get a resolver for a given assset class at a particular timestamp using a hint.
* @param l1 address of the l1
* @param assetClass the uint96 assetClass
* @param timestamp timestamp to get the resolver at
* @param hint hint for the checkpoint index
* @return address of the resolver
*/
function resolverAt(
address l1,
uint96 assetClass,
uint48 timestamp,
bytes memory hint
) external view returns (address);
/**
* @notice Get a resolver for a given assset class using a hint.
* @param l1 address of the l1
* @param assetClass the uint96 assetClass
* @param hint hint for the checkpoint index
* @return address of the resolver
*/
function resolver(address l1, uint96 assetClass, bytes memory hint) external view returns (address);
/**
* @notice Request a slash using a assset class for a particular operator by a given amount using hints.
* @param l1 address of the l1
* @param assetClass the uint96 assetClass
* @param operator address of the operator
* @param amount maximum amount of the collateral to be slashed
* @param captureTimestamp time point when the stake was captured
* @param hints hints for checkpoints' indexes
* @return slashIndex index of the slash request
* @dev Only a network middleware can call this function.
*/
function requestSlash(
address l1,
uint96 assetClass,
address operator,
uint256 amount,
uint48 captureTimestamp,
bytes calldata hints
) external returns (uint256 slashIndex);
/**
* @notice Execute a slash with a given slash index using hints.
* @param slashIndex index of the slash request
* @param hints hints for checkpoints' indexes
* @return slashedAmount virtual amount of the collateral slashed
* @dev Only a network middleware can call this function.
*/
function executeSlash(uint256 slashIndex, bytes calldata hints) external returns (uint256 slashedAmount);
/**
* @notice Veto a slash with a given slash index using hints.
* @param slashIndex index of the slash request
* @param hints hints for checkpoints' indexes
* @dev Only a resolver can call this function.
*/
function vetoSlash(uint256 slashIndex, bytes calldata hints) external;
/**
* @notice Set a resolver for a assset class using hints.
* identifier identifier of the assset class
* @param resolver address of the resolver
* @param hints hints for checkpoints' indexes
* @dev Only a network can call this function.
*/
function setResolver(uint96 identifier, address resolver, bytes calldata hints) external;
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
struct LastUptimeCheckpoint {
uint256 remainingUptime;
uint256 attributedUptime;
uint256 timestamp;
}
interface IUptimeTracker {
/**
* @dev Error thrown when a validator's uptime is not recorded for a given epoch
* @param epoch Epoch for which uptime was not recorded
* @param validator Validator's unique validation ID
*/
error UptimeTracker__ValidatorUptimeNotRecorded(uint48 epoch, bytes32 validator);
/**
* @dev Error thrown when an operator has no validators for a given epoch
* @param operator Operator's address
* @param epoch Epoch for which uptime was not recorded
*/
error UptimeTracker__NoValidators(address operator, uint48 epoch);
/**
* @dev Error thrown when a warp message has an invalid origin sender address
* @param senderAddress Sender address of the warp message
*/
error InvalidWarpOriginSenderAddress(address senderAddress);
/**
* @dev Error thrown when a warp message has an invalid source chain ID
* @param sourceChainID Source chain ID of the warp message
*/
error InvalidWarpSourceChainID(bytes32 sourceChainID);
/**
* @dev Error thrown when a warp message is invalid
*/
error InvalidWarpMessage();
/**
* @notice Emitted when a validator's uptime is computed.
* @param validationID Unique ID of the validator's validation period.
* @param firstEpoch First epoch included in this uptime calculation.
* @param uptimeSecondsAdded Recorded uptime (in seconds) since the last proof.
* @param numberOfEpochs Number of epochs covered by this uptime.
*/
event ValidatorUptimeComputed(
bytes32 indexed validationID, uint48 indexed firstEpoch, uint256 uptimeSecondsAdded, uint256 numberOfEpochs
);
/**
* @notice Emitted when an operator's uptime is computed.
* @param operator Operator's address.
* @param epoch Epoch for which uptime was recorded.
* @param uptime Average uptime (in seconds) of the operator's validators.
*/
event OperatorUptimeComputed(address indexed operator, uint48 indexed epoch, uint256 uptime);
/**
* @notice Computes and records the validator's uptime for each epoch.
* @dev TODO: get the (`validationID`, `uptime`) from a ValidationUptimeMessage or make this function permissioned as last resort
* @param messageIndex The index of the uptime message in the WarpMessenger.
*/
function computeValidatorUptime(
uint32 messageIndex
) external;
/**
* @notice Computes and records an operato r’s uptime for a given epoch.
* @dev Aggregates uptime from all validators operated by the given operator for a given epoch.
* @param operator Address of the operator.
* @param epoch Epoch for which uptime is computed.
*/
function computeOperatorUptimeAt(address operator, uint48 epoch) external;
/**
* @notice Returns the last uptime checkpoint for a validator.
* @param validationID The validator's unique validation ID.
* @return Last recorded uptime checkpoint.
*/
function getLastUptimeCheckpoint(
bytes32 validationID
) external view returns (LastUptimeCheckpoint memory);
}// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// SPDX-License-Identifier: Ecosystem
pragma solidity 0.8.25;
import {PChainOwner, ConversionData} from "./interfaces/IValidatorManager.sol";
/**
* @dev Packing utilities for the ICM message types used by the Validator Manager contracts, as specified in ACP-77:
* https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/77-reinventing-subnets
*/
library ValidatorMessages {
// The information that uniquely identifies an L1 validation period.
// The validationID is the SHA-256 hash of the concatenation of the CODEC_ID,
// REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID, and the concatenated ValidationPeriod fields.
struct ValidationPeriod {
bytes32 l1ID;
bytes nodeID;
bytes blsPublicKey;
uint64 registrationExpiry;
PChainOwner remainingBalanceOwner;
PChainOwner disableOwner;
uint64 weight;
}
// The P-Chain uses a hardcoded codecID of 0 for all messages.
uint16 internal constant CODEC_ID = 0;
// The P-Chain signs a SubnetToL1ConversionMessage that is used to verify the L1's initial validators.
uint32 internal constant SUBNET_TO_L1_CONVERSION_MESSAGE_TYPE_ID = 0;
// L1s send a RegisterL1ValidatorMessage to the P-Chain to register a validator.
uint32 internal constant REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID = 1;
// The P-Chain responds with a RegisterL1ValidatorMessage indicating whether the registration was successful
// for the given validation ID.
uint32 internal constant L1_VALIDATOR_REGISTRATION_MESSAGE_TYPE_ID = 2;
// L1s can send a L1ValidatorWeightMessage to the P-Chain to update a validator's weight.
// The P-Chain responds with another L1ValidatorWeightMessage acknowledging the weight update.
uint32 internal constant L1_VALIDATOR_WEIGHT_MESSAGE_TYPE_ID = 3;
// The L1 will self-sign a ValidationUptimeMessage to be provided when a validator is initiating
// the end of their validation period.
uint32 internal constant VALIDATION_UPTIME_MESSAGE_TYPE_ID = 0;
error InvalidMessageLength(uint32 actual, uint32 expected);
error InvalidCodecID(uint32 id);
error InvalidMessageType();
error InvalidBLSPublicKey();
/**
* @notice Packs a SubnetToL1ConversionMessage message into a byte array.
* The message format specification is:
* +--------------------+----------+----------+
* | codecID : uint16 | 2 bytes |
* +--------------------+----------+----------+
* | typeID : uint32 | 4 bytes |
* +--------------------+----------+----------+
* | conversionID : [32]byte | 32 bytes |
* +--------------------+----------+----------+
* | 38 bytes |
* +----------+
*
* @param conversionID The subnet conversion ID to pack into the message.
* @return The packed message.
*/
function packSubnetToL1ConversionMessage(bytes32 conversionID)
external
pure
returns (bytes memory)
{
return abi.encodePacked(CODEC_ID, SUBNET_TO_L1_CONVERSION_MESSAGE_TYPE_ID, conversionID);
}
/**
* @notice Unpacks a byte array as a SubnetToL1ConversionMessage message.
* The message format specification is the same as the one used in above for packing.
*
* @param input The byte array to unpack.
* @return The unpacked conversionID.
*/
function unpackSubnetToL1ConversionMessage(bytes memory input)
external
pure
returns (bytes32)
{
if (input.length != 38) {
revert InvalidMessageLength(uint32(input.length), 38);
}
// Unpack the codec ID
uint16 codecID;
for (uint256 i; i < 2; ++i) {
codecID |= uint16(uint8(input[i])) << uint16((8 * (1 - i)));
}
if (codecID != CODEC_ID) {
revert InvalidCodecID(codecID);
}
// Unpack the type ID
uint32 typeID;
for (uint256 i; i < 4; ++i) {
typeID |= uint32(uint8(input[i + 2])) << uint32((8 * (3 - i)));
}
if (typeID != SUBNET_TO_L1_CONVERSION_MESSAGE_TYPE_ID) {
revert InvalidMessageType();
}
// Unpack the conversionID
bytes32 conversionID;
for (uint256 i; i < 32; ++i) {
conversionID |= bytes32(uint256(uint8(input[i + 6])) << (8 * (31 - i)));
}
return conversionID;
}
/**
* @notice Packs ConversionData into a byte array.
* This byte array is the SHA256 pre-image of the conversionID hash
* The message format specification is:
*
* ConversionData:
* +----------------+-----------------+--------------------------------------------------------+
* | codecID : uint16 | 2 bytes |
* +----------------+-----------------+--------------------------------------------------------+
* | l1ID : [32]byte | 32 bytes |
* +----------------+-----------------+--------------------------------------------------------+
* | managerChainID : [32]byte | 32 bytes |
* +----------------+-----------------+--------------------------------------------------------+
* | managerAddress : []byte | 4 + len(managerAddress) bytes |
* +----------------+-----------------+--------------------------------------------------------+
* | validators : []ValidatorData | 4 + sum(validatorLengths) bytes |
* +----------------+-----------------+--------------------------------------------------------+
* | 74 + len(managerAddress) + len(validatorLengths) bytes |
* +--------------------------------------------------------+
* ValidatorData:
* +--------------+----------+------------------------+
* | nodeID : []byte | 4 + len(nodeID) bytes |
* +--------------+----------+------------------------+
* | blsPublicKey : [48]byte | 48 bytes |
* +--------------+----------+------------------------+
* | weight : uint64 | 8 bytes |
* +--------------+----------+------------------------+
* | 60 + len(nodeID) bytes |
* +------------------------+
*
* @dev Input validation is skipped, since the returned value is intended to be compared
* directly with an authenticated ICM message.
* @param conversionData The struct representing data to pack into the message.
* @return The packed message.
*/
function packConversionData(ConversionData memory conversionData)
external
pure
returns (bytes memory)
{
// Hardcoded 20 is for length of the managerAddress on EVM chains
// solhint-disable-next-line func-named-parameters
bytes memory res = abi.encodePacked(
CODEC_ID,
conversionData.l1ID,
conversionData.validatorManagerBlockchainID,
uint32(20),
conversionData.validatorManagerAddress,
uint32(conversionData.initialValidators.length)
);
// The approach below of encoding initialValidators using `abi.encodePacked` in a loop
// was tested against pre-allocating the array and doing manual byte by byte packing and
// it was found to be more gas efficient.
for (uint256 i; i < conversionData.initialValidators.length; ++i) {
res = abi.encodePacked(
res,
uint32(conversionData.initialValidators[i].nodeID.length),
conversionData.initialValidators[i].nodeID,
conversionData.initialValidators[i].blsPublicKey,
conversionData.initialValidators[i].weight
);
}
return res;
}
/**
* @notice Packs a RegisterL1ValidatorMessage message into a byte array.
* The message format specification is:
*
* RegisterL1ValidatorMessage:
* +-----------------------+-------------+--------------------------------------------------------------------+
* | codecID : uint16 | 2 bytes |
* +-----------------------+-------------+--------------------------------------------------------------------+
* | typeID : uint32 | 4 bytes |
* +-----------------------+-------------+-------------------------------------------------------------------+
* | l1ID : [32]byte | 32 bytes |
* +-----------------------+-------------+--------------------------------------------------------------------+
* | nodeID : []byte | 4 + len(nodeID) bytes |
* +-----------------------+-------------+--------------------------------------------------------------------+
* | blsPublicKey : [48]byte | 48 bytes |
* +-----------------------+-------------+--------------------------------------------------------------------+
* | expiry : uint64 | 8 bytes |
* +-----------------------+-------------+--------------------------------------------------------------------+
* | remainingBalanceOwner : PChainOwner | 8 + len(addresses) * 20 bytes |
* +-----------------------+-------------+--------------------------------------------------------------------+
* | disableOwner : PChainOwner | 8 + len(addresses) * 20 bytes |
* +-----------------------+-------------+--------------------------------------------------------------------+
* | weight : uint64 | 8 bytes |
* +-----------------------+-------------+--------------------------------------------------------------------+
* | 122 + len(nodeID) + (len(addresses1) + len(addresses2)) * 20 bytes |
* +--------------------------------------------------------------------+
*
* PChainOwner:
* +-----------+------------+-------------------------------+
* | threshold : uint32 | 4 bytes |
* +-----------+------------+-------------------------------+
* | addresses : [][20]byte | 4 + len(addresses) * 20 bytes |
* +-----------+------------+-------------------------------+
* | 8 + len(addresses) * 20 bytes |
* +-------------------------------+
*
* @param validationPeriod The information to pack into the message.
* @return The validationID and the packed message.
*/
function packRegisterL1ValidatorMessage(ValidationPeriod memory validationPeriod)
external
pure
returns (bytes32, bytes memory)
{
if (validationPeriod.blsPublicKey.length != 48) {
revert InvalidBLSPublicKey();
}
// solhint-disable-next-line func-named-parameters
bytes memory res = abi.encodePacked(
CODEC_ID,
REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID,
validationPeriod.l1ID,
uint32(validationPeriod.nodeID.length),
validationPeriod.nodeID,
validationPeriod.blsPublicKey,
validationPeriod.registrationExpiry,
validationPeriod.remainingBalanceOwner.threshold,
uint32(validationPeriod.remainingBalanceOwner.addresses.length)
);
for (uint256 i; i < validationPeriod.remainingBalanceOwner.addresses.length; ++i) {
res = abi.encodePacked(res, validationPeriod.remainingBalanceOwner.addresses[i]);
}
res = abi.encodePacked(
res,
validationPeriod.disableOwner.threshold,
uint32(validationPeriod.disableOwner.addresses.length)
);
for (uint256 i; i < validationPeriod.disableOwner.addresses.length; ++i) {
res = abi.encodePacked(res, validationPeriod.disableOwner.addresses[i]);
}
res = abi.encodePacked(res, validationPeriod.weight);
return (sha256(res), res);
}
/**
* @notice Unpacks a byte array as a RegisterL1ValidatorMessage message.
* The message format specification is the same as the one used in above for packing.
*
* @param input The byte array to unpack.
* @return The unpacked ValidationPeriod.
*/
function unpackRegisterL1ValidatorMessage(bytes memory input)
external
pure
returns (ValidationPeriod memory)
{
uint32 index = 0;
ValidationPeriod memory validation;
// Unpack the codec ID
// Individual fields are unpacked in their own scopes to avoid stack too deep errors.
{
uint16 codecID;
for (uint256 i; i < 2; ++i) {
codecID |= uint16(uint8(input[i + index])) << uint16((8 * (1 - i)));
}
if (codecID != CODEC_ID) {
revert InvalidCodecID(codecID);
}
index += 2;
}
// Unpack the type ID
{
uint32 typeID;
for (uint256 i; i < 4; ++i) {
typeID |= uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
}
if (typeID != REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID) {
revert InvalidMessageType();
}
index += 4;
}
// Unpack the l1ID
{
bytes32 l1ID;
for (uint256 i; i < 32; ++i) {
l1ID |= bytes32(uint256(uint8(input[i + index])) << (8 * (31 - i)));
}
validation.l1ID = l1ID;
index += 32;
}
// Unpack the nodeID length
uint32 nodeIDLength;
{
for (uint256 i; i < 4; ++i) {
nodeIDLength |= uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
}
index += 4;
// Unpack the nodeID
bytes memory nodeID = new bytes(nodeIDLength);
for (uint256 i; i < nodeIDLength; ++i) {
nodeID[i] = input[i + index];
}
validation.nodeID = nodeID;
index += nodeIDLength;
}
// Unpack the blsPublicKey
{
bytes memory blsPublicKey = new bytes(48);
for (uint256 i; i < 48; ++i) {
blsPublicKey[i] = input[i + index];
}
validation.blsPublicKey = blsPublicKey;
index += 48;
}
// Unpack the registration expiry
{
uint64 expiry;
for (uint256 i; i < 8; ++i) {
expiry |= uint64(uint8(input[i + index])) << uint64((8 * (7 - i)));
}
validation.registrationExpiry = expiry;
index += 8;
}
// Unpack the remainingBalanceOwner threshold
uint32 remainingBalanceOwnerAddressesLength;
{
uint32 remainingBalanceOwnerThreshold;
for (uint256 i; i < 4; ++i) {
remainingBalanceOwnerThreshold |=
uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
}
index += 4;
// Unpack the remainingBalanceOwner addresses length
for (uint256 i; i < 4; ++i) {
remainingBalanceOwnerAddressesLength |=
uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
}
index += 4;
// Unpack the remainingBalanceOwner addresses
address[] memory remainingBalanceOwnerAddresses =
new address[](remainingBalanceOwnerAddressesLength);
for (uint256 i; i < remainingBalanceOwnerAddressesLength; ++i) {
bytes memory addrBytes = new bytes(20);
for (uint256 j; j < 20; ++j) {
addrBytes[j] = input[j + index];
}
address addr;
// solhint-disable-next-line no-inline-assembly
assembly {
addr := mload(add(addrBytes, 20))
}
remainingBalanceOwnerAddresses[i] = addr;
index += 20;
}
validation.remainingBalanceOwner = PChainOwner({
threshold: remainingBalanceOwnerThreshold,
addresses: remainingBalanceOwnerAddresses
});
}
// Unpack the disableOwner threshold
uint32 disableOwnerAddressesLength;
{
uint32 disableOwnerThreshold;
for (uint256 i; i < 4; ++i) {
disableOwnerThreshold |= uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
}
index += 4;
// Unpack the disableOwner addresses length
for (uint256 i; i < 4; ++i) {
disableOwnerAddressesLength |=
uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
}
index += 4;
// Unpack the disableOwner addresses
address[] memory disableOwnerAddresses = new address[](disableOwnerAddressesLength);
for (uint256 i; i < disableOwnerAddressesLength; ++i) {
bytes memory addrBytes = new bytes(20);
for (uint256 j; j < 20; ++j) {
addrBytes[j] = input[j + index];
}
address addr;
// solhint-disable-next-line no-inline-assembly
assembly {
addr := mload(add(addrBytes, 20))
}
disableOwnerAddresses[i] = addr;
index += 20;
}
validation.disableOwner =
PChainOwner({threshold: disableOwnerThreshold, addresses: disableOwnerAddresses});
}
// Now that we have all the variable lengths, validate the input length
uint32 expectedLength = 122 + nodeIDLength
+ (remainingBalanceOwnerAddressesLength + disableOwnerAddressesLength) * 20;
if (input.length != expectedLength) {
revert InvalidMessageLength(uint32(input.length), expectedLength);
}
// Unpack the weight
{
uint64 weight;
for (uint256 i; i < 8; ++i) {
weight |= uint64(uint8(input[i + index])) << uint64((8 * (7 - i)));
}
validation.weight = weight;
}
return validation;
}
/**
* @notice Packs a L1ValidatorRegistrationMessage into a byte array.
* The message format specification is:
* +--------------+----------+----------+
* | codecID : uint16 | 2 bytes |
* +--------------+----------+----------+
* | typeID : uint32 | 4 bytes |
* +--------------+----------+----------+
* | validationID : [32]byte | 32 bytes |
* +--------------+----------+----------+
* | valid : bool | 1 byte |
* +--------------+----------+----------+
* | 39 bytes |
* +----------+
*
* @param validationID The ID of the validation period.
* @param valid true if the validation period was registered, false if it was not and never will be.
* @return The packed message.
*
*/
function packL1ValidatorRegistrationMessage(
bytes32 validationID,
bool valid
) external pure returns (bytes memory) {
return abi.encodePacked(
CODEC_ID, L1_VALIDATOR_REGISTRATION_MESSAGE_TYPE_ID, validationID, valid
);
}
/**
* @notice Unpacks a byte array as a L1ValidatorRegistrationMessage message.
* The message format specification is the same as the one used in above for packing.
*
* @param input The byte array to unpack.
* @return The validationID and whether the validation period was registered or is not a
* validator and never will be a validator due to the expiry time passing.
*/
function unpackL1ValidatorRegistrationMessage(bytes memory input)
external
pure
returns (bytes32, bool)
{
if (input.length != 39) {
revert InvalidMessageLength(uint32(input.length), 39);
}
// Unpack the codec ID
uint16 codecID;
for (uint256 i; i < 2; ++i) {
codecID |= uint16(uint8(input[i])) << uint16((8 * (1 - i)));
}
if (codecID != CODEC_ID) {
revert InvalidCodecID(codecID);
}
// Unpack the type ID
uint32 typeID;
for (uint256 i; i < 4; ++i) {
typeID |= uint32(uint8(input[i + 2])) << uint32((8 * (3 - i)));
}
if (typeID != L1_VALIDATOR_REGISTRATION_MESSAGE_TYPE_ID) {
revert InvalidMessageType();
}
// Unpack the validation ID.
bytes32 validationID;
for (uint256 i; i < 32; ++i) {
validationID |= bytes32(uint256(uint8(input[i + 6])) << (8 * (31 - i)));
}
// Unpack the validity
bool valid = input[38] != 0;
return (validationID, valid);
}
/**
* @notice Packs a L1ValidatorWeightMessage message into a byte array.
* The message format specification is:
* +--------------+----------+----------+
* | codecID : uint16 | 2 bytes |
* +--------------+----------+----------+
* | typeID : uint32 | 4 bytes |
* +--------------+----------+----------+
* | validationID : [32]byte | 32 bytes |
* +--------------+----------+----------+
* | nonce : uint64 | 8 bytes |
* +--------------+----------+----------+
* | weight : uint64 | 8 bytes |
* +--------------+----------+----------+
* | 54 bytes |
* +----------+
*
* @param validationID The ID of the validation period.
* @param nonce The nonce of the validation ID.
* @param weight The new weight of the validator.
* @return The packed message.
*/
function packL1ValidatorWeightMessage(
bytes32 validationID,
uint64 nonce,
uint64 weight
) external pure returns (bytes memory) {
return abi.encodePacked(
CODEC_ID, L1_VALIDATOR_WEIGHT_MESSAGE_TYPE_ID, validationID, nonce, weight
);
}
/**
* @notice Unpacks a byte array as an L1ValidatorWeightMessage.
* The message format specification is the same as the one used in above for packing.
*
* @param input The byte array to unpack.
* @return The validationID, nonce, and weight.
*/
function unpackL1ValidatorWeightMessage(bytes memory input)
external
pure
returns (bytes32, uint64, uint64)
{
if (input.length != 54) {
revert InvalidMessageLength(uint32(input.length), 54);
}
// Unpack the codec ID.
uint16 codecID;
for (uint256 i; i < 2; ++i) {
codecID |= uint16(uint8(input[i])) << uint16((8 * (1 - i)));
}
if (codecID != CODEC_ID) {
revert InvalidCodecID(codecID);
}
// Unpack the type ID.
uint32 typeID;
for (uint256 i; i < 4; ++i) {
typeID |= uint32(uint8(input[i + 2])) << uint32((8 * (3 - i)));
}
if (typeID != L1_VALIDATOR_WEIGHT_MESSAGE_TYPE_ID) {
revert InvalidMessageType();
}
// Unpack the validation ID.
bytes32 validationID;
for (uint256 i; i < 32; ++i) {
validationID |= bytes32(uint256(uint8(input[i + 6])) << (8 * (31 - i)));
}
// Unpack the nonce.
uint64 nonce;
for (uint256 i; i < 8; ++i) {
nonce |= uint64(uint8(input[i + 38])) << uint64((8 * (7 - i)));
}
// Unpack the weight.
uint64 weight;
for (uint256 i; i < 8; ++i) {
weight |= uint64(uint8(input[i + 46])) << uint64((8 * (7 - i)));
}
return (validationID, nonce, weight);
}
/**
* @notice Packs a ValidationUptimeMessage into a byte array.
* The message format specification is:
* +--------------+----------+----------+
* | codecID : uint16 | 2 bytes |
* +--------------+----------+----------+
* | typeID : uint32 | 4 bytes |
* +--------------+----------+----------+
* | validationID : [32]byte | 32 bytes |
* +--------------+----------+----------+
* | uptime : uint64 | 8 bytes |
* +--------------+----------+----------+
* | 46 bytes |
* +----------+
*
* @param validationID The ID of the validation period.
* @param uptime The uptime of the validator.
* @return The packed message.
*/
function packValidationUptimeMessage(
bytes32 validationID,
uint64 uptime
) external pure returns (bytes memory) {
return abi.encodePacked(CODEC_ID, VALIDATION_UPTIME_MESSAGE_TYPE_ID, validationID, uptime);
}
/**
* @notice Unpacks a byte array as a ValidationUptimeMessage.
* The message format specification is the same as the one used in above for packing.
*
* @param input The byte array to unpack.
* @return The validationID and uptime.
*/
function unpackValidationUptimeMessage(bytes memory input)
external
pure
returns (bytes32, uint64)
{
if (input.length != 46) {
revert InvalidMessageLength(uint32(input.length), 46);
}
// Unpack the codec ID.
uint16 codecID;
for (uint256 i; i < 2; ++i) {
codecID |= uint16(uint8(input[i])) << uint16((8 * (1 - i)));
}
if (codecID != CODEC_ID) {
revert InvalidCodecID(codecID);
}
// Unpack the type ID.
uint32 typeID;
for (uint256 i; i < 4; ++i) {
typeID |= uint32(uint8(input[i + 2])) << uint32((8 * (3 - i)));
}
if (typeID != VALIDATION_UPTIME_MESSAGE_TYPE_ID) {
revert InvalidMessageType();
}
// Unpack the validation ID.
bytes32 validationID;
for (uint256 i; i < 32; ++i) {
validationID |= bytes32(uint256(uint8(input[i + 6])) << (8 * (31 - i)));
}
// Unpack the uptime.
uint64 uptime;
for (uint256 i; i < 8; ++i) {
uptime |= uint64(uint8(input[i + 38])) << uint64((8 * (7 - i)));
}
return (validationID, uptime);
}
}// (c) 2022-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
struct WarpMessage {
bytes32 sourceChainID;
address originSenderAddress;
bytes payload;
}
struct WarpBlockHash {
bytes32 sourceChainID;
bytes32 blockHash;
}
interface IWarpMessenger {
event SendWarpMessage(address indexed sender, bytes32 indexed messageID, bytes message);
// sendWarpMessage emits a request for the subnet to send a warp message from [msg.sender]
// with the specified parameters.
// This emits a SendWarpMessage log from the precompile. When the corresponding block is accepted
// the Accept hook of the Warp precompile is invoked with all accepted logs emitted by the Warp
// precompile.
// Each validator then adds the UnsignedWarpMessage encoded in the log to the set of messages
// it is willing to sign for an off-chain relayer to aggregate Warp signatures.
function sendWarpMessage(bytes calldata payload) external returns (bytes32 messageID);
// getVerifiedWarpMessage parses the pre-verified warp message in the
// predicate storage slots as a WarpMessage and returns it to the caller.
// If the message exists and passes verification, returns the verified message
// and true.
// Otherwise, returns false and the empty value for the message.
function getVerifiedWarpMessage(uint32 index) external view returns (WarpMessage calldata message, bool valid);
// getVerifiedWarpBlockHash parses the pre-verified WarpBlockHash message in the
// predicate storage slots as a WarpBlockHash message and returns it to the caller.
// If the message exists and passes verification, returns the verified message
// and true.
// Otherwise, returns false and the empty value for the message.
function getVerifiedWarpBlockHash(
uint32 index
) external view returns (WarpBlockHash calldata warpBlockHash, bool valid);
// getBlockchainID returns the snow.Context BlockchainID of this chain.
// This blockchainID is the hash of the transaction that created this blockchain on the P-Chain
// and is not related to the Ethereum ChainID.
function getBlockchainID() external view returns (bytes32 blockchainID);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
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: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @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 AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @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
* {FailedInnerCall} 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 AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @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, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @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, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @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 {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// 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 (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @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 {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @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 ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 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 ERC165Upgradeable is Initializable, IERC165 {
function __ERC165_init() internal onlyInitializing {
}
function __ERC165_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165Checker.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Library used to query support of an interface declared via {IERC165}.
*
* Note that these functions return the actual result of the query: they do not
* `revert` if an interface is not supported. It is up to the caller to decide
* what to do in these cases.
*/
library ERC165Checker {
// As per the EIP-165 spec, no interface should ever match 0xffffffff
bytes4 private constant INTERFACE_ID_INVALID = 0xffffffff;
/**
* @dev Returns true if `account` supports the {IERC165} interface.
*/
function supportsERC165(address account) internal view returns (bool) {
// Any contract that implements ERC165 must explicitly indicate support of
// InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
return
supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
!supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID);
}
/**
* @dev Returns true if `account` supports the interface defined by
* `interfaceId`. Support for {IERC165} itself is queried automatically.
*
* See {IERC165-supportsInterface}.
*/
function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
// query support of both ERC165 as per the spec and support of _interfaceId
return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
}
/**
* @dev Returns a boolean array where each value corresponds to the
* interfaces passed in and whether they're supported or not. This allows
* you to batch check interfaces for a contract where your expectation
* is that some interfaces may not be supported.
*
* See {IERC165-supportsInterface}.
*/
function getSupportedInterfaces(
address account,
bytes4[] memory interfaceIds
) internal view returns (bool[] memory) {
// an array of booleans corresponding to interfaceIds and whether they're supported or not
bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);
// query support of ERC165 itself
if (supportsERC165(account)) {
// query support of each interface in interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
}
}
return interfaceIdsSupported;
}
/**
* @dev Returns true if `account` supports all the interfaces defined in
* `interfaceIds`. Support for {IERC165} itself is queried automatically.
*
* Batch-querying can lead to gas savings by skipping repeated checks for
* {IERC165} support.
*
* See {IERC165-supportsInterface}.
*/
function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
// query support of ERC165 itself
if (!supportsERC165(account)) {
return false;
}
// query support of each interface in interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
return false;
}
}
// all interfaces supported
return true;
}
/**
* @notice Query if a contract implements an interface, does not check ERC165 support
* @param account The address of the contract to query for support of an interface
* @param interfaceId The interface identifier, as specified in ERC-165
* @return true if the contract at account indicates support of the interface with
* identifier interfaceId, false otherwise
* @dev Assumes that account contains a contract that supports ERC165, otherwise
* the behavior of this method is undefined. This precondition can be checked
* with {supportsERC165}.
*
* Some precompiled contracts will falsely indicate support for a given interface, so caution
* should be exercised when using this function.
*
* Interface identification is specified in ERC-165.
*/
function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
// prepare call
bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId));
// perform static call
bool success;
uint256 returnSize;
uint256 returnValue;
assembly {
success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
returnSize := returndatasize()
returnValue := mload(0x00)
}
return success && returnSize >= 0x20 && returnValue > 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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 ERC165 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 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
interface IBaseDelegator {
error BaseDelegator__AlreadySet();
error BaseDelegator__InsufficientHookGas();
error BaseDelegator__NotL1();
error BaseDelegator__NotSlasher();
error BaseDelegator__NotVault();
error BaseDelegator__NotInitialized();
error BaseDelegator__ZeroAddress(string name);
error BaseDelegator__NotAuthorizedMiddleware();
/**
* @notice Base parameters needed for delegators' deployment.
* @param defaultAdminRoleHolder address of the initial DEFAULT_ADMIN_ROLE holder
* @param hook address of the hook contract
* @param hookSetRoleHolder address of the initial HOOK_SET_ROLE holder
*/
struct BaseParams {
address defaultAdminRoleHolder;
address hook;
address hookSetRoleHolder;
}
/**
* @notice Base hints for a stake.
* @param operatorVaultOptInHint hint for the operator-vault opt-in
* @param operatorL1OptInHint hint for the operator-l1 opt-in
*/
struct StakeBaseHints {
bytes operatorVaultOptInHint;
bytes operatorL1OptInHint;
}
/**
* @notice Emitted when a asset class maximum limit is set.
* @param l1 address of the l1.
* @param assetClass the uint96 assetClass.
* @param amount new maximum asset class limit (how much stake the asset class is ready to get)
*/
event SetMaxL1Limit(address indexed l1, uint96 indexed assetClass, uint256 amount);
/**
* @notice Emitted when a slash happens.
* @param l1 address of the l1.
* @param assetClass the uint96 assetClass.
* @param operator address of the operator
* @param amount amount of the collateral to be slashed
* @param captureTimestamp time point when the stake was captured
*/
event OnSlash(
address indexed l1, uint96 indexed assetClass, address indexed operator, uint256 amount, uint48 captureTimestamp
);
/**
* @notice Emitted when a hook is set.
* @param hook address of the hook
*/
event SetHook(address indexed hook);
/**
* @notice Get a version of the delegator (different versions mean different interfaces).
* @return version of the delegator
* @dev Must return 1 for this one.
*/
/**
* @notice Get the factory's address.
* @return address of the factory
*/
function FACTORY() external view returns (address);
/**
* @notice Get the entity's type.
* @return type of the entity
*/
function TYPE() external view returns (uint64);
/**
* @notice Initialize this entity contract by using a given data.
* @param data some data to use
*/
function initialize(
bytes calldata data
) external;
function VERSION() external view returns (uint64);
/**
* @notice Get the l1 registry's address.
* @return address of the l1 registry
*/
function L1_REGISTRY() external view returns (address);
/**
* @notice Get the vault factory's address.
* @return address of the vault factory
*/
function VAULT_FACTORY() external view returns (address);
/**
* @notice Get the operator-vault opt-in service's address.
* @return address of the operator-vault opt-in service
*/
function OPERATOR_VAULT_OPT_IN_SERVICE() external view returns (address);
/**
* @notice Get the operator-l1 opt-in service's address.
* @return address of the operator-l1 opt-in service
*/
function OPERATOR_L1_OPT_IN_SERVICE() external view returns (address);
/**
* @notice Get a gas limit for the hook.
* @return value of the hook gas limit
*/
function HOOK_GAS_LIMIT() external view returns (uint256);
/**
* @notice Get a reserve gas between the gas limit check and the hook's execution.
* @return value of the reserve gas
*/
function HOOK_RESERVE() external view returns (uint256);
/**
* @notice Get a hook setter's role.
* @return assetClass of the hook setter role
*/
function HOOK_SET_ROLE() external view returns (bytes32);
/**
* @notice Get the vault's address.
* @return address of the vault
*/
function vault() external view returns (address);
/**
* @notice Get the hook's address.
* @return address of the hook
* @dev The hook can have arbitrary logic under certain functions, however, it doesn't affect the stake guarantees.
*/
function hook() external view returns (address);
/**
* @notice Get a particular asset class maximum limit
* (meaning the asset class is not ready to get more as a stake).
* @param l1 address of the l1.
* @param assetClass the uint96 assetClass.
* @return maximum limit of the asset class
*/
function maxL1Limit(address l1, uint96 assetClass) external view returns (uint256);
/**
* @notice Get a stake that a given asset class could be able to slash for a certain operator at a given timestamp
* until the end of the consequent epoch using hints (if no cross-slashing and no slashings by the asset class).
* @param l1 address of the l1.
* @param assetClass the uint96 assetClass.
* @param operator address of the operator
* @param timestamp time point to capture the stake at
* @param hints hints for the checkpoints' indexes
* @return slashable stake at the given timestamp until the end of the consequent epoch
* @dev Warning: it is not safe to use timestamp >= current one for the stake capturing, as it can change later.
*/
function stakeAt(
address l1,
uint96 assetClass,
address operator,
uint48 timestamp,
bytes memory hints
) external view returns (uint256);
/**
* @notice Get a stake that a given asset class will be able to slash
* for a certain operator until the end of the next epoch (if no cross-slashing and no slashings by the asset class).
* @param l1 address of the l1.
* @param assetClass the uint96 assetClass.
* @param operator address of the operator
* @return slashable stake until the end of the next epoch
* @dev Warning: this function is not safe to use for stake capturing, as it can change by the end of the block.
*/
function stake(address l1, uint96 assetClass, address operator) external view returns (uint256);
/**
* @notice Set a maximum limit for a asset class (how much stake the asset class is ready to get).
* assetClass assetClass of the asset class
* @param l1 address of the l1
* @param amount new maximum asset class limit
* @dev Only a l1 can call this function.
*/
function setMaxL1Limit(address l1, uint96 assetClass, uint256 amount) external;
/**
* @notice Set a new hook.
* @param hook address of the hook
* @dev Only a HOOK_SET_ROLE holder can call this function.
* The hook can have arbitrary logic under certain functions, however, it doesn't affect the stake guarantees.
*/
function setHook(
address hook
) external;
/**
* @notice Called when a slash happens.
* @param l1 address of the l1.
* @param assetClass the uint96 assetClass.
* @param operator address of the operator
* @param amount amount of the collateral slashed
* @param captureTimestamp time point when the stake was captured
* @param data some additional data
* @dev Only the vault's slasher can call this function.
*/
function onSlash(
address l1,
uint96 assetClass,
address operator,
uint256 amount,
uint48 captureTimestamp,
bytes calldata data
) external;
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
interface IDelegatorHook {
/**
* @notice Called when a slash happens.
* @param l1 address of the l1.
* @param assetClass the uint96 assetClass.
* @param operator address of the operator
* @param amount amount of the collateral to be slashed
* @param captureTimestamp time point when the stake was captured
* @param data some additional data
*/
function onSlash(
address l1,
uint96 assetClass,
address operator,
uint256 amount,
uint48 captureTimestamp,
bytes calldata data
) external;
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
import {IMigratablesFactory} from "./common/IMigratablesFactory.sol";
interface IVaultFactory is IMigratablesFactory {}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
interface IL1Registry {
event RegisterL1(address indexed l1);
event SetL1Middleware(address indexed l1, address indexed l1Middleware);
event SetMetadataURL(address indexed l1, string metadataURL);
error L1Registry__L1AlreadyRegistered();
error L1Registry__L1NotRegistered();
error L1Registry__InvalidValidatorManager(address l1);
error L1Registry__InvalidL1Middleware();
error L1Registry__NotValidatorManagerOwner(address caller, address expectedOwner);
error L1Registry__InsufficientFee();
error L1Registry__FeeTransferFailed();
error L1Registry__FeeExceedsMaximum(uint256 newFee, uint256 maxFee);
error L1Registry__ZeroAddress(string name);
error L1Registry__NotFeeCollector(address caller);
error L1Registry__NoFeesToWithdraw();
/**
* @notice Register an Avalanche L1
* @dev l1 must be the manager of the Avalanche L1
* @dev msg.sender must be a SecurityModule of the l1
* @dev l1Middleware must be a SecurityModule of the Avalanche L1
* @param l1 The Avalanche L1. Should be The ValidatorManager.
* @param l1Middleware The l1Middleware of the Avalanche L1
* @param metadataURL The metadata URL of the Avalanche L1
*/
function registerL1(
address l1,
address l1Middleware,
string calldata metadataURL
)
/*, uint32 messageIndex, SubnetConversionData subnetConversionData*/
external
payable;
/**
* @notice Check if an address is registered as an L1
* @param l1 The Avalanche L1. Should be The ValidatorManager.
* @return True if the address is registered as an L1, false otherwise
*/
function isRegistered(
address l1
) external view returns (bool);
/**
* @notice Check if an address is registered as an L1 and if the Middleware is correct
* @param l1 The Avalanche L1. Should be The ValidatorManager.
* @param l1middleware_ The l1Middleware to check
* @return True if the address is registered as an L1 and the middleware is correct, false otherwise
*/
function isRegisteredWithMiddleware(address l1, address l1middleware_) external view returns (bool);
/**
* @notice Get the L1 at a specific index
* @param index The index of the L1 to get
* @return The address of the L1 at the specified index
* @return The l1Middleware of the L1 at the specified index
* @return The metadata URL of the L1 at the specified index
*/
function getL1At(
uint256 index
) external view returns (address, address, string memory);
/**
* @notice Get the total number of L1s
* @return Total number of L1s
*/
function totalL1s() external view returns (uint256);
/**
* @notice Get all L1s
* @return Array of all L1s
* @return Array of all L1s' l1Middlewares
* @return Array of all L1s' metadata URLs
*/
function getAllL1s() external view returns (address[] memory, address[] memory, string[] memory);
/**
* @notice Set the l1Middleware of an L1
* @param l1 The Avalanche L1. Should be The ValidatorManager.
* @param l1Middleware_ The new l1Middleware
*/
function setL1Middleware(address l1, address l1Middleware_) external;
/**
* @notice Set the metadata URL of an L1
* @param l1 The Avalanche L1. Should be The ValidatorManager.
* @param metadataURL The new metadata URL
*/
function setMetadataURL(address l1, string calldata metadataURL) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
/// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
struct ReentrancyGuardStorage {
uint256 _status;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
assembly {
$.slot := ReentrancyGuardStorageLocation
}
}
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
$._status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// On the first call to nonReentrant, _status will be NOT_ENTERED
if ($._status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
$._status = ENTERED;
}
function _nonReentrantAfter() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
$._status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
return $._status == ENTERED;
}
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
interface IBaseSlasher {
error BaseSlasher__NoBurner();
error BaseSlasher__InsufficientBurnerGas();
error BaseSlasher__NotNetworkMiddleware();
error BaseSlasher__NotVault();
error BaseSlasher__NotInitialized();
/**
* @notice Base parameters needed for slashers' deployment.
* @param isBurnerHook if the burner is needed to be called on a slashing
*/
struct BaseParams {
bool isBurnerHook;
}
/**
* @notice Hints for a slashable stake.
* @param stakeHints hints for the stake checkpoints
* @param cumulativeSlashFromHint hint for the cumulative slash amount at a capture timestamp
*/
struct SlashableStakeHints {
bytes stakeHints;
bytes cumulativeSlashFromHint;
}
/**
* @notice General data for the delegator.
* @param slasherType type of the slasher
* @param data slasher-dependent data for the delegator
*/
struct GeneralDelegatorData {
uint64 slasherType;
bytes data;
}
/**
* @notice Get the factory's address.
* @return address of the factory
*/
function FACTORY() external view returns (address);
/**
* @notice Get the entity's type.
* @return type of the entity
*/
function TYPE() external view returns (uint64);
/**
* @notice Initialize this entity contract by using a given data.
* @param data some data to use
*/
function initialize(
bytes calldata data
) external;
/**
* @notice Get a gas limit for the burner.
* @return value of the burner gas limit
*/
function BURNER_GAS_LIMIT() external view returns (uint256);
/**
* @notice Get a reserve gas between the gas limit check and the burner's execution.
* @return value of the reserve gas
*/
function BURNER_RESERVE() external view returns (uint256);
/**
* @notice Get the vault factory's address.
* @return address of the vault factory
*/
function VAULT_FACTORY() external view returns (address);
/**
* @notice Get the network middleware service's address.
* @return address of the network middleware service
*/
function NETWORK_MIDDLEWARE_SERVICE() external view returns (address);
/**
* @notice Get the vault's address.
* @return address of the vault to perform slashings on
*/
function vault() external view returns (address);
/**
* @notice Get if the burner is needed to be called on a slashing.
* @return if the burner is a hook
*/
function isBurnerHook() external view returns (bool);
/**
* @notice Get the latest capture timestamp that was slashed on a subnetwork.
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @param operator address of the operator
* @return latest capture timestamp that was slashed
*/
function latestSlashedCaptureTimestamp(bytes32 subnetwork, address operator) external view returns (uint48);
/**
* @notice Get a cumulative slash amount for an operator on a subnetwork until a given timestamp (inclusively) using a hint.
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @param operator address of the operator
* @param timestamp time point to get the cumulative slash amount until (inclusively)
* @param hint hint for the checkpoint index
* @return cumulative slash amount until the given timestamp (inclusively)
*/
function cumulativeSlashAt(
bytes32 subnetwork,
address operator,
uint48 timestamp,
bytes memory hint
) external view returns (uint256);
/**
* @notice Get a cumulative slash amount for an operator on a subnetwork.
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @param operator address of the operator
* @return cumulative slash amount
*/
function cumulativeSlash(bytes32 subnetwork, address operator) external view returns (uint256);
/**
* @notice Get a slashable amount of a stake got at a given capture timestamp using hints.
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @param operator address of the operator
* @param captureTimestamp time point to get the stake amount at
* @param hints hints for the checkpoints' indexes
* @return slashable amount of the stake
*/
function slashableStake(
bytes32 subnetwork,
address operator,
uint48 captureTimestamp,
bytes memory hints
) external view returns (uint256);
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
/**
* @title IDelegatorFactory
* @dev Combined interface integrating IDelegatorFactory, IFactory, IRegistry, and IERC165.
*/
interface IDelegatorFactory is IERC165 {
error DelegatorFactory__EntityNotExist();
error DelegatorFactory__AlreadyBlacklisted();
error DelegatorFactory__AlreadyWhitelisted();
error DelegatorFactory__InvalidImplementation();
error DelegatorFactory__InvalidType();
/**
* @notice Emitted when an entity is added.
* @param entity address of the added entity
*/
event AddEntity(address indexed entity);
/**
* @notice Emitted when a new type is whitelisted.
* @param implementation address of the new implementation
*/
event Whitelist(address indexed implementation);
/**
* @notice Emitted when a type is blacklisted (e.g., in case of invalid implementation).
* @param type_ type that was blacklisted
* @dev The given type is still deployable.
*/
event Blacklist(uint64 indexed type_);
/**
* @notice Query if a contract implements an interface
* @param interfaceId The interface identifier, as specified in ERC-165
* @return `true` if the contract implements `interfaceId` and
* `interfaceId` is not 0xffffffff, `false` otherwise
*/
function supportsInterface(
bytes4 interfaceId
) external view returns (bool);
/**
* @notice Get if a given address is an entity.
* @param account address to check
* @return if the given address is an entity
*/
function isEntity(
address account
) external view returns (bool);
/**
* @notice Get a total number of entities.
* @return total number of entities added
*/
function totalEntities() external view returns (uint256);
/**
* @notice Get an entity given its index.
* @param index index of the entity to get
* @return address of the entity
*/
function entity(
uint256 index
) external view returns (address);
/**
* @notice Get the total number of whitelisted types.
* @return total number of types
*/
function totalTypes() external view returns (uint64);
/**
* @notice Get the implementation for a given type.
* @param type_ position to get the implementation at
* @return address of the implementation
*/
function implementation(
uint64 type_
) external view returns (address);
/**
* @notice Get if a type is blacklisted (e.g., in case of invalid implementation).
* @param type_ type to check
* @return whether the type is blacklisted
* @dev The given type is still deployable.
*/
function blacklisted(
uint64 type_
) external view returns (bool);
/**
* @notice Whitelist a new type of entity.
* @param implementation address of the new implementation
*/
function whitelist(
address implementation
) external;
/**
* @notice Blacklist a type of entity.
* @param type_ type to blacklist
* @dev The given type will still be deployable.
*/
function blacklist(
uint64 type_
) external;
/**
* @notice Create a new entity at the factory.
* @param type_ type's implementation to use
* @param data initial data for the entity creation
* @return address of the entity
* @dev CREATE2 salt is constructed from the given parameters.
*/
function create(uint64 type_, bytes calldata data) external returns (address);
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
import {Checkpoints as OZCheckpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
import {Math} from "@openzeppelin/contracts/utils/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 key.
*/
library ExtendedCheckpoints {
using OZCheckpoints for OZCheckpoints.Trace208;
error SystemCheckpoint();
struct Trace208 {
OZCheckpoints.Trace208 _trace;
}
struct Checkpoint208 {
uint48 _key;
uint208 _value;
}
struct Trace256 {
OZCheckpoints.Trace208 _trace;
uint256[] _values;
}
struct Checkpoint256 {
uint48 _key;
uint256 _value;
}
/**
* @dev Pushes a (`key`, `value`) pair into a Trace208 so that it is stored as the checkpoint.
*
* Returns previous value and new value.
*/
function push(Trace208 storage self, uint48 key, uint208 value) internal returns (uint208, uint208) {
return self._trace.push(key, value);
}
/**
* @dev Returns the value in the last (most recent) checkpoint with a key lower or equal than the search key, or zero
* if there is none.
*/
function upperLookupRecent(Trace208 storage self, uint48 key) internal view returns (uint208) {
return self._trace.upperLookupRecent(key);
}
/**
* @dev Returns the value in the last (most recent) checkpoint with a key lower or equal than the search key, or zero
* if there is none.
*
* NOTE: This is a variant of {upperLookupRecent} that can be optimized by getting the hint
* (index of the checkpoint with a key lower or equal than the search key).
*/
function upperLookupRecent(Trace208 storage self, uint48 key, bytes memory hint_) internal view returns (uint208) {
if (hint_.length == 0) {
return upperLookupRecent(self, key);
}
uint32 hint = abi.decode(hint_, (uint32));
Checkpoint208 memory checkpoint = at(self, hint);
if (checkpoint._key == key) {
return checkpoint._value;
}
if (checkpoint._key < key && (hint == length(self) - 1 || at(self, hint + 1)._key > key)) {
return checkpoint._value;
}
return upperLookupRecent(self, key);
}
/**
* @dev Returns whether there is a checkpoint with a key lower or equal than the search key in the structure (i.e. it is not empty),
* and if so the key and value in the checkpoint, and its position in the trace.
*/
function upperLookupRecentCheckpoint(
Trace208 storage self,
uint48 key
) internal view returns (bool, uint48, uint208, uint32) {
uint256 len = self._trace._checkpoints.length;
uint256 low = 0;
uint256 high = len;
if (len > 5) {
uint256 mid = len - Math.sqrt(len);
if (key < _unsafeAccess(self._trace._checkpoints, mid)._key) {
high = mid;
} else {
low = mid + 1;
}
}
uint256 pos = _upperBinaryLookup(self._trace._checkpoints, key, low, high);
if (pos == 0) {
return (false, 0, 0, 0);
}
OZCheckpoints.Checkpoint208 memory checkpoint = _unsafeAccess(self._trace._checkpoints, pos - 1);
return (true, checkpoint._key, checkpoint._value, uint32(pos - 1));
}
/**
* @dev Returns whether there is a checkpoint with a key lower or equal than the search key in the structure (i.e. it is not empty),
* and if so the key and value in the checkpoint, and its position in the trace.
*
* NOTE: This is a variant of {upperLookupRecentCheckpoint} that can be optimized by getting the hint
* (index of the checkpoint with a key lower or equal than the search key).
*/
function upperLookupRecentCheckpoint(
Trace208 storage self,
uint48 key,
bytes memory hint_
) internal view returns (bool, uint48, uint208, uint32) {
if (hint_.length == 0) {
return upperLookupRecentCheckpoint(self, key);
}
uint32 hint = abi.decode(hint_, (uint32));
Checkpoint208 memory checkpoint = at(self, hint);
if (checkpoint._key == key) {
return (true, checkpoint._key, checkpoint._value, hint);
}
if (checkpoint._key < key && (hint == length(self) - 1 || at(self, hint + 1)._key > key)) {
return (true, checkpoint._key, checkpoint._value, hint);
}
return upperLookupRecentCheckpoint(self, key);
}
/**
* @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) {
return self._trace.latest();
}
/**
* @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, uint48, uint208) {
return self._trace.latestCheckpoint();
}
/**
* @dev Returns a total number of checkpoints.
*/
function length(
Trace208 storage self
) internal view returns (uint256) {
return self._trace.length();
}
/**
* @dev Returns checkpoint at a given position.
*/
function at(Trace208 storage self, uint32 pos) internal view returns (Checkpoint208 memory) {
OZCheckpoints.Checkpoint208 memory checkpoint = self._trace.at(pos);
return Checkpoint208({_key: checkpoint._key, _value: checkpoint._value});
}
/**
* @dev Pops the last (most recent) checkpoint.
*/
function pop(
Trace208 storage self
) internal returns (uint208 value) {
value = self._trace.latest();
self._trace._checkpoints.pop();
}
/**
* @dev Pushes a (`key`, `value`) pair into a Trace256 so that it is stored as the checkpoint.
*
* Returns previous value and new value.
*/
function push(Trace256 storage self, uint48 key, uint256 value) internal returns (uint256, uint256) {
if (self._values.length == 0) {
self._values.push(0);
}
uint256 len = self._values.length;
self._trace.push(key, uint208(len));
self._values.push(value);
return (self._values[len - 1], value);
}
/**
* @dev Returns the value in the last (most recent) checkpoint with a key lower or equal than the search key, or zero
* if there is none.
*/
function upperLookupRecent(Trace256 storage self, uint48 key) internal view returns (uint256) {
uint208 idx = self._trace.upperLookupRecent(key);
return idx > 0 ? self._values[idx] : 0;
}
/**
* @dev Returns the value in the last (most recent) checkpoint with a key lower or equal than the search key, or zero
* if there is none.
*
* NOTE: This is a variant of {upperLookupRecent} that can be optimized by getting the hint
* (index of the checkpoint with a key lower or equal than the search key).
*/
function upperLookupRecent(Trace256 storage self, uint48 key, bytes memory hint_) internal view returns (uint256) {
if (hint_.length == 0) {
return upperLookupRecent(self, key);
}
uint32 hint = abi.decode(hint_, (uint32));
Checkpoint256 memory checkpoint = at(self, hint);
if (checkpoint._key == key) {
return checkpoint._value;
}
if (checkpoint._key < key && (hint == length(self) - 1 || at(self, hint + 1)._key > key)) {
return checkpoint._value;
}
return upperLookupRecent(self, key);
}
/**
* @dev Returns whether there is a checkpoint with a key lower or equal than the search key in the structure (i.e. it is not empty),
* and if so the key and value in the checkpoint, and its position in the trace.
*/
function upperLookupRecentCheckpoint(
Trace256 storage self,
uint48 key
) internal view returns (bool, uint48, uint256, uint32) {
uint256 len = self._trace._checkpoints.length;
uint256 low = 0;
uint256 high = len;
if (len > 5) {
uint256 mid = len - Math.sqrt(len);
if (key < _unsafeAccess(self._trace._checkpoints, mid)._key) {
high = mid;
} else {
low = mid + 1;
}
}
uint256 pos = _upperBinaryLookup(self._trace._checkpoints, key, low, high);
if (pos == 0) {
return (false, 0, 0, 0);
}
OZCheckpoints.Checkpoint208 memory checkpoint = _unsafeAccess(self._trace._checkpoints, pos - 1);
return (true, checkpoint._key, self._values[checkpoint._value], uint32(pos - 1));
}
/**
* @dev Returns whether there is a checkpoint with a key lower or equal than the search key in the structure (i.e. it is not empty),
* and if so the key and value in the checkpoint, and its position in the trace.
*
* NOTE: This is a variant of {upperLookupRecentCheckpoint} that can be optimized by getting the hint
* (index of the checkpoint with a key lower or equal than the search key).
*/
function upperLookupRecentCheckpoint(
Trace256 storage self,
uint48 key,
bytes memory hint_
) internal view returns (bool, uint48, uint256, uint32) {
if (hint_.length == 0) {
return upperLookupRecentCheckpoint(self, key);
}
uint32 hint = abi.decode(hint_, (uint32));
Checkpoint256 memory checkpoint = at(self, hint);
if (checkpoint._key == key) {
return (true, checkpoint._key, self._values[checkpoint._value], hint);
}
if (checkpoint._key < key && (hint == length(self) - 1 || at(self, hint + 1)._key > key)) {
return (true, checkpoint._key, self._values[checkpoint._value], hint);
}
return upperLookupRecentCheckpoint(self, key);
}
/**
* @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) {
uint208 idx = self._trace.latest();
return idx > 0 ? self._values[idx] : 0;
}
/**
* @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, uint48 _key, uint256 _value) {
uint256 idx;
(exists, _key, idx) = self._trace.latestCheckpoint();
_value = exists ? self._values[idx] : 0;
}
/**
* @dev Returns a total number of checkpoints.
*/
function length(
Trace256 storage self
) internal view returns (uint256) {
return self._trace.length();
}
/**
* @dev Returns checkpoint at a given position.
*/
function at(Trace256 storage self, uint32 pos) internal view returns (Checkpoint256 memory) {
OZCheckpoints.Checkpoint208 memory checkpoint = self._trace.at(pos);
return Checkpoint256({_key: checkpoint._key, _value: self._values[checkpoint._value]});
}
/**
* @dev Pops the last (most recent) checkpoint.
*/
function pop(
Trace256 storage self
) internal returns (uint256 value) {
uint208 idx = self._trace.latest();
if (idx == 0) {
revert SystemCheckpoint();
}
value = self._values[idx];
self._trace._checkpoints.pop();
}
/**
* @dev Return the index of the last (most recent) checkpoint with a key lower 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 _upperBinaryLookup(
OZCheckpoints.Checkpoint208[] 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 Access an element of the array without performing a bounds check. The position is assumed to be within bounds.
*/
function _unsafeAccess(
OZCheckpoints.Checkpoint208[] storage self,
uint256 pos
) private pure returns (OZCheckpoints.Checkpoint208 storage result) {
assembly {
mstore(0, self.slot)
result.slot := add(keccak256(0, 0x20), pos)
}
}
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
/**
* @dev This library adds helper functions for ERC4626 math operations.
*/
library ERC4626Math {
using Math for uint256;
/// @dev Offset used in calculations to prevent division by zero and improve precision
uint256 private constant DECIMALS_OFFSET = 0;
function previewDeposit(uint256 assets, uint256 totalShares, uint256 totalAssets) internal pure returns (uint256) {
return convertToShares(assets, totalShares, totalAssets, Math.Rounding.Floor);
}
function previewMint(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
return convertToAssets(shares, totalAssets, totalShares, Math.Rounding.Ceil);
}
function previewWithdraw(
uint256 assets,
uint256 totalShares,
uint256 totalAssets
) internal pure returns (uint256) {
return convertToShares(assets, totalShares, totalAssets, Math.Rounding.Ceil);
}
function previewRedeem(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
return convertToAssets(shares, totalAssets, totalShares, Math.Rounding.Floor);
}
/**
* @dev Internal conversion function (from assets to shares) with support for rounding direction.
*/
function convertToShares(
uint256 assets,
uint256 totalShares,
uint256 totalAssets,
Math.Rounding rounding
) internal pure returns (uint256) {
return assets.mulDiv(totalShares + 10 ** DECIMALS_OFFSET, totalAssets + 1, rounding);
}
/**
* @dev Internal conversion function (from shares to assets) with support for rounding direction.
*/
function convertToAssets(
uint256 shares,
uint256 totalAssets,
uint256 totalShares,
Math.Rounding rounding
) internal pure returns (uint256) {
return shares.mulDiv(totalAssets + 1, totalShares + 10 **DECIMALS_OFFSET, rounding);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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 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 an uint of `bits` size.
*/
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
/**
* @dev An int value doesn't fit in an 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 An 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);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol";
import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*/
abstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20, IERC20Metadata, IERC20Errors {
/// @custom:storage-location erc7201:openzeppelin.storage.ERC20
struct ERC20Storage {
mapping(address account => uint256) _balances;
mapping(address account => mapping(address spender => uint256)) _allowances;
uint256 _totalSupply;
string _name;
string _symbol;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00;
function _getERC20Storage() private pure returns (ERC20Storage storage $) {
assembly {
$.slot := ERC20StorageLocation
}
}
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
__ERC20_init_unchained(name_, symbol_);
}
function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
ERC20Storage storage $ = _getERC20Storage();
$._name = name_;
$._symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
ERC20Storage storage $ = _getERC20Storage();
return $._name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
ERC20Storage storage $ = _getERC20Storage();
return $._symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
return $._totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
return $._balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
return $._allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
ERC20Storage storage $ = _getERC20Storage();
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
$._totalSupply += value;
} else {
uint256 fromBalance = $._balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
$._balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
$._totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
$._balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
ERC20Storage storage $ = _getERC20Storage();
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
$._allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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 OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @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.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
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) {
OwnableStorage storage $ = _getOwnableStorage();
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 {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity 0.8.25;
import {
IValidatorManager,
Validator,
ValidatorChurnPeriod,
ValidatorManagerSettings,
ValidatorRegistrationInput
} from "@avalabs/icm-contracts/validator-manager/interfaces/IValidatorManager.sol";
/**
* @dev Balancer Validator Manager settings, used to initialize the Balancer Validator Manager
*/
struct BalancerValidatorManagerSettings {
ValidatorManagerSettings baseSettings;
address initialOwner;
address initialSecurityModule;
uint64 initialSecurityModuleMaxWeight;
bytes[] migratedValidators;
}
/**
* @title IBalancerValidatorManager
* @author ADDPHO
* @notice Interface for Balancer Validator Manager contracts
* @custom:security-contact [email protected]
*/
interface IBalancerValidatorManager is IValidatorManager {
/**
* @notice Emitted when a security module is registered, updated, or removed (maxWeight = 0)
* @param securityModule The address of the security module
* @param maxWeight The maximum total weight allowed for validators managed by this module
*/
event SetUpSecurityModule(address indexed securityModule, uint64 maxWeight);
error BalancerValidatorManager__MigratedValidatorsTotalWeightMismatch(
uint64 migratedValidatorsTotalWeight, uint64 currentL1TotalWeight
);
error BalancerValidatorManager__SecurityModuleAlreadyRegistered(address securityModule);
error BalancerValidatorManager__SecurityModuleNotRegistered(address securityModule);
error BalancerValidatorManager__SecurityModuleMaxWeightExceeded(
address securityModule, uint64 weight, uint64 maxWeight
);
error BalancerValidatorManager__SecurityModuleNewMaxWeightLowerThanCurrentWeight(
address securityModule, uint64 newMaxWeight, uint64 currentWeight
);
error BalancerValidatorManager__InitialSecurityModuleMaxWeightLowerThanTotalWeight(
address securityModule, uint64 initialMaxWeight, uint64 totalWeight
);
error BalancerValidatorManager__NewWeightIsZero();
error BalancerValidatorManager__ValidatorNotBelongingToSecurityModule(
bytes32 validationID, address securityModule
);
error BalancerValidatorManager__PendingWeightUpdate(bytes32 validationID);
error BalancerValidatorManager__NoPendingWeightUpdate(bytes32 validationID);
error BalancerValidatorManager__InvalidNonce(uint64 nonce);
error BalancerValidatorManager__ValidatorAlreadyMigrated(bytes32 validationID);
/**
* @notice Returns the ValidatorManager churn period in seconds
* @return churnPeriodSeconds The churn period in seconds
*/
function getChurnPeriodSeconds() external view returns (uint64 churnPeriodSeconds);
/**
* @notice Returns the maximum churn rate per churn period (in percentage)
* @return maximumChurnPercentage The maximum churn percentage
*/
function getMaximumChurnPercentage() external view returns (uint64 maximumChurnPercentage);
/**
* @notice Returns the current churn period
* @return churnPeriod The current churn period
*/
function getCurrentChurnPeriod()
external
view
returns (ValidatorChurnPeriod memory churnPeriod);
/**
* @notice Returns the list of registered security modules
* @return securityModules The list of registered security modules
*/
function getSecurityModules() external view returns (address[] memory securityModules);
/**
* @notice Returns the weight associated with a security module
* @param securityModule The address of the security module
* @return weight The weight of the security module
*/
function getSecurityModuleWeights(
address securityModule
) external view returns (uint64 weight, uint64 maxWeight);
/**
* @notice Returns whether a validator has a pending weight update
* @param validationID The ID of the validator
* @return Whether the validator has a pending weight update
*/
function isValidatorPendingWeightUpdate(
bytes32 validationID
) external view returns (bool);
/**
* @notice Returns the L1ID associated with this validator manager
* @return l1ID The L1ID
*/
function getL1ID() external view returns (bytes32 l1ID);
/**
* @notice Registers a new security module with a maximum weight limit
* @param securityModule The address of the security module to register
* @param maxWeight The maximum total weight allowed for validators managed by this module
*/
function setUpSecurityModule(address securityModule, uint64 maxWeight) external;
/**
* @notice Begins the validator registration process, and sets the {weight} of the validator.
* @param registrationInput The inputs for a validator registration.
* @param weight The weight of the validator being registered.
* @return validationID The ID of the validator registration.
*/
/**
* @notice Begins the validator registration process, and sets the {weight} of the validator.
* @dev Can only be called by registered security modules
* @param registrationInput The inputs for a validator registration.
* @param weight The weight of the validator being registered.
* @return validationID The ID of the validator registration.
*/
function initializeValidatorRegistration(
ValidatorRegistrationInput calldata registrationInput,
uint64 weight
) external returns (bytes32 validationID);
/**
* @notice Begins the process of ending an active validation period. The validation period must have been previously
* started by a successful call to {completeValidatorRegistration} with the given validationID.
* @dev Can only be called by the security module that registered the validator
* @param validationID The ID of the validation period being ended.
* @return validator The validator that is being ended
*/
function initializeEndValidation(
bytes32 validationID
) external returns (Validator memory validator);
/**
* @notice Initiates a weight update for a validator
* @dev Can only be called by the security module that registered the validator
* @param validationID The ID of the validation period being updated
* @param newWeight The new weight to set for the validator
*/
function initializeValidatorWeightUpdate(
bytes32 validationID,
uint64 newWeight
) external returns (Validator memory validator);
/**
* @notice Completes a pending validator weight update after P-Chain confirmation
* @dev Can only be called by the security module that registered the validator
* @param validationID The ID of the validation period being updated
* @param messageIndex The index of the Warp message containing the weight update confirmation
*/
function completeValidatorWeightUpdate(bytes32 validationID, uint32 messageIndex) external;
/**
* @notice Resends a pending validator weight update message to the P-Chain
* @param validationID The ID of the validation period being updated
*/
function resendValidatorWeightUpdate(
bytes32 validationID
) external;
}// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// SPDX-License-Identifier: Ecosystem
pragma solidity 0.8.25;
import {ValidatorMessages} from "./ValidatorMessages.sol";
import {
InitialValidator,
IValidatorManager,
PChainOwner,
ConversionData,
Validator,
ValidatorChurnPeriod,
ValidatorManagerSettings,
ValidatorRegistrationInput,
ValidatorStatus
} from "./interfaces/IValidatorManager.sol";
import {
IWarpMessenger,
WarpMessage
} from "@avalabs/[email protected]/contracts/interfaces/IWarpMessenger.sol";
import {ContextUpgradeable} from
"@openzeppelin/[email protected]/utils/ContextUpgradeable.sol";
import {Initializable} from
"@openzeppelin/[email protected]/proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IValidatorManager} interface.
*
* @custom:security-contact https://github.com/ava-labs/icm-contracts/blob/main/SECURITY.md
*/
abstract contract ValidatorManager is Initializable, ContextUpgradeable, IValidatorManager {
// solhint-disable private-vars-leading-underscore
/// @custom:storage-location erc7201:avalanche-icm.storage.ValidatorManager
struct ValidatorManagerStorage {
/// @notice The l1ID associated with this validator manager.
bytes32 _l1ID;
/// @notice The number of seconds after which to reset the churn tracker.
uint64 _churnPeriodSeconds;
/// @notice The maximum churn rate allowed per churn period.
uint8 _maximumChurnPercentage;
/// @notice The churn tracker used to track the amount of stake added or removed in the churn period.
ValidatorChurnPeriod _churnTracker;
/// @notice Maps the validationID to the registration message such that the message can be re-sent if needed.
mapping(bytes32 => bytes) _pendingRegisterValidationMessages;
/// @notice Maps the validationID to the validator information.
mapping(bytes32 => Validator) _validationPeriods;
/// @notice Maps the nodeID to the validationID for validation periods that have not ended.
mapping(bytes => bytes32) _registeredValidators;
/// @notice Boolean that indicates if the initial validator set has been set.
bool _initializedValidatorSet;
}
// solhint-enable private-vars-leading-underscore
// keccak256(abi.encode(uint256(keccak256("avalanche-icm.storage.ValidatorManager")) - 1)) & ~bytes32(uint256(0xff));
bytes32 public constant VALIDATOR_MANAGER_STORAGE_LOCATION =
0xe92546d698950ddd38910d2e15ed1d923cd0a7b3dde9e2a6a3f380565559cb00;
uint8 public constant MAXIMUM_CHURN_PERCENTAGE_LIMIT = 20;
uint64 public constant MAXIMUM_REGISTRATION_EXPIRY_LENGTH = 2 days;
uint32 public constant ADDRESS_LENGTH = 20; // This is only used as a packed uint32
uint8 public constant BLS_PUBLIC_KEY_LENGTH = 48;
bytes32 public constant P_CHAIN_BLOCKCHAIN_ID = bytes32(0);
error InvalidValidatorManagerAddress(address validatorManagerAddress);
error InvalidWarpOriginSenderAddress(address senderAddress);
error InvalidValidatorManagerBlockchainID(bytes32 blockchainID);
error InvalidWarpSourceChainID(bytes32 sourceChainID);
error InvalidRegistrationExpiry(uint64 registrationExpiry);
error InvalidInitializationStatus();
error InvalidMaximumChurnPercentage(uint8 maximumChurnPercentage);
error InvalidBLSKeyLength(uint256 length);
error InvalidNodeID(bytes nodeID);
error InvalidConversionID(bytes32 encodedConversionID, bytes32 expectedConversionID);
error InvalidTotalWeight(uint64 weight);
error InvalidValidationID(bytes32 validationID);
error InvalidValidatorStatus(ValidatorStatus status);
error InvalidWarpMessage();
error MaxChurnRateExceeded(uint64 churnAmount);
error NodeAlreadyRegistered(bytes nodeID);
error UnexpectedRegistrationStatus(bool validRegistration);
error InvalidPChainOwnerThreshold(uint256 threshold, uint256 addressesLength);
error PChainOwnerAddressesNotSorted();
// solhint-disable ordering
/**
* @dev This storage is visible to child contracts for convenience.
* External getters would be better practice, but code size limitations are preventing this.
* Child contracts should probably never write to this storage.
*/
function _getValidatorManagerStorage()
internal
pure
returns (ValidatorManagerStorage storage $)
{
// solhint-disable-next-line no-inline-assembly
assembly {
$.slot := VALIDATOR_MANAGER_STORAGE_LOCATION
}
}
/**
* @notice Warp precompile used for sending and receiving Warp messages.
*/
IWarpMessenger public constant WARP_MESSENGER =
IWarpMessenger(0x0200000000000000000000000000000000000005);
// solhint-disable-next-line func-name-mixedcase
function __ValidatorManager_init(ValidatorManagerSettings calldata settings)
internal
onlyInitializing
{
__Context_init();
__ValidatorManager_init_unchained(settings);
}
// solhint-disable-next-line func-name-mixedcase
function __ValidatorManager_init_unchained(ValidatorManagerSettings calldata settings)
internal
onlyInitializing
{
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
$._l1ID = settings.l1ID;
if (
settings.maximumChurnPercentage > MAXIMUM_CHURN_PERCENTAGE_LIMIT
|| settings.maximumChurnPercentage == 0
) {
revert InvalidMaximumChurnPercentage(settings.maximumChurnPercentage);
}
$._maximumChurnPercentage = settings.maximumChurnPercentage;
$._churnPeriodSeconds = settings.churnPeriodSeconds;
}
modifier initializedValidatorSet() {
if (!_getValidatorManagerStorage()._initializedValidatorSet) {
revert InvalidInitializationStatus();
}
_;
}
/**
* @notice See {IValidatorManager-initializeValidatorSet}.
*/
function initializeValidatorSet(
ConversionData calldata conversionData,
uint32 messageIndex
) external {
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
if ($._initializedValidatorSet) {
revert InvalidInitializationStatus();
}
// Check that the blockchainID and validator manager address in the ConversionData correspond to this contract.
// Other validation checks are done by the P-Chain when converting the L1, so are not required here.
if (conversionData.validatorManagerBlockchainID != WARP_MESSENGER.getBlockchainID()) {
revert InvalidValidatorManagerBlockchainID(conversionData.validatorManagerBlockchainID);
}
if (address(conversionData.validatorManagerAddress) != address(this)) {
revert InvalidValidatorManagerAddress(address(conversionData.validatorManagerAddress));
}
uint256 numInitialValidators = conversionData.initialValidators.length;
uint64 totalWeight;
for (uint32 i; i < numInitialValidators; ++i) {
InitialValidator memory initialValidator = conversionData.initialValidators[i];
if ($._registeredValidators[initialValidator.nodeID] != bytes32(0)) {
revert NodeAlreadyRegistered(initialValidator.nodeID);
}
// Validation ID of the initial validators is the sha256 hash of the
// convert subnet to L1 tx ID and the index of the initial validator.
bytes32 validationID = sha256(abi.encodePacked(conversionData.l1ID, i));
// Save the initial validator as an active validator.
$._registeredValidators[initialValidator.nodeID] = validationID;
$._validationPeriods[validationID].status = ValidatorStatus.Active;
$._validationPeriods[validationID].nodeID = initialValidator.nodeID;
$._validationPeriods[validationID].startingWeight = initialValidator.weight;
$._validationPeriods[validationID].messageNonce = 0;
$._validationPeriods[validationID].weight = initialValidator.weight;
$._validationPeriods[validationID].startedAt = uint64(block.timestamp);
$._validationPeriods[validationID].endedAt = 0;
totalWeight += initialValidator.weight;
emit InitialValidatorCreated(
validationID, initialValidator.nodeID, initialValidator.weight
);
}
$._churnTracker.totalWeight = totalWeight;
// Rearranged equation for totalWeight < (100 / $._maximumChurnPercentage)
// Total weight must be above this value in order to not trigger churn limits with an added/removed weight of 1.
if (totalWeight * $._maximumChurnPercentage < 100) {
revert InvalidTotalWeight(totalWeight);
}
// Verify that the sha256 hash of the L1 conversion data matches with the Warp message's conversionID.
bytes32 conversionID = ValidatorMessages.unpackSubnetToL1ConversionMessage(
_getPChainWarpMessage(messageIndex).payload
);
bytes memory encodedConversion = ValidatorMessages.packConversionData(conversionData);
bytes32 encodedConversionID = sha256(encodedConversion);
if (encodedConversionID != conversionID) {
revert InvalidConversionID(encodedConversionID, conversionID);
}
$._initializedValidatorSet = true;
}
function _validatePChainOwner(PChainOwner calldata pChainOwner) internal pure {
// If threshold is 0, addresses must be empty.
if (pChainOwner.threshold == 0 && pChainOwner.addresses.length != 0) {
revert InvalidPChainOwnerThreshold(pChainOwner.threshold, pChainOwner.addresses.length);
}
// Threshold must be less than or equal to the number of addresses.
if (pChainOwner.threshold > pChainOwner.addresses.length) {
revert InvalidPChainOwnerThreshold(pChainOwner.threshold, pChainOwner.addresses.length);
}
// Addresses must be sorted in ascending order
for (uint256 i = 1; i < pChainOwner.addresses.length; i++) {
// Compare current address with the previous one
if (pChainOwner.addresses[i] < pChainOwner.addresses[i - 1]) {
revert PChainOwnerAddressesNotSorted();
}
}
}
/**
* @notice Begins the validator registration process, and sets the initial weight for the validator.
* This is the only method related to validator registration and removal that needs the initializedValidatorSet
* modifier. All others are guarded by checking the validator status changes initialized in this function.
* @param input The inputs for a validator registration.
* @param weight The weight of the validator being registered.
*/
function _initializeValidatorRegistration(
ValidatorRegistrationInput calldata input,
uint64 weight
) internal virtual initializedValidatorSet returns (bytes32) {
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
if (
input.registrationExpiry <= block.timestamp
|| input.registrationExpiry >= block.timestamp + MAXIMUM_REGISTRATION_EXPIRY_LENGTH
) {
revert InvalidRegistrationExpiry(input.registrationExpiry);
}
// Ensure the new validator doesn't overflow the total weight
if (uint256(weight) + uint256($._churnTracker.totalWeight) > type(uint64).max) {
revert InvalidTotalWeight(weight);
}
_validatePChainOwner(input.remainingBalanceOwner);
_validatePChainOwner(input.disableOwner);
// Ensure the nodeID is not the zero address, and is not already an active validator.
if (input.blsPublicKey.length != BLS_PUBLIC_KEY_LENGTH) {
revert InvalidBLSKeyLength(input.blsPublicKey.length);
}
if (input.nodeID.length == 0) {
revert InvalidNodeID(input.nodeID);
}
if ($._registeredValidators[input.nodeID] != bytes32(0)) {
revert NodeAlreadyRegistered(input.nodeID);
}
// Check that adding this validator would not exceed the maximum churn rate.
_checkAndUpdateChurnTracker(weight, 0);
(bytes32 validationID, bytes memory registerL1ValidatorMessage) = ValidatorMessages
.packRegisterL1ValidatorMessage(
ValidatorMessages.ValidationPeriod({
l1ID: $._l1ID,
nodeID: input.nodeID,
blsPublicKey: input.blsPublicKey,
remainingBalanceOwner: input.remainingBalanceOwner,
disableOwner: input.disableOwner,
registrationExpiry: input.registrationExpiry,
weight: weight
})
);
$._pendingRegisterValidationMessages[validationID] = registerL1ValidatorMessage;
$._registeredValidators[input.nodeID] = validationID;
// Submit the message to the Warp precompile.
bytes32 messageID = WARP_MESSENGER.sendWarpMessage(registerL1ValidatorMessage);
$._validationPeriods[validationID].status = ValidatorStatus.PendingAdded;
$._validationPeriods[validationID].nodeID = input.nodeID;
$._validationPeriods[validationID].startingWeight = weight;
$._validationPeriods[validationID].messageNonce = 0;
$._validationPeriods[validationID].weight = weight;
$._validationPeriods[validationID].startedAt = 0; // The validation period only starts once the registration is acknowledged.
$._validationPeriods[validationID].endedAt = 0;
emit ValidationPeriodCreated(
validationID, input.nodeID, messageID, weight, input.registrationExpiry
);
return validationID;
}
/**
* @notice See {IValidatorManager-resendRegisterValidatorMessage}.
*/
function resendRegisterValidatorMessage(bytes32 validationID) external {
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
// The initial validator set must have been set already to have pending register validation messages.
if ($._pendingRegisterValidationMessages[validationID].length == 0) {
revert InvalidValidationID(validationID);
}
if ($._validationPeriods[validationID].status != ValidatorStatus.PendingAdded) {
revert InvalidValidatorStatus($._validationPeriods[validationID].status);
}
// Submit the message to the Warp precompile.
WARP_MESSENGER.sendWarpMessage($._pendingRegisterValidationMessages[validationID]);
}
/**
* @notice See {IValidatorManager-completeValidatorRegistration}.
*/
function completeValidatorRegistration(uint32 messageIndex) external {
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
(bytes32 validationID, bool validRegistration) = ValidatorMessages
.unpackL1ValidatorRegistrationMessage(_getPChainWarpMessage(messageIndex).payload);
if (!validRegistration) {
revert UnexpectedRegistrationStatus(validRegistration);
}
// The initial validator set must have been set already to have pending register validation messages.
if ($._pendingRegisterValidationMessages[validationID].length == 0) {
revert InvalidValidationID(validationID);
}
if ($._validationPeriods[validationID].status != ValidatorStatus.PendingAdded) {
revert InvalidValidatorStatus($._validationPeriods[validationID].status);
}
delete $._pendingRegisterValidationMessages[validationID];
$._validationPeriods[validationID].status = ValidatorStatus.Active;
$._validationPeriods[validationID].startedAt = uint64(block.timestamp);
emit ValidationPeriodRegistered(
validationID, $._validationPeriods[validationID].weight, block.timestamp
);
}
/**
* @notice Returns a validation ID registered to the given nodeID
* @param nodeID ID of the node associated with the validation ID
*/
function registeredValidators(bytes calldata nodeID) public view returns (bytes32) {
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
return $._registeredValidators[nodeID];
}
/**
* @notice Returns a validator registered to the given validationID
* @param validationID ID of the validation period associated with the validator
*/
function getValidator(bytes32 validationID) public view returns (Validator memory) {
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
return $._validationPeriods[validationID];
}
/**
* @notice Begins the process of ending an active validation period. The validation period must have been previously
* started by a successful call to {completeValidatorRegistration} with the given validationID.
* Any rewards for this validation period will stop accruing when this function is called.
* @param validationID The ID of the validation period being ended.
*/
function _initializeEndValidation(bytes32 validationID)
internal
virtual
returns (Validator memory)
{
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
// Ensure the validation period is active.
// The initial validator set must have been set already to have active validators.
Validator memory validator = $._validationPeriods[validationID];
if (validator.status != ValidatorStatus.Active) {
revert InvalidValidatorStatus($._validationPeriods[validationID].status);
}
// Update the validator status to pending removal.
// They are not removed from the active validators mapping until the P-Chain acknowledges the removal.
validator.status = ValidatorStatus.PendingRemoved;
// Set the end time of the validation period, since it is no longer known to be an active validator
// on the P-Chain.
validator.endedAt = uint64(block.timestamp);
// Save the validator updates.
$._validationPeriods[validationID] = validator;
(, bytes32 messageID) = _setValidatorWeight(validationID, 0);
// Emit the event to signal the start of the validator removal process.
emit ValidatorRemovalInitialized(validationID, messageID, validator.weight, block.timestamp);
return validator;
}
/**
* @notice See {IValidatorManager-resendEndValidatorMessage}.
*/
function resendEndValidatorMessage(bytes32 validationID) external {
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
Validator memory validator = $._validationPeriods[validationID];
// The initial validator set must have been set already to have pending end validation messages.
if (validator.status != ValidatorStatus.PendingRemoved) {
revert InvalidValidatorStatus($._validationPeriods[validationID].status);
}
WARP_MESSENGER.sendWarpMessage(
ValidatorMessages.packL1ValidatorWeightMessage(validationID, validator.messageNonce, 0)
);
}
/**
* @notice Completes the process of ending a validation period by receiving an acknowledgement from the P-Chain
* that the validation ID is not active and will never be active in the future.
* Note: that this function can be used for successful validation periods that have been explicitly
* ended by calling {initializeEndValidation} or for validation periods that never began on the P-Chain due to the
* {registrationExpiry} being reached.
* @return (Validation ID, Validator instance) representing the completed validation period.
*/
function _completeEndValidation(uint32 messageIndex)
internal
returns (bytes32, Validator memory)
{
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
// Get the Warp message.
(bytes32 validationID, bool validRegistration) = ValidatorMessages
.unpackL1ValidatorRegistrationMessage(_getPChainWarpMessage(messageIndex).payload);
if (validRegistration) {
revert UnexpectedRegistrationStatus(validRegistration);
}
Validator memory validator = $._validationPeriods[validationID];
// The validation status is PendingRemoved if validator removal was initiated with a call to {initiateEndValidation}.
// The validation status is PendingAdded if the validator was never registered on the P-Chain.
// The initial validator set must have been set already to have pending validation messages.
if (
validator.status != ValidatorStatus.PendingRemoved
&& validator.status != ValidatorStatus.PendingAdded
) {
revert InvalidValidatorStatus(validator.status);
}
if (validator.status == ValidatorStatus.PendingRemoved) {
validator.status = ValidatorStatus.Completed;
} else {
validator.status = ValidatorStatus.Invalidated;
}
// Remove the validator from the registered validators mapping.
delete $._registeredValidators[validator.nodeID];
// Update the validator.
$._validationPeriods[validationID] = validator;
// Emit event.
emit ValidationPeriodEnded(validationID, validator.status);
return (validationID, validator);
}
function _incrementAndGetNonce(bytes32 validationID) internal returns (uint64) {
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
return ++$._validationPeriods[validationID].messageNonce;
}
function _getPChainWarpMessage(uint32 messageIndex)
internal
view
returns (WarpMessage memory)
{
(WarpMessage memory warpMessage, bool valid) =
WARP_MESSENGER.getVerifiedWarpMessage(messageIndex);
if (!valid) {
revert InvalidWarpMessage();
}
// Must match to P-Chain blockchain id, which is 0.
if (warpMessage.sourceChainID != P_CHAIN_BLOCKCHAIN_ID) {
revert InvalidWarpSourceChainID(warpMessage.sourceChainID);
}
if (warpMessage.originSenderAddress != address(0)) {
revert InvalidWarpOriginSenderAddress(warpMessage.originSenderAddress);
}
return warpMessage;
}
function _setValidatorWeight(
bytes32 validationID,
uint64 newWeight
) internal returns (uint64, bytes32) {
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
uint64 validatorWeight = $._validationPeriods[validationID].weight;
// Check that changing the validator weight would not exceed the maximum churn rate.
_checkAndUpdateChurnTracker(newWeight, validatorWeight);
uint64 nonce = _incrementAndGetNonce(validationID);
$._validationPeriods[validationID].weight = newWeight;
// Submit the message to the Warp precompile.
bytes32 messageID = WARP_MESSENGER.sendWarpMessage(
ValidatorMessages.packL1ValidatorWeightMessage(validationID, nonce, newWeight)
);
emit ValidatorWeightUpdate({
validationID: validationID,
nonce: nonce,
weight: newWeight,
setWeightMessageID: messageID
});
return (nonce, messageID);
}
function _getChurnPeriodSeconds() internal view returns (uint64) {
return _getValidatorManagerStorage()._churnPeriodSeconds;
}
/**
* @dev Helper function to check if the stake weight to be added or removed would exceed the maximum stake churn
* rate for the past churn period. If the churn rate is exceeded, the function will revert. If the churn rate is
* not exceeded, the function will update the churn tracker with the new weight.
*/
function _checkAndUpdateChurnTracker(
uint64 newValidatorWeight,
uint64 oldValidatorWeight
) private {
ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
uint64 weightChange;
if (newValidatorWeight > oldValidatorWeight) {
weightChange = newValidatorWeight - oldValidatorWeight;
} else {
weightChange = oldValidatorWeight - newValidatorWeight;
}
uint256 currentTime = block.timestamp;
ValidatorChurnPeriod memory churnTracker = $._churnTracker;
if (
churnTracker.startedAt == 0
|| currentTime >= churnTracker.startedAt + $._churnPeriodSeconds
) {
churnTracker.churnAmount = weightChange;
churnTracker.startedAt = currentTime;
churnTracker.initialWeight = churnTracker.totalWeight;
} else {
// Churn is always additive whether the weight is being added or removed.
churnTracker.churnAmount += weightChange;
}
// Rearranged equation of maximumChurnPercentage >= currentChurnPercentage to avoid integer division truncation.
if ($._maximumChurnPercentage * churnTracker.initialWeight < churnTracker.churnAmount * 100)
{
revert MaxChurnRateExceeded(churnTracker.churnAmount);
}
// Two separate calculations because we're using uints and (newValidatorWeight - oldValidatorWeight) could underflow.
churnTracker.totalWeight += newValidatorWeight;
churnTracker.totalWeight -= oldValidatorWeight;
// Rearranged equation for totalWeight < (100 / $._maximumChurnPercentage)
// Total weight must be above this value in order to not trigger churn limits with an added/removed weight of 1.
if (churnTracker.totalWeight * $._maximumChurnPercentage < 100) {
revert InvalidTotalWeight(churnTracker.totalWeight);
}
$._churnTracker = churnTracker;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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 OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @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.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
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) {
OwnableStorage storage $ = _getOwnableStorage();
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 {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js.
pragma solidity ^0.8.20;
import {EnumerableSet} from "./EnumerableSet.sol";
/**
* @dev Library for managing an enumerable variant of Solidity's
* https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
* type.
*
* Maps have the following properties:
*
* - Entries are added, removed, and checked for existence in constant time
* (O(1)).
* - Entries are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableMap for EnumerableMap.UintToAddressMap;
*
* // Declare a set state variable
* EnumerableMap.UintToAddressMap private myMap;
* }
* ```
*
* The following map types are supported:
*
* - `uint256 -> address` (`UintToAddressMap`) since v3.0.0
* - `address -> uint256` (`AddressToUintMap`) since v4.6.0
* - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0
* - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
* - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.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 EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableMap.
* ====
*/
library EnumerableMap {
using EnumerableSet for EnumerableSet.Bytes32Set;
// To implement this library for multiple types with as little code repetition as possible, we write it in
// terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
// and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map.
// This means that we can only create new EnumerableMaps for types that fit in bytes32.
/**
* @dev Query for a nonexistent map key.
*/
error EnumerableMapNonexistentKey(bytes32 key);
struct Bytes32ToBytes32Map {
// Storage of keys
EnumerableSet.Bytes32Set _keys;
mapping(bytes32 key => bytes32) _values;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) {
map._values[key] = value;
return map._keys.add(key);
}
/**
* @dev Removes a key-value pair from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) {
delete map._values[key];
return map._keys.remove(key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) {
return map._keys.contains(key);
}
/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) {
return map._keys.length();
}
/**
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) {
bytes32 key = map._keys.at(index);
return (key, map._values[key]);
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) {
bytes32 value = map._values[key];
if (value == bytes32(0)) {
return (contains(map, key), bytes32(0));
} else {
return (true, value);
}
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) {
bytes32 value = map._values[key];
if (value == 0 && !contains(map, key)) {
revert EnumerableMapNonexistentKey(key);
}
return value;
}
/**
* @dev Return the an array containing all the keys
*
* 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 map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
return map._keys.values();
}
// UintToUintMap
struct UintToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) {
return set(map._inner, bytes32(key), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToUintMap storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. 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(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(key)));
}
/**
* @dev Return the an array containing all the keys
*
* 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 map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToUintMap storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintToAddressMap
struct UintToAddressMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
return set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToAddressMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. 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(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), address(uint160(uint256(value))));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, address(uint160(uint256(value))));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
return address(uint160(uint256(get(map._inner, bytes32(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* 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 map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressToUintMap
struct AddressToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) {
return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(AddressToUintMap storage map, address key) internal returns (bool) {
return remove(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(AddressToUintMap storage map, address key) internal view returns (bool) {
return contains(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(AddressToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. 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(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (address(uint160(uint256(key))), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(AddressToUintMap storage map, address key) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(uint256(uint160(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* 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 map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(AddressToUintMap storage map) internal view returns (address[] memory) {
bytes32[] memory store = keys(map._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// Bytes32ToUintMap
struct Bytes32ToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) {
return set(map._inner, key, bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) {
return remove(map._inner, key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) {
return contains(map._inner, key);
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(Bytes32ToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. 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(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (key, uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, key);
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) {
return uint256(get(map._inner, key));
}
/**
* @dev Return the an array containing all the keys
*
* 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 map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) {
bytes32[] memory store = keys(map._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
interface IAssetClassRegistry {
error AssetClassRegistry__InvalidAsset();
error AssetClassRegistry__AssetNotFound();
error AssetClassRegistry__AssetAlreadyRegistered();
error AssetClassRegistry__AssetClassAlreadyExists();
error AssetClassRegistry__AssetClassNotFound();
error AssetClassRegistry__AssetIsPrimaryAssetClass(uint256 assetClassId);
error AssetClassRegistry__AssetsStillExist();
error AssetClassRegistry__InvalidStakingRequirements();
event AssetClassAdded(uint256 indexed assetClassId, uint256 primaryAssetMinStake, uint256 primaryAssetMaxStake);
event AssetAdded(uint256 indexed assetClassId, address indexed asset);
event AssetRemoved(uint256 indexed assetClassId, address indexed asset);
event AssetClassRemoved(uint256 indexed assetClassId);
/**
* @notice Adds a new asset class
* @param assetClassId New asset class ID
* @param minValidatorStake Minimum validator stake
* @param maxValidatorStake Maximum validator stake
* @param initialAsset Initial asset to add to the asset class
*/
function addAssetClass(
uint256 assetClassId,
uint256 minValidatorStake,
uint256 maxValidatorStake,
address initialAsset
) external;
/**
* @notice Adds a asset to an asset class.
* @param assetClassId The ID of the asset class.
* @param asset The address of the asset to add.
*/
function addAssetToClass(uint256 assetClassId, address asset) external;
/**
* @notice Removes a asset from an asset class, except .
* @param assetClassId The ID of the asset class.
* @param asset The address of the asset to remove.
*/
function removeAssetFromClass(uint256 assetClassId, address asset) external;
/**
* @notice Removes an asset class.
* @param assetClassId The ID of the asset class.
*/
function removeAssetClass(
uint256 assetClassId
) external;
/**
* @notice Returns all the assets in a specific asset class.
* @param assetClassId The ID of the asset class.
* @return An array of asset addresses in the asset class.
*/
function getClassAssets(
uint256 assetClassId
) external view returns (address[] memory);
/**
* @notice Returns the minimum validator stake for a specific asset class.
* @param assetClassId The ID of the asset class.
* @return The minimum and maximum validator stake.
*/
function getClassStakingRequirements(
uint256 assetClassId
) external view returns (uint256, uint256);
/**
* @notice Fetches the active asset class IDs
* @return An array of asset class IDs
*/
function getAssetClassIds() external view returns (uint96[] memory);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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 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, uint224) {
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 optimised 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 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1);
return (true, ckpt._key, ckpt._value);
}
}
/**
* @dev Returns the number of checkpoint.
*/
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, uint224) {
uint256 pos = self.length;
if (pos > 0) {
// Copying to memory is important here.
Checkpoint224 memory last = _unsafeAccess(self, pos - 1);
// Checkpoint keys must be non-decreasing.
if (last._key > key) {
revert CheckpointUnorderedInsertion();
}
// Update or push new checkpoint
if (last._key == key) {
_unsafeAccess(self, pos - 1)._value = value;
} else {
self.push(Checkpoint224({_key: key, _value: value}));
}
return (last._value, value);
} else {
self.push(Checkpoint224({_key: key, _value: value}));
return (0, value);
}
}
/**
* @dev Return the index of the last (most recent) checkpoint with key lower 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 _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 is 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(0, self.slot)
result.slot := add(keccak256(0, 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, uint208) {
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 optimised 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 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1);
return (true, ckpt._key, ckpt._value);
}
}
/**
* @dev Returns the number of checkpoint.
*/
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, uint208) {
uint256 pos = self.length;
if (pos > 0) {
// Copying to memory is important here.
Checkpoint208 memory last = _unsafeAccess(self, pos - 1);
// Checkpoint keys must be non-decreasing.
if (last._key > key) {
revert CheckpointUnorderedInsertion();
}
// Update or push new checkpoint
if (last._key == key) {
_unsafeAccess(self, pos - 1)._value = value;
} else {
self.push(Checkpoint208({_key: key, _value: value}));
}
return (last._value, value);
} else {
self.push(Checkpoint208({_key: key, _value: value}));
return (0, value);
}
}
/**
* @dev Return the index of the last (most recent) checkpoint with key lower 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 _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 is 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(0, self.slot)
result.slot := add(keccak256(0, 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, uint160) {
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 optimised 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 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1);
return (true, ckpt._key, ckpt._value);
}
}
/**
* @dev Returns the number of checkpoint.
*/
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, uint160) {
uint256 pos = self.length;
if (pos > 0) {
// Copying to memory is important here.
Checkpoint160 memory last = _unsafeAccess(self, pos - 1);
// Checkpoint keys must be non-decreasing.
if (last._key > key) {
revert CheckpointUnorderedInsertion();
}
// Update or push new checkpoint
if (last._key == key) {
_unsafeAccess(self, pos - 1)._value = value;
} else {
self.push(Checkpoint160({_key: key, _value: value}));
}
return (last._value, value);
} else {
self.push(Checkpoint160({_key: key, _value: value}));
return (0, value);
}
}
/**
* @dev Return the index of the last (most recent) checkpoint with key lower 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 _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 is 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(0, self.slot)
result.slot := add(keccak256(0, 0x20), pos)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (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;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* 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[EIP 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: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO
pragma solidity ^0.8.0;
import {IRegistry} from "./IRegistry.sol";
interface IMigratablesFactory is IRegistry {
error MigratableFactory__AlreadyBlacklisted();
error MigratableFactory__AlreadyWhitelisted();
error MigratableFactory__InvalidImplementation();
error MigratableFactory__InvalidVersion();
error MigratableFactory__NotOwner();
error MigratableFactory__OldVersion();
error MigratableFactory__VersionBlacklisted();
/**
* @notice Emitted when a new implementation is whitelisted.
* @param implementation address of the new implementation
*/
event Whitelist(address indexed implementation);
/**
* @notice Emitted when a version is blacklisted (e.g., in case of invalid implementation).
* @param version version that was blacklisted
* @dev The given version is still deployable.
*/
event Blacklist(uint64 indexed version);
/**
* @notice Emitted when an entity is migrated to a new version.
* @param entity address of the entity
* @param newVersion new version of the entity
*/
event Migrate(address indexed entity, uint64 newVersion);
/**
* @notice Get the last available version.
* @return version of the last implementation
* @dev If zero, no implementations are whitelisted.
*/
function lastVersion() external view returns (uint64);
/**
* @notice Get the implementation for a given version.
* @param version version to get the implementation for
* @return address of the implementation
* @dev Reverts when an invalid version.
*/
function implementation(
uint64 version
) external view returns (address);
/**
* @notice Get if a version is blacklisted (e.g., in case of invalid implementation).
* @param version version to check
* @return whether the version is blacklisted
* @dev The given version is still deployable.
*/
function blacklisted(
uint64 version
) external view returns (bool);
/**
* @notice Whitelist a new implementation for entities.
* @param implementation address of the new implementation
*/
function whitelist(
address implementation
) external;
/**
* @notice Blacklist a version of entities.
* @param version version to blacklist
* @dev The given version will still be deployable.
*/
function blacklist(
uint64 version
) external;
/**
* @notice Create a new entity at the factory.
* @param version entity's version to use
* @param owner initial owner of the entity
* @param data initial data for the entity creation
* @param delegatorFactory address of the delegator factory
* @param slasherFactory address of the slashers factory
* @return address of the entity
* @dev CREATE2 salt is constructed from the given parameters.
*/
function create(
uint64 version,
address owner,
bytes calldata data,
address delegatorFactory,
address slasherFactory
) external returns (address);
/**
* @notice Migrate a given entity to a given newer version.
* @param entity address of the entity to migrate`
* @param newVersion new version to migrate to
* @param data some data to reinitialize the contract with
* @dev Only the entity's owner can call this function.
*/
function migrate(address entity, uint64 newVersion, bytes calldata data) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @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 ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
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.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @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.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [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 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;
}
// 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 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;
/// @solidity memory-safe-assembly
assembly {
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 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;
/// @solidity memory-safe-assembly
assembly {
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 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;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}{
"remappings": [
"@suzaku/collateral/=lib/collateral/src/",
"@suzaku/core/=lib/suzaku-core/",
"@suzaku/contracts-lib/=lib/suzaku-contracts-library/",
"@suzaku/contracts-library/=lib/suzaku-contracts-library/src/",
"@avalabs/teleporter/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/",
"@avalabs/icm-contracts/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/",
"@avalabs/[email protected]/=lib/suzaku-contracts-library/lib/icm-contracts/lib/subnet-evm/contracts/",
"@forge-std/=lib/suzaku-contracts-library/lib/icm-contracts/lib/forge-std/src/",
"@mocks/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/mocks/",
"@openzeppelin/contracts-upgradeable/=lib/collateral/lib/openzeppelin-contracts-upgradeable/contracts/",
"@openzeppelin/[email protected]/=lib/suzaku-contracts-library/lib/openzeppelin-contracts-upgradeable/contracts/",
"@openzeppelin/contracts/=lib/collateral/lib/openzeppelin-contracts/contracts/",
"@openzeppelin/[email protected]/=lib/suzaku-contracts-library/lib/openzeppelin-contracts/contracts/",
"@openzeppelin/foundry-upgrades/=lib/suzaku-core/lib/openzeppelin-foundry-upgrades/src/",
"@symbiotic/core/=lib/suzaku-core/lib/core/src/",
"@teleporter/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/teleporter/",
"@utilities/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/utilities/",
"collateral/=lib/collateral/",
"core/=lib/suzaku-core/lib/core/",
"ds-test/=lib/collateral/lib/permit2/lib/solmate/lib/ds-test/src/",
"erc4626-tests/=lib/collateral/lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
"forge-gas-snapshot/=lib/collateral/lib/permit2/lib/forge-gas-snapshot/src/",
"forge-std/=lib/forge-std/src/",
"icm-contracts/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/",
"openzeppelin-contracts-upgradeable/=lib/collateral/lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/collateral/lib/openzeppelin-contracts/",
"openzeppelin-foundry-upgrades/=lib/suzaku-core/lib/openzeppelin-foundry-upgrades/src/",
"permit2/=lib/collateral/lib/permit2/",
"solidity-stringutils/=lib/suzaku-core/lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/",
"solmate/=lib/collateral/lib/permit2/lib/solmate/",
"subnet-evm/=lib/suzaku-contracts-library/lib/icm-contracts/lib/subnet-evm/",
"suzaku-contracts-library/=lib/suzaku-contracts-library/",
"suzaku-core/=lib/suzaku-core/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": true,
"libraries": {
"lib/suzaku-contracts-library/lib/icm-contracts/contracts/validator-manager/ValidatorMessages.sol": {
"ValidatorMessages": "0xE816DeF564C09F96E3Ea72A2F16400f75e7C4830"
}
}
}Contract ABI
API[{"inputs":[],"name":"AccessControlBadConfirmation","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"name":"AccessControlUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"AlreadyClaimedForLatestEpoch","type":"error"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"AlreadyCompleted","type":"error"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"AssetClassNotFound","type":"error"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"DistributionNotComplete","type":"error"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"EnumerableMapNonexistentKey","type":"error"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"EpochNotFinished","type":"error"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"EpochStillClaimable","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"uint16","name":"fee","type":"uint16"}],"name":"FeePercentageTooHigh","type":"error"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"InvalidAdmin","type":"error"},{"inputs":[{"internalType":"uint16","name":"fee","type":"uint16"}],"name":"InvalidFee","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[{"internalType":"address","name":"l1Middleware","type":"address"}],"name":"InvalidL1Middleware","type":"error"},{"inputs":[{"internalType":"uint256","name":"minUptime","type":"uint256"}],"name":"InvalidMinUptime","type":"error"},{"inputs":[{"internalType":"uint48","name":"numberOfEpochs","type":"uint48"}],"name":"InvalidNumberOfEpochs","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"InvalidOperator","type":"error"},{"inputs":[{"internalType":"address","name":"protocolOwner","type":"address"}],"name":"InvalidProtocolOwner","type":"error"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"}],"name":"InvalidRecipient","type":"error"},{"inputs":[{"internalType":"uint256","name":"rewardsAmount","type":"uint256"}],"name":"InvalidRewardsAmount","type":"error"},{"inputs":[{"internalType":"address","name":"rewardsToken","type":"address"}],"name":"InvalidRewardsToken","type":"error"},{"inputs":[{"internalType":"uint16","name":"share","type":"uint16"}],"name":"InvalidShare","type":"error"},{"inputs":[{"internalType":"address","name":"uptimeTracker","type":"address"}],"name":"InvalidUptimeTracker","type":"error"},{"inputs":[],"name":"MathOverflowedMulDiv","type":"error"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"NoRewardsToClaim","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"OperatorUptimeNotSet","type":"error"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"RewardsAlreadyDistributed","type":"error"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"uint48","name":"requiredEpoch","type":"uint48"}],"name":"RewardsDistributionTooEarly","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"uint16","name":"totalFees","type":"uint16"}],"name":"TotalFeesExceed100","type":"error"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"ZeroVaultStake","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminRoleAssigned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rewardsToken","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CuratorFeeClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"newFee","type":"uint16"}],"name":"CuratorFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rewardsToken","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"OperatorFeeClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"newFee","type":"uint16"}],"name":"OperatorFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rewardsToken","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ProtocolFeeClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"newFee","type":"uint16"}],"name":"ProtocolFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newProtocolOwner","type":"address"}],"name":"ProtocolOwnerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint48","name":"startEpoch","type":"uint48"},{"indexed":false,"internalType":"uint256","name":"numberOfEpochs","type":"uint256"},{"indexed":true,"internalType":"address","name":"rewardsToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"rewardsAmount","type":"uint256"}],"name":"RewardsAmountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rewardsToken","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RewardsClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"RewardsDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rewardsDistributor","type":"address"}],"name":"RewardsDistributorRoleAssigned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rewardsManager","type":"address"}],"name":"RewardsManagerRoleAssigned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint96","name":"assetClassId","type":"uint96"},{"indexed":false,"internalType":"uint16","name":"rewardsPercentage","type":"uint16"}],"name":"RewardsShareUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint48","name":"epoch","type":"uint48"},{"indexed":true,"internalType":"address","name":"rewardsToken","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"UndistributedRewardsClaimed","type":"event"},{"inputs":[],"name":"BASIS_POINTS_DENOMINATOR","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PROTOCOL_OWNER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARDS_DISTRIBUTOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARDS_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rewardsToken","type":"address"},{"internalType":"address","name":"recipient","type":"address"}],"name":"claimCuratorFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rewardsToken","type":"address"},{"internalType":"address","name":"recipient","type":"address"}],"name":"claimOperatorFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rewardsToken","type":"address"},{"internalType":"address","name":"recipient","type":"address"}],"name":"claimProtocolFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rewardsToken","type":"address"},{"internalType":"address","name":"recipient","type":"address"}],"name":"claimRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"address","name":"rewardsToken","type":"address"},{"internalType":"address","name":"recipient","type":"address"}],"name":"claimUndistributedRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"curatorFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"address","name":"curator","type":"address"}],"name":"curatorShares","outputs":[{"internalType":"uint256","name":"share","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"uint48","name":"batchSize","type":"uint48"}],"name":"distributeRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"distributionBatches","outputs":[{"internalType":"uint256","name":"lastProcessedOperator","type":"uint256"},{"internalType":"bool","name":"isComplete","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"epochDuration","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"address","name":"token","type":"address"}],"name":"getRewardsAmountPerTokenFromEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"getRewardsAmountPerTokenFromEpoch","outputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin_","type":"address"},{"internalType":"address","name":"protocolOwner_","type":"address"},{"internalType":"address payable","name":"l1Middleware_","type":"address"},{"internalType":"address","name":"uptimeTracker_","type":"address"},{"internalType":"uint16","name":"protocolFee_","type":"uint16"},{"internalType":"uint16","name":"operatorFee_","type":"uint16"},{"internalType":"uint16","name":"curatorFee_","type":"uint16"},{"internalType":"uint256","name":"minRequiredUptime_","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l1Middleware","outputs":[{"internalType":"contract AvalancheL1Middleware","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"curator","type":"address"}],"name":"lastEpochClaimedCurator","outputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"lastEpochClaimedOperator","outputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"protocolOwner","type":"address"}],"name":"lastEpochClaimedProtocol","outputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"lastEpochClaimedStaker","outputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"middlewareVaultManager","outputs":[{"internalType":"contract MiddlewareVaultManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minRequiredUptime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"address","name":"operator","type":"address"}],"name":"operatorBeneficiariesShares","outputs":[{"internalType":"uint256","name":"share","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"operatorFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"address","name":"operator","type":"address"}],"name":"operatorShares","outputs":[{"internalType":"uint256","name":"share","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rewardsToken","type":"address"}],"name":"protocolRewards","outputs":[{"internalType":"uint256","name":"rewardsAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"assetClass","type":"uint96"}],"name":"rewardsSharePerAssetClass","outputs":[{"internalType":"uint16","name":"rewardsShare","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMinUptime","type":"uint256"}],"name":"setMinRequiredUptime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newProtocolOwner","type":"address"}],"name":"setProtocolOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint48","name":"startEpoch","type":"uint48"},{"internalType":"uint48","name":"numberOfEpochs","type":"uint48"},{"internalType":"address","name":"rewardsToken","type":"address"},{"internalType":"uint256","name":"rewardsAmount","type":"uint256"}],"name":"setRewardsAmountForEpochs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newRewardsDistributor","type":"address"}],"name":"setRewardsDistributorRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newRewardsManager","type":"address"}],"name":"setRewardsManagerRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"assetClass","type":"uint96"},{"internalType":"uint16","name":"share","type":"uint16"}],"name":"setRewardsShareForAssetClass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"newFee","type":"uint16"}],"name":"updateCuratorFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"newFee","type":"uint16"}],"name":"updateOperatorFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"newFee","type":"uint16"}],"name":"updateProtocolFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"uptimeTracker","outputs":[{"internalType":"contract UptimeTracker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"address","name":"vault","type":"address"}],"name":"vaultShares","outputs":[{"internalType":"uint256","name":"share","type":"uint256"}],"stateMutability":"view","type":"function"}]Contract Creation Code
60808060405234601557613856908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80630154d0741461231657806301ffc9a7146122c0578063020624d2146122425780630a2f6357146121f057806312117e7714611e8a578063137fd2db14611e2857806322224b3714611cd5578063248a9ca314611c9c578063250b346a14611bbf5780632f2ff15d14611b76578063314ab46c14611b30578063359c055d146119bf57806336568abe146119785780633c535c5f146119385780634ff0876a1461191057806354150263146118f3578063564ca4641461186557806366388d031461182057806367534add146117f85780636dca32d2146117bc578063733f44ae146115d15780637ffc46ab146115a6578063811c97211461156657806381c5bfa41461151957806389afc0f1146114f657806391d14854146114a1578063a217fddf14611487578063a610652814611425578063b0e21e8a14611405578063bbea04d914611365578063bcd7927f14610fe4578063bd306d2514610f92578063c34c228914610f58578063ce3cc80714610edd578063ce525a7514610ebb578063cfa498a314610e9f578063d547741f14610e54578063dbfa483c14610c56578063dca3daae14610c04578063deba7c5114610bb2578063e0dd245714610b78578063e45a01c414610b38578063e5cd63b114610afe578063ed0a750c14610ad6578063f1e42ccd14610491578063f2e7b1ed146102a5578063f489cf68146102655763f626270b14610229575f80fd5b34610261576020366003190112610261576001600160a01b0361024a6123de565b165f526009602052602060405f2054604051908152f35b5f80fd5b34610261576020366003190112610261576001600160a01b036102866123de565b165f52600d602052602065ffffffffffff60405f205416604051908152f35b34610261576040366003190112610261576102be6123de565b6102c66123c8565b906001600160a01b0380831680156104795750805f5460301c1690604051938492635cbeecf160e11b845283600460209788935afa92831561046e575f9361043f575b50335f52600e855265ffffffffffff938460405f20541691858516938415158061042b575b610405578661033d5f956124bf565b945b818616878110156103ac57805f5260068b5260405f20335f528b5260405f20549081156103a1575f52600a8b526103798660405f20612a40565b80156103a1579161038f61039592600194612aa9565b906124d6565b955b011693879061033f565b505094600190610397565b509296945050935080156103ed57600e946103d0946103cb9316612c2d565b612490565b92335f525260405f20911665ffffffffffff198254161790555f80f35b60405163371bbb2960e11b8152336004820152602490fd5b604051637ebe62ef60e01b815233600482015265ffffffffffff85166024820152604490fd5b508661043687612490565b1684101561032e565b610460919350853d8711610467575b610458818361243c565b810190612472565b9185610309565b503d61044e565b6040513d5f823e3d90fd5b60249060405190630bc2c5df60e11b82526004820152fd5b34610261576040366003190112610261576104aa6123de565b6104b26123c8565b6001600160a01b03811680156104795750335f52600b60205265ffffffffffff60405f20541660018060a01b035f5460301c16604051635cbeecf160e11b8152602081600481855afa90811561046e575f91610ab7575b5065ffffffffffff8116151580610a9d575b610a77579091849261052d5f926124bf565b9360018060a01b0360015416925b65ffffffffffff851665ffffffffffff87161015610a30576040516332a90e1b60e01b815265ffffffffffff87166004820152935f85602481845afa94851561046e575f95610a0c575b506040516311369f0f60e01b815265ffffffffffff8816600482015297602089602481885afa98891561046e575f996109eb575b505f965f5b87518110156106745761060e60208c6001600160a01b036105df858d6124fb565b5116604051916105ee83612420565b5f83526040518095819482936377daaceb60e11b845233600485016125e4565b03915afa90811561046e575f91610642575b5061062e575b6001016105be565b9761063a6001916125a5565b989050610626565b90506020813d60201161066c575b8161065d6020938361243c565b8101031261026157518c610620565b3d9150610650565b50909596949893979291610687866124e3565b95610695604051978861243c565b8087526106a4601f19916124e3565b013660208801375f965f5b89518110156107865761070060206001600160a01b036106cf848e6124fb565b5116604051906106de82612420565b5f82526040516377daaceb60e11b8152938492839182918e33600485016125e4565b03915afa90811561046e575f91610754575b50610720575b6001016106af565b9760019061074c906001600160a01b0361073a8c8e6124fb565b5116610746828c6124fb565b526125a5565b989050610718565b90506020813d60201161077e575b8161076f6020938361243c565b8101031261026157518d610712565b3d9150610762565b509650965090925095909295604051976311369f0f60e01b895265ffffffffffff851660048a0152602089602481845afa98891561046e575f996109ca575b5065ffffffffffff85165f52600a6020526107e38360405f20612a40565b9687156109b0575f945b895186101561098a576001600160a01b03610808878c6124fb565b511665ffffffffffff88165f52600760205260405f20815f5260205260405f20548c811561097e576020610862916040519061084382612420565b5f8252604051630b5cf1a760e21b8152938492839233600485016125e4565b0381865afa92831561046e578e915f94610947575b508315610906576108c993929160209165ffffffffffff6040519261089b84612420565b5f845260405197889485938493630a1e440d60e31b85521660048401526040602484015260448301906125c0565b03915afa92831561046e575f93610913575b508b83156109065792600194926108f861038f936108fd96612aa9565b612bba565b955b01946107ed565b50505050946001906108ff565b9092506020813d60201161093f575b8161092f6020938361243c565b810103126102615751918e6108db565b3d9150610922565b915092506020813d602011610976575b816109646020938361243c565b81010312610261578d9051928f610877565b3d9150610957565b505050946001906108ff565b9750949095935065ffffffffffff92985060019197505b0116949395909592919261053b565b94909593965065ffffffffffff92985060019197506109a1565b6109e491995060203d60201161046757610458818361243c565b97896107c5565b610a0591995060203d60201161046757610458818361243c565b97896105b9565b610a299195503d805f833e610a21818361243c565b810190612523565b9388610585565b8691859180156103ed57610a50936103cb926001600160a01b0316612c2d565b335f52600b60205265ffffffffffff60405f20911665ffffffffffff198254161790555f80f35b604051637ebe62ef60e01b815233600482015265ffffffffffff84166024820152604490fd5b5065ffffffffffff610aae82612490565b1683101561051b565b610ad0915060203d60201161046757610458818361243c565b85610509565b34610261575f366003190112610261576002546040516001600160a01b039091168152602090f35b34610261575f3660031901126102615760206040517f395a0294cd085f8232b41d6875810a7f41d359f4e1281ec5b60138a9a1e62dff8152f35b34610261576020366003190112610261576001600160a01b03610b596123de565b165f52600b602052602065ffffffffffff60405f205416604051908152f35b34610261575f3660031901126102615760206040517f51785d261c3b3e8c2cfe2d0e108b9ab2b1fb703ff9e9e49aba4ebdc85c36152b8152f35b3461026157604036600319011261026157610bcb61239e565b65ffffffffffff610bda6123c8565b91165f52600760205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b3461026157604036600319011261026157610c1d61239e565b65ffffffffffff610c2c6123c8565b91165f52600860205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b3461026157608036600319011261026157610c6f61239e565b610c776123b3565b90610c806123f4565b60643590610c8c6126c4565b6001600160a01b0316908115610e3b578015610e235765ffffffffffff8094168015610e0b578082028281048203610df757604051906020936323b872dd60e01b60208401526024923360248201523060448201528260648201526064815260a081019281841067ffffffffffffffff851117610de357610d17610d4a92610d50956040528961366e565b610d2861ffff91825f541690612aa9565b885f526009602052610d3f60405f209182546124d6565b90555f541682612aa9565b906125b3565b91865f9616955b87811682811015610daf578701888111610d9c57889182600192165f52600a8752610d948860405f20815f52600281018a528860405f2055613727565b500116610d57565b83634e487b7160e01b5f5260116004525ffd5b86887f2901fd37b49145e0b4299d55442c5a6d105a73af06d898dfe79e30cf6f0c820f6040868982519182526020820152a3005b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b6024906040519063cee9681b60e01b82526004820152fd5b6024906040519063af8a36b160e01b82526004820152fd5b604051632484d91d60e01b815260048101839052602490fd5b3461026157604036600319011261026157610e9d600435610e736123c8565b90805f525f80516020613801833981519152602052610e98600160405f20015461271f565b612c7f565b005b34610261575f3660031901126102615760206040516127108152f35b34610261575f3660031901126102615760205f5461ffff60405191831c168152f35b3461026157602036600319011261026157610ef66123de565b610efe61266e565b6001600160a01b038116908115610f3f57610f18906127d7565b507fa57d72cef3e09f15eee5e9df8c6d1fcbd9dbb9eaba5a06c41eab256452bb99e45f80a2005b604051630bc2c5df60e11b815260048101839052602490fd5b34610261575f3660031901126102615760206040517f9df62d436bfc9f3be4953ab398f3aa862316b013d490e2138c80b4b2eadeabd78152f35b3461026157604036600319011261026157610fab61239e565b65ffffffffffff610fba6123c8565b91165f52600660205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b3461026157606036600319011261026157610ffd61239e565b6110056123c8565b61100d6123f4565b6110156126c4565b6001600160a01b0381811693841561134c5765ffffffffffff80911692835f526020916004835260019060ff8260405f200154161561133357845f5460301c169060405190635cbeecf160e11b82528582600481865afa91821561046e575f92611314575b5060028801818111610df75781169116106112fb57855f52600a84526110a38760405f20612a40565b9182156103ed5760045f809360405192838092636c88e31960e11b82525afa801561046e5782915f916112e1575b505f925b6112ae575b50602491505f87825416604051938480926332a90e1b60e01b82528c60048301525afa91821561046e575f92611292575b50905f825b611246575b50815f925b611194575b50505090610d4a6111309282612aa9565b9081156103ed578161118b916111847f10ab8c046ad20604ac9082fa423d13dcd1709565393e77c1478317b5a7489e7e96885f52600a875260405f2099168099815f526002810188525f6040812055613727565b5087612c2d565b604051908152a4005b90919282518410156112405760049087896111af87876124fb565b511660405193848092638da5cb5b60e01b82525afa801561046e5783925f91611200575b5082916111f7918c5f5260088b528b60405f2091165f528a5260405f2054906124d6565b9401929161111a565b809350898092503d8311611239575b611219818361243c565b81010312610261576111f7839291611231849361245e565b9150916111d3565b503d61120f565b9261111f565b9281929151841015611289578161127f81928b5f5260078a5260405f208b61126e89896124fb565b51165f528a5260405f2054906124d6565b9401909291611110565b92919091611115565b6112a79192503d805f833e610a21818361243c565b908a61110b565b80518310156112dc57816112d48194958b5f5260068a5260405f208b61126e89876124fb565b9401926110d5565b6110da565b6112f591503d805f833e610a21818361243c565b8b6110d1565b6040516302a3d25d60e41b815260048101879052602490fd5b61132c919250863d881161046757610458818361243c565b908a61107a565b6040516358bf5bfb60e11b815260048101879052602490fd5b604051630bc2c5df60e11b815260048101869052602490fd5b346102615760403660031901126102615761137e61240a565b6024359061ffff821680920361026157611396612613565b61271082116113ec5760206001600160601b037fe3e459a3704c1dd004ff9be28a459efb27b73e3570c1b4b13aeba94e5310e6b0921692835f52600f825260405f208161ffff19825416179055604051908152a2005b6040516313573bbd60e31b815260048101839052602490fd5b34610261575f36600319011261026157602061ffff5f5416604051908152f35b346102615760203660031901126102615761143e6123de565b611446612613565b6001600160a01b038116908115610f3f5761146090612884565b507f501fd608c18945fd096d9274a50e1c9048ac305255bd9de1f712f8ecfcd8e9b15f80a2005b34610261575f3660031901126102615760206040515f8152f35b34610261576040366003190112610261576114ba6123c8565b6004355f525f8051602061380183398151915260205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b34610261575f36600319011261026157602061ffff5f5460101c16604051908152f35b3461026157602036600319011261026157600435611535612613565b65ffffffffffff60025460a01c16811161154e57600355005b602490604051906379b5b5bb60e11b82526004820152fd5b34610261576020366003190112610261576001600160a01b036115876123de565b165f52600c602052602065ffffffffffff60405f205416604051908152f35b34610261575f366003190112610261575f5460405160309190911c6001600160a01b03168152602090f35b34610261576040366003190112610261576115ea61239e565b6115f26123b3565b906115fb6126c4565b65ffffffffffff9081811691825f52600460205260405f209160018060a01b0390815f5460301c169160405196635cbeecf160e11b8852602088600481875afa97881561046e575f9861179b575b50600193600187019760ff89541661178357866116658b6124a7565b1681101561175b5750604051636c88e31960e11b81529798505f90889060049082905afa96871561046e575f97611739575b508554945f93929190855b6116c6575b5050505050505054905111156116b957005b805460ff19166001179055005b885187108061172e575b15611729576116eb836116e3898c6124fb565b511685612d12565b611701836116f9898c6124fb565b51168561309b565b85870194858811610df75761171c6117229188978b556125a5565b976125a5565b96946116a2565b6116a7565b5080821685106116d0565b849392919750611752903d805f833e610a21818361243c565b96909192611697565b604490876117688c6124a7565b6040519263548cd72d60e11b84526004840152166024820152fd5b60249060405190632d80689560e21b82526004820152fd5b6117b591985060203d60201161046757610458818361243c565b9688611649565b34610261576020366003190112610261576001600160601b036117dd61240a565b165f52600f602052602061ffff60405f205416604051908152f35b34610261575f366003190112610261576001546040516001600160a01b039091168152602090f35b346102615760203660031901126102615765ffffffffffff61184061239e565b165f5260046020526040805f2060ff6001825492015416825191825215156020820152f35b346102615760203660031901126102615761187e61238d565b611886612613565b61ffff81169061271082116118da577f08820d76e20733f8e3d65d71140d92733224f51e0812416d3623c72c6c365c179160209163ffff00005f549160101b169063ffff00001916175f55604051908152a1005b60405163f6f4a38f60e01b815260048101839052602490fd5b34610261575f366003190112610261576020600354604051908152f35b34610261575f36600319011261026157602065ffffffffffff60025460a01c16604051908152f35b34610261576020366003190112610261576001600160a01b036119596123de565b165f52600e602052602065ffffffffffff60405f205416604051908152f35b34610261576040366003190112610261576119916123c8565b336001600160a01b038216036119ad57610e9d90600435612c7f565b60405163334bd91960e11b8152600490fd5b34610261576020806003193601126102615765ffffffffffff6119e061239e565b16805f52600a90600a835260405f2091604051938485828654928381520180965f52835f20925f5b85828210611b1a57505050611a1f9250038661243c565b845192611a44611a2e856124e3565b94611a3c604051968761243c565b8086526124e3565b84830190601f19013682375f5b8751811015611a98575f8381528585526040902060019190611a87906001600160a01b03611a7f848d6124fb565b511690612a40565b611a9182896124fb565b5201611a51565b858883868a604051948594604086019060408752518091526060860192905f5b818110611afa57505050848203858401525180825290820192915f5b828110611ae357505050500390f35b835185528695509381019392810192600101611ad4565b82516001600160a01b031685528897509385019391850191600101611ab8565b855484526001958601958b955093019201611a08565b34610261576040366003190112610261576020611b6e611b4e61239e565b65ffffffffffff611b5d6123c8565b91165f52600a835260405f20612a40565b604051908152f35b3461026157604036600319011261026157610e9d600435611b956123c8565b90805f525f80516020613801833981519152602052611bba600160405f20015461271f565b6129d0565b3461026157604036600319011261026157611bd86123de565b611be06123c8565b335f9081527f53f86594caac808e93186bdae378eecbec7ef80c6b741e6f848242ce6bbe0ca760205260409020549091907f395a0294cd085f8232b41d6875810a7f41d359f4e1281ec5b60138a9a1e62dff9060ff1615611c7e57506001600160a01b0390828216801561047957501690815f52600960205260405f205480156103ed57611c6e9183612c2d565b5f90815260096020526040812055005b6044906040519063e2517d3f60e01b82523360048301526024820152fd5b34610261576020366003190112610261576004355f525f805160206138018339815191526020526020600160405f200154604051908152f35b3461026157604036600319011261026157611cee6123de565b611cf66123c8565b906001600160a01b0380831680156104795750805f5460301c1690604051938492635cbeecf160e11b845283600460209788935afa92831561046e575f93611e09575b50335f52600c855265ffffffffffff938460405f205416918585169384151580611df5575b6104055786611d6d5f956124bf565b945b81861687811015611dd657805f5260088b5260405f20335f528b5260405f2054908115611dcb575f52600a8b52611da98660405f20612a40565b8015611dcb579161038f611dbf92600194612aa9565b955b0116938790611d6f565b505094600190611dc1565b509296945050935080156103ed57600c946103d0946103cb9316612c2d565b5086611e0087612490565b16841015611d5e565b611e21919350853d871161046757610458818361243c565b9185611d39565b3461026157602036600319011261026157611e416123de565b611e4961266e565b6001600160a01b038116908115610f3f57611e639061292a565b507f4b702e0c3f02069aa8f4b56a159c7b04aa778d066308e17c81955107c11fc7815f80a2005b346102615761010036600319011261026157611ea46123de565b611eac6123c8565b6001600160a01b03906044358281169182820361026157606435938085168095036102615760843561ffff958682168092036102615760a4359387851685036102615760c4359788168803610261577ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a009889549760ff8960401c16159867ffffffffffffffff8116801590816121e8575b60011490816121de575b1590816121d5575b506121c35767ffffffffffffffff1981166001178c55896121a4575b50801561218c5750821561217357848116801561215b57508482168015612143575090611fae82611f9e611fb49461274d565b50611fa8816127d7565b50612884565b5061292a565b505f54946601000000000000600160d01b039060301b169083826601000000000000600160d01b0319881617805f5560301c16936040519863b9bbb6f560e01b8a526020998a816004818a5afa92831561046e578b915f94612107575b50966004976001600160601b0360a01b94168460015416176001558460025494851617600255604051978880926329c2e7c360e21b82525afa91821561046e5763ffff0000965f936120e2575b5065ffff00000000929365ffffffffffff60d01b809565ffffffffffff60a01b9060a01b1692161717600255891b16951617179160101b1617175f5560e4356003556120a657005b7fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29168ff000000000000000019815416905560405160018152a1005b65ffff000000009350612101908c8d3d1061046757610458818361243c565b9261205e565b8281939295503d831161213c575b61211f818361243c565b81010312610261576004966121348c9261245e565b939097612011565b503d612115565b60249060405190637d3fd69f60e11b82526004820152fd5b6024906040519063c36ee61160e01b82526004820152fd5b604051631283e4c360e01b815260048101849052602490fd5b60249060405190631afcfa3760e31b82526004820152fd5b68ffffffffffffffffff191668010000000000000001178b558b611f6b565b60405163f92ee8a960e01b8152600490fd5b9050158d611f4f565b303b159150611f47565b8b9150611f3d565b346102615760403660031901126102615761220961239e565b65ffffffffffff6122186123c8565b91165f52600560205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b346102615760203660031901126102615761ffff61225e61238d565b612266612613565b1661271081116122a8576020817f15189fb9f7333d3c2ac875339ddb50cd49f554b5ed014b4b06111a437ec0b0c59261ffff195f5416175f55604051908152a1005b6024906040519063f6f4a38f60e01b82526004820152fd5b346102615760203660031901126102615760043563ffffffff60e01b811680910361026157602090637965db0b60e01b8114908115612305575b506040519015158152f35b6301ffc9a760e01b149050826122fa565b346102615760203660031901126102615761232f61238d565b612337612613565b61ffff81169061271082116118da575f805465ffff000000001916602092831b65ffff00000000161790556040519182527f2a7454b7f7c7420b1c343eeee16c5398b93c19f3398f92b247838873a57e378791a1005b6004359061ffff8216820361026157565b6004359065ffffffffffff8216820361026157565b6024359065ffffffffffff8216820361026157565b602435906001600160a01b038216820361026157565b600435906001600160a01b038216820361026157565b604435906001600160a01b038216820361026157565b600435906001600160601b038216820361026157565b6020810190811067ffffffffffffffff821117610de357604052565b90601f8019910116810190811067ffffffffffffffff821117610de357604052565b51906001600160a01b038216820361026157565b90816020910312610261575165ffffffffffff811681036102615790565b65ffffffffffff9081165f190191908211610df757565b65ffffffffffff9081166001190191908211610df757565b90600165ffffffffffff80931601918211610df757565b91908201809211610df757565b67ffffffffffffffff8111610de35760051b60200190565b805182101561250f5760209160051b010190565b634e487b7160e01b5f52603260045260245ffd5b60209081818403126102615780519067ffffffffffffffff821161026157019180601f84011215610261578251612559816124e3565b93612567604051958661243c565b818552838086019260051b820101928311610261578301905b82821061258e575050505090565b83809161259a8461245e565b815201910190612580565b5f198114610df75760010190565b91908203918211610df757565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b65ffffffffffff612610949360609360018060a01b0316835216602082015281604082015201906125c0565b90565b335f9081527fa8c56b0bde8ed88154b58109a7ae1167ae4ef886b0b03b8b38372be426b6ea2960205260409020547f51785d261c3b3e8c2cfe2d0e108b9ab2b1fb703ff9e9e49aba4ebdc85c36152b9060ff1615611c7e5750565b335f9081527fb7db2dd08fcb62d0c9e08c51941cae53c267786a0b75803fb7960902fc8ef97d602052604090205460ff16156126a657565b60405163e2517d3f60e01b81523360048201525f6024820152604490fd5b335f9081527f0892b1202b1511bac89b1a3007f83860fbc9e7e5a01f85bf69e046c113a67ea160205260409020547f9df62d436bfc9f3be4953ab398f3aa862316b013d490e2138c80b4b2eadeabd79060ff1615611c7e5750565b805f525f8051602061380183398151915260205260405f20335f5260205260ff60405f20541615611c7e5750565b6001600160a01b03165f8181527fb7db2dd08fcb62d0c9e08c51941cae53c267786a0b75803fb7960902fc8ef97d60205260409020545f805160206138018339815191529060ff166127d1575f805260205260405f20815f5260205260405f20600160ff1982541617905533905f5f805160206137e18339815191528180a4600190565b50505f90565b6001600160a01b03165f8181527fa8c56b0bde8ed88154b58109a7ae1167ae4ef886b0b03b8b38372be426b6ea2960205260409020547f51785d261c3b3e8c2cfe2d0e108b9ab2b1fb703ff9e9e49aba4ebdc85c36152b91905f805160206138018339815191529060ff1661287d57825f5260205260405f20815f5260205260405f20600160ff1982541617905533915f805160206137e18339815191525f80a4600190565b5050505f90565b6001600160a01b03165f8181527f0892b1202b1511bac89b1a3007f83860fbc9e7e5a01f85bf69e046c113a67ea160205260409020547f9df62d436bfc9f3be4953ab398f3aa862316b013d490e2138c80b4b2eadeabd791905f805160206138018339815191529060ff1661287d57825f5260205260405f20815f5260205260405f20600160ff1982541617905533915f805160206137e18339815191525f80a4600190565b6001600160a01b03165f8181527f53f86594caac808e93186bdae378eecbec7ef80c6b741e6f848242ce6bbe0ca760205260409020547f395a0294cd085f8232b41d6875810a7f41d359f4e1281ec5b60138a9a1e62dff91905f805160206138018339815191529060ff1661287d57825f5260205260405f20815f5260205260405f20600160ff1982541617905533915f805160206137e18339815191525f80a4600190565b90815f525f805160206138018339815191528060205260405f209160018060a01b031691825f5260205260ff60405f205416155f1461287d57825f5260205260405f20815f5260205260405f20600160ff1982541617905533915f805160206137e18339815191525f80a4600190565b6001600160a01b039091165f81815260028301602052604090205491821590829082612a89575b5050612a71575090565b6024906040519063015ab34360e11b82526004820152fd5b612aa192506001915f520160205260405f2054151590565b15815f612a67565b90808202905f1981840990828083109203918083039214612b18576127109082821115612b06577fbc01a36e2eb1c432ca57a786c226809d495182a9930be0ded288ce703afb7e91940990828211900360fc1b910360041c170290565b60405163227bc15360e01b8152600490fd5b505061271091500490565b61271091818302915f1984820993838086109503948086039514612b985784831115612b0657829109815f038216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b505080925015612ba6570490565b634e487b7160e01b5f52601260045260245ffd5b9091828202915f1984820993838086109503948086039514612b985784831115612b0657829109815f038216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b60405163a9059cbb60e01b60208201526001600160a01b039092166024830152604480830193909352918152608081019167ffffffffffffffff831182841017610de357612c7d9260405261366e565b565b90815f525f805160206138018339815191528060205260405f209160018060a01b031691825f5260205260ff60405f2054165f1461287d57825f5260205260405f20815f5260205260405f2060ff19815416905533917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b5f80a4600190565b51906001600160601b038216820361026157565b91909160018060a01b038060025416926040516306b4f20360e41b815260208160448165ffffffffffff988989166004830152878b1660248301525afa90811561046e575f91613069575b50600354811061302d57612d79908560025460a01c1690612b23565b935f935f54936040519363b2834db760e01b85525f85600481858a60301c165afa94851561046e575f95612f92575b505f965b8551881015612f2d57612e1360206001600160601b03612dcc8b8a6124fb565b51604051634b785e6960e11b815265ffffffffffff891660048201526001600160a01b038f16602482015291166001600160601b0316604482015291829081906064820190565b0381878c60301c165afa90811561046e575f91612efb575b506001600160601b03612e3e8a896124fb565b51604051630e4e7bd560e01b815287891660048201529116602482015260208160448160308d901c89165afa90811561046e57888b915f93612ec1575b5092612eb4600195936001600160601b03612e9c61038f95612eb9986124fb565b51165f52600f60205261ffff60405f20541692612b23565b612aa9565b970196612dac565b94925050506020833d602011612ef3575b81612edf6020938361243c565b810103126102615791518988612eb4612e7b565b3d9150612ed2565b90506020813d602011612f25575b81612f166020938361243c565b8101031261026157515f612e2b565b3d9150612f09565b61ffff91999750612f77949550612f5592969398612f4a91612aa9565b9260101c1682612aa9565b908387165f52600660205260405f208587165f526020528160405f20556125b3565b93165f52600560205260405f2091165f5260205260405f2055565b9094503d805f833e612fa4818361243c565b8101906020818303126102615780519067ffffffffffffffff821161026157019080601f83011215610261578151612fdb816124e3565b92612fe9604051948561243c565b81845260208085019260051b82010192831161026157602001905b82821061301557505050935f612da8565b6020809161302284612cfe565b815201910190613004565b50939290918181165f52600560205260405f208385165f526020525f6040812055165f52600660205260405f2091165f526020525f6040812055565b90506020813d602011613093575b816130846020938361243c565b8101031261026157515f612d5d565b3d9150613077565b65ffffffffffff81165f52600560205260405f2060018060a01b0383165f5260205260405f20548015613669576001546040516332a90e1b60e01b815265ffffffffffff8416600482015293905f90859060249082906001600160a01b03165afa93841561046e575f94613648575b505f546040516311369f0f60e01b815265ffffffffffff851660048201529291906020908490602490829060301c6001600160a01b03165afa92831561046e575f93613627575b505f5b855181101561361f576024906001600160a01b0361317282896124fb565b5116602060018060a01b036001541660405194858092639374a51d60e01b82528560048301525afa92831561046e575f936135e3575b50604051630ce9b79360e41b815292602084600481855afa93841561046e575f946135a7575b505f5460405163289c0bd760e21b815294909190602086600481603087901c6001600160a01b03165afa95861561046e57899688915f9161355b575b509165ffffffffffff979160209361328060405161322781612420565b5f8152604051633dc5038960e01b81526001600160a01b0393841660048201526001600160601b03891660248201529290931660448301529390991660648a015260a060848a01528892839182919060a48301906125c0565b03916001600160a01b03165afa94851561046e575f95613527575b50846132af575b5050506001915001613154565b604051634b785e6960e11b815265ffffffffffff8a1660048201526001600160a01b03871660248201526001600160601b038216604482015260208180606481010381603087901c6001600160a01b03165afa95861561046e5788915f976134ee575b5090612eb46133248861334494612b23565b6001600160601b0385165f52600f60205261ffff60405f20541690612aa9565b946001600160601b03604051926314b1bc0360e21b845260018060a01b038916600485015265ffffffffffff8c16602485015216604483015260208260648160018060a01b038760301c165afa91821561046e575f926134ba575b5081613498575b505061ffff6133b99160201c1684612aa9565b65ffffffffffff88165f52600860205260405f2093604051638da5cb5b60e01b8152602081600481875afa90811561046e575f91613452575b509060019561341e9392878060a01b03165f5260205260405f206134178382546124d6565b90556125b3565b9065ffffffffffff88165f52600760205260405f20905f5260205261344860405f209182546124d6565b90555f80806132a2565b929190506020833d602011613490575b8161346f6020938361243c565b810103126102615760019561348661341e9461245e565b91929350956133f2565b3d9150613462565b6133b992956134ad61ffff936134b393612b23565b90612aa9565b94916133a6565b9091506020813d6020116134e6575b816134d66020938361243c565b810103126102615751905f61339f565b3d91506134c9565b915095506020813d60201161351f575b8161350b6020938361243c565b810103126102615751948790612eb4613312565b3d91506134fe565b9094506020813d602011613553575b816135436020938361243c565b810103126102615751935f61329b565b3d9150613536565b975050506020863d60201161359f575b816135786020938361243c565b8101031261026157886020918861359565ffffffffffff9961245e565b919350919761320a565b3d915061356b565b9093506020813d6020116135db575b816135c36020938361243c565b81010312610261576135d49061245e565b925f6131ce565b3d91506135b6565b9092506020813d602011613617575b816135ff6020938361243c565b810103126102615761361090612cfe565b915f6131a8565b3d91506135f2565b505050505050565b61364191935060203d60201161046757610458818361243c565b915f613151565b602492919450613661903d805f833e610a21818361243c565b93909161310a565b505050565b81516001600160a01b03909116915f91829160200182855af13d1561371b573d67ffffffffffffffff8111610de3576136c991604051916136b96020601f19601f840116018461243c565b82523d5f602084013e5b8361377d565b80519081151591826136f7575b50506136df5750565b60249060405190635274afe760e01b82526004820152fd5b81925090602091810103126102615760200151801590811503610261575f806136d6565b6136c9906060906136c3565b5f8281526001820160205260409020546127d15780549068010000000000000000821015610de3576001820180825582101561250f5782600192825f5260205f2001558054925f520160205260405f2055600190565b906137a4575080511561379257805190602001fd5b604051630a12f52160e11b8152600490fd5b815115806137d7575b6137b5575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b156137ad56fe2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a2646970667358221220be298dcfac029e7302247aea40fd0e049991dd66aa71683dc5697b7191ad82cb64736f6c63430008190033
Deployed Bytecode
0x60806040526004361015610011575f80fd5b5f3560e01c80630154d0741461231657806301ffc9a7146122c0578063020624d2146122425780630a2f6357146121f057806312117e7714611e8a578063137fd2db14611e2857806322224b3714611cd5578063248a9ca314611c9c578063250b346a14611bbf5780632f2ff15d14611b76578063314ab46c14611b30578063359c055d146119bf57806336568abe146119785780633c535c5f146119385780634ff0876a1461191057806354150263146118f3578063564ca4641461186557806366388d031461182057806367534add146117f85780636dca32d2146117bc578063733f44ae146115d15780637ffc46ab146115a6578063811c97211461156657806381c5bfa41461151957806389afc0f1146114f657806391d14854146114a1578063a217fddf14611487578063a610652814611425578063b0e21e8a14611405578063bbea04d914611365578063bcd7927f14610fe4578063bd306d2514610f92578063c34c228914610f58578063ce3cc80714610edd578063ce525a7514610ebb578063cfa498a314610e9f578063d547741f14610e54578063dbfa483c14610c56578063dca3daae14610c04578063deba7c5114610bb2578063e0dd245714610b78578063e45a01c414610b38578063e5cd63b114610afe578063ed0a750c14610ad6578063f1e42ccd14610491578063f2e7b1ed146102a5578063f489cf68146102655763f626270b14610229575f80fd5b34610261576020366003190112610261576001600160a01b0361024a6123de565b165f526009602052602060405f2054604051908152f35b5f80fd5b34610261576020366003190112610261576001600160a01b036102866123de565b165f52600d602052602065ffffffffffff60405f205416604051908152f35b34610261576040366003190112610261576102be6123de565b6102c66123c8565b906001600160a01b0380831680156104795750805f5460301c1690604051938492635cbeecf160e11b845283600460209788935afa92831561046e575f9361043f575b50335f52600e855265ffffffffffff938460405f20541691858516938415158061042b575b610405578661033d5f956124bf565b945b818616878110156103ac57805f5260068b5260405f20335f528b5260405f20549081156103a1575f52600a8b526103798660405f20612a40565b80156103a1579161038f61039592600194612aa9565b906124d6565b955b011693879061033f565b505094600190610397565b509296945050935080156103ed57600e946103d0946103cb9316612c2d565b612490565b92335f525260405f20911665ffffffffffff198254161790555f80f35b60405163371bbb2960e11b8152336004820152602490fd5b604051637ebe62ef60e01b815233600482015265ffffffffffff85166024820152604490fd5b508661043687612490565b1684101561032e565b610460919350853d8711610467575b610458818361243c565b810190612472565b9185610309565b503d61044e565b6040513d5f823e3d90fd5b60249060405190630bc2c5df60e11b82526004820152fd5b34610261576040366003190112610261576104aa6123de565b6104b26123c8565b6001600160a01b03811680156104795750335f52600b60205265ffffffffffff60405f20541660018060a01b035f5460301c16604051635cbeecf160e11b8152602081600481855afa90811561046e575f91610ab7575b5065ffffffffffff8116151580610a9d575b610a77579091849261052d5f926124bf565b9360018060a01b0360015416925b65ffffffffffff851665ffffffffffff87161015610a30576040516332a90e1b60e01b815265ffffffffffff87166004820152935f85602481845afa94851561046e575f95610a0c575b506040516311369f0f60e01b815265ffffffffffff8816600482015297602089602481885afa98891561046e575f996109eb575b505f965f5b87518110156106745761060e60208c6001600160a01b036105df858d6124fb565b5116604051916105ee83612420565b5f83526040518095819482936377daaceb60e11b845233600485016125e4565b03915afa90811561046e575f91610642575b5061062e575b6001016105be565b9761063a6001916125a5565b989050610626565b90506020813d60201161066c575b8161065d6020938361243c565b8101031261026157518c610620565b3d9150610650565b50909596949893979291610687866124e3565b95610695604051978861243c565b8087526106a4601f19916124e3565b013660208801375f965f5b89518110156107865761070060206001600160a01b036106cf848e6124fb565b5116604051906106de82612420565b5f82526040516377daaceb60e11b8152938492839182918e33600485016125e4565b03915afa90811561046e575f91610754575b50610720575b6001016106af565b9760019061074c906001600160a01b0361073a8c8e6124fb565b5116610746828c6124fb565b526125a5565b989050610718565b90506020813d60201161077e575b8161076f6020938361243c565b8101031261026157518d610712565b3d9150610762565b509650965090925095909295604051976311369f0f60e01b895265ffffffffffff851660048a0152602089602481845afa98891561046e575f996109ca575b5065ffffffffffff85165f52600a6020526107e38360405f20612a40565b9687156109b0575f945b895186101561098a576001600160a01b03610808878c6124fb565b511665ffffffffffff88165f52600760205260405f20815f5260205260405f20548c811561097e576020610862916040519061084382612420565b5f8252604051630b5cf1a760e21b8152938492839233600485016125e4565b0381865afa92831561046e578e915f94610947575b508315610906576108c993929160209165ffffffffffff6040519261089b84612420565b5f845260405197889485938493630a1e440d60e31b85521660048401526040602484015260448301906125c0565b03915afa92831561046e575f93610913575b508b83156109065792600194926108f861038f936108fd96612aa9565b612bba565b955b01946107ed565b50505050946001906108ff565b9092506020813d60201161093f575b8161092f6020938361243c565b810103126102615751918e6108db565b3d9150610922565b915092506020813d602011610976575b816109646020938361243c565b81010312610261578d9051928f610877565b3d9150610957565b505050946001906108ff565b9750949095935065ffffffffffff92985060019197505b0116949395909592919261053b565b94909593965065ffffffffffff92985060019197506109a1565b6109e491995060203d60201161046757610458818361243c565b97896107c5565b610a0591995060203d60201161046757610458818361243c565b97896105b9565b610a299195503d805f833e610a21818361243c565b810190612523565b9388610585565b8691859180156103ed57610a50936103cb926001600160a01b0316612c2d565b335f52600b60205265ffffffffffff60405f20911665ffffffffffff198254161790555f80f35b604051637ebe62ef60e01b815233600482015265ffffffffffff84166024820152604490fd5b5065ffffffffffff610aae82612490565b1683101561051b565b610ad0915060203d60201161046757610458818361243c565b85610509565b34610261575f366003190112610261576002546040516001600160a01b039091168152602090f35b34610261575f3660031901126102615760206040517f395a0294cd085f8232b41d6875810a7f41d359f4e1281ec5b60138a9a1e62dff8152f35b34610261576020366003190112610261576001600160a01b03610b596123de565b165f52600b602052602065ffffffffffff60405f205416604051908152f35b34610261575f3660031901126102615760206040517f51785d261c3b3e8c2cfe2d0e108b9ab2b1fb703ff9e9e49aba4ebdc85c36152b8152f35b3461026157604036600319011261026157610bcb61239e565b65ffffffffffff610bda6123c8565b91165f52600760205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b3461026157604036600319011261026157610c1d61239e565b65ffffffffffff610c2c6123c8565b91165f52600860205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b3461026157608036600319011261026157610c6f61239e565b610c776123b3565b90610c806123f4565b60643590610c8c6126c4565b6001600160a01b0316908115610e3b578015610e235765ffffffffffff8094168015610e0b578082028281048203610df757604051906020936323b872dd60e01b60208401526024923360248201523060448201528260648201526064815260a081019281841067ffffffffffffffff851117610de357610d17610d4a92610d50956040528961366e565b610d2861ffff91825f541690612aa9565b885f526009602052610d3f60405f209182546124d6565b90555f541682612aa9565b906125b3565b91865f9616955b87811682811015610daf578701888111610d9c57889182600192165f52600a8752610d948860405f20815f52600281018a528860405f2055613727565b500116610d57565b83634e487b7160e01b5f5260116004525ffd5b86887f2901fd37b49145e0b4299d55442c5a6d105a73af06d898dfe79e30cf6f0c820f6040868982519182526020820152a3005b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b6024906040519063cee9681b60e01b82526004820152fd5b6024906040519063af8a36b160e01b82526004820152fd5b604051632484d91d60e01b815260048101839052602490fd5b3461026157604036600319011261026157610e9d600435610e736123c8565b90805f525f80516020613801833981519152602052610e98600160405f20015461271f565b612c7f565b005b34610261575f3660031901126102615760206040516127108152f35b34610261575f3660031901126102615760205f5461ffff60405191831c168152f35b3461026157602036600319011261026157610ef66123de565b610efe61266e565b6001600160a01b038116908115610f3f57610f18906127d7565b507fa57d72cef3e09f15eee5e9df8c6d1fcbd9dbb9eaba5a06c41eab256452bb99e45f80a2005b604051630bc2c5df60e11b815260048101839052602490fd5b34610261575f3660031901126102615760206040517f9df62d436bfc9f3be4953ab398f3aa862316b013d490e2138c80b4b2eadeabd78152f35b3461026157604036600319011261026157610fab61239e565b65ffffffffffff610fba6123c8565b91165f52600660205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b3461026157606036600319011261026157610ffd61239e565b6110056123c8565b61100d6123f4565b6110156126c4565b6001600160a01b0381811693841561134c5765ffffffffffff80911692835f526020916004835260019060ff8260405f200154161561133357845f5460301c169060405190635cbeecf160e11b82528582600481865afa91821561046e575f92611314575b5060028801818111610df75781169116106112fb57855f52600a84526110a38760405f20612a40565b9182156103ed5760045f809360405192838092636c88e31960e11b82525afa801561046e5782915f916112e1575b505f925b6112ae575b50602491505f87825416604051938480926332a90e1b60e01b82528c60048301525afa91821561046e575f92611292575b50905f825b611246575b50815f925b611194575b50505090610d4a6111309282612aa9565b9081156103ed578161118b916111847f10ab8c046ad20604ac9082fa423d13dcd1709565393e77c1478317b5a7489e7e96885f52600a875260405f2099168099815f526002810188525f6040812055613727565b5087612c2d565b604051908152a4005b90919282518410156112405760049087896111af87876124fb565b511660405193848092638da5cb5b60e01b82525afa801561046e5783925f91611200575b5082916111f7918c5f5260088b528b60405f2091165f528a5260405f2054906124d6565b9401929161111a565b809350898092503d8311611239575b611219818361243c565b81010312610261576111f7839291611231849361245e565b9150916111d3565b503d61120f565b9261111f565b9281929151841015611289578161127f81928b5f5260078a5260405f208b61126e89896124fb565b51165f528a5260405f2054906124d6565b9401909291611110565b92919091611115565b6112a79192503d805f833e610a21818361243c565b908a61110b565b80518310156112dc57816112d48194958b5f5260068a5260405f208b61126e89876124fb565b9401926110d5565b6110da565b6112f591503d805f833e610a21818361243c565b8b6110d1565b6040516302a3d25d60e41b815260048101879052602490fd5b61132c919250863d881161046757610458818361243c565b908a61107a565b6040516358bf5bfb60e11b815260048101879052602490fd5b604051630bc2c5df60e11b815260048101869052602490fd5b346102615760403660031901126102615761137e61240a565b6024359061ffff821680920361026157611396612613565b61271082116113ec5760206001600160601b037fe3e459a3704c1dd004ff9be28a459efb27b73e3570c1b4b13aeba94e5310e6b0921692835f52600f825260405f208161ffff19825416179055604051908152a2005b6040516313573bbd60e31b815260048101839052602490fd5b34610261575f36600319011261026157602061ffff5f5416604051908152f35b346102615760203660031901126102615761143e6123de565b611446612613565b6001600160a01b038116908115610f3f5761146090612884565b507f501fd608c18945fd096d9274a50e1c9048ac305255bd9de1f712f8ecfcd8e9b15f80a2005b34610261575f3660031901126102615760206040515f8152f35b34610261576040366003190112610261576114ba6123c8565b6004355f525f8051602061380183398151915260205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b34610261575f36600319011261026157602061ffff5f5460101c16604051908152f35b3461026157602036600319011261026157600435611535612613565b65ffffffffffff60025460a01c16811161154e57600355005b602490604051906379b5b5bb60e11b82526004820152fd5b34610261576020366003190112610261576001600160a01b036115876123de565b165f52600c602052602065ffffffffffff60405f205416604051908152f35b34610261575f366003190112610261575f5460405160309190911c6001600160a01b03168152602090f35b34610261576040366003190112610261576115ea61239e565b6115f26123b3565b906115fb6126c4565b65ffffffffffff9081811691825f52600460205260405f209160018060a01b0390815f5460301c169160405196635cbeecf160e11b8852602088600481875afa97881561046e575f9861179b575b50600193600187019760ff89541661178357866116658b6124a7565b1681101561175b5750604051636c88e31960e11b81529798505f90889060049082905afa96871561046e575f97611739575b508554945f93929190855b6116c6575b5050505050505054905111156116b957005b805460ff19166001179055005b885187108061172e575b15611729576116eb836116e3898c6124fb565b511685612d12565b611701836116f9898c6124fb565b51168561309b565b85870194858811610df75761171c6117229188978b556125a5565b976125a5565b96946116a2565b6116a7565b5080821685106116d0565b849392919750611752903d805f833e610a21818361243c565b96909192611697565b604490876117688c6124a7565b6040519263548cd72d60e11b84526004840152166024820152fd5b60249060405190632d80689560e21b82526004820152fd5b6117b591985060203d60201161046757610458818361243c565b9688611649565b34610261576020366003190112610261576001600160601b036117dd61240a565b165f52600f602052602061ffff60405f205416604051908152f35b34610261575f366003190112610261576001546040516001600160a01b039091168152602090f35b346102615760203660031901126102615765ffffffffffff61184061239e565b165f5260046020526040805f2060ff6001825492015416825191825215156020820152f35b346102615760203660031901126102615761187e61238d565b611886612613565b61ffff81169061271082116118da577f08820d76e20733f8e3d65d71140d92733224f51e0812416d3623c72c6c365c179160209163ffff00005f549160101b169063ffff00001916175f55604051908152a1005b60405163f6f4a38f60e01b815260048101839052602490fd5b34610261575f366003190112610261576020600354604051908152f35b34610261575f36600319011261026157602065ffffffffffff60025460a01c16604051908152f35b34610261576020366003190112610261576001600160a01b036119596123de565b165f52600e602052602065ffffffffffff60405f205416604051908152f35b34610261576040366003190112610261576119916123c8565b336001600160a01b038216036119ad57610e9d90600435612c7f565b60405163334bd91960e11b8152600490fd5b34610261576020806003193601126102615765ffffffffffff6119e061239e565b16805f52600a90600a835260405f2091604051938485828654928381520180965f52835f20925f5b85828210611b1a57505050611a1f9250038661243c565b845192611a44611a2e856124e3565b94611a3c604051968761243c565b8086526124e3565b84830190601f19013682375f5b8751811015611a98575f8381528585526040902060019190611a87906001600160a01b03611a7f848d6124fb565b511690612a40565b611a9182896124fb565b5201611a51565b858883868a604051948594604086019060408752518091526060860192905f5b818110611afa57505050848203858401525180825290820192915f5b828110611ae357505050500390f35b835185528695509381019392810192600101611ad4565b82516001600160a01b031685528897509385019391850191600101611ab8565b855484526001958601958b955093019201611a08565b34610261576040366003190112610261576020611b6e611b4e61239e565b65ffffffffffff611b5d6123c8565b91165f52600a835260405f20612a40565b604051908152f35b3461026157604036600319011261026157610e9d600435611b956123c8565b90805f525f80516020613801833981519152602052611bba600160405f20015461271f565b6129d0565b3461026157604036600319011261026157611bd86123de565b611be06123c8565b335f9081527f53f86594caac808e93186bdae378eecbec7ef80c6b741e6f848242ce6bbe0ca760205260409020549091907f395a0294cd085f8232b41d6875810a7f41d359f4e1281ec5b60138a9a1e62dff9060ff1615611c7e57506001600160a01b0390828216801561047957501690815f52600960205260405f205480156103ed57611c6e9183612c2d565b5f90815260096020526040812055005b6044906040519063e2517d3f60e01b82523360048301526024820152fd5b34610261576020366003190112610261576004355f525f805160206138018339815191526020526020600160405f200154604051908152f35b3461026157604036600319011261026157611cee6123de565b611cf66123c8565b906001600160a01b0380831680156104795750805f5460301c1690604051938492635cbeecf160e11b845283600460209788935afa92831561046e575f93611e09575b50335f52600c855265ffffffffffff938460405f205416918585169384151580611df5575b6104055786611d6d5f956124bf565b945b81861687811015611dd657805f5260088b5260405f20335f528b5260405f2054908115611dcb575f52600a8b52611da98660405f20612a40565b8015611dcb579161038f611dbf92600194612aa9565b955b0116938790611d6f565b505094600190611dc1565b509296945050935080156103ed57600c946103d0946103cb9316612c2d565b5086611e0087612490565b16841015611d5e565b611e21919350853d871161046757610458818361243c565b9185611d39565b3461026157602036600319011261026157611e416123de565b611e4961266e565b6001600160a01b038116908115610f3f57611e639061292a565b507f4b702e0c3f02069aa8f4b56a159c7b04aa778d066308e17c81955107c11fc7815f80a2005b346102615761010036600319011261026157611ea46123de565b611eac6123c8565b6001600160a01b03906044358281169182820361026157606435938085168095036102615760843561ffff958682168092036102615760a4359387851685036102615760c4359788168803610261577ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a009889549760ff8960401c16159867ffffffffffffffff8116801590816121e8575b60011490816121de575b1590816121d5575b506121c35767ffffffffffffffff1981166001178c55896121a4575b50801561218c5750821561217357848116801561215b57508482168015612143575090611fae82611f9e611fb49461274d565b50611fa8816127d7565b50612884565b5061292a565b505f54946601000000000000600160d01b039060301b169083826601000000000000600160d01b0319881617805f5560301c16936040519863b9bbb6f560e01b8a526020998a816004818a5afa92831561046e578b915f94612107575b50966004976001600160601b0360a01b94168460015416176001558460025494851617600255604051978880926329c2e7c360e21b82525afa91821561046e5763ffff0000965f936120e2575b5065ffff00000000929365ffffffffffff60d01b809565ffffffffffff60a01b9060a01b1692161717600255891b16951617179160101b1617175f5560e4356003556120a657005b7fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29168ff000000000000000019815416905560405160018152a1005b65ffff000000009350612101908c8d3d1061046757610458818361243c565b9261205e565b8281939295503d831161213c575b61211f818361243c565b81010312610261576004966121348c9261245e565b939097612011565b503d612115565b60249060405190637d3fd69f60e11b82526004820152fd5b6024906040519063c36ee61160e01b82526004820152fd5b604051631283e4c360e01b815260048101849052602490fd5b60249060405190631afcfa3760e31b82526004820152fd5b68ffffffffffffffffff191668010000000000000001178b558b611f6b565b60405163f92ee8a960e01b8152600490fd5b9050158d611f4f565b303b159150611f47565b8b9150611f3d565b346102615760403660031901126102615761220961239e565b65ffffffffffff6122186123c8565b91165f52600560205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b346102615760203660031901126102615761ffff61225e61238d565b612266612613565b1661271081116122a8576020817f15189fb9f7333d3c2ac875339ddb50cd49f554b5ed014b4b06111a437ec0b0c59261ffff195f5416175f55604051908152a1005b6024906040519063f6f4a38f60e01b82526004820152fd5b346102615760203660031901126102615760043563ffffffff60e01b811680910361026157602090637965db0b60e01b8114908115612305575b506040519015158152f35b6301ffc9a760e01b149050826122fa565b346102615760203660031901126102615761232f61238d565b612337612613565b61ffff81169061271082116118da575f805465ffff000000001916602092831b65ffff00000000161790556040519182527f2a7454b7f7c7420b1c343eeee16c5398b93c19f3398f92b247838873a57e378791a1005b6004359061ffff8216820361026157565b6004359065ffffffffffff8216820361026157565b6024359065ffffffffffff8216820361026157565b602435906001600160a01b038216820361026157565b600435906001600160a01b038216820361026157565b604435906001600160a01b038216820361026157565b600435906001600160601b038216820361026157565b6020810190811067ffffffffffffffff821117610de357604052565b90601f8019910116810190811067ffffffffffffffff821117610de357604052565b51906001600160a01b038216820361026157565b90816020910312610261575165ffffffffffff811681036102615790565b65ffffffffffff9081165f190191908211610df757565b65ffffffffffff9081166001190191908211610df757565b90600165ffffffffffff80931601918211610df757565b91908201809211610df757565b67ffffffffffffffff8111610de35760051b60200190565b805182101561250f5760209160051b010190565b634e487b7160e01b5f52603260045260245ffd5b60209081818403126102615780519067ffffffffffffffff821161026157019180601f84011215610261578251612559816124e3565b93612567604051958661243c565b818552838086019260051b820101928311610261578301905b82821061258e575050505090565b83809161259a8461245e565b815201910190612580565b5f198114610df75760010190565b91908203918211610df757565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b65ffffffffffff612610949360609360018060a01b0316835216602082015281604082015201906125c0565b90565b335f9081527fa8c56b0bde8ed88154b58109a7ae1167ae4ef886b0b03b8b38372be426b6ea2960205260409020547f51785d261c3b3e8c2cfe2d0e108b9ab2b1fb703ff9e9e49aba4ebdc85c36152b9060ff1615611c7e5750565b335f9081527fb7db2dd08fcb62d0c9e08c51941cae53c267786a0b75803fb7960902fc8ef97d602052604090205460ff16156126a657565b60405163e2517d3f60e01b81523360048201525f6024820152604490fd5b335f9081527f0892b1202b1511bac89b1a3007f83860fbc9e7e5a01f85bf69e046c113a67ea160205260409020547f9df62d436bfc9f3be4953ab398f3aa862316b013d490e2138c80b4b2eadeabd79060ff1615611c7e5750565b805f525f8051602061380183398151915260205260405f20335f5260205260ff60405f20541615611c7e5750565b6001600160a01b03165f8181527fb7db2dd08fcb62d0c9e08c51941cae53c267786a0b75803fb7960902fc8ef97d60205260409020545f805160206138018339815191529060ff166127d1575f805260205260405f20815f5260205260405f20600160ff1982541617905533905f5f805160206137e18339815191528180a4600190565b50505f90565b6001600160a01b03165f8181527fa8c56b0bde8ed88154b58109a7ae1167ae4ef886b0b03b8b38372be426b6ea2960205260409020547f51785d261c3b3e8c2cfe2d0e108b9ab2b1fb703ff9e9e49aba4ebdc85c36152b91905f805160206138018339815191529060ff1661287d57825f5260205260405f20815f5260205260405f20600160ff1982541617905533915f805160206137e18339815191525f80a4600190565b5050505f90565b6001600160a01b03165f8181527f0892b1202b1511bac89b1a3007f83860fbc9e7e5a01f85bf69e046c113a67ea160205260409020547f9df62d436bfc9f3be4953ab398f3aa862316b013d490e2138c80b4b2eadeabd791905f805160206138018339815191529060ff1661287d57825f5260205260405f20815f5260205260405f20600160ff1982541617905533915f805160206137e18339815191525f80a4600190565b6001600160a01b03165f8181527f53f86594caac808e93186bdae378eecbec7ef80c6b741e6f848242ce6bbe0ca760205260409020547f395a0294cd085f8232b41d6875810a7f41d359f4e1281ec5b60138a9a1e62dff91905f805160206138018339815191529060ff1661287d57825f5260205260405f20815f5260205260405f20600160ff1982541617905533915f805160206137e18339815191525f80a4600190565b90815f525f805160206138018339815191528060205260405f209160018060a01b031691825f5260205260ff60405f205416155f1461287d57825f5260205260405f20815f5260205260405f20600160ff1982541617905533915f805160206137e18339815191525f80a4600190565b6001600160a01b039091165f81815260028301602052604090205491821590829082612a89575b5050612a71575090565b6024906040519063015ab34360e11b82526004820152fd5b612aa192506001915f520160205260405f2054151590565b15815f612a67565b90808202905f1981840990828083109203918083039214612b18576127109082821115612b06577fbc01a36e2eb1c432ca57a786c226809d495182a9930be0ded288ce703afb7e91940990828211900360fc1b910360041c170290565b60405163227bc15360e01b8152600490fd5b505061271091500490565b61271091818302915f1984820993838086109503948086039514612b985784831115612b0657829109815f038216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b505080925015612ba6570490565b634e487b7160e01b5f52601260045260245ffd5b9091828202915f1984820993838086109503948086039514612b985784831115612b0657829109815f038216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b60405163a9059cbb60e01b60208201526001600160a01b039092166024830152604480830193909352918152608081019167ffffffffffffffff831182841017610de357612c7d9260405261366e565b565b90815f525f805160206138018339815191528060205260405f209160018060a01b031691825f5260205260ff60405f2054165f1461287d57825f5260205260405f20815f5260205260405f2060ff19815416905533917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b5f80a4600190565b51906001600160601b038216820361026157565b91909160018060a01b038060025416926040516306b4f20360e41b815260208160448165ffffffffffff988989166004830152878b1660248301525afa90811561046e575f91613069575b50600354811061302d57612d79908560025460a01c1690612b23565b935f935f54936040519363b2834db760e01b85525f85600481858a60301c165afa94851561046e575f95612f92575b505f965b8551881015612f2d57612e1360206001600160601b03612dcc8b8a6124fb565b51604051634b785e6960e11b815265ffffffffffff891660048201526001600160a01b038f16602482015291166001600160601b0316604482015291829081906064820190565b0381878c60301c165afa90811561046e575f91612efb575b506001600160601b03612e3e8a896124fb565b51604051630e4e7bd560e01b815287891660048201529116602482015260208160448160308d901c89165afa90811561046e57888b915f93612ec1575b5092612eb4600195936001600160601b03612e9c61038f95612eb9986124fb565b51165f52600f60205261ffff60405f20541692612b23565b612aa9565b970196612dac565b94925050506020833d602011612ef3575b81612edf6020938361243c565b810103126102615791518988612eb4612e7b565b3d9150612ed2565b90506020813d602011612f25575b81612f166020938361243c565b8101031261026157515f612e2b565b3d9150612f09565b61ffff91999750612f77949550612f5592969398612f4a91612aa9565b9260101c1682612aa9565b908387165f52600660205260405f208587165f526020528160405f20556125b3565b93165f52600560205260405f2091165f5260205260405f2055565b9094503d805f833e612fa4818361243c565b8101906020818303126102615780519067ffffffffffffffff821161026157019080601f83011215610261578151612fdb816124e3565b92612fe9604051948561243c565b81845260208085019260051b82010192831161026157602001905b82821061301557505050935f612da8565b6020809161302284612cfe565b815201910190613004565b50939290918181165f52600560205260405f208385165f526020525f6040812055165f52600660205260405f2091165f526020525f6040812055565b90506020813d602011613093575b816130846020938361243c565b8101031261026157515f612d5d565b3d9150613077565b65ffffffffffff81165f52600560205260405f2060018060a01b0383165f5260205260405f20548015613669576001546040516332a90e1b60e01b815265ffffffffffff8416600482015293905f90859060249082906001600160a01b03165afa93841561046e575f94613648575b505f546040516311369f0f60e01b815265ffffffffffff851660048201529291906020908490602490829060301c6001600160a01b03165afa92831561046e575f93613627575b505f5b855181101561361f576024906001600160a01b0361317282896124fb565b5116602060018060a01b036001541660405194858092639374a51d60e01b82528560048301525afa92831561046e575f936135e3575b50604051630ce9b79360e41b815292602084600481855afa93841561046e575f946135a7575b505f5460405163289c0bd760e21b815294909190602086600481603087901c6001600160a01b03165afa95861561046e57899688915f9161355b575b509165ffffffffffff979160209361328060405161322781612420565b5f8152604051633dc5038960e01b81526001600160a01b0393841660048201526001600160601b03891660248201529290931660448301529390991660648a015260a060848a01528892839182919060a48301906125c0565b03916001600160a01b03165afa94851561046e575f95613527575b50846132af575b5050506001915001613154565b604051634b785e6960e11b815265ffffffffffff8a1660048201526001600160a01b03871660248201526001600160601b038216604482015260208180606481010381603087901c6001600160a01b03165afa95861561046e5788915f976134ee575b5090612eb46133248861334494612b23565b6001600160601b0385165f52600f60205261ffff60405f20541690612aa9565b946001600160601b03604051926314b1bc0360e21b845260018060a01b038916600485015265ffffffffffff8c16602485015216604483015260208260648160018060a01b038760301c165afa91821561046e575f926134ba575b5081613498575b505061ffff6133b99160201c1684612aa9565b65ffffffffffff88165f52600860205260405f2093604051638da5cb5b60e01b8152602081600481875afa90811561046e575f91613452575b509060019561341e9392878060a01b03165f5260205260405f206134178382546124d6565b90556125b3565b9065ffffffffffff88165f52600760205260405f20905f5260205261344860405f209182546124d6565b90555f80806132a2565b929190506020833d602011613490575b8161346f6020938361243c565b810103126102615760019561348661341e9461245e565b91929350956133f2565b3d9150613462565b6133b992956134ad61ffff936134b393612b23565b90612aa9565b94916133a6565b9091506020813d6020116134e6575b816134d66020938361243c565b810103126102615751905f61339f565b3d91506134c9565b915095506020813d60201161351f575b8161350b6020938361243c565b810103126102615751948790612eb4613312565b3d91506134fe565b9094506020813d602011613553575b816135436020938361243c565b810103126102615751935f61329b565b3d9150613536565b975050506020863d60201161359f575b816135786020938361243c565b8101031261026157886020918861359565ffffffffffff9961245e565b919350919761320a565b3d915061356b565b9093506020813d6020116135db575b816135c36020938361243c565b81010312610261576135d49061245e565b925f6131ce565b3d91506135b6565b9092506020813d602011613617575b816135ff6020938361243c565b810103126102615761361090612cfe565b915f6131a8565b3d91506135f2565b505050505050565b61364191935060203d60201161046757610458818361243c565b915f613151565b602492919450613661903d805f833e610a21818361243c565b93909161310a565b505050565b81516001600160a01b03909116915f91829160200182855af13d1561371b573d67ffffffffffffffff8111610de3576136c991604051916136b96020601f19601f840116018461243c565b82523d5f602084013e5b8361377d565b80519081151591826136f7575b50506136df5750565b60249060405190635274afe760e01b82526004820152fd5b81925090602091810103126102615760200151801590811503610261575f806136d6565b6136c9906060906136c3565b5f8281526001820160205260409020546127d15780549068010000000000000000821015610de3576001820180825582101561250f5782600192825f5260205f2001558054925f520160205260405f2055600190565b906137a4575080511561379257805190602001fd5b604051630a12f52160e11b8152600490fd5b815115806137d7575b6137b5575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b156137ad56fe2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a2646970667358221220be298dcfac029e7302247aea40fd0e049991dd66aa71683dc5697b7191ad82cb64736f6c63430008190033
Loading...
Loading
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
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.