Skip to main content
rule

solidity — Cursor rule: secure, gas-aware smart contracts

A Cursor rule that sets style and safety requirements for Solidity code: pinned pragma, custom errors, reentrancy protection. Useful when you write contracts in Cursor.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/solidity.mdc
Open on GitHub

What solidity does

This is a .cursor/rules/solidity.mdc file that Cursor picks up while you work on code. It covers file layout and contract structure: an SPDX license header with a pinned compiler version, 4-space indentation, the order of declarations inside a contract, and naming conventions.

A separate section deals with gas and security. Instead of require strings the rule asks for custom errors, immutable values go into immutable and constant, and unchecked blocks are allowed only where overflow is provably impossible. Functions that change state and call external contracts should follow Checks-Effects-Interactions with a reentrancy guard.

The examples target compiler version 0.8.29 and point to audited libraries such as OpenZeppelin. If your project runs a different Solidity version, you will have to adjust them. The file cuts off inside the reentrancy section, so the remaining security guidance is not covered.

What it helps with

  • pin the compiler version and license in every file
  • swap require strings for custom errors
  • move fixed values into immutable and constant
  • guard state-changing functions against reentrancy
  • bring contract and function names in line with the Solidity style guide

Example prompts for the agent

  • Check my contract against the solidity rules and show where the element order is wrong
  • Rewrite this withdraw function to use a custom error instead of a require string
  • Look at the contract and tell me which variables should be immutable or constant

How to install solidity

Manually: put the files in place

.cursor/rules/solidity.mdc

Frontmatter: description, globs, alwaysApply

Step by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules

Questions about solidity

How do I install the solidity rule in Cursor?

Create a .cursor/rules/solidity.mdc file in your project root and paste the rule content into it.

What does the solidity rule do?

It tells Cursor how to write Solidity: file header, element order, naming, custom errors, immutable and constant, and reentrancy protection.

Which Solidity version does the rule target?

The examples use compiler 0.8.29 with an exact pragma. On another version you will need to adapt them.

Source text

solidity.mdc · CC0-1.0
---
description: Definitive guidelines for writing secure, maintainable, and gas-efficient Solidity smart contracts, emphasizing modern best practices and security-first development.
globs: **/*
---
# Solidity Best Practices

This guide outlines the definitive best practices for writing Solidity smart contracts, ensuring security, readability, and efficiency. Adhere to these rules for all new and refactored code.

## 1. Code Organization & Structure

### 1.1 File Header
Every Solidity file MUST start with an SPDX license identifier and a precise pragma statement. Pin to a specific, audited compiler version.

❌ BAD
```solidity
pragma solidity ^0.8.0; // Vague pragma, allows minor version bumps
// No license
contract MyContract {}
```

✅ GOOD
```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.29; // Pin to specific, audited compiler version
contract MyContract {}
```

### 1.2 Indentation & Whitespace
Use 4 spaces for indentation. Never use tabs. Maintain consistent spacing around operators and within control structures.

❌ BAD
```solidity
function myFunction (uint256  _value) public {
if(_value > 0){ emit Event(_value);}
}
```

✅ GOOD
```solidity
function myFunction(uint256 _value) public {
    if (_value > 0) {
        emit Event(_value);
    }
}
```

### 1.3 Blank Lines
Use two blank lines between top-level declarations (contracts, interfaces, libraries). Use one blank line between function declarations within a contract.

❌ BAD
```solidity
contract A {}
contract B {}
contract C {
    function foo() public {}
    function bar() public {}
}
```

✅ GOOD
```solidity
contract A {}


contract B {}


contract C {
    function foo() public {}

    function bar() public {}
}
```

### 1.4 Maximum Line Length
Keep lines under 120 characters. Wrap long lines with a single indent for arguments, placing the closing parenthesis/semicolon on its own line.

❌ BAD
```solidity
function reallyLongFunctionName(address _arg1, uint256 _arg2, bytes32 _arg3, bool _arg4, string memory _arg5) public pure returns (bool) { return true; }
```

✅ GOOD
```solidity
function reallyLongFunctionName(
    address _arg1,
    uint256 _arg2,
    bytes32 _arg3,
    bool _arg4,
    string memory _arg5
)
    public
    pure
    returns (bool)
{
    return true;
}
```

### 1.5 Order of Elements
Organize contract elements for clarity and consistency.

1.  `type` declarations (structs, enums)
2.  State variables (constants, immutables, then others by visibility: public, internal, private)
3.  Events
4.  Errors
5.  Constructor
6.  `receive()` function (if exists)
7.  `fallback()` function (if exists)
8.  External functions (view/pure last)
9.  Public functions (view/pure last)
10. Internal functions (view/pure last)
11. Private functions (view/pure last)

### 1.6 Naming Conventions
Follow the Solidity style guide: `PascalCase` for Contracts, Structs, Enums, Custom Errors; `camelCase` for Functions, Events, Modifiers, Local Variables, State Variables; `SCREAMING_SNAKE_CASE` for `constant` and `immutable` variables. Prefix `_` for function parameters.

❌ BAD
```solidity
contract my_contract {
    uint256 PUBLIC_VAR;
    function get_value() internal view returns (uint256) {
        uint256 local_var = 1;
        return local_var;
    }
}
```

✅ GOOD
```solidity
contract MyContract {
    uint256 public publicVar;
    uint256 internal immutable MY_IMMUTABLE_VALUE;

    function getValue(uint256 _input) internal view returns (uint256) {
        uint256 localVariable = _input;
        return localVariable;
    }
}
```

### 1.7 Visibility Declarations
Explicitly declare visibility (`public`, `external`, `internal`, `private`) for all state variables and functions. Never rely on defaults.

❌ BAD
```solidity
uint256 myVar; // Defaults to internal
function calculate() returns (uint256) { // Defaults to public
    return 1;
}
```

✅ GOOD
```solidity
uint256 private myVar;
function calculate() external view returns (uint256) {
    return 1;
}
```

### 1.8 Natspec Documentation
Document all public-facing functions, events, and custom errors using Natspec. This is critical for clarity and security audits.

❌ BAD
```solidity
function transfer(address to, uint256 amount) public returns (bool) {
    // ...
}
```

✅ GOOD
```solidity
/// @notice Transfers `amount` tokens from the caller to `to`.
/// @param to The address to transfer tokens to.
/// @param amount The amount of tokens to transfer.
/// @return A boolean indicating whether the transfer was successful.
/// @dev Emits a {Transfer} event.
function transfer(address to, uint256 amount) public returns (bool) {
    // ...
}
```

## 2. Common Patterns & Anti-patterns

### 2.1 Custom Errors for Reverts
Always use custom errors instead of `require` strings. This saves gas and provides clearer, structured error handling.

❌ BAD
```solidity
function withdraw(uint256 amount) public {
    require(balance[msg.sender] >= amount, "Insufficient balance");
    // ...
}
```

✅ GOOD
```solidity
error InsufficientBalance(uint256 requested, uint256 available);

function withdraw(uint256 amount) public {
    if (balance[msg.sender] < amount) {
        revert InsufficientBalance(amount, balance[msg.sender]);
    }
    // ...
}
```

### 2.2 Immutable and Constant Variables
Use `immutable` for state variables initialized in the constructor and never changed. Use `constant` for compile-time fixed values. This saves gas by storing values in code, not storage.

❌ BAD
```solidity
uint256 public owner; // Stored in storage, mutable
uint256 public MAX_SUPPLY = 1000; // Stored in storage, mutable
constructor() {
    owner = msg.sender;
}
```

✅ GOOD
```solidity
address public immutable OWNER; // Stored in code, immutable
uint256 public constant MAX_SUPPLY = 1000; // Stored in code, constant

constructor() {
    OWNER = msg.sender;
}
```

### 2.3 `unchecked` Blocks
Only use `unchecked` blocks when you have *provably* ensured that arithmetic operations will not overflow or underflow. This is a micro-optimization and should be used sparingly, as it bypasses default 0.8.x safety.

❌ BAD
```solidity
function add(uint256 a, uint256 b) public pure returns (uint256) {
    return a + b; // Relying on default 0.8.x overflow checks
}
```

✅ GOOD
```solidity
function add(uint256 a, uint256 b) public pure returns (uint256) {
    // If a+b is guaranteed not to overflow due to external constraints
    unchecked {
        return a + b;
    }
}
```

### 2.4 Minimal On-chain Logic
Keep on-chain logic as minimal as possible. Perform heavy computation off-chain and verify results on-chain to reduce attack surface and gas costs.

## 3. Security & Pitfalls

### 3.1 Reentrancy Protection
Always use the Checks-Effects-Interactions pattern and apply reentrancy guards to functions that interact with external contracts and modify state. Leverage audited libraries like OpenZeppelin.

❌ BAD
```solidity
function withdrawFunds(uint256 amount) public {
    (bool success, ) = msg.sender.call{value: amount}(""); // External call first
    require(success, "Transfer failed");
    balance[msg.sender] -= amount; // State update after external call
}
```

✅ GOOD
```solidity
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract MyContract is ReentrancyGuard {
    mapping(address => uint256) public balance;

    function withdrawFunds(uint256 amount) public nonReentrant {
        // Checks
        require(balance[msg.sender] >= amount, "Insufficient balance");

        // Effects
        balance[msg.sender] -= amount;

        // Interactions
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }
}
```

### 3.2 Compiler Version
Always use the latest **stable and audited** Solidity compiler version for deployment. Use the absolute latest version for static analysis to catch new warnings.

### 3.3 Avoid `delegatecall` Proxies (Unless Expert)
`delegatecall` proxy patterns are highly complex and error-prone. Prefer contract migration or simpler upgradeability patterns (e.g., data separation) unless your team has deep EVM expertise and dedicated audit resources.

### 3.4 No Inline Assembly (Unless Expert)
Avoid inline assembly unless absolutely necessary and you have mastered the EVM Yellow Paper. It bypasses compiler safety features and is a common source of critical bugs.

## 4. Testing & Verification

### 4.1 Comprehensive Unit Testing
Write thorough unit tests covering all functions, edge cases, and failure conditions. Use modern frameworks like Foundry or Hardhat.

### 4.2 Static Analysis & Fuzzing
Integrate static analysis tools (Slither, Crytic) and fuzzing/property testing tools (Echidna, Manticore) into your CI pipeline. Address all reported issues.

### 4.3 Security Audits
Prioritize external security audits for all production-ready contracts. No amount of internal testing replaces a professional audit.

### 4.4 Post-Deployment Monitoring
Implement robust monitoring for deployed contracts, watching logs and being prepared to react to incidents. Maintain a clear incident response plan.

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Similar IDE rules

Everything for Security →

From the same set69

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor