Gate-enforced design phase for Solidity smart contract development. Use this skill before writing ANY .sol file — contracts, interfaces, libraries, or upgradeable implementations. Triggers on: "write a contract", "design a vault", "build a staking system", "create a token", "plan this protocol", "what should the architecture be", "I want to build X on-chain", or any similar intent to create new Solidity code. Enforces: design doc → interface → approval before a single line of implementation code. The plan gate cannot be skipped, even for "simple" contracts.
Before writing ANY contract, interface, library, or significant function. This is the mandatory entry point for all new Solidity work — whether the user wants a token, a vault, a governance contract, an AMM, or a single utility function.
NO CONTRACT CODE BEFORE AN APPROVED DESIGN DOCUMENT AND COMMITTED INTERFACE
No .sol files, no scaffolding, no function signatures may be created or written until:
docs/designs/YYYY-MM-DD-<contract-name>-design.mdsrc/interfaces/I<ContractName>.solIf the PreToolUse hook fires and blocks a .sol write, explain the gate and offer to run through
the planning checklist at speed — not skip it.
Before asking any questions, classify the contract. The category determines which vulnerability patterns, OpenZeppelin bases, and known attack surfaces apply.
| Category | Common OZ Bases | Known Attack Patterns to Check |
|---|---|---|
| ERC-20 token | ERC20, ERC20Permit, ERC20Burnable, ERC20Votes | Approval frontrunning, infinite approval risks, permit replay |
| ERC-721 NFT | ERC721, ERC721Enumerable, ERC721URIStorage | Reentrancy on safeTransferFrom, royalty bypass |
| ERC-1155 multi-token | ERC1155, ERC1155Supply | Batch transfer reentrancy, operator approval abuse |
| ERC-4626 vault | ERC4626, ERC20 | First-depositor inflation attack, rounding direction (share math), donation attack |
| Staking / rewards | Ownable2Step, ReentrancyGuard, Pausable | Reward manipulation, flash-stake, incorrect accounting |
| Governance | Governor, GovernorTimelockControl, GovernorVotes | Proposal manipulation, quorum manipulation, timelock bypass |
| Lending / borrow | AccessControl, ReentrancyGuard | Price oracle manipulation, liquidation griefing, bad debt socialization |
| AMM / DEX | Custom (Uniswap V2/V3 patterns) | Sandwich attacks, price manipulation, donation attacks |
| Oracle consumer | None (integration only) | Stale prices, zero/negative price, L2 sequencer downtime |
| Proxy / upgradeable | UUPSUpgradeable, Initializable | Storage collision, uninitialized implementation, _authorizeUpgrade missing |
| Bridge / cross-chain | Custom | Message replay, double-spend on re-org, incorrect domain separator |
| Registry / factory | Ownable2Step, AccessControl | Frontrunning of registration, unauthorized factory deploys |
Infer the category from the user's description — do not ask "what category is this?". Say "This looks like an ERC-4626 vault — does that sound right?" then confirm.
The classification step is not optional. Running the wrong checklist on the wrong contract type produces false confidence.
If the contract falls into a recognized category, read the relevant section of
brainstorming-questions.md before proceeding to Phase 2.
Ask these questions conversationally, grouped by concern area. Do not dump all questions at once. Every item must be resolved before the design doc is written.
safeTransfer and measure balance delta, not the amount paramSafeERC20 usage everywheretokensReceived hooks that allow reentrancy)?
Enumerate every role:
For each role, specify:
Ownable2Step two-step is mandatory for admin roles)Access control pattern selection:
Ownable2Step (never Ownable — single-step transfer = one typo loses admin)AccessControl with explicit bytes32 role constantsTimelockController as the admin, not an EOAFor every piece of mutable state:
If the contract has a lifecycle with distinct phases (e.g., Funding → Active → Closed → Distributing), draw the state machine:
[State A] --triggerFunction()--> [State B] --anotherFunction()--> [State C]
Every valid transition must be explicit. Every invalid transition is a bug.
For every external contract call (protocol integrations, token interactions, oracles):
| Dependency | Address source | Audited? | What if it reverts? | What if it returns garbage? |
|---|---|---|---|---|
| Chainlink price feed | Configurable | Yes | No price, should revert | Negative/zero check required |
| USDC token | Hardcoded | Yes | Transaction reverts normally | N/A (audited standard) |
| Uniswap V3 pool | Configurable | Yes | Route failed, needs fallback | Slippage check required |
Red flags that must be explicitly resolved:
Invariants are statements that MUST always be true, regardless of any sequence of transactions. Write them as Solidity-style assertions — they will be copied directly into invariant test files:
// invariant: vault.totalAssets() <= token.balanceOf(address(vault))
// invariant: totalSupply() == ghost_totalDeposited - ghost_totalWithdrawn
Prompt the user with these categories:
Accounting invariants (asset conservation):
totalSupply() == sum of all balances[address]token.balanceOf(vault) >= totalAssets()Authorization invariants (access control):
only ADMIN_ROLE can call setFee()paused == true → no deposit or withdraw succeedsMonotonicity invariants (one-way counters):
totalDeposited is non-decreasingnonces[user] is strictly increasingSafety invariants (value bounds):
fee <= MAX_FEE (1000 bps) at all timesshares > 0 iff user deposited > 0At least 3 invariants must be documented before the design doc is written.
UUPS pattern (default choice):
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";_authorizeUpgrade with access control (this function is the entire security of UUPS)_disableInitializers() in the implementation constructor (prevents direct initialization of impl)initialize() replaces constructor(), has initializer modifier__gap storage reservation required in every upgradeable base contractTransparent pattern (when UUPS is wrong):
Beacon pattern (when many proxies need the same upgrade):
Explicitly think through these attack vectors before finalizing the design:
Front-running:
Sandwich attacks:
Flash loan attacks:
Oracle manipulation:
MEV / value extraction:
After gathering all the above, propose 2 to 3 distinct architectural approaches:
Approach A: [name]
- Description: [1-2 sentences]
- Gas profile: [high/medium/low]
- Complexity: [simple/moderate/complex]
- Upgrade flexibility: [locked-in/partially-flexible/fully-upgradeable]
- Composability: [easy to integrate / requires adapter / monolithic]
- Key risk: [the one thing that could go wrong with this approach]
Approach B: [name]
...
Recommend one approach and explain why.
Present the chosen design. Wait for EXPLICIT approval. Do not interpret silence as consent. Do not infer approval from enthusiasm ("that sounds great!"). The user must say yes, approve, looks good, proceed, or equivalent.
If the user says "just start writing code" or "skip the plan", invoke the blocked rationalization: the plan gate exists because design failures are the most expensive bugs in Solidity. Offer to make the planning fast — not to skip it.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @title I<ContractName> — <one-line description>
/// @notice <What this contract does in plain English>
/// @dev <Implementation notes: which patterns it uses, what inherits it>
interface I<ContractName> {
// =========================================================================
// Events
// =========================================================================
/// @notice Emitted when <event description>
/// @param <param> <description, units, indexed reason>
event <EventName>(<indexed params>);
// =========================================================================
// Errors
// =========================================================================
/// @notice Thrown when <condition>
/// @param <param> <what it tells the caller>
error <ErrorName>(<typed params>);
// =========================================================================
// External functions
// =========================================================================
/// @notice <What this does>
/// @dev <CEI note, access control, state changes>
/// @param <param> <description, constraints, units>
/// @return <return> <description>
function <functionName>(<params>) external returns (<return>);
}
The interface is the specification. Every function the implementation will expose must appear here with full NatSpec. Implementation without a committed interface is guessing.
File path: docs/designs/YYYY-MM-DD-<contract-name>-design.md
All sections must be present:
docs/designs/YYYY-MM-DD-<contract-name>-design.mdsrc/interfaces/I<ContractName>.solAfter writing both artifacts, commit before any implementation:
git add docs/designs/ src/interfaces/
git commit -m "design: add <ContractName> design doc and interface"
The only valid exit from solidity-planner is:
solidity-builder| Rationalization | Counter |
|---|---|
| "The contract is simple, I don't need to design it" | The Parity multisig was "simple." The Re-entrancy bug in The DAO was "simple." Design finds the gaps that seem obvious afterward. |
| "I'll define interfaces later" | Interfaces are the specification. Implementation without them is guessing at behavior, not implementing it. |
| "I know what I'm building" | Write it down. If you know it, it takes 10 minutes. If it takes longer, you found something you didn't know. |
| "Access control is obvious" | Access control is the #1 vulnerability class in DeFi by dollar value. It's architecture, not a detail. |
| "The user said just write the code" | Explain the plan gate and offer to make planning fast — not to skip it. A 15-minute design session is cheaper than a $5M exploit. |
| "We're iterating fast, we'll add design docs later" | 'Later' means never. The design doc is the cheapest artifact in the entire project. Write it first. |
| "It's a clone of an existing protocol" | Clones inherit vulnerabilities and add new ones from customization. Still needs design. |