Solidity smart contract security and foundry
Skill hamzabellouch/agent-skills/Web3 and Smart Contracts/solidity-smart-contract-security-and-foundry
Comprehensive collection of 380+ production-ready Agent Skills (26 domains) conforming to the Agent Skills Standard, featuring native auto-discovery for Antigravity, Gemini CLI, Claude Code, Cursor, and Codex.
npx -y skills add hamzabellouch/agent-skills --skill solidity-smart-contract-security-and-foundryAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 21 days oldThe repository was created 21 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Advanced Solidity smart contract development, security hardening, vulnerability defense, and testing framework using Foundry (Forge, Cast, Anvil). Use when writing, auditing, testing, or securing Ethereum/EVM smart contracts.
SKILL.md
10.7 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it
Solidity Smart Contract Security & Foundry Engineering Guide
This skill guide provides production-grade standards, vulnerability defenses, security patterns, anti-patterns, and comprehensive Foundry testing strategies for EVM smart contract development.
1. Core Principles of EVM Security
- Defense in Depth: Relying on a single security layer (e.g., modifier access control) is insufficient. Use checks-effects-interactions, reentrancy guards, rate limiting, and invariant testing.
- Minimize Surface Area: Expose only required functions with the strictest visibility (
privateorinternalpreferred;externaloverpublicfor external entry points). - Explicit Invariants: Define strict properties that must hold true before, during, and after state execution (e.g.,
total_shares * price_per_share == total_assets). - Assume Unchecked External Input: External calls can execute arbitrary code, reenter state, or fail maliciously. Treat every external contract/call as untrusted.
2. Common Vulnerabilities & Security Defenses
2.1 Reentrancy & Read-Only Reentrancy
Mechanism
Occurs when an external call is executed before internal state updates are completed, allowing an attacker to call back into the contract (or a reading contract) while state is inconsistent.
Defense Strategy
- CEI Pattern: Checks-Effects-Interactions (validate inputs, modify local state, interact with external contracts).
- Transient Storage Guard (EIP-1153): High-efficiency gas reentrancy lock using
tstore/tload. - OpenZeppelin ReentrancyGuardTransient / ReentrancyGuard.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Vault is ReentrancyGuardTransient {
using SafeERC20 for IERC20;
IERC20 public immutable token;
mapping(address => uint256) private _balances;
event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
constructor(IERC20 _token) {
token = _token;
}
function deposit(uint256 amount) external nonReentrant {
require(amount > 0, "Invalid amount");
// Effects
_balances[msg.sender] += amount;
// Interactions
token.safeTransferFrom(msg.sender, address(this), amount);
emit Deposited(msg.sender, amount);
}
function withdraw(uint256 amount) external nonReentrant {
require(_balances[msg.sender] >= amount, "Insufficient balance");
// Effects - Update state BEFORE external interaction
_balances[msg.sender] -= amount;
// Interactions
token.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
}
2.2 Oracle Manipulation & Flash Loan Attacks
Mechanism
Spot prices from DEX reserves (e.g., Uniswap v2 pair reserves) can be manipulated within a single transaction using flash loans.
Defense Strategy
- Never use spot reserves (
getReserves()) for pricing collateral or liquidations. - Use Chainlink Oracles with strict freshness, positivity, and circuit-breaker checks.
- Use Time-Weighted Average Price (TWAP) with an adequate observation window if Chainlink is unavailable.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
library ChainlinkOracleLib {
error PriceStale();
error InvalidPrice();
error IncompleteRound();
uint256 private constant MAX_TIMEOUT = 3 hours;
function getLatestPrice(AggregatorV3Interface priceFeed) internal view returns (uint256) {
(
uint80 roundId,
int256 price,
,
uint256 updatedAt,
uint80 answeredInRound
) = priceFeed.latestRoundData();
if (price <= 0) revert InvalidPrice();
if (updatedAt == 0 || updatedAt < block.timestamp - MAX_TIMEOUT) revert PriceStale();
if (answeredInRound < roundId) revert IncompleteRound();
return uint256(price);
}
}
2.3 Access Control & Privilege Escalation
Anti-Pattern
Using tx.origin for authentication or failing to secure initializer functions in upgradeable contracts.
Defense Strategy
- Use
msg.senderfor authorization. - Implement fine-grained Role-Based Access Control (
AccessControlUpgradeable/AccessControlEnumerable). - Secure Initializers with
_disableInitializers()in constructors.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "@openzeppelin/contracts/access/AccessControl.sol";
contract SystemAdmin is AccessControl {
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bool public paused;
event PausedStateChanged(bool isPaused);
constructor(address defaultAdmin, address operator) {
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_grantRole(OPERATOR_ROLE, operator);
}
function setPaused(bool _paused) external onlyRole(PAUSER_ROLE) {
paused = _paused;
emit PausedStateChanged(_paused);
}
function executeCriticalOperation() external onlyRole(OPERATOR_ROLE) {
require(!paused, "Contract is paused");
// Critical business logic
}
}
2.4 Signature Malleability & Replay Attacks
Mechanism
EIP-191 / EIP-712 signatures can be replayed across different chains or reused if nonces, domain separators, and malleability controls (s value upper bound check) are missing.
Defense Strategy
- Validate EIP-712 domain separator including
block.chainidandaddress(this). - Use OpenZeppelin's
ECDSAlibrary which enforces low-s values and signature length checks. - Store consumed signature nonces or hashes.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract MetaTxProcessor is EIP712 {
using ECDSA for bytes32;
bytes32 private constant EXECUTE_TYPEHASH = keccak256("Execute(address sender,uint256 amount,uint256 nonce,uint256 deadline)");
mapping(address => uint256) public nonces;
error SignatureExpired();
error InvalidSignature();
constructor() EIP712("MetaTxProcessor", "1.0.0") {}
function executeMetaTx(
address sender,
uint256 amount,
uint256 deadline,
bytes calldata signature
) external {
if (block.timestamp > deadline) revert SignatureExpired();
uint256 currentNonce = nonces[sender]++;
bytes32 structHash = keccak256(
abi.encode(EXECUTE_TYPEHASH, sender, amount, currentNonce, deadline)
);
bytes32 digest = _hashTypedDataV4(structHash);
address signer = digest.recover(signature);
if (signer != sender) revert InvalidSignature();
// Perform execution logic for sender
}
}
3. Foundry Development & Testing Guide
Foundry provides high-performance testing via Rust (forge), chain manipulation (cast), and local node capabilities (anvil).
3.1 Advanced Forge Testing Patterns
- Unit Testing: Test functions in isolation with deterministic parameters.
- Fuzz Testing: Use property-based random inputs with
bound()andvm.assume(). - Invariant Testing: Stateful fuzzing ensuring system invariants hold across random sequences of call sequences.
- Fork Testing: Mainnet/testnet state simulation using
vm.createSelectFork().
3.2 Production Foundry Test Suite Example
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "forge-std/Test.sol";
import "../src/Vault.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockToken is ERC20 {
constructor() ERC20("Mock Token", "MTK") {
_mint(msg.sender, 1_000_000 * 1e18);
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
contract VaultTest is Test {
Vault public vault;
MockToken public token;
address public user1 = address(0x1);
address public user2 = address(0x2);
function setUp() public {
token = new MockToken();
vault = new Vault(IERC20(address(token)));
token.mint(user1, 10_000 * 1e18);
token.mint(user2, 10_000 * 1e18);
vm.prank(user1);
token.approve(address(vault), type(uint256).max);
vm.prank(user2);
token.approve(address(vault), type(uint256).max);
}
/// @dev Basic Unit Test
function test_DepositSuccessful() public {
vm.prank(user1);
vault.deposit(1_000 * 1e18);
assertEq(vault.balanceOf(user1), 1_000 * 1e18);
assertEq(token.balanceOf(address(vault)), 1_000 * 1e18);
}
/// @dev Fuzz Test with Input Bounds
function testFuzz_Deposit(uint256 amount) public {
amount = bound(amount, 1, 10_000 * 1e18);
vm.prank(user1);
vault.deposit(amount);
assertEq(vault.balanceOf(user1), amount);
}
/// @dev Reentrancy Failure Verification
function test_ReentrancyProtection() public {
// Verification logic for reentrancy rejection
vm.prank(user1);
vault.deposit(500 * 1e18);
vm.prank(user1);
vault.withdraw(500 * 1e18);
assertEq(vault.balanceOf(user1), 0);
}
}
4. Security Audit Checklist
- State Variables: Are all state variables explicitly marked
private,internal, orpublic? - Reentrancy: Are external calls placed strictly after state modifications? Are
nonReentrantmodifiers added where required? - Integer Safety: Is Solidity
^0.8.0used? Areuncheckedblocks used strictly when mathematical underflow/overflow is impossible? - ERC20 Transfers: Is
SafeERC20used for all external ERC20 calls to handle non-compliant tokens (e.g. USDT)? - Oracles: Are oracle responses validated for stale data (
updatedAt), zero/negative values, and round completeness? - Front-Running / MEV: Are slippage protection thresholds (
minAmountOut) passed as explicit user parameters? - Access Control: Are two-step ownership transfers used (
Ownable2Step)?
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.