Feature Tip: Add private address tag to any address under My Name Tag !
Source Code
Overview
ETH Balance
0 ETH
Eth Value
$0.00| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||
|---|---|---|---|---|---|---|---|
| 0x6101c060 | 24470858 | 13 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
RegenStakerWithoutDelegateSurrogateVotes
Compiler Version
v0.8.33+commit.64118f21
Optimization Enabled:
Yes with 1 runs
Other Settings:
prague EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: AGPL-3.0-only
// This contract inherits from Staker.sol by [ScopeLift](https://scopelift.co)
// Staker.sol is licensed under AGPL-3.0-only.
// Users of this should ensure compliance with the AGPL-3.0-only license terms of the inherited Staker.sol contract.
pragma solidity ^0.8.0;
// === Base Imports ===
import { RegenStakerBase, Staker, IERC20, DelegationSurrogate, IAddressSet, IEarningPowerCalculator } from "src/regen/RegenStakerBase.sol";
import { AccessMode } from "src/constants.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
// === Contract Header ===
/// @title RegenStakerWithoutDelegateSurrogateVotes
/// @author [Golem Foundation](https://golem.foundation)
/// @custom:security-contact [email protected]
/// @notice Variant of RegenStakerBase for regular ERC20 tokens without delegation support.
/// @custom:origin https://github.com/ScopeLift/flexible-voting/blob/master/src/Staker.sol
/// @dev Eliminates surrogate pattern; tokens are held directly by this contract.
/// @dev DELEGATION LIMITATION: Delegatee is tracked for compatibility but has no effect on token delegation.
///
/// @dev VARIANT COMPARISON: (See RegenStaker.sol for the delegation variant)
/// ┌─────────────────────────────────────┬─────────────────┬──────────────────────────────────┐
/// │ Feature │ RegenStaker │ RegenStakerWithoutDelegateSurro… │
/// ├─────────────────────────────────────┼─────────────────┼──────────────────────────────────┤
/// │ Delegation Support │ ✓ Full Support │ ✗ No Support │
/// │ Surrogate Deployment │ ✓ Per Delegatee │ ✗ Contract as Surrogate │
/// │ Token Holder │ Surrogates │ Contract Directly │
/// │ Voting Capability │ ✓ via Surrogate │ ✗ Not Available │
/// │ Gas Cost (First Delegatee) │ Higher │ Lower │
/// │ Integration Complexity │ Higher │ Lower │
/// └─────────────────────────────────────┴─────────────────┴──────────────────────────────────┘
///
/// @dev VARIANT COMPARISON: See RegenStaker.sol for detailed comparison table.
///
/// @dev KEY DIFFERENCES FROM RegenStaker:
/// - No delegation support: delegatee parameter is informational only
/// - Lower gas costs: no surrogate contract deployment
/// - Simpler integration: contract holds tokens directly
/// - No voting capabilities through delegation
/// - Same security model: both variants use owner-centric allowset authorization
///
/// @dev USE CASE: Choose this variant for simple ERC20 staking without governance requirements.
contract RegenStakerWithoutDelegateSurrogateVotes is RegenStakerBase {
// === Custom Errors ===
error DelegationNotSupported();
// === Constructor ===
/// @notice Constructor for the RegenStakerWithoutDelegateSurrogateVotes contract.
/// @param _rewardsToken Token distributed as staking rewards
/// @param _stakeToken ERC20 token users stake (must implement IERC20Permit)
/// @param _earningPowerCalculator Contract calculating earning power from stakes
/// @param _maxBumpTip Maximum tip for earning power bumps in reward token base units
/// @param _admin Address with admin permissions (TRUSTED)
/// @param _rewardDuration Duration for reward distribution in seconds
/// @param _minimumStakeAmount Minimum stake required in stake token base units
/// @param _stakerAllowset Allowset for ALLOWSET mode (can be address(0))
/// @param _stakerBlockset Blockset for BLOCKSET mode (can be address(0))
/// @param _stakerAccessMode Staker access mode (NONE, ALLOWSET, or BLOCKSET)
/// @param _allocationMechanismAllowset Allowset of approved allocation mechanisms (SECURITY CRITICAL)
/// Only audited and trusted allocation mechanisms should be in the allowset.
/// Users contribute funds to these mechanisms and may lose funds if mechanisms are malicious.
constructor(
IERC20 _rewardsToken,
IERC20 _stakeToken,
IEarningPowerCalculator _earningPowerCalculator,
uint256 _maxBumpTip,
address _admin,
uint128 _rewardDuration,
uint128 _minimumStakeAmount,
IAddressSet _stakerAllowset,
IAddressSet _stakerBlockset,
AccessMode _stakerAccessMode,
IAddressSet _allocationMechanismAllowset
)
RegenStakerBase(
_rewardsToken,
_stakeToken,
_earningPowerCalculator,
_maxBumpTip,
_admin,
_rewardDuration,
_minimumStakeAmount,
_stakerAllowset,
_stakerBlockset,
_stakerAccessMode,
_allocationMechanismAllowset,
"RegenStakerWithoutDelegateSurrogateVotes"
)
{}
// === Overridden Functions ===
/// @notice Validates sufficient reward token balance and returns the required balance for this variant
/// @dev Overrides base to include totalStaked for same-token scenarios since stakes are held in main contract
/// @param _amount Reward amount being added in reward token base units
/// @return required Required balance including appropriate obligations
function _validateAndGetRequiredBalance(uint256 _amount) internal view override returns (uint256 required) {
uint256 currentBalance = REWARD_TOKEN.balanceOf(address(this));
uint256 carryOverAmount = totalRewards - totalClaimedRewards;
if (address(REWARD_TOKEN) == address(STAKE_TOKEN)) {
// Same-token scenario: stakes ARE in main contract, so include totalStaked
// Accounting: totalStaked + totalRewards - totalClaimedRewards + newAmount
required = totalStaked + carryOverAmount + _amount;
} else {
// Different-token scenario: stakes are separate, only track reward obligations
// Accounting: totalRewards - totalClaimedRewards + newAmount
required = carryOverAmount + _amount;
}
if (currentBalance < required) {
revert InsufficientRewardBalance(currentBalance, required);
}
return required;
}
/// @notice Returns this contract as the "surrogate" since we hold tokens directly
/// @dev ARCHITECTURE: This variant uses address(this) as surrogate to eliminate delegation complexity
/// while maintaining compatibility with base Staker contract logic. This allows reuse of all
/// base functionality without deploying separate surrogate contracts.
/// @dev WARNING: Deviates from standard surrogate pattern. Always returns address(this).
/// Integrators expecting separate surrogate contracts will fail. Do not assume external
/// surrogate contracts exist when integrating with this variant.
function surrogates(address /* _delegatee */) public view override returns (DelegationSurrogate) {
return DelegationSurrogate(address(this));
}
/// @notice Returns this contract as the "surrogate" - no separate contracts needed
/// @dev SIMPLIFICATION: Eliminates need for complex token transfer overrides
function _fetchOrDeploySurrogate(address /* _delegatee */) internal view override returns (DelegationSurrogate) {
return DelegationSurrogate(address(this));
}
/// @notice Override to support withdrawals when this contract acts as its own surrogate
/// @dev Since this contract uses address(this) as surrogate, use safeTransfer for contract-to-user paths.
function _stakeTokenSafeTransferFrom(address _from, address _to, uint256 _value) internal override {
// Use safeTransfer for withdrawals (contract -> user)
if (_from == address(this)) {
SafeERC20.safeTransfer(STAKE_TOKEN, _to, _value);
return;
}
// Default behavior for deposits (user -> contract)
super._stakeTokenSafeTransferFrom(_from, _to, _value);
}
/// @notice Delegation changes are not supported in this variant
/// @dev Always reverts since this contract doesn't use delegation surrogates - always uses address(this)
/// @dev Both alterDelegatee() and alterDelegateeOnBehalf() call this internal function
function _alterDelegatee(Deposit storage, DepositIdentifier, address) internal pure override {
revert DelegationNotSupported();
}
}// SPDX-License-Identifier: AGPL-3.0-only
// This contract inherits from Staker.sol by [ScopeLift](https://scopelift.co)
// Staker.sol is licensed under AGPL-3.0-only.
// Users of this should ensure compliance with the AGPL-3.0-only license terms of the inherited Staker.sol contract.
pragma solidity ^0.8.0;
// OpenZeppelin Imports
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
// Staker Library Imports
import { Staker, DelegationSurrogate, SafeCast, SafeERC20, IERC20 } from "staker/Staker.sol";
import { StakerOnBehalf } from "staker/extensions/StakerOnBehalf.sol";
import { StakerPermitAndStake } from "staker/extensions/StakerPermitAndStake.sol";
// Local Imports
import { IAddressSet } from "src/utils/IAddressSet.sol";
import { IEarningPowerCalculator } from "staker/interfaces/IEarningPowerCalculator.sol";
import { TokenizedAllocationMechanism } from "src/mechanisms/TokenizedAllocationMechanism.sol";
import { OctantQFMechanism } from "src/mechanisms/mechanism/OctantQFMechanism.sol";
import { AccessMode } from "src/constants.sol";
import { NotInAllowset } from "src/errors.sol";
// === Contract Header ===
/// @title RegenStakerBase
/// @author [Golem Foundation](https://golem.foundation)
/// @custom:security-contact [email protected]
/// @custom:origin https://github.com/ScopeLift/staker
/// @notice Base contract for RegenStaker variants, extending the Staker contract by [ScopeLift](https://scopelift.co).
/// @notice Provides shared functionality including:
/// - Variable reward duration (7-3000 days, configurable by admin)
/// - Earning power management with external bumping incentivized by tips (up to maxBumpTip)
/// - Adjustable minimum stake amount (existing deposits grandfathered with restrictions)
/// - Access control for stakers and allocation mechanisms
/// - Reward compounding (when REWARD_TOKEN == STAKE_TOKEN)
/// - Reward contribution to approved allocation mechanisms
/// - Admin controls (pause/unpause, config updates)
///
/// @dev WITHDRAWAL PROTECTION:
/// Users can always withdraw their staked tokens, even when the contract is paused.
/// The pause functionality affects all other operations (stake, claim, contribute, compound)
/// but explicitly excludes withdrawals to preserve user access to their principal funds.
/// This design ensures emergency pause can halt new deposits and reward operations while
/// maintaining user control over their staked assets at all times.
///
/// @dev CLAIMER PERMISSION MODEL:
/// Claimers are trusted entities designated by deposit owners with specific permissions:
///
/// Permission Matrix:
/// ┌─────────────────────────┬──────────┬─────────┐
/// │ Operation │ Owner │ Claimer │
/// ├─────────────────────────┼──────────┼─────────┤
/// │ Claim rewards │ ✓ │ ✓ │
/// │ Compound rewards*† │ ✓ │ ✓ │
/// │ Contribute to public‡ │ ✓ │ ✓ │
/// │ Stake more │ ✓ │ ✗ │
/// │ Withdraw │ ✓ │ ✗ │
/// │ Alter delegatee │ ✓ │ ✗ │
/// │ Alter claimer │ ✓ │ ✗ │
/// └─────────────────────────┴──────────┴─────────┘
/// * Compounding increases deposit stake (intended behavior)
/// † Compounding requires deposit owner to pass stakerAccessMode checks (allowset/blockset enforcement)
/// ‡ Mechanism must be on allocationMechanismAllowset; contributor checked via mechanism's contributionAllowset
/// § VOTING POWER: The contributor (msg.sender) receives voting power in the allocation mechanism,
/// NOT the deposit owner. When a claimer contributes, the claimer gets voting power.
///
/// When designating a claimer, owners explicitly trust them with:
/// 1. Claiming accrued rewards on their behalf
/// 2. Compounding rewards to increase stake position (when REWARD_TOKEN == STAKE_TOKEN)
/// 3. Contributing unclaimed rewards to approved allocation mechanisms
/// 4. Receiving voting power in allocation mechanisms when they contribute (claimer gets voting power, not owner)
///
/// Security boundaries are maintained:
/// - Claimers cannot withdraw principal or rewards to arbitrary addresses
/// - Claimers cannot modify deposit parameters
/// - Owners can revoke claimer designation at any time via alterClaimer()
///
/// @notice Token requirements: STAKE_TOKEN and REWARD_TOKEN must be standard ERC-20 tokens.
/// Unsupported token behaviors include fee-on-transfer/deflationary mechanisms, rebasing,
/// or non-standard return values. Accounting assumes transferred amount equals requested
/// amount; non-standard tokens can break deposits, withdrawals, or reward accounting.
/// @dev Integer division causes ~1 wei precision loss, negligible due to SCALE_FACTOR (1e36).
/// @dev This base is abstract, with variants implementing token-specific behaviors (e.g., delegation surrogates).
/// @dev Earning power updates are required after balance changes; some are automatic, others via bumpEarningPower.
abstract contract RegenStakerBase is Staker, Pausable, ReentrancyGuard, EIP712, StakerPermitAndStake, StakerOnBehalf {
using SafeCast for uint256;
// === Enums ===
// === Structs ===
/// @notice Struct to hold shared configuration state
/// @dev Groups related configuration variables for better storage efficiency and easier inheritance.
struct SharedState {
uint128 rewardDuration;
uint128 minimumStakeAmount;
IAddressSet stakerAllowset;
IAddressSet allocationMechanismAllowset;
IAddressSet stakerBlockset;
AccessMode stakerAccessMode;
}
// === Constants ===
/// @notice Minimum allowed reward duration in seconds (7 days).
uint256 public constant MIN_REWARD_DURATION = 7 days;
/// @notice Maximum allowed reward duration to prevent excessively long reward periods.
uint256 public constant MAX_REWARD_DURATION = 3000 days;
// === Custom Errors ===
/// @param user Address that failed allowset check
error StakerNotAllowed(address user);
/// @param user Address found in blockset
error StakerBlocked(address user);
/// @param mechanism Allocation mechanism that rejected contributor
/// @param owner Deposit owner attempting contribution
error DepositOwnerNotEligibleForMechanism(address mechanism, address owner);
/// @param currentBalance Actual token balance in contract (in token base units)
/// @param required Minimum balance needed for totalStaked plus reward amount (in token base units)
error InsufficientRewardBalance(uint256 currentBalance, uint256 required);
/// @param requested Requested amount in token base units
/// @param available Available amount in token base units
error CantAfford(uint256 requested, uint256 available);
/// @param expected Minimum stake amount required in token base units
/// @param actual Actual stake amount provided in token base units
error MinimumStakeAmountNotMet(uint256 expected, uint256 actual);
/// @param rewardDuration Invalid duration value in seconds
error InvalidRewardDuration(uint256 rewardDuration);
error CannotChangeRewardDurationDuringActiveReward();
error CompoundingNotSupported();
error CannotRaiseMinimumStakeAmountDuringActiveReward();
error CannotRaiseMaxBumpTipDuringActiveReward();
/// @notice Error thrown when attempting to change earning power calculator during active reward
error CannotChangeEarningPowerCalculatorDuringActiveReward();
error ZeroOperation();
error NoOperation();
error DisablingAllocationMechanismAllowsetNotAllowed();
/// @param expected Address of REWARD_TOKEN
/// @param actual Address of token expected by allocation mechanism
error AssetMismatch(address expected, address actual);
// === State Variables ===
/// @notice Shared configuration state instance
/// @dev Internal storage for shared configuration accessible via getters.
SharedState internal sharedState;
/// @notice Tracks the total amount of rewards that have been added via notifyRewardAmount
/// @dev This accumulates all reward amounts ever added to the contract
uint256 public totalRewards;
/// @notice Tracks the total amount of rewards that have been consumed by users
/// @dev This includes claims, compounding, contributions, and tips
uint256 public totalClaimedRewards;
/// @notice Summary of the most recently scheduled reward cycle.
/// @dev Tracks both the new amount and any carried-over rewards for analytics and UX.
struct RewardSchedule {
uint256 addedAmount;
uint256 carryOverAmount;
uint256 totalScheduledAmount;
uint256 requiredBalance;
uint256 duration;
uint256 endTime;
}
/// @notice Cached metadata for the most recent reward schedule.
RewardSchedule public latestRewardSchedule;
// === Events ===
/// @notice Emitted when the staker allowset is updated
/// @param allowset Address of new allowset contract controlling staker access
event StakerAllowsetAssigned(IAddressSet indexed allowset);
/// @notice Emitted when the staker blockset is updated
/// @param blockset Address of new blockset contract defining blocked stakers
event StakerBlocksetAssigned(IAddressSet indexed blockset);
/// @notice Emitted when staker access mode is changed
/// @param mode New access control mode (NONE, ALLOWSET, or BLOCKSET)
event AccessModeSet(AccessMode indexed mode);
/// @notice Emitted when the allocation mechanism allowset is updated
/// @param allowset Address of new allowset contract defining approved mechanisms
event AllocationMechanismAllowsetAssigned(IAddressSet indexed allowset);
/// @notice Emitted when the reward duration is updated
/// @param newDuration New duration for reward distribution in seconds
event RewardDurationSet(uint256 newDuration);
/// @notice Emitted when a new reward schedule is created or updated.
/// @param addedAmount Newly supplied reward amount for this cycle
/// @param carryOverAmount Unclaimed rewards carried over into the new cycle
/// @param totalScheduledAmount Total rewards scheduled for distribution this cycle
/// @param requiredBalance Total balance the contract must hold after notification
/// @param duration Duration over which the rewards will stream
/// @param endTime Timestamp when the reward cycle is scheduled to end
event RewardScheduleUpdated(
uint256 addedAmount,
uint256 carryOverAmount,
uint256 totalScheduledAmount,
uint256 requiredBalance,
uint256 duration,
uint256 endTime
);
/// @notice Emitted when rewards are contributed to an allocation mechanism
/// @param depositId Deposit being used for contribution
/// @param contributor Address making the contribution (receives voting power)
/// @param fundingRound Allocation mechanism receiving the contribution
/// @param amount Contribution amount in reward token base units
event RewardContributed(
DepositIdentifier indexed depositId,
address indexed contributor,
address indexed fundingRound,
uint256 amount
);
/// @param delegatee Address for which surrogate should exist but doesn't
error SurrogateNotFound(address delegatee);
/// @notice Emitted when the minimum stake amount is updated
/// @param newMinimumStakeAmount New minimum stake required in stake token base units
event MinimumStakeAmountSet(uint256 newMinimumStakeAmount);
// === Getters ===
/// @notice Gets the current reward duration
/// @return Duration for reward distribution in seconds
function rewardDuration() external view returns (uint256) {
return sharedState.rewardDuration;
}
/// @notice Gets the staker allowset
/// @return Allowset contract controlling staker access
function stakerAllowset() external view returns (IAddressSet) {
return sharedState.stakerAllowset;
}
/// @notice Gets the staker blockset
/// @return Blockset contract defining blocked stakers
function stakerBlockset() external view returns (IAddressSet) {
return sharedState.stakerBlockset;
}
/// @notice Gets the staker access mode
/// @return Current access control mode (NONE, ALLOWSET, or BLOCKSET)
function stakerAccessMode() external view returns (AccessMode) {
return sharedState.stakerAccessMode;
}
/// @notice Gets the allocation mechanism allowset
/// @return Allowset contract defining approved allocation mechanisms
function allocationMechanismAllowset() external view returns (IAddressSet) {
return sharedState.allocationMechanismAllowset;
}
/// @notice Gets the minimum stake amount
/// @return Minimum stake required in stake token base units
function minimumStakeAmount() external view returns (uint256) {
return sharedState.minimumStakeAmount;
}
// === Constructor ===
/// @notice Constructor for RegenStakerBase
/// @dev Initializes Staker, extensions, and shared state
/// @param _rewardsToken Token distributed as staking rewards
/// @param _stakeToken Token users stake (must support IERC20Permit)
/// @param _earningPowerCalculator Contract calculating earning power from stakes
/// @param _maxBumpTip Maximum tip for earning power bumps in reward token base units
/// @param _admin Address with admin permissions
/// @param _rewardDuration Duration for reward distribution in seconds
/// @param _minimumStakeAmount Minimum stake required in stake token base units
/// @param _stakerAllowset Allowset contract for ALLOWSET mode (can be address(0))
/// @param _stakerBlockset Blockset contract for BLOCKSET mode (can be address(0))
/// @param _stakerAccessMode Initial access control mode (NONE, ALLOWSET, or BLOCKSET)
/// @param _allocationMechanismAllowset Allowset of approved allocation mechanisms (cannot be address(0))
/// @param _eip712Name EIP712 domain name for signature verification
constructor(
IERC20 _rewardsToken,
IERC20 _stakeToken,
IEarningPowerCalculator _earningPowerCalculator,
uint256 _maxBumpTip,
address _admin,
uint128 _rewardDuration,
uint128 _minimumStakeAmount,
IAddressSet _stakerAllowset,
IAddressSet _stakerBlockset,
AccessMode _stakerAccessMode,
IAddressSet _allocationMechanismAllowset,
string memory _eip712Name
)
Staker(_rewardsToken, _stakeToken, _earningPowerCalculator, _maxBumpTip, _admin)
StakerPermitAndStake(IERC20Permit(address(_stakeToken)))
EIP712(_eip712Name, "1")
{
// Fee collection has been eliminated - set MAX_CLAIM_FEE to 0 to disable fees permanently
MAX_CLAIM_FEE = 0;
// Explicitly initialize claimFeeParameters to zero state for clarity
claimFeeParameters = ClaimFeeParameters({ feeAmount: 0, feeCollector: address(0) });
// Enable self-transfers for compound operations when stake and reward tokens are the same
// This allows compoundRewards to use _stakeTokenSafeTransferFrom with address(this) as source
if (address(STAKE_TOKEN) == address(REWARD_TOKEN)) {
SafeERC20.safeIncreaseAllowance(STAKE_TOKEN, address(this), type(uint256).max);
}
// Initialize shared state
_initializeSharedState(
_rewardDuration,
_minimumStakeAmount,
_stakerAllowset,
_stakerBlockset,
_stakerAccessMode,
_allocationMechanismAllowset
);
}
// === Internal Functions ===
/// @notice Initialize shared state with validation
/// @dev Called by child constructors to set up shared configuration
/// @param _rewardDuration Duration for reward distribution in seconds
/// @param _minimumStakeAmount Minimum stake required in stake token base units
/// @param _stakerAllowset Allowset contract for ALLOWSET mode (can be address(0))
/// @param _stakerBlockset Blockset contract for BLOCKSET mode (can be address(0))
/// @param _stakerAccessMode Initial access control mode (NONE, ALLOWSET, or BLOCKSET)
/// @param _allocationMechanismAllowset Allowset of approved allocation mechanisms
function _initializeSharedState(
uint128 _rewardDuration,
uint128 _minimumStakeAmount,
IAddressSet _stakerAllowset,
IAddressSet _stakerBlockset,
AccessMode _stakerAccessMode,
IAddressSet _allocationMechanismAllowset
) internal {
require(
_rewardDuration >= MIN_REWARD_DURATION && _rewardDuration <= MAX_REWARD_DURATION,
InvalidRewardDuration(uint256(_rewardDuration))
);
// Align initialization invariants with setters: allocation mechanism allowset cannot be disabled
require(address(_allocationMechanismAllowset) != address(0), DisablingAllocationMechanismAllowsetNotAllowed());
// Sanity check: Allocation mechanism allowset must be distinct from staker address sets
require(
address(_allocationMechanismAllowset) != address(_stakerAllowset) &&
address(_allocationMechanismAllowset) != address(_stakerBlockset),
Staker__InvalidAddress()
);
// Emit events first to match setter ordering
emit RewardDurationSet(_rewardDuration);
emit MinimumStakeAmountSet(_minimumStakeAmount);
emit StakerAllowsetAssigned(_stakerAllowset);
emit StakerBlocksetAssigned(_stakerBlockset);
emit AccessModeSet(_stakerAccessMode);
emit AllocationMechanismAllowsetAssigned(_allocationMechanismAllowset);
// Assign to storage after emits for consistency with setters
sharedState.rewardDuration = _rewardDuration;
sharedState.minimumStakeAmount = _minimumStakeAmount;
sharedState.stakerAllowset = _stakerAllowset;
sharedState.stakerBlockset = _stakerBlockset;
sharedState.stakerAccessMode = _stakerAccessMode;
sharedState.allocationMechanismAllowset = _allocationMechanismAllowset;
}
/// @notice Sets the reward duration for future reward notifications
/// @dev GAS IMPLICATIONS: Shorter reward durations may result in higher gas costs for certain
/// operations due to more frequent reward rate calculations. Consider gas costs when
/// selecting reward durations.
/// @dev Can only be called by admin and not during active reward period
/// @param _rewardDuration New reward duration in seconds (7 days minimum, 3000 days maximum)
function setRewardDuration(uint128 _rewardDuration) external {
_revertIfNotAdmin();
require(block.timestamp > rewardEndTime, CannotChangeRewardDurationDuringActiveReward());
require(
_rewardDuration >= MIN_REWARD_DURATION && _rewardDuration <= MAX_REWARD_DURATION,
InvalidRewardDuration(uint256(_rewardDuration))
);
require(sharedState.rewardDuration != _rewardDuration, NoOperation());
emit RewardDurationSet(_rewardDuration);
sharedState.rewardDuration = _rewardDuration;
}
/// @notice Internal implementation of notifyRewardAmount using custom reward duration
/// @dev Overrides the base Staker logic to use variable duration
/// @dev Enforces monotonic reward property: totalRewards can only increase, never decrease.
/// Once rewards are notified and time has elapsed, those elapsed portions cannot be
/// clawed back. Admins can adjust future reward rates by notifying new amounts, but
/// the current schedule represents a commitment for its duration and typically won't
/// be changed mid-cycle.
/// @param _amount Reward amount to notify in reward token base units
/// @param _requiredBalance Required contract balance calculated by variant-specific validation
function _notifyRewardAmountWithCustomDuration(uint256 _amount, uint256 _requiredBalance) internal {
if (!isRewardNotifier[msg.sender]) revert Staker__Unauthorized("not notifier", msg.sender);
rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();
if (block.timestamp >= rewardEndTime) {
// Scale to maintain precision across variable durations
scaledRewardRate = (_amount * SCALE_FACTOR) / sharedState.rewardDuration;
} else {
uint256 _remainingReward = scaledRewardRate * (rewardEndTime - block.timestamp);
// Scale to maintain precision across variable durations
scaledRewardRate = (_remainingReward + _amount * SCALE_FACTOR) / sharedState.rewardDuration;
}
rewardEndTime = block.timestamp + sharedState.rewardDuration;
lastCheckpointTime = block.timestamp;
if (scaledRewardRate < SCALE_FACTOR) revert Staker__InvalidRewardRate();
// Calculate reward schedule metadata before updating totalRewards
uint256 carryOverAmount = totalRewards - totalClaimedRewards;
uint256 totalScheduledAmount = carryOverAmount + _amount;
// Track total rewards added
totalRewards += _amount;
emit RewardNotified(_amount, msg.sender);
latestRewardSchedule = RewardSchedule({
addedAmount: _amount,
carryOverAmount: carryOverAmount,
totalScheduledAmount: totalScheduledAmount,
requiredBalance: _requiredBalance,
duration: sharedState.rewardDuration,
endTime: rewardEndTime
});
emit RewardScheduleUpdated(
_amount,
carryOverAmount,
totalScheduledAmount,
_requiredBalance,
sharedState.rewardDuration,
rewardEndTime
);
}
/// @notice Sets the allowset for stakers (who can stake tokens)
/// @dev OPERATIONAL IMPACT: Affects all stake and stakeMore operations immediately.
/// @dev GRANDFATHERING: Existing stakers can continue operations regardless of new allowset.
/// @dev Can only be called by admin
/// @dev NOTE: Use setAccessMode(AccessMode.NONE) to disable access control, not address(0)
/// @param _stakerAllowset New staker allowset contract
function setStakerAllowset(IAddressSet _stakerAllowset) external {
require(sharedState.stakerAllowset != _stakerAllowset, NoOperation());
require(address(_stakerAllowset) != address(sharedState.allocationMechanismAllowset), Staker__InvalidAddress());
_revertIfNotAdmin();
emit StakerAllowsetAssigned(_stakerAllowset);
sharedState.stakerAllowset = _stakerAllowset;
}
/// @notice Sets the staker blockset
/// @dev OPERATIONAL IMPACT: Affects all stake operations immediately.
/// @dev Can only be called by admin
/// @dev NOTE: Use setAccessMode(AccessMode.NONE) to disable access control, not address(0)
/// @param _stakerBlockset New staker blockset contract
function setStakerBlockset(IAddressSet _stakerBlockset) external {
_revertIfNotAdmin();
require(sharedState.stakerBlockset != _stakerBlockset, NoOperation());
require(address(_stakerBlockset) != address(sharedState.allocationMechanismAllowset), Staker__InvalidAddress());
emit StakerBlocksetAssigned(_stakerBlockset);
sharedState.stakerBlockset = _stakerBlockset;
}
/// @notice Sets the staker access mode
/// @dev OPERATIONAL IMPACT: Changes which address set (allowset/blockset) is active
/// @dev Can only be called by admin
/// @param _mode New access mode (NONE, ALLOWSET, or BLOCKSET)
function setAccessMode(AccessMode _mode) external {
_revertIfNotAdmin();
require(sharedState.stakerAccessMode != _mode, NoOperation());
emit AccessModeSet(_mode);
sharedState.stakerAccessMode = _mode;
}
/// @notice Sets the allowset for allocation mechanisms
/// @dev SECURITY: Only add thoroughly audited allocation mechanisms to this allowset.
/// Users will contribute rewards to approved mechanisms and funds cannot be recovered
/// if sent to malicious or buggy implementations.
/// @dev EVALUATION PROCESS: New mechanisms should undergo comprehensive security audit,
/// integration testing, and governance review before approval.
/// @dev OPERATIONAL IMPACT: Changes affect all future contributions. Existing contributions
/// to previously approved mechanisms are not affected.
/// @dev Can only be called by admin. Cannot set to address(0).
/// @dev AUDIT NOTE: Changes require governance approval.
/// @param _allocationMechanismAllowset New allowset contract (cannot be address(0))
function setAllocationMechanismAllowset(IAddressSet _allocationMechanismAllowset) external {
require(sharedState.allocationMechanismAllowset != _allocationMechanismAllowset, NoOperation());
require(address(_allocationMechanismAllowset) != address(0), DisablingAllocationMechanismAllowsetNotAllowed());
// Prevent footgun: allocation mechanism allowset must be distinct from staker address sets
require(
address(_allocationMechanismAllowset) != address(sharedState.stakerAllowset) &&
address(_allocationMechanismAllowset) != address(sharedState.stakerBlockset),
Staker__InvalidAddress()
);
_revertIfNotAdmin();
emit AllocationMechanismAllowsetAssigned(_allocationMechanismAllowset);
sharedState.allocationMechanismAllowset = _allocationMechanismAllowset;
}
/// @notice Sets the minimum stake amount
/// @dev GRANDFATHERING: Existing deposits below new minimum remain valid but will be
/// restricted from partial withdrawals and stakeMore operations until brought above threshold.
/// @dev TIMING RESTRICTION: Cannot raise minimum during active reward period for user protection.
/// @dev OPERATIONAL IMPACT: Affects all new stakes immediately. Consider user communication before changes.
/// @dev Can only be called by admin
/// @param _minimumStakeAmount New minimum stake amount in wei (0 = no minimum)
function setMinimumStakeAmount(uint128 _minimumStakeAmount) external {
_revertIfNotAdmin();
require(
_minimumStakeAmount <= sharedState.minimumStakeAmount || block.timestamp > rewardEndTime,
CannotRaiseMinimumStakeAmountDuringActiveReward()
);
emit MinimumStakeAmountSet(_minimumStakeAmount);
sharedState.minimumStakeAmount = _minimumStakeAmount;
}
/// @notice Sets the maximum bump tip with governance protection
/// @dev TIMING RESTRICTION: During active reward period only decreases are allowed; increases must wait until after rewardEndTime.
/// @dev SECURITY: Prevents malicious admin from extracting unclaimed rewards via tip manipulation.
/// @dev GOVERNANCE PROTECTION: Aligns with setMinimumStakeAmount protection for consistency.
/// @dev Can only be called by admin and not during active reward period
/// @param _newMaxBumpTip New maximum bump tip value in wei
function setMaxBumpTip(uint256 _newMaxBumpTip) external virtual override {
_revertIfNotAdmin();
// Allow decreases anytime; increases only after reward period ends
require(
_newMaxBumpTip <= maxBumpTip || block.timestamp > rewardEndTime,
CannotRaiseMaxBumpTipDuringActiveReward()
);
_setMaxBumpTip(_newMaxBumpTip);
}
/// @notice Pauses the contract, disabling user operations except withdrawals and view functions
/// @dev EMERGENCY USE: Intended for security incidents or critical maintenance.
/// @dev SCOPE: Affects stake, claim, contribute, and compound operations.
/// @dev USER PROTECTION: Withdrawals remain enabled to preserve user access to their funds.
/// @dev ADMIN ONLY: Only admin can pause. Use emergency procedures for urgent situations.
function pause() external whenNotPaused {
_revertIfNotAdmin();
_pause();
}
/// @notice Unpauses the contract, re-enabling all user operations
/// @dev RECOVERY: Use after resolving issues that required pause.
/// @dev ADMIN ONLY: Only admin can unpause. Ensure all issues resolved before unpause.
function unpause() external whenPaused {
_revertIfNotAdmin();
_unpause();
}
// === Public Functions ===
/// @notice Contributes unclaimed rewards to a user-specified allocation mechanism
/// @dev CONTRIBUTION RISK: Contributed funds are transferred to external allocation mechanisms
/// for public good causes. Malicious mechanisms may misappropriate funds for unintended
/// purposes rather than the stated public good cause.
/// @dev TRUST MODEL: Allocation mechanisms must be approved by protocol governance.
/// Only contribute to mechanisms you trust, as the protocol cannot recover funds
/// sent to malicious or buggy allocation mechanisms.
/// @dev VOTING POWER ASSIGNMENT: The contributor (msg.sender) receives voting power in the
/// allocation mechanism, NOT necessarily the deposit owner. When a claimer contributes
/// owner's rewards, the CLAIMER receives the voting power. This is intended behavior
/// as part of the claimer trust model.
/// @dev SECURITY: This function first withdraws rewards to the contributor, then the contributor
/// must have pre-approved the allocation mechanism to pull the tokens.
/// @dev SECURITY AUDIT: Ensure allocation mechanisms are immutable after approval.
/// @dev AUTHZ: Authorized caller is the deposit owner or the designated claimer; the claimer acts
/// as the owner's agent for rewards. Contribution access control enforced by mechanism.
/// @dev Requires contract not paused and uses reentrancy guard
/// @param _depositId Deposit identifier to contribute from
/// @param _allocationMechanismAddress Approved allocation mechanism to receive contribution
/// @param _amount Amount of unclaimed rewards to contribute (must be <= available rewards)
/// @param _deadline Signature expiration timestamp
/// @param _v Signature component v
/// @param _r Signature component r
/// @param _s Signature component s
/// @return amountContributedToAllocationMechanism Actual amount contributed
function contribute(
DepositIdentifier _depositId,
address _allocationMechanismAddress,
uint256 _amount,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) public virtual whenNotPaused nonReentrant returns (uint256 amountContributedToAllocationMechanism) {
_revertIfAddressZero(_allocationMechanismAddress);
require(
sharedState.allocationMechanismAllowset.contains(_allocationMechanismAddress),
NotInAllowset(_allocationMechanismAddress)
);
// Validate asset compatibility to fail fast and provide clear error
{
address expectedAsset = address(TokenizedAllocationMechanism(_allocationMechanismAddress).asset());
if (address(REWARD_TOKEN) != expectedAsset) {
revert AssetMismatch(address(REWARD_TOKEN), expectedAsset);
}
}
Deposit storage deposit = deposits[_depositId];
if (deposit.claimer != msg.sender && deposit.owner != msg.sender) {
revert Staker__Unauthorized("not claimer or owner", msg.sender);
}
// Defense-in-depth dual-check architecture (Cantina Finding #127 fix):
// 1. TAM checks msg.sender (claimer/contributor) via beforeSignupHook - receives voting power
// 2. RegenStaker checks deposit.owner (fund source) must also be eligible (defense-in-depth)
// This prevents delisted owners from using allowlisted claimers as proxies
//
// IMPORTANT: Voting power goes to msg.sender (claimer), NOT deposit.owner
// Per documented permission model (see lines 56-64), the contributor (msg.sender) receives
// voting power, preserving claimer autonomy. The owner check here is an additional security
// layer to ensure fund sources are also eligible, closing the bypass vector identified in
// Cantina Finding #127 where delisted owners could use allowlisted claimers as proxies.
// Explicit fund source check: Verify deposit owner is also eligible for this mechanism
// Assumes mechanism implements canSignup() (OctantQFMechanism interface)
bool ownerCanSignup = OctantQFMechanism(payable(_allocationMechanismAddress)).canSignup(deposit.owner);
if (!ownerCanSignup) {
revert DepositOwnerNotEligibleForMechanism(_allocationMechanismAddress, deposit.owner);
}
_checkpointGlobalReward();
_checkpointReward(deposit);
uint256 unclaimedAmount = deposit.scaledUnclaimedRewardCheckpoint / SCALE_FACTOR;
require(_amount <= unclaimedAmount, CantAfford(_amount, unclaimedAmount));
// Special case: Allow zero-amount contributions to enable users to register for voting
// without contributing funds. This is useful for participation-only scenarios where
// users want to signal support without financial commitment.
if (_amount == 0) {
emit RewardContributed(_depositId, msg.sender, _allocationMechanismAddress, 0);
TokenizedAllocationMechanism(_allocationMechanismAddress).signupOnBehalfWithSignature(
msg.sender, // Claimer/contributor receives voting power and provides signature
0,
_deadline,
_v,
_r,
_s
);
return 0;
}
amountContributedToAllocationMechanism = _amount;
_consumeRewards(deposit, _amount);
// Defensive earning power update - maintaining consistency with base Staker pattern
uint256 _oldEarningPower = deposit.earningPower;
uint256 _newEarningPower = earningPowerCalculator.getEarningPower(
deposit.balance,
deposit.owner,
deposit.delegatee
);
// Update earning power totals before modifying deposit state
totalEarningPower = _calculateTotalEarningPower(_oldEarningPower, _newEarningPower, totalEarningPower);
depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
_oldEarningPower,
_newEarningPower,
depositorTotalEarningPower[deposit.owner]
);
deposit.earningPower = _newEarningPower.toUint96();
emit RewardClaimed(_depositId, msg.sender, amountContributedToAllocationMechanism, _newEarningPower);
// approve the allocation mechanism to spend the rewards
SafeERC20.safeIncreaseAllowance(
REWARD_TOKEN,
_allocationMechanismAddress,
amountContributedToAllocationMechanism
);
emit RewardContributed(
_depositId,
msg.sender,
_allocationMechanismAddress,
amountContributedToAllocationMechanism
);
TokenizedAllocationMechanism(_allocationMechanismAddress).signupOnBehalfWithSignature(
msg.sender, // Claimer/contributor receives voting power and provides signature
amountContributedToAllocationMechanism,
_deadline,
_v,
_r,
_s
);
// check that allowance is zero
require(REWARD_TOKEN.allowance(address(this), _allocationMechanismAddress) == 0, "allowance not zero");
return amountContributedToAllocationMechanism;
}
/// @notice Compounds rewards by claiming them and immediately restaking them into the same deposit
/// @dev REQUIREMENT: Only works when REWARD_TOKEN == STAKE_TOKEN, otherwise reverts.
/// @dev EARNING POWER: Compounding updates earning power based on new total balance.
/// @dev GAS OPTIMIZATION: More efficient than separate claim + stake operations.
/// @dev CLAIMER PERMISSIONS: This function grants claimers the ability to increase deposit stakes
/// through compounding. This is INTENDED BEHAVIOR - when an owner designates a claimer, they
/// explicitly trust them with both reward claiming AND limited staking operations (compounding).
/// Claimers cannot withdraw funds or alter deposit parameters, maintaining security boundaries.
/// @dev STAKER ACCESS: The deposit OWNER (not the caller/claimer) must pass stakerAccessMode checks.
/// If ALLOWSET mode active, owner must be in allowset. If BLOCKSET mode active, owner must not
/// be in blockset. Claimer's access status is not checked.
/// @dev Requires contract not paused and uses reentrancy guard
/// @param _depositId Deposit to compound rewards for
/// @return compoundedAmount Amount of rewards compounded (returns 0 if no unclaimed rewards available)
function compoundRewards(
DepositIdentifier _depositId
) external virtual whenNotPaused nonReentrant returns (uint256 compoundedAmount) {
if (address(REWARD_TOKEN) != address(STAKE_TOKEN)) {
revert CompoundingNotSupported();
}
Deposit storage deposit = deposits[_depositId];
address depositOwner = deposit.owner;
if (deposit.claimer != msg.sender && depositOwner != msg.sender) {
revert Staker__Unauthorized("not claimer or owner", msg.sender);
}
_checkStakerAccess(depositOwner);
_checkpointGlobalReward();
_checkpointReward(deposit);
uint256 unclaimedAmount = deposit.scaledUnclaimedRewardCheckpoint / SCALE_FACTOR;
if (unclaimedAmount == 0) {
return 0;
}
compoundedAmount = unclaimedAmount;
uint256 tempEarningPower = earningPowerCalculator.getEarningPower(
deposit.balance,
deposit.owner,
deposit.delegatee
);
uint256 newBalance = deposit.balance + compoundedAmount;
uint256 oldEarningPower = deposit.earningPower; // Save old earning power for event
uint256 newEarningPower = earningPowerCalculator.getEarningPower(newBalance, deposit.owner, deposit.delegatee);
totalEarningPower = _calculateTotalEarningPower(oldEarningPower, newEarningPower, totalEarningPower);
depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
oldEarningPower,
newEarningPower,
depositorTotalEarningPower[deposit.owner]
);
totalStaked += compoundedAmount;
depositorTotalStaked[depositOwner] += compoundedAmount;
_consumeRewards(deposit, unclaimedAmount);
deposit.balance = newBalance.toUint96();
deposit.earningPower = newEarningPower.toUint96();
// Transfer compounded rewards using the same pattern as _stakeMore for consistency
// The surrogate must already exist since the deposit exists (created during initial stake)
// This ensures child contracts can customize behavior through _stakeTokenSafeTransferFrom
DelegationSurrogate _surrogate = surrogates(deposit.delegatee);
if (address(_surrogate) == address(0)) {
revert SurrogateNotFound(deposit.delegatee);
}
_stakeTokenSafeTransferFrom(address(this), address(_surrogate), compoundedAmount);
emit RewardClaimed(_depositId, msg.sender, compoundedAmount, tempEarningPower);
emit StakeDeposited(depositOwner, _depositId, compoundedAmount, newBalance, newEarningPower);
_revertIfMinimumStakeAmountNotMet(_depositId);
return compoundedAmount;
}
/// @notice Internal helper to check minimum stake amount
/// @dev Reverts if balance is below minimum and not zero
/// Exception: Zero balance is allowed (permits full withdrawal to 0)
/// @param _depositId Deposit to check eligibility for
function _revertIfMinimumStakeAmountNotMet(DepositIdentifier _depositId) internal view {
Deposit storage deposit = deposits[_depositId];
if (deposit.balance < sharedState.minimumStakeAmount && deposit.balance > 0) {
revert MinimumStakeAmountNotMet(sharedState.minimumStakeAmount, deposit.balance);
}
}
function _checkStakerAccess(address user) internal view {
if (sharedState.stakerAccessMode == AccessMode.ALLOWSET) {
if (!sharedState.stakerAllowset.contains(user)) {
revert StakerNotAllowed(user);
}
} else if (sharedState.stakerAccessMode == AccessMode.BLOCKSET) {
if (sharedState.stakerBlockset.contains(user)) {
revert StakerBlocked(user);
}
}
}
/// @notice Atomically updates deposit checkpoint and totalClaimedRewards
/// @dev Ensures consistent state updates when rewards are consumed
/// @param _deposit Deposit storage reference to update
/// @param _amount Amount of rewards being claimed
function _consumeRewards(Deposit storage _deposit, uint256 _amount) internal {
if (_amount > 0) {
uint256 scaledAmount = _amount * SCALE_FACTOR;
_deposit.scaledUnclaimedRewardCheckpoint = _deposit.scaledUnclaimedRewardCheckpoint - scaledAmount;
totalClaimedRewards = totalClaimedRewards + _amount;
}
}
/// @notice Pauses reward streaming during idle windows (when `totalEarningPower == 0`) by
/// extending `rewardEndTime` by the idle duration; no rewards accrue while idle.
/// @dev When earning power is non-zero, accrues `rewardPerTokenAccumulatedCheckpoint` as usual.
function _checkpointGlobalReward() internal virtual override {
uint256 lastDistributed = lastTimeRewardDistributed();
uint256 elapsed = lastDistributed - lastCheckpointTime;
if (elapsed > 0 && scaledRewardRate != 0) {
if (totalEarningPower == 0) {
rewardEndTime += elapsed;
} else {
rewardPerTokenAccumulatedCheckpoint += (scaledRewardRate * elapsed) / totalEarningPower;
}
}
lastCheckpointTime = lastDistributed;
}
// === Overridden Functions ===
/// @inheritdoc Staker
/// @dev Overrides to block changes during active reward periods
function setEarningPowerCalculator(address _newEarningPowerCalculator) external virtual override {
_revertIfNotAdmin();
require(block.timestamp > rewardEndTime, CannotChangeEarningPowerCalculatorDuringActiveReward());
_setEarningPowerCalculator(_newEarningPowerCalculator);
}
/// @notice Prevents staking 0, staking below the minimum, staking when paused, and unauthorized staking.
/// @dev Uses reentrancy guard
/// @param _depositor Address making the deposit
/// @param _amount Amount to stake
/// @param _delegatee Address to receive voting power delegation
/// @param _claimer Address authorized to claim rewards
/// @return _depositId Deposit identifier for the created deposit
function _stake(
address _depositor,
uint256 _amount,
address _delegatee,
address _claimer
) internal virtual override whenNotPaused nonReentrant returns (DepositIdentifier _depositId) {
require(_amount > 0, ZeroOperation());
_checkStakerAccess(_depositor);
_depositId = super._stake(_depositor, _amount, _delegatee, _claimer);
_revertIfMinimumStakeAmountNotMet(_depositId);
}
/// @notice Prevents withdrawing 0; prevents withdrawals that drop balance below minimum.
/// @dev USER PROTECTION: Withdrawals remain enabled even when contract is paused to ensure
/// users can always access their principal funds.
/// @dev Uses reentrancy guard
/// @param deposit Deposit storage reference
/// @param _depositId Deposit identifier
/// @param _amount Amount to withdraw
function _withdraw(
Deposit storage deposit,
DepositIdentifier _depositId,
uint256 _amount
) internal virtual override nonReentrant {
require(_amount > 0, ZeroOperation());
super._withdraw(deposit, _depositId, _amount);
_revertIfMinimumStakeAmountNotMet(_depositId);
}
/// @notice Overrides to add reentrancy protection.
/// @dev Uses reentrancy guard
/// @param deposit Deposit storage reference
/// @param _depositId Deposit identifier
/// @param _newDelegatee Address to receive voting power delegation
function _alterDelegatee(
Deposit storage deposit,
DepositIdentifier _depositId,
address _newDelegatee
) internal virtual override whenNotPaused nonReentrant {
super._alterDelegatee(deposit, _depositId, _newDelegatee);
}
/// @notice Overrides to add reentrancy protection.
/// @dev Uses reentrancy guard
/// @param deposit Deposit storage reference
/// @param _depositId Deposit identifier
/// @param _newClaimer Address authorized to claim rewards
function _alterClaimer(
Deposit storage deposit,
DepositIdentifier _depositId,
address _newClaimer
) internal virtual override whenNotPaused nonReentrant {
super._alterClaimer(deposit, _depositId, _newClaimer);
}
/// @notice Overrides to add pause protection and track totalClaimedRewards for balance validation
/// @dev Reuses base Staker logic (with fee=0) and adds totalClaimedRewards tracking
/// @dev nonReentrant protects against reentrancy despite updating totalClaimedRewards after transfer
/// @param _depositId Deposit identifier
/// @param deposit Deposit storage reference
/// @param _claimer Address authorized to claim rewards
/// @return Claimed amount in reward token base units
function _claimReward(
DepositIdentifier _depositId,
Deposit storage deposit,
address _claimer
) internal virtual override whenNotPaused nonReentrant returns (uint256) {
uint256 _claimedAmount = super._claimReward(_depositId, deposit, _claimer);
totalClaimedRewards += _claimedAmount;
return _claimedAmount;
}
/// @notice Override notifyRewardAmount to use custom reward duration
/// @dev nonReentrant as a belts-and-braces guard against exotic ERC20 callback reentry
/// @param _amount Reward amount in reward token base units
function notifyRewardAmount(uint256 _amount) external virtual override nonReentrant {
uint256 requiredBalance = _validateAndGetRequiredBalance(_amount);
_notifyRewardAmountWithCustomDuration(_amount, requiredBalance);
}
/// @notice Validates sufficient reward token balance and returns the required balance
/// @dev Virtual function allowing variants to implement appropriate balance checks
/// @param _amount Reward amount in reward token base units being added
/// @return required Required balance for this variant in reward token base units
function _validateAndGetRequiredBalance(uint256 _amount) internal view virtual returns (uint256 required) {
uint256 currentBalance = REWARD_TOKEN.balanceOf(address(this));
// For variants with surrogates: stakes are NOT in main contract
// Only track rewards obligations: outstanding rewards + new amount
uint256 carryOverAmount = totalRewards - totalClaimedRewards;
required = carryOverAmount + _amount;
if (currentBalance < required) {
revert InsufficientRewardBalance(currentBalance, required);
}
return required;
}
/// @notice Prevents staking more when paused or by unauthorized owners; ensures non-zero amount and final balance meets minimum.
/// @dev Uses reentrancy guard; validates deposit.owner against staker access control before proceeding
/// @param deposit Deposit storage reference
/// @param _depositId Deposit identifier
/// @param _amount Additional stake amount in stake token base units
function _stakeMore(
Deposit storage deposit,
DepositIdentifier _depositId,
uint256 _amount
) internal virtual override whenNotPaused nonReentrant {
require(_amount > 0, ZeroOperation());
_checkStakerAccess(deposit.owner);
super._stakeMore(deposit, _depositId, _amount);
_revertIfMinimumStakeAmountNotMet(_depositId);
}
/// @notice Override to add nonReentrant modifier and fix checks-effects-interactions pattern
/// @dev Adds reentrancy protection and corrects state update ordering
/// @dev Updates state BEFORE external transfer to prevent reentrancy vulnerabilities
/// @param _depositId Deposit identifier to bump earning power for
/// @param _tipReceiver Address receiving tip for updating earning power
/// @param _requestedTip Tip amount requested in reward token base units
function bumpEarningPower(
DepositIdentifier _depositId,
address _tipReceiver,
uint256 _requestedTip
) public virtual override whenNotPaused nonReentrant {
if (_requestedTip > maxBumpTip) revert Staker__InvalidTip();
Deposit storage deposit = deposits[_depositId];
_checkpointGlobalReward();
_checkpointReward(deposit);
uint256 _unclaimedRewards = deposit.scaledUnclaimedRewardCheckpoint / SCALE_FACTOR;
(uint256 _newEarningPower, bool _isQualifiedForBump) = earningPowerCalculator.getNewEarningPower(
deposit.balance,
deposit.owner,
deposit.delegatee,
deposit.earningPower
);
if (!_isQualifiedForBump || _newEarningPower == deposit.earningPower) {
revert Staker__Unqualified(_newEarningPower);
}
if (_newEarningPower > deposit.earningPower && _unclaimedRewards < _requestedTip) {
revert Staker__InsufficientUnclaimedRewards();
}
uint256 tipToPay = _requestedTip;
if (_requestedTip > _unclaimedRewards) {
tipToPay = _unclaimedRewards;
}
emit EarningPowerBumped(_depositId, deposit.earningPower, _newEarningPower, msg.sender, _tipReceiver, tipToPay);
// Update global earning power & deposit earning power based on this bump
totalEarningPower = _calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
deposit.earningPower,
_newEarningPower,
depositorTotalEarningPower[deposit.owner]
);
deposit.earningPower = _newEarningPower.toUint96();
// CRITICAL: Update state BEFORE external call (checks-effects-interactions pattern)
// This prevents reentrancy attacks via malicious reward tokens with callbacks
_consumeRewards(deposit, tipToPay);
// External call AFTER all state updates. Some ERC20 tokens revert on zero-value transfers,
// so skip the call entirely when no tip is due. This also prevents unnecessary gas consumption for zero-value transfers.
if (tipToPay > 0) {
SafeERC20.safeTransfer(REWARD_TOKEN, _tipReceiver, tipToPay);
}
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.25;
// Global constants and enums used across Octant contracts
// ══════════════════════════════════════════════════════════════════════════════
// SENTINEL VALUES
// ══════════════════════════════════════════════════════════════════════════════
// Sentinel value representing native ETH (address(0) for ETH instead of ERC20)
address constant NATIVE_TOKEN = address(0);
// ══════════════════════════════════════════════════════════════════════════════
// EVM / PROTOCOL CONSTANTS
// ══════════════════════════════════════════════════════════════════════════════
// EIP-7825 per-transaction gas limit (2^24 = 16,777,216)
// Used for gas profiling DAO proposals to ensure they fit within limits
uint256 constant EIP_7825_TX_GAS_LIMIT = 16_777_216;
// ══════════════════════════════════════════════════════════════════════════════
// MAINNET TOKEN ADDRESSES
// ══════════════════════════════════════════════════════════════════════════════
// USDC token address on Ethereum mainnet
address constant USDC_MAINNET = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
// ══════════════════════════════════════════════════════════════════════════════
// OCTANT DEPLOYED CONTRACTS (MAINNET)
// ══════════════════════════════════════════════════════════════════════════════
// Morpho Compounder Strategy Factory on Ethereum mainnet (V1 - no symbol param)
address constant MORPHO_STRATEGY_FACTORY_MAINNET = 0x052d20B0e0b141988bD32772C735085e45F357c1;
// Morpho Compounder Strategy Factory V2 on Ethereum mainnet (with symbol param)
// Deployed 2025-01-22: https://etherscan.io/tx/0x21da599d0259e3d4caf6f0510598630a66a290099afb105a43b0c2a4d96e7c08
address constant MORPHO_STRATEGY_FACTORY_V2_MAINNET = 0xd8Df22cB3c3876487961aC2500889664632674d7;
// ══════════════════════════════════════════════════════════════════════════════
// EXTERNAL PROTOCOL ADDRESSES (MAINNET)
// ══════════════════════════════════════════════════════════════════════════════
// Yearn TokenizedStrategy singleton on Ethereum mainnet (yield-donating variant, V1 - no symbol param)
address constant YIELD_DONATING_TOKENIZED_STRATEGY_MAINNET = 0xb27064A2C51b8C5b39A5Bb911AD34DB039C3aB9c;
// YieldDonatingTokenizedStrategy V2 on Ethereum mainnet (with symbol param)
// Deployed 2025-01-22: https://etherscan.io/tx/0xa8b239d1302d650cd0dc2c3b0a2b9f1bdbb85d24e01d666f7b55c6cef76a87c4
address constant YIELD_DONATING_TOKENIZED_STRATEGY_V2_MAINNET = 0xea648c313b497fECfBC629e73cB61Db34181F067;
// Gnosis Safe MultiSendCallOnly canonical deployment on Ethereum mainnet
// Used for batching multiple transactions in a single Safe execution
address constant SAFE_MULTISEND_MAINNET = 0x40A2aCCbd92BCA938b02010E17A5b8929b49130D;
// ══════════════════════════════════════════════════════════════════════════════
// ENUMS
// ══════════════════════════════════════════════════════════════════════════════
/**
* @notice Access control modes for address set validation
* @dev Used by LinearAllowanceExecutor and RegenStaker
*/
enum AccessMode {
NONE, // No access control (permissionless)
ALLOWSET, // Only addresses in allowset are permitted
BLOCKSET // All addresses except those in blockset are permitted
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_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 Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(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.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
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 Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity 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 {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
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 silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.20;
import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.
*
* The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
* encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
* does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
* produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
* separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
* separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
*
* @custom:oz-upgrades-unsafe-allow state-variable-immutable
*/
abstract contract EIP712 is IERC5267 {
using ShortStrings for *;
bytes32 private constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _cachedDomainSeparator;
uint256 private immutable _cachedChainId;
address private immutable _cachedThis;
bytes32 private immutable _hashedName;
bytes32 private immutable _hashedVersion;
ShortString private immutable _name;
ShortString private immutable _version;
// slither-disable-next-line constable-states
string private _nameFallback;
// slither-disable-next-line constable-states
string private _versionFallback;
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) {
_name = name.toShortStringWithFallback(_nameFallback);
_version = version.toShortStringWithFallback(_versionFallback);
_hashedName = keccak256(bytes(name));
_hashedVersion = keccak256(bytes(version));
_cachedChainId = block.chainid;
_cachedDomainSeparator = _buildDomainSeparator();
_cachedThis = address(this);
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
return _cachedDomainSeparator;
} else {
return _buildDomainSeparator();
}
}
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
}
/**
* @inheritdoc IERC5267
*/
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
return (
hex"0f", // 01111
_EIP712Name(),
_EIP712Version(),
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
/**
* @dev The name parameter for the EIP712 domain.
*
* NOTE: By default this function reads _name which is an immutable value.
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
*/
// solhint-disable-next-line func-name-mixedcase
function _EIP712Name() internal view returns (string memory) {
return _name.toStringWithFallback(_nameFallback);
}
/**
* @dev The version parameter for the EIP712 domain.
*
* NOTE: By default this function reads _version which is an immutable value.
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
*/
// solhint-disable-next-line func-name-mixedcase
function _EIP712Version() internal view returns (string memory) {
return _version.toStringWithFallback(_versionFallback);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @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 EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* 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 ReentrancyGuard {
// 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;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_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 {
// 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 {
// 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) {
return _status == ENTERED;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC-20 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: AGPL-3.0-only
pragma solidity ^0.8.23;
import {DelegationSurrogate} from "./DelegationSurrogate.sol";
import {INotifiableRewardReceiver} from "./interfaces/INotifiableRewardReceiver.sol";
import {IEarningPowerCalculator} from "./interfaces/IEarningPowerCalculator.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
/// @title Staker
/// @author [ScopeLift](https://scopelift.co)
/// @notice This contract manages the distribution of rewards to stakers. Rewards are denominated
/// in an ERC20 token and sent to the contract by authorized reward notifiers. To stake means to
/// deposit a designated, delegable ERC20 governance token and leave it over a period of time.
/// The contract allows stakers to delegate the voting power of the tokens they stake to any
/// governance delegatee on a per deposit basis. The contract also allows stakers to designate the
/// claimer address that earns rewards for the associated deposit.
///
/// The staking mechanism of this contract is directly inspired by the Synthetix StakingRewards.sol
/// implementation. The core mechanic involves the streaming of rewards over a designated period
/// of time. Each staker earns rewards proportional to their share of the total stake, and each
/// staker earns only while their tokens are staked. Stakers may add or withdraw their stake at any
/// point. Claimers can claim the rewards they've earned at any point. When a new reward is
/// received, the reward duration restarts, and the rate at which rewards are streamed is updated
/// to include the newly received rewards along with any remaining rewards that have finished
/// streaming since the last time a reward was received.
///
/// The rate at which a depositor earns rewards is proportional to their earning power. Earning
/// power is based on the amount the depositor has staked and the activity of their delegatee.
/// The calculation of earning power is handled by a separate module called the earning power
/// calculator. This module is set by the owner, and can be updated by the owner. If the owner of
/// the Staker contract is a DAO, which is the expected common case, this means the DAO has
/// the ability to define and iterate on its own definition of active, aligned participation,
/// and to decide how to reward it.
abstract contract Staker is INotifiableRewardReceiver, Multicall {
using SafeCast for uint256;
/// @notice A unique identifier assigned to each deposit.
type DepositIdentifier is uint256;
/// @notice Emitted when stake is deposited by a depositor, either to a new deposit or one that
/// already exists.
event StakeDeposited(
address owner,
DepositIdentifier indexed depositId,
uint256 amount,
uint256 depositBalance,
uint256 earningPower
);
/// @notice Emitted when a depositor withdraws some portion of stake from a given deposit.
event StakeWithdrawn(
address owner,
DepositIdentifier indexed depositId,
uint256 amount,
uint256 depositBalance,
uint256 earningPower
);
/// @notice Emitted when a deposit's delegatee is changed.
event DelegateeAltered(
DepositIdentifier indexed depositId,
address oldDelegatee,
address newDelegatee,
uint256 earningPower
);
/// @notice Emitted when a deposit's claimer is changed.
event ClaimerAltered(
DepositIdentifier indexed depositId,
address indexed oldClaimer,
address indexed newClaimer,
uint256 earningPower
);
/// @notice Emitted when a claimer claims their earned reward.
event RewardClaimed(
DepositIdentifier indexed depositId,
address indexed claimer,
uint256 amount,
uint256 earningPower
);
/// @notice Emitted when this contract is notified of a new reward.
event RewardNotified(uint256 amount, address notifier);
/// @notice Emitted when the admin address is set.
event AdminSet(address indexed oldAdmin, address indexed newAdmin);
/// @notice Emitted when the earning power calculator address is set.
event EarningPowerCalculatorSet(
address indexed oldEarningPowerCalculator, address indexed newEarningPowerCalculator
);
/// @notice Emitted when the max bump tip is modified.
event MaxBumpTipSet(uint256 oldMaxBumpTip, uint256 newMaxBumpTip);
/// @notice Emitted when the claim fee parameters are modified.
event ClaimFeeParametersSet(
uint96 oldFeeAmount, uint96 newFeeAmount, address oldFeeCollector, address newFeeCollector
);
/// @notice Emitted when a reward notifier address is enabled or disabled.
event RewardNotifierSet(address indexed account, bool isEnabled);
/// @notice Emitted when a deposit's earning power is changed via bumping.
event EarningPowerBumped(
DepositIdentifier indexed depositId,
uint256 oldEarningPower,
uint256 newEarningPower,
address bumper,
address tipReceiver,
uint256 tipAmount
);
/// @notice Thrown when an account attempts a call for which it lacks appropriate permission.
/// @param reason Human readable code explaining why the call is unauthorized.
/// @param caller The address that attempted the unauthorized call.
error Staker__Unauthorized(bytes32 reason, address caller);
/// @notice Thrown if the new rate after a reward notification would be zero.
error Staker__InvalidRewardRate();
/// @notice Thrown if the following invariant is broken after a new reward: the contract should
/// always have a reward balance sufficient to distribute at the reward rate across the reward
/// duration.
error Staker__InsufficientRewardBalance();
/// @notice Thrown if the unclaimed rewards are insufficient to cover a bumper's requested tip,
/// or in the case of an earning power decrease the tip of a subsequent earning power increase.
error Staker__InsufficientUnclaimedRewards();
/// @notice Thrown if a caller attempts to specify address zero for certain designated addresses.
error Staker__InvalidAddress();
/// @notice Thrown if a bumper's requested tip is invalid.
error Staker__InvalidTip();
/// @notice Thrown if the claim fee parameters are outside permitted bounds.
error Staker__InvalidClaimFeeParameters();
/// @notice Thrown when an onBehalf method is called with a deadline that has expired.
error Staker__ExpiredDeadline();
/// @notice Thrown if a caller supplies an invalid signature to a method that requires one.
error Staker__InvalidSignature();
/// @notice Thrown if an earning power update is unqualified to be bumped.
/// @param score The would-be new earning power which did not qualify.
error Staker__Unqualified(uint256 score);
/// @notice Metadata associated with a discrete staking deposit.
/// @param balance The deposit's staked balance.
/// @param owner The owner of this deposit.
/// @param delegatee The governance delegate who receives the voting weight for this deposit.
/// @param claimer The address which has the right to withdraw rewards earned by this
/// deposit.
/// @param earningPower The "power" this deposit has as it pertains to earning rewards, which
/// accrue to this deposit at a rate proportional to its share of the total earning power of the
/// system.
/// @param rewardPerTokenCheckpoint Checkpoint of the reward per token accumulator for this
/// deposit. It represents the value of the global accumulator at the last time a given deposit's
/// rewards were calculated and stored. The difference between the global value and this value
/// can be used to calculate the interim rewards earned by given deposit.
/// @param scaledUnclaimedRewardCheckpoint Checkpoint of the unclaimed rewards earned by a given
/// deposit with the scale factor included. This value is stored any time an action is taken that
/// specifically impacts the rate at which rewards are earned by a given deposit. Total unclaimed
/// rewards for a deposit are thus this value plus all rewards earned after this checkpoint was
/// taken. This value is reset to zero when the deposit's rewards are claimed.
struct Deposit {
uint96 balance;
address owner;
uint96 earningPower;
address delegatee;
address claimer;
uint256 rewardPerTokenCheckpoint;
uint256 scaledUnclaimedRewardCheckpoint;
}
/// @notice Parameters associated with the fee assessed when rewards are claimed.
/// @param feeAmount The absolute amount of the reward token that is taken as a fee when rewards
/// are claimed for a given deposit.
/// @param feeCollector The address to which reward token fees are sent.
struct ClaimFeeParameters {
uint96 feeAmount;
address feeCollector;
}
/// @notice ERC20 token in which rewards are denominated and distributed.
IERC20 public immutable REWARD_TOKEN;
/// @notice Delegable governance token which users stake to earn rewards.
IERC20 public immutable STAKE_TOKEN;
/// @notice Length of time over which rewards sent to this contract are distributed to stakers.
uint256 public constant REWARD_DURATION = 30 days;
/// @notice Scale factor used in reward calculation math to reduce rounding errors caused by
/// truncation during division.
uint256 public constant SCALE_FACTOR = 1e36;
/// @notice The maximum value to which the claim fee can be set.
/// @dev For anything other than a zero value, this immutable parameter should be set in the
/// constructor of a concrete implementation inheriting from Staker.
uint256 public immutable MAX_CLAIM_FEE;
/// @dev Unique identifier that will be used for the next deposit.
DepositIdentifier private nextDepositId;
/// @notice Permissioned actor that can enable/disable `rewardNotifier` addresses, set the max
/// bump tip, set the claim fee parameters, and update the earning power calculator.
address public admin;
/// @notice Maximum tip a bumper can request.
uint256 public maxBumpTip;
/// @notice Global amount currently staked across all deposits.
uint256 public totalStaked;
/// @notice Global amount of earning power for all deposits.
uint256 public totalEarningPower;
/// @notice Contract that determines a deposit's earning power based on their delegatee.
/// @dev An earning power calculator should take into account that a deposit's earning power is a
/// uint96. There may be overflow issues within governance staker if this is not taken into
/// account. Also, there should be some mechanism to prevent the deposit from frequently being
/// bumpable: if earning power changes frequently, this will eat into a users unclaimed rewards.
IEarningPowerCalculator public earningPowerCalculator;
/// @notice Tracks the total staked by a depositor across all unique deposits.
mapping(address depositor => uint256 amount) public depositorTotalStaked;
/// @notice Tracks the total earning power by a depositor across all unique deposits.
mapping(address depositor => uint256 earningPower) public depositorTotalEarningPower;
/// @notice Stores the metadata associated with a given deposit.
mapping(DepositIdentifier depositId => Deposit deposit) public deposits;
/// @notice Time at which rewards distribution will complete if there are no new rewards.
uint256 public rewardEndTime;
/// @notice Last time at which the global rewards accumulator was updated.
uint256 public lastCheckpointTime;
/// @notice Global rate at which rewards are currently being distributed to stakers,
/// denominated in scaled reward tokens per second, using the SCALE_FACTOR.
uint256 public scaledRewardRate;
/// @notice Checkpoint value of the global reward per token accumulator.
uint256 public rewardPerTokenAccumulatedCheckpoint;
/// @notice Maps addresses to whether they are authorized to call `notifyRewardAmount`.
mapping(address rewardNotifier => bool) public isRewardNotifier;
/// @notice Current configuration parameters for the fee assessed on claiming.
ClaimFeeParameters public claimFeeParameters;
/// @param _rewardToken ERC20 token in which rewards will be denominated.
/// @param _stakeToken Delegable governance token which users will stake to earn rewards.
/// @param _earningPowerCalculator The contract that will serve as the initial calculator of
/// earning power for the staker system.
/// @param _admin Address which will have permission to manage reward notifiers, claim fee
/// parameters, the max bump tip, and the reward calculator.
constructor(
IERC20 _rewardToken,
IERC20 _stakeToken,
IEarningPowerCalculator _earningPowerCalculator,
uint256 _maxBumpTip,
address _admin
) {
REWARD_TOKEN = _rewardToken;
STAKE_TOKEN = _stakeToken;
_setAdmin(_admin);
_setMaxBumpTip(_maxBumpTip);
_setEarningPowerCalculator(address(_earningPowerCalculator));
}
/// @notice Set the admin address.
/// @param _newAdmin Address of the new admin.
/// @dev Caller must be the current admin.
function setAdmin(address _newAdmin) external virtual {
_revertIfNotAdmin();
_setAdmin(_newAdmin);
}
/// @notice Set the earning power calculator address.
function setEarningPowerCalculator(address _newEarningPowerCalculator) external virtual {
_revertIfNotAdmin();
_setEarningPowerCalculator(_newEarningPowerCalculator);
}
/// @notice Set the max bump tip.
/// @param _newMaxBumpTip Value of the new max bump tip.
/// @dev Caller must be the current admin.
function setMaxBumpTip(uint256 _newMaxBumpTip) external virtual {
_revertIfNotAdmin();
_setMaxBumpTip(_newMaxBumpTip);
}
/// @notice Enables or disables a reward notifier address.
/// @param _rewardNotifier Address of the reward notifier.
/// @param _isEnabled `true` to enable the `_rewardNotifier`, or `false` to disable.
/// @dev Caller must be the current admin.
function setRewardNotifier(address _rewardNotifier, bool _isEnabled) external virtual {
_revertIfNotAdmin();
isRewardNotifier[_rewardNotifier] = _isEnabled;
emit RewardNotifierSet(_rewardNotifier, _isEnabled);
}
/// @notice Updates the parameters related to the claim fee.
/// @param _params The new fee parameters.
/// @dev Caller must be current admin.
function setClaimFeeParameters(ClaimFeeParameters memory _params) external virtual {
_revertIfNotAdmin();
_setClaimFeeParameters(_params);
}
/// @notice A method to get the delegation surrogate contract for a given delegate.
/// @param _delegatee The address to which the delegation surrogate is delegating voting power.
/// @return The delegation surrogate.
/// @dev A concrete implementation should return a delegate surrogate address for a given
/// delegatee. In practice this may be as simple as returning an address stored in a mapping or
/// computing its create2 address.
function surrogates(address _delegatee) public view virtual returns (DelegationSurrogate);
/// @notice Timestamp representing the last time at which rewards have been distributed, which is
/// either the current timestamp (because rewards are still actively being streamed) or the time
/// at which the reward duration ended (because all rewards to date have already been streamed).
/// @return Timestamp representing the last time at which rewards have been distributed.
function lastTimeRewardDistributed() public view virtual returns (uint256) {
if (rewardEndTime <= block.timestamp) return rewardEndTime;
else return block.timestamp;
}
/// @notice Live value of the global reward per token accumulator. It is the sum of the last
/// checkpoint value with the live calculation of the value that has accumulated in the interim.
/// This number should monotonically increase over time as more rewards are distributed.
/// @return Live value of the global reward per token accumulator.
function rewardPerTokenAccumulated() public view virtual returns (uint256) {
if (totalEarningPower == 0) return rewardPerTokenAccumulatedCheckpoint;
return rewardPerTokenAccumulatedCheckpoint
+ (scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime)) / totalEarningPower;
}
/// @notice Live value of the unclaimed rewards earned by a given deposit. It is the
/// sum of the last checkpoint value of the unclaimed rewards with the live calculation of the
/// rewards that have accumulated for this account in the interim. This value can only increase,
/// until it is reset to zero once the unearned rewards are claimed.
///
/// Note that the contract tracks the unclaimed rewards internally with the scale factor
/// included, in order to avoid the accrual of precision losses as users takes actions that
/// cause rewards to be checkpointed. This external helper method is useful for integrations, and
/// returns the value after it has been scaled down to the reward token's raw decimal amount.
/// @param _depositId Identifier of the deposit in question.
/// @return Live value of the unclaimed rewards earned by a given deposit.
function unclaimedReward(DepositIdentifier _depositId) external view virtual returns (uint256) {
return _scaledUnclaimedReward(deposits[_depositId]) / SCALE_FACTOR;
}
/// @notice Stake tokens to a new deposit. The caller must pre-approve the staking contract to
/// spend at least the would-be staked amount of the token.
/// @param _amount The amount of the staking token to stake.
/// @param _delegatee The address to assign the governance voting weight of the staked tokens.
/// @return _depositId The unique identifier for this deposit.
/// @dev The delegatee may not be the zero address. The deposit will be owned by the message
/// sender, and the claimer will also be the message sender.
function stake(uint256 _amount, address _delegatee)
external
virtual
returns (DepositIdentifier _depositId)
{
_depositId = _stake(msg.sender, _amount, _delegatee, msg.sender);
}
/// @notice Method to stake tokens to a new deposit. The caller must pre-approve the staking
/// contract to spend at least the would-be staked amount of the token.
/// @param _amount Quantity of the staking token to stake.
/// @param _delegatee Address to assign the governance voting weight of the staked tokens.
/// @param _claimer Address that will have the right to claim rewards for this stake.
/// @return _depositId Unique identifier for this deposit.
/// @dev Neither the delegatee nor the claimer may be the zero address. The deposit will be
/// owned by the message sender.
function stake(uint256 _amount, address _delegatee, address _claimer)
external
virtual
returns (DepositIdentifier _depositId)
{
_depositId = _stake(msg.sender, _amount, _delegatee, _claimer);
}
/// @notice Add more staking tokens to an existing deposit. A staker should call this method when
/// they have an existing deposit, and wish to stake more while retaining the same delegatee and
/// claimer.
/// @param _depositId Unique identifier of the deposit to which stake will be added.
/// @param _amount Quantity of stake to be added.
/// @dev The message sender must be the owner of the deposit.
function stakeMore(DepositIdentifier _depositId, uint256 _amount) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, msg.sender);
_stakeMore(deposit, _depositId, _amount);
}
/// @notice For an existing deposit, change the address to which governance voting power is
/// assigned.
/// @param _depositId Unique identifier of the deposit which will have its delegatee altered.
/// @param _newDelegatee Address of the new governance delegate.
/// @dev The new delegatee may not be the zero address. The message sender must be the owner of
/// the deposit.
function alterDelegatee(DepositIdentifier _depositId, address _newDelegatee) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, msg.sender);
_alterDelegatee(deposit, _depositId, _newDelegatee);
}
/// @notice For an existing deposit, change the claimer account which has the right to
/// withdraw staking rewards.
/// @param _depositId Unique identifier of the deposit which will have its claimer altered.
/// @param _newClaimer Address of the new claimer.
/// @dev The new claimer may not be the zero address. The message sender must be the owner of
/// the deposit.
function alterClaimer(DepositIdentifier _depositId, address _newClaimer) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, msg.sender);
_alterClaimer(deposit, _depositId, _newClaimer);
}
/// @notice Withdraw staked tokens from an existing deposit.
/// @param _depositId Unique identifier of the deposit from which stake will be withdrawn.
/// @param _amount Quantity of staked token to withdraw.
/// @dev The message sender must be the owner of the deposit. Stake is withdrawn to the message
/// sender's account.
function withdraw(DepositIdentifier _depositId, uint256 _amount) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, msg.sender);
_withdraw(deposit, _depositId, _amount);
}
/// @notice Claim reward tokens earned by a given deposit. Message sender must be the claimer
/// address of the deposit or the owner of the deposit. Tokens are sent to the caller.
/// @param _depositId Identifier of the deposit from which accrued rewards will be claimed.
/// @return Amount of reward tokens claimed, after the fee has been assessed.
function claimReward(DepositIdentifier _depositId) external virtual returns (uint256) {
Deposit storage deposit = deposits[_depositId];
if (deposit.claimer != msg.sender && deposit.owner != msg.sender) {
revert Staker__Unauthorized("not claimer or owner", msg.sender);
}
return _claimReward(_depositId, deposit, msg.sender);
}
/// @notice Called by an authorized rewards notifier to alert the staking contract that a new
/// reward has been transferred to it. It is assumed that the reward has already been
/// transferred to this staking contract before the rewards notifier calls this method.
/// @param _amount Quantity of reward tokens the staking contract is being notified of.
/// @dev It is critical that only well behaved contracts are approved by the admin to call this
/// method, for two reasons.
///
/// 1. A misbehaving contract could grief stakers by frequently notifying this contract of tiny
/// rewards, thereby continuously stretching out the time duration over which real rewards are
/// distributed. It is required that reward notifiers supply reasonable rewards at reasonable
/// intervals.
// 2. A misbehaving contract could falsely notify this contract of rewards that were not actually
/// distributed, creating a shortfall for those claiming their rewards after others. It is
/// required that a notifier contract always transfers the `_amount` to this contract before
/// calling this method.
function notifyRewardAmount(uint256 _amount) external virtual {
if (!isRewardNotifier[msg.sender]) revert Staker__Unauthorized("not notifier", msg.sender);
// We checkpoint the accumulator without updating the timestamp at which it was updated,
// because that second operation will be done after updating the reward rate.
rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();
if (block.timestamp >= rewardEndTime) {
scaledRewardRate = (_amount * SCALE_FACTOR) / REWARD_DURATION;
} else {
uint256 _remainingReward = scaledRewardRate * (rewardEndTime - block.timestamp);
scaledRewardRate = (_remainingReward + _amount * SCALE_FACTOR) / REWARD_DURATION;
}
rewardEndTime = block.timestamp + REWARD_DURATION;
lastCheckpointTime = block.timestamp;
if ((scaledRewardRate / SCALE_FACTOR) == 0) revert Staker__InvalidRewardRate();
// This check cannot _guarantee_ sufficient rewards have been transferred to the contract,
// because it cannot isolate the unclaimed rewards owed to stakers left in the balance. While
// this check is useful for preventing degenerate cases, it is not sufficient. Therefore, it is
// critical that only safe reward notifier contracts are approved to call this method by the
// admin.
if (
(scaledRewardRate * REWARD_DURATION) > (REWARD_TOKEN.balanceOf(address(this)) * SCALE_FACTOR)
) revert Staker__InsufficientRewardBalance();
emit RewardNotified(_amount, msg.sender);
}
/// @notice A function that a bumper can call to update a deposit's earning power when a
/// qualifying change in the earning power is returned by the earning power calculator. A
/// deposit's earning power may change as determined by the algorithm of the current earning power
/// calculator. In order to incentivize bumpers to trigger these updates a portion of deposit's
/// unclaimed rewards are sent to the bumper.
/// @param _depositId The identifier for the deposit that needs an updated earning power.
/// @param _tipReceiver The receiver of the reward for updating a deposit's earning power.
/// @param _requestedTip The amount of tip requested by the third-party.
function bumpEarningPower(
DepositIdentifier _depositId,
address _tipReceiver,
uint256 _requestedTip
) external virtual {
if (_requestedTip > maxBumpTip) revert Staker__InvalidTip();
Deposit storage deposit = deposits[_depositId];
_checkpointGlobalReward();
_checkpointReward(deposit);
uint256 _unclaimedRewards = deposit.scaledUnclaimedRewardCheckpoint / SCALE_FACTOR;
(uint256 _newEarningPower, bool _isQualifiedForBump) = earningPowerCalculator.getNewEarningPower(
deposit.balance, deposit.owner, deposit.delegatee, deposit.earningPower
);
if (!_isQualifiedForBump || _newEarningPower == deposit.earningPower) {
revert Staker__Unqualified(_newEarningPower);
}
if (_newEarningPower > deposit.earningPower && _unclaimedRewards < _requestedTip) {
revert Staker__InsufficientUnclaimedRewards();
}
// Note: underflow causes a revert if the requested tip is more than unclaimed rewards
if (_newEarningPower < deposit.earningPower && (_unclaimedRewards - _requestedTip) < maxBumpTip)
{
revert Staker__InsufficientUnclaimedRewards();
}
emit EarningPowerBumped(
_depositId, deposit.earningPower, _newEarningPower, msg.sender, _tipReceiver, _requestedTip
);
// Update global earning power & deposit earning power based on this bump
totalEarningPower =
_calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
);
deposit.earningPower = _newEarningPower.toUint96();
// Send tip to the receiver
SafeERC20.safeTransfer(REWARD_TOKEN, _tipReceiver, _requestedTip);
deposit.scaledUnclaimedRewardCheckpoint =
deposit.scaledUnclaimedRewardCheckpoint - (_requestedTip * SCALE_FACTOR);
}
/// @notice Live value of the unclaimed rewards earned by a given deposit with the
/// scale factor included. Used internally for calculating reward checkpoints while minimizing
/// precision loss.
/// @return Live value of the unclaimed rewards earned by a given deposit with the
/// scale factor included.
/// @dev See documentation for the public, non-scaled `unclaimedReward` method for more details.
function _scaledUnclaimedReward(Deposit storage deposit) internal view virtual returns (uint256) {
return deposit.scaledUnclaimedRewardCheckpoint
+ (deposit.earningPower * (rewardPerTokenAccumulated() - deposit.rewardPerTokenCheckpoint));
}
/// @notice Internal method which finds the existing surrogate contract—or deploys a new one if
/// none exists—for a given delegatee.
/// @param _delegatee Account for which a surrogate is sought.
/// @return _surrogate The address of the surrogate contract for the delegatee.
/// @dev A concrete implementation would either deploy a new delegate surrogate or return an
/// existing surrogate for a given delegatee address.
function _fetchOrDeploySurrogate(address _delegatee)
internal
virtual
returns (DelegationSurrogate _surrogate);
/// @notice Internal convenience method which calls the `transferFrom` method on the stake token
/// contract and reverts on failure.
/// @param _from Source account from which stake token is to be transferred.
/// @param _to Destination account of the stake token which is to be transferred.
/// @param _value Quantity of stake token which is to be transferred.
function _stakeTokenSafeTransferFrom(address _from, address _to, uint256 _value) internal virtual {
SafeERC20.safeTransferFrom(STAKE_TOKEN, _from, _to, _value);
}
/// @notice Internal method which generates and returns a unique, previously unused deposit
/// identifier.
/// @return _depositId Previously unused deposit identifier.
function _useDepositId() internal virtual returns (DepositIdentifier _depositId) {
_depositId = nextDepositId;
nextDepositId = DepositIdentifier.wrap(DepositIdentifier.unwrap(_depositId) + 1);
}
/// @notice Internal convenience methods which performs the staking operations.
/// @dev This method must only be called after proper authorization has been completed.
/// @dev See public stake methods for additional documentation.
function _stake(address _depositor, uint256 _amount, address _delegatee, address _claimer)
internal
virtual
returns (DepositIdentifier _depositId)
{
_revertIfAddressZero(_delegatee);
_revertIfAddressZero(_claimer);
_checkpointGlobalReward();
DelegationSurrogate _surrogate = _fetchOrDeploySurrogate(_delegatee);
_depositId = _useDepositId();
uint256 _earningPower = earningPowerCalculator.getEarningPower(_amount, _depositor, _delegatee);
totalStaked += _amount;
totalEarningPower += _earningPower;
depositorTotalStaked[_depositor] += _amount;
depositorTotalEarningPower[_depositor] += _earningPower;
deposits[_depositId] = Deposit({
balance: _amount.toUint96(),
owner: _depositor,
delegatee: _delegatee,
claimer: _claimer,
earningPower: _earningPower.toUint96(),
rewardPerTokenCheckpoint: rewardPerTokenAccumulatedCheckpoint,
scaledUnclaimedRewardCheckpoint: 0
});
_stakeTokenSafeTransferFrom(_depositor, address(_surrogate), _amount);
emit StakeDeposited(_depositor, _depositId, _amount, _amount, _earningPower);
emit ClaimerAltered(_depositId, address(0), _claimer, _earningPower);
emit DelegateeAltered(_depositId, address(0), _delegatee, _earningPower);
}
/// @notice Internal convenience method which adds more stake to an existing deposit.
/// @dev This method must only be called after proper authorization has been completed.
/// @dev See public stakeMore methods for additional documentation.
function _stakeMore(Deposit storage deposit, DepositIdentifier _depositId, uint256 _amount)
internal
virtual
{
_checkpointGlobalReward();
_checkpointReward(deposit);
DelegationSurrogate _surrogate = surrogates(deposit.delegatee);
uint256 _newBalance = deposit.balance + _amount;
uint256 _newEarningPower =
earningPowerCalculator.getEarningPower(_newBalance, deposit.owner, deposit.delegatee);
totalEarningPower =
_calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
totalStaked += _amount;
depositorTotalStaked[deposit.owner] += _amount;
depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
);
deposit.earningPower = _newEarningPower.toUint96();
deposit.balance = _newBalance.toUint96();
_stakeTokenSafeTransferFrom(deposit.owner, address(_surrogate), _amount);
emit StakeDeposited(deposit.owner, _depositId, _amount, _newBalance, _newEarningPower);
}
/// @notice Internal convenience method which alters the delegatee of an existing deposit.
/// @dev This method must only be called after proper authorization has been completed.
/// @dev See public alterDelegatee methods for additional documentation.
function _alterDelegatee(
Deposit storage deposit,
DepositIdentifier _depositId,
address _newDelegatee
) internal virtual {
_revertIfAddressZero(_newDelegatee);
_checkpointGlobalReward();
_checkpointReward(deposit);
DelegationSurrogate _oldSurrogate = surrogates(deposit.delegatee);
uint256 _newEarningPower =
earningPowerCalculator.getEarningPower(deposit.balance, deposit.owner, _newDelegatee);
totalEarningPower =
_calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
);
emit DelegateeAltered(_depositId, deposit.delegatee, _newDelegatee, _newEarningPower);
deposit.delegatee = _newDelegatee;
deposit.earningPower = _newEarningPower.toUint96();
DelegationSurrogate _newSurrogate = _fetchOrDeploySurrogate(_newDelegatee);
_stakeTokenSafeTransferFrom(address(_oldSurrogate), address(_newSurrogate), deposit.balance);
}
/// @notice Internal convenience method which alters the claimer of an existing deposit.
/// @dev This method must only be called after proper authorization has been completed.
/// @dev See public alterClaimer methods for additional documentation.
function _alterClaimer(Deposit storage deposit, DepositIdentifier _depositId, address _newClaimer)
internal
virtual
{
_revertIfAddressZero(_newClaimer);
_checkpointGlobalReward();
_checkpointReward(deposit);
// Updating the earning power here is not strictly necessary, but if the user is touching their
// deposit anyway, it seems reasonable to make sure their earning power is up to date.
uint256 _newEarningPower =
earningPowerCalculator.getEarningPower(deposit.balance, deposit.owner, deposit.delegatee);
totalEarningPower =
_calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
);
deposit.earningPower = _newEarningPower.toUint96();
emit ClaimerAltered(_depositId, deposit.claimer, _newClaimer, _newEarningPower);
deposit.claimer = _newClaimer;
}
/// @notice Internal convenience method which withdraws the stake from an existing deposit.
/// @dev This method must only be called after proper authorization has been completed.
/// @dev See public withdraw methods for additional documentation.
function _withdraw(Deposit storage deposit, DepositIdentifier _depositId, uint256 _amount)
internal
virtual
{
_checkpointGlobalReward();
_checkpointReward(deposit);
// overflow prevents withdrawing more than balance
uint256 _newBalance = deposit.balance - _amount;
uint256 _newEarningPower =
earningPowerCalculator.getEarningPower(_newBalance, deposit.owner, deposit.delegatee);
totalStaked -= _amount;
totalEarningPower =
_calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
depositorTotalStaked[deposit.owner] -= _amount;
depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
);
deposit.balance = _newBalance.toUint96();
deposit.earningPower = _newEarningPower.toUint96();
_stakeTokenSafeTransferFrom(address(surrogates(deposit.delegatee)), deposit.owner, _amount);
emit StakeWithdrawn(deposit.owner, _depositId, _amount, _newBalance, _newEarningPower);
}
/// @notice Internal convenience method which claims earned rewards.
/// @return Amount of reward tokens claimed, after the claim fee has been assessed.
/// @dev This method must only be called after proper authorization has been completed.
/// @dev See public claimReward methods for additional documentation.
function _claimReward(DepositIdentifier _depositId, Deposit storage deposit, address _claimer)
internal
virtual
returns (uint256)
{
_checkpointGlobalReward();
_checkpointReward(deposit);
uint256 _reward = deposit.scaledUnclaimedRewardCheckpoint / SCALE_FACTOR;
// Intentionally reverts due to overflow if unclaimed rewards are less than fee.
uint256 _payout = _reward - claimFeeParameters.feeAmount;
if (_payout == 0) return 0;
// retain sub-wei dust that would be left due to the precision loss
deposit.scaledUnclaimedRewardCheckpoint =
deposit.scaledUnclaimedRewardCheckpoint - (_reward * SCALE_FACTOR);
uint256 _newEarningPower =
earningPowerCalculator.getEarningPower(deposit.balance, deposit.owner, deposit.delegatee);
emit RewardClaimed(_depositId, _claimer, _payout, _newEarningPower);
totalEarningPower =
_calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
);
deposit.earningPower = _newEarningPower.toUint96();
SafeERC20.safeTransfer(REWARD_TOKEN, _claimer, _payout);
if (claimFeeParameters.feeAmount > 0) {
SafeERC20.safeTransfer(
REWARD_TOKEN, claimFeeParameters.feeCollector, claimFeeParameters.feeAmount
);
}
return _payout;
}
/// @notice Checkpoints the global reward per token accumulator.
function _checkpointGlobalReward() internal virtual {
rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();
lastCheckpointTime = lastTimeRewardDistributed();
}
/// @notice Checkpoints the unclaimed rewards and reward per token accumulator of a given
/// deposit.
/// @param deposit The deposit for which the reward parameters will be checkpointed.
/// @dev This is a sensitive internal helper method that must only be called after global rewards
/// accumulator has been checkpointed. It assumes the global `rewardPerTokenCheckpoint` is up to
/// date.
function _checkpointReward(Deposit storage deposit) internal virtual {
deposit.scaledUnclaimedRewardCheckpoint = _scaledUnclaimedReward(deposit);
deposit.rewardPerTokenCheckpoint = rewardPerTokenAccumulatedCheckpoint;
}
/// @notice Internal helper method which calculates and returns an updated value for total
/// earning power based on the old and new earning power of a deposit which is being changed.
/// @param _depositOldEarningPower The earning power of the deposit before a change is applied.
/// @param _depositNewEarningPower The earning power of the deposit after a change is applied.
/// @return _newTotalEarningPower The new total earning power.
function _calculateTotalEarningPower(
uint256 _depositOldEarningPower,
uint256 _depositNewEarningPower,
uint256 _totalEarningPower
) internal pure returns (uint256 _newTotalEarningPower) {
return _totalEarningPower + _depositNewEarningPower - _depositOldEarningPower;
}
/// @notice Internal helper method which sets the admin address.
/// @param _newAdmin Address of the new admin.
function _setAdmin(address _newAdmin) internal virtual {
_revertIfAddressZero(_newAdmin);
emit AdminSet(admin, _newAdmin);
admin = _newAdmin;
}
/// @notice Internal helper method which sets the earning power calculator address.
function _setEarningPowerCalculator(address _newEarningPowerCalculator) internal virtual {
_revertIfAddressZero(_newEarningPowerCalculator);
emit EarningPowerCalculatorSet(address(earningPowerCalculator), _newEarningPowerCalculator);
earningPowerCalculator = IEarningPowerCalculator(_newEarningPowerCalculator);
}
/// @notice Internal helper method which sets the max bump tip.
/// @param _newMaxTip Value of the new max bump tip.
function _setMaxBumpTip(uint256 _newMaxTip) internal virtual {
emit MaxBumpTipSet(maxBumpTip, _newMaxTip);
maxBumpTip = _newMaxTip;
}
/// @notice Internal helper method which sets the claim fee parameters.
/// @param _params The new fee parameters.
function _setClaimFeeParameters(ClaimFeeParameters memory _params) internal virtual {
if (
_params.feeAmount > MAX_CLAIM_FEE
|| (_params.feeCollector == address(0) && _params.feeAmount > 0)
) revert Staker__InvalidClaimFeeParameters();
emit ClaimFeeParametersSet(
claimFeeParameters.feeAmount,
_params.feeAmount,
claimFeeParameters.feeCollector,
_params.feeCollector
);
claimFeeParameters = _params;
}
/// @notice Internal helper method which reverts Staker__Unauthorized if the message
/// sender is not the admin.
function _revertIfNotAdmin() internal view virtual {
if (msg.sender != admin) revert Staker__Unauthorized("not admin", msg.sender);
}
/// @notice Internal helper method which reverts Staker__Unauthorized if the alleged
/// owner is not the true owner of the deposit.
/// @param deposit Deposit to validate.
/// @param _owner Alleged owner of deposit.
function _revertIfNotDepositOwner(Deposit storage deposit, address _owner) internal view virtual {
if (_owner != deposit.owner) revert Staker__Unauthorized("not owner", _owner);
}
/// @notice Internal helper method which reverts with Staker__InvalidAddress if the
/// account in question is address zero.
/// @param _account Account to verify.
function _revertIfAddressZero(address _account) internal pure {
if (_account == address(0)) revert Staker__InvalidAddress();
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;
import {Staker} from "../Staker.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
/// @title StakerOnBehalf
/// @author [ScopeLift](https://scopelift.co)
/// @notice This contract extension adds signature execution functionality to the Staker
/// base contract, allowing key operations to be executed via signatures rather than requiring the
/// owner or claimer to execute transactions directly. This includes staking, withdrawing,
/// altering delegatees and claimers, and claiming rewards. Each operation requires a unique
/// signature that is validated against the appropriate signer (owner or claimer) before
/// execution.
abstract contract StakerOnBehalf is Staker, EIP712, Nonces {
/// @notice Thrown when an onBehalf method is called with a deadline that has expired.
error StakerOnBehalf__ExpiredDeadline();
/// @notice Thrown if a caller supplies an invalid signature to a method that requires one.
error StakerOnBehalf__InvalidSignature();
/// @notice Type hash used when encoding data for `stakeOnBehalf` calls.
bytes32 public constant STAKE_TYPEHASH = keccak256(
"Stake(uint256 amount,address delegatee,address claimer,address depositor,uint256 nonce,uint256 deadline)"
);
/// @notice Type hash used when encoding data for `stakeMoreOnBehalf` calls.
bytes32 public constant STAKE_MORE_TYPEHASH = keccak256(
"StakeMore(uint256 depositId,uint256 amount,address depositor,uint256 nonce,uint256 deadline)"
);
/// @notice Type hash used when encoding data for `alterDelegateeOnBehalf` calls.
bytes32 public constant ALTER_DELEGATEE_TYPEHASH = keccak256(
"AlterDelegatee(uint256 depositId,address newDelegatee,address depositor,uint256 nonce,uint256 deadline)"
);
/// @notice Type hash used when encoding data for `alterClaimerOnBehalf` calls.
bytes32 public constant ALTER_CLAIMER_TYPEHASH = keccak256(
"AlterClaimer(uint256 depositId,address newClaimer,address depositor,uint256 nonce,uint256 deadline)"
);
/// @notice Type hash used when encoding data for `withdrawOnBehalf` calls.
bytes32 public constant WITHDRAW_TYPEHASH = keccak256(
"Withdraw(uint256 depositId,uint256 amount,address depositor,uint256 nonce,uint256 deadline)"
);
/// @notice Type hash used when encoding data for `claimRewardOnBehalf` calls.
bytes32 public constant CLAIM_REWARD_TYPEHASH =
keccak256("ClaimReward(uint256 depositId,uint256 nonce,uint256 deadline)");
/// @notice Returns the domain separator used in the encoding of the signatures for this contract.
/// @return The domain separator, as a bytes32 value, used for EIP-712 signatures.
function DOMAIN_SEPARATOR() external view returns (bytes32) {
return _domainSeparatorV4();
}
/// @notice Allows an address to increment their nonce and therefore invalidate any pending signed
/// actions.
function invalidateNonce() external virtual {
_useNonce(msg.sender);
}
/// @notice Stake tokens to a new deposit on behalf of a user, using a signature to validate the
/// user's intent. The caller must pre-approve the staking contract to spend at least the
/// would-be staked amount of the token.
/// @param _amount Quantity of the staking token to stake.
/// @param _delegatee Address to assign the governance voting weight of the staked tokens.
/// @param _claimer Address that will have the right to claim rewards for this stake.
/// @param _depositor Address of the user on whose behalf this stake is being made.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the user authorizing this stake.
/// @return _depositId Unique identifier for this deposit.
/// @dev Neither the delegatee nor the claimer may be the zero address.
function stakeOnBehalf(
uint256 _amount,
address _delegatee,
address _claimer,
address _depositor,
uint256 _deadline,
bytes memory _signature
) external virtual returns (DepositIdentifier _depositId) {
_revertIfPastDeadline(_deadline);
_revertIfSignatureIsNotValidNow(
_depositor,
_hashTypedDataV4(
keccak256(
abi.encode(
STAKE_TYPEHASH,
_amount,
_delegatee,
_claimer,
_depositor,
_useNonce(_depositor),
_deadline
)
)
),
_signature
);
_depositId = _stake(_depositor, _amount, _delegatee, _claimer);
}
/// @notice Add more staking tokens to an existing deposit on behalf of a user, using a signature
/// to validate the user's intent. A staker should call this method when they have an existing
/// deposit, and wish to stake more while retaining the same delegatee and claimer. The caller
/// must pre-approve the staking contract to spend at least the would-be staked amount of the
/// token.
/// @param _depositId Unique identifier of the deposit to which stake will be added.
/// @param _amount Quantity of stake to be added.
/// @param _depositor Address of the user on whose behalf this stake is being made.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the user authorizing this stake.
function stakeMoreOnBehalf(
DepositIdentifier _depositId,
uint256 _amount,
address _depositor,
uint256 _deadline,
bytes memory _signature
) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, _depositor);
_revertIfPastDeadline(_deadline);
_revertIfSignatureIsNotValidNow(
_depositor,
_hashTypedDataV4(
keccak256(
abi.encode(
STAKE_MORE_TYPEHASH, _depositId, _amount, _depositor, _useNonce(_depositor), _deadline
)
)
),
_signature
);
_stakeMore(deposit, _depositId, _amount);
}
/// @notice For an existing deposit, change the address to which governance voting power is
/// assigned on behalf of a user, using a signature to validate the user's intent.
/// @param _depositId Unique identifier of the deposit which will have its delegatee altered.
/// @param _newDelegatee Address of the new governance delegate.
/// @param _depositor Address of the user on whose behalf this action is being taken.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the user authorizing this stake.
/// @dev The new delegatee may not be the zero address.
function alterDelegateeOnBehalf(
DepositIdentifier _depositId,
address _newDelegatee,
address _depositor,
uint256 _deadline,
bytes memory _signature
) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, _depositor);
_revertIfPastDeadline(_deadline);
_revertIfSignatureIsNotValidNow(
_depositor,
_hashTypedDataV4(
keccak256(
abi.encode(
ALTER_DELEGATEE_TYPEHASH,
_depositId,
_newDelegatee,
_depositor,
_useNonce(_depositor),
_deadline
)
)
),
_signature
);
_alterDelegatee(deposit, _depositId, _newDelegatee);
}
/// @notice For an existing deposit, change the claimer account which has the right to
/// withdraw staking rewards accruing on behalf of a user, using a signature to validate the
/// user's intent.
/// @param _depositId Unique identifier of the deposit which will have its claimer altered.
/// @param _newClaimer Address of the new claimer.
/// @param _depositor Address of the user on whose behalf this action is being taken.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the user authorizing this stake.
/// @dev The new claimer may not be the zero address.
function alterClaimerOnBehalf(
DepositIdentifier _depositId,
address _newClaimer,
address _depositor,
uint256 _deadline,
bytes memory _signature
) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, _depositor);
_revertIfPastDeadline(_deadline);
_revertIfSignatureIsNotValidNow(
_depositor,
_hashTypedDataV4(
keccak256(
abi.encode(
ALTER_CLAIMER_TYPEHASH,
_depositId,
_newClaimer,
_depositor,
_useNonce(_depositor),
_deadline
)
)
),
_signature
);
_alterClaimer(deposit, _depositId, _newClaimer);
}
/// @notice Withdraw staked tokens from an existing deposit on behalf of a user, using a
/// signature to validate the user's intent.
/// @param _depositId Unique identifier of the deposit from which stake will be withdrawn.
/// @param _amount Quantity of staked token to withdraw.
/// @param _depositor Address of the user on whose behalf this action is being taken.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the user authorizing this stake.
/// @dev Stake is withdrawn to the deposit owner's account.
function withdrawOnBehalf(
DepositIdentifier _depositId,
uint256 _amount,
address _depositor,
uint256 _deadline,
bytes memory _signature
) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, _depositor);
_revertIfPastDeadline(_deadline);
_revertIfSignatureIsNotValidNow(
_depositor,
_hashTypedDataV4(
keccak256(
abi.encode(
WITHDRAW_TYPEHASH, _depositId, _amount, _depositor, _useNonce(_depositor), _deadline
)
)
),
_signature
);
_withdraw(deposit, _depositId, _amount);
}
/// @notice Claim reward tokens earned by a given deposit, using a signature to validate the
/// caller's intent. The signer must be the claimer address of the deposit Tokens are sent to
/// the claimer.
/// @param _depositId The identifier for the deposit for which to claim rewards.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the claimer or owner authorizing this reward claim.
/// @return Amount of reward tokens claimed, after the fee has been assessed.
function claimRewardOnBehalf(
DepositIdentifier _depositId,
uint256 _deadline,
bytes memory _signature
) external virtual returns (uint256) {
_revertIfPastDeadline(_deadline);
Deposit storage deposit = deposits[_depositId];
bytes32 _claimerHash = _hashTypedDataV4(
keccak256(abi.encode(CLAIM_REWARD_TYPEHASH, _depositId, nonces(deposit.claimer), _deadline))
);
bool _isValidClaimerClaim =
SignatureChecker.isValidSignatureNow(deposit.claimer, _claimerHash, _signature);
if (_isValidClaimerClaim) {
_useNonce(deposit.claimer);
return _claimReward(_depositId, deposit, deposit.claimer);
}
bytes32 _ownerHash = _hashTypedDataV4(
keccak256(abi.encode(CLAIM_REWARD_TYPEHASH, _depositId, _useNonce(deposit.owner), _deadline))
);
bool _isValidOwnerClaim =
SignatureChecker.isValidSignatureNow(deposit.owner, _ownerHash, _signature);
if (!_isValidOwnerClaim) revert StakerOnBehalf__InvalidSignature();
return _claimReward(_depositId, deposit, deposit.owner);
}
/// @notice Internal helper method which reverts with StakerOnBehalf__ExpiredDeadline if the
/// provided deadline has passed.
/// @param _deadline The timestamp that represents when the operation should no longer be valid.
function _revertIfPastDeadline(uint256 _deadline) internal view virtual {
if (block.timestamp > _deadline) revert StakerOnBehalf__ExpiredDeadline();
}
/// @notice Internal helper method which reverts with Staker__InvalidSignature if the
/// signature is invalid.
/// @param _signer Address of the signer.
/// @param _hash Hash of the message.
/// @param _signature Signature to validate.
function _revertIfSignatureIsNotValidNow(address _signer, bytes32 _hash, bytes memory _signature)
internal
view
virtual
{
bool _isValid = SignatureChecker.isValidSignatureNow(_signer, _hash, _signature);
if (!_isValid) revert StakerOnBehalf__InvalidSignature();
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;
import {Staker} from "../Staker.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
/// @title StakerPermitAndStake
/// @author [ScopeLift](https://scopelift.co)
/// @notice This contract extension adds permit functionality to the Staker base contract,
/// allowing token approvals to happen via signatures rather than requiring a separate transaction.
/// The permit functionality is used in conjunction with staking operations, improving UX by
/// enabling users to approve and stake tokens in a single transaction. Note that this extension
/// requires the stake token to support EIP-2612 permit functionality.
abstract contract StakerPermitAndStake is Staker {
/// @notice Thrown if an inheritor misconfigures the staking token on deployment.
error StakerPermitAndStake__UnauthorizedToken();
/// @param _permitToken The token that is used for staking, which must support EIP-2612. It also
/// must be the same as the parent Staker's STAKE_TOKEN.
constructor(IERC20Permit _permitToken) {
if (address(STAKE_TOKEN) != address(_permitToken)) {
revert StakerPermitAndStake__UnauthorizedToken();
}
}
/// @notice Method to stake tokens to a new deposit. Before the staking operation occurs, a
/// signature is passed to the token contract's permit method to spend the would-be staked amount
/// of the token.
/// @param _amount Quantity of the staking token to stake.
/// @param _delegatee Address to assign the governance voting weight of the staked tokens.
/// @param _claimer Address that will have the right to claim rewards for this stake.
/// @param _deadline The timestamp after which the permit signature should expire.
/// @param _v ECDSA signature component: Parity of the `y` coordinate of point `R`
/// @param _r ECDSA signature component: x-coordinate of `R`
/// @param _s ECDSA signature component: `s` value of the signature
/// @return _depositId Unique identifier for this deposit.
/// @dev Neither the delegatee nor the claimer may be the zero address. The deposit will be
/// owned by the message sender.
function permitAndStake(
uint256 _amount,
address _delegatee,
address _claimer,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external virtual returns (DepositIdentifier _depositId) {
try IERC20Permit(address(STAKE_TOKEN)).permit(
msg.sender, address(this), _amount, _deadline, _v, _r, _s
) {} catch {}
_depositId = _stake(msg.sender, _amount, _delegatee, _claimer);
}
/// @notice Add more staking tokens to an existing deposit. A staker should call this method when
/// they have an existing deposit, and wish to stake more while retaining the same delegatee and
/// claimer. Before the staking operation occurs, a signature is passed to the token
/// contract's permit method to spend the would-be staked amount of the token.
/// @param _depositId Unique identifier of the deposit to which stake will be added.
/// @param _amount Quantity of stake to be added.
/// @param _deadline The timestamp after which the permit signature should expire.
/// @param _v ECDSA signature component: Parity of the `y` coordinate of point `R`
/// @param _r ECDSA signature component: x-coordinate of `R`
/// @param _s ECDSA signature component: `s` value of the signature
/// @dev The message sender must be the owner of the deposit.
function permitAndStakeMore(
DepositIdentifier _depositId,
uint256 _amount,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, msg.sender);
try IERC20Permit(address(STAKE_TOKEN)).permit(
msg.sender, address(this), _amount, _deadline, _v, _r, _s
) {} catch {}
_stakeMore(deposit, _depositId, _amount);
}
}// SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.0; /// @title IAddressSet /// @author [Golem Foundation](https://golem.foundation) /// @custom:security-contact [email protected] /// @notice Interface for a managed set of addresses with access control /// @dev Provides add/remove operations and membership checking interface IAddressSet { /// @notice Check if an address is in the set /// @param account Address to check /// @return True if the address is in the set, false otherwise function contains(address account) external view returns (bool); /// @notice Add an address to the set /// @param account Address to add /// @dev Reverts if address is already in the set or is address(0) function add(address account) external; /// @notice Add multiple addresses to the set /// @param accounts Addresses to add /// @dev Reverts on first invalid address (already in set or address(0)) function add(address[] memory accounts) external; /// @notice Remove an address from the set /// @param account Address to remove /// @dev Reverts if address is not in the set function remove(address account) external; /// @notice Remove multiple addresses from the set /// @param accounts Addresses to remove /// @dev Reverts on first address not in the set function remove(address[] memory accounts) external; }
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;
/// @title IEarningPowerCalculator
/// @author [ScopeLift](https://scopelift.co)
/// @notice Interface to which Earning Power Calculators must conform in order to provide earning
/// power updates to an instance of Staker. Well behaving earning power calculators should:
///
/// 1. Be deterministic, i.e. produce the same output for the same input at a given time.
/// 2. Return values that are in the same order of magnitude as reasonable stake token amounts.
/// Avoid returning values that are dramatically detached from the staked amount.
/// 3. Avoid too much "churn" on earning power values, in particular, avoid returning "true" for
/// the `getNewEarningPower` method's `_isQualifiedForBump` too frequently, as such an earning
/// calculator would result in repeated bumps on a user's deposit, requiring excessive
/// monitoring on their behalf to avoid eating into their rewards.
interface IEarningPowerCalculator {
/// @notice Returns the current earning power for a given staker, delegatee and staking amount.
/// @param _amountStaked The amount of tokens staked.
/// @param _staker The address of the staker.
/// @param _delegatee The address of their chosen delegatee.
/// @return _earningPower The calculated earning power.
function getEarningPower(uint256 _amountStaked, address _staker, address _delegatee)
external
view
returns (uint256 _earningPower);
/// @notice Returns the current earning power for a given staker, delegatee, staking amount, and
/// old earning power, along with a flag denoting whether the change in earning power warrants
/// "bumping." Bumping means paying a third party a bit of the rewards to update the deposit's
/// earning power on the depositor's behalf.
/// @param _amountStaked The amount of tokens staked.
/// @param _staker The address of the staker.
/// @param _delegatee The address of their chosen delegatee.
/// @param _oldEarningPower The earning power currently assigned to the deposit for which new
/// earning power is being calculated.
/// @return _newEarningPower The calculated earning power.
/// @return _isQualifiedForBump A flag indicating whether or not this new earning power qualifies
/// the deposit for having its earning power bumped.
/// @dev Earning Power calculators should only "qualify" a bump when the difference warrants a
/// forced update by a third party. This could be, for example, to reduce a deposit's earning
/// power because their delegatee has become inactive. Even in these cases, a calculator should
/// avoid qualifying for a bump too frequently. A calculator implementer may, for example, want
/// to implement a grace period or a threshold difference before qualifying a deposit for a bump.
function getNewEarningPower(
uint256 _amountStaked,
address _staker,
address _delegatee,
uint256 _oldEarningPower
) external view returns (uint256 _newEarningPower, bool _isQualifiedForBump);
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.20;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol";
/// @notice Interface for base allocation mechanism strategy implementations
/// @dev Follows Yearn V3 pattern where shared implementation calls base strategy via interface
interface IBaseAllocationStrategy {
/// @notice Hook to allow or block registration
function beforeSignupHook(address user) external returns (bool);
/// @notice Hook to allow or block proposal creation
function beforeProposeHook(address proposer) external view returns (bool);
/// @notice Hook to calculate new voting power on registration
function getVotingPowerHook(address user, uint256 deposit) external view returns (uint256);
/// @notice Hook to validate existence and integrity of a proposal ID
function validateProposalHook(uint256 pid) external view returns (bool);
/// @notice Hook to process a vote
function processVoteHook(
uint256 pid,
address voter,
uint8 choice,
uint256 weight,
uint256 oldPower
) external returns (uint256 newPower);
/// @notice Check if proposal met quorum requirement
function hasQuorumHook(uint256 pid) external view returns (bool);
/// @notice Hook to convert final vote tallies into vault shares to mint
function convertVotesToShares(uint256 pid) external view returns (uint256 sharesToMint);
/// @notice Hook to modify the behavior of finalizeVoteTally
function beforeFinalizeVoteTallyHook() external returns (bool);
/// @notice Hook to fetch the recipient address for a proposal
function getRecipientAddressHook(uint256 pid) external view returns (address recipient);
/// @notice Hook to perform custom distribution of shares when a proposal is queued
/// @dev If this returns (true, assetsTransferred), default share minting is skipped and totalAssets is updated
/// @param recipient Address of the recipient for the proposal
/// @param sharesToMint Number of shares to distribute/mint to the recipient
/// @return handled True if custom distribution was handled, false to use default minting
/// @return assetsTransferred Amount of assets transferred directly to recipient (to update totalAssets)
function requestCustomDistributionHook(
address recipient,
uint256 sharesToMint
) external returns (bool handled, uint256 assetsTransferred);
/// @notice Hook to get the available withdraw limit for a share owner
function availableWithdrawLimit(address shareOwner) external view returns (uint256);
/// @notice Hook to calculate total assets including any matching pools or custom logic
function calculateTotalAssetsHook() external view returns (uint256);
}
/**
* @title TokenizedAllocationMechanism
* @author [Golem Foundation](https://golem.foundation)
* @custom:security-contact [email protected]
* @notice Shared implementation for allocation/voting mechanisms (Yearn V3 pattern)
* @dev Handles all standard voting logic via delegatecall from mechanism proxies
*
* PROPOSAL STATE MACHINE:
* ═══════════════════════════════════
* Pending → Active → Tallying → Defeated/Succeeded → Queued → Redeemable → Expired
* ↓
* Canceled
*
* STATE DESCRIPTIONS:
* - Pending: Proposal created, waiting for votingDelay
* - Active: Voting period active, users can cast votes
* - Tallying: Voting ended, waiting for finalization
* - Defeated: Voting ended, failed quorum
* - Succeeded: Voting ended, passed quorum
* - Queued: Shares minted, waiting for timelock
* - Redeemable: Timelock passed, within grace period
* - Expired: Grace period passed, redemptions closed
* - Canceled: Proposer canceled (terminal state)
*
* LIFECYCLE TIMELINE:
* ═══════════════════════════════════
* T0: Proposal created (Pending)
* T0 + votingDelay: Voting opens (Active)
* T0 + votingDelay + votingPeriod: Voting closes (Tallying)
* Anyone calls finalizeVoteTally(): Defeated or Succeeded
* If Succeeded, anyone calls queue(): Queued (shares minted)
* Queued + timelockDelay: Redeemable (redemptions allowed)
* Redeemable + gracePeriod: Expired (redemptions closed)
*
* VOTING MECHANICS:
* ═══════════════════════════════════
* 1. Users signup (deposit assets, get voting power)
* 2. Proposals created during registration period
* 3. Users cast votes (Against/For/Abstain)
* 4. Votes finalized after votingPeriod
* 5. Successful proposals get shares minted
* 6. Recipients redeem shares for assets
*
* SECURITY FEATURES:
* ═══════════════════════════════════
* - Timelock: Delay before redemptions (security buffer)
* - Grace Period: Limited redemption window
* - Quorum: Minimum votes required
* - EIP-712: Gasless signups and votes
* - Reentrancy protection
* - Pausability
*
* @custom:security State machine enforces proper proposal progression
* @custom:security Timelock provides security delay before fund distribution
*/
contract TokenizedAllocationMechanism is IERC20 {
using SafeERC20 for IERC20;
using SafeERC20 for ERC20;
using Math for uint256;
// Custom Errors
error ZeroAssetAddress();
error ZeroVotingDelay();
error ZeroVotingPeriod();
error ZeroQuorumShares();
error ZeroTimelockDelay();
error ZeroGracePeriod();
error ZeroStartBlock();
/// @param startTime Proposed start timestamp in seconds
/// @param currentTime Current block timestamp in seconds
error InvalidStartTime(uint256 startTime, uint256 currentTime);
error EmptyName();
error EmptySymbol();
/// @param user Address attempting to register
error RegistrationBlocked(address user);
/// @param currentTime Current block timestamp in seconds
/// @param endTime Voting end timestamp in seconds
error VotingEnded(uint256 currentTime, uint256 endTime);
/// @param user Address that is already registered
error AlreadyRegistered(address user);
/// @param deposit Deposit amount in asset base units (token decimals)
/// @param maxAllowed Maximum allowed deposit in asset base units
error DepositTooLarge(uint256 deposit, uint256 maxAllowed);
/// @param votingPower Voting power in shares units in share base units
/// @param maxAllowed Maximum allowed voting power in share base units
error VotingPowerTooLarge(uint256 votingPower, uint256 maxAllowed);
/// @param deposit Deposit amount in asset base units (token decimals)
error InsufficientDeposit(uint256 deposit);
/// @param proposer Address attempting to propose
error ProposeNotAllowed(address proposer);
/// @param recipient Invalid recipient address
error InvalidRecipient(address recipient);
/// @param user Invalid user address
error InvalidUser(address user);
/// @param recipient Recipient with an active proposal
error RecipientUsed(address recipient);
/// @param pid Proposal id
/// @param expected Expected recipient address
/// @param actual Provided recipient address
error RecipientMismatch(uint256 pid, address expected, address actual);
/// @param pid Proposal id
error DescriptionMismatch(uint256 pid);
error EmptyDescription();
/// @param length Provided description length in bytes
/// @param maxLength Maximum allowed length in bytes
error DescriptionTooLong(uint256 length, uint256 maxLength);
/// @param currentTime Current block timestamp in seconds
/// @param endTime Voting end timestamp in seconds
error VotingNotEnded(uint256 currentTime, uint256 endTime);
error TallyAlreadyFinalized();
error FinalizationBlocked();
error TallyNotFinalized();
/// @param pid Invalid proposal id
error InvalidProposal(uint256 pid);
/// @param pid Canceled proposal id
error ProposalCanceledError(uint256 pid);
/// @param pid Proposal id
/// @param forVotes Total for votes in share base units
/// @param againstVotes Total against votes in share base units
/// @param required Quorum threshold in shares in share base units
error NoQuorum(uint256 pid, uint256 forVotes, uint256 againstVotes, uint256 required);
/// @param pid Proposal id
error AlreadyQueued(uint256 pid);
error QueueingClosedAfterRedemption();
/// @param pid Proposal id
/// @param sharesToMint Calculated shares to mint in share base units
error NoAllocation(uint256 pid, uint256 sharesToMint);
/// @param requested Assets requested in base units (token decimals)
/// @param available Available assets in base units (token decimals)
error InsufficientAssets(uint256 requested, uint256 available);
/// @param currentTime Current block timestamp in seconds
/// @param startTime Voting start timestamp in seconds
/// @param endTime Voting end timestamp in seconds
error VotingClosed(uint256 currentTime, uint256 startTime, uint256 endTime);
/// @param weight Vote weight in shares in share base units
/// @param votingPower Voter's voting power in shares in share base units
error InvalidWeight(uint256 weight, uint256 votingPower);
/// @param weight Vote weight in shares in share base units
/// @param maxAllowed Maximum allowed weight in shares in share base units
error WeightTooLarge(uint256 weight, uint256 maxAllowed);
/// @param oldPower Previous voting power in shares in share base units
/// @param newPower New voting power in shares in share base units
error PowerIncreased(uint256 oldPower, uint256 newPower);
/// @param caller Caller address
/// @param proposer Expected proposer address
error NotProposer(address caller, address proposer);
/// @param pid Proposal id
error AlreadyCanceled(uint256 pid);
error Unauthorized();
error AlreadyInitialized();
error PausedError();
error ReentrantCall();
/// @param deadline Signature deadline timestamp in seconds
/// @param currentTime Current block timestamp in seconds
error ExpiredSignature(uint256 deadline, uint256 currentTime);
error InvalidSignature();
/// @param recovered Address recovered from signature
/// @param expected Expected signer address
error InvalidSigner(address recovered, address expected);
/// @notice Maximum safe value for internal math to avoid overflows
/// @dev Capped at uint128.max to keep intermediate operations within safe bounds
uint256 public constant MAX_SAFE_VALUE = type(uint128).max;
/// @notice Storage slot for allocation mechanism data (EIP-1967-like deterministic slot)
/// @dev Calculated as keccak256("tokenized.allocation.storage") - 1 to minimize collision risk
bytes32 private constant ALLOCATION_STORAGE_SLOT = bytes32(uint256(keccak256("tokenized.allocation.storage")) - 1);
/// @notice EIP-712 Domain separator typehash
/// @dev Used to compute domain separator for structured data signing
bytes32 private constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/// @notice Signup typehash for EIP-712 structured data
/// @dev Typed data: Signup(user, payer, deposit, nonce, deadline)
bytes32 private constant SIGNUP_TYPEHASH =
keccak256("Signup(address user,address payer,uint256 deposit,uint256 nonce,uint256 deadline)");
/// @notice CastVote typehash for EIP-712 structured data
/// @dev Typed data: CastVote(voter, proposalId, choice, weight, expectedRecipient, nonce, deadline)
bytes32 private constant CAST_VOTE_TYPEHASH =
keccak256(
"CastVote(address voter,uint256 proposalId,uint8 choice,uint256 weight,address expectedRecipient,uint256 nonce,uint256 deadline)"
);
/// @notice EIP-712 version string used in domain separator
/// @dev Update only with extreme caution; changing breaks signature domain
string private constant EIP712_VERSION = "1";
// ============================================
// ENUMS
// ============================================
/**
* @notice Vote types for proposal voting
* @dev Used in castVote() to indicate vote direction
*/
enum VoteType {
/// @notice Vote against the proposal
Against,
/// @notice Vote in favor of the proposal
For,
/// @notice Abstain from voting (recorded but doesn't affect outcome)
Abstain
}
/**
* @notice Proposal lifecycle states
* @dev State machine progression enforced by contract logic
*/
enum ProposalState {
/// @notice Created, waiting for votingDelay to pass
Pending,
/// @notice Voting period active, can cast votes
Active,
/// @notice Proposer canceled, terminal state
Canceled,
/// @notice Voting ended, awaiting finalization
Tallying,
/// @notice Finalized, failed quorum (terminal)
Defeated,
/// @notice Finalized, passed quorum, ready to queue
Succeeded,
/// @notice Shares minted, waiting for timelock
Queued,
/// @notice Timelock passed, in grace period (can redeem)
Redeemable,
/// @notice Grace period ended (redemptions closed)
Expired
}
/**
* @notice Core proposal data used throughout the allocation mechanism
* @dev Stores immutable metadata for a proposal; dynamic tallies are kept elsewhere
*/
struct Proposal {
/// @notice Number of shares requested if proposal succeeds in share base units
uint256 sharesRequested;
/// @notice Address that created the proposal
address proposer;
/// @notice Intended recipient of minted shares upon queue
address recipient;
/// @notice Human-readable description or rationale for the proposal
string description;
/// @notice True if the proposer canceled the proposal (terminal state)
bool canceled;
}
/// @notice Main storage struct containing all allocation mechanism state
/// @dev Stored at a deterministic slot; see {ALLOCATION_STORAGE_SLOT}
struct AllocationStorage {
// Basic information
/// @notice ERC20 name for the shares token
string name;
/// @notice ERC20 symbol for the shares token
string symbol;
/// @notice Underlying ERC20 asset used for deposits and redemptions
IERC20 asset;
// Configuration (immutable after initialization)
/// @notice Block number at initialization for legacy compatibility (blocks)
uint256 startBlock;
/// @notice Voting delay after start before voting opens (seconds)
uint256 votingDelay;
/// @notice Voting duration once opened (seconds)
uint256 votingPeriod;
/// @notice Timelock duration after queue before redemptions (seconds)
uint256 timelockDelay;
/// @notice Grace period during which redemptions are allowed (seconds)
uint256 gracePeriod;
/// @notice Quorum threshold in shares required for success in share base units
uint256 quorumShares;
/// @notice Mechanism start timestamp (seconds)
uint256 startTime;
/// @notice Timestamp when voting opens (startTime + votingDelay) (seconds)
uint256 votingStartTime;
/// @notice Timestamp when voting ends (startTime + votingDelay + votingPeriod) (seconds)
uint256 votingEndTime;
/// @notice Timestamp when {finalizeVoteTally} was called (seconds)
uint256 tallyFinalizedTime;
// Access control
/// @notice Current contract owner authorized to manage configuration
address owner;
/// @notice Pending owner waiting to accept ownership
address pendingOwner;
/// @notice Global pause flag to disable mutating actions
bool paused;
/// @notice True once {initialize} has been successfully called
bool initialized;
// Reentrancy protection
/// @notice Reentrancy guard flag (1 = NOT_ENTERED, 2 = ENTERED)
uint8 reentrancyStatus;
// Voting state
/// @notice True if vote tally is finalized (post-voting)
bool tallyFinalized;
/// @notice Monotonic counter used to assign new proposal ids
uint256 proposalIdCounter;
/// @notice Global timestamp when all redemptions and transfers can begin (seconds)
uint256 globalRedemptionStart;
/// @notice Global timestamp when the redemption period ends (seconds)
uint256 globalRedemptionEndTime;
// Allocation Mechanism Vault Storage (merged from DistributionMechanism)
/// @notice Per-address sequential nonces for EIP-712 signatures
mapping(address => uint256) nonces;
/// @notice Share balances per account in share base units
mapping(address => uint256) balances;
/// @notice Allowances mapping for share spenders in share base units
mapping(address => mapping(address => uint256)) allowances;
/// @notice Total number of shares in circulation in share base units
uint256 totalSupply;
/// @notice Total assets under management in underlying base units
/// @dev Manually tracked to prevent PPS manipulation through airdrops
uint256 totalAssets;
// Strategy Management
/// @notice Address permitted to perform keeper operations
address keeper;
/// @notice Management address authorized to update configuration
address management;
/// @notice Decimals used by asset and this shares token
uint8 decimals;
// Mappings
/// @notice Mapping from proposal id to stored {Proposal}
mapping(uint256 => Proposal) proposals;
/// @notice Tracks active proposal id for a given recipient (if any)
mapping(address => uint256) activeProposalByRecipient;
/// @notice Voting power per user in shares in share base units
mapping(address => uint256) votingPower;
/// @notice Shares allocated to each proposal in share base units
mapping(uint256 => uint256) proposalShares;
// EIP712 storage
/// @notice Cached EIP-712 domain separator for signatures
bytes32 domainSeparator; // Cached domain separator
/// @notice Chain id used in domain separator to provide fork protection
uint256 initialChainId; // Chain ID at deployment for fork protection
}
// ---------- Storage Access for Hooks ----------
/// @notice Emitted when a user completes registration
/// @param user Address of the registered user
/// @param votingPower Voting power granted (shares, 18 decimals)
event UserRegistered(address indexed user, uint256 votingPower);
/// @notice Emitted when a new proposal is created
/// @param pid Newly assigned proposal id
/// @param proposer Address that created the proposal
/// @param recipient Intended recipient of minted shares upon queue
/// @param description Human-readable proposal description
event ProposalCreated(uint256 indexed pid, address indexed proposer, address indexed recipient, string description);
/// @notice Emitted when a vote is cast
/// @param voter Address casting the vote
/// @param pid Proposal id being voted on
/// @param weight Vote weight used (shares, 18 decimals)
event VotesCast(address indexed voter, uint256 indexed pid, uint256 weight);
/// @notice Emitted when vote tally is finalized
event VoteTallyFinalized();
/// @notice Emitted when a proposal is queued and shares minted
/// @param pid Proposal id being queued
/// @param eta Timestamp when timelock elapses and redemptions can begin (seconds)
/// @param shareAmount Number of shares minted/allocated in share base units
event ProposalQueued(uint256 indexed pid, uint256 eta, uint256 shareAmount);
/// @notice Emitted when a proposal is canceled
/// @param pid Proposal id that was canceled
/// @param proposer Address of the canceling proposer
event ProposalCanceled(uint256 indexed pid, address indexed proposer);
/// @notice Emitted when ownership transfer is initiated
/// @param currentOwner Current owner address
/// @param pendingOwner Address nominated to become the new owner
event OwnershipTransferInitiated(address indexed currentOwner, address indexed pendingOwner);
/// @notice Emitted when ownership transfer is canceled
/// @param currentOwner Current owner address
/// @param canceledPendingOwner Previously pending owner whose transfer was canceled
event OwnershipTransferCanceled(address indexed currentOwner, address indexed canceledPendingOwner);
/// @notice Emitted when ownership is transferred
/// @param previousOwner Address of the previous owner
/// @param newOwner Address of the new owner
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/// @notice Emitted when keeper is updated
/// @param previousKeeper Old keeper address
/// @param newKeeper New keeper address
event KeeperUpdated(address indexed previousKeeper, address indexed newKeeper);
/// @notice Emitted when management is updated
/// @param previousManagement Old management address
/// @param newManagement New management address
event ManagementUpdated(address indexed previousManagement, address indexed newManagement);
/// @notice Emitted when contract is paused/unpaused
/// @param paused True if paused, false if unpaused
event PausedStatusChanged(bool paused);
/// @notice Emitted when global redemption period is set
/// @param redemptionStart Timestamp when global redemptions can begin (seconds)
/// @param redemptionEnd Timestamp when global redemptions end (seconds)
event GlobalRedemptionPeriodSet(uint256 redemptionStart, uint256 redemptionEnd);
/// @notice Emitted when tokens are swept after grace period
/// @param token Token address that was swept
/// @param receiver Recipient of swept tokens
/// @param amount Amount swept in token base units
event Swept(address indexed token, address indexed receiver, uint256 amount);
// Additional events from DistributionMechanism
/// @param caller Address initiating the redemption
/// @param receiver Address receiving the underlying assets
/// @param owner Owner of the shares being redeemed
/// @param assets Amount of underlying assets transferred (asset base units)
/// @param shares Amount of shares burned in share base units
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
// ---------- Storage Access ----------
/// @notice Get the storage struct from the predefined slot
/// @return s Storage struct containing all mutable state
function _getStorage() internal pure returns (AllocationStorage storage s) {
bytes32 slot = ALLOCATION_STORAGE_SLOT;
assembly {
s.slot := slot
}
}
/// @notice Constructor to prevent initialization of the library implementation
constructor() {
AllocationStorage storage s = _getStorage();
s.initialized = true; // Prevent initialization on the library contract
s.reentrancyStatus = 1; // Initialize reentrancy guard to NOT_ENTERED
}
/// @notice Returns the domain separator, updating it if chain ID changed (fork protection)
function DOMAIN_SEPARATOR() public returns (bytes32) {
AllocationStorage storage s = _getStorage();
if (block.chainid == s.initialChainId) {
return s.domainSeparator;
} else {
s.initialChainId = block.chainid;
bytes32 domainSeparator = _computeDomainSeparator(s);
s.domainSeparator = domainSeparator;
return domainSeparator;
}
}
/// @dev Computes the domain separator
function _computeDomainSeparator(AllocationStorage storage s) private view returns (bytes32) {
return
keccak256(
abi.encode(
TYPE_HASH,
keccak256(bytes(s.name)),
keccak256(bytes(EIP712_VERSION)),
block.chainid,
address(this)
)
);
}
// ---------- Modifiers ----------
modifier onlyOwner() {
AllocationStorage storage s = _getStorage();
if (msg.sender != s.owner) revert Unauthorized();
_;
}
modifier whenNotPaused() {
if (_getStorage().paused) revert PausedError();
_;
}
modifier nonReentrant() {
AllocationStorage storage s = _getStorage();
if (s.reentrancyStatus == 2) revert ReentrantCall();
s.reentrancyStatus = 2;
_;
s.reentrancyStatus = 1;
}
// ---------- Initialization ----------
/// @notice Initialize the allocation mechanism with configuration
/// @dev Can only be called once by the strategy/clone proxy; subsequent calls revert
/// @param _owner Address that will become the owner and management
/// @param _asset Underlying ERC20 asset used for deposits/redemptions
/// @param _name ERC20 name for the shares token
/// @param _symbol ERC20 symbol for the shares token
/// @param _votingDelay Delay before voting opens (seconds)
/// @param _votingPeriod Duration of the voting phase (seconds)
/// @param _quorumShares Quorum threshold (shares, 18 decimals)
/// @param _timelockDelay Delay after queueing before redemptions (seconds)
/// @param _gracePeriod Duration of redemption window (seconds)
/// @custom:security Initialization guarded by `AlreadyInitialized` check
function initialize(
address _owner,
IERC20 _asset,
string memory _name,
string memory _symbol,
uint256 _votingDelay,
uint256 _votingPeriod,
uint256 _quorumShares,
uint256 _timelockDelay,
uint256 _gracePeriod
) external {
_initializeAllocation(
_owner,
_asset,
_name,
_symbol,
_votingDelay,
_votingPeriod,
_quorumShares,
_timelockDelay,
_gracePeriod
);
}
/// @notice Internal allocation mechanism initialization
/// @dev Shared initializer used by {initialize}
/// @param _owner Address that will become the owner and management
/// @param _asset Underlying ERC20 asset used for deposits/redemptions
/// @param _name ERC20 name for the shares token
/// @param _symbol ERC20 symbol for the shares token
/// @param _votingDelay Delay before voting opens (seconds)
/// @param _votingPeriod Duration of the voting phase (seconds)
/// @param _quorumShares Quorum threshold (shares, 18 decimals)
/// @param _timelockDelay Delay after queueing before redemptions (seconds)
/// @param _gracePeriod Duration of redemption window (seconds)
function _initializeAllocation(
address _owner,
IERC20 _asset,
string memory _name,
string memory _symbol,
uint256 _votingDelay,
uint256 _votingPeriod,
uint256 _quorumShares,
uint256 _timelockDelay,
uint256 _gracePeriod
) internal {
AllocationStorage storage s = _getStorage();
// Validate inputs
if (_owner == address(0)) revert Unauthorized();
if (address(_asset) == address(0)) revert ZeroAssetAddress();
if (_votingDelay == 0) revert ZeroVotingDelay();
if (_votingPeriod == 0) revert ZeroVotingPeriod();
if (_quorumShares == 0) revert ZeroQuorumShares();
if (_timelockDelay == 0) revert ZeroTimelockDelay();
if (_gracePeriod == 0) revert ZeroGracePeriod();
if (bytes(_name).length == 0) revert EmptyName();
if (bytes(_symbol).length == 0) revert EmptySymbol();
if (s.initialized == true) revert AlreadyInitialized();
// Set configuration
s.owner = _owner;
s.asset = _asset;
s.name = _name;
s.symbol = _symbol;
s.votingDelay = _votingDelay;
s.votingPeriod = _votingPeriod;
s.quorumShares = _quorumShares;
s.timelockDelay = _timelockDelay;
s.gracePeriod = _gracePeriod;
s.startBlock = block.number; // Keep for legacy getter compatibility
s.initialized = true;
s.reentrancyStatus = 1; // Initialize reentrancy guard to NOT_ENTERED
// Set timestamp-based timeline starting from deployment time
s.startTime = block.timestamp;
s.votingStartTime = s.startTime + _votingDelay;
s.votingEndTime = s.votingStartTime + _votingPeriod;
// Set management roles to owner
s.management = _owner;
s.keeper = _owner;
s.decimals = ERC20(address(_asset)).decimals();
// Initialize EIP712 domain separator
s.initialChainId = block.chainid;
s.domainSeparator = _computeDomainSeparator(s);
emit OwnershipTransferred(address(0), _owner);
}
// ---------- Registration ----------
/// @notice Register to gain voting power by depositing underlying tokens
/// @param deposit Amount of underlying to deposit (asset base units, may be zero)
/// @custom:security Reentrancy protected; callable only when not paused
function signup(uint256 deposit) external nonReentrant whenNotPaused {
_executeSignup(msg.sender, deposit, msg.sender);
}
/// @notice Register on behalf of another user using EIP-712 signature
/// @param user Address of the user signing up
/// @param deposit Amount of underlying to deposit (asset base units)
/// @param deadline Expiration timestamp for the signature (seconds)
/// @param v Signature parameter
/// @param r Signature parameter
/// @param s Signature parameter
/// @dev The deposit will be taken from msg.sender, not the user. Increments `nonces[user]`.
/// @custom:security Reentrancy protected; callable only when not paused
function signupOnBehalfWithSignature(
address user,
uint256 deposit,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external nonReentrant whenNotPaused {
_validateSignature(
user,
keccak256(abi.encode(SIGNUP_TYPEHASH, user, msg.sender, deposit, _getStorage().nonces[user]++, deadline)),
deadline,
v,
r,
s
);
_executeSignup(user, deposit, msg.sender);
}
/// @notice Register with voting power using EIP-712 signature
/// @param user Address of the user signing up
/// @param deposit Amount of underlying to deposit (asset base units)
/// @param deadline Expiration timestamp for the signature (seconds)
/// @param v Signature parameter
/// @param r Signature parameter
/// @param s Signature parameter
/// @dev The deposit will be taken from the user themselves. Increments `nonces[user]`.
/// @custom:security Reentrancy protected; callable only when not paused
function signupWithSignature(
address user,
uint256 deposit,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external nonReentrant whenNotPaused {
_validateSignature(
user,
keccak256(abi.encode(SIGNUP_TYPEHASH, user, user, deposit, _getStorage().nonces[user]++, deadline)),
deadline,
v,
r,
s
);
_executeSignup(user, deposit, user);
}
/// @dev Validates signature parameters with ERC1271 support for contract signers
function _validateSignature(
address expectedSigner,
bytes32 structHash,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) private {
// Check deadline
if (block.timestamp > deadline) revert ExpiredSignature(deadline, block.timestamp);
// Compute EIP712 digest
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), structHash));
// Try ECDSA recovery first
(address recovered, , ) = ECDSA.tryRecover(digest, v, r, s);
// If ECDSA recovery matches expected signer, we're done
if (recovered == expectedSigner) {
return;
}
// If expectedSigner is a contract, try ERC1271 validation
if (expectedSigner.code.length > 0) {
// Pack signature components for ERC1271
bytes memory signature = abi.encodePacked(r, s, v);
try IERC1271(expectedSigner).isValidSignature(digest, signature) returns (bytes4 magicValue) {
if (magicValue == 0x1626ba7e) {
return; // Valid ERC1271 signature
}
} catch {
// Fall through to revert
}
}
// Neither ECDSA nor ERC1271 validation succeeded
revert InvalidSigner(recovered, expectedSigner);
}
/// @dev Internal signup execution logic
function _executeSignup(address user, uint256 deposit, address payer) private {
AllocationStorage storage s = _getStorage();
// Prevent zero address registration
if (user == address(0)) revert InvalidUser(user);
// Call hook for validation via interface (Yearn V3 pattern)
if (!IBaseAllocationStrategy(address(this)).beforeSignupHook(user)) {
revert RegistrationBlocked(user);
}
if (block.timestamp > s.votingEndTime) revert VotingEnded(block.timestamp, s.votingEndTime);
if (deposit > MAX_SAFE_VALUE) revert DepositTooLarge(deposit, MAX_SAFE_VALUE);
if (deposit > 0) s.asset.safeTransferFrom(payer, address(this), deposit);
uint256 newPower = IBaseAllocationStrategy(address(this)).getVotingPowerHook(user, deposit);
if (newPower > MAX_SAFE_VALUE) revert VotingPowerTooLarge(newPower, MAX_SAFE_VALUE);
// Prevent registration with zero voting power when deposit is non-zero
if (newPower == 0 && deposit > 0) revert InsufficientDeposit(deposit);
// Add to existing voting power to support multiple signups
uint256 totalPower = s.votingPower[user] + newPower;
if (totalPower > MAX_SAFE_VALUE) revert VotingPowerTooLarge(totalPower, MAX_SAFE_VALUE);
s.votingPower[user] = totalPower;
emit UserRegistered(user, newPower);
}
// ---------- Proposal Creation ----------
/// @notice Create a new proposal targeting `recipient`
/// @param recipient Address to receive allocated vault shares upon queue
/// @param description Human-readable description or rationale for the proposal
/// @return pid Unique identifier for the new proposal
/// @custom:security Reentrancy protected; callable only when not paused; subject to strategy hook
function propose(
address recipient,
string calldata description
) external whenNotPaused nonReentrant returns (uint256 pid) {
address proposer = msg.sender;
// Call hook for validation - Potential DoS risk - malicious keeper/management contracts could revert these calls
if (!IBaseAllocationStrategy(address(this)).beforeProposeHook(proposer)) revert ProposeNotAllowed(proposer);
if (recipient == address(0) || recipient == address(this)) revert InvalidRecipient(recipient);
AllocationStorage storage s = _getStorage();
// Proposing only allowed before voting period ends
if (block.timestamp > s.votingEndTime) {
revert VotingEnded(block.timestamp, s.votingEndTime);
}
if (s.activeProposalByRecipient[recipient] != 0) {
revert RecipientUsed(recipient);
}
if (bytes(description).length == 0) revert EmptyDescription();
if (bytes(description).length > 1000) revert DescriptionTooLong(bytes(description).length, 1000);
pid = ++s.proposalIdCounter;
s.proposals[pid] = Proposal(0, proposer, recipient, description, false);
s.activeProposalByRecipient[recipient] = pid;
emit ProposalCreated(pid, proposer, recipient, description);
}
// ---------- Voting ----------
/// @notice Cast a vote on a proposal
/// @param pid Proposal ID
/// @param choice VoteType (Against, For, Abstain)
/// @param weight Amount of voting power to apply (shares, 18 decimals)
/// @param expectedRecipient Expected recipient address to prevent reorganization attacks
/// @custom:security Reentrancy protected; callable only when not paused; only during voting window
function castVote(
uint256 pid,
VoteType choice,
uint256 weight,
address expectedRecipient
) external nonReentrant whenNotPaused {
_executeCastVote(msg.sender, pid, choice, weight, expectedRecipient);
}
/// @notice Cast vote using EIP-712 signature
/// @param voter Address of the voter
/// @param pid Proposal ID
/// @param choice Vote choice (Against, For, Abstain)
/// @param weight Voting weight to use (shares, 18 decimals)
/// @param expectedRecipient Expected recipient address for the proposal
/// @param deadline Expiration timestamp for the signature (seconds)
/// @param v Signature parameter
/// @param r Signature parameter
/// @param s Signature parameter
/// @custom:security Reentrancy protected; callable only when not paused; only during voting window
function castVoteWithSignature(
address voter,
uint256 pid,
VoteType choice,
uint256 weight,
address expectedRecipient,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external nonReentrant whenNotPaused {
uint256 nonce = _getStorage().nonces[voter]++;
_validateSignature(
voter,
keccak256(
abi.encode(CAST_VOTE_TYPEHASH, voter, pid, uint8(choice), weight, expectedRecipient, nonce, deadline)
),
deadline,
v,
r,
s
);
_executeCastVote(voter, pid, choice, weight, expectedRecipient);
}
/// @dev Internal vote execution logic
function _executeCastVote(
address voter,
uint256 pid,
VoteType choice,
uint256 weight,
address expectedRecipient
) private {
AllocationStorage storage s = _getStorage();
// Validate proposal
if (!IBaseAllocationStrategy(address(this)).validateProposalHook(pid)) revert InvalidProposal(pid);
// Check if proposal is canceled
Proposal storage p = s.proposals[pid];
if (p.canceled) revert ProposalCanceledError(pid);
// Verify recipient matches voter's expectation to prevent reorganization attacks
if (p.recipient != expectedRecipient) revert RecipientMismatch(pid, expectedRecipient, p.recipient);
// Cache storage timestamps to avoid multiple reads in error message
uint256 votingStart = s.votingStartTime;
uint256 votingEnd = s.votingEndTime;
// Check voting window
if (block.timestamp < votingStart || block.timestamp > votingEnd) {
revert VotingClosed(block.timestamp, votingStart, votingEnd);
}
uint256 oldPower = s.votingPower[voter];
if (weight == 0) revert InvalidWeight(weight, oldPower);
if (weight > MAX_SAFE_VALUE) revert WeightTooLarge(weight, MAX_SAFE_VALUE);
// Note: weight > oldPower check is redundant with processVoteHook's quadratic cost validation
// The hook will revert with InsufficientVotingPowerForQuadraticCost if weight^2 > oldPower
uint256 newPower = IBaseAllocationStrategy(address(this)).processVoteHook(
pid,
voter,
uint8(choice),
weight,
oldPower
);
if (newPower > oldPower) revert PowerIncreased(oldPower, newPower);
s.votingPower[voter] = newPower;
emit VotesCast(voter, pid, weight);
}
// ---------- Vote Tally Finalization ----------
/// @notice Finalize vote tally once voting period has ended
/// @custom:security Only owner; reentrancy protected
function finalizeVoteTally() external onlyOwner nonReentrant {
AllocationStorage storage s = _getStorage();
if (block.timestamp <= s.votingEndTime) revert VotingNotEnded(block.timestamp, s.votingEndTime);
if (s.tallyFinalized) revert TallyAlreadyFinalized();
if (!IBaseAllocationStrategy(address(this)).beforeFinalizeVoteTallyHook()) revert FinalizationBlocked();
// Set total assets using strategy-specific calculation
// This allows for custom logic like matching pools in quadratic funding
s.totalAssets = IBaseAllocationStrategy(address(this)).calculateTotalAssetsHook();
// Set global redemption start time for all proposals
s.globalRedemptionStart = block.timestamp + s.timelockDelay;
s.globalRedemptionEndTime = s.globalRedemptionStart + s.gracePeriod;
s.tallyFinalizedTime = block.timestamp;
s.tallyFinalized = true;
emit VoteTallyFinalized();
emit GlobalRedemptionPeriodSet(s.globalRedemptionStart, s.globalRedemptionEndTime);
}
// ---------- Queue Proposal ----------
/// @notice Queue proposal and trigger share distribution
/// @param pid Proposal ID to queue
/// @custom:security Reentrancy protected; callable only after tally finalized and before redemption
function queueProposal(uint256 pid) external nonReentrant {
AllocationStorage storage s = _getStorage();
if (!s.tallyFinalized) revert TallyNotFinalized();
// Check if redemption period has started - no new queuing after redemption begins
if (s.globalRedemptionStart != 0 && block.timestamp >= s.globalRedemptionStart) {
revert QueueingClosedAfterRedemption();
}
if (!IBaseAllocationStrategy(address(this)).validateProposalHook(pid)) revert InvalidProposal(pid);
Proposal storage p = s.proposals[pid];
if (p.canceled) revert ProposalCanceledError(pid);
if (!IBaseAllocationStrategy(address(this)).hasQuorumHook(pid)) revert NoQuorum(pid, 0, 0, s.quorumShares);
if (s.proposalShares[pid] != 0) revert AlreadyQueued(pid);
uint256 sharesToMint = IBaseAllocationStrategy(address(this)).convertVotesToShares(pid);
if (sharesToMint == 0) revert NoAllocation(pid, sharesToMint);
s.proposalShares[pid] = sharesToMint;
address recipient = IBaseAllocationStrategy(address(this)).getRecipientAddressHook(pid);
// Try custom distribution hook first
(bool customDistributionHandled, uint256 assetsTransferred) = IBaseAllocationStrategy(address(this))
.requestCustomDistributionHook(recipient, sharesToMint);
// If custom distribution was handled, update totalAssets to reflect assets transferred out
if (customDistributionHandled) {
if (assetsTransferred > s.totalAssets) revert InsufficientAssets(assetsTransferred, s.totalAssets);
s.totalAssets -= assetsTransferred;
} else {
// If custom distribution wasn't handled, mint shares by default
_mint(s, recipient, sharesToMint);
}
emit ProposalQueued(pid, s.globalRedemptionStart, sharesToMint);
}
// ---------- State Machine ----------
/// @notice Get the current state of a proposal
/// @param pid Proposal ID
/// @return Current state of the proposal
function state(uint256 pid) external view returns (ProposalState) {
if (!IBaseAllocationStrategy(address(this)).validateProposalHook(pid)) revert InvalidProposal(pid);
return _state(pid);
}
/// @dev Internal state computation for a proposal with direct time range checks
function _state(uint256 pid) internal view returns (ProposalState) {
AllocationStorage storage s = _getStorage();
Proposal storage p = s.proposals[pid];
if (p.canceled) return ProposalState.Canceled;
// Check if proposal failed quorum (defeated proposals never change state)
if (s.tallyFinalized && !IBaseAllocationStrategy(address(this)).hasQuorumHook(pid)) {
return ProposalState.Defeated;
}
// Before voting starts (Pending or Delay phases)
if (block.timestamp < s.votingStartTime) {
return ProposalState.Pending;
}
// During voting period or before tally finalized
else if (block.timestamp <= s.votingEndTime) {
return ProposalState.Active;
}
// After voting ends but before tally finalized
else if (!s.tallyFinalized) {
return ProposalState.Tallying;
}
uint256 shares = s.proposalShares[pid];
// After tally finalized - check if queued or succeeded
if (s.globalRedemptionStart != 0 && block.timestamp < s.globalRedemptionStart) {
return shares == 0 ? ProposalState.Succeeded : ProposalState.Queued;
}
// During redemption period
else if (s.globalRedemptionEndTime != 0 && block.timestamp <= s.globalRedemptionEndTime) {
return shares == 0 ? ProposalState.Succeeded : ProposalState.Redeemable;
}
// After redemption period (grace period expired)
else {
return ProposalState.Expired;
}
}
// ---------- Proposal Management ----------
/// @notice Cancel a proposal
/// @dev Can only be called before vote tally is finalized. After finalization, all proposals are immutable.
/// @dev This prevents race conditions and ensures coordinators can verify all proposals before committing.
/// @param pid Proposal ID to cancel
function cancelProposal(uint256 pid) external nonReentrant {
AllocationStorage storage s = _getStorage();
// Prevent cancellation after finalization - proposals become immutable
if (s.tallyFinalized) revert TallyAlreadyFinalized();
if (!IBaseAllocationStrategy(address(this)).validateProposalHook(pid)) revert InvalidProposal(pid);
Proposal storage p = s.proposals[pid];
if (msg.sender != p.proposer) revert NotProposer(msg.sender, p.proposer);
if (p.canceled) revert AlreadyCanceled(pid);
p.canceled = true;
uint256 trackedPid = s.activeProposalByRecipient[p.recipient];
if (trackedPid == pid) {
delete s.activeProposalByRecipient[p.recipient];
}
emit ProposalCanceled(pid, p.proposer);
}
// ---------- View Functions ----------
/// @notice Get total number of proposals created
function getProposalCount() external view returns (uint256) {
return _getStorage().proposalIdCounter;
}
// Public getters for storage access
/// @notice Returns the mechanism name
function name() external view returns (string memory) {
return _getStorage().name;
}
/// @notice Returns the mechanism symbol
function symbol() external view returns (string memory) {
return _getStorage().symbol;
}
/// @notice Returns the underlying asset
function asset() external view returns (IERC20) {
return _getStorage().asset;
}
/// @notice Returns the current owner
function owner() external view returns (address) {
return _getStorage().owner;
}
/// @notice Returns the pending owner awaiting acceptance
function pendingOwner() external view returns (address) {
return _getStorage().pendingOwner;
}
/// @notice Returns whether vote tally has been finalized
function tallyFinalized() external view returns (bool) {
return _getStorage().tallyFinalized;
}
/// @notice Returns proposal data for a given proposal ID
function proposals(uint256 pid) external view returns (Proposal memory) {
return _getStorage().proposals[pid];
}
/// @notice Returns the voting power for a user
function votingPower(address user) external view returns (uint256) {
return _getStorage().votingPower[user];
}
/// @notice Returns allocated shares for a proposal
function proposalShares(uint256 pid) external view returns (uint256) {
return _getStorage().proposalShares[pid];
}
// Configuration getters
/// @notice Returns the block number when mechanism was initialized
function startBlock() external view returns (uint256) {
return _getStorage().startBlock;
}
/// @notice Returns the voting delay period in blocks
function votingDelay() external view returns (uint256) {
return _getStorage().votingDelay;
}
/// @notice Returns the voting period duration in blocks
function votingPeriod() external view returns (uint256) {
return _getStorage().votingPeriod;
}
/// @notice Returns the minimum shares required for quorum
function quorumShares() external view returns (uint256) {
return _getStorage().quorumShares;
}
/// @notice Returns the timelock delay in seconds
function timelockDelay() external view returns (uint256) {
return _getStorage().timelockDelay;
}
/// @notice Returns the grace period duration in seconds
function gracePeriod() external view returns (uint256) {
return _getStorage().gracePeriod;
}
/// @notice Returns the global redemption start timestamp
function globalRedemptionStart() external view returns (uint256) {
return _getStorage().globalRedemptionStart;
}
/// @notice Returns the voting start timestamp
function votingStartTime() external view returns (uint256) {
return _getStorage().votingStartTime;
}
/// @notice Returns the voting end timestamp
function votingEndTime() external view returns (uint256) {
return _getStorage().votingEndTime;
}
/// @notice Returns the mechanism start timestamp
function startTime() external view returns (uint256) {
return _getStorage().startTime;
}
/// @notice Returns the current nonce for an address
/// @param account Address to check nonce for
/// @return Current nonce for permit operations
function nonces(address account) external view returns (uint256) {
return _getStorage().nonces[account];
}
// ---------- Emergency Functions ----------
/// @notice Initiate ownership transfer to a new address (step 1 of 2)
/// @param newOwner Address to transfer ownership to
function transferOwnership(address newOwner) external onlyOwner {
if (newOwner == address(0)) revert Unauthorized();
AllocationStorage storage s = _getStorage();
s.pendingOwner = newOwner;
emit OwnershipTransferInitiated(s.owner, newOwner);
}
/// @notice Accept ownership transfer (step 2 of 2)
/// @dev Must be called by the pending owner to complete the transfer
function acceptOwnership() external {
AllocationStorage storage s = _getStorage();
address pending = s.pendingOwner;
if (msg.sender != pending) revert Unauthorized();
address oldOwner = s.owner;
s.owner = pending;
s.pendingOwner = address(0);
emit OwnershipTransferred(oldOwner, pending);
}
/// @notice Cancel pending ownership transfer
/// @dev Can only be called by current owner
function cancelOwnershipTransfer() external onlyOwner {
AllocationStorage storage s = _getStorage();
if (s.pendingOwner == address(0)) revert Unauthorized();
address canceledPendingOwner = s.pendingOwner;
s.pendingOwner = address(0);
emit OwnershipTransferCanceled(s.owner, canceledPendingOwner);
}
/// @notice Update keeper address
function setKeeper(address newKeeper) external onlyOwner {
if (newKeeper == address(0)) revert Unauthorized();
AllocationStorage storage s = _getStorage();
address oldKeeper = s.keeper;
s.keeper = newKeeper;
emit KeeperUpdated(oldKeeper, newKeeper);
}
/// @notice Update management address
function setManagement(address newManagement) external onlyOwner {
if (newManagement == address(0)) revert Unauthorized();
AllocationStorage storage s = _getStorage();
address oldManagement = s.management;
s.management = newManagement;
emit ManagementUpdated(oldManagement, newManagement);
}
/// @notice Emergency pause all operations
function pause() external onlyOwner {
AllocationStorage storage s = _getStorage();
s.paused = true;
emit PausedStatusChanged(true);
}
/// @notice Resume operations after pause
function unpause() external onlyOwner {
AllocationStorage storage s = _getStorage();
s.paused = false;
emit PausedStatusChanged(false);
}
/// @notice Check if contract is paused
function paused() external view returns (bool) {
return _getStorage().paused;
}
/// @notice Sweep remaining tokens after grace period expires
/// @dev Can only be called by owner after global grace period ends
/// @param token Token to sweep (use address(0) for ETH)
/// @param receiver Address to receive swept tokens
function sweep(address token, address receiver) external onlyOwner nonReentrant {
AllocationStorage storage s = _getStorage();
// Ensure grace period has expired for everyone
require(s.globalRedemptionStart != 0, "Redemption period not started");
require(block.timestamp > s.globalRedemptionEndTime, "Grace period not expired");
require(receiver != address(0), "Invalid receiver");
if (token == address(0)) {
// Sweep ETH
uint256 balance = address(this).balance;
require(balance > 0, "No ETH to sweep");
(bool success, ) = receiver.call{ value: balance }("");
require(success, "ETH transfer failed");
emit Swept(token, receiver, balance);
} else {
// Sweep any ERC20 token
IERC20 tokenContract = IERC20(token);
uint256 balance = tokenContract.balanceOf(address(this));
require(balance > 0, "No tokens to sweep");
tokenContract.safeTransfer(receiver, balance);
emit Swept(token, receiver, balance);
}
}
/*//////////////////////////////////////////////////////////////
ALLOCATION VAULT FUNCTIONALITY
//////////////////////////////////////////////////////////////*/
/**
* @notice Redeems exactly `shares` from `shareOwner` and
* sends `assets` of underlying tokens to `receiver`.
* @param shares Amount of shares to burn
* @param receiver Address to receive withdrawn assets
* @param shareOwner Address whose shares are burned
* @return assetsWithdrawn Actual amount of underlying withdrawn in asset base units
* @dev Reverts with "ZERO_ASSETS" if shares amount rounds to 0 assets
* @dev Reverts with "redeem more than max" if shares > maxRedeem(shareOwner)
*/
function redeem(uint256 shares, address receiver, address shareOwner) external nonReentrant returns (uint256) {
// Get the storage slot for all following calls.
AllocationStorage storage S = _getStorage();
require(shares <= _maxRedeem(S, shareOwner), "Allocation: redeem more than max");
// slither-disable-next-line uninitialized-local
uint256 assets;
// Check for rounding error or 0 value.
require((assets = _convertToAssets(S, shares, Math.Rounding.Floor)) != 0, "ZERO_ASSETS");
// We need to return the actual amount withdrawn.
return _withdraw(S, receiver, shareOwner, assets, shares);
}
/**
* @notice Get the total amount of assets this strategy holds
* as of the last report.
*
* We manually track `totalAssets` to avoid any PPS manipulation.
*
* @return totalAssets_ Total assets the strategy holds.
*/
function totalAssets() external view returns (uint256) {
return _totalAssets(_getStorage());
}
/**
* @notice Get the current supply of the strategies shares.
*
* Locked shares issued to the strategy from profits are not
* counted towards the full supply until they are unlocked.
*
* As more shares slowly unlock the totalSupply will decrease
* causing the PPS of the strategy to increase.
*
* @return totalSupply_ Total amount of shares outstanding.
*/
function totalSupply() external view returns (uint256) {
return _totalSupply(_getStorage());
}
/**
* @notice The amount of shares that the strategy would
* exchange for the amount of assets provided, in an
* ideal scenario where all the conditions are met.
*
* @param assets Amount of underlying assets
* @return shares_ Expected shares that assets represent
*/
function convertToShares(uint256 assets) external view returns (uint256) {
return _convertToShares(_getStorage(), assets, Math.Rounding.Floor);
}
/**
* @notice The amount of assets that the strategy would
* exchange for the amount of shares provided, in an
* ideal scenario where all the conditions are met.
*
* @param shares Amount of strategy shares
* @return assets_ Expected assets the shares represent in asset base units
*/
function convertToAssets(uint256 shares) external view returns (uint256) {
return _convertToAssets(_getStorage(), shares, Math.Rounding.Floor);
}
/**
* @notice Allows an on-chain or off-chain user to simulate
* the effects of their redemption at the current block,
* given current on-chain conditions.
* @dev This will round down.
*
* @param shares Amount of shares to redeem
* @return assets_ Amount of assets that would be returned in asset base units
*/
function previewRedeem(uint256 shares) external view returns (uint256) {
AllocationStorage storage s = _getStorage();
// Return 0 if outside redemption period [t_r_start, t_r_end]
if (s.globalRedemptionStart == 0 || block.timestamp < s.globalRedemptionStart) {
return 0; // Before redemption period starts
}
if (s.globalRedemptionEndTime != 0 && block.timestamp > s.globalRedemptionEndTime) {
return 0; // After redemption period ends
}
return _convertToAssets(s, shares, Math.Rounding.Floor);
}
/**
* @notice Total number of strategy shares that can be
* redeemed from the strategy by `shareOwner`, where `shareOwner`
* corresponds to the msg.sender of a {redeem} call.
*
* @param shareOwner Address that owns the shares
* @return _maxRedeem Maximum shares that can be redeemed
*/
function maxRedeem(address shareOwner) external view returns (uint256) {
return _maxRedeem(_getStorage(), shareOwner);
}
/// @notice Returns the management address
function management() external view returns (address) {
return _getStorage().management;
}
/// @notice Returns the keeper address
function keeper() external view returns (address) {
return _getStorage().keeper;
}
/// @notice Returns the decimals used for the token (always 18)
function decimals() external pure returns (uint8) {
return 18;
}
/// @notice Returns the balance of an account
function balanceOf(address account) external view returns (uint256) {
return _balanceOf(_getStorage(), account);
}
/// @notice Returns the allowance of a spender for a token owner
function allowance(address tokenOwner, address spender) external view returns (uint256) {
return _allowance(_getStorage(), tokenOwner, spender);
}
/*//////////////////////////////////////////////////////////////
INTERNAL VAULT VIEW METHODS
//////////////////////////////////////////////////////////////*/
/// @dev Internal implementation of {totalAssets}.
function _totalAssets(AllocationStorage storage S) internal view returns (uint256) {
return S.totalAssets;
}
/// @dev Internal implementation of {totalSupply}.
function _totalSupply(AllocationStorage storage S) internal view returns (uint256) {
return S.totalSupply;
}
/// @dev Internal implementation of {convertToShares}.
function _convertToShares(
AllocationStorage storage S,
uint256 assets,
Math.Rounding _rounding
) internal view returns (uint256) {
// Saves an extra SLOAD if values are non-zero.
uint256 totalSupply_ = _totalSupply(S);
// If supply is 0, convert assets from asset decimals to 18 decimals (share decimals)
if (totalSupply_ == 0) {
uint8 assetDecimals = S.decimals;
if (assetDecimals == 18) {
return assets;
} else if (assetDecimals < 18) {
// Scale up: multiply by 10^(18 - assetDecimals)
uint256 scaleFactor = 10 ** (18 - assetDecimals);
return assets * scaleFactor;
} else {
// Scale down: divide by 10^(assetDecimals - 18)
uint256 scaleFactor = 10 ** (assetDecimals - 18);
return assets / scaleFactor;
}
}
uint256 totalAssets_ = _totalAssets(S);
// If assets are 0 but supply is not PPS = 0.
if (totalAssets_ == 0) return 0;
return assets.mulDiv(totalSupply_, totalAssets_, _rounding);
}
/// @dev Internal implementation of {convertToAssets}.
function _convertToAssets(
AllocationStorage storage S,
uint256 shares,
Math.Rounding _rounding
) internal view returns (uint256) {
// Saves an extra SLOAD if totalSupply() is non-zero.
uint256 supply = _totalSupply(S);
if (supply == 0) {
// Convert shares from 18 decimals to asset decimals
uint8 assetDecimals = S.decimals;
if (assetDecimals == 18) {
return shares;
} else if (assetDecimals < 18) {
// Scale down: divide by 10^(18 - assetDecimals)
uint256 scaleFactor = 10 ** (18 - assetDecimals);
return shares / scaleFactor;
} else {
// Scale up: multiply by 10^(assetDecimals - 18)
uint256 scaleFactor = 10 ** (assetDecimals - 18);
return shares * scaleFactor;
}
}
return shares.mulDiv(_totalAssets(S), supply, _rounding);
}
/// @dev Internal implementation of {maxRedeem}.
function _maxRedeem(AllocationStorage storage S, address shareOwner) internal view returns (uint256 maxRedeem_) {
// Get the max the owner could withdraw currently.
maxRedeem_ = IBaseAllocationStrategy(address(this)).availableWithdrawLimit(shareOwner);
// Conversion would overflow and saves a min check if there is no withdrawal limit.
if (maxRedeem_ == type(uint256).max) {
maxRedeem_ = _balanceOf(S, shareOwner);
} else {
maxRedeem_ = Math.min(
// Can't redeem more than the balance.
_convertToShares(S, maxRedeem_, Math.Rounding.Floor),
_balanceOf(S, shareOwner)
);
}
}
/// @dev Internal implementation of {balanceOf}.
function _balanceOf(AllocationStorage storage S, address account) internal view returns (uint256) {
return S.balances[account];
}
/// @dev Internal implementation of {allowance}.
function _allowance(
AllocationStorage storage S,
address tokenOwner,
address spender
) internal view returns (uint256) {
return S.allowances[tokenOwner][spender];
}
/*//////////////////////////////////////////////////////////////
INTERNAL VAULT WRITE METHODS
//////////////////////////////////////////////////////////////*/
/**
* @dev To be called during {redeem} and {withdraw}.
*
* This will handle all logic, transfers and accounting
* in order to service the withdraw request.
*/
function _withdraw(
AllocationStorage storage S,
address receiver,
address shareOwner,
uint256 assets,
uint256 shares
) internal returns (uint256) {
require(receiver != address(0), "ZERO ADDRESS");
// Spend allowance if applicable.
if (msg.sender != shareOwner) {
_spendAllowance(S, shareOwner, msg.sender, shares);
}
// Cache `asset` since it is used multiple times..
ERC20 _asset = ERC20(address(S.asset));
// Ensure sufficient balance for withdrawal
uint256 idle = _asset.balanceOf(address(this));
require(idle >= assets, "Insufficient balance for withdrawal");
// Update assets based on how much we took.
S.totalAssets -= assets;
_burn(S, shareOwner, shares);
// Transfer the amount of underlying to the receiver.
_asset.safeTransfer(receiver, assets);
emit Withdraw(msg.sender, receiver, shareOwner, assets, shares);
// Return the actual amount of assets withdrawn.
return assets;
}
/*//////////////////////////////////////////////////////////////
ERC20 METHODS
//////////////////////////////////////////////////////////////*/
/**
* @notice Transfer '_amount` of shares from `msg.sender` to `to`.
* @dev
* Requirements:
*
* - `to` cannot be the zero address.
* - `to` cannot be the address of the strategy.
* - the caller must have a balance of at least `_amount`.
*
* @param to Address receiving the shares
* @param amount Amount of shares to transfer
* @return success True if operation succeeded
*/
function transfer(address to, uint256 amount) external returns (bool) {
_transfer(_getStorage(), msg.sender, to, amount);
return true;
}
/**
* @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
* @dev
*
* NOTE: If `amount` 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.
*
* 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.
*
* @param spender the address to allow the shares to be moved by.
* @param amount the amount of shares to allow `spender` to move.
* @return success True if the operation succeeded.
*/
function approve(address spender, uint256 amount) external returns (bool) {
_approve(_getStorage(), msg.sender, spender, amount);
return true;
}
/**
* @notice `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* @dev
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `to` cannot be the address of the strategy.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*
* Emits a {Transfer} event.
*
* @param from the address to be moving shares from.
* @param to the address to be moving shares to.
* @param amount the quantity of shares to move.
* @return success True if the operation succeeded.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
AllocationStorage storage S = _getStorage();
_spendAllowance(S, from, msg.sender, amount);
_transfer(S, from, to, amount);
return true;
}
/**
* @dev Moves `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.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `to` cannot be the strategies address
* - `from` must have a balance of at least `amount`.
*
*/
function _transfer(AllocationStorage storage S, address from, address to, uint256 amount) internal {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
require(to != address(this), "ERC20 transfer to strategy");
// Only allow transfers during redemption period [globalRedemptionStart, globalRedemptionEndTime]
// Before finalization: globalRedemptionEndTime is 0, so block.timestamp > 0 blocks transfers
// After finalization: both timestamps are set, creating the valid redemption window
if (block.timestamp < S.globalRedemptionStart || block.timestamp > S.globalRedemptionEndTime) {
revert("Transfers only allowed during redemption period");
}
S.balances[from] -= amount;
unchecked {
S.balances[to] += amount;
}
emit Transfer(from, to, amount);
}
/**
* @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*
*/
function _mint(AllocationStorage storage S, address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
S.totalSupply += amount;
unchecked {
S.balances[account] += amount;
}
emit Transfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(AllocationStorage storage S, address account, uint256 amount) internal {
require(account != address(0), "ERC20: burn from the zero address");
S.balances[account] -= amount;
unchecked {
S.totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
}
/**
* @dev Sets `amount` 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.
*/
function _approve(AllocationStorage storage S, address tokenOwner, address spender, uint256 amount) internal {
require(tokenOwner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
S.allowances[tokenOwner][spender] = amount;
emit Approval(tokenOwner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(
AllocationStorage storage S,
address tokenOwner,
address spender,
uint256 amount
) internal {
uint256 currentAllowance = _allowance(S, tokenOwner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(S, tokenOwner, spender, currentAllowance - amount);
}
}
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.20;
import { QuadraticVotingMechanism } from "./QuadraticVotingMechanism.sol";
import { IAddressSet } from "src/utils/IAddressSet.sol";
import { AccessMode } from "src/constants.sol";
import { AllocationConfig, TokenizedAllocationMechanism } from "src/mechanisms/BaseAllocationMechanism.sol";
import { NotInAllowset, InBlockset } from "src/errors.sol";
/// @title Octant Quadratic Funding Mechanism
/// @author [Golem Foundation](https://golem.foundation)
/// @custom:security-contact [email protected]
/// @notice Quadratic funding mechanism with configurable signup access control.
/// @dev Extends `QuadraticVotingMechanism` and integrates allowset/blockset access modes
/// to restrict who can register during contribution windows. Owner-only control via
/// underlying `TokenizedAllocationMechanism` ownership checks.
contract OctantQFMechanism is QuadraticVotingMechanism {
/// @notice Current access mode for signup eligibility (NONE, ALLOWSET, BLOCKSET)
AccessMode public contributionAccessMode;
/// @notice Address set used when `contributionAccessMode == ALLOWSET`
IAddressSet public contributionAllowset;
/// @notice Address set used when `contributionAccessMode == BLOCKSET`
IAddressSet public contributionBlockset;
/// @notice Emitted when the allowset contract is assigned
/// @param allowset New allowset contract
event ContributionAllowsetAssigned(IAddressSet indexed allowset);
/// @notice Emitted when the blockset contract is assigned
/// @param blockset New blockset contract
event ContributionBlocksetAssigned(IAddressSet indexed blockset);
/// @notice Emitted when the contribution access mode is updated
/// @param mode New access mode (NONE, ALLOWSET, BLOCKSET)
event AccessModeSet(AccessMode indexed mode);
/// @notice Construct a new OctantQF mechanism
/// @param _implementation Address of shared TokenizedAllocationMechanism implementation
/// @param _config Allocation configuration struct
/// @param _alphaNumerator Alpha numerator (dimensionless; 1.0 = denominator)
/// @param _alphaDenominator Alpha denominator (must be > 0)
/// @param _contributionAllowset Address set used in ALLOWSET mode
/// @param _contributionBlockset Address set used in BLOCKSET mode
/// @param _contributionAccessMode Initial access mode (NONE, ALLOWSET, BLOCKSET)
constructor(
address _implementation,
AllocationConfig memory _config,
uint256 _alphaNumerator,
uint256 _alphaDenominator,
IAddressSet _contributionAllowset,
IAddressSet _contributionBlockset,
AccessMode _contributionAccessMode
) QuadraticVotingMechanism(_implementation, _config, _alphaNumerator, _alphaDenominator) {
contributionAllowset = _contributionAllowset;
contributionBlockset = _contributionBlockset;
contributionAccessMode = _contributionAccessMode;
emit ContributionAllowsetAssigned(_contributionAllowset);
emit ContributionBlocksetAssigned(_contributionBlockset);
emit AccessModeSet(_contributionAccessMode);
}
/// @notice Hook to validate user eligibility during signup
/// @param user Address attempting to register
/// @return True if registration should proceed
/// @dev Reverts with specific error messages for unauthorized users
function _beforeSignupHook(address user) internal view virtual override returns (bool) {
if (!_isUserAuthorized(user)) {
if (contributionAccessMode == AccessMode.ALLOWSET) {
revert NotInAllowset(user);
} else {
revert InBlockset(user);
}
}
return true;
}
/// @dev Internal helper to check access control without reverting
/// @param user Address to check
/// @return True if user passes access control checks, false otherwise
function _isUserAuthorized(address user) internal view returns (bool) {
if (contributionAccessMode == AccessMode.ALLOWSET) {
return contributionAllowset.contains(user);
} else if (contributionAccessMode == AccessMode.BLOCKSET) {
return !contributionBlockset.contains(user);
}
return true;
}
/// @notice Sets the contribution allowset (for ALLOWSET mode)
/// @param _allowset New allowset contract address
/// @dev Non-retroactive. Existing voting power is not affected.
/// @custom:security Only owner via underlying mechanism ownership check
function setContributionAllowset(IAddressSet _allowset) external {
require(_tokenizedAllocation().owner() == msg.sender, "Only owner");
contributionAllowset = _allowset;
emit ContributionAllowsetAssigned(_allowset);
}
/// @notice Sets the contribution blockset (for BLOCKSET mode)
/// @param _blockset New blockset contract address
/// @dev Non-retroactive. Existing voting power is not affected.
/// @custom:security Only owner via underlying mechanism ownership check
function setContributionBlockset(IAddressSet _blockset) external {
require(_tokenizedAllocation().owner() == msg.sender, "Only owner");
contributionBlockset = _blockset;
emit ContributionBlocksetAssigned(_blockset);
}
/// @notice Sets the contribution access mode
/// @param _mode New access mode (NONE, ALLOWSET, or BLOCKSET)
/// @dev Only allowed before voting starts or after tally finalization.
/// Non-retroactive. Existing voting power is not affected.
/// @custom:security Only owner via underlying mechanism; blocked during active voting
function setAccessMode(AccessMode _mode) external {
TokenizedAllocationMechanism tam = _tokenizedAllocation();
require(tam.owner() == msg.sender, "Only owner");
// Safety check: Prevent mode switching during active voting or before finalization
// This prevents attackers from gaining voting power mid-vote or front-running mode switches
bool beforeVoting = block.timestamp < tam.votingStartTime();
bool afterFinalization = tam.tallyFinalized();
require(
beforeVoting || afterFinalization,
"Mode changes only allowed before voting starts or after tally finalization"
);
contributionAccessMode = _mode;
emit AccessModeSet(_mode);
}
/// @notice Checks if a user is eligible to signup/contribute based on current access mode
/// @dev Required for allocation mechanism to be compatible with RegenStaker.
/// Used for defense-in-depth checks. Respects contributionAccessMode:
/// NONE: always returns true
/// ALLOWSET: returns true if user is in contributionAllowset
/// BLOCKSET: returns true if user is NOT in contributionBlockset
/// @param user Address to check
/// @return canSignup_ True if user can signup, false otherwise
function canSignup(address user) external view returns (bool) {
return _isUserAuthorized(user);
}
}// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.25; // Centralized custom errors for gas-efficient reverts across Octant contracts. // Using custom errors saves ~50 bytes per error vs require(condition, "string"). error Unauthorized(); error ZeroAddress(); error ReentrancyGuard__ReentrantCall(); error ZeroShares(); error ZeroAssets(); error ERC20InsufficientBalance(); error AlreadyInitialized(); error NotInAllowset(address user); error InBlockset(address user); error TokenizedStrategy__NotEmergencyAuthorized(); error TokenizedStrategy__NotKeeperOrManagement(); error TokenizedStrategy__NotOperator(); error TokenizedStrategy__NotManagement(); error TokenizedStrategy__NotRegenGovernance(); error TokenizedStrategy__AlreadyInitialized(); error TokenizedStrategy__DepositMoreThanMax(); error TokenizedStrategy__MintMoreThanMax(); error TokenizedStrategy__InvalidMaxLoss(); error TokenizedStrategy__TransferFromZeroAddress(); error TokenizedStrategy__TransferToZeroAddress(); error TokenizedStrategy__TransferToStrategy(); error TokenizedStrategy__MintToZeroAddress(); error TokenizedStrategy__BurnFromZeroAddress(); error TokenizedStrategy__ApproveFromZeroAddress(); error TokenizedStrategy__ApproveToZeroAddress(); error TokenizedStrategy__InsufficientAllowance(); error TokenizedStrategy__PermitDeadlineExpired(); error TokenizedStrategy__InvalidSigner(); error TokenizedStrategy__TransferFailed(); error TokenizedStrategy__NotSelf(); error TokenizedStrategy__WithdrawMoreThanMax(); error TokenizedStrategy__RedeemMoreThanMax(); error TokenizedStrategy__NotPendingManagement(); error TokenizedStrategy__StrategyNotInShutdown(); error TokenizedStrategy__TooMuchLoss(); error BaseStrategy__NotSelf();
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol)
pragma solidity ^0.8.20;
import {Strings} from "../Strings.sol";
/**
* @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
*
* The library provides methods for generating a hash of a message that conforms to the
* https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
* specifications.
*/
library MessageHashUtils {
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing a bytes32 `messageHash` with
* `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
* keccak256, although any bytes32 value can be safely used because the final digest will
* be re-hashed.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
}
}
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing an arbitrary `message` with
* `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
}
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x00` (data with intended validator).
*
* The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
* `validator` address. Then hashing the result.
*
* See {ECDSA-recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(hex"19_00", validator, data));
}
/**
* @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.
*/
function toDataWithIntendedValidatorHash(
address validator,
bytes32 messageHash
) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, hex"19_00")
mstore(0x02, shl(96, validator))
mstore(0x16, messageHash)
digest := keccak256(0x00, 0x36)
}
}
/**
* @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).
*
* The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
* `\x19\x01` and hashing the result. It corresponds to the hash signed by the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
*
* See {ECDSA-recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/ShortStrings.sol)
pragma solidity ^0.8.20;
import {StorageSlot} from "./StorageSlot.sol";
// | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
// | length | 0x BB |
type ShortString is bytes32;
/**
* @dev This library provides functions to convert short memory strings
* into a `ShortString` type that can be used as an immutable variable.
*
* Strings of arbitrary length can be optimized using this library if
* they are short enough (up to 31 bytes) by packing them with their
* length (1 byte) in a single EVM word (32 bytes). Additionally, a
* fallback mechanism can be used for every other case.
*
* Usage example:
*
* ```solidity
* contract Named {
* using ShortStrings for *;
*
* ShortString private immutable _name;
* string private _nameFallback;
*
* constructor(string memory contractName) {
* _name = contractName.toShortStringWithFallback(_nameFallback);
* }
*
* function name() external view returns (string memory) {
* return _name.toStringWithFallback(_nameFallback);
* }
* }
* ```
*/
library ShortStrings {
// Used as an identifier for strings longer than 31 bytes.
bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;
error StringTooLong(string str);
error InvalidShortString();
/**
* @dev Encode a string of at most 31 chars into a `ShortString`.
*
* This will trigger a `StringTooLong` error is the input string is too long.
*/
function toShortString(string memory str) internal pure returns (ShortString) {
bytes memory bstr = bytes(str);
if (bstr.length > 31) {
revert StringTooLong(str);
}
return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
}
/**
* @dev Decode a `ShortString` back to a "normal" string.
*/
function toString(ShortString sstr) internal pure returns (string memory) {
uint256 len = byteLength(sstr);
// using `new string(len)` would work locally but is not memory safe.
string memory str = new string(32);
assembly ("memory-safe") {
mstore(str, len)
mstore(add(str, 0x20), sstr)
}
return str;
}
/**
* @dev Return the length of a `ShortString`.
*/
function byteLength(ShortString sstr) internal pure returns (uint256) {
uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
if (result > 31) {
revert InvalidShortString();
}
return result;
}
/**
* @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
*/
function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
if (bytes(value).length < 32) {
return toShortString(value);
} else {
StorageSlot.getStringSlot(store).value = value;
return ShortString.wrap(FALLBACK_SENTINEL);
}
}
/**
* @dev Decode a string that was encoded to `ShortString` or written to storage using {toShortStringWithFallback}.
*/
function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
return toString(value);
} else {
return store;
}
}
/**
* @dev Return the length of a string that was encoded to `ShortString` or written to storage using
* {toShortStringWithFallback}.
*
* WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
* actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
*/
function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
return byteLength(value);
} else {
return bytes(store).length;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)
pragma solidity ^0.8.20;
interface IERC5267 {
/**
* @dev MAY be emitted to signal that the domain could have changed.
*/
event EIP712DomainChanged();
/**
* @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
* signature.
*/
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/// @title DelegationSurrogate
/// @author [ScopeLift](https://scopelift.co)
/// @notice A dead-simple contract whose only purpose is to hold ERC20 tokens which can always be
/// moved by the Surrogate's deployer.
abstract contract DelegationSurrogate {
/// @param _token The token that will be held by this surrogate.
constructor(IERC20 _token) {
_token.approve(msg.sender, type(uint256).max);
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/// @title INotifiableRewardReceiver
/// @author [ScopeLift](https://scopelift.co)
/// @notice The communication interface between contracts that distribute rewards and the
/// Staker contract. In particular, said contracts only need to know the staker
/// implements the specified methods in order to forward payouts to the staker contract. The
/// Staker contract receives the rewards and abstracts the distribution mechanics.
interface INotifiableRewardReceiver {
/// @notice ERC20 token in which rewards are denominated and distributed.
function REWARD_TOKEN() external view returns (IERC20);
/// @notice Method called to notify a reward receiver it has received a reward.
/// @param _amount The amount of reward.
function notifyRewardAmount(uint256 _amount) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Multicall.sol)
pragma solidity ^0.8.20;
import {Address} from "./Address.sol";
import {Context} from "./Context.sol";
/**
* @dev Provides a function to batch together multiple calls in a single external call.
*
* Consider any assumption about calldata validation performed by the sender may be violated if it's not especially
* careful about sending transactions invoking {multicall}. For example, a relay address that filters function
* selectors won't filter calls nested within a {multicall} operation.
*
* NOTE: Since 5.0.1 and 4.9.4, this contract identifies non-canonical contexts (i.e. `msg.sender` is not {Context-_msgSender}).
* If a non-canonical context is identified, the following self `delegatecall` appends the last bytes of `msg.data`
* to the subcall. This makes it safe to use with {ERC2771Context}. Contexts that don't affect the resolution of
* {Context-_msgSender} are not propagated to subcalls.
*/
abstract contract Multicall is Context {
/**
* @dev Receives and executes a batch of function calls on this contract.
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
bytes memory context = msg.sender == _msgSender()
? new bytes(0)
: msg.data[msg.data.length - _contextSuffixLength():];
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
results[i] = Address.functionDelegateCall(address(this), bytes.concat(data[i], context));
}
return results;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
pragma solidity ^0.8.20;
/**
* @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeCast {
/**
* @dev Value doesn't fit in 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);
}
/**
* @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
*/
function toUint(bool b) internal pure returns (uint256 u) {
assembly ("memory-safe") {
u := iszero(iszero(b))
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/SignatureChecker.sol)
pragma solidity ^0.8.20;
import {ECDSA} from "./ECDSA.sol";
import {IERC1271} from "../../interfaces/IERC1271.sol";
/**
* @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA
* signatures from externally owned accounts (EOAs) as well as ERC-1271 signatures from smart contract wallets like
* Argent and Safe Wallet (previously Gnosis Safe).
*/
library SignatureChecker {
/**
* @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the
* signature is validated against that smart contract using ERC-1271, otherwise it's validated using `ECDSA.recover`.
*
* NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
* change through time. It could return true at block N and false at block N+1 (or the opposite).
*/
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
if (signer.code.length == 0) {
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature);
return err == ECDSA.RecoverError.NoError && recovered == signer;
} else {
return isValidERC1271SignatureNow(signer, hash, signature);
}
}
/**
* @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
* against the signer smart contract using ERC-1271.
*
* NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
* change through time. It could return true at block N and false at block N+1 (or the opposite).
*/
function isValidERC1271SignatureNow(
address signer,
bytes32 hash,
bytes memory signature
) internal view returns (bool) {
(bool success, bytes memory result) = signer.staticcall(
abi.encodeCall(IERC1271.isValidSignature, (hash, signature))
);
return (success &&
result.length >= 32 &&
abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Nonces.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides tracking nonces for addresses. Nonces will only increment.
*/
abstract contract Nonces {
/**
* @dev The nonce used for an `account` is not the expected current nonce.
*/
error InvalidAccountNonce(address account, uint256 currentNonce);
mapping(address account => uint256) private _nonces;
/**
* @dev Returns the next unused nonce for an address.
*/
function nonces(address owner) public view virtual returns (uint256) {
return _nonces[owner];
}
/**
* @dev Consumes a nonce.
*
* Returns the current value and increments nonce.
*/
function _useNonce(address owner) internal virtual returns (uint256) {
// For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
// decremented or reset. This guarantees that the nonce never overflows.
unchecked {
// It is important to do x++ and not ++x here.
return _nonces[owner]++;
}
}
/**
* @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
*/
function _useCheckedNonce(address owner, uint256 nonce) internal virtual {
uint256 current = _useNonce(owner);
if (nonce != current) {
revert InvalidAccountNonce(owner, current);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.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 ERC-20
* applications.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* Both values are immutable: they can only be set once during construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
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) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
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) {
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}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* 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 {
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:
*
* ```solidity
* 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 {
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.3.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Return the 512-bit addition of two uint256.
*
* The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
*/
function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
assembly ("memory-safe") {
low := add(a, b)
high := lt(low, a)
}
}
/**
* @dev Return the 512-bit multiplication of two uint256.
*
* The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
*/
function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
// 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
// the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = high * 2²⁵⁶ + low.
assembly ("memory-safe") {
let mm := mulmod(a, b, not(0))
low := mul(a, b)
high := sub(sub(mm, low), lt(mm, low))
}
}
/**
* @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a + b;
success = c >= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a - b;
success = c <= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a * b;
assembly ("memory-safe") {
// Only true when the multiplication doesn't overflow
// (c / a == b) || (a == 0)
success := or(eq(div(c, a), b), iszero(a))
}
// equivalent to: success ? c : 0
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `DIV` opcode returns zero when the denominator is 0.
result := div(a, b)
}
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `MOD` opcode returns zero when the denominator is 0.
result := mod(a, b)
}
}
}
/**
* @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryAdd(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
*/
function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
(, uint256 result) = trySub(a, b);
return result;
}
/**
* @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryMul(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * SafeCast.toUint(condition));
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
Panic.panic(Panic.DIVISION_BY_ZERO);
}
// The following calculation ensures accurate ceiling division without overflow.
// Since a is non-zero, (a - 1) / b will not overflow.
// The largest possible result occurs when (a - 1) / b is type(uint256).max,
// but the largest value we can obtain is type(uint256).max - 1, which happens
// when a = type(uint256).max and b = 1.
unchecked {
return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
}
}
/**
* @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
*
* Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
// Handle non-overflow cases, 256 by 256 division.
if (high == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return low / denominator;
}
// Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
if (denominator <= high) {
Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [high low].
uint256 remainder;
assembly ("memory-safe") {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
high := sub(high, gt(remainder, low))
low := sub(low, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly ("memory-safe") {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [high low] by twos.
low := div(low, twos)
// Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from high into low.
low |= high * twos;
// Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
// that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv ≡ 1 mod 2⁴.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2⁸
inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
inverse *= 2 - denominator * inverse; // inverse mod 2³²
inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
// less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
// is no longer required.
result = low * inverse;
return result;
}
}
/**
* @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
}
/**
* @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
*/
function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
if (high >= 1 << n) {
Panic.panic(Panic.UNDER_OVERFLOW);
}
return (high << (256 - n)) | (low >> n);
}
}
/**
* @dev Calculates x * y >> n with full precision, following the selected rounding direction.
*/
function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
}
/**
* @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
*
* If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
* If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
*
* If the input value is not inversible, 0 is returned.
*
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
* inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
*/
function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
unchecked {
if (n == 0) return 0;
// The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
// Used to compute integers x and y such that: ax + ny = gcd(a, n).
// When the gcd is 1, then the inverse of a modulo n exists and it's x.
// ax + ny = 1
// ax = 1 + (-y)n
// ax ≡ 1 (mod n) # x is the inverse of a modulo n
// If the remainder is 0 the gcd is n right away.
uint256 remainder = a % n;
uint256 gcd = n;
// Therefore the initial coefficients are:
// ax + ny = gcd(a, n) = n
// 0a + 1n = n
int256 x = 0;
int256 y = 1;
while (remainder != 0) {
uint256 quotient = gcd / remainder;
(gcd, remainder) = (
// The old remainder is the next gcd to try.
remainder,
// Compute the next remainder.
// Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
// where gcd is at most n (capped to type(uint256).max)
gcd - remainder * quotient
);
(x, y) = (
// Increment the coefficient of a.
y,
// Decrement the coefficient of n.
// Can overflow, but the result is casted to uint256 so that the
// next value of y is "wrapped around" to a value between 0 and n - 1.
x - y * int256(quotient)
);
}
if (gcd != 1) return 0; // No inverse exists.
return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
}
}
/**
* @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
*
* From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
* prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
* `a**(p-2)` is the modular multiplicative inverse of a in Fp.
*
* NOTE: this function does NOT check that `p` is a prime greater than `2`.
*/
function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
unchecked {
return Math.modExp(a, p - 2, p);
}
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
*
* Requirements:
* - modulus can't be zero
* - underlying staticcall to precompile must succeed
*
* IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
* sure the chain you're using it on supports the precompiled contract for modular exponentiation
* at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
* the underlying function will succeed given the lack of a revert, but the result may be incorrectly
* interpreted as 0.
*/
function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
(bool success, uint256 result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
* It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
* to operate modulo 0 or if the underlying precompile reverted.
*
* IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
* you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
* https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
* of a revert, but the result may be incorrectly interpreted as 0.
*/
function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
if (m == 0) return (false, 0);
assembly ("memory-safe") {
let ptr := mload(0x40)
// | Offset | Content | Content (Hex) |
// |-----------|------------|--------------------------------------------------------------------|
// | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x60:0x7f | value of b | 0x<.............................................................b> |
// | 0x80:0x9f | value of e | 0x<.............................................................e> |
// | 0xa0:0xbf | value of m | 0x<.............................................................m> |
mstore(ptr, 0x20)
mstore(add(ptr, 0x20), 0x20)
mstore(add(ptr, 0x40), 0x20)
mstore(add(ptr, 0x60), b)
mstore(add(ptr, 0x80), e)
mstore(add(ptr, 0xa0), m)
// Given the result < m, it's guaranteed to fit in 32 bytes,
// so we can use the memory scratch space located at offset 0.
success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
result := mload(0x00)
}
}
/**
* @dev Variant of {modExp} that supports inputs of arbitrary length.
*/
function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
(bool success, bytes memory result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Variant of {tryModExp} that supports inputs of arbitrary length.
*/
function tryModExp(
bytes memory b,
bytes memory e,
bytes memory m
) internal view returns (bool success, bytes memory result) {
if (_zeroBytes(m)) return (false, new bytes(0));
uint256 mLen = m.length;
// Encode call args in result and move the free memory pointer
result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
assembly ("memory-safe") {
let dataPtr := add(result, 0x20)
// Write result on top of args to avoid allocating extra memory.
success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
// Overwrite the length.
// result.length > returndatasize() is guaranteed because returndatasize() == m.length
mstore(result, mLen)
// Set the memory pointer after the returned data.
mstore(0x40, add(dataPtr, mLen))
}
}
/**
* @dev Returns whether the provided byte array is zero.
*/
function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
for (uint256 i = 0; i < byteArray.length; ++i) {
if (byteArray[i] != 0) {
return false;
}
}
return true;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* This method is based on Newton's method for computing square roots; the algorithm is restricted to only
* using integer operations.
*/
function sqrt(uint256 a) internal pure returns (uint256) {
unchecked {
// Take care of easy edge cases when a == 0 or a == 1
if (a <= 1) {
return a;
}
// In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
// sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
// the current value as `ε_n = | x_n - sqrt(a) |`.
//
// For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
// of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
// bigger than any uint256.
//
// By noticing that
// `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
// we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
// to the msb function.
uint256 aa = a;
uint256 xn = 1;
if (aa >= (1 << 128)) {
aa >>= 128;
xn <<= 64;
}
if (aa >= (1 << 64)) {
aa >>= 64;
xn <<= 32;
}
if (aa >= (1 << 32)) {
aa >>= 32;
xn <<= 16;
}
if (aa >= (1 << 16)) {
aa >>= 16;
xn <<= 8;
}
if (aa >= (1 << 8)) {
aa >>= 8;
xn <<= 4;
}
if (aa >= (1 << 4)) {
aa >>= 4;
xn <<= 2;
}
if (aa >= (1 << 2)) {
xn <<= 1;
}
// We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
//
// We can refine our estimation by noticing that the middle of that interval minimizes the error.
// If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
// This is going to be our x_0 (and ε_0)
xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)
// From here, Newton's method give us:
// x_{n+1} = (x_n + a / x_n) / 2
//
// One should note that:
// x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
// = ((x_n² + a) / (2 * x_n))² - a
// = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
// = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
// = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
// = (x_n² - a)² / (2 * x_n)²
// = ((x_n² - a) / (2 * x_n))²
// ≥ 0
// Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
//
// This gives us the proof of quadratic convergence of the sequence:
// ε_{n+1} = | x_{n+1} - sqrt(a) |
// = | (x_n + a / x_n) / 2 - sqrt(a) |
// = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
// = | (x_n - sqrt(a))² / (2 * x_n) |
// = | ε_n² / (2 * x_n) |
// = ε_n² / | (2 * x_n) |
//
// For the first iteration, we have a special case where x_0 is known:
// ε_1 = ε_0² / | (2 * x_0) |
// ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
// ≤ 2**(2*e-4) / (3 * 2**(e-1))
// ≤ 2**(e-3) / 3
// ≤ 2**(e-3-log2(3))
// ≤ 2**(e-4.5)
//
// For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
// ε_{n+1} = ε_n² / | (2 * x_n) |
// ≤ (2**(e-k))² / (2 * 2**(e-1))
// ≤ 2**(2*e-2*k) / 2**e
// ≤ 2**(e-2*k)
xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above
xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5
xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9
xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18
xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36
xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72
// Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
// ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
// sqrt(a) or sqrt(a) + 1.
return xn - SafeCast.toUint(xn > a / xn);
}
}
/**
* @dev Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// If upper 8 bits of 16-bit half set, add 8 to result
r |= SafeCast.toUint((x >> r) > 0xff) << 3;
// If upper 4 bits of 8-bit half set, add 4 to result
r |= SafeCast.toUint((x >> r) > 0xf) << 2;
// Shifts value right by the current result and use it as an index into this lookup table:
//
// | x (4 bits) | index | table[index] = MSB position |
// |------------|---------|-----------------------------|
// | 0000 | 0 | table[0] = 0 |
// | 0001 | 1 | table[1] = 0 |
// | 0010 | 2 | table[2] = 1 |
// | 0011 | 3 | table[3] = 1 |
// | 0100 | 4 | table[4] = 2 |
// | 0101 | 5 | table[5] = 2 |
// | 0110 | 6 | table[6] = 2 |
// | 0111 | 7 | table[7] = 2 |
// | 1000 | 8 | table[8] = 3 |
// | 1001 | 9 | table[9] = 3 |
// | 1010 | 10 | table[10] = 3 |
// | 1011 | 11 | table[11] = 3 |
// | 1100 | 12 | table[12] = 3 |
// | 1101 | 13 | table[13] = 3 |
// | 1110 | 14 | table[14] = 3 |
// | 1111 | 15 | table[15] = 3 |
//
// The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.
assembly ("memory-safe") {
r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))
}
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(
bytes32 hash,
bytes memory signature
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly ("memory-safe") {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (interfaces/IERC1271.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-1271 standard signature validation method for
* contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
*/
interface IERC1271 {
/**
* @dev Should return whether the signature provided is valid for the provided data
* @param hash Hash of the data to be signed
* @param signature Signature byte array associated with `hash`
*/
function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.20;
import { BaseAllocationMechanism, AllocationConfig } from "src/mechanisms/BaseAllocationMechanism.sol";
import { TokenizedAllocationMechanism } from "src/mechanisms/TokenizedAllocationMechanism.sol";
import { ProperQF } from "src/mechanisms/voting-strategy/ProperQF.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
/**
* @title Quadratic Voting Mechanism
* @author [Golem Foundation](https://golem.foundation)
* @custom:security-contact [email protected]
* @notice Implements quadratic funding using ProperQF algorithm
* @dev Follows Yearn V3 proxy pattern with ProperQF voting strategy
*
* QUADRATIC FUNDING:
* ═══════════════════════════════════
* - Cost of voting is quadratic: weight votes costs weight² power
* - Prevents whale dominance (expensive to cast many votes)
* - Benefits: Small contributors have proportionally more impact
*
* VOTE COST EXAMPLES:
* - 10 votes costs 100 voting power (10²)
* - 20 votes costs 400 voting power (20²)
* - 100 votes costs 10,000 voting power (100²)
*
* ONE-TIME VOTING:
* ⚠️ Users can only vote ONCE per proposal
* - Vote is final and cannot be changed
* - Cannot increase, decrease, or cancel vote
* - Additional deposits create new voting power for other proposals
* - UI must warn users before voting
*
* VOTING POWER:
* - Normalized to 18 decimals regardless of asset decimals
* - Linear relationship: 1 asset = 1 voting power (after normalization)
* - Each deposit adds to cumulative voting power
*
* @custom:security One-time voting prevents manipulation via vote adjustments
* @custom:security Quadratic cost reduces whale influence
*/
contract QuadraticVotingMechanism is BaseAllocationMechanism, ProperQF {
// ============================================
// ERRORS
// ============================================
error ZeroAddressCannotPropose();
error OnlyForVotesSupported();
error InsufficientVotingPowerForQuadraticCost();
error AlreadyVoted(address voter, uint256 pid);
// ============================================
// STATE VARIABLES
// ============================================
/// @notice Tracks whether a voter has voted on a specific proposal
/// @dev Maps pid → voter → hasVoted (prevents double voting)
mapping(uint256 => mapping(address => bool)) public hasVoted;
/**
* @notice Initialize QuadraticVotingMechanism with configuration and alpha parameters
* @dev Called by AllocationMechanismFactory during CREATE2 deployment
* Sets up ProperQF algorithm with specified alpha weighting
* @param _implementation Address of shared TokenizedAllocationMechanism implementation
* @param _config Configuration struct with mechanism parameters
* @param _alphaNumerator Alpha numerator (dimensionless ratio, 0 to _alphaDenominator)
* @param _alphaDenominator Alpha denominator (dimensionless ratio, must be > 0)
*/
constructor(
address _implementation,
AllocationConfig memory _config,
uint256 _alphaNumerator,
uint256 _alphaDenominator
) BaseAllocationMechanism(_implementation, _config) {
_setAlpha(_alphaNumerator, _alphaDenominator);
}
/**
* @notice Hook to validate proposer authorization
* @dev Only keeper or management addresses can create proposals
* Prevents spam and maintains curation quality
* @param proposer Address attempting to create proposal
* @return authorized True if proposer is keeper or management
*/
function _beforeProposeHook(address proposer) internal view virtual override returns (bool) {
if (proposer == address(0)) revert ZeroAddressCannotPropose();
// Get keeper and management addresses from TokenizedAllocationMechanism
address keeper = _tokenizedAllocation().keeper();
address management = _tokenizedAllocation().management();
// Allow if proposer is either keeper or management
return proposer == keeper || proposer == management;
}
/**
* @notice Hook to validate proposal exists
* @param pid Proposal ID to validate
* @return valid True if proposal exists
*/
function _validateProposalHook(uint256 pid) internal view virtual override returns (bool) {
return _proposalExists(pid);
}
/**
* @notice Hook to authorize user registration
* @dev Allows all users to register, including multiple signups
* Each signup adds voting power that can be used on un-voted proposals
*
* IMPORTANT DESIGN CONSIDERATION:
* Pure quadratic voting (QV) should restrict to single signups to prevent
* double-spending of vote credits. However, quadratic funding (QF) variants
* allow multiple signups where users contribute their own funds to increase
* voting power. Derived contracts can override to enforce single-signup.
* @return authorized True (all users can signup)
*/
function _beforeSignupHook(address) internal virtual override returns (bool) {
return true;
}
/**
* @notice Hook to calculate voting power from deposit amount
* @dev Normalizes asset amount to 18 decimals for consistent voting power
* 1 token (in asset decimals) = 1 voting power (in 18 decimals)
*
* NORMALIZATION EXAMPLES:
* - USDC (6 decimals): 1,000,000 (1 USDC) → 1e18 voting power
* - WETH (18 decimals): 1e18 (1 WETH) → 1e18 voting power
* - WBTC (8 decimals): 100,000,000 (1 WBTC) → 1e18 voting power
* @param deposit Amount deposited in asset's native decimals
* @return votingPower Normalized voting power in 18 decimals
*/
function _getVotingPowerHook(
address,
uint256 deposit
) internal view virtual override returns (uint256 votingPower) {
// Get asset decimals
uint8 assetDecimals = IERC20Metadata(address(asset)).decimals();
// Convert to 18 decimals for voting power
if (assetDecimals == 18) {
return deposit;
} else if (assetDecimals < 18) {
// Scale up: multiply by 10^(18 - assetDecimals)
uint256 scaleFactor = 10 ** (18 - assetDecimals);
return deposit * scaleFactor;
} else {
// Scale down: divide by 10^(assetDecimals - 18)
uint256 scaleFactor = 10 ** (assetDecimals - 18);
return deposit / scaleFactor;
}
}
/**
* @notice Internal helper to normalize token amount to 18 decimals
* @dev Matches voting power normalization logic in _getVotingPowerHook
* @param amount Token amount in asset's native decimals
* @param assetDecimals Decimal places of the asset token
* @return normalized Amount normalized to 18 decimals
*/
function _normalizeToDecimals(uint256 amount, uint8 assetDecimals) internal pure returns (uint256 normalized) {
if (assetDecimals == 18) {
return amount;
} else if (assetDecimals < 18) {
// Scale up: multiply by 10^(18 - assetDecimals)
uint256 scaleFactor = 10 ** (18 - assetDecimals);
return amount * scaleFactor;
} else {
// Scale down: divide by 10^(assetDecimals - 18)
uint256 scaleFactor = 10 ** (assetDecimals - 18);
return amount / scaleFactor;
}
}
/**
* @notice Hook to process vote with quadratic cost and single-vote enforcement
* @dev Implements quadratic voting: to cast W votes, you pay W² voting power
* Each voter can only vote ONCE per proposal (no adjustments)
*
* QUADRATIC COST FORMULA:
* cost = weight × weight
*
* EXAMPLES:
* - Cast 10 votes → costs 100 voting power
* - Cast 50 votes → costs 2,500 voting power
* - Cast 100 votes → costs 10,000 voting power
*
* This makes whale attacks expensive while giving smaller voters
* proportionally more influence per token.
* @param pid Proposal ID to vote on
* @param voter Address casting the vote
* @param choice Vote type (must be VoteType.For)
* @param weight Number of votes to cast (dimensionless)
* @param oldPower Voter's current voting power (in 18 decimals)
* @return newPower Remaining voting power after quadratic cost deduction (in 18 decimals)
* @custom:security Single-vote enforcement prevents manipulation via vote adjustments
* @custom:security Reverts if voter already voted on this proposal
*/
function _processVoteHook(
uint256 pid,
address voter,
TokenizedAllocationMechanism.VoteType choice,
uint256 weight,
uint256 oldPower
) internal virtual override returns (uint256) {
if (choice != TokenizedAllocationMechanism.VoteType.For) revert OnlyForVotesSupported();
// Check if voter has already voted on this proposal
if (hasVoted[pid][voter]) revert AlreadyVoted(voter, pid);
// Quadratic cost: to vote with weight W, you pay W^2 voting power
uint256 quadraticCost = weight * weight;
if (quadraticCost > oldPower) revert InsufficientVotingPowerForQuadraticCost();
// Use ProperQF's unchecked vote processing since we control the inputs
// contribution = quadratic cost, voteWeight = actual vote weight
// We know: quadraticCost = weight^2, so sqrt(quadraticCost) = weight (perfect square root relationship)
_processVoteUnchecked(pid, quadraticCost, weight);
// Mark that voter has voted on this proposal
hasVoted[pid][voter] = true;
// Return remaining voting power after quadratic cost
return oldPower - quadraticCost;
}
/**
* @notice Hook to check if proposal meets quorum threshold
* @dev Quorum based on total funding (quadratic + linear components)
* Uses ProperQF formula: F_j = α×(sum_sqrt)² + (1-α)×sum_contributions
* @param pid Proposal ID to check
* @return meetsQuorum True if project funding ≥ quorum threshold
*/
function _hasQuorumHook(uint256 pid) internal view virtual override returns (bool meetsQuorum) {
// Get the project's funding metrics
// getTally() returns: alpha-weighted quadratic funding + alpha-weighted linear funding
(, , uint256 quadraticFunding, uint256 linearFunding) = getTally(pid);
// Calculate total funding: both components are already alpha-weighted
// F_j = α × (sum_sqrt)² + (1-α) × sum_contributions
uint256 projectTotalFunding = quadraticFunding + linearFunding;
// Project meets quorum if it has minimum funding threshold
return projectTotalFunding >= _getQuorumShares();
}
/**
* @notice Hook to convert proposal funding into allocation shares
* @dev Returns total funding amount (quadratic + linear components)
* Both components are already alpha-weighted by ProperQF
* @param pid Proposal ID
* @return shares Total funding to allocate in share base units
*/
function _convertVotesToShares(uint256 pid) internal view virtual override returns (uint256 shares) {
// Get project funding metrics
// getTally() returns: alpha-weighted quadratic funding + alpha-weighted linear funding
(, , uint256 quadraticFunding, uint256 linearFunding) = getTally(pid);
// Calculate total funding: both components are already alpha-weighted
// F_j = α × (sum_sqrt)² + (1-α) × sum_contributions
return quadraticFunding + linearFunding;
}
/// @notice Allow finalization once voting period ends
function _beforeFinalizeVoteTallyHook() internal pure virtual override returns (bool) {
return true;
}
/// @notice Get recipient address for proposal
function _getRecipientAddressHook(uint256 pid) internal view virtual override returns (address) {
TokenizedAllocationMechanism.Proposal memory proposal = _getProposal(pid);
if (proposal.recipient == address(0)) revert TokenizedAllocationMechanism.InvalidRecipient(proposal.recipient);
return proposal.recipient;
}
/// @notice Handle custom share distribution - returns false to use default minting
/// @return handled False to indicate default minting should be used
/// @return assetsTransferred 0 since no custom distribution is performed
function _requestCustomDistributionHook(
address,
uint256
) internal pure virtual override returns (bool handled, uint256 assetsTransferred) {
// Return false to indicate we want to use the default share minting in TokenizedAllocationMechanism
// This allows the base implementation to handle the minting via _mint()
return (false, 0);
}
// Note: _availableWithdrawLimit is now inherited from BaseAllocationMechanism
// The default implementation enforces timelock and grace period boundaries
/// @notice Calculate total assets including matching pool + user deposits for finalization
/// @dev This snapshots the total asset balance in the contract during finalize
/// @return Total assets available for allocation (matching pool + user signup deposits)
function _calculateTotalAssetsHook() internal view virtual override returns (uint256) {
// Return current asset balance of the contract
// This includes both:
// 1. Matching pool funds (pre-funded in setUp)
// 2. User deposits from signups
return asset.balanceOf(address(this));
}
/// @notice Get project funding breakdown for a proposal
/// @param pid Proposal ID
/// @return sumContributions Total contribution amounts
/// @return sumSquareRoots Sum of square roots for quadratic calculation
/// @return quadraticFunding Quadratic funding component
/// @return linearFunding Linear funding component
function getProposalFunding(
uint256 pid
)
external
view
returns (uint256 sumContributions, uint256 sumSquareRoots, uint256 quadraticFunding, uint256 linearFunding)
{
if (!_validateProposalHook(pid)) revert TokenizedAllocationMechanism.InvalidProposal(pid);
// Return zero funding for cancelled proposals
if (_tokenizedAllocation().state(pid) == TokenizedAllocationMechanism.ProposalState.Canceled) {
return (0, 0, 0, 0);
}
return getTally(pid);
}
/// @notice Set the alpha parameter for quadratic vs linear funding weighting
/// @param newNumerator Numerator of new alpha value (dimensionless ratio)
/// @param newDenominator Denominator of new alpha value (dimensionless ratio)
/// @dev Alpha determines the ratio: F_j = α × (sum_sqrt)² + (1-α) × sum_contributions
/// @dev Only callable by owner (inherited from BaseAllocationMechanism via TokenizedAllocationMechanism)
function setAlpha(uint256 newNumerator, uint256 newDenominator) external {
// Access control: only owner can modify alpha
require(_tokenizedAllocation().owner() == msg.sender, "Only owner can set alpha");
// Update alpha using ProperQF's internal function (validates constraints internally)
_setAlpha(newNumerator, newDenominator);
}
/// @notice Calculate optimal alpha for 1:1 shares-to-assets ratio given fixed matching pool amount
/// @param matchingPoolAmount Fixed amount of matching funds available (in token's native decimals)
/// @param totalUserDeposits Total user deposits in the mechanism (in token's native decimals)
/// @return optimalAlphaNumerator Calculated alpha numerator
/// @return optimalAlphaDenominator Calculated alpha denominator
/// @dev Internally normalizes amounts to 18 decimals to match quadratic/linear sum calculations
function calculateOptimalAlpha(
uint256 matchingPoolAmount,
uint256 totalUserDeposits
) external view returns (uint256 optimalAlphaNumerator, uint256 optimalAlphaDenominator) {
// Get asset decimals to normalize amounts
uint8 assetDecimals = IERC20Metadata(address(asset)).decimals();
// Normalize both amounts to 18 decimals to match quadratic/linear sums
uint256 normalizedMatchingPool = _normalizeToDecimals(matchingPoolAmount, assetDecimals);
uint256 normalizedUserDeposits = _normalizeToDecimals(totalUserDeposits, assetDecimals);
return
_calculateOptimalAlpha(
normalizedMatchingPool,
totalQuadraticSum(),
totalLinearSum(),
normalizedUserDeposits
);
}
/**
* @notice Reject ETH deposits to prevent permanent fund loss
* @dev Overrides BaseAllocationMechanism's receive() function
* This mechanism only supports ERC20 tokens, not native ETH
* @custom:security Prevents accidental ETH loss
*/
receive() external payable override {
revert("ETH not supported - use ERC20 tokens only");
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.20;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TokenizedAllocationMechanism, IBaseAllocationStrategy } from "src/mechanisms/TokenizedAllocationMechanism.sol";
/**
* @notice Configuration parameters for allocation mechanism initialization
* @dev All timing parameters are in seconds
*/
struct AllocationConfig {
/// @notice Underlying ERC20 asset token for deposits
IERC20 asset;
/// @notice Name for allocation mechanism shares (ERC20 metadata)
string name;
/// @notice Symbol for allocation mechanism shares (ERC20 metadata)
string symbol;
/// @notice Delay before voting begins after proposal creation
/// @dev In seconds. Provides time for proposal review before voting starts
uint256 votingDelay;
/// @notice Duration of the voting period
/// @dev In seconds. Time window during which users can cast votes
uint256 votingPeriod;
/// @notice Minimum voting power required for proposal to pass
/// @dev In share units. Proposal needs >= quorumShares total votes to succeed
uint256 quorumShares;
/// @notice Delay before redemptions can begin after vote finalization
/// @dev In seconds. Security buffer before funds can be withdrawn
uint256 timelockDelay;
/// @notice Duration of redemption window
/// @dev In seconds. Time window during which shares can be redeemed
uint256 gracePeriod;
/// @notice Address that owns/controls the allocation mechanism
/// @dev Typically the deployer. Has admin privileges
address owner;
}
/**
* @title Base Allocation Mechanism
* @author [Golem Foundation](https://golem.foundation)
* @custom:security-contact [email protected]
* @notice Abstract base for allocation/voting mechanisms using lightweight proxy pattern
* @dev Follows Yearn V3 architecture: minimal proxy delegating to shared TokenizedAllocationMechanism
*
* ARCHITECTURE PATTERN:
* ═══════════════════════════════════
* - Inheritors only implement custom hooks (no shared logic duplication)
* - Shared logic lives in TokenizedAllocationMechanism (implementation)
* - Each mechanism is a lightweight proxy with custom behavior via hooks
* - Delegatecall pattern: proxy storage, implementation logic
*
* HOOK SYSTEM:
* ═══════════════════════════════════
* 13 abstract hooks to implement:
*
* Registration:
* - _beforeSignupHook: Allow/block user registration
* - _getVotingPowerHook: Calculate voting power on signup
*
* Proposing:
* - _beforeProposeHook: Allow/block proposal creation
* - _validateProposalHook: Validate proposal exists
*
* Voting:
* - _processVoteHook: Process vote and update voting power
* - _hasQuorumHook: Check if proposal reached quorum
*
* Distribution:
* - _convertVotesToShares: Convert votes to vault shares
* - _getRecipientAddressHook: Get recipient for proposal
* - _requestCustomDistributionHook: Custom share distribution
*
* Finalization:
* - _beforeFinalizeVoteTallyHook: Pre-finalization checks
*
* Withdrawal:
* - _availableWithdrawLimit: Enforce timelock/grace period
*
* Accounting:
* - _calculateTotalAssetsHook: Total assets including matching pools
*
* LIFECYCLE:
* ═══════════════════════════════════
* 1. Deploy: Constructor calls TokenizedAllocationMechanism.initialize()
* 2. Registration: Users deposit assets and receive voting power
* 3. Propose: Create proposals for funding allocation
* 4. Vote: Users cast votes (Against/For/Abstain)
* 5. Finalize: Tally votes, determine winning proposals
* 6. Queue: Mint shares for successful proposals (after timelock)
* 7. Redeem: Recipients redeem shares for assets (during grace period)
*
* TIMELOCK & GRACE PERIOD:
* ═══════════════════════════════════
* - Timelock: Security delay before redemptions begin
* - Grace Period: Window during which redemptions are allowed
* - After grace period: Unredeemed shares become non-withdrawable
*
* @custom:security All hooks called via delegatecall from implementation
* @custom:security onlySelf modifier prevents direct external calls to hooks
*/
abstract contract BaseAllocationMechanism is IBaseAllocationStrategy {
// ---------- Immutable Storage ----------
/// @notice Address of the shared TokenizedAllocationMechanism implementation
address internal immutable tokenizedAllocationAddress;
/// @notice Underlying asset for the allocation mechanism
IERC20 internal immutable asset;
// ---------- Events ----------
/// @notice Emitted when the allocation mechanism is initialized
event AllocationMechanismInitialized(
address indexed implementation,
address indexed asset,
string name,
string symbol
);
// ---------- Constructor ----------
/// @notice Initializes the allocation mechanism with implementation and configuration
/// @param _implementation Address of the TokenizedAllocationMechanism implementation
/// @param _config Configuration parameters for the allocation mechanism
constructor(address _implementation, AllocationConfig memory _config) {
// Store immutable values
tokenizedAllocationAddress = _implementation;
asset = _config.asset;
// Initialize the TokenizedAllocationMechanism storage via delegatecall
(bool success, ) = _implementation.delegatecall(
abi.encodeCall(
TokenizedAllocationMechanism.initialize,
(
_config.owner, // owner
_config.asset,
_config.name,
_config.symbol,
_config.votingDelay,
_config.votingPeriod,
_config.quorumShares,
_config.timelockDelay,
_config.gracePeriod
)
)
);
require(success, "Initialization failed");
emit AllocationMechanismInitialized(_implementation, address(_config.asset), _config.name, _config.symbol);
}
// ============================================
// ABSTRACT HOOKS - REGISTRATION
// ============================================
/**
* @notice REQUIRED: Determines if a user can register
* @dev Called before signup to implement access control
*
* IMPLEMENTATION GUIDANCE:
* - Return true to allow registration
* - Return false to block (will revert signup)
* - Can check allowlists, blocklists, KYC status, etc.
*
* COMMON PATTERNS:
* - Open: Always return true (permissionless)
* - Allowlist: Check mapping(address => bool) allowedUsers
* - Minimum deposit: Check deposit >= minimumAmount
*
* @param user Address attempting to register
* @return allow True to allow registration, false to block
*/
function _beforeSignupHook(address user) internal virtual returns (bool);
/**
* @notice REQUIRED: Determines if an address can create proposals
* @dev Called before propose to implement proposer restrictions
*
* IMPLEMENTATION GUIDANCE:
* - Return true to allow proposal creation
* - Return false to block (will revert)
* - Can check minimum voting power, specific roles, etc.
*
* COMMON PATTERNS:
* - Open: Always return true
* - Minimum power: Check votingPower >= threshold
* - Registered only: Check user has deposited
*
* @param proposer Address attempting to create proposal
* @return allow True to allow proposal, false to block
*/
function _beforeProposeHook(address proposer) internal view virtual returns (bool);
/**
* @notice REQUIRED: Calculates voting power assigned on registration
* @dev Called during signup to determine user's initial voting power
*
* IMPLEMENTATION GUIDANCE:
* - Must return value > 0 for successful registration
* - Should be deterministic based on deposit amount
* - Consider manipulation resistance (flash loan protection)
*
* COMMON PATTERNS:
* - Linear: power = deposit (1 token = 1 vote)
* - Quadratic: power = sqrt(deposit) (reduces whale influence)
* - Capped: power = min(deposit, maxPower)
* - Time-weighted: power = f(deposit, stakeDuration)
*
* SECURITY CONSIDERATIONS:
* - Prevent flash loan manipulation (use checkpoints/locks)
* - Validate deposit amount > 0
* - Ensure calculation cannot overflow
*
* @param user Address registering (can be used for user-specific logic)
* @param deposit Amount of underlying tokens deposited (in asset base units)
* @return power Voting power to assign (0 = registration fails)
*/
function _getVotingPowerHook(address user, uint256 deposit) internal view virtual returns (uint256);
// ============================================
// ABSTRACT HOOKS - PROPOSAL VALIDATION
// ============================================
/**
* @notice REQUIRED: Validates that a proposal ID exists
* @dev Called to verify proposal exists before operations
*
* @param pid Proposal ID to validate
* @return valid True if proposal exists and is valid
*/
function _validateProposalHook(uint256 pid) internal view virtual returns (bool);
// ============================================
// ABSTRACT HOOKS - VOTING
// ============================================
/**
* @notice REQUIRED: Processes a vote and updates voting power
* @dev Called when user casts a vote. Must update vote tallies and return updated voting power
*
* IMPLEMENTATION GUIDANCE:
* - Record vote in proposal's tally
* - Deduct used voting power from voter's available power
* - Return new voting power (must be <= oldPower)
* - Prevent double voting or power overflow
*
* VOTING MODELS:
* - One-time: Use full power once (newPower = 0)
* - Proportional: Deduct weight from power (newPower = oldPower - weight)
* - Quadratic: Deduct weight squared (cost increases with usage)
*
* @param pid Proposal ID being voted on
* @param voter Address casting the vote
* @param choice Vote type (Against=0, For=1, Abstain=2)
* @param weight Amount of voting power to use
* @param oldPower Voter's current voting power before this vote
* @return newPower Voter's voting power after this vote (must be <= oldPower)
*/
function _processVoteHook(
uint256 pid,
address voter,
TokenizedAllocationMechanism.VoteType choice,
uint256 weight,
uint256 oldPower
) internal virtual returns (uint256 newPower);
/**
* @notice REQUIRED: Checks if proposal reached quorum
* @dev Called to determine if proposal has enough votes to pass
*
* @param pid Proposal ID
* @return hasQuorum True if proposal has sufficient votes
*/
function _hasQuorumHook(uint256 pid) internal view virtual returns (bool);
// ============================================
// ABSTRACT HOOKS - DISTRIBUTION
// ============================================
/**
* @notice REQUIRED: Converts votes to vault shares for winning proposal
* @dev Called when queuing successful proposal. Determines funding allocation
*
* COMMON PATTERNS:
* - Direct: shares = votes (1 vote = 1 asset)
* - Quadratic: shares = votes^2 (quadratic funding)
* - Matched: shares = votes + matching(votes)
*
* @param pid Proposal ID being queued
* @return sharesToMint Vault shares to allocate (in share base units)
*/
function _convertVotesToShares(uint256 pid) internal view virtual returns (uint256 sharesToMint);
/**
* @notice REQUIRED: Pre-finalization validation hook
* @dev Called before finalizeVoteTally to enforce any custom rules
*
* @return allow True to proceed with finalization
*/
function _beforeFinalizeVoteTallyHook() internal virtual returns (bool);
/**
* @notice REQUIRED: Returns recipient address for a proposal
* @dev Called during redemption to determine where shares go
*
* @param pid Proposal ID
* @return recipient Address to receive the minted shares
*/
function _getRecipientAddressHook(uint256 pid) internal view virtual returns (address recipient);
/**
* @notice OPTIONAL: Custom distribution instead of standard share minting
* @dev Called during queue. Return (true, amount) to skip default minting
*
* USE CASES:
* - Direct asset transfer instead of shares
* - Multi-recipient distribution
* - Custom vesting schedules
*
* DEFAULT BEHAVIOR:
* Return (false, 0) to use standard share minting
*
* @param recipient Address of the recipient
* @param sharesToMint Number of shares to distribute
* @return handled True if custom distribution performed (skips default minting)
* @return assetsTransferred Amount of assets transferred (for totalAssets accounting)
*/
function _requestCustomDistributionHook(
address recipient,
uint256 sharesToMint
) internal virtual returns (bool handled, uint256 assetsTransferred);
/// @dev Hook to get the available withdraw limit for a share owner
/// @dev Default implementation enforces timelock and grace period boundaries
/// @dev Can be overridden for custom withdrawal limit logic
/// @return limit Available withdraw limit (type(uint256).max for unlimited, 0 for blocked)
function _availableWithdrawLimit(address /* shareOwner */) internal view virtual returns (uint256) {
// Get the global redemption start time
uint256 globalRedemptionStart = _getGlobalRedemptionStart();
// If no global redemption time set (not finalized), no withdrawals allowed
if (globalRedemptionStart == 0) {
return 0;
}
// Check if still in timelock period
if (block.timestamp < globalRedemptionStart) {
return 0; // Cannot withdraw during timelock
}
// Check if grace period has expired
uint256 gracePeriod = _getGracePeriod();
if (block.timestamp > globalRedemptionStart + gracePeriod) {
return 0; // Cannot withdraw after grace period expires
}
// Within valid redemption window - no limit
return type(uint256).max;
}
/// @dev Hook to calculate total assets including any matching pools or custom logic
/// @return totalAssets Total assets for this allocation mechanism
function _calculateTotalAssetsHook() internal view virtual returns (uint256);
// ---------- External Hook Functions (Yearn V3 Pattern) ----------
// These are called by TokenizedAllocationMechanism via delegatecall
// and use onlySelf modifier to ensure security
/// @notice Ensures function can only be called via delegatecall from TokenizedAllocationMechanism
/// @dev In delegatecall context, msg.sender is the proxy address (address(this)), not the caller
modifier onlySelf() {
// In delegatecall context, msg.sender must be address(this) to ensure
// hooks can only be called via delegatecall from TokenizedAllocationMechanism
require(msg.sender == address(this), "!self");
_;
}
/// @notice External wrapper for _beforeSignupHook, called by TokenizedAllocationMechanism via delegatecall
/// @param user Address attempting to register
/// @return allow True to allow registration, false to block
function beforeSignupHook(address user) external onlySelf returns (bool) {
return _beforeSignupHook(user);
}
/// @notice External wrapper for _beforeProposeHook, called by TokenizedAllocationMechanism via delegatecall
/// @param proposer Address attempting to create proposal
/// @return allow True to allow proposal, false to block
function beforeProposeHook(address proposer) external view onlySelf returns (bool) {
return _beforeProposeHook(proposer);
}
/// @notice External wrapper for _getVotingPowerHook, called by TokenizedAllocationMechanism via delegatecall
/// @param user Address registering
/// @param deposit Amount deposited in asset base units
/// @return power Voting power to assign
function getVotingPowerHook(address user, uint256 deposit) external view onlySelf returns (uint256) {
return _getVotingPowerHook(user, deposit);
}
/// @notice External wrapper for _validateProposalHook, called by TokenizedAllocationMechanism via delegatecall
/// @param pid Proposal ID to validate
/// @return valid True if proposal exists and is valid
function validateProposalHook(uint256 pid) external view onlySelf returns (bool) {
return _validateProposalHook(pid);
}
/// @notice External wrapper for _processVoteHook, called by TokenizedAllocationMechanism via delegatecall
/// @param pid Proposal ID being voted on
/// @param voter Address casting the vote
/// @param choice Vote type (0=Against, 1=For, 2=Abstain)
/// @param weight Amount of voting power to use
/// @param oldPower Voter's current voting power
/// @return newPower Voter's voting power after this vote
function processVoteHook(
uint256 pid,
address voter,
uint8 choice,
uint256 weight,
uint256 oldPower
) external onlySelf returns (uint256) {
return _processVoteHook(pid, voter, TokenizedAllocationMechanism.VoteType(choice), weight, oldPower);
}
/// @notice External wrapper for _hasQuorumHook, called by TokenizedAllocationMechanism via delegatecall
/// @param pid Proposal ID
/// @return hasQuorum True if proposal has sufficient votes
function hasQuorumHook(uint256 pid) external view onlySelf returns (bool) {
return _hasQuorumHook(pid);
}
/// @notice External wrapper for _convertVotesToShares, called by TokenizedAllocationMechanism via delegatecall
/// @param pid Proposal ID being queued
/// @return sharesToMint Vault shares to allocate in share base units
function convertVotesToShares(uint256 pid) external view onlySelf returns (uint256) {
return _convertVotesToShares(pid);
}
/// @notice External wrapper for _beforeFinalizeVoteTallyHook, called by TokenizedAllocationMechanism via delegatecall
/// @return allow True to proceed with finalization
function beforeFinalizeVoteTallyHook() external onlySelf returns (bool) {
return _beforeFinalizeVoteTallyHook();
}
/// @notice External wrapper for _getRecipientAddressHook, called by TokenizedAllocationMechanism via delegatecall
/// @param pid Proposal ID
/// @return recipient Address to receive the minted shares
function getRecipientAddressHook(uint256 pid) external view onlySelf returns (address) {
return _getRecipientAddressHook(pid);
}
/// @notice External wrapper for _requestCustomDistributionHook, called by TokenizedAllocationMechanism via delegatecall
/// @param recipient Address of the recipient
/// @param sharesToMint Number of shares to distribute
/// @return handled True if custom distribution performed
/// @return assetsTransferred Amount of assets transferred
function requestCustomDistributionHook(
address recipient,
uint256 sharesToMint
) external onlySelf returns (bool handled, uint256 assetsTransferred) {
return _requestCustomDistributionHook(recipient, sharesToMint);
}
/// @notice External wrapper for _availableWithdrawLimit, called by TokenizedAllocationMechanism via delegatecall
/// @param shareOwner Address to check withdraw limit for
/// @return limit Available withdraw limit (type(uint256).max for unlimited, 0 for blocked)
function availableWithdrawLimit(address shareOwner) external view onlySelf returns (uint256) {
return _availableWithdrawLimit(shareOwner);
}
/// @notice External wrapper for _calculateTotalAssetsHook, called by TokenizedAllocationMechanism via delegatecall
/// @return totalAssets Total assets for this allocation mechanism
function calculateTotalAssetsHook() external view onlySelf returns (uint256) {
return _calculateTotalAssetsHook();
}
// ---------- Internal Helpers ----------
/// @notice Access TokenizedAllocationMechanism interface for internal calls
/// @dev Uses current contract address since storage is local
function _tokenizedAllocation() internal view returns (TokenizedAllocationMechanism) {
return TokenizedAllocationMechanism(address(this));
}
/// @notice Get grace period from configuration
/// @return Grace period in seconds
function _getGracePeriod() internal view returns (uint256) {
return _tokenizedAllocation().gracePeriod();
}
/// @dev Get global redemption start timestamp
/// @return Timestamp when redemption window opens (0 if not finalized)
function _getGlobalRedemptionStart() internal view returns (uint256) {
return _tokenizedAllocation().globalRedemptionStart();
}
// ---------- Fallback Function ----------
/// @notice Delegates all undefined function calls to TokenizedAllocationMechanism
/// @dev This enables the proxy pattern where shared logic lives in the implementation
fallback() external payable virtual {
address _impl = tokenizedAllocationAddress;
assembly {
// Copy calldata to memory
calldatacopy(0, 0, calldatasize())
// Delegatecall to implementation contract
let result := delegatecall(gas(), _impl, 0, calldatasize(), 0, 0)
// Copy return data
returndatacopy(0, 0, returndatasize())
// Handle result
switch result
case 0 {
// Delegatecall failed, revert with error data
revert(0, returndatasize())
}
default {
// Delegatecall succeeded, return data
return(0, returndatasize())
}
}
}
/// @notice Receive function to accept ETH
receive() external payable virtual {}
// ---------- View Helpers for Inheritors ----------
/// @notice Get the current proposal count
/// @dev Helper for concrete implementations to access storage
function _getProposalCount() internal view returns (uint256) {
return _tokenizedAllocation().getProposalCount();
}
/// @notice Check if a proposal exists
/// @dev Helper for concrete implementations
function _proposalExists(uint256 pid) internal view returns (bool) {
return pid > 0 && pid <= _getProposalCount();
}
/// @notice Get proposal details
/// @dev Helper for concrete implementations
function _getProposal(uint256 pid) internal view returns (TokenizedAllocationMechanism.Proposal memory) {
return _tokenizedAllocation().proposals(pid);
}
/// @notice Get voting power for an address
/// @dev Helper for concrete implementations
function _getVotingPower(address user) internal view returns (uint256) {
return _tokenizedAllocation().votingPower(user);
}
/// @notice Get quorum shares requirement
/// @dev Helper for concrete implementations
function _getQuorumShares() internal view returns (uint256) {
return _tokenizedAllocation().quorumShares();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
using SafeCast for *;
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
uint256 private constant SPECIAL_CHARS_LOOKUP =
(1 << 0x08) | // backspace
(1 << 0x09) | // tab
(1 << 0x0a) | // newline
(1 << 0x0c) | // form feed
(1 << 0x0d) | // carriage return
(1 << 0x22) | // double quote
(1 << 0x5c); // backslash
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev The string being parsed contains characters that are not in scope of the given base.
*/
error StringsInvalidChar();
/**
* @dev The string being parsed is not a properly formatted address.
*/
error StringsInvalidAddressFormat();
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly ("memory-safe") {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
assembly ("memory-safe") {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
* representation, according to EIP-55.
*/
function toChecksumHexString(address addr) internal pure returns (string memory) {
bytes memory buffer = bytes(toHexString(addr));
// hash the hex part of buffer (skip length + 2 bytes, length 40)
uint256 hashValue;
assembly ("memory-safe") {
hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
}
for (uint256 i = 41; i > 1; --i) {
// possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
// case shift by xoring with 0x20
buffer[i] ^= 0x20;
}
hashValue >>= 4;
}
return string(buffer);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
/**
* @dev Parse a decimal string and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input) internal pure returns (uint256) {
return parseUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
uint256 result = 0;
for (uint256 i = begin; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 9) return (false, 0);
result *= 10;
result += chr;
}
return (true, result);
}
/**
* @dev Parse a decimal string and returns the value as a `int256`.
*
* Requirements:
* - The string must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input) internal pure returns (int256) {
return parseInt(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
(bool success, int256 value) = tryParseInt(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
* the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
}
uint256 private constant ABS_MIN_INT256 = 2 ** 255;
/**
* @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character or if the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, int256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseIntUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseIntUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, int256 value) {
bytes memory buffer = bytes(input);
// Check presence of a negative sign.
bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
bool positiveSign = sign == bytes1("+");
bool negativeSign = sign == bytes1("-");
uint256 offset = (positiveSign || negativeSign).toUint();
(bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);
if (absSuccess && absValue < ABS_MIN_INT256) {
return (true, negativeSign ? -int256(absValue) : int256(absValue));
} else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
return (true, type(int256).min);
} else return (false, 0);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input) internal pure returns (uint256) {
return parseHexUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseHexUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
* invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseHexUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseHexUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
// skip 0x prefix if present
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 offset = hasPrefix.toUint() * 2;
uint256 result = 0;
for (uint256 i = begin + offset; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 15) return (false, 0);
result *= 16;
unchecked {
// Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
// This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.
result += chr;
}
}
return (true, result);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input) internal pure returns (address) {
return parseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
(bool success, address value) = tryParseAddress(input, begin, end);
if (!success) revert StringsInvalidAddressFormat();
return value;
}
/**
* @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
* formatted address. See {parseAddress-string} requirements.
*/
function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
return tryParseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
* formatted address. See {parseAddress-string-uint256-uint256} requirements.
*/
function tryParseAddress(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, address value) {
if (end > bytes(input).length || begin > end) return (false, address(0));
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
// check that input is the correct length
if (end - begin == expectedLength) {
// length guarantees that this does not overflow, and value is at most type(uint160).max
(bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
return (s, address(uint160(v)));
} else {
return (false, address(0));
}
}
function _tryParseChr(bytes1 chr) private pure returns (uint8) {
uint8 value = uint8(chr);
// Try to parse `chr`:
// - Case 1: [0-9]
// - Case 2: [a-f]
// - Case 3: [A-F]
// - otherwise not supported
unchecked {
if (value > 47 && value < 58) value -= 48;
else if (value > 96 && value < 103) value -= 87;
else if (value > 64 && value < 71) value -= 55;
else return type(uint8).max;
}
return value;
}
/**
* @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.
*
* WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.
*
* NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of
* RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode
* characters that are not in this range, but other tooling may provide different results.
*/
function escapeJSON(string memory input) internal pure returns (string memory) {
bytes memory buffer = bytes(input);
bytes memory output = new bytes(2 * buffer.length); // worst case scenario
uint256 outputLength = 0;
for (uint256 i; i < buffer.length; ++i) {
bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));
if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {
output[outputLength++] = "\\";
if (char == 0x08) output[outputLength++] = "b";
else if (char == 0x09) output[outputLength++] = "t";
else if (char == 0x0a) output[outputLength++] = "n";
else if (char == 0x0c) output[outputLength++] = "f";
else if (char == 0x0d) output[outputLength++] = "r";
else if (char == 0x5c) output[outputLength++] = "\\";
else if (char == 0x22) {
// solhint-disable-next-line quotes
output[outputLength++] = '"';
}
} else {
output[outputLength++] = char;
}
}
// write the actual length and deallocate unused memory
assembly ("memory-safe") {
mstore(output, outputLength)
mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))
}
return string(output);
}
/**
* @dev Reads a bytes32 from a bytes array without bounds checking.
*
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
* assembly block as such would prevent some optimizations.
*/
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
// This is not memory safe in the general case, but all calls to this private function are within bounds.
assembly ("memory-safe") {
value := mload(add(buffer, add(0x20, offset)))
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC-1967 implementation slot:
* ```solidity
* contract ERC1967 {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct Int256Slot {
int256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Int256Slot` with member `value` located at `slot`.
*/
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
/**
* @dev Returns a `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, bytes memory returndata) = recipient.call{value: amount}("");
if (!success) {
_revert(returndata);
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, 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 {Errors.FailedCall}) 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 {Errors.FailedCall} 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 {Errors.FailedCall}.
*/
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
assembly ("memory-safe") {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.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 ERC-20 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.1.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC-20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 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 ERC-721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-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 ERC-1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 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.1.0) (utils/Panic.sol)
pragma solidity ^0.8.20;
/**
* @dev Helper library for emitting standardized panic codes.
*
* ```solidity
* contract Example {
* using Panic for uint256;
*
* // Use any of the declared internal constants
* function foo() { Panic.GENERIC.panic(); }
*
* // Alternatively
* function foo() { Panic.panic(Panic.GENERIC); }
* }
* ```
*
* Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
*
* _Available since v5.1._
*/
// slither-disable-next-line unused-state
library Panic {
/// @dev generic / unspecified error
uint256 internal constant GENERIC = 0x00;
/// @dev used by the assert() builtin
uint256 internal constant ASSERT = 0x01;
/// @dev arithmetic underflow or overflow
uint256 internal constant UNDER_OVERFLOW = 0x11;
/// @dev division or modulo by zero
uint256 internal constant DIVISION_BY_ZERO = 0x12;
/// @dev enum conversion error
uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
/// @dev invalid encoding in storage
uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
/// @dev empty array pop
uint256 internal constant EMPTY_ARRAY_POP = 0x31;
/// @dev array out of bounds access
uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
/// @dev resource error (too large allocation or too large array)
uint256 internal constant RESOURCE_ERROR = 0x41;
/// @dev calling invalid internal function
uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;
/// @dev Reverts with a panic code. Recommended to use with
/// the internal constants with predefined codes.
function panic(uint256 code) internal pure {
assembly ("memory-safe") {
mstore(0x00, 0x4e487b71)
mstore(0x20, code)
revert(0x1c, 0x24)
}
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.0;
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
/**
* @title Proper Quadratic Funding (QF) math and tallying
* @author [Golem Foundation](https://golem.foundation)
* @custom:security-contact [email protected]
* @notice Incremental QF tallying utilities with alpha-weighted quadratic/linear funding.
* @dev Provides storage isolation via deterministic slot, input validation helpers,
* and funding aggregation with well-defined rounding behavior.
*/
abstract contract ProperQF {
using Math for uint256;
// Custom Errors
error ContributionMustBePositive();
error VoteWeightMustBePositive();
error VoteWeightOverflow(); // Keep for backward compatibility in tests
error SquareRootTooLarge();
error VoteWeightOutsideTolerance();
error QuadraticSumUnderflow();
error LinearSumUnderflow();
error DenominatorMustBePositive();
error AlphaMustBeLessOrEqualToOne();
/// @notice Storage slot for ProperQF storage (ERC-7201 namespaced storage)
/// @dev https://eips.ethereum.org/EIPS/eip-7201
bytes32 private constant STORAGE_SLOT =
bytes32(uint256(keccak256(abi.encode(uint256(keccak256(bytes("proper.qf.storage"))) - 1))) & ~uint256(0xff));
/// @notice Per-project aggregated sums
struct Project {
/// @notice Sum of contributions for this project (asset base units)
uint256 sumContributions;
/// @notice Sum of square roots of all contributions (dimensionless)
uint256 sumSquareRoots;
}
/// @notice Main storage struct containing all mutable state for ProperQF
struct ProperQFStorage {
/// @notice Mapping of project IDs to project data
mapping(uint256 => Project) projects;
/// @notice Numerator for alpha (dimensionless; 1.0 = denominator)
uint256 alphaNumerator;
/// @notice Denominator for alpha (must be > 0)
uint256 alphaDenominator;
/// @notice Sum of all quadratic terms across projects (dimensionless squared weights)
uint256 totalQuadraticSum;
/// @notice Sum of all linear contributions across projects (asset base units)
uint256 totalLinearSum;
/// @notice Alpha-weighted total funding across all projects (asset base units)
/// @dev Uses uint256 for precision in calculations
uint256 totalFunding;
}
/// @notice Emitted when alpha parameters are updated
/// @param oldNumerator Previous alpha numerator
/// @param oldDenominator Previous alpha denominator
/// @param newNumerator New alpha numerator
/// @param newDenominator New alpha denominator
event AlphaUpdated(uint256 oldNumerator, uint256 oldDenominator, uint256 newNumerator, uint256 newDenominator);
/// @notice Constructor initializes default alpha values in storage
constructor() {
ProperQFStorage storage s = _getProperQFStorage();
s.alphaNumerator = 10000; // Default alpha = 1.0 (10000/10000)
s.alphaDenominator = 10000;
}
/// @notice Get the storage struct from the predefined slot
/// @return s Storage struct containing all mutable state for ProperQF
function _getProperQFStorage() internal pure returns (ProperQFStorage storage s) {
bytes32 slot = STORAGE_SLOT;
assembly {
s.slot := slot
}
}
/// @notice Returns project aggregated sums
/// @param projectId ID of the project to query
function projects(uint256 projectId) public view returns (Project memory) {
return _getProperQFStorage().projects[projectId];
}
/// @notice Returns alpha numerator
function alphaNumerator() public view returns (uint256) {
return _getProperQFStorage().alphaNumerator;
}
/// @notice Returns alpha denominator
function alphaDenominator() public view returns (uint256) {
return _getProperQFStorage().alphaDenominator;
}
/// @notice Returns total quadratic sum across all projects
function totalQuadraticSum() public view returns (uint256) {
return _getProperQFStorage().totalQuadraticSum;
}
/// @notice Returns total linear sum across all projects
function totalLinearSum() public view returns (uint256) {
return _getProperQFStorage().totalLinearSum;
}
/// @notice Returns alpha-weighted total funding across all projects
function totalFunding() public view returns (uint256) {
return _getProperQFStorage().totalFunding;
}
/**
* @notice Process a vote and update the tally for the voting strategy
* @dev Implements incremental update quadratic funding algorithm with validations:
* - contribution > 0 (asset base units)
* - voteWeight > 0 and voteWeight^2 == contribution within 10% tolerance
* @param projectId ID of project to update
* @param contribution Contribution to add in asset base units
* @param voteWeight Square root of contribution (dimensionless)
*/
function _processVote(uint256 projectId, uint256 contribution, uint256 voteWeight) internal virtual {
if (contribution == 0) revert ContributionMustBePositive();
if (voteWeight == 0) revert VoteWeightMustBePositive();
uint256 voteWeightSquared = voteWeight * voteWeight; // Reverts on overflow in Solidity 0.8+
if (voteWeightSquared > contribution) revert SquareRootTooLarge();
// 10% tolerance, asymmetric: voteWeight can be lower than actualSqrt, but not higher
uint256 actualSqrt = contribution.sqrt();
uint256 tolerance = actualSqrt / 10;
if (voteWeight < actualSqrt - tolerance || voteWeight > actualSqrt) {
revert VoteWeightOutsideTolerance();
}
_processVoteUnchecked(projectId, contribution, voteWeight);
}
/**
* @notice Process vote without validation - for trusted callers who have already validated
* @dev Skips input validation for gas optimization when caller guarantees correctness
* @param projectId ID of project to update
* @param contribution Contribution amount (asset base units)
* @param voteWeight Vote weight (dimensionless; sqrt of contribution)
*/
function _processVoteUnchecked(uint256 projectId, uint256 contribution, uint256 voteWeight) internal {
ProperQFStorage storage s = _getProperQFStorage();
Project memory project = s.projects[projectId];
uint256 newSumSquareRoots = project.sumSquareRoots + voteWeight;
uint256 newSumContributions = project.sumContributions + contribution;
uint256 oldQuadraticFunding = project.sumSquareRoots * project.sumSquareRoots;
uint256 newQuadraticFunding = newSumSquareRoots * newSumSquareRoots;
if (s.totalQuadraticSum < oldQuadraticFunding) revert QuadraticSumUnderflow();
if (s.totalLinearSum < project.sumContributions) revert LinearSumUnderflow();
uint256 newTotalQuadraticSum = s.totalQuadraticSum - oldQuadraticFunding + newQuadraticFunding;
uint256 newTotalLinearSum = s.totalLinearSum - project.sumContributions + newSumContributions;
s.totalQuadraticSum = newTotalQuadraticSum;
s.totalLinearSum = newTotalLinearSum;
project.sumSquareRoots = newSumSquareRoots;
project.sumContributions = newSumContributions;
s.projects[projectId] = project;
s.totalFunding = _calculateWeightedTotalFunding();
}
/**
* @notice Calculate alpha-weighted total funding across all projects
* @dev Rounding: per-project integer division makes sum(project funding) ≤ totalFunding.
* Discrepancy ε is bounded: 0 ≤ ε ≤ 2(|P|-1) where |P| is number of projects.
* This dust ensures no over-allocation; all funds are still fully distributed.
* @return totalFunding_ Weighted total funding across all projects (asset base units)
*/
function _calculateWeightedTotalFunding() internal view returns (uint256) {
ProperQFStorage storage s = _getProperQFStorage();
uint256 weightedQuadratic = (s.totalQuadraticSum * s.alphaNumerator) / s.alphaDenominator;
uint256 weightedLinear = (s.totalLinearSum * (s.alphaDenominator - s.alphaNumerator)) / s.alphaDenominator;
return weightedQuadratic + weightedLinear;
}
/**
* @notice Return current funding metrics for a specific project
* @dev Aggregates sums and computes alpha-weighted components on-demand.
* @param projectId ID of project to tally
* @return sumContributions Total sum of contributions (asset base units)
* @return sumSquareRoots Sum of square roots of contributions (dimensionless)
* @return quadraticFunding Alpha-weighted quadratic funding: ⌊α × S_j²⌋ (asset base units)
* @return linearFunding Alpha-weighted linear funding: ⌊(1-α) × Sum_j⌋ (asset base units)
* @dev Rounding: sum of per-project funding ≤ totalFunding() with small bounded dust ε.
*/
function getTally(
uint256 projectId
)
public
view
returns (uint256 sumContributions, uint256 sumSquareRoots, uint256 quadraticFunding, uint256 linearFunding)
{
ProperQFStorage storage s = _getProperQFStorage();
Project storage project = s.projects[projectId];
uint256 rawQuadraticFunding = project.sumSquareRoots * project.sumSquareRoots;
return (
project.sumContributions,
project.sumSquareRoots,
(rawQuadraticFunding * s.alphaNumerator) / s.alphaDenominator,
(project.sumContributions * (s.alphaDenominator - s.alphaNumerator)) / s.alphaDenominator
);
}
/**
* @notice Set alpha parameter determining ratio between quadratic and linear funding
* @param newNumerator Numerator of new alpha (0 ≤ numerator ≤ denominator)
* @param newDenominator Denominator of new alpha (> 0)
*/
function _setAlpha(uint256 newNumerator, uint256 newDenominator) internal {
if (newDenominator == 0) revert DenominatorMustBePositive();
if (newNumerator > newDenominator) revert AlphaMustBeLessOrEqualToOne();
ProperQFStorage storage s = _getProperQFStorage();
uint256 oldNumerator = s.alphaNumerator;
uint256 oldDenominator = s.alphaDenominator;
s.alphaNumerator = newNumerator;
s.alphaDenominator = newDenominator;
// Recalculate total funding with new alpha
s.totalFunding = _calculateWeightedTotalFunding();
emit AlphaUpdated(oldNumerator, oldDenominator, newNumerator, newDenominator);
}
/**
* @notice Get current alpha ratio components
* @return numerator Current alpha numerator
* @return denominator Current alpha denominator
*/
function getAlpha() public view returns (uint256, uint256) {
ProperQFStorage storage s = _getProperQFStorage();
return (s.alphaNumerator, s.alphaDenominator);
}
/**
* @notice Calculate optimal alpha for 1:1 shares-to-assets ratio given fixed matching pool amount
* @dev Solve α where: α × totalQuadraticSum + (1−α) × totalLinearSum = totalUserDeposits + matchingPoolAmount
* @param matchingPoolAmount Matching pool amount (asset base units)
* @param quadraticSum Total quadratic sum across all proposals (dimensionless)
* @param linearSum Total linear sum across all proposals (asset base units)
* @param totalUserDeposits Total user deposits in the mechanism (asset base units)
* @return optimalAlphaNumerator Calculated alpha numerator
* @return optimalAlphaDenominator Calculated alpha denominator
*/
function _calculateOptimalAlpha(
uint256 matchingPoolAmount,
uint256 quadraticSum,
uint256 linearSum,
uint256 totalUserDeposits
) internal pure returns (uint256 optimalAlphaNumerator, uint256 optimalAlphaDenominator) {
if (quadraticSum <= linearSum) {
// No quadratic funding benefit, set alpha to 0
optimalAlphaNumerator = 0;
optimalAlphaDenominator = 1;
return (optimalAlphaNumerator, optimalAlphaDenominator);
}
uint256 totalAssetsAvailable = totalUserDeposits + matchingPoolAmount;
uint256 quadraticAdvantage = quadraticSum - linearSum;
// We want: α × quadraticSum + (1-α) × linearSum = totalAssetsAvailable
// Solving for α: α × (quadraticSum - linearSum) = totalAssetsAvailable - linearSum
// Therefore: α = (totalAssetsAvailable - linearSum) / (quadraticSum - linearSum)
if (totalAssetsAvailable <= linearSum) {
// Not enough assets even for linear funding, set alpha to 0
optimalAlphaNumerator = 0;
optimalAlphaDenominator = 1;
} else {
uint256 numerator = totalAssetsAvailable - linearSum;
if (numerator >= quadraticAdvantage) {
// Enough assets for full quadratic funding
optimalAlphaNumerator = 1;
optimalAlphaDenominator = 1;
} else {
// Calculate fractional alpha
optimalAlphaNumerator = numerator;
optimalAlphaDenominator = quadraticAdvantage;
}
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * int256(SafeCast.toUint(condition)));
}
}
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson.
// Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift,
// taking advantage of the most significant (or "sign" bit) in two's complement representation.
// This opcode adds new most significant bits set to the value of the previous most significant bit. As a result,
// the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative).
int256 mask = n >> 255;
// A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it.
return uint256((n + mask) ^ mask);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}{
"remappings": [
"@gnosis.pm/safe-contracts/=dependencies/safe-global-safe-smart-account-1.4.1-3/",
"@openzeppelin/contracts-upgradeable/=dependencies/@openzeppelin-contracts-upgradeable-5.3.0/contracts/",
"@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-5.3.0/contracts/",
"@tokenized-strategy-periphery/=dependencies/tokenized-strategy-periphery-3.0.2/src/",
"abdk-libraries-solidity/=dependencies/abdk-libraries-solidity-3.2/",
"erc4626-tests/=dependencies/erc4626-tests-0.0.0/",
"forge-std/=dependencies/forge-std-1.14.0/src/",
"hats-protocol/=dependencies/hats-protocol-1.0/src/",
"kontrol-cheatcodes/=dependencies/kontrol-cheatcodes-v0.0.1/",
"lib/ERC1155/=dependencies/hats-protocol-1.0/lib/ERC1155/",
"solady-test/=dependencies/solady-0.1.21/test/",
"solady/=dependencies/solady-0.1.21/src/",
"solbase/=dependencies/solbase-0.0.0/src/",
"solmate/=dependencies/solmate-6.8.0/",
"staker/=dependencies/staker-1.0.1/src/",
"surl/=dependencies/surl-0.0.0/src/",
"tokenized-strategy/=dependencies/tokenized-strategy-3.0.4/",
"zodiac/=dependencies/gnosisguild-zodiac-4.1.1/contracts/",
"@openzeppelin-contracts-5.3.0/=dependencies/@openzeppelin-contracts-5.3.0/",
"@openzeppelin-contracts-upgradeable-5.3.0/=dependencies/@openzeppelin-contracts-upgradeable-5.3.0/",
"@tokenized-strategy/=dependencies/tokenized-strategy-periphery-3.0.2/lib/tokenized-strategy/src/",
"@yearn-vaults/=dependencies/tokenized-strategy-periphery-3.0.2/lib/yearn-vaults-v3/contracts/",
"ERC1155/=dependencies/hats-protocol-1.0/lib/ERC1155/",
"abdk-libraries-solidity-3.2/=dependencies/abdk-libraries-solidity-3.2/",
"ds-test/=dependencies/tokenized-strategy-periphery-3.0.2/lib/forge-std/lib/ds-test/src/",
"erc4626-tests-0.0.0/=dependencies/erc4626-tests-0.0.0/",
"forge-std-1.14.0/=dependencies/forge-std-1.14.0/src/",
"gnosisguild-zodiac-4.1.1/=dependencies/gnosisguild-zodiac-4.1.1/contracts/",
"hats-protocol-1.0/=dependencies/hats-protocol-1.0/src/",
"kontrol-cheatcodes-v0.0.1/=dependencies/kontrol-cheatcodes-v0.0.1/",
"openzeppelin-contracts/=dependencies/staker-1.0.1/lib/openzeppelin-contracts/",
"safe-global-safe-smart-account-1.4.1-3/=dependencies/safe-global-safe-smart-account-1.4.1-3/",
"solady-0.1.21/=dependencies/solady-0.1.21/src/",
"solady-0.1.26/=dependencies/solady-0.1.26/src/",
"solbase-0.0.0/=dependencies/solbase-0.0.0/src/",
"solidity-stringutils/=dependencies/surl-0.0.0/lib/solidity-stringutils/src/",
"solmate-6.8.0/=dependencies/solmate-6.8.0/src/",
"staker-1.0.1/=dependencies/staker-1.0.1/src/",
"staker-test/=dependencies/staker-1.0.1/test/",
"surl-0.0.0/=dependencies/surl-0.0.0/src/",
"tokenized-strategy-3.0.4/=dependencies/tokenized-strategy-3.0.4/src/",
"tokenized-strategy-periphery-3.0.2/=dependencies/tokenized-strategy-periphery-3.0.2/src/",
"utils/=dependencies/hats-protocol-1.0/lib/utils/"
],
"optimizer": {
"enabled": true,
"runs": 1
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "prague",
"viaIR": false
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"contract IERC20","name":"_rewardsToken","type":"address"},{"internalType":"contract IERC20","name":"_stakeToken","type":"address"},{"internalType":"contract IEarningPowerCalculator","name":"_earningPowerCalculator","type":"address"},{"internalType":"uint256","name":"_maxBumpTip","type":"uint256"},{"internalType":"address","name":"_admin","type":"address"},{"internalType":"uint128","name":"_rewardDuration","type":"uint128"},{"internalType":"uint128","name":"_minimumStakeAmount","type":"uint128"},{"internalType":"contract IAddressSet","name":"_stakerAllowset","type":"address"},{"internalType":"contract IAddressSet","name":"_stakerBlockset","type":"address"},{"internalType":"enum AccessMode","name":"_stakerAccessMode","type":"uint8"},{"internalType":"contract IAddressSet","name":"_allocationMechanismAllowset","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"expected","type":"address"},{"internalType":"address","name":"actual","type":"address"}],"name":"AssetMismatch","type":"error"},{"inputs":[],"name":"CannotChangeEarningPowerCalculatorDuringActiveReward","type":"error"},{"inputs":[],"name":"CannotChangeRewardDurationDuringActiveReward","type":"error"},{"inputs":[],"name":"CannotRaiseMaxBumpTipDuringActiveReward","type":"error"},{"inputs":[],"name":"CannotRaiseMinimumStakeAmountDuringActiveReward","type":"error"},{"inputs":[{"internalType":"uint256","name":"requested","type":"uint256"},{"internalType":"uint256","name":"available","type":"uint256"}],"name":"CantAfford","type":"error"},{"inputs":[],"name":"CompoundingNotSupported","type":"error"},{"inputs":[],"name":"DelegationNotSupported","type":"error"},{"inputs":[{"internalType":"address","name":"mechanism","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"DepositOwnerNotEligibleForMechanism","type":"error"},{"inputs":[],"name":"DisablingAllocationMechanismAllowsetNotAllowed","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"FailedCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentBalance","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"InsufficientRewardBalance","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"currentNonce","type":"uint256"}],"name":"InvalidAccountNonce","type":"error"},{"inputs":[{"internalType":"uint256","name":"rewardDuration","type":"uint256"}],"name":"InvalidRewardDuration","type":"error"},{"inputs":[],"name":"InvalidShortString","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"MinimumStakeAmountNotMet","type":"error"},{"inputs":[],"name":"NoOperation","type":"error"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"NotInAllowset","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"uint8","name":"bits","type":"uint8"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"SafeCastOverflowedUintDowncast","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"StakerBlocked","type":"error"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"StakerNotAllowed","type":"error"},{"inputs":[],"name":"StakerOnBehalf__ExpiredDeadline","type":"error"},{"inputs":[],"name":"StakerOnBehalf__InvalidSignature","type":"error"},{"inputs":[],"name":"StakerPermitAndStake__UnauthorizedToken","type":"error"},{"inputs":[],"name":"Staker__ExpiredDeadline","type":"error"},{"inputs":[],"name":"Staker__InsufficientRewardBalance","type":"error"},{"inputs":[],"name":"Staker__InsufficientUnclaimedRewards","type":"error"},{"inputs":[],"name":"Staker__InvalidAddress","type":"error"},{"inputs":[],"name":"Staker__InvalidClaimFeeParameters","type":"error"},{"inputs":[],"name":"Staker__InvalidRewardRate","type":"error"},{"inputs":[],"name":"Staker__InvalidSignature","type":"error"},{"inputs":[],"name":"Staker__InvalidTip","type":"error"},{"inputs":[{"internalType":"bytes32","name":"reason","type":"bytes32"},{"internalType":"address","name":"caller","type":"address"}],"name":"Staker__Unauthorized","type":"error"},{"inputs":[{"internalType":"uint256","name":"score","type":"uint256"}],"name":"Staker__Unqualified","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"inputs":[{"internalType":"address","name":"delegatee","type":"address"}],"name":"SurrogateNotFound","type":"error"},{"inputs":[],"name":"ZeroOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"enum AccessMode","name":"mode","type":"uint8"}],"name":"AccessModeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IAddressSet","name":"allowset","type":"address"}],"name":"AllocationMechanismAllowsetAssigned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint96","name":"oldFeeAmount","type":"uint96"},{"indexed":false,"internalType":"uint96","name":"newFeeAmount","type":"uint96"},{"indexed":false,"internalType":"address","name":"oldFeeCollector","type":"address"},{"indexed":false,"internalType":"address","name":"newFeeCollector","type":"address"}],"name":"ClaimFeeParametersSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"Staker.DepositIdentifier","name":"depositId","type":"uint256"},{"indexed":true,"internalType":"address","name":"oldClaimer","type":"address"},{"indexed":true,"internalType":"address","name":"newClaimer","type":"address"},{"indexed":false,"internalType":"uint256","name":"earningPower","type":"uint256"}],"name":"ClaimerAltered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"Staker.DepositIdentifier","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"address","name":"oldDelegatee","type":"address"},{"indexed":false,"internalType":"address","name":"newDelegatee","type":"address"},{"indexed":false,"internalType":"uint256","name":"earningPower","type":"uint256"}],"name":"DelegateeAltered","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"Staker.DepositIdentifier","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"oldEarningPower","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newEarningPower","type":"uint256"},{"indexed":false,"internalType":"address","name":"bumper","type":"address"},{"indexed":false,"internalType":"address","name":"tipReceiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tipAmount","type":"uint256"}],"name":"EarningPowerBumped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldEarningPowerCalculator","type":"address"},{"indexed":true,"internalType":"address","name":"newEarningPowerCalculator","type":"address"}],"name":"EarningPowerCalculatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldMaxBumpTip","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newMaxBumpTip","type":"uint256"}],"name":"MaxBumpTipSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newMinimumStakeAmount","type":"uint256"}],"name":"MinimumStakeAmountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"Staker.DepositIdentifier","name":"depositId","type":"uint256"},{"indexed":true,"internalType":"address","name":"claimer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"earningPower","type":"uint256"}],"name":"RewardClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"Staker.DepositIdentifier","name":"depositId","type":"uint256"},{"indexed":true,"internalType":"address","name":"contributor","type":"address"},{"indexed":true,"internalType":"address","name":"fundingRound","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RewardContributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newDuration","type":"uint256"}],"name":"RewardDurationSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"notifier","type":"address"}],"name":"RewardNotified","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"bool","name":"isEnabled","type":"bool"}],"name":"RewardNotifierSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"addedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"carryOverAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalScheduledAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"requiredBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"RewardScheduleUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"Staker.DepositIdentifier","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"depositBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"earningPower","type":"uint256"}],"name":"StakeDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"Staker.DepositIdentifier","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"depositBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"earningPower","type":"uint256"}],"name":"StakeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IAddressSet","name":"allowset","type":"address"}],"name":"StakerAllowsetAssigned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IAddressSet","name":"blockset","type":"address"}],"name":"StakerBlocksetAssigned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"ALTER_CLAIMER_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ALTER_DELEGATEE_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CLAIM_REWARD_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_CLAIM_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_REWARD_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_REWARD_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARD_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARD_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SCALE_FACTOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_MORE_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allocationMechanismAllowset","outputs":[{"internalType":"contract IAddressSet","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"address","name":"_newClaimer","type":"address"}],"name":"alterClaimer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"address","name":"_newClaimer","type":"address"},{"internalType":"address","name":"_depositor","type":"address"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"alterClaimerOnBehalf","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"address","name":"_newDelegatee","type":"address"}],"name":"alterDelegatee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"address","name":"_newDelegatee","type":"address"},{"internalType":"address","name":"_depositor","type":"address"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"alterDelegateeOnBehalf","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"address","name":"_tipReceiver","type":"address"},{"internalType":"uint256","name":"_requestedTip","type":"uint256"}],"name":"bumpEarningPower","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimFeeParameters","outputs":[{"internalType":"uint96","name":"feeAmount","type":"uint96"},{"internalType":"address","name":"feeCollector","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"claimRewardOnBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"}],"name":"compoundRewards","outputs":[{"internalType":"uint256","name":"compoundedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"address","name":"_allocationMechanismAddress","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"uint8","name":"_v","type":"uint8"},{"internalType":"bytes32","name":"_r","type":"bytes32"},{"internalType":"bytes32","name":"_s","type":"bytes32"}],"name":"contribute","outputs":[{"internalType":"uint256","name":"amountContributedToAllocationMechanism","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"depositor","type":"address"}],"name":"depositorTotalEarningPower","outputs":[{"internalType":"uint256","name":"earningPower","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"depositor","type":"address"}],"name":"depositorTotalStaked","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"depositId","type":"uint256"}],"name":"deposits","outputs":[{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint96","name":"earningPower","type":"uint96"},{"internalType":"address","name":"delegatee","type":"address"},{"internalType":"address","name":"claimer","type":"address"},{"internalType":"uint256","name":"rewardPerTokenCheckpoint","type":"uint256"},{"internalType":"uint256","name":"scaledUnclaimedRewardCheckpoint","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"earningPowerCalculator","outputs":[{"internalType":"contract IEarningPowerCalculator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"invalidateNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rewardNotifier","type":"address"}],"name":"isRewardNotifier","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastCheckpointTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastTimeRewardDistributed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestRewardSchedule","outputs":[{"internalType":"uint256","name":"addedAmount","type":"uint256"},{"internalType":"uint256","name":"carryOverAmount","type":"uint256"},{"internalType":"uint256","name":"totalScheduledAmount","type":"uint256"},{"internalType":"uint256","name":"requiredBalance","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxBumpTip","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minimumStakeAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"notifyRewardAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_delegatee","type":"address"},{"internalType":"address","name":"_claimer","type":"address"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"uint8","name":"_v","type":"uint8"},{"internalType":"bytes32","name":"_r","type":"bytes32"},{"internalType":"bytes32","name":"_s","type":"bytes32"}],"name":"permitAndStake","outputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"uint8","name":"_v","type":"uint8"},{"internalType":"bytes32","name":"_r","type":"bytes32"},{"internalType":"bytes32","name":"_s","type":"bytes32"}],"name":"permitAndStakeMore","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardEndTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardPerTokenAccumulated","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardPerTokenAccumulatedCheckpoint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"scaledRewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum AccessMode","name":"_mode","type":"uint8"}],"name":"setAccessMode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newAdmin","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IAddressSet","name":"_allocationMechanismAllowset","type":"address"}],"name":"setAllocationMechanismAllowset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"feeAmount","type":"uint96"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct Staker.ClaimFeeParameters","name":"_params","type":"tuple"}],"name":"setClaimFeeParameters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newEarningPowerCalculator","type":"address"}],"name":"setEarningPowerCalculator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newMaxBumpTip","type":"uint256"}],"name":"setMaxBumpTip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"_minimumStakeAmount","type":"uint128"}],"name":"setMinimumStakeAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"_rewardDuration","type":"uint128"}],"name":"setRewardDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_rewardNotifier","type":"address"},{"internalType":"bool","name":"_isEnabled","type":"bool"}],"name":"setRewardNotifier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IAddressSet","name":"_stakerAllowset","type":"address"}],"name":"setStakerAllowset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IAddressSet","name":"_stakerBlockset","type":"address"}],"name":"setStakerBlockset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_delegatee","type":"address"}],"name":"stake","outputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_delegatee","type":"address"},{"internalType":"address","name":"_claimer","type":"address"}],"name":"stake","outputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"stakeMore","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_depositor","type":"address"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"stakeMoreOnBehalf","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_delegatee","type":"address"},{"internalType":"address","name":"_claimer","type":"address"},{"internalType":"address","name":"_depositor","type":"address"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"stakeOnBehalf","outputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakerAccessMode","outputs":[{"internalType":"enum AccessMode","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakerAllowset","outputs":[{"internalType":"contract IAddressSet","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakerBlockset","outputs":[{"internalType":"contract IAddressSet","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"surrogates","outputs":[{"internalType":"contract DelegationSurrogate","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalClaimedRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalEarningPower","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"}],"name":"unclaimedReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_depositId","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_depositor","type":"address"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"withdrawOnBehalf","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
6101c0604052348015610010575f5ffd5b50604051615fb6380380615fb683398101604081905261002f91610893565b8a8a8a8a8a8a8a8a8a8a8a604051806060016040528060288152602001615f8e602891396040805180820190915260018152603160f81b60208201526001600160a01b03808e166080528c1660a0528b9082908e838e8e8e610090816101fd565b61009982610261565b6100a2836102a2565b50506001601055506100b991508390506011610306565b610180526100c8816012610306565b6101a0528151602080840191909120610140528151908201206101605246610100526101576101405161016051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b60e0525050306101205260a0516001600160a01b0382811691161461018f576040516301e8cb4560e31b815260040160405180910390fd5b505f60c081905260408051808201909152818152602001819052600e5560805160a0516001600160a01b039182169116036101d35760a0516101d390305f19610338565b6101e18787878787876103c5565b5050505050505050505050505050505050505050505050610b97565b61020681610675565b6001546040516001600160a01b038084169216907fbf265e8326285a2747e33e54d5945f7111f2b5edb826eb8c08d4677779b3ff97905f90a3600180546001600160a01b0319166001600160a01b0392909216919091179055565b60025460408051918252602082018390527faeea2b5ee5fb4e4473cc85584d77137f4c6f996691f89746291ce55425120015910160405180910390a1600255565b6102ab81610675565b6005546040516001600160a01b038084169216907fb7c75e18483153b6b8c9c9a379610df50999c601b86f112f162ea62bc8678724905f90a3600580546001600160a01b0319166001600160a01b0392909216919091179055565b5f6020835110156103215761031a8361069f565b9050610332565b8161032c8482610a0a565b5060ff90505b92915050565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f919085169063dd62ed3e90604401602060405180830381865afa158015610385573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a99190610ac8565b90506103bf84846103ba8585610adf565b6106dc565b50505050565b62093a80866001600160801b0316101580156103ee5750630f731400866001600160801b031611155b866001600160801b0316906104225760405163074faf5d60e31b815260040161041991815260200190565b60405180910390fd5b506001600160a01b03811661044a57604051630ac58fed60e21b815260040160405180910390fd5b836001600160a01b0316816001600160a01b03161415801561047e5750826001600160a01b0316816001600160a01b031614155b61049b57604051630426574760e11b815260040160405180910390fd5b7f823b3cede11929e1b876086e20886121c9c32f04426a36ed323a85d541df3b8e866040516104ca9190610afe565b60405180910390a17f5febf96039ec9415638c738f7102ae754ebf18784713f4cfefef8484d352edfe856040516105019190610afe565b60405180910390a16040516001600160a01b038516907fb7f912b432bbc47994e136552c5b7848d71048222eab29b00c35c00899155c72905f90a26040516001600160a01b038416907faac29fbc402d011d11d07d15f6ef2d175c7f8b186e3763b43334f45b8079a1b7905f90a281600281111561058157610581610b12565b6040517fe192fac29e26bb1206168747d1ec5c6dd9f61bb550a815072b0e136f6018f925905f90a26040516001600160a01b038216907fd4fe8d4aa58aa780080ba8f9c18852dc59a6c75f8dc8fe07dcc358330212ec24905f90a26001600160801b03858116600160801b0290871617601455601580546001600160a01b038087166001600160a01b0319928316179092556017805492861691831682178155849290916001600160a81b031990911617600160a01b83600281111561064957610649610b12565b0217905550601680546001600160a01b0319166001600160a01b03929092169190911790555050505050565b6001600160a01b03811661069c57604051630426574760e11b815260040160405180910390fd5b50565b5f5f829050601f815111156106c9578260405163305a27a960e01b81526004016104199190610b26565b80516106d482610b5b565b179392505050565b5f836001600160a01b031663095ea7b384846040516024016106ff929190610b7e565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050905061073e84826107a460201b60201c565b6103bf5761079a84856001600160a01b031663095ea7b3865f604051602401610768929190610b7e565b60408051808303601f1901815291905260208101805160e09390931b6001600160e01b0393841617905291506107ed16565b6103bf84826107ed565b5f5f5f5f60205f8651602088015f8a5af192503d91505f5190508280156107e3575081156107d557806001146107e3565b5f866001600160a01b03163b115b9695505050505050565b5f5f60205f8451602086015f885af18061080c576040513d5f823e3d81fd5b50505f513d91508115610823578060011415610830565b6001600160a01b0384163b155b156103bf57604051635274afe760e01b81526001600160a01b0385166004820152602401610419565b6001600160a01b038116811461069c575f5ffd5b80516001600160801b0381168114610883575f5ffd5b919050565b805161088381610859565b5f5f5f5f5f5f5f5f5f5f5f6101608c8e0312156108ae575f5ffd5b8b516108b981610859565b60208d0151909b506108ca81610859565b60408d0151909a506108db81610859565b60608d015160808e0151919a5098506108f381610859565b965061090160a08d0161086d565b955061090f60c08d0161086d565b945060e08c015161091f81610859565b6101008d015190945061093181610859565b6101208d015190935060038110610946575f5ffd5b91506109556101408d01610888565b90509295989b509295989b9093969950565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061098f57607f821691505b6020821081036109ad57634e487b7160e01b5f52602260045260245ffd5b50919050565b601f821115610a055782821115610a0557805f5260205f20601f840160051c60208510156109de57505f5b90810190601f840160051c035f5b81811015610a01575f838201556001016109ec565b5050505b505050565b81516001600160401b03811115610a2357610a23610967565b610a3781610a31845461097b565b846109b3565b6020601f821160018114610a69575f8315610a525750848201515b5f19600385901b1c1916600184901b178455610ac1565b5f84815260208120601f198516915b82811015610a985787850151825560209485019460019092019101610a78565b5084821015610ab557868401515f19600387901b60f8161c191681555b505060018360011b0184555b5050505050565b5f60208284031215610ad8575f5ffd5b5051919050565b8082018082111561033257634e487b7160e01b5f52601160045260245ffd5b6001600160801b0391909116815260200190565b634e487b7160e01b5f52602160045260245ffd5b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b805160208083015191908110156109ad575f1960209190910360031b1b16919050565b6001600160a01b03929092168252602082015260400190565b60805160a05160c05160e05161010051610120516101405161016051610180516101a051615311610c7d5f395f61353001525f61350301525f612bc201525f612b9a01525f612af501525f612b1f01525f612b4901525f818161051a015261269301525f81816103fb01528181611954015281816122670152818161234701528181612cb60152818161334d0152613cca01525f818161077901528181610da901528181610dd8015281816111840152818161124a0152818161162d0152818161197e01528181612c1601528181612ce0015281816143d5015261441501526153115ff3fe608060405234801561000f575f5ffd5b5060043610610378575f3560e01c80630495d7251461037c578063073a2ae914610391578063081969b4146103a457806308484dee146103b75780630e15561a146103d35780631285d6b5146103dc5780631c39b672146103f6578063255a4f731461041d57806325fc4d3014610430578063276d7334146104435780632b7d9706146104575780632c4f88cd1461046a57806331cb4bea1461047d57806334560e50146104915780633644e515146104a457806337e9f64a146104ac57806339aab4b0146104b55780633c6b16ab146104c95780633d4bd89f146104dc5780633f4520f5146104ef5780633f4ba83a146104fa578063441a3e701461050257806346800f7514610515578063495835761461053c578063558007991461054f5780635a57b46f146105625780635ade228a1461056a5780635ba8fcfe146105745780635c975abb146105875780635cc27d6f1461059e57806361dc0549146105a857806364b5c91c146105b1578063656c579f146105d05780636b036f45146105e3578063703762da146105fb578063704b6c021461060e57806372a35df91461062157806376c5d75814610634578063774da6741461064857806377d7a921146106945780637aa23c52146106a85780637acb7757146106b15780637e31d2cc146106c45780637ecebe00146106d7578063817b1cd2146106ea57806383ce8511146106f35780638456cb591461070657806384b0196e1461070e57806389ee09f0146107295780638d0726941461073d5780638fa13835146107505780639718e11b1461076357806399248ea714610774578063a7d545db1461079b578063ac9650d8146107ae578063ae0d27b6146107ce578063ae169a50146107d6578063aec275a9146107e9578063b02c43d0146107fa578063bb60030d146108b0578063be9b19e0146108c3578063c21f6375146108dd578063c3be9978146108f0578063c3c16e4e1461090f578063c6f6173b14610918578063cb3056ad14610921578063cd2ee8f714610934578063ce4b5bbe14610947578063d578ceab1461095c578063da755f0214610965578063db1f598914610978578063ddda8a111461098b578063e6dec27014610993578063e70f9445146109a6578063f520e7e5146109c8578063f5e05cc7146109d9578063f851a44014610a24578063fd6fe8a814610a37575b5f5ffd5b61038f61038a366004614770565b610a4a565b005b61038f61039f3660046147d9565b610a5e565b61038f6103b2366004614807565b610a85565b6103c060045481565b6040519081526020015b60405180910390f35b6103c060185481565b6017546001600160a01b03165b6040516103ca919061482d565b6103e97f000000000000000000000000000000000000000000000000000000000000000081565b61038f61042b36600461484e565b610b26565b61038f61043e36600461487a565b610b8c565b6103c05f5160206151dc5f395f51905f5281565b6103c06104653660046148a5565b610c87565b61038f610478366004614906565b611332565b6103c05f5160206151fc5f395f51905f5281565b61038f61049f36600461491d565b611372565b6103c0611662565b6103c060095481565b6103c05f51602061529c5f395f51905f5281565b61038f6104d7366004614906565b611670565b6103c06104ea3660046149db565b611699565b6103c0630f73140081565b61038f611733565b61038f610510366004614a5c565b61174d565b6103c07f000000000000000000000000000000000000000000000000000000000000000081565b61038f61054a36600461487a565b61176f565b61038f61055d366004614807565b6117a2565b61038f6118a8565b6103c062278d0081565b61038f610582366004614a7c565b6118b1565b600f5460ff165b60405190151581526020016103ca565b6103c062093a8081565b6103c0600a5481565b6103c06105bf36600461487a565b60076020525f908152604090205481565b6103c06105de366004614906565b611941565b601454600160801b90046001600160801b03166103c0565b61038f610609366004614a5c565b611d78565b61038f61061c36600461487a565b611d9a565b6005546103e9906001600160a01b031681565b6103c05f5160206151bc5f395f51905f5281565b600e5461066d906001600160601b03811690600160601b90046001600160a01b031682565b604080516001600160601b0390931683526001600160a01b039091166020830152016103ca565b6103c05f5160206152bc5f395f51905f5281565b6103c060025481565b6103c06106bf3660046147d9565b611dab565b61038f6106d2366004614aec565b611dbf565b6103c06106e536600461487a565b611e7c565b6103c060035481565b61038f610701366004614b0a565b611e96565b61038f611f1e565b610716611f36565b6040516103ca9796959493929190614b65565b6103c05f51602061521c5f395f51905f5281565b61038f61074b36600461487a565b611f78565b61038f61075e366004614a7c565b612032565b6016546001600160a01b03166103e9565b6103e97f000000000000000000000000000000000000000000000000000000000000000081565b6103c06107a9366004614bfb565b61207e565b6107c16107bc366004614c3a565b612093565b6040516103ca9190614ca9565b6103c0612179565b6103c06107e4366004614906565b6121c6565b6015546001600160a01b03166103e9565b61085e610808366004614906565b60086020525f9081526040902080546001820154600283015460038401546004909401546001600160601b03808516956001600160a01b03600160601b9687900481169692861695929092048216939091169187565b604080516001600160601b0398891681526001600160a01b0397881660208201529790951694870194909452918416606086015292909216608084015260a083019190915260c082015260e0016103ca565b61038f6108be3660046147d9565b61222c565b601754600160a01b900460ff166040516103ca9190614d20565b6103c06108eb366004614d46565b61224e565b6103c06108fe36600461487a565b60066020525f908152604090205481565b6103c0600b5481565b6103c0600c5481565b6103c061092f366004614906565b6122e9565b61038f610942366004614d93565b612319565b6103c06a0c097ce7bc90715b34b9f160241b81565b6103c060195481565b61038f610973366004614b0a565b6123c5565b61038f61098636600461487a565b612411565b6103c06124cb565b6103e96109a136600461487a565b503090565b61058e6109b436600461487a565b600d6020525f908152604090205460ff1681565b6014546001600160801b03166103c0565b601a54601b54601c54601d54601e54601f546109f795949392919086565b604080519687526020870195909552938501929092526060840152608083015260a082015260c0016103ca565b6001546103e9906001600160a01b031681565b6103c0610a45366004614de0565b6124e1565b610a52612652565b610a5b81612691565b50565b5f828152600860205260409020610a7581336127a4565b610a808184846127f9565b505050565b610a8d612652565b6014546001600160801b03600160801b9091048116908216111580610ab3575060095442115b610ad057604051639bc24cbf60e01b815260040160405180910390fd5b7f5febf96039ec9415638c738f7102ae754ebf18784713f4cfefef8484d352edfe81604051610aff9190614e2b565b60405180910390a1601480546001600160801b03928316600160801b029216919091179055565b610b2e612652565b6001600160a01b0382165f818152600d6020908152604091829020805460ff191685151590811790915591519182527fb33ca1dabfb28a2e1bd06b332c396b1a3d538278691341a5503bbad53f919197910160405180910390a25050565b6016546001600160a01b03808316911603610bba57604051634ae9dc4b60e11b815260040160405180910390fd5b6001600160a01b038116610be157604051630ac58fed60e21b815260040160405180910390fd5b6015546001600160a01b03828116911614801590610c0d57506017546001600160a01b03828116911614155b610c2a57604051630426574760e11b815260040160405180910390fd5b610c32612652565b6040516001600160a01b038216907fd4fe8d4aa58aa780080ba8f9c18852dc59a6c75f8dc8fe07dcc358330212ec24905f90a2601680546001600160a01b0319166001600160a01b0392909216919091179055565b5f610c90612812565b610c98612836565b610ca187612860565b601654604051630bb7c8fd60e31b81526001600160a01b0390911690635dbe47e890610cd1908a9060040161482d565b602060405180830381865afa158015610cec573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d109190614e3f565b8790610d3957604051632dde5e4f60e11b8152600401610d30919061482d565b60405180910390fd5b505f876001600160a01b03166338d52e0f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d77573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d9b9190614e5a565b9050806001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614610e13577f000000000000000000000000000000000000000000000000000000000000000081604051634e83a9b960e01b8152600401610d30929190614e75565b505f88815260086020526040902060028101546001600160a01b03163314801590610e4f57508054600160601b90046001600160a01b03163314155b15610e6f5733604051630581a50f60e41b8152600401610d309190614e8f565b80546040516302f50e9b60e41b81525f916001600160a01b03808c1692632f50e9b092610ea892600160601b909104169060040161482d565b602060405180830381865afa158015610ec3573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ee79190614e3f565b905080610f1d578154604051631ec3d94d60e31b8152610d30918b91600160601b9091046001600160a01b031690600401614e75565b610f25612887565b610f2e82612914565b5f6a0c097ce7bc90715b34b9f160241b8360040154610f4d9190614ed4565b9050888180821115610f7457604051634e64fbf760e11b8152600401610d30929190614ef3565b5050885f0361102657896001600160a01b0316336001600160a01b03168c5f51602061527c5f395f51905f525f604051610fb091815260200190565b60405180910390a4604051630e22c14b60e01b81526001600160a01b038b1690630e22c14b90610fee9033905f908d908d908d908d90600401614f01565b5f604051808303815f87803b158015611005575f5ffd5b505af1158015611017573d5f5f3e3d5ffd5b505050505f935050505061131d565b889350611033838a61292d565b6001830154600554845460405162834efd60e71b81526001600160601b03808516945f946001600160a01b03908116946341a77e809461108b94821693600160601b9283900484169392900490911690600401614f37565b602060405180830381865afa1580156110a6573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110ca9190614f61565b90506110d98282600454612979565b6004558454600160601b90046001600160a01b03165f908152600760205260409020546111099083908390612979565b8554600160601b90046001600160a01b03165f908152600760205260409020556111328161298f565b6001860180546001600160601b0319166001600160601b039290921691909117905560405133908e905f51602061519c5f395f51905f5290611177908a908690614ef3565b60405180910390a36111aa7f00000000000000000000000000000000000000000000000000000000000000008d886129c6565b8b6001600160a01b0316336001600160a01b03168e5f51602061527c5f395f51905f52896040516111dd91815260200190565b60405180910390a4604051630e22c14b60e01b81526001600160a01b038d1690630e22c14b9061121b9033908a908f908f908f908f90600401614f01565b5f604051808303815f87803b158015611232575f5ffd5b505af1158015611244573d5f5f3e3d5ffd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663dd62ed3e308e6040518363ffffffff1660e01b8152600401611296929190614e75565b602060405180830381865afa1580156112b1573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112d59190614f61565b156113175760405162461bcd60e51b8152602060048201526012602482015271616c6c6f77616e6365206e6f74207a65726f60701b6044820152606401610d30565b50505050505b6113276001601055565b979650505050505050565b61133a612652565b6002548111158061134c575060095442115b61136957604051637e72b6c760e01b815260040160405180910390fd5b610a5b81612a51565b61137a612812565b611382612836565b6002548111156113a55760405163192501ab60e31b815260040160405180910390fd5b5f8381526008602052604090206113ba612887565b6113c381612914565b5f6a0c097ce7bc90715b34b9f160241b82600401546113e29190614ed4565b6005548354600185015460405160016243a1e360e11b031981526001600160601b0380841660048301526001600160a01b03600160601b94859004811660248401529383048416604483015290911660648201529293505f928392919091169063ff78bc3a906084016040805180830381865afa158015611465573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114899190614f78565b915091508015806114a6575060018401546001600160601b031682145b156114c757604051632b2807ef60e21b815260048101839052602401610d30565b60018401546001600160601b0316821180156114e257508483105b156115005760405163b219487760e01b815260040160405180910390fd5b848381111561150c5750825b6001850154604080516001600160601b0390921682526020820185905233908201526001600160a01b03881660608201526080810182905288907f04d2114cbff027138003f7a9e452588f683397107f4d4ab1a83f17e80ebad12e9060a00160405180910390a26001850154600454611590916001600160601b0316908590612979565b60045560018501548554600160601b90046001600160a01b03165f908152600760205260409020546115cd916001600160601b0316908590612979565b8554600160601b90046001600160a01b03165f908152600760205260409020556115f68361298f565b6001860180546001600160601b0319166001600160601b0392909216919091179055611622858261292d565b8015611653576116537f00000000000000000000000000000000000000000000000000000000000000008883612a91565b5050505050610a806001601055565b5f61166b612ae9565b905090565b611678612836565b5f61168282612c12565b905061168e8282612d66565b50610a5b6001601055565b5f6116a383612fc2565b611727846117215f51602061521c5f395f51905f528a8a8a8a6116c58c612fe3565b6040805160208101979097528601949094526001600160a01b03928316606086015290821660808501521660a083015260c082015260e08101879052610100015b60405160208183030381529060405280519060200120613005565b84613031565b6113278488888861305d565b61173b6130b8565b611743612652565b61174b6130db565b565b5f82815260086020526040902061176481336127a4565b610a80818484613127565b611777612652565b600954421161179957604051630c6ed80760e01b815260040160405180910390fd5b610a5b8161316d565b6117aa612652565b60095442116117cc576040516329fc2c8b60e01b815260040160405180910390fd5b62093a80816001600160801b0316101580156117f55750630f731400816001600160801b031611155b816001600160801b0316906118205760405163074faf5d60e31b8152600401610d3091815260200190565b506014546001600160801b0380831691160361184f57604051634ae9dc4b60e11b815260040160405180910390fd5b7f823b3cede11929e1b876086e20886121c9c32f04426a36ed323a85d541df3b8e8160405161187e9190614e2b565b60405180910390a1601480546001600160801b0319166001600160801b0392909216919091179055565b610a5b33612fe3565b5f8581526008602052604090206118c881856127a4565b6118d183612fc2565b61192e846117215f51602061529c5f395f51905f528989896118f28b612fe3565b6040805160208101969096528501939093526001600160a01b03918216606085015216608083015260a082015260c0810187905260e001611706565b6119398187876127f9565b505050505050565b5f61194a612812565b611952612836565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316146119c457604051630d54ba3960e11b815260040160405180910390fd5b5f828152600860205260409020805460028201546001600160a01b03600160601b909204821691163314801590611a0457506001600160a01b0381163314155b15611a245733604051630581a50f60e41b8152600401610d309190614e8f565b611a2d816131d1565b611a35612887565b611a3e82612914565b5f6a0c097ce7bc90715b34b9f160241b8360040154611a5d9190614ed4565b9050805f03611a71575f9350505050611d69565b6005548354600185015460405162834efd60e71b815293965086935f936001600160a01b03908116936341a77e8093611aca936001600160601b03831693600160601b9384900481169390920490911690600401614f37565b602060405180830381865afa158015611ae5573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b099190614f61565b84549091505f90611b249087906001600160601b0316614f9c565b6001860154600554875460405162834efd60e71b81529394506001600160601b038316935f936001600160a01b03938416936341a77e8093611b7d938993600160601b9283900484169392900490911690600401614faf565b602060405180830381865afa158015611b98573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bbc9190614f61565b9050611bcb8282600454612979565b6004558654600160601b90046001600160a01b03165f90815260076020526040902054611bfb9083908390612979565b8754600160601b90046001600160a01b03165f90815260076020526040812091909155600380548a9290611c30908490614f9c565b90915550506001600160a01b0386165f90815260066020526040812080548a9290611c5c908490614f9c565b90915550611c6c9050878661292d565b611c758361298f565b87546001600160601b0319166001600160601b0391909116178755611c998161298f565b6001880180546001600160601b0319166001600160601b03929092169190911790553080611cf0576001880154604051633f88874160e21b8152610d3091600160601b90046001600160a01b03169060040161482d565b611cfb30828b613338565b336001600160a01b03168a5f51602061519c5f395f51905f528b88604051611d24929190614ef3565b60405180910390a3895f51602061523c5f395f51905f52888b8786604051611d4f9493929190614fce565b60405180910390a2611d608a61337e565b50505050505050505b611d736001601055565b919050565b5f828152600860205260409020611d8f81336127a4565b610a80818484613405565b611da2612652565b610a5b8161345b565b5f611db83384843361305d565b9392505050565b611dc7612652565b806002811115611dd957611dd9614d0c565b601754600160a01b900460ff166002811115611df757611df7614d0c565b03611e1557604051634ae9dc4b60e11b815260040160405180910390fd5b806002811115611e2757611e27614d0c565b6040517fe192fac29e26bb1206168747d1ec5c6dd9f61bb550a815072b0e136f6018f925905f90a26017805482919060ff60a01b1916600160a01b836002811115611e7457611e74614d0c565b021790555050565b6001600160a01b03165f9081526013602052604090205490565b5f858152600860205260409020611ead81856127a4565b611eb683612fc2565b611f13846117215f5160206152bc5f395f51905f52898989611ed78b612fe3565b60408051602081019690965285019390935260608401919091526001600160a01b0316608083015260a082015260c0810187905260e001611706565b611939818787613405565b611f26612812565b611f2e612652565b61174b6134bf565b5f6060805f5f5f6060611f476134fc565b611f4f613529565b604080515f80825260208201909252600f60f81b9b939a50919850469750309650945092509050565b611f80612652565b6017546001600160a01b03808316911603611fae57604051634ae9dc4b60e11b815260040160405180910390fd5b6016546001600160a01b0390811690821603611fdd57604051630426574760e11b815260040160405180910390fd5b6040516001600160a01b038216907faac29fbc402d011d11d07d15f6ef2d175c7f8b186e3763b43334f45b8079a1b7905f90a2601780546001600160a01b0319166001600160a01b0392909216919091179055565b5f85815260086020526040902061204981856127a4565b61205283612fc2565b612073846117215f5160206151dc5f395f51905f528989896118f28b612fe3565b611939818787613556565b5f61208b3385858561305d565b949350505050565b604080515f815260208101909152606090826001600160401b038111156120bc576120bc614748565b6040519080825280602002602001820160405280156120ef57816020015b60608152602001906001900390816120da5790505b5091505f5b838110156121705761214b3086868481811061211257612112615007565b9050602002810190612124919061501b565b856040516020016121379392919061507b565b604051602081830303815290604052613571565b83828151811061215d5761215d615007565b60209081029190910101526001016120f4565b50505b92915050565b5f6004545f0361218a5750600c5490565b600454600a546121986124cb565b6121a29190614ff4565b600b546121af9190615090565b6121b99190614ed4565b600c5461166b9190614f9c565b5f81815260086020526040812060028101546001600160a01b0316331480159061220157508054600160601b90046001600160a01b03163314155b156122215733604051630581a50f60e41b8152600401610d309190614e8f565b611db88382336135e3565b5f82815260086020526040902061224381336127a4565b610a80818484613556565b60405163d505accf60e01b81525f906001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063d505accf906122a890339030908d908b908b908b908b906004016150a7565b5f604051808303815f87803b1580156122bf575f5ffd5b505af19250505080156122d0575060015b506122dd3389898961305d565b98975050505050505050565b5f8181526008602052604081206a0c097ce7bc90715b34b9f160241b9061230f90613624565b6121739190614ed4565b5f86815260086020526040902061233081336127a4565b60405163d505accf60e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063d505accf9061238890339030908b908b908b908b908b906004016150a7565b5f604051808303815f87803b15801561239f575f5ffd5b505af19250505080156123b0575060015b506123bc818888613405565b50505050505050565b5f8581526008602052604090206123dc81856127a4565b6123e583612fc2565b612406846117215f5160206151bc5f395f51905f52898989611ed78b612fe3565b611939818787613127565b6015546001600160a01b0380831691160361243f57604051634ae9dc4b60e11b815260040160405180910390fd5b6016546001600160a01b039081169082160361246e57604051630426574760e11b815260040160405180910390fd5b612476612652565b6040516001600160a01b038216907fb7f912b432bbc47994e136552c5b7848d71048222eab29b00c35c00899155c72905f90a2601580546001600160a01b0319166001600160a01b0392909216919091179055565b5f42600954116124dc575060095490565b504290565b5f6124eb83612fc2565b5f848152600860205260408120600281015490919061253b905f5160206151fc5f395f51905f52908890612527906001600160a01b0316611e7c565b8860405160200161170694939291906150e8565b60028301549091505f90612559906001600160a01b03168387613663565b9050801561259e576002830154612578906001600160a01b0316612fe3565b50600283015461259490889085906001600160a01b03166135e3565b9350505050611db8565b5f6125e45f5160206151fc5f395f51905f52896125d0875f01600c9054906101000a90046001600160a01b0316612fe3565b8a60405160200161170694939291906150e8565b84549091505f9061260690600160601b90046001600160a01b03168389613663565b90508061262657604051635d29e1bd60e01b815260040160405180910390fd5b8454612645908a908790600160601b90046001600160a01b03166135e3565b9998505050505050505050565b6001546001600160a01b0316331461174b57604051630581a50f60e41b8152683737ba1030b236b4b760b91b6004820152336024820152604401610d30565b7f0000000000000000000000000000000000000000000000000000000000000000815f01516001600160601b031611806126ea575060208101516001600160a01b03161580156126ea575080516001600160601b031615155b1561270b57604051600162505a9360e01b0319815260040160405180910390fd5b600e548151602080840151604080516001600160601b038087168252909416928401929092526001600160a01b03600160601b90940484168383015292909216606082015290517f2b9fb16353eaf3b33538b5a2635d05cee2876d96ddd81b0d2ea647b44594a11b9181900360800190a180516020909101516001600160a01b0316600160601b026001600160601b0390911617600e55565b81546001600160a01b03828116600160601b90920416146127f557604051630581a50f60e41b8152683737ba1037bbb732b960b91b60048201526001600160a01b0382166024820152604401610d30565b5050565b604051632b2c27d760e01b815260040160405180910390fd5b600f5460ff161561174b5760405163d93c066560e01b815260040160405180910390fd5b60026010540361285957604051633ee5aeb560e01b815260040160405180910390fd5b6002601055565b6001600160a01b038116610a5b57604051630426574760e11b815260040160405180910390fd5b5f6128906124cb565b90505f600a54826128a19190614ff4565b90505f811180156128b35750600b5415155b1561290e576004545f036128dd578060095f8282546128d29190614f9c565b9091555061290e9050565b60045481600b546128ee9190615090565b6128f89190614ed4565b600c5f8282546129089190614f9c565b90915550505b50600a55565b61291d81613624565b6004820155600c54600390910155565b80156127f5575f61294c6a0c097ce7bc90715b34b9f160241b83615090565b905080836004015461295e9190614ff4565b6004840155601954612971908390614f9c565b601955505050565b5f836129858484614f9c565b61208b9190614ff4565b5f6001600160601b038211156129c2576040516306dfcc6560e41b81526060600482015260248101839052604401610d30565b5090565b604051636eb1769f60e11b81525f906001600160a01b0385169063dd62ed3e906129f69030908790600401614e75565b602060405180830381865afa158015612a11573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612a359190614f61565b9050612a4b8484612a468585614f9c565b6136d3565b50505050565b7faeea2b5ee5fb4e4473cc85584d77137f4c6f996691f89746291ce5542512001560025482604051612a84929190614ef3565b60405180910390a1600255565b610a8083846001600160a01b031663a9059cbb8585604051602401612ab7929190615103565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505061375f565b5f306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148015612b4157507f000000000000000000000000000000000000000000000000000000000000000046145b15612b6b57507f000000000000000000000000000000000000000000000000000000000000000090565b61166b604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b5f5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166370a08231306040518263ffffffff1660e01b8152600401612c60919061482d565b602060405180830381865afa158015612c7b573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612c9f9190614f61565b90505f601954601854612cb29190614ff4565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031603612d2d578381600354612d1c9190614f9c565b612d269190614f9c565b9250612d3a565b612d378482614f9c565b92505b82821015612d5f5781836040516365ac9d4360e01b8152600401610d30929190614ef3565b5050919050565b335f908152600d602052604090205460ff16612dac57604051630581a50f60e41b81526b3737ba103737ba34b334b2b960a11b6004820152336024820152604401610d30565b612db4612179565b600c556009544210612df6576014546001600160801b0316612de46a0c097ce7bc90715b34b9f160241b84615090565b612dee9190614ed4565b600b55612e52565b5f42600954612e059190614ff4565b600b54612e129190615090565b6014549091506001600160801b0316612e396a0c097ce7bc90715b34b9f160241b85615090565b612e439083614f9c565b612e4d9190614ed4565b600b55505b601454612e68906001600160801b031642614f9c565b60095542600a55600b546a0c097ce7bc90715b34b9f160241b1115612ea057604051631a9bcffd60e31b815260040160405180910390fd5b5f601954601854612eb19190614ff4565b90505f612ebe8483614f9c565b90508360185f828254612ed19190614f9c565b9091555050604080518581523360208201527f48f411442545d43fc993afadc0fd408cf0d1c606f3d10d1de7b05c994463ed97910160405180910390a16040805160c08082018352868252602080830186905282840185905260608084018890526014546001600160801b0316608080860182905260095460a0968701819052601a8c9055601b8a9055601c899055601d8b9055601e839055601f81905587518c81529485018a905296840188905291830189905290820152918201929092527fd0587546e17e5ccacd913705ae1e04dc4fb5f0ba61c1fe539fd10232aa445d41910160405180910390a150505050565b80421115610a5b5760405163762ef75f60e01b815260040160405180910390fd5b6001600160a01b03165f90815260136020526040902080546001810190915590565b5f612173613011612ae9565b8360405161190160f01b8152600281019290925260228201526042902090565b5f61303d848484613663565b905080612a4b57604051635d29e1bd60e01b815260040160405180910390fd5b5f613066612812565b61306e612836565b5f841161308e5760405163da7e9f7f60e01b815260040160405180910390fd5b613097856131d1565b6130a3858585856137c2565b90506130ae8161337e565b61208b6001601055565b600f5460ff1661174b57604051638dfc202b60e01b815260040160405180910390fd5b6130e36130b8565b600f805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b60405161311d919061482d565b60405180910390a1565b61312f612836565b5f811161314f5760405163da7e9f7f60e01b815260040160405180910390fd5b61315a838383613a85565b6131638261337e565b610a806001601055565b61317681612860565b6005546040516001600160a01b038084169216907fb7c75e18483153b6b8c9c9a379610df50999c601b86f112f162ea62bc8678724905f90a3600580546001600160a01b0319166001600160a01b0392909216919091179055565b6001601754600160a01b900460ff1660028111156131f1576131f1614d0c565b0361328457601554604051630bb7c8fd60e31b81526001600160a01b0390911690635dbe47e89061322690849060040161482d565b602060405180830381865afa158015613241573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906132659190614e3f565b610a5b5780604051636fb518f560e01b8152600401610d30919061482d565b6002601754600160a01b900460ff1660028111156132a4576132a4614d0c565b03610a5b57601754604051630bb7c8fd60e31b81526001600160a01b0390911690635dbe47e8906132d990849060040161482d565b602060405180830381865afa1580156132f4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906133189190614e3f565b15610a5b57806040516339c1aba760e21b8152600401610d30919061482d565b306001600160a01b0384160361337357610a807f00000000000000000000000000000000000000000000000000000000000000008383612a91565b610a80838383613cc5565b5f8181526008602052604090206014548154600160801b9091046001600160801b03166001600160601b039091161080156133c2575080546001600160601b031615155b156127f557601454815460405163667d9f8d60e01b8152600160801b9092046001600160801b031660048301526001600160601b03166024820152604401610d30565b61340d612812565b613415612836565b5f81116134355760405163da7e9f7f60e01b815260040160405180910390fd5b825461345090600160601b90046001600160a01b03166131d1565b61315a838383613cf1565b61346481612860565b6001546040516001600160a01b038084169216907fbf265e8326285a2747e33e54d5945f7111f2b5edb826eb8c08d4677779b3ff97905f90a3600180546001600160a01b0319166001600160a01b0392909216919091179055565b6134c7612812565b600f805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586131103390565b606061166b7f00000000000000000000000000000000000000000000000000000000000000006011613f27565b606061166b7f00000000000000000000000000000000000000000000000000000000000000006012613f27565b61355e612812565b613566612836565b613163838383613fd0565b60605f5f846001600160a01b03168460405161358d919061511c565b5f60405180830381855af49150503d805f81146135c5576040519150601f19603f3d011682016040523d82523d5f602084013e6135ca565b606091505b50915091506135da858383614184565b95945050505050565b5f6135ec612812565b6135f4612836565b5f6136008585856141d7565b90508060195f8282546136139190614f9c565b909155505060016010559050611db8565b5f8160030154613632612179565b61363c9190614ff4565b600183015461365491906001600160601b0316615090565b82600401546121739190614f9c565b5f836001600160a01b03163b5f036136c1575f5f613681858561445e565b5090925090505f81600381111561369a5761369a614d0c565b1480156136b85750856001600160a01b0316826001600160a01b0316145b92505050611db8565b6136cc8484846144a7565b9050611db8565b5f836001600160a01b031663095ea7b384846040516024016136f6929190615103565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050905061372f848261457e565b612a4b5761375984856001600160a01b031663095ea7b3865f604051602401612ab7929190615103565b612a4b84825b5f5f60205f8451602086015f885af18061377e576040513d5f823e3d81fd5b50505f513d915081156137955780600114156137a2565b6001600160a01b0384163b155b15612a4b5783604051635274afe760e01b8152600401610d30919061482d565b5f6137cc83612860565b6137d582612860565b6137dd612887565b306137e66145c3565b60055460405162834efd60e71b81529193505f916001600160a01b03909116906341a77e809061381e9089908b908a90600401614faf565b602060405180830381865afa158015613839573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061385d9190614f61565b90508560035f8282546138709190614f9c565b925050819055508060045f8282546138889190614f9c565b90915550506001600160a01b0387165f90815260066020526040812080548892906138b4908490614f9c565b90915550506001600160a01b0387165f90815260076020526040812080548392906138e0908490614f9c565b925050819055506040518060e001604052806138fb8861298f565b6001600160601b03168152602001886001600160a01b031681526020016139218361298f565b6001600160601b0390811682526001600160a01b03888116602080850191909152888216604080860191909152600c546060808701919091525f60809687018190528a815260088452829020875193880151938616600160601b948616850217815591870151908701519416938316909102929092176001830155918301516002820180546001600160a01b031916919093161790915560a0820151600382015560c0909101516004909101556139d9878388613338565b825f51602061523c5f395f51905f52888889856040516139fc9493929190614fce565b60405180910390a2836001600160a01b03165f6001600160a01b0316845f51602061525c5f395f51905f5284604051613a3791815260200190565b60405180910390a4827fe03d018e6f94071d97d5befc6a430dedbb1792ef805cf2042edbc2bb75b445285f8784604051613a7393929190615127565b60405180910390a25050949350505050565b613a8d612887565b613a9683612914565b82545f90613aae9083906001600160601b0316614ff4565b6005548554600187015460405162834efd60e71b81529394505f936001600160a01b03938416936341a77e8093613afd938893600160601b928390048416939290910490911690600401614faf565b602060405180830381865afa158015613b18573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613b3c9190614f61565b90508260035f828254613b4f9190614ff4565b90915550506001850154600454613b71916001600160601b0316908390612979565b6004558454600160601b90046001600160a01b03165f9081526006602052604081208054859290613ba3908490614ff4565b909155505060018501548554600160601b90046001600160a01b03165f90815260076020526040902054613be2916001600160601b0316908390612979565b8554600160601b90046001600160a01b03165f90815260076020526040902055613c0b8261298f565b85546001600160601b0319166001600160601b0391909116178555613c2f8161298f565b6001860180546001600160601b0319166001600160601b0392909216919091179055613c6d308654600160601b90046001600160a01b031685613338565b845460405185917f5d89f707c2087d1f0c2ec0ef87eb3bbf3c0eac23c0028df006c7de091258544d91613cb691600160601b90046001600160a01b031690879087908790614fce565b60405180910390a25050505050565b610a807f00000000000000000000000000000000000000000000000000000000000000008484846145d5565b613cf9612887565b613d0283612914565b825430905f90613d1c9084906001600160601b0316614f9c565b6005548654600188015460405162834efd60e71b81529394505f936001600160a01b03938416936341a77e8093613d6b938893600160601b928390048416939290910490911690600401614faf565b602060405180830381865afa158015613d86573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613daa9190614f61565b6001870154600454919250613dcc916001600160601b03909116908390612979565b6004819055508360035f828254613de39190614f9c565b90915550508554600160601b90046001600160a01b03165f9081526006602052604081208054869290613e17908490614f9c565b909155505060018601548654600160601b90046001600160a01b03165f90815260076020526040902054613e56916001600160601b0316908390612979565b8654600160601b90046001600160a01b03165f90815260076020526040902055613e7f8161298f565b6001870180546001600160601b0319166001600160601b0392909216919091179055613eaa8261298f565b86546001600160601b0319166001600160601b039190911617808755613ee190600160601b90046001600160a01b03168486613338565b855460405186915f51602061523c5f395f51905f5291613f1791600160601b90046001600160a01b031690889087908790614fce565b60405180910390a2505050505050565b606060ff8314613f4157613f3a836145fd565b9050612173565b818054613f4d9061514b565b80601f0160208091040260200160405190810160405280929190818152602001828054613f799061514b565b8015613fc45780601f10613f9b57610100808354040283529160200191613fc4565b820191905f5260205f20905b815481529060010190602001808311613fa757829003601f168201915b50505050509050612173565b613fd981612860565b613fe1612887565b613fea83612914565b6005548354600185015460405162834efd60e71b81525f936001600160a01b03908116936341a77e809361403e936001600160601b03831693600160601b9384900481169390920490911690600401614f37565b602060405180830381865afa158015614059573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061407d9190614f61565b600185015460045491925061409f916001600160601b03909116908390612979565b60045560018401548454600160601b90046001600160a01b03165f908152600760205260409020546140dc916001600160601b0316908390612979565b8454600160601b90046001600160a01b03165f908152600760205260409020556141058161298f565b6001850180546001600160601b0319166001600160601b039290921691909117905560028401546040518281526001600160a01b0384811692169085905f51602061525c5f395f51905f529060200160405180910390a45060029290920180546001600160a01b0319166001600160a01b039093169290921790915550565b606082614199576141948261463a565b611db8565b81511580156141b057506001600160a01b0384163b155b156141d05783604051639996b31560e01b8152600401610d30919061482d565b5080611db8565b5f6141e0612887565b6141e983612914565b5f6a0c097ce7bc90715b34b9f160241b84600401546142089190614ed4565b600e549091505f90614223906001600160601b031683614ff4565b9050805f03614236575f92505050611db8565b61424e6a0c097ce7bc90715b34b9f160241b83615090565b856004015461425d9190614ff4565b6004808701919091556005548654600188015460405162834efd60e71b81525f946001600160a01b03948516946341a77e80946142b9946001600160601b03821694600160601b92839004841694929091049092169101614f37565b602060405180830381865afa1580156142d4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906142f89190614f61565b9050846001600160a01b0316875f51602061519c5f395f51905f528484604051614323929190614ef3565b60405180910390a36001860154600454614348916001600160601b0316908390612979565b60045560018601548654600160601b90046001600160a01b03165f90815260076020526040902054614385916001600160601b0316908390612979565b8654600160601b90046001600160a01b03165f908152600760205260409020556143ae8161298f565b6001870180546001600160601b0319166001600160601b03929092169190911790556143fb7f00000000000000000000000000000000000000000000000000000000000000008684612a91565b600e546001600160601b03161561445457600e54614454907f0000000000000000000000000000000000000000000000000000000000000000906001600160a01b03600160601b820416906001600160601b0316612a91565b5095945050505050565b5f5f5f8351604103614495576020840151604085015160608601515f1a61448788828585614663565b9550955095505050506144a0565b505081515f91506002905b9250925092565b5f5f5f856001600160a01b031685856040516024016144c7929190615183565b60408051601f198184030181529181526020820180516001600160e01b0316630b135d3f60e11b179052516144fc919061511c565b5f60405180830381855afa9150503d805f8114614534576040519150601f19603f3d011682016040523d82523d5f602084013e614539565b606091505b509150915081801561454d57506020815110155b801561457457508051630b135d3f60e11b906145729083016020908101908401614f61565b145b9695505050505050565b5f5f5f5f60205f8651602088015f8a5af192503d91505f519050828015614574575081156145af5780600114614574565b50505050506001600160a01b03163b151590565b5f546145d0816001614f9c565b5f5590565b612a4b84856001600160a01b03166323b872dd868686604051602401612ab793929190615127565b60605f61460983614721565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b80511561464a5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f80806fa2a8918ca85bafe22016d0b997e4df60600160ff1b0384111561469257505f91506003905082614717565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa1580156146e3573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b03811661470e57505f925060019150829050614717565b92505f91508190505b9450945094915050565b5f60ff8216601f81111561217357604051632cd44ac360e21b815260040160405180910390fd5b634e487b7160e01b5f52604160045260245ffd5b6001600160a01b0381168114610a5b575f5ffd5b5f6040828403128015614781575f5ffd5b50604080519081016001600160401b03811182821017156147a4576147a4614748565b60405282356001600160601b03811681146147bd575f5ffd5b815260208301356147cd8161475c565b60208201529392505050565b5f5f604083850312156147ea575f5ffd5b8235915060208301356147fc8161475c565b809150509250929050565b5f60208284031215614817575f5ffd5b81356001600160801b0381168114611db8575f5ffd5b6001600160a01b0391909116815260200190565b8015158114610a5b575f5ffd5b5f5f6040838503121561485f575f5ffd5b823561486a8161475c565b915060208301356147fc81614841565b5f6020828403121561488a575f5ffd5b8135611db88161475c565b803560ff81168114611d73575f5ffd5b5f5f5f5f5f5f5f60e0888a0312156148bb575f5ffd5b8735965060208801356148cd8161475c565b955060408801359450606088013593506148e960808901614895565b9699959850939692959460a0840135945060c09093013592915050565b5f60208284031215614916575f5ffd5b5035919050565b5f5f5f6060848603121561492f575f5ffd5b8335925060208401356149418161475c565b929592945050506040919091013590565b5f82601f830112614961575f5ffd5b81356001600160401b0381111561497a5761497a614748565b604051601f8201601f19908116603f011681016001600160401b03811182821017156149a8576149a8614748565b6040528181528382016020018510156149bf575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f5f5f5f5f60c087890312156149f0575f5ffd5b863595506020870135614a028161475c565b94506040870135614a128161475c565b93506060870135614a228161475c565b92506080870135915060a08701356001600160401b03811115614a43575f5ffd5b614a4f89828a01614952565b9150509295509295509295565b5f5f60408385031215614a6d575f5ffd5b50508035926020909101359150565b5f5f5f5f5f60a08688031215614a90575f5ffd5b853594506020860135614aa28161475c565b93506040860135614ab28161475c565b92506060860135915060808601356001600160401b03811115614ad3575f5ffd5b614adf88828901614952565b9150509295509295909350565b5f60208284031215614afc575f5ffd5b813560038110611db8575f5ffd5b5f5f5f5f5f60a08688031215614b1e575f5ffd5b85359450602086013593506040860135614ab28161475c565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b60ff60f81b8816815260e060208201525f614b8360e0830189614b37565b8281036040840152614b958189614b37565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b81811015614bea578351835260209384019390920191600101614bcc565b50909b9a5050505050505050505050565b5f5f5f60608486031215614c0d575f5ffd5b833592506020840135614c1f8161475c565b91506040840135614c2f8161475c565b809150509250925092565b5f5f60208385031215614c4b575f5ffd5b82356001600160401b03811115614c60575f5ffd5b8301601f81018513614c70575f5ffd5b80356001600160401b03811115614c85575f5ffd5b8560208260051b8401011115614c99575f5ffd5b6020919091019590945092505050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b82811015614d0057603f19878603018452614ceb858351614b37565b94506020938401939190910190600101614ccf565b50929695505050505050565b634e487b7160e01b5f52602160045260245ffd5b6020810160038310614d4057634e487b7160e01b5f52602160045260245ffd5b91905290565b5f5f5f5f5f5f5f60e0888a031215614d5c575f5ffd5b873596506020880135614d6e8161475c565b95506040880135614d7e8161475c565b9450606088013593506148e960808901614895565b5f5f5f5f5f5f60c08789031215614da8575f5ffd5b863595506020870135945060408701359350614dc660608801614895565b9598949750929560808101359460a0909101359350915050565b5f5f5f60608486031215614df2575f5ffd5b833592506020840135915060408401356001600160401b03811115614e15575f5ffd5b614e2186828701614952565b9150509250925092565b6001600160801b0391909116815260200190565b5f60208284031215614e4f575f5ffd5b8151611db881614841565b5f60208284031215614e6a575f5ffd5b8151611db88161475c565b6001600160a01b0392831681529116602082015260400190565b733737ba1031b630b4b6b2b91037b91037bbb732b960611b81526001600160a01b0391909116602082015260400190565b634e487b7160e01b5f52601160045260245ffd5b5f82614eee57634e487b7160e01b5f52601260045260245ffd5b500490565b918252602082015260400190565b6001600160a01b039690961686526020860194909452604085019290925260ff166060840152608083015260a082015260c00190565b6001600160601b039390931683526001600160a01b03918216602084015216604082015260600190565b5f60208284031215614f71575f5ffd5b5051919050565b5f5f60408385031215614f89575f5ffd5b825160208401519092506147fc81614841565b8082018082111561217357612173614ec0565b9283526001600160a01b03918216602084015216604082015260600190565b6001600160a01b0394909416845260208401929092526040830152606082015260800190565b8181038181111561217357612173614ec0565b634e487b7160e01b5f52603260045260245ffd5b5f5f8335601e19843603018112615030575f5ffd5b8301803591506001600160401b03821115615049575f5ffd5b60200191503681900382131561505d575f5ffd5b9250929050565b5f81518060208401855e5f93019283525090919050565b828482375f8382015f81526145748185615064565b808202811582820484141761217357612173614ec0565b6001600160a01b0397881681529590961660208601526040850193909352606084019190915260ff16608083015260a082015260c081019190915260e00190565b93845260208401929092526040830152606082015260800190565b6001600160a01b03929092168252602082015260400190565b5f611db88284615064565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600181811c9082168061515f57607f821691505b60208210810361517d57634e487b7160e01b5f52602260045260245ffd5b50919050565b828152604060208201525f61208b6040830184614b3756fefc6ecd966b430510beae9124efe7dd517ed5286d7ed0ae258f8556c7a9765a444aa66922cb54c64ed3272a327b8339c58555b41169c1fb39efb4535c6f9d1703867b6c541e0f40dd4e47d08157ce464787a92cf2aa13644ecf51331804fc0847ed72e8b8797645ac76e0741da7d8d0afc24dddb21ca6338d5c5520b05b192306613950e00b9e6a31ffbad41bdf66f194d1a276b1e117790800eaca48ebdd1d551a325385f16807e99fb688b597db78b00faee313dcf02e882dd16daab6fc3e1f76ecb53d27210f298c33177d8fc8947b65cc94a2d54f65112683d2369058af90416c3c1f040daee5e4cca619ec40763a6c4a06ec7458fffe7cf841c3e4da4a65300c1a75d358323d4ec7b212cd1e88afbd0250186b09c957b383ee5e6ad897f50b1db0ab4204ded87d69d4fe6739295ee2fef3e78103e2099d8e18e112053b20a2646970667358221220089a1015cad0ef5b3a15394f10b5e96969ef89caab87e2e4f7f349c4ff4275f264736f6c63430008210033526567656e5374616b6572576974686f757444656c6567617465537572726f67617465566f746573000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007dd9c5cba05e151c895fde1cf355c9a1d5da642900000000000000000000000043b92510e301aa4f23a59f557476322160a22d1c00000000000000000000000000000000000000000000000000071afd498d0000000000000000000000000000ed0044feb17407c989c5703767f3a8de3f9dbd3f0000000000000000000000000000000000000000000000000000000000278d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf8acc1d7e0f460483da3fa73d7c57139a19a74
Deployed Bytecode
0x608060405234801561000f575f5ffd5b5060043610610378575f3560e01c80630495d7251461037c578063073a2ae914610391578063081969b4146103a457806308484dee146103b75780630e15561a146103d35780631285d6b5146103dc5780631c39b672146103f6578063255a4f731461041d57806325fc4d3014610430578063276d7334146104435780632b7d9706146104575780632c4f88cd1461046a57806331cb4bea1461047d57806334560e50146104915780633644e515146104a457806337e9f64a146104ac57806339aab4b0146104b55780633c6b16ab146104c95780633d4bd89f146104dc5780633f4520f5146104ef5780633f4ba83a146104fa578063441a3e701461050257806346800f7514610515578063495835761461053c578063558007991461054f5780635a57b46f146105625780635ade228a1461056a5780635ba8fcfe146105745780635c975abb146105875780635cc27d6f1461059e57806361dc0549146105a857806364b5c91c146105b1578063656c579f146105d05780636b036f45146105e3578063703762da146105fb578063704b6c021461060e57806372a35df91461062157806376c5d75814610634578063774da6741461064857806377d7a921146106945780637aa23c52146106a85780637acb7757146106b15780637e31d2cc146106c45780637ecebe00146106d7578063817b1cd2146106ea57806383ce8511146106f35780638456cb591461070657806384b0196e1461070e57806389ee09f0146107295780638d0726941461073d5780638fa13835146107505780639718e11b1461076357806399248ea714610774578063a7d545db1461079b578063ac9650d8146107ae578063ae0d27b6146107ce578063ae169a50146107d6578063aec275a9146107e9578063b02c43d0146107fa578063bb60030d146108b0578063be9b19e0146108c3578063c21f6375146108dd578063c3be9978146108f0578063c3c16e4e1461090f578063c6f6173b14610918578063cb3056ad14610921578063cd2ee8f714610934578063ce4b5bbe14610947578063d578ceab1461095c578063da755f0214610965578063db1f598914610978578063ddda8a111461098b578063e6dec27014610993578063e70f9445146109a6578063f520e7e5146109c8578063f5e05cc7146109d9578063f851a44014610a24578063fd6fe8a814610a37575b5f5ffd5b61038f61038a366004614770565b610a4a565b005b61038f61039f3660046147d9565b610a5e565b61038f6103b2366004614807565b610a85565b6103c060045481565b6040519081526020015b60405180910390f35b6103c060185481565b6017546001600160a01b03165b6040516103ca919061482d565b6103e97f0000000000000000000000007dd9c5cba05e151c895fde1cf355c9a1d5da642981565b61038f61042b36600461484e565b610b26565b61038f61043e36600461487a565b610b8c565b6103c05f5160206151dc5f395f51905f5281565b6103c06104653660046148a5565b610c87565b61038f610478366004614906565b611332565b6103c05f5160206151fc5f395f51905f5281565b61038f61049f36600461491d565b611372565b6103c0611662565b6103c060095481565b6103c05f51602061529c5f395f51905f5281565b61038f6104d7366004614906565b611670565b6103c06104ea3660046149db565b611699565b6103c0630f73140081565b61038f611733565b61038f610510366004614a5c565b61174d565b6103c07f000000000000000000000000000000000000000000000000000000000000000081565b61038f61054a36600461487a565b61176f565b61038f61055d366004614807565b6117a2565b61038f6118a8565b6103c062278d0081565b61038f610582366004614a7c565b6118b1565b600f5460ff165b60405190151581526020016103ca565b6103c062093a8081565b6103c0600a5481565b6103c06105bf36600461487a565b60076020525f908152604090205481565b6103c06105de366004614906565b611941565b601454600160801b90046001600160801b03166103c0565b61038f610609366004614a5c565b611d78565b61038f61061c36600461487a565b611d9a565b6005546103e9906001600160a01b031681565b6103c05f5160206151bc5f395f51905f5281565b600e5461066d906001600160601b03811690600160601b90046001600160a01b031682565b604080516001600160601b0390931683526001600160a01b039091166020830152016103ca565b6103c05f5160206152bc5f395f51905f5281565b6103c060025481565b6103c06106bf3660046147d9565b611dab565b61038f6106d2366004614aec565b611dbf565b6103c06106e536600461487a565b611e7c565b6103c060035481565b61038f610701366004614b0a565b611e96565b61038f611f1e565b610716611f36565b6040516103ca9796959493929190614b65565b6103c05f51602061521c5f395f51905f5281565b61038f61074b36600461487a565b611f78565b61038f61075e366004614a7c565b612032565b6016546001600160a01b03166103e9565b6103e97f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b6103c06107a9366004614bfb565b61207e565b6107c16107bc366004614c3a565b612093565b6040516103ca9190614ca9565b6103c0612179565b6103c06107e4366004614906565b6121c6565b6015546001600160a01b03166103e9565b61085e610808366004614906565b60086020525f9081526040902080546001820154600283015460038401546004909401546001600160601b03808516956001600160a01b03600160601b9687900481169692861695929092048216939091169187565b604080516001600160601b0398891681526001600160a01b0397881660208201529790951694870194909452918416606086015292909216608084015260a083019190915260c082015260e0016103ca565b61038f6108be3660046147d9565b61222c565b601754600160a01b900460ff166040516103ca9190614d20565b6103c06108eb366004614d46565b61224e565b6103c06108fe36600461487a565b60066020525f908152604090205481565b6103c0600b5481565b6103c0600c5481565b6103c061092f366004614906565b6122e9565b61038f610942366004614d93565b612319565b6103c06a0c097ce7bc90715b34b9f160241b81565b6103c060195481565b61038f610973366004614b0a565b6123c5565b61038f61098636600461487a565b612411565b6103c06124cb565b6103e96109a136600461487a565b503090565b61058e6109b436600461487a565b600d6020525f908152604090205460ff1681565b6014546001600160801b03166103c0565b601a54601b54601c54601d54601e54601f546109f795949392919086565b604080519687526020870195909552938501929092526060840152608083015260a082015260c0016103ca565b6001546103e9906001600160a01b031681565b6103c0610a45366004614de0565b6124e1565b610a52612652565b610a5b81612691565b50565b5f828152600860205260409020610a7581336127a4565b610a808184846127f9565b505050565b610a8d612652565b6014546001600160801b03600160801b9091048116908216111580610ab3575060095442115b610ad057604051639bc24cbf60e01b815260040160405180910390fd5b7f5febf96039ec9415638c738f7102ae754ebf18784713f4cfefef8484d352edfe81604051610aff9190614e2b565b60405180910390a1601480546001600160801b03928316600160801b029216919091179055565b610b2e612652565b6001600160a01b0382165f818152600d6020908152604091829020805460ff191685151590811790915591519182527fb33ca1dabfb28a2e1bd06b332c396b1a3d538278691341a5503bbad53f919197910160405180910390a25050565b6016546001600160a01b03808316911603610bba57604051634ae9dc4b60e11b815260040160405180910390fd5b6001600160a01b038116610be157604051630ac58fed60e21b815260040160405180910390fd5b6015546001600160a01b03828116911614801590610c0d57506017546001600160a01b03828116911614155b610c2a57604051630426574760e11b815260040160405180910390fd5b610c32612652565b6040516001600160a01b038216907fd4fe8d4aa58aa780080ba8f9c18852dc59a6c75f8dc8fe07dcc358330212ec24905f90a2601680546001600160a01b0319166001600160a01b0392909216919091179055565b5f610c90612812565b610c98612836565b610ca187612860565b601654604051630bb7c8fd60e31b81526001600160a01b0390911690635dbe47e890610cd1908a9060040161482d565b602060405180830381865afa158015610cec573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d109190614e3f565b8790610d3957604051632dde5e4f60e11b8152600401610d30919061482d565b60405180910390fd5b505f876001600160a01b03166338d52e0f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d77573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d9b9190614e5a565b9050806001600160a01b03167f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031614610e13577f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281604051634e83a9b960e01b8152600401610d30929190614e75565b505f88815260086020526040902060028101546001600160a01b03163314801590610e4f57508054600160601b90046001600160a01b03163314155b15610e6f5733604051630581a50f60e41b8152600401610d309190614e8f565b80546040516302f50e9b60e41b81525f916001600160a01b03808c1692632f50e9b092610ea892600160601b909104169060040161482d565b602060405180830381865afa158015610ec3573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ee79190614e3f565b905080610f1d578154604051631ec3d94d60e31b8152610d30918b91600160601b9091046001600160a01b031690600401614e75565b610f25612887565b610f2e82612914565b5f6a0c097ce7bc90715b34b9f160241b8360040154610f4d9190614ed4565b9050888180821115610f7457604051634e64fbf760e11b8152600401610d30929190614ef3565b5050885f0361102657896001600160a01b0316336001600160a01b03168c5f51602061527c5f395f51905f525f604051610fb091815260200190565b60405180910390a4604051630e22c14b60e01b81526001600160a01b038b1690630e22c14b90610fee9033905f908d908d908d908d90600401614f01565b5f604051808303815f87803b158015611005575f5ffd5b505af1158015611017573d5f5f3e3d5ffd5b505050505f935050505061131d565b889350611033838a61292d565b6001830154600554845460405162834efd60e71b81526001600160601b03808516945f946001600160a01b03908116946341a77e809461108b94821693600160601b9283900484169392900490911690600401614f37565b602060405180830381865afa1580156110a6573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110ca9190614f61565b90506110d98282600454612979565b6004558454600160601b90046001600160a01b03165f908152600760205260409020546111099083908390612979565b8554600160601b90046001600160a01b03165f908152600760205260409020556111328161298f565b6001860180546001600160601b0319166001600160601b039290921691909117905560405133908e905f51602061519c5f395f51905f5290611177908a908690614ef3565b60405180910390a36111aa7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28d886129c6565b8b6001600160a01b0316336001600160a01b03168e5f51602061527c5f395f51905f52896040516111dd91815260200190565b60405180910390a4604051630e22c14b60e01b81526001600160a01b038d1690630e22c14b9061121b9033908a908f908f908f908f90600401614f01565b5f604051808303815f87803b158015611232575f5ffd5b505af1158015611244573d5f5f3e3d5ffd5b505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663dd62ed3e308e6040518363ffffffff1660e01b8152600401611296929190614e75565b602060405180830381865afa1580156112b1573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112d59190614f61565b156113175760405162461bcd60e51b8152602060048201526012602482015271616c6c6f77616e6365206e6f74207a65726f60701b6044820152606401610d30565b50505050505b6113276001601055565b979650505050505050565b61133a612652565b6002548111158061134c575060095442115b61136957604051637e72b6c760e01b815260040160405180910390fd5b610a5b81612a51565b61137a612812565b611382612836565b6002548111156113a55760405163192501ab60e31b815260040160405180910390fd5b5f8381526008602052604090206113ba612887565b6113c381612914565b5f6a0c097ce7bc90715b34b9f160241b82600401546113e29190614ed4565b6005548354600185015460405160016243a1e360e11b031981526001600160601b0380841660048301526001600160a01b03600160601b94859004811660248401529383048416604483015290911660648201529293505f928392919091169063ff78bc3a906084016040805180830381865afa158015611465573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114899190614f78565b915091508015806114a6575060018401546001600160601b031682145b156114c757604051632b2807ef60e21b815260048101839052602401610d30565b60018401546001600160601b0316821180156114e257508483105b156115005760405163b219487760e01b815260040160405180910390fd5b848381111561150c5750825b6001850154604080516001600160601b0390921682526020820185905233908201526001600160a01b03881660608201526080810182905288907f04d2114cbff027138003f7a9e452588f683397107f4d4ab1a83f17e80ebad12e9060a00160405180910390a26001850154600454611590916001600160601b0316908590612979565b60045560018501548554600160601b90046001600160a01b03165f908152600760205260409020546115cd916001600160601b0316908590612979565b8554600160601b90046001600160a01b03165f908152600760205260409020556115f68361298f565b6001860180546001600160601b0319166001600160601b0392909216919091179055611622858261292d565b8015611653576116537f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28883612a91565b5050505050610a806001601055565b5f61166b612ae9565b905090565b611678612836565b5f61168282612c12565b905061168e8282612d66565b50610a5b6001601055565b5f6116a383612fc2565b611727846117215f51602061521c5f395f51905f528a8a8a8a6116c58c612fe3565b6040805160208101979097528601949094526001600160a01b03928316606086015290821660808501521660a083015260c082015260e08101879052610100015b60405160208183030381529060405280519060200120613005565b84613031565b6113278488888861305d565b61173b6130b8565b611743612652565b61174b6130db565b565b5f82815260086020526040902061176481336127a4565b610a80818484613127565b611777612652565b600954421161179957604051630c6ed80760e01b815260040160405180910390fd5b610a5b8161316d565b6117aa612652565b60095442116117cc576040516329fc2c8b60e01b815260040160405180910390fd5b62093a80816001600160801b0316101580156117f55750630f731400816001600160801b031611155b816001600160801b0316906118205760405163074faf5d60e31b8152600401610d3091815260200190565b506014546001600160801b0380831691160361184f57604051634ae9dc4b60e11b815260040160405180910390fd5b7f823b3cede11929e1b876086e20886121c9c32f04426a36ed323a85d541df3b8e8160405161187e9190614e2b565b60405180910390a1601480546001600160801b0319166001600160801b0392909216919091179055565b610a5b33612fe3565b5f8581526008602052604090206118c881856127a4565b6118d183612fc2565b61192e846117215f51602061529c5f395f51905f528989896118f28b612fe3565b6040805160208101969096528501939093526001600160a01b03918216606085015216608083015260a082015260c0810187905260e001611706565b6119398187876127f9565b505050505050565b5f61194a612812565b611952612836565b7f0000000000000000000000007dd9c5cba05e151c895fde1cf355c9a1d5da64296001600160a01b03167f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316146119c457604051630d54ba3960e11b815260040160405180910390fd5b5f828152600860205260409020805460028201546001600160a01b03600160601b909204821691163314801590611a0457506001600160a01b0381163314155b15611a245733604051630581a50f60e41b8152600401610d309190614e8f565b611a2d816131d1565b611a35612887565b611a3e82612914565b5f6a0c097ce7bc90715b34b9f160241b8360040154611a5d9190614ed4565b9050805f03611a71575f9350505050611d69565b6005548354600185015460405162834efd60e71b815293965086935f936001600160a01b03908116936341a77e8093611aca936001600160601b03831693600160601b9384900481169390920490911690600401614f37565b602060405180830381865afa158015611ae5573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b099190614f61565b84549091505f90611b249087906001600160601b0316614f9c565b6001860154600554875460405162834efd60e71b81529394506001600160601b038316935f936001600160a01b03938416936341a77e8093611b7d938993600160601b9283900484169392900490911690600401614faf565b602060405180830381865afa158015611b98573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bbc9190614f61565b9050611bcb8282600454612979565b6004558654600160601b90046001600160a01b03165f90815260076020526040902054611bfb9083908390612979565b8754600160601b90046001600160a01b03165f90815260076020526040812091909155600380548a9290611c30908490614f9c565b90915550506001600160a01b0386165f90815260066020526040812080548a9290611c5c908490614f9c565b90915550611c6c9050878661292d565b611c758361298f565b87546001600160601b0319166001600160601b0391909116178755611c998161298f565b6001880180546001600160601b0319166001600160601b03929092169190911790553080611cf0576001880154604051633f88874160e21b8152610d3091600160601b90046001600160a01b03169060040161482d565b611cfb30828b613338565b336001600160a01b03168a5f51602061519c5f395f51905f528b88604051611d24929190614ef3565b60405180910390a3895f51602061523c5f395f51905f52888b8786604051611d4f9493929190614fce565b60405180910390a2611d608a61337e565b50505050505050505b611d736001601055565b919050565b5f828152600860205260409020611d8f81336127a4565b610a80818484613405565b611da2612652565b610a5b8161345b565b5f611db83384843361305d565b9392505050565b611dc7612652565b806002811115611dd957611dd9614d0c565b601754600160a01b900460ff166002811115611df757611df7614d0c565b03611e1557604051634ae9dc4b60e11b815260040160405180910390fd5b806002811115611e2757611e27614d0c565b6040517fe192fac29e26bb1206168747d1ec5c6dd9f61bb550a815072b0e136f6018f925905f90a26017805482919060ff60a01b1916600160a01b836002811115611e7457611e74614d0c565b021790555050565b6001600160a01b03165f9081526013602052604090205490565b5f858152600860205260409020611ead81856127a4565b611eb683612fc2565b611f13846117215f5160206152bc5f395f51905f52898989611ed78b612fe3565b60408051602081019690965285019390935260608401919091526001600160a01b0316608083015260a082015260c0810187905260e001611706565b611939818787613405565b611f26612812565b611f2e612652565b61174b6134bf565b5f6060805f5f5f6060611f476134fc565b611f4f613529565b604080515f80825260208201909252600f60f81b9b939a50919850469750309650945092509050565b611f80612652565b6017546001600160a01b03808316911603611fae57604051634ae9dc4b60e11b815260040160405180910390fd5b6016546001600160a01b0390811690821603611fdd57604051630426574760e11b815260040160405180910390fd5b6040516001600160a01b038216907faac29fbc402d011d11d07d15f6ef2d175c7f8b186e3763b43334f45b8079a1b7905f90a2601780546001600160a01b0319166001600160a01b0392909216919091179055565b5f85815260086020526040902061204981856127a4565b61205283612fc2565b612073846117215f5160206151dc5f395f51905f528989896118f28b612fe3565b611939818787613556565b5f61208b3385858561305d565b949350505050565b604080515f815260208101909152606090826001600160401b038111156120bc576120bc614748565b6040519080825280602002602001820160405280156120ef57816020015b60608152602001906001900390816120da5790505b5091505f5b838110156121705761214b3086868481811061211257612112615007565b9050602002810190612124919061501b565b856040516020016121379392919061507b565b604051602081830303815290604052613571565b83828151811061215d5761215d615007565b60209081029190910101526001016120f4565b50505b92915050565b5f6004545f0361218a5750600c5490565b600454600a546121986124cb565b6121a29190614ff4565b600b546121af9190615090565b6121b99190614ed4565b600c5461166b9190614f9c565b5f81815260086020526040812060028101546001600160a01b0316331480159061220157508054600160601b90046001600160a01b03163314155b156122215733604051630581a50f60e41b8152600401610d309190614e8f565b611db88382336135e3565b5f82815260086020526040902061224381336127a4565b610a80818484613556565b60405163d505accf60e01b81525f906001600160a01b037f0000000000000000000000007dd9c5cba05e151c895fde1cf355c9a1d5da6429169063d505accf906122a890339030908d908b908b908b908b906004016150a7565b5f604051808303815f87803b1580156122bf575f5ffd5b505af19250505080156122d0575060015b506122dd3389898961305d565b98975050505050505050565b5f8181526008602052604081206a0c097ce7bc90715b34b9f160241b9061230f90613624565b6121739190614ed4565b5f86815260086020526040902061233081336127a4565b60405163d505accf60e01b81526001600160a01b037f0000000000000000000000007dd9c5cba05e151c895fde1cf355c9a1d5da6429169063d505accf9061238890339030908b908b908b908b908b906004016150a7565b5f604051808303815f87803b15801561239f575f5ffd5b505af19250505080156123b0575060015b506123bc818888613405565b50505050505050565b5f8581526008602052604090206123dc81856127a4565b6123e583612fc2565b612406846117215f5160206151bc5f395f51905f52898989611ed78b612fe3565b611939818787613127565b6015546001600160a01b0380831691160361243f57604051634ae9dc4b60e11b815260040160405180910390fd5b6016546001600160a01b039081169082160361246e57604051630426574760e11b815260040160405180910390fd5b612476612652565b6040516001600160a01b038216907fb7f912b432bbc47994e136552c5b7848d71048222eab29b00c35c00899155c72905f90a2601580546001600160a01b0319166001600160a01b0392909216919091179055565b5f42600954116124dc575060095490565b504290565b5f6124eb83612fc2565b5f848152600860205260408120600281015490919061253b905f5160206151fc5f395f51905f52908890612527906001600160a01b0316611e7c565b8860405160200161170694939291906150e8565b60028301549091505f90612559906001600160a01b03168387613663565b9050801561259e576002830154612578906001600160a01b0316612fe3565b50600283015461259490889085906001600160a01b03166135e3565b9350505050611db8565b5f6125e45f5160206151fc5f395f51905f52896125d0875f01600c9054906101000a90046001600160a01b0316612fe3565b8a60405160200161170694939291906150e8565b84549091505f9061260690600160601b90046001600160a01b03168389613663565b90508061262657604051635d29e1bd60e01b815260040160405180910390fd5b8454612645908a908790600160601b90046001600160a01b03166135e3565b9998505050505050505050565b6001546001600160a01b0316331461174b57604051630581a50f60e41b8152683737ba1030b236b4b760b91b6004820152336024820152604401610d30565b7f0000000000000000000000000000000000000000000000000000000000000000815f01516001600160601b031611806126ea575060208101516001600160a01b03161580156126ea575080516001600160601b031615155b1561270b57604051600162505a9360e01b0319815260040160405180910390fd5b600e548151602080840151604080516001600160601b038087168252909416928401929092526001600160a01b03600160601b90940484168383015292909216606082015290517f2b9fb16353eaf3b33538b5a2635d05cee2876d96ddd81b0d2ea647b44594a11b9181900360800190a180516020909101516001600160a01b0316600160601b026001600160601b0390911617600e55565b81546001600160a01b03828116600160601b90920416146127f557604051630581a50f60e41b8152683737ba1037bbb732b960b91b60048201526001600160a01b0382166024820152604401610d30565b5050565b604051632b2c27d760e01b815260040160405180910390fd5b600f5460ff161561174b5760405163d93c066560e01b815260040160405180910390fd5b60026010540361285957604051633ee5aeb560e01b815260040160405180910390fd5b6002601055565b6001600160a01b038116610a5b57604051630426574760e11b815260040160405180910390fd5b5f6128906124cb565b90505f600a54826128a19190614ff4565b90505f811180156128b35750600b5415155b1561290e576004545f036128dd578060095f8282546128d29190614f9c565b9091555061290e9050565b60045481600b546128ee9190615090565b6128f89190614ed4565b600c5f8282546129089190614f9c565b90915550505b50600a55565b61291d81613624565b6004820155600c54600390910155565b80156127f5575f61294c6a0c097ce7bc90715b34b9f160241b83615090565b905080836004015461295e9190614ff4565b6004840155601954612971908390614f9c565b601955505050565b5f836129858484614f9c565b61208b9190614ff4565b5f6001600160601b038211156129c2576040516306dfcc6560e41b81526060600482015260248101839052604401610d30565b5090565b604051636eb1769f60e11b81525f906001600160a01b0385169063dd62ed3e906129f69030908790600401614e75565b602060405180830381865afa158015612a11573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612a359190614f61565b9050612a4b8484612a468585614f9c565b6136d3565b50505050565b7faeea2b5ee5fb4e4473cc85584d77137f4c6f996691f89746291ce5542512001560025482604051612a84929190614ef3565b60405180910390a1600255565b610a8083846001600160a01b031663a9059cbb8585604051602401612ab7929190615103565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505061375f565b5f306001600160a01b037f000000000000000000000000d085448ebf79099b33103352cb4002d3b153ad0216148015612b4157507f000000000000000000000000000000000000000000000000000000000000000146145b15612b6b57507fc923e2eda0d7b1a68158845ed8aec552afe98dc4a1251204fb8ea50da33bd12290565b61166b604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527fd94178f8968e8a660ba5659d82fb6e9a50199d8b913a89390e524f22089f6732918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b5f5f7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b03166370a08231306040518263ffffffff1660e01b8152600401612c60919061482d565b602060405180830381865afa158015612c7b573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612c9f9190614f61565b90505f601954601854612cb29190614ff4565b90507f0000000000000000000000007dd9c5cba05e151c895fde1cf355c9a1d5da64296001600160a01b03167f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031603612d2d578381600354612d1c9190614f9c565b612d269190614f9c565b9250612d3a565b612d378482614f9c565b92505b82821015612d5f5781836040516365ac9d4360e01b8152600401610d30929190614ef3565b5050919050565b335f908152600d602052604090205460ff16612dac57604051630581a50f60e41b81526b3737ba103737ba34b334b2b960a11b6004820152336024820152604401610d30565b612db4612179565b600c556009544210612df6576014546001600160801b0316612de46a0c097ce7bc90715b34b9f160241b84615090565b612dee9190614ed4565b600b55612e52565b5f42600954612e059190614ff4565b600b54612e129190615090565b6014549091506001600160801b0316612e396a0c097ce7bc90715b34b9f160241b85615090565b612e439083614f9c565b612e4d9190614ed4565b600b55505b601454612e68906001600160801b031642614f9c565b60095542600a55600b546a0c097ce7bc90715b34b9f160241b1115612ea057604051631a9bcffd60e31b815260040160405180910390fd5b5f601954601854612eb19190614ff4565b90505f612ebe8483614f9c565b90508360185f828254612ed19190614f9c565b9091555050604080518581523360208201527f48f411442545d43fc993afadc0fd408cf0d1c606f3d10d1de7b05c994463ed97910160405180910390a16040805160c08082018352868252602080830186905282840185905260608084018890526014546001600160801b0316608080860182905260095460a0968701819052601a8c9055601b8a9055601c899055601d8b9055601e839055601f81905587518c81529485018a905296840188905291830189905290820152918201929092527fd0587546e17e5ccacd913705ae1e04dc4fb5f0ba61c1fe539fd10232aa445d41910160405180910390a150505050565b80421115610a5b5760405163762ef75f60e01b815260040160405180910390fd5b6001600160a01b03165f90815260136020526040902080546001810190915590565b5f612173613011612ae9565b8360405161190160f01b8152600281019290925260228201526042902090565b5f61303d848484613663565b905080612a4b57604051635d29e1bd60e01b815260040160405180910390fd5b5f613066612812565b61306e612836565b5f841161308e5760405163da7e9f7f60e01b815260040160405180910390fd5b613097856131d1565b6130a3858585856137c2565b90506130ae8161337e565b61208b6001601055565b600f5460ff1661174b57604051638dfc202b60e01b815260040160405180910390fd5b6130e36130b8565b600f805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b60405161311d919061482d565b60405180910390a1565b61312f612836565b5f811161314f5760405163da7e9f7f60e01b815260040160405180910390fd5b61315a838383613a85565b6131638261337e565b610a806001601055565b61317681612860565b6005546040516001600160a01b038084169216907fb7c75e18483153b6b8c9c9a379610df50999c601b86f112f162ea62bc8678724905f90a3600580546001600160a01b0319166001600160a01b0392909216919091179055565b6001601754600160a01b900460ff1660028111156131f1576131f1614d0c565b0361328457601554604051630bb7c8fd60e31b81526001600160a01b0390911690635dbe47e89061322690849060040161482d565b602060405180830381865afa158015613241573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906132659190614e3f565b610a5b5780604051636fb518f560e01b8152600401610d30919061482d565b6002601754600160a01b900460ff1660028111156132a4576132a4614d0c565b03610a5b57601754604051630bb7c8fd60e31b81526001600160a01b0390911690635dbe47e8906132d990849060040161482d565b602060405180830381865afa1580156132f4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906133189190614e3f565b15610a5b57806040516339c1aba760e21b8152600401610d30919061482d565b306001600160a01b0384160361337357610a807f0000000000000000000000007dd9c5cba05e151c895fde1cf355c9a1d5da64298383612a91565b610a80838383613cc5565b5f8181526008602052604090206014548154600160801b9091046001600160801b03166001600160601b039091161080156133c2575080546001600160601b031615155b156127f557601454815460405163667d9f8d60e01b8152600160801b9092046001600160801b031660048301526001600160601b03166024820152604401610d30565b61340d612812565b613415612836565b5f81116134355760405163da7e9f7f60e01b815260040160405180910390fd5b825461345090600160601b90046001600160a01b03166131d1565b61315a838383613cf1565b61346481612860565b6001546040516001600160a01b038084169216907fbf265e8326285a2747e33e54d5945f7111f2b5edb826eb8c08d4677779b3ff97905f90a3600180546001600160a01b0319166001600160a01b0392909216919091179055565b6134c7612812565b600f805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586131103390565b606061166b7f00000000000000000000000000000000000000000000000000000000000000ff6011613f27565b606061166b7f31000000000000000000000000000000000000000000000000000000000000016012613f27565b61355e612812565b613566612836565b613163838383613fd0565b60605f5f846001600160a01b03168460405161358d919061511c565b5f60405180830381855af49150503d805f81146135c5576040519150601f19603f3d011682016040523d82523d5f602084013e6135ca565b606091505b50915091506135da858383614184565b95945050505050565b5f6135ec612812565b6135f4612836565b5f6136008585856141d7565b90508060195f8282546136139190614f9c565b909155505060016010559050611db8565b5f8160030154613632612179565b61363c9190614ff4565b600183015461365491906001600160601b0316615090565b82600401546121739190614f9c565b5f836001600160a01b03163b5f036136c1575f5f613681858561445e565b5090925090505f81600381111561369a5761369a614d0c565b1480156136b85750856001600160a01b0316826001600160a01b0316145b92505050611db8565b6136cc8484846144a7565b9050611db8565b5f836001600160a01b031663095ea7b384846040516024016136f6929190615103565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050905061372f848261457e565b612a4b5761375984856001600160a01b031663095ea7b3865f604051602401612ab7929190615103565b612a4b84825b5f5f60205f8451602086015f885af18061377e576040513d5f823e3d81fd5b50505f513d915081156137955780600114156137a2565b6001600160a01b0384163b155b15612a4b5783604051635274afe760e01b8152600401610d30919061482d565b5f6137cc83612860565b6137d582612860565b6137dd612887565b306137e66145c3565b60055460405162834efd60e71b81529193505f916001600160a01b03909116906341a77e809061381e9089908b908a90600401614faf565b602060405180830381865afa158015613839573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061385d9190614f61565b90508560035f8282546138709190614f9c565b925050819055508060045f8282546138889190614f9c565b90915550506001600160a01b0387165f90815260066020526040812080548892906138b4908490614f9c565b90915550506001600160a01b0387165f90815260076020526040812080548392906138e0908490614f9c565b925050819055506040518060e001604052806138fb8861298f565b6001600160601b03168152602001886001600160a01b031681526020016139218361298f565b6001600160601b0390811682526001600160a01b03888116602080850191909152888216604080860191909152600c546060808701919091525f60809687018190528a815260088452829020875193880151938616600160601b948616850217815591870151908701519416938316909102929092176001830155918301516002820180546001600160a01b031916919093161790915560a0820151600382015560c0909101516004909101556139d9878388613338565b825f51602061523c5f395f51905f52888889856040516139fc9493929190614fce565b60405180910390a2836001600160a01b03165f6001600160a01b0316845f51602061525c5f395f51905f5284604051613a3791815260200190565b60405180910390a4827fe03d018e6f94071d97d5befc6a430dedbb1792ef805cf2042edbc2bb75b445285f8784604051613a7393929190615127565b60405180910390a25050949350505050565b613a8d612887565b613a9683612914565b82545f90613aae9083906001600160601b0316614ff4565b6005548554600187015460405162834efd60e71b81529394505f936001600160a01b03938416936341a77e8093613afd938893600160601b928390048416939290910490911690600401614faf565b602060405180830381865afa158015613b18573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613b3c9190614f61565b90508260035f828254613b4f9190614ff4565b90915550506001850154600454613b71916001600160601b0316908390612979565b6004558454600160601b90046001600160a01b03165f9081526006602052604081208054859290613ba3908490614ff4565b909155505060018501548554600160601b90046001600160a01b03165f90815260076020526040902054613be2916001600160601b0316908390612979565b8554600160601b90046001600160a01b03165f90815260076020526040902055613c0b8261298f565b85546001600160601b0319166001600160601b0391909116178555613c2f8161298f565b6001860180546001600160601b0319166001600160601b0392909216919091179055613c6d308654600160601b90046001600160a01b031685613338565b845460405185917f5d89f707c2087d1f0c2ec0ef87eb3bbf3c0eac23c0028df006c7de091258544d91613cb691600160601b90046001600160a01b031690879087908790614fce565b60405180910390a25050505050565b610a807f0000000000000000000000007dd9c5cba05e151c895fde1cf355c9a1d5da64298484846145d5565b613cf9612887565b613d0283612914565b825430905f90613d1c9084906001600160601b0316614f9c565b6005548654600188015460405162834efd60e71b81529394505f936001600160a01b03938416936341a77e8093613d6b938893600160601b928390048416939290910490911690600401614faf565b602060405180830381865afa158015613d86573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613daa9190614f61565b6001870154600454919250613dcc916001600160601b03909116908390612979565b6004819055508360035f828254613de39190614f9c565b90915550508554600160601b90046001600160a01b03165f9081526006602052604081208054869290613e17908490614f9c565b909155505060018601548654600160601b90046001600160a01b03165f90815260076020526040902054613e56916001600160601b0316908390612979565b8654600160601b90046001600160a01b03165f90815260076020526040902055613e7f8161298f565b6001870180546001600160601b0319166001600160601b0392909216919091179055613eaa8261298f565b86546001600160601b0319166001600160601b039190911617808755613ee190600160601b90046001600160a01b03168486613338565b855460405186915f51602061523c5f395f51905f5291613f1791600160601b90046001600160a01b031690889087908790614fce565b60405180910390a2505050505050565b606060ff8314613f4157613f3a836145fd565b9050612173565b818054613f4d9061514b565b80601f0160208091040260200160405190810160405280929190818152602001828054613f799061514b565b8015613fc45780601f10613f9b57610100808354040283529160200191613fc4565b820191905f5260205f20905b815481529060010190602001808311613fa757829003601f168201915b50505050509050612173565b613fd981612860565b613fe1612887565b613fea83612914565b6005548354600185015460405162834efd60e71b81525f936001600160a01b03908116936341a77e809361403e936001600160601b03831693600160601b9384900481169390920490911690600401614f37565b602060405180830381865afa158015614059573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061407d9190614f61565b600185015460045491925061409f916001600160601b03909116908390612979565b60045560018401548454600160601b90046001600160a01b03165f908152600760205260409020546140dc916001600160601b0316908390612979565b8454600160601b90046001600160a01b03165f908152600760205260409020556141058161298f565b6001850180546001600160601b0319166001600160601b039290921691909117905560028401546040518281526001600160a01b0384811692169085905f51602061525c5f395f51905f529060200160405180910390a45060029290920180546001600160a01b0319166001600160a01b039093169290921790915550565b606082614199576141948261463a565b611db8565b81511580156141b057506001600160a01b0384163b155b156141d05783604051639996b31560e01b8152600401610d30919061482d565b5080611db8565b5f6141e0612887565b6141e983612914565b5f6a0c097ce7bc90715b34b9f160241b84600401546142089190614ed4565b600e549091505f90614223906001600160601b031683614ff4565b9050805f03614236575f92505050611db8565b61424e6a0c097ce7bc90715b34b9f160241b83615090565b856004015461425d9190614ff4565b6004808701919091556005548654600188015460405162834efd60e71b81525f946001600160a01b03948516946341a77e80946142b9946001600160601b03821694600160601b92839004841694929091049092169101614f37565b602060405180830381865afa1580156142d4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906142f89190614f61565b9050846001600160a01b0316875f51602061519c5f395f51905f528484604051614323929190614ef3565b60405180910390a36001860154600454614348916001600160601b0316908390612979565b60045560018601548654600160601b90046001600160a01b03165f90815260076020526040902054614385916001600160601b0316908390612979565b8654600160601b90046001600160a01b03165f908152600760205260409020556143ae8161298f565b6001870180546001600160601b0319166001600160601b03929092169190911790556143fb7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28684612a91565b600e546001600160601b03161561445457600e54614454907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2906001600160a01b03600160601b820416906001600160601b0316612a91565b5095945050505050565b5f5f5f8351604103614495576020840151604085015160608601515f1a61448788828585614663565b9550955095505050506144a0565b505081515f91506002905b9250925092565b5f5f5f856001600160a01b031685856040516024016144c7929190615183565b60408051601f198184030181529181526020820180516001600160e01b0316630b135d3f60e11b179052516144fc919061511c565b5f60405180830381855afa9150503d805f8114614534576040519150601f19603f3d011682016040523d82523d5f602084013e614539565b606091505b509150915081801561454d57506020815110155b801561457457508051630b135d3f60e11b906145729083016020908101908401614f61565b145b9695505050505050565b5f5f5f5f60205f8651602088015f8a5af192503d91505f519050828015614574575081156145af5780600114614574565b50505050506001600160a01b03163b151590565b5f546145d0816001614f9c565b5f5590565b612a4b84856001600160a01b03166323b872dd868686604051602401612ab793929190615127565b60605f61460983614721565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b80511561464a5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f80806fa2a8918ca85bafe22016d0b997e4df60600160ff1b0384111561469257505f91506003905082614717565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa1580156146e3573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b03811661470e57505f925060019150829050614717565b92505f91508190505b9450945094915050565b5f60ff8216601f81111561217357604051632cd44ac360e21b815260040160405180910390fd5b634e487b7160e01b5f52604160045260245ffd5b6001600160a01b0381168114610a5b575f5ffd5b5f6040828403128015614781575f5ffd5b50604080519081016001600160401b03811182821017156147a4576147a4614748565b60405282356001600160601b03811681146147bd575f5ffd5b815260208301356147cd8161475c565b60208201529392505050565b5f5f604083850312156147ea575f5ffd5b8235915060208301356147fc8161475c565b809150509250929050565b5f60208284031215614817575f5ffd5b81356001600160801b0381168114611db8575f5ffd5b6001600160a01b0391909116815260200190565b8015158114610a5b575f5ffd5b5f5f6040838503121561485f575f5ffd5b823561486a8161475c565b915060208301356147fc81614841565b5f6020828403121561488a575f5ffd5b8135611db88161475c565b803560ff81168114611d73575f5ffd5b5f5f5f5f5f5f5f60e0888a0312156148bb575f5ffd5b8735965060208801356148cd8161475c565b955060408801359450606088013593506148e960808901614895565b9699959850939692959460a0840135945060c09093013592915050565b5f60208284031215614916575f5ffd5b5035919050565b5f5f5f6060848603121561492f575f5ffd5b8335925060208401356149418161475c565b929592945050506040919091013590565b5f82601f830112614961575f5ffd5b81356001600160401b0381111561497a5761497a614748565b604051601f8201601f19908116603f011681016001600160401b03811182821017156149a8576149a8614748565b6040528181528382016020018510156149bf575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f5f5f5f5f60c087890312156149f0575f5ffd5b863595506020870135614a028161475c565b94506040870135614a128161475c565b93506060870135614a228161475c565b92506080870135915060a08701356001600160401b03811115614a43575f5ffd5b614a4f89828a01614952565b9150509295509295509295565b5f5f60408385031215614a6d575f5ffd5b50508035926020909101359150565b5f5f5f5f5f60a08688031215614a90575f5ffd5b853594506020860135614aa28161475c565b93506040860135614ab28161475c565b92506060860135915060808601356001600160401b03811115614ad3575f5ffd5b614adf88828901614952565b9150509295509295909350565b5f60208284031215614afc575f5ffd5b813560038110611db8575f5ffd5b5f5f5f5f5f60a08688031215614b1e575f5ffd5b85359450602086013593506040860135614ab28161475c565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b60ff60f81b8816815260e060208201525f614b8360e0830189614b37565b8281036040840152614b958189614b37565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b81811015614bea578351835260209384019390920191600101614bcc565b50909b9a5050505050505050505050565b5f5f5f60608486031215614c0d575f5ffd5b833592506020840135614c1f8161475c565b91506040840135614c2f8161475c565b809150509250925092565b5f5f60208385031215614c4b575f5ffd5b82356001600160401b03811115614c60575f5ffd5b8301601f81018513614c70575f5ffd5b80356001600160401b03811115614c85575f5ffd5b8560208260051b8401011115614c99575f5ffd5b6020919091019590945092505050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b82811015614d0057603f19878603018452614ceb858351614b37565b94506020938401939190910190600101614ccf565b50929695505050505050565b634e487b7160e01b5f52602160045260245ffd5b6020810160038310614d4057634e487b7160e01b5f52602160045260245ffd5b91905290565b5f5f5f5f5f5f5f60e0888a031215614d5c575f5ffd5b873596506020880135614d6e8161475c565b95506040880135614d7e8161475c565b9450606088013593506148e960808901614895565b5f5f5f5f5f5f60c08789031215614da8575f5ffd5b863595506020870135945060408701359350614dc660608801614895565b9598949750929560808101359460a0909101359350915050565b5f5f5f60608486031215614df2575f5ffd5b833592506020840135915060408401356001600160401b03811115614e15575f5ffd5b614e2186828701614952565b9150509250925092565b6001600160801b0391909116815260200190565b5f60208284031215614e4f575f5ffd5b8151611db881614841565b5f60208284031215614e6a575f5ffd5b8151611db88161475c565b6001600160a01b0392831681529116602082015260400190565b733737ba1031b630b4b6b2b91037b91037bbb732b960611b81526001600160a01b0391909116602082015260400190565b634e487b7160e01b5f52601160045260245ffd5b5f82614eee57634e487b7160e01b5f52601260045260245ffd5b500490565b918252602082015260400190565b6001600160a01b039690961686526020860194909452604085019290925260ff166060840152608083015260a082015260c00190565b6001600160601b039390931683526001600160a01b03918216602084015216604082015260600190565b5f60208284031215614f71575f5ffd5b5051919050565b5f5f60408385031215614f89575f5ffd5b825160208401519092506147fc81614841565b8082018082111561217357612173614ec0565b9283526001600160a01b03918216602084015216604082015260600190565b6001600160a01b0394909416845260208401929092526040830152606082015260800190565b8181038181111561217357612173614ec0565b634e487b7160e01b5f52603260045260245ffd5b5f5f8335601e19843603018112615030575f5ffd5b8301803591506001600160401b03821115615049575f5ffd5b60200191503681900382131561505d575f5ffd5b9250929050565b5f81518060208401855e5f93019283525090919050565b828482375f8382015f81526145748185615064565b808202811582820484141761217357612173614ec0565b6001600160a01b0397881681529590961660208601526040850193909352606084019190915260ff16608083015260a082015260c081019190915260e00190565b93845260208401929092526040830152606082015260800190565b6001600160a01b03929092168252602082015260400190565b5f611db88284615064565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600181811c9082168061515f57607f821691505b60208210810361517d57634e487b7160e01b5f52602260045260245ffd5b50919050565b828152604060208201525f61208b6040830184614b3756fefc6ecd966b430510beae9124efe7dd517ed5286d7ed0ae258f8556c7a9765a444aa66922cb54c64ed3272a327b8339c58555b41169c1fb39efb4535c6f9d1703867b6c541e0f40dd4e47d08157ce464787a92cf2aa13644ecf51331804fc0847ed72e8b8797645ac76e0741da7d8d0afc24dddb21ca6338d5c5520b05b192306613950e00b9e6a31ffbad41bdf66f194d1a276b1e117790800eaca48ebdd1d551a325385f16807e99fb688b597db78b00faee313dcf02e882dd16daab6fc3e1f76ecb53d27210f298c33177d8fc8947b65cc94a2d54f65112683d2369058af90416c3c1f040daee5e4cca619ec40763a6c4a06ec7458fffe7cf841c3e4da4a65300c1a75d358323d4ec7b212cd1e88afbd0250186b09c957b383ee5e6ad897f50b1db0ab4204ded87d69d4fe6739295ee2fef3e78103e2099d8e18e112053b20a2646970667358221220089a1015cad0ef5b3a15394f10b5e96969ef89caab87e2e4f7f349c4ff4275f264736f6c63430008210033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007dd9c5cba05e151c895fde1cf355c9a1d5da642900000000000000000000000043b92510e301aa4f23a59f557476322160a22d1c00000000000000000000000000000000000000000000000000071afd498d0000000000000000000000000000ed0044feb17407c989c5703767f3a8de3f9dbd3f0000000000000000000000000000000000000000000000000000000000278d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf8acc1d7e0f460483da3fa73d7c57139a19a74
-----Decoded View---------------
Arg [0] : _rewardsToken (address): 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Arg [1] : _stakeToken (address): 0x7DD9c5Cba05E151C895FDe1CF355C9A1D5DA6429
Arg [2] : _earningPowerCalculator (address): 0x43b92510e301aA4F23a59F557476322160A22d1c
Arg [3] : _maxBumpTip (uint256): 2000000000000000
Arg [4] : _admin (address): 0xeD0044FEB17407C989C5703767F3A8DE3f9DbD3f
Arg [5] : _rewardDuration (uint128): 2592000
Arg [6] : _minimumStakeAmount (uint128): 0
Arg [7] : _stakerAllowset (address): 0x0000000000000000000000000000000000000000
Arg [8] : _stakerBlockset (address): 0x0000000000000000000000000000000000000000
Arg [9] : _stakerAccessMode (uint8): 0
Arg [10] : _allocationMechanismAllowset (address): 0x0cf8Acc1d7E0F460483dA3FA73d7C57139a19a74
-----Encoded View---------------
11 Constructor Arguments found :
Arg [0] : 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
Arg [1] : 0000000000000000000000007dd9c5cba05e151c895fde1cf355c9a1d5da6429
Arg [2] : 00000000000000000000000043b92510e301aa4f23a59f557476322160a22d1c
Arg [3] : 00000000000000000000000000000000000000000000000000071afd498d0000
Arg [4] : 000000000000000000000000ed0044feb17407c989c5703767f3a8de3f9dbd3f
Arg [5] : 0000000000000000000000000000000000000000000000000000000000278d00
Arg [6] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [7] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [8] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [9] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [10] : 0000000000000000000000000cf8acc1d7e0f460483da3fa73d7c57139a19a74
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 33 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ 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.