259 lines
7.6 KiB
Markdown
259 lines
7.6 KiB
Markdown
---
|
|
name: foundry-solidity
|
|
description: Build and test Solidity smart contracts with Foundry toolkit. Use when developing Ethereum contracts, writing Forge tests, deploying with scripts, or debugging with Cast/Anvil. Triggers on Foundry commands (forge, cast, anvil), Solidity testing, smart contract development, or files like foundry.toml, *.t.sol, *.s.sol.
|
|
---
|
|
|
|
# Foundry Solidity Development
|
|
|
|
Complete guide for building secure, efficient smart contracts with **Foundry 1.5.0** and **Solidity 0.8.30**.
|
|
|
|
## When to Use This Skill
|
|
|
|
- Developing Ethereum/EVM smart contracts
|
|
- Writing Forge tests (unit, fuzz, invariant, fork)
|
|
- Deploying contracts with scripts
|
|
- Using Foundry tools (forge, cast, anvil, chisel)
|
|
- Working with `foundry.toml`, `*.t.sol`, `*.s.sol` files
|
|
- Debugging transactions and contract interactions
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
# Create new project
|
|
forge init my-project && cd my-project
|
|
|
|
# Build contracts
|
|
forge build
|
|
|
|
# Run tests
|
|
forge test
|
|
|
|
# Deploy (dry-run)
|
|
forge script script/Deploy.s.sol --rpc-url sepolia
|
|
|
|
# Deploy (broadcast)
|
|
forge script script/Deploy.s.sol --rpc-url sepolia --broadcast --verify
|
|
```
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
my-project/
|
|
├── foundry.toml # Configuration
|
|
├── src/ # Contracts
|
|
│ └── Counter.sol
|
|
├── test/ # Tests (*.t.sol)
|
|
│ └── Counter.t.sol
|
|
├── script/ # Deploy scripts (*.s.sol)
|
|
│ └── Deploy.s.sol
|
|
└── lib/ # Dependencies
|
|
└── forge-std/
|
|
```
|
|
|
|
## Core Commands
|
|
|
|
### Build & Test
|
|
|
|
```bash
|
|
forge build # Compile
|
|
forge test # Run all tests
|
|
forge test -vvvv # With traces
|
|
forge test --match-test testDeposit # Filter by test name
|
|
forge test --match-contract Vault # Filter by contract
|
|
forge test --fork-url $RPC_URL # Fork testing
|
|
forge test --gas-report # Gas usage report
|
|
```
|
|
|
|
### Deployment
|
|
|
|
```bash
|
|
# Single contract
|
|
forge create src/Token.sol:Token --rpc-url sepolia --private-key $KEY --broadcast
|
|
|
|
# Script deployment (recommended)
|
|
forge script script/Deploy.s.sol:Deploy --rpc-url sepolia --broadcast --verify
|
|
|
|
# Verify existing contract
|
|
forge verify-contract $ADDRESS src/Token.sol:Token --chain sepolia
|
|
```
|
|
|
|
### Cast - Blockchain Interactions
|
|
|
|
```bash
|
|
cast call $CONTRACT "balanceOf(address)" $USER --rpc-url mainnet
|
|
cast send $CONTRACT "transfer(address,uint256)" $TO $AMOUNT --private-key $KEY
|
|
cast decode-tx $TX_HASH
|
|
cast storage $CONTRACT 0 --rpc-url mainnet
|
|
```
|
|
|
|
### Anvil - Local Node
|
|
|
|
```bash
|
|
anvil # Start local node
|
|
anvil --fork-url $RPC_URL # Fork mainnet
|
|
anvil --fork-block-number 18000000
|
|
```
|
|
|
|
## Basic Test Contract
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.30;
|
|
|
|
import {Test, console} from "forge-std/Test.sol";
|
|
import {Counter} from "../src/Counter.sol";
|
|
|
|
contract CounterTest is Test {
|
|
Counter public counter;
|
|
address public user;
|
|
|
|
function setUp() public {
|
|
counter = new Counter();
|
|
user = makeAddr("user");
|
|
deal(user, 10 ether);
|
|
}
|
|
|
|
function test_Increment() public {
|
|
counter.increment();
|
|
assertEq(counter.number(), 1);
|
|
}
|
|
|
|
function test_RevertWhen_Unauthorized() public {
|
|
vm.expectRevert("Unauthorized");
|
|
vm.prank(user);
|
|
counter.adminFunction();
|
|
}
|
|
|
|
function testFuzz_SetNumber(uint256 x) public {
|
|
x = bound(x, 0, 1000);
|
|
counter.setNumber(x);
|
|
assertEq(counter.number(), x);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Essential Cheatcodes
|
|
|
|
```solidity
|
|
// Identity & ETH
|
|
address alice = makeAddr("alice"); // Create labeled address
|
|
deal(alice, 10 ether); // Give ETH
|
|
deal(address(token), alice, 1000e18); // Give ERC20
|
|
|
|
// Impersonation
|
|
vm.prank(alice); // Next call as alice
|
|
vm.startPrank(alice); // All calls as alice
|
|
vm.stopPrank();
|
|
|
|
// Time & Block
|
|
vm.warp(block.timestamp + 1 days); // Set timestamp
|
|
vm.roll(block.number + 100); // Set block number
|
|
|
|
// Assertions
|
|
vm.expectRevert("Error message"); // Expect revert
|
|
vm.expectRevert(CustomError.selector); // Custom error
|
|
vm.expectEmit(true, true, false, true); // Expect event
|
|
emit Transfer(from, to, amount); // Must match next emit
|
|
|
|
// Storage
|
|
vm.store(addr, slot, value); // Write storage
|
|
vm.load(addr, slot); // Read storage
|
|
```
|
|
|
|
## Deploy Script
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.30;
|
|
|
|
import {Script, console} from "forge-std/Script.sol";
|
|
import {Counter} from "../src/Counter.sol";
|
|
|
|
contract Deploy is Script {
|
|
function run() external {
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
|
|
vm.startBroadcast(deployerKey);
|
|
Counter counter = new Counter();
|
|
counter.setNumber(42);
|
|
vm.stopBroadcast();
|
|
|
|
console.log("Deployed to:", address(counter));
|
|
}
|
|
}
|
|
```
|
|
|
|
## Modern Solidity Patterns (0.8.30)
|
|
|
|
```solidity
|
|
// Custom errors (gas efficient)
|
|
error InsufficientBalance(uint256 available, uint256 required);
|
|
|
|
// Transient storage (0.8.28+) - cheap reentrancy guard
|
|
bool transient locked;
|
|
modifier nonReentrant() {
|
|
require(!locked, "Reentrancy");
|
|
locked = true;
|
|
_;
|
|
locked = false;
|
|
}
|
|
|
|
// Immutable variables (cheap reads)
|
|
address public immutable owner;
|
|
|
|
// Named mapping parameters
|
|
mapping(address user => uint256 balance) public balances;
|
|
|
|
// require with custom error (0.8.26+)
|
|
require(amount <= balance, InsufficientBalance(balance, amount));
|
|
```
|
|
|
|
## Configuration (foundry.toml)
|
|
|
|
```toml
|
|
[profile.default]
|
|
src = "src"
|
|
out = "out"
|
|
libs = ["lib"]
|
|
solc = "0.8.30"
|
|
optimizer = true
|
|
optimizer_runs = 200
|
|
evm_version = "prague"
|
|
|
|
fuzz.runs = 256
|
|
invariant.runs = 256
|
|
invariant.depth = 50
|
|
|
|
[rpc_endpoints]
|
|
mainnet = "${MAINNET_RPC_URL}"
|
|
sepolia = "${SEPOLIA_RPC_URL}"
|
|
|
|
[etherscan]
|
|
mainnet = { key = "${ETHERSCAN_API_KEY}" }
|
|
sepolia = { key = "${ETHERSCAN_API_KEY}" }
|
|
|
|
[profile.ci]
|
|
fuzz.runs = 10000
|
|
invariant.runs = 1000
|
|
```
|
|
|
|
## References
|
|
|
|
For detailed guides, see:
|
|
|
|
- **Testing**: See `references/testing.md` for complete testing patterns (unit, fuzz, invariant, fork), all cheatcodes, and best practices
|
|
- **forge-std API**: See `references/forge-std-api.md` for complete library reference (150+ functions)
|
|
- **Solidity 0.8.30**: See `references/solidity-modern.md` for new features and modern syntax
|
|
- **Deployment**: See `references/deployment.md` for scripting, verification, and multi-chain deployment
|
|
- **Configuration**: See `references/configuration.md` for all foundry.toml options
|
|
- **Gas Optimization**: See `references/gas-optimization.md` for storage packing, compiler settings, and profiling
|
|
- **Patterns**: See `references/patterns.md` for access control, reentrancy guards, factories, and common idioms
|
|
- **Security**: See `references/security.md` for vulnerabilities, defensive patterns, and audit preparation
|
|
- **Resources**: See `references/resources.md` for official docs, libraries, security tools, and learning paths
|
|
- **Debugging**: See `references/debugging.md` for traces, breakpoints, console.log, and the interactive debugger
|
|
- **Dependencies**: See `references/dependencies.md` for forge install, remappings, and Soldeer package manager
|
|
- **CI/CD**: See `references/cicd.md` for GitHub Actions workflows, caching, and gas tracking
|
|
- **Chisel**: See `references/chisel.md` for the interactive Solidity REPL
|
|
- **Cast Advanced**: See `references/cast-advanced.md` for decoding, encoding, wallet management, and batch operations
|
|
- **Anvil Advanced**: See `references/anvil-advanced.md` for impersonation, state manipulation, and mining modes
|