name: rr-solidity
description: Comprehensive Solidity smart contract development skill using Foundry framework. Use for writing, testing, deploying, and auditing Solidity contracts with security-first practices. Also triggers when working with .sol files, Foundry project files (foundry.toml), test files (.t.sol), or smart contract deployment scripts. Example triggers: "Write smart contract", "Create Solidity test", "Deploy contract", "Audit smart contract", "Fix security vulnerability", "Write Foundry test", "Set up Foundry project"
Solidity Development with Foundry
Overview
Comprehensive skill for professional Solidity smart contract development using the Foundry framework. Provides security-first development practices, testing patterns, static analysis integration (Slither, solhint), and deployment workflows for EVM-compatible blockchains.
When to Use This Skill
Automatically activate when:
- Working with
.sol (Solidity) files
- User mentions smart contracts, blockchain, Web3, DeFi, or Ethereum
- Foundry project detected (
foundry.toml present)
- User requests contract deployment, testing, or security auditing
- Working with forge, cast, or anvil commands
- Implementing ERC standards (ERC20, ERC721, ERC1155, etc.)
Development Workflow
1. Write Secure Contracts
Follow security-first patterns from references/solidity-security.md:
Always implement:
- Checks-Effects-Interactions (CEI) pattern
- Access control (Ownable, AccessControl)
- Input validation
- Emergency pause mechanisms
- Pull over push for payments
- Custom errors for gas efficiency
Example contract structure:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
contract MyContract is Ownable, Pausable {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error InvalidInput();
error InsufficientBalance();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event ActionCompleted(address indexed user, uint256 amount);
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
mapping(address => uint256) public balances;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor() Ownable(msg.sender) {}
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
function deposit() external payable whenNotPaused {
if (msg.value == 0) revert InvalidInput();
balances[msg.sender] += msg.value;
emit ActionCompleted(msg.sender, msg.value);
}
function withdraw(uint256 amount) external whenNotPaused {
// CEI Pattern: Checks
if (amount == 0) revert InvalidInput();
if (balances[msg.sender] < amount) revert InsufficientBalance();
// Effects
balances[msg.sender] -= amount;
// Interactions
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
emit ActionCompleted(msg.sender, amount);
}
}
2. Write Comprehensive Tests
Follow testing patterns from references/foundry-testing.md:
Test structure:
import {Test, console} from "forge-std/Test.sol";
import {MyContract} from "../src/MyContract.sol";
contract MyContractTest is Test {
MyContract public myContract;
address constant OWNER = address(1);
address constant USER = address(2);
function setUp() public {
vm.prank(OWNER);
myContract = new MyContract();
}
// Unit tests
function test_Deposit() public { }
// Edge cases
function test_RevertWhen_ZeroDeposit() public { }
// Fuzz tests
function testFuzz_Deposit(uint256 amount) public { }
// Invariant tests
function invariant_TotalBalanceMatchesContract() public { }
}
Run tests:
forge test
forge test -vvv
forge test --gas-report
forge coverage
3. Security Analysis
Run comprehensive security checks:
slither . --exclude-optimization --exclude-informational
solhint 'src/**/*.sol' 'test/**/*.sol'
bash scripts/check_security.sh
Pre-deployment checklist (from references/solidity-security.md):
4. Build and Deploy
Build contracts:
forge build --optimize --optimizer-runs 200
Deploy using script:
// script/Deploy.s.sol
import {Script} from "forge-std/Script.sol";
import {MyContract} from "../src/MyContract.sol";
contract DeployScript is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
MyContract myContract = new MyContract();
console.log("Deployed at:", address(myContract));
vm.stopBroadcast();
}
}
Execute deployment:
source .env
forge script script/Deploy.s.sol --rpc-url $SEPOLIA_RPC_URL
forge script script/Deploy.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--broadcast \
--verify
forge script script/Deploy.s.sol \
--rpc-url $MAINNET_RPC_URL \
--broadcast \
--verify
Advanced Testing Patterns
Mainnet Forking
Test against real deployed contracts:
contract ForkTest is Test {
IERC20 constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
function setUp() public {
vm.createSelectFork("mainnet", 18_000_000);
}
function test_InteractWithRealProtocol() public {
// Test with actual mainnet state
}
}
forge test --fork-url $MAINNET_RPC_URL
Invariant Testing
Test properties that must always hold:
function invariant_SumOfBalancesEqualsTotalSupply() public {
assertEq(token.totalSupply(), handler.sumOfBalances());
}
Fuzz Testing
Test with randomized inputs:
function testFuzz_Transfer(address to, uint256 amount) public {
vm.assume(to != address(0));
amount = bound(amount, 1, 1000 ether);
// Test with random inputs
}
Gas Optimization
Key strategies from references/solidity-security.md:
- Storage packing - Group uint8/uint16/etc in single slots
- uint256 for counters - Native EVM word size
- calldata over memory - For function parameters
- Events over storage - Log data instead of storing
- Custom errors - More gas-efficient than strings
- Immutable/constant - For unchanging values
Contract Verification
forge verify-contract $CONTRACT_ADDRESS \
src/MyContract.sol:MyContract \
--chain-id 1 \
--etherscan-api-key $ETHERSCAN_KEY
forge verify-contract $CONTRACT_ADDRESS \
src/MyContract.sol:MyContract \
--chain-id 1 \
--etherscan-api-key $ETHERSCAN_KEY \
--constructor-args $(cast abi-encode "constructor(uint256)" 100)
Common Commands Reference
Quick reference from references/foundry-commands.md:
Development:
forge build
forge test
forge test -vvv
forge coverage
forge fmt
forge clean
Testing:
forge test --match-test test_Transfer
forge test --match-contract MyTest
forge test --gas-report
forge test --fork-url $RPC_URL
forge snapshot
Deployment:
forge script script/Deploy.s.sol --broadcast
forge create src/MyContract.sol:MyContract --rpc-url $RPC_URL
forge verify-contract $ADDRESS src/MyContract.sol:MyContract
Debugging:
forge test --debug test_MyTest
cast run $TX_HASH --debug
cast run $TX_HASH --trace
Local node:
anvil
anvil --fork-url $MAINNET_RPC_URL
Security Tools Integration
Slither
Static analyzer with 99+ vulnerability detectors:
slither .
slither . --exclude-optimization
slither . --exclude-informational
slither . --checklist
solhint
Linter configured via assets/solhint.config.json:
solhint 'src/**/*.sol'
solhint 'test/**/*.sol'
solhint --fix 'src/**/*.sol'
Resources
references/
solidity-security.md - Comprehensive security patterns, vulnerabilities, and mitigation strategies
foundry-testing.md - Testing patterns including fuzzing, invariants, fork testing, and mocking
foundry-commands.md - Complete command reference for forge, cast, and anvil
Best Practices Summary
- Security First: Always implement CEI pattern, access controls, and input validation
- Test Coverage: Aim for >90% coverage with unit, fuzz, and invariant tests
- Static Analysis: Run Slither and solhint before every deployment
- Gas Optimization: Apply after security, never at the expense of safety
- Documentation: Use NatSpec comments for all public/external functions
- Audit: Professional audit required for mainnet deployment
- Verification: Verify all contracts on Etherscan post-deployment
- Emergency: Include pause mechanisms for critical contracts
Common Patterns to Reference
When implementing specific functionality, reference these sections:
- ERC20 tokens - Use OpenZeppelin's implementation
- Access control -
references/solidity-security.md (AccessControl, Ownable)
- Upgradeable contracts - OpenZeppelin's proxy patterns
- Reentrancy protection -
references/solidity-security.md (CEI pattern)
- Testing external protocols -
references/foundry-testing.md (fork testing)
- Gas optimization -
references/solidity-security.md (optimization strategies)
- Randomness - Use Chainlink VRF, never block.timestamp alone
- Oracles - Use Chainlink price feeds for production
Workflow Example
Complete workflow for adding a new feature:
- Write contract following security patterns
- Write tests with >90% coverage
- Run tests:
forge test -vvv
- Check coverage:
forge coverage
- Run Slither:
slither .
- Run solhint:
solhint 'src/**/*.sol'
- Format code:
forge fmt
- Build:
forge build
- Deploy to testnet:
forge script script/Deploy.s.sol --broadcast
- Verify:
forge verify-contract ...
- Get audit (for mainnet)
- Deploy to mainnet after audit approval