solidity-guide

Applies to: Solidity 0.8.20+, Ethereum, EVM-compatible chains, DeFi, NFTs

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "solidity-guide" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-solidity-guide

Solidity Guide

Applies to: Solidity 0.8.20+, Ethereum, EVM-compatible chains, DeFi, NFTs

Core Principles

  • Security First: Every function is a potential attack surface. Assume adversarial callers.

  • Gas Efficiency: On-chain computation is expensive. Optimize storage access and minimize state changes.

  • Explicit Over Implicit: Use explicit visibility, explicit types, and named return values.

  • Immutability by Default: Prefer immutable and constant for values that do not change after deployment.

  • Standards Compliance: Use OpenZeppelin for ERC standards. Do not roll your own token logic.

Guardrails

Compiler Version

  • ALWAYS specify pragma version range: pragma solidity ^0.8.20;

  • Do NOT use floating pragmas in production (e.g., >=0.8.0 ). Pin to a minor range.

  • Enable the optimizer with at least 200 runs for production deployments.

Security

  • NEVER use tx.origin for authorization. Use msg.sender .

  • NEVER use selfdestruct in new contracts (deprecated in Dencun).

  • ALWAYS use ReentrancyGuard from OpenZeppelin for functions that make external calls.

  • ALWAYS validate external inputs: check zero addresses, bounds, and array lengths.

  • Use SafeERC20 for token transfers (handles non-standard return values).

  • Emit events for every state-changing operation (required for off-chain indexing).

Gas Optimization

  • Pack storage variables: group uint128 , uint64 , bool into single 256-bit slots.

  • Use calldata instead of memory for read-only external function parameters.

  • Use custom errors instead of require strings (saves ~50 bytes per error site).

  • Cache storage reads in local variables when accessed more than once.

  • Use unchecked blocks for arithmetic that provably cannot overflow.

  • Prefer mapping over array for large datasets (O(1) vs O(n) lookups).

Access Control

  • Use OpenZeppelin AccessControl or Ownable2Step (not plain Ownable ).

  • Separate admin roles: deployer, upgrader, pauser, minter. No single god key.

  • Consider a timelock for sensitive admin operations (TimelockController ).

Upgradability

  • Use UUPS proxy pattern (preferred) or Transparent proxy when upgradability is required.

  • NEVER change storage layout ordering in upgraded implementations.

  • Use @openzeppelin/contracts-upgradeable with initializer instead of constructors.

  • ALWAYS include a storage gap: uint256[50] private __gap;

  • Disable initializers in the constructor: _disableInitializers();

Key Patterns

Checks-Effects-Interactions (CEI)

Prevents reentrancy by ordering operations: validate, update state, then call externally.

function withdraw(uint256 amount) external nonReentrant { // 1. CHECKS if (amount == 0) revert ZeroAmount(); if (balances[msg.sender] < amount) revert InsufficientBalance();

// 2. EFFECTS: update state BEFORE external calls
balances[msg.sender] -= amount;

// 3. INTERACTIONS: external calls last
(bool success, ) = msg.sender.call{value: amount}("");
if (!success) revert TransferFailed();

emit Withdrawn(msg.sender, amount);

}

Pull Over Push

Let users withdraw funds instead of pushing payments to them.

mapping(address => uint256) public pendingWithdrawals;

function claimPayment() external nonReentrant { uint256 amount = pendingWithdrawals[msg.sender]; if (amount == 0) revert NothingToClaim(); pendingWithdrawals[msg.sender] = 0;

(bool success, ) = msg.sender.call{value: amount}("");
if (!success) revert TransferFailed();
emit PaymentClaimed(msg.sender, amount);

}

Guard Checks with Custom Errors

Custom errors are cheaper than require strings and support typed parameters.

error Unauthorized(address caller); error InsufficientBalance(uint256 requested, uint256 available); error ZeroAddress();

function transfer(address to, uint256 amount) external { if (to == address(0)) revert ZeroAddress(); if (balances[msg.sender] < amount) { revert InsufficientBalance(amount, balances[msg.sender]); } balances[msg.sender] -= amount; balances[to] += amount; emit Transfer(msg.sender, to, amount); }

UUPS Proxy Pattern

import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract MyProtocol is UUPSUpgradeable, OwnableUpgradeable { uint256 public protocolFee;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() { _disableInitializers(); }

function initialize(address owner, uint256 fee) external initializer {
    __Ownable_init(owner);
    __UUPSUpgradeable_init();
    protocolFee = fee;
}

function _authorizeUpgrade(address) internal override onlyOwner {}

uint256[49] private __gap;

}

Security Checklist

  • Reentrancy: Apply nonReentrant to external-call functions. Follow CEI even with the guard. Watch for cross-function reentrancy on shared state.

  • Integer Safety: Solidity 0.8+ has overflow checks. Use unchecked only when provably safe.

  • Front-Running: Use commit-reveal for auctions/voting. Add deadline params to swaps. Use block.timestamp , not block.number .

  • Access Control: Never rely on tx.origin . Use Pausable for emergency stops.

Testing

Foundry (forge) -- Recommended

// test/Vault.t.sol pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol"; import {Vault} from "../src/Vault.sol";

contract VaultTest is Test { Vault public vault; address public alice = makeAddr("alice");

function setUp() public {
    vault = new Vault();
    vm.deal(alice, 10 ether);
}

function test_deposit_updates_balance() public {
    vm.prank(alice);
    vault.deposit{value: 1 ether}();
    assertEq(vault.balances(alice), 1 ether);
}

function test_withdraw_reverts_on_insufficient_balance() public {
    vm.prank(alice);
    vm.expectRevert(Vault.InsufficientBalance.selector);
    vault.withdraw(1 ether);
}

// Fuzz testing: forge generates random inputs automatically
function testFuzz_deposit_and_withdraw(uint96 amount) public {
    vm.assume(amount > 0 &#x26;&#x26; amount &#x3C;= 10 ether);
    vm.startPrank(alice);
    vault.deposit{value: amount}();
    vault.withdraw(amount);
    vm.stopPrank();
    assertEq(vault.balances(alice), 0);
}

}

Hardhat (alternative)

Use npx hardhat test with Chai matchers and ethers.js for JS/TS projects.

Tooling

forge build && forge test # Compile and test (Foundry) forge test -vvvv # Verbose with stack traces forge test --fuzz-runs 10000 # Extended fuzz testing forge coverage # Coverage report forge fmt # Format Solidity files forge snapshot # Gas snapshot for benchmarking slither . # Static analysis (Slither) myth analyze src/Contract.sol # Symbolic execution (Mythril) npx hardhat compile && npx hardhat test # Hardhat alternative

Pre-Deployment Checklist

  • forge test passes, Slither reports zero high/medium findings

  • Gas snapshot compared (forge snapshot --diff )

  • Storage layout verified for upgradeable contracts

  • Deploy script tested on fork: forge script --fork-url $RPC_URL

  • Admin keys secured (multisig, timelock)

References

  • references/patterns.md -- ERC20, proxy upgrades, gas optimization examples

External References

  • Solidity Docs | OpenZeppelin | Foundry Book

  • Solidity by Example | EIP Standards

  • Smart Contract Security (Consensys) | Slither

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

blazor

No summary provided by upstream source.

Repository SourceNeeds Review