Audit LIQ Staking Distributor
📄 Official Security Audit (Completed & Actioned)
Project: LIQStakingDistributor Date: September 2025 Network: Base Auditor: Independent Security Review
Executive Summary
Overall Risk: Low (after fixes)
Status: ✅ All identified issues addressed and incorporated into the contract above.
The distributor implements a standard, robust per‑share accumulator for multi‑token rewards (ERC‑20 + ETH) and introduces appropriate operational controls: pausing, emergency withdrawal gating, and FOT‑safe funding.
What Changed (Issues Fixed & Actioned)
M‑1
emergencyWithdraw bypassed lock without a circuit breaker
Medium
Fixed: added Pausable; emergencyWithdraw now requires whenPaused; clears stale unlockTime on full exit.
M‑2
Funding used nominal amount, not received (FOT)
Medium
Fixed: ERC‑20 reward notify now uses balance‑delta (received = after - before) and requires received > 0.
L‑1
No global pause for incidents
Low
Fixed: Integrated Pausable; applied whenNotPaused to stake/unstake/claim/notify.
L‑2
Stale unlockTime after full exit
Low
Fixed: Clear unlockTime when balance becomes 0 (exit, unstake‑to‑zero, emergency).
L‑3
Reward token list unbounded (gas)
Low
Mitigated: Added retireRewardToken with a queued==0 guard; enumeration remains curated by ops.
L‑4
Redundant debt write in claimReward
Info
Fixed: rely on _harvestOne debt assignment; removed extra write.
L‑5
No rescue path for dust
Low
Fixed: recoverERC20 with LIQ exclusion; ops policy required for usage.
Design Overview
Stake LIQ with a 7‑day lock; lock extends on additional staking (never shortens).
Rewards: ETH (
address(0)) and ERC‑20s; distributed pro‑rata viaaccRewardPerShare.Queued rewards: If no stakers, added to
queuedRewards[token]; once any stake exists, flush into the accumulator.Claims: All tokens (
claimRewards) or a single token (claimReward(token)); ETH via.call{value:…}with state updated before transfer.
Access Control
Owner:
pause/unpause, set reward notifiers, retire tokens, rescue ERC‑20 (not LIQ).Reward Notifiers: Vault/Treasury/Owner (and any others the owner authorizes).
Users: Stake / Unstake (subject to lock) / Claim / Emergency withdraw (only when paused).
Safety & Invariants
Reentrancy: All state‑mutating externals are
nonReentrant; state changes precede ETH/token transfers.Accounting:
Math.mulDivprevents overflow / precision loss; total user claims ≤ funded (minor rounding dust possible).FOT‑safe: Notify uses balance‑delta.
Operational: Global pause halts stake/unstake/claim/notify; emergencyWithdraw only during pause.
Test Checklist (passed assumptions)
Funding paths for ETH & ERC‑20 (incl. fee‑on‑transfer) distribute expected totals.
Lock enforcement; lock extension on top‑ups; unlock post 7 days; stale locks cleared at zero balance.
Accumulator math: Σ user claims ≤ Σ funded per token; rounding dust remains in contract.
Pause behavior: Stake/Unstake/Claim/Notify blocked;
emergencyWithdrawallowed; unpause resumes operations.Reentrancy: Malicious token/ETH claim reentrancy blocked by guards & order of operations.
📚 Smart Contracts Overview (Updated)
Below is an expanded & up‑to‑date view of all core components and their roles in your Base + Aerodrome deployment.
Core Protocol Contracts
PermalockVault_V5.sol (Vault)
Purpose: Custodies and manages veAERO NFTs; mints iAERO & LIQ on deposits; gates all veAERO actions.
Key Functions:
deposit(): lock AERO into the primary veNFT; mints iAERO (user+treasury) & LIQ (user+treasury share).depositVeNFT(): onboards an external veAERO NFT under management; may merge into primary.executeNFTAction(): strict selector allowlist forincreaseAmount,increaseUnlockTime,merge(transfers blocked).performMaintenance(): merges additional NFTs and rebases (extends) primary to max where needed.Sweeps:
sweepERC20/ETH(Owner orrewardsCollector); AERO sweep restricted to collector only.Break‑glass: time‑locked managed rescue to
rescueSafeunderpaused + emergencyPause.
Permissions:
Owner: protocol multisig; authorizes
rewardsCollector(Harvester) &votingManager; setsauthorizedTarget(Aerodrome Voter).Authorized: can call
executeNFTActionto the Voter.Keeper: can run
performMaintenance().
iAEROToken.sol
Purpose: Liquid receipt token for deposited AERO.
Features: ERC‑20 + Permit; minted by Vault 1:1 (minus protocol fee share to treasury).
LIQToken.sol
Purpose: Governance/incentive token with emission halving in the Vault.
Features: Max supply (e.g., 100M); ERC‑20 + Permit; minted by Vault; holders may burn.
Staking & Rewards
EpochStakingDistributor.sol
Purpose: Epoch‑based rewards for iAERO stakers.
Mechanics: Rewards bucketed by
(token, epoch); users can claim N tokens for N epochs (UI can source epoch/token lists via RewardsSugar or equivalent).stiAERO: Receipt token is minted 1:1 on stake and burned on unstake (used as potential collateral externally).
Security: Pausable, balance‑delta for FOT tokens, hardened setter for
stiAERO, optional freeze of receipt pointer.
StiAERO.sol
Purpose: ERC‑20 receipt for iAERO stakers.
Roles:
MINTER_ROLE/BURNER_ROLEgranted to EpochStakingDistributor; admin on multisig.
LIQStakingDistributor.sol (this contract)
Purpose: Rewards LIQ stakers (e.g., TreasuryDistributor flow = 80% of the 10% protocol share).
Mechanics: Per‑share accumulator; ETH + multi‑token support; 7‑day lock; global pause; emergencyWithdraw only when paused; FOT‑safe notify; optional retire of tokens.
Reward Collection & Splits
RewardsHarvesterV2.sol
Purpose: Claims Aerodrome bribes/fees/emissions for the Vault’s veNFT and routes them by policy.
Split:
80% → iAERO stakers (EpochStakingDistributor)
10% → TreasuryDistributor (splits 80/20)
10% → Peg reserve (to
pegDefenderortreasury)
Safety: If Distributor notify fails, staker share falls back to Treasury/TreasuryDistributor; uses
forceApprovewith reset.
TreasuryDistributor.sol
Purpose: Splits protocol’s 10% share.
Split:
80% → LIQ stakers (LIQStakingDistributor) (8% of total)
20% → Treasury ops (2% of total)
Voting & Bribe Management
VotingManagerOptimised.sol
Purpose: Orchestrates Aerodrome voting via Vault’s veNFT, manages bribe deposits and refunds, performs oracle‑weighted allocation.
Bribes: ERC‑20 + ETH supported; per‑epoch slices with minimum USD value per epoch (via Chainlink feeds).
Refunds: If epoch not executed or pool not voted, refundable after a grace period.
Treasury Claims: Post‑vote, keeper collects bribe slices for pools that were actually voted.
External / Data Helpers (Optional)
RewardsSugar / LpSugar (readers): Off‑chain or on‑chain dataset helpers you can use in UI/backend to enumerate tokens & pools, fetch current epoch expectations, and drive claim batching UX.
System Interactions (Mermaid)
graph TD
User[User] -->|Deposit AERO| Vault[PermalockVault_V5]
Vault -->|Mint iAERO| iAERO[iAERO Token]
Vault -->|Mint LIQ| LIQ[LIQ Token]
%% iAERO staking side
User -->|Stake iAERO| ESD[EpochStakingDistributor]
ESD -->|Mint 1:1| stiAERO[StiAERO Receipt]
ESD -->|Burn on Unstake| stiAERO
%% LIQ staking side
User -->|Stake LIQ| LSD[LIQStakingDistributor]
%% Harvester routes rewards
RH[RewardsHarvesterV2] -->|80% Stakers| ESD
RH -->|10%| TD[TreasuryDistributor]
RH -->|10%| Peg[Peg Reserve / Defender]
%% TreasuryDistributor splits 10%
TD -->|80% of 10%| LSD
TD -->|20% of 10%| Treasury[Treasury Ops]
%% Voting management
VM[VotingManagerOptimised] -->|Execute Votes| Vault
Vault -->|executeNFTAction to Voter| Aerodrome[Aerodrome Voter]
VM -->|Bribe Refunds & Treasury Claims| TreasuryQuick Function Index (high‑level)
Vault
User:
deposit,depositVeNFTOps:
performMaintenanceManager/Harvester:
executeNFTAction,sweepERC20/ETHAdmin:
setRewardsCollector,setVotingManager,setAuthorizedTarget, rescue suite
EpochStakingDistributor
Funding:
notifyRewardAmount, epoch variants/batchStake:
stake,stakeFor, burn/mint stiAEROClaim:
claim,claimMany,claimLatestAdmin:
setReceiptToken,freezeReceiptToken,setAllowedFunder, pause
LIQStakingDistributor
Stake:
stake(7d lock),unstake,exit,emergencyWithdraw (paused only)Funding:
notifyRewardAmount(ETH + ERC‑20 balance‑delta)Claim:
claimRewards,claimRewardAdmin:
setRewardNotifier,retireRewardToken,recoverERC20, pause
RewardsHarvesterV2
claimAerodromeRewards(bribes/fees/emissions)processAndDistribute,processAndDistributeETH(80/10/10 split with safe fallback)
VotingManagerOptimised
Bribes:
depositBribe,depositETHBribe,refundMyBribes,claimTreasuryBribesVoting:
executeVotesAuto,executeVotesWithWeightsOracles:
setOracle,batchConfigureOraclesPools:
addPools,removePoolAdmin:
pause/unpause,emergencyWithdraw(restricted)
TreasuryDistributor
distribute(token)—routes 80/20 of protocol’s 10% share
Operational Notes
Use multisig as
owner/DEFAULT_ADMIN_ROLE.Authorize only trusted reward notifiers (Vault, Harvester, Treasury).
Run keepers for Harvester (claim/split) and VotingManager (vote execution, bribe handling).
Configure Chainlink oracles for every bribe token (incl. ETH/USD).
Prefer a small curated set of reward tokens (retire seldom‑used tokens when
queued==0).
Last updated