.cursor/skills/solidity/SKILL.md
# Skill: solidity ## Scope - Solidity 0.8.24 smart contract development with Foundry - EVM internals and execution environment understanding - Security patterns and vulnerability prevention - Gas optimization techniques - Testing strategies with Foundry (unit, fuzz, invariant) - Client interactions using viem v2 - OpenZeppelin Contracts integration Does NOT cover: - Frontend wallet integration (see wagmi skill) - Solana development (see solana skill) ## Assumptions - Solidity 0.8.24 - Found
npx skillsauth add blockmatic-icebox/basilic-old .cursor/skills/solidityInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Does NOT cover:
getAddress() from viem (never cast directly)@openzeppelin/=lib/openzeppelin-contracts/)calldata for read-only function parametersunchecked blocks for safe math operations (loop counters, bounded increments)block.timestamp for critical logic (miners can manipulate)Always update state before external calls to prevent reentrancy:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract SecureVault {
mapping(address => uint256) public balances;
error InsufficientBalance();
error TransferFailed();
function withdraw(uint256 amount) external {
// 1. CHECKS
if (balances[msg.sender] < amount) revert InsufficientBalance();
// 2. EFFECTS (update state first)
balances[msg.sender] -= amount;
// 3. INTERACTIONS (external call last)
(bool ok,) = msg.sender.call{value: amount}("");
if (!ok) revert TransferFailed();
}
}
Use custom errors for gas efficiency:
error InsufficientBalance(uint256 requested, uint256 available);
error Unauthorized(address caller);
error TransferFailed();
function withdraw(uint256 amount) external {
if (balances[msg.sender] < amount) {
revert InsufficientBalance(amount, balances[msg.sender]);
}
// ...
}
Use OpenZeppelin Contracts via remappings in foundry.toml:
[profile.default]
remappings = [
"@openzeppelin/=lib/openzeppelin-contracts/",
]
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MTK") {}
}
Pack storage efficiently to save gas:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Optimized {
// Pack into single slot (32 bytes)
struct User {
uint128 balance; // 16 bytes
uint64 lastUpdate; // 8 bytes
uint32 nonce; // 4 bytes
bool active; // 1 byte
// 3 bytes padding
}
mapping(address => User) public users;
}
Use calldata for read-only parameters:
// ✅ Good: calldata for read-only
function processData(bytes calldata data) external {
// data is read-only, saves ~3 gas per byte
}
// ❌ Bad: memory for read-only
function processData(bytes memory data) external {
// Unnecessary copy to memory
}
Cache storage reads in memory when used multiple times:
// ✅ Good: cache storage read
function processUser(address user) external {
uint256 balance = balances[user]; // Cache once
if (balance > 0) {
// Use cached value multiple times
process(balance);
update(balance);
}
}
// ❌ Bad: multiple storage reads
function processUser(address user) external {
if (balances[user] > 0) {
process(balances[user]); // Storage read #1
update(balances[user]); // Storage read #2
}
}
Use unchecked blocks for math operations where overflow/underflow is impossible:
// ✅ Good: unchecked for safe operations
function increment(uint256 x) external pure returns (uint256) {
unchecked {
return x + 1; // Safe: can't overflow uint256
}
}
// ✅ Good: loop counter increment
function processBatch(uint256[] calldata items) external {
for (uint256 i = 0; i < items.length;) {
process(items[i]);
unchecked {
++i; // Safe: loop bound prevents overflow
}
}
}
// ❌ Bad: unnecessary checked math
function increment(uint256 x) external pure returns (uint256) {
return x + 1; // Unnecessary: Solidity 0.8+ checks by default
}
Always follow CEI pattern (see Contract Development section above). For additional protection, use reentrancy guards when needed:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Vault is ReentrancyGuard {
function withdraw(uint256 amount) external nonReentrant {
// CEI pattern + reentrancy guard for extra protection
}
}
Always protect admin functions:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract SecureContract {
address public owner;
error Unauthorized();
modifier onlyOwner() {
if (msg.sender != owner) revert Unauthorized();
_;
}
function setAdmin(address newAdmin) external onlyOwner {
owner = newAdmin;
}
}
Validate all inputs before processing:
error ZeroAddress();
error InvalidAmount();
error OutOfBounds(uint256 value, uint256 max);
function transfer(address to, uint256 amount) external {
// Zero address check
if (to == address(0)) revert ZeroAddress();
// Bounds check
if (amount == 0) revert InvalidAmount();
if (amount > balances[msg.sender]) revert InsufficientBalance();
// Process transfer
balances[msg.sender] -= amount;
balances[to] += amount;
}
Use Foundry's testing framework:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/TestToken.sol";
contract TestTokenTest is Test {
TestToken token;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
function setUp() public {
token = new TestToken("Test", "TEST", 18);
}
function test_Mint() public {
token.mint(alice, 1000);
assertEq(token.balanceOf(alice), 1000);
}
}
Test with random inputs to find edge cases:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
contract SecurityTest is Test {
Vault vault;
function setUp() public {
vault = new Vault();
}
function testFuzz_Withdraw(uint256 amount) public {
// Bound input to reasonable range
amount = bound(amount, 1, type(uint128).max);
vm.deal(address(this), amount);
vault.deposit{value: amount}();
uint256 before = address(this).balance;
vault.withdraw(amount);
uint256 after = address(this).balance;
assertEq(after, before + amount);
}
}
Test system-wide properties that should always hold:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
contract TokenInvariantTest is Test {
Token token;
address[] actors;
function setUp() public {
token = new Token();
// Setup actors...
}
function invariant_BalancesMatchTotalSupply() public {
uint256 sum = 0;
for (uint i = 0; i < actors.length; i++) {
sum += token.balanceOf(actors[i]);
}
assertEq(token.totalSupply(), sum);
}
}
Read contract storage using viem v2:
import { createPublicClient, http, keccak256, encodePacked } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http(),
})
// Read mapping value: balances[address]
async function getBalance(contract: `0x${string}`, user: `0x${string}`) {
const slot = keccak256(encodePacked(['address', 'uint256'], [user, 0n]))
return await client.getStorageAt({ address: contract, slot })
}
Use EIP-1559 for predictable fee structure:
import { createWalletClient, http, parseEther, parseGwei } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createWalletClient({
chain: arbitrumSepolia,
transport: http(),
})
const hash = await client.sendTransaction({
to: '0x...',
value: parseEther('0.1'),
type: 'eip1559',
maxFeePerGas: parseGwei('30'),
maxPriorityFeePerGas: parseGwei('2'),
})
onlyOwner, onlyRole)forge testforge test --fuzzinvariant_* functionsforge snapshotforge coveragecast for debugging and interactionshttps://sepolia-rollup.arbitrum.io/rpccalldata saves ~3 gas per byte but is read-only. Use calldata for read-only parameters.web3-frontend for frontend integrationdevelopment
# Skill: wagmi ## Scope - React/Next.js wallet integration with Wagmi v3 for EVM chains - Contract interactions using viem v2 for address validation and transaction building - Transaction state management and error handling - Custom hooks wrapping wagmi for contract-specific interactions Does NOT cover: - Solana frontend development - Backend RPC interactions - Smart contract development ## Assumptions - Wagmi v3.3.2+ - viem v2.44.4 - React 18+ or Next.js 14+ - TypeScript v5+ with strict mo
development
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
development
Advanced TypeScript patterns for type-safe, maintainable code using sophisticated type system features. Use when building type-safe APIs, implementing complex domain models, or leveraging TypeScript's advanced type capabilities.
development
TanStack Query (React Query) for async operations, data fetching, caching, and state management. Use when: fetching server data, managing async operations, caching responses, handling mutations, or any operation that benefits from automatic state management and caching.