skills-ws
Version:
87 agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.
239 lines (191 loc) • 6.27 kB
Markdown
- 2. Vulnerability Checklist
- 2.1 Reentrancy
- 2.2 Oracle Manipulation / Price Feed Attacks
- 2.3 Flash Loan Attack Vectors
- 2.4 Storage Collisions in Proxies
- 2.5 Front-Running / Sandwich Attacks / MEV
- 2.6 Access Control Issues
- 2.7 Integer Overflow/Underflow
- 2.8 Unchecked External Calls
- 2.9 Denial of Service Patterns
**Vulnerable:**
```solidity
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok);
balances[msg.sender] -= amount; // STATE AFTER CALL — reentrancy
}
```
**Fixed (CEI Pattern):**
```solidity
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // EFFECTS first
(bool ok, ) = msg.sender.call{value: amount}(""); // INTERACTION last
require(ok);
}
```
Cross-function reentrancy: check if any two functions share state and one has an external call before state update.
**Vulnerable (spot price):**
```solidity
function getPrice() public view returns (uint256) {
(uint112 r0, uint112 r1, ) = pair.getReserves();
return (uint256(r1) * 1e18) / uint256(r0); // manipulable in same tx
}
```
**Fixed (Chainlink + staleness check):**
```solidity
function getPrice() public view returns (uint256) {
(, int256 answer, , uint256 updatedAt, ) = priceFeed.latestRoundData();
require(answer > 0, "invalid price");
require(block.timestamp - updatedAt < 3600, "stale price");
return uint256(answer);
}
```
Also consider TWAP for on-chain pricing:
```solidity
// Uniswap V3 TWAP — use OracleLibrary.consult(pool, twapInterval)
```
Audit checks:
- Can any single-tx deposit + action + withdraw exploit state?
- Are governance votes protected by minimum holding periods?
- Are liquidity-based calculations snapshottable in one block?
**Guard pattern:**
```solidity
mapping(address => uint256) public lastDepositBlock;
function deposit() external {
lastDepositBlock[msg.sender] = block.number;
// ...
}
function vote() external {
require(block.number > lastDepositBlock[msg.sender], "same block");
// ...
}
```
**Problem:** Proxy and implementation share storage. Misaligned slots corrupt data.
```solidity
// Implementation V1
contract V1 {
uint256 public value; // slot 0
address public owner; // slot 1
}
// Implementation V2 — WRONG: inserted variable shifts slots
contract V2 {
uint256 public value; // slot 0
uint256 public newVar; // slot 1 — COLLISION with owner!
address public owner; // slot 2
}
// Implementation V2 — CORRECT: append only
contract V2 {
uint256 public value; // slot 0
address public owner; // slot 1
uint256 public newVar; // slot 2 — safe, appended
}
```
Use `forge inspect ContractName storage-layout` to verify slot alignment between versions.
**Vulnerable swap:**
```solidity
function swap(uint256 amountIn) external {
router.swapExactTokensForTokens(amountIn, 0, path, msg.sender, block.timestamp);
// amountOutMin = 0 allows sandwich
}
```
**Fixed:**
```solidity
function swap(uint256 amountIn, uint256 minOut, uint256 deadline) external {
require(block.timestamp <= deadline, "expired");
router.swapExactTokensForTokens(amountIn, minOut, path, msg.sender, deadline);
}
```
For sensitive operations, use commit-reveal:
```solidity
mapping(bytes32 => uint256) public commits;
function commit(bytes32 hash) external { commits[hash] = block.number; }
function reveal(uint256 value, bytes32 salt) external {
bytes32 h = keccak256(abi.encodePacked(value, salt, msg.sender));
require(commits[h] > 0 && block.number > commits[h] + 1, "too early");
delete commits[h];
_execute(value);
}
```
**Vulnerable (tx.origin):**
```solidity
function withdraw() external {
require(tx.origin == owner); // phishing attack via malicious contract
}
```
**Fixed:**
```solidity
function withdraw() external {
require(msg.sender == owner); // or use OpenZeppelin Ownable/AccessControl
}
```
Check for:
- Missing access modifiers on admin functions
- Single-step ownership transfer (use Ownable2Step)
- DEFAULT_ADMIN_ROLE granted too broadly
- Functions that should be `onlyOwner` but are `public`
### 2.7 Integer Overflow/Underflow
**Pre-0.8.0 (vulnerable):**
```solidity
// Solidity <0.8.0
uint8 balance = 255;
balance += 1; // wraps to 0 silently
// Fix: use SafeMath
balance = balance.add(1); // reverts on overflow
```
**Post-0.8.0:** Built-in overflow checks. But `unchecked {}` blocks bypass them:
```solidity
unchecked {
uint8 x = 255;
x += 1; // wraps to 0 — intentional? Audit this.
}
```
Audit every `unchecked` block. Verify the math genuinely cannot overflow.
**Vulnerable:**
```solidity
payable(to).send(amount); // return value ignored — funds may not arrive
token.transfer(to, amount); // non-standard tokens may return false
```
**Fixed:**
```solidity
(bool ok, ) = payable(to).call{value: amount}("");
require(ok, "ETH transfer failed");
// For ERC20:
SafeERC20.safeTransfer(token, to, amount);
```
Also check: `delegatecall` return values, low-level `call` without length check.
**Unbounded loop (gas griefing):**
```solidity
// VULNERABLE: attacker adds thousands of entries
function distributeRewards() external {
for (uint i = 0; i < recipients.length; i++) {
token.transfer(recipients[i], rewards[i]); // OOG if array is huge
}
}
```
**Fixed (pull pattern):**
```solidity
mapping(address => uint256) public pendingRewards;
function claimReward() external {
uint256 amount = pendingRewards[msg.sender];
pendingRewards[msg.sender] = 0;
token.safeTransfer(msg.sender, amount);
}
```
Other DoS vectors:
- External call in loop (one revert blocks all)
- Block gas limit reached via large array iteration
- Griefing via forced revert in `receive()` / `fallback()`
---