Skip to main content

staking

Auto-loaded by defi-auditor agent during Phase 2 when analyzing staking mechanisms. Provides patterns for: reward-per-token accumulator (Synthetix style), lock periods, multi-reward tokens, boosted staking. Critical: precision loss, first staker advantage, late staker dilution, unstake reentrancy.

跳到安装

来源信息

仓库
BitterSecurity/Vigilo
最近来源活动
2026年2月2日 05:07
检测到的 SKILL.md 语言
英语
星标
66
分支
17

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
staking
description
Auto-loaded by defi-auditor agent during Phase 2 when analyzing staking mechanisms. Provides patterns for: reward-per-token accumulator (Synthetix style), lock periods, multi-reward tokens, boosted staking. Critical: precision loss, first staker advantage, late staker dilution, unstake reentrancy.
user-invocable
false
# Staking Protocol Patterns This skill provides comprehensive knowledge for auditing DeFi staking and reward distribution mechanisms. ## Common Staking Patterns ### 1. Simple Staking (No Rewards) ```solidity contract SimpleStaking { mapping(address => uint256) public staked; function stake(uint256 amount) external { token.transferFrom(msg.sender, address(this), amount); staked[msg.sender] += amount; } function unstake(uint256 amount) external { require(staked[msg.sender] >= amount); staked[msg.sender] -= amount; token.transfer(msg.sender, amount); } } ``` ### 2. Reward Per Token Accumulator (Synthetix Style) ```solidity contract StakingRewards { uint256 public rewardPerTokenStored; mapping(address => uint256) public userRewardPerTokenPaid; mapping(address => uint256) public rewards; function rewardPerToken() public view returns (uint256) { if (totalSupply == 0) return rewardPerTokenStored; return rewardPerTokenStored + (rewardRate * (block.timestamp - lastUpdateTime) * 1e18 / totalSupply); } function earned(address account) public view returns (uint256) { return (balanceOf[account] * (rewardPerToken() - userRewardPerTokenPaid[account]) / 1e18) + rewards[account]; } modifier updateReward(address account) { rewardPerTokenStored = rewardPerToken(); lastUpdateTime = block.timestamp; if (account != address(0)) { rewards[account] = earned(account); userRewardPerTokenPaid[account] = rewardPerTokenStored; } _; } } ``` --- ## Reward Distribution Vulnerabilities ### 1. Precision Loss **Vulnerable Pattern:** ```solidity // DANGEROUS: Low precision accumulator function rewardPerToken() public view returns (uint256) { // If rewardRate * timeDelta < totalSupply, result is 0! return rewardPerTokenStored + (rewardRate * (block.timestamp - lastUpdateTime) / totalSupply); } ``` **Secure Pattern:** ```solidity // Use high precision (1e18 or 1e27) function rewardPerToken() public view returns (uint256) { return rewardPerTokenStored + (rewardRate * (block.timestamp - lastUpdateTime) * 1e18 / totalSupply); } ``` ### 2. First Staker Advantage **Vulnerable Pattern:** ```solidity // DANGEROUS: First staker gets all pending rewards function stake(uint256 amount) external { // If totalSupply was 0, rewardPerTokenStored didn't update // First staker claims all rewards since lastUpdateTime _updateReward(msg.sender); // ... } ``` **Secure Pattern:** ```solidity function stake(uint256 amount) external { _updateReward(msg.sender); // If first staker, reset lastUpdateTime if (totalSupply == 0) { lastUpdateTime = block.timestamp; } // ... } ``` ### 3. Late Staker Dilution Attack ``` 1. Attacker waits for rewards to accumulate 2. Stakes large amount just before reward distribution 3. Claims disproportionate rewards 4. Unstakes immediately Mitigation: Time-weighted rewards or lock periods ``` ### 4. Reward Calculation Overflow **Vulnerable Pattern:** ```solidity // DANGEROUS: Can overflow with large values uint256 pending = balance * rewardPerToken / 1e18; // If balance and rewardPerToken are both large... ``` **Secure Pattern:** ```solidity // Use mulDiv to avoid overflow uint256 pending = FullMath.mulDiv(balance, rewardPerToken, 1e18); ``` --- ## Lock Period Vulnerabilities ### 1. Lockup Bypass **Vulnerable Pattern:** ```solidity // DANGEROUS: Lock only checked on unstake function unstake(uint256 amount) external { require(block.timestamp >= lockEnd[msg.sender], "Locked"); // But user can transfer stake tokens! } ``` **Secure Pattern:** ```solidity // Non-transferable during lock, or track per-deposit locks function _beforeTokenTransfer(address from, address to, uint256) internal { if (from != address(0) && to != address(0)) { require(block.timestamp >= lockEnd[from], "Locked"); } } ``` ### 2. Lock Extension Manipulation ```solidity // If lock period extends on additional stake // User might be tricked into longer lock than expected // Always show clear lock end time before stake ``` --- ## Unstaking Vulnerabilities ### 1. Unstake Reentrancy **Vulnerable Pattern:** ```solidity // DANGEROUS: Reward sent before state update function unstake(uint256 amount) external { uint256 reward = earned(msg.sender); rewardToken.transfer(msg.sender, reward); // External call! balances[msg.sender] -= amount; // State update after } ``` **Secure Pattern:** ```solidity function unstake(uint256 amount) external nonReentrant { uint256 reward = earned(msg.sender); balances[msg.sender] -= amount; // State first rewards[msg.sender] = 0; rewardToken.transfer(msg.sender, reward); stakedToken.transfer(msg.sender, amount); } ``` ### 2. Dust Stuck in Contract ```solidity // Rounding can leave tiny amounts stuck // Ensure users can withdraw their full balance function unstake(uint256 amount) external { if (amount == type(uint256).max) { amount = balances[msg.sender]; } // ... } ``` --- ## Multi-Reward Token Patterns ### Multiple Reward Accumulators ```solidity contract MultiRewards { struct Reward { uint256 rewardPerTokenStored; uint256 rewardRate; uint256 lastUpdateTime; } mapping(address => Reward) public rewardData; mapping(address => mapping(address => uint256)) public userRewardPerTokenPaid; function earned(address account, address rewardToken) public view returns (uint256) { // Similar to single reward but per token } } ``` ### Vulnerability: Different Update Frequencies ```solidity // If reward tokens update at different times // Can lead to incorrect calculations // Always update ALL reward tokens together modifier updateRewards(address account) { for (uint i = 0; i < rewardTokens.length; i++) { _updateReward(rewardTokens[i], account); } _; } ``` --- ## Boosted Staking ### veToken / Gauge Patterns ```solidity // Boost based on lock duration function getBoost(address user) public view returns (uint256) { uint256 lockDuration = lockEnd[user] - block.timestamp; return 1e18 + (lockDuration * maxBoost / maxLockDuration); } function earned(address user) public view returns (uint256) { uint256 boost = getBoost(user); return baseEarned(user) * boost / 1e18; } ``` ### Boost Manipulation ```solidity // Attacker repeatedly extends lock to maintain boost // While others with same stake earn less // Mitigation: Time-averaged boost or snapshot boost ``` --- ## Staking Audit Checklist ### Reward Calculation - [ ] High precision used (1e18 minimum) - [ ] No overflow in multiplication - [ ] Division before multiplication avoided - [ ] Zero totalSupply handled ### First/Late Staker - [ ] First staker doesn't get all pending rewards - [ ] Late staker can't dilute existing stakers unfairly - [ ] Rewards accrue correctly when totalSupply changes ### Lock Periods - [ ] Lock enforced on transfers (not just unstake) - [ ] Lock extension clearly communicated - [ ] No lock bypass via stake token transfer ### Reentrancy - [ ] CEI pattern followed - [ ] nonReentrant modifier on sensitive functions - [ ] State updated before external calls ### Edge Cases - [ ] Zero stake amount handled - [ ] Full unstake (max amount) works - [ ] Dust doesn't get stuck - [ ] Reward period end handled ### Multi-Reward - [ ] All reward tokens update together - [ ] Adding/removing reward tokens safe - [ ] Different decimals handled ## Severity Classification ### Critical - Reentrancy on unstake/claim - First staker steals all rewards - Precision loss loses significant value ### High - Lock bypass possible - Late staker dilution attack - Reward overflow ### Medium - Dust stuck in contract - Boost manipulation - Reward token update desync --- ## Liquid Staking Derivatives (LSD) Patterns (2025-2026) ### Overview LSDs (stETH, rETH, cbETH, etc.) introduce unique vulnerabilities when used in DeFi protocols. ### 1. stETH Rebasing Integration **Vulnerable Pattern:** ```solidity // DANGEROUS: Caches stETH balance mapping(address => uint256) public deposits; function deposit(uint256 amount) external { stETH.transferFrom(msg.sender, address(this), amount); deposits[msg.sender] = amount; // @audit Becomes stale after rebase! } function withdraw() external { uint256 amount = deposits[msg.sender]; stETH.transfer(msg.sender, amount); // @audit May not have enough after negative rebase } ``` **Secure Pattern:** ```solidity // Use wstETH (wrapped stETH) which doesn't rebase // Or track shares instead of amounts function deposit(uint256 amount) external { uint256 sharesBefore = stETH.sharesOf(address(this)); stETH.transferFrom(msg.sender, address(this), amount); uint256 sharesReceived = stETH.sharesOf(address(this)) - sharesBefore; userShares[msg.sender] += sharesReceived; } ``` **Search Queries:** ``` Grep("stETH|wstETH|Lido|sharesOf", glob="**/*.sol") ``` ### 2. LSD Depeg Risk in Lending **Vulnerable Pattern:** ```solidity // DANGEROUS: Assumes 1:1 peg function getCollateralValue(address user) view returns (uint256) { uint256 stEthAmount = stETH.balanceOf(user); return stEthAmount * ethPrice / 1e18; // @audit Assumes stETH = ETH! } ``` **Historical Depeg Events:** - stETH depegged ~5% in June 2022 - rETH has traded at premiums/discounts - cbETH experienced volatility during market stress **Secure Pattern:** ```solidity function getCollateralValue(address user) view returns (uint256) { uint256 stEthAmount = stETH.balanceOf(user); uint256 stEthPrice = oracle.getPrice(address(stETH)); // Use actual price return stEthAmount * stEthPrice / 1e18; } ``` ### 3. Withdrawal Queue Vulnerabilities **LSDs have withdrawal delays:** | LSD | Withdrawal Time | Mechanism | |-----|-----------------|-----------| | stETH | 1-5 days | Request queue | | rETH | Variable | Burn for ETH | | cbETH | Instant* | Trade on DEX | *cbETH doesn't have native redemption, only DEX trading **Vulnerable Pattern:** ```solidity // DANGEROUS: Assumes instant withdrawal function liquidate(address user) external { uint256 stEth = collateral[user]; stETH.transfer(liquidator, stEth); // @audit Liquidator can't quickly sell! } ``` ### 4. Restaking Integration (EigenLayer) When LSDs are restaked: **Risk Chain:** ``` ETH → Lido (stETH) → EigenLayer → AVS ↓ Risk 1 ↓ Risk 2 ↓ Risk 3 Slashing Slashing Slashing ``` **Vulnerable Pattern:** ```solidity // DANGEROUS: Not accounting for restaking risk function acceptCollateral(address token) external view returns (bool) { return token == address(stETH); // @audit Is this stETH restaked? Higher risk! } ``` **Search Queries:** ``` Grep("EigenLayer|restake|AVS|operator", glob="**/*.sol") Grep("rETH|cbETH|frxETH|sfrxETH", glob="**/*.sol") ``` --- ## LSD Audit Checklist ### Rebasing Tokens (stETH) - [ ] Uses shares not amounts for accounting - [ ] Or uses wrapped version (wstETH) - [ ] Handles negative rebase scenarios ### Price/Peg - [ ] Uses oracle for LSD price (not 1:1 assumption) - [ ] Circuit breaker for extreme depeg - [ ] Liquidation accounts for depeg risk ### Withdrawals - [ ] Understands withdrawal delay - [ ] Liquidation mechanism accounts for illiquidity - [ ] Emergency scenarios handled ### Restaking - [ ] Knows if LSD is restaked - [ ] Additional slashing risk priced in - [ ] Operator risk considered --- ## Search Query Reference ``` # Find LSD usage Grep("stETH|wstETH|rETH|cbETH|frxETH|sfrxETH", glob="**/*.sol") Grep("Lido|RocketPool|Coinbase|Frax", glob="**/*.sol") # Find rebasing handling Grep("sharesOf|getSharesByPooledEth|getPooledEthByShares", glob="**/*.sol") # Find restaking Grep("EigenLayer|restake|restaking|AVS", glob="**/*.sol") ```
在 GitHub 查看