# Solidity Security

> >-

- **Type:** Skill
- **Install:** `agentstack add skill-kaynetik-skills-solidity-security`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [kaynetik](https://agentstack.voostack.com/s/kaynetik)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [kaynetik](https://github.com/kaynetik)
- **Source:** https://github.com/kaynetik/skills/tree/main/solidity-security

## Install

```sh
agentstack add skill-kaynetik-skills-solidity-security
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Solidity Security

Vulnerability prevention, secure patterns, gas-safe optimizations, audit preparation.

---

## Code Style Rules

### No Unicode Separator Comments

Never use Unicode box-drawing characters (`─`, `━`, `═`, etc.) as comment decorators or section separators in generated code. This includes patterns like:

```
// ── State ─────────────────────────────────────────
// ══ Errors ═════════════════════════════════════════
```

These are AI slop. They carry no semantic value, are invisible noise in diffs, and mark generated code as low-quality. Use plain labels or nothing at all:

```solidity
// State
mapping(address => uint256) public balances;

// Errors
error InsufficientBalance();
```

---

## Vulnerabilities & Secure Patterns

### 1. Reentrancy

External call before state update lets an attacker re-enter mid-execution.

**Vulnerable:**

```solidity
function withdraw() public {
    uint256 amount = balances[msg.sender];
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok);
    balances[msg.sender] = 0; // state update after call
}
```

**Secure - CEI + ReentrancyGuard:**

```solidity
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract Vault is ReentrancyGuard {
    // Errors
    error InsufficientBalance();
    error TransferFailed();

    function withdraw(uint256 amount) external nonReentrant {
        if (balances[msg.sender] = 0.8.0 has checked arithmetic by default. For `unchecked` blocks, the surrounding logic must prove bounds:

```solidity
uint256 len = arr.length;
for (uint256 i; i = minOutput, "Slippage");
}
```

**Secure - Commit-Reveal:**

```solidity
// State
mapping(bytes32 => uint256) public commitBlock;
uint256 public constant REVEAL_DELAY = 1;

// Errors
error NoCommitment();
error RevealTooEarly();

function commit(bytes32 hash) external {
    commitBlock[hash] = block.number;
}

function reveal(uint256 amount, uint256 minOutput, bytes32 secret) external {
    bytes32 hash = keccak256(abi.encodePacked(msg.sender, amount, minOutput, secret));
    if (commitBlock[hash] == 0) revert NoCommitment();
    if (block.number  MAX_STALENESS) revert StaleOracle();
    return uint256(price);
}
```

### 7. Proxy / Upgrade Pitfalls

| Risk | Prevention |
|------|-----------|
| Storage collision | EIP-1967 slots, OZ upgrades plugin |
| Uninitialized proxy | `initialize()` in same tx as deploy |
| Selector clash | `TransparentUpgradeableProxy` or UUPS |
| Re-initialization | `_disableInitializers()` in constructor |

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

### 8. Signature Replay

```solidity
error InvalidSignature();
error NonceAlreadyUsed();

mapping(bytes32 => bool) public usedNonces;

function executeWithSig(
    address signer, uint256 amount, bytes32 nonce, bytes calldata sig
) external {
    if (usedNonces[nonce]) revert NonceAlreadyUsed();

    bytes32 digest = keccak256(abi.encodePacked(
        "\x19\x01", DOMAIN_SEPARATOR, keccak256(abi.encode(signer, amount, nonce))
    ));

    if (ECDSA.recover(digest, sig) != signer) revert InvalidSignature();

    usedNonces[nonce] = true;
}
```

Use EIP-712 typed data + nonce + `block.chainid` in the domain separator.

---

## Design Patterns

### Pull Over Push

```solidity
// State
mapping(address => uint256) public pending;

// Errors
error NothingToWithdraw();
error TransferFailed();

function recordPayment(address recipient, uint256 amount) internal {
    pending[recipient] += amount;
}

function withdraw() external {
    uint256 amount = pending[msg.sender];
    if (amount == 0) revert NothingToWithdraw();
    pending[msg.sender] = 0;
    (bool ok, ) = msg.sender.call{value: amount}("");
    if (!ok) revert TransferFailed();
}
```

### Emergency Stop

```solidity
import {PausableUpgradeable} from
    "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";

contract Protocol is PausableUpgradeable, OwnableUpgradeable {
    function deposit() external payable whenNotPaused { /* ... */ }
    function pause() external onlyOwner { _pause(); }
    function unpause() external onlyOwner { _unpause(); }
}
```

### Input Validation

```solidity
error ZeroAddress();
error ZeroAmount();
error InsufficientBalance(uint256 available, uint256 requested);

function transfer(address to, uint256 amount) external {
    if (to == address(0)) revert ZeroAddress();
    if (amount == 0) revert ZeroAmount();
    if (balances[msg.sender] = 0.8.4) are cheaper than string reverts and encode structured data.

```solidity
error WithdrawalExceedsBalance(uint256 requested, uint256 available);

function withdraw(uint256 amount) external {
    if (amount > address(this).balance) {
        revert WithdrawalExceedsBalance(amount, address(this).balance);
    }
}
```

### Events for Off-Chain Data

```solidity
event DataStored(address indexed user, uint256 indexed id, bytes data);

function storeData(uint256 id, bytes calldata data) external {
    emit DataStored(msg.sender, id, data);
}
```

Only persist to storage what on-chain logic actually reads.

---

## Security Tooling

| Category | Tool | Purpose |
|----------|------|---------|
| Static analysis | Slither | Detector suite for common vulns |
| Static analysis | Aderyn | Rust-based, Foundry-native |
| Fuzzing | Echidna | Property-based Solidity fuzzer |
| Fuzzing | Medusa | Go-based alternative to Echidna |
| Formal verification | Certora | Prover for critical invariants |
| Formal verification | Halmos | Symbolic execution for Foundry |
| SMT | SMTChecker | Built-in bounded model checker |

### Minimum CI Pipeline

```bash
slither . --filter-paths "node_modules|lib"
forge test --fuzz-runs 10000
forge snapshot --check
```

---

## Testing for Security (Foundry)

```solidity
import "forge-std/Test.sol";

contract SecurityTest is Test {
    Vault vault;
    address attacker = makeAddr("attacker");

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

    function test_RevertWhen_ReentrancyAttempted() public {
        ReentrancyAttacker exploit = new ReentrancyAttacker(address(vault));
        vm.deal(address(exploit), 1 ether);
        vm.expectRevert();
        exploit.attack();
    }

    function test_RevertWhen_UnauthorizedWithdraw() public {
        vm.prank(attacker);
        vm.expectRevert(Vault.Unauthorized.selector);
        vault.emergencyWithdraw();
    }

    function testFuzz_TransferNeverExceedsBalance(uint256 amount) public {
        vm.assume(amount > 0 && amount  0).
    /// @return shares  Vault shares minted.
    function deposit(address token, uint256 amount) external returns (uint256 shares) {
        // ...
    }
}
```

---

## Quick Reference

| Vulnerability | Fix |
|---------------|-----|
| Reentrancy | CEI + `ReentrancyGuard` |
| Missing access control | `Ownable2Step` / `AccessControl` |
| Unchecked ERC20 return | `SafeERC20` |
| Oracle manipulation | TWAP + freshness check + sanity bounds |
| Frontrunning | Commit-reveal, slippage + deadline params |
| Proxy storage collision | EIP-1967, OZ upgrades plugin |
| `tx.origin` auth | `msg.sender` |
| On-chain randomness | Chainlink VRF |
| Unbounded loop DoS | Pagination or pull pattern |
| Signature replay | EIP-712 + nonce + `block.chainid` |
| Flash loan price manipulation | TWAP, multiple oracles |
| Push-payment DoS | Pull-over-push |
| Delegatecall to untrusted | Never; or restrict target via allowlist |

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [kaynetik](https://github.com/kaynetik)
- **Source:** [kaynetik/skills](https://github.com/kaynetik/skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-kaynetik-skills-solidity-security
- Seller: https://agentstack.voostack.com/s/kaynetik
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
