Writing or reviewing Solidity smart contracts, implementing ERC-20/721/1155 token standards, building DeFi protocols, optimizing gas costs, perform...
Blockchain and Smart Contracts
When to activate
Writing or reviewing Solidity smart contracts, implementing ERC-20/721/1155 token standards, building DeFi protocols, optimizing gas costs, performing smart contract security reviews, setting up Hardhat or Foundry development and test environments, or writing fuzz tests for smart contracts.
When NOT to use
Blockchain infrastructure that does not involve smart contracts (node setup, RPC endpoints, indexers). Off-chain applications that merely read blockchain state via ethers.js or viem without writing contracts. NFT minting front ends where the contract is already deployed and audited. General cryptography problems that do not involve on-chain execution.
Instructions
OpenZeppelin Inheritance
Always use OpenZeppelin's battle-tested base contracts as the foundation — do not reimplement standard token logic from scratch:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
contract GovernanceToken is ERC20, AccessControl, ReentrancyGuard, Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
uint256 public constant MAX_SUPPLY = 100_000_000 * 10 ** 18;
constructor(address admin) ERC20("Governance Token", "GOV") {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, admin);
_grantRole(PAUSER_ROLE, admin);
}
function mint(address to, uint256 amount)
external
onlyRole(MINTER_ROLE)
whenNotPaused
nonReentrant
{
require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
_mint(to, amount);
}
}
Prefer over when multiple roles with different permissions are needed. Use instead of when single-owner control is sufficient — it prevents ownership being transferred to an incorrect address (requires the new owner to accept).
AccessControl
Ownable
Ownable2Step
Ownable
Gas Optimization
Pack storage slots: the EVM stores state in 32-byte slots. Pack multiple small values into one slot — reading a slot costs 2,100 gas (cold) regardless of how many values are packed:
// Memory — copies array into memory, more expensive
function sum(uint256[] memory values) public pure returns (uint256) { ... }
// Calldata — reads directly from call data, no copy
function sum(uint256[] calldata values) external pure returns (uint256) { ... }
Custom errors are cheaper than require strings:
// Expensive: require stores and reverts with a string
require(msg.sender == owner, "Not the owner");
// Cheap: custom error uses ~50% less gas, also cleaner stack traces
error Unauthorized(address caller);
if (msg.sender != owner) revert Unauthorized(msg.sender);
unchecked arithmetic when overflow is impossible:
// Safe unchecked: loop index can never overflow uint256 in any realistic scenario
for (uint256 i = 0; i < length;) {
process(arr[i]);
unchecked { ++i; } // saves ~30 gas per iteration vs checked increment
}
// Subtraction where underflow is validated by the check above
unchecked {
uint256 remaining = cap - minted; // safe because minted <= cap is checked earlier
}
ERC Standards
ERC-20: fungible token. Override decimals() to return 6 for stablecoins (USDC convention) or use default 18.
ERC-721: non-fungible token. Implement tokenURI to return IPFS or on-chain metadata URI:
Custom errors: NotAllowlisted(), AllowlistExhausted(), ExceedsMaxSupply() — cheaper than require strings.
royaltyInfo returns 5% (500 bps) to owner address via inherited ERC-2981.
Foundry fuzz test testFuzz_royaltyNeverExceedsSalePrice(uint256 salePrice) runs 10,000 iterations asserting royalty <= salePrice for all inputs. Second fuzz test testFuzz_allowlistMintDecrementsBalance(address minter, uint256 quota) verifies balance always decrements exactly 1 per mint regardless of initial quota.