Skip to content
Security
Skill

/delegatecall-risks

Detect delegatecall risks — uninitialized proxies, malicious implementations, storage-slot collisions, delegatecall to user-controlled addresses, library delegatecall pitfalls. Activate on `delegatecall`, UUPS proxy upgrades, multicall implementations, diamond facets,

From plugin
rugproof
952 skills23 agents45 commands4 hooks
Install
$ npx -y skills add omermaksutii/RugProof --skill delegatecall-risks --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/delegatecall-risks

Context preview

The summary Claude sees to decide when to auto-load this skill.

Detect delegatecall risks — uninitialized proxies, malicious implementations, storage-slot collisions, delegatecall to user-controlled addresses, library delegatecall pitfalls. Activate on `delegatecall`, UUPS proxy upgrades, multicall implementations, diamond facets,

SKILL.md

delegatecall-risks.SKILL.md
name: delegatecall-risks
description: Detect delegatecall risks — uninitialized proxies, malicious implementations, storage-slot collisions, delegatecall to user-controlled addresses, library delegatecall pitfalls. Activate on `delegatecall`, UUPS proxy upgrades, multicall implementations, diamond facets, governor-execute patterns.

Delegatecall risk detection

When this applies

  • Any `delegatecall` site
  • Proxy implementations (UUPS, Transparent, Beacon, Diamond)
  • `Multicall` / `Multicall3` / batch-execute on the contract itself
  • Governor's `execute()` flow
  • Library `using X for *` where X uses delegatecall
  • Generic call-forwarder / Safe modules

Detection patterns

delegatecall to user-controlled address (CRITICAL)

function exec(address impl, bytes calldata data) external {
    impl.delegatecall(data);   // ← attacker provides impl, owns storage + can selfdestruct
}

Uninitialized UUPS implementation (CRITICAL)

The *implementation* contract, if uninitialized, can be `initialize`d by anyone, then UUPS-upgraded to a `selfdestruct` impl, bricking the implementation. (Famous: Parity multisig.) Always `_disableInitializers()` in constructor.

Storage slot collision in proxy (CRITICAL)

Proxy uses slot 0 for admin, impl uses slot 0 for `owner` → impl writes corrupt proxy admin. Use EIP-1967 namespaced slots.

delegatecall + `msg.value` (MEDIUM-HIGH)

Forwarding `msg.value` via delegatecall to a function that doesn't expect ether → trapped funds or double-accounting.

Multicall + delegatecall + msg.sender confusion (HIGH)

`Multicall` via delegatecall preserves `msg.sender`, so signed-payload-based functions (`permit`, ERC-2771) can be combined in surprising ways. Audit each function for "what if called via multicall with attacker-controlled previous step?"

Diamond facet self-destruct (HIGH — pre-EIP-6780)

A malicious or buggy facet that calls `selfdestruct` deletes the diamond. EIP-6780 limits this to same-tx-deploy, but old diamonds remain at risk. See [[selfdestruct-eip6780]].

Library marked `internal` actually compiled with delegatecall (HIGH)

Solidity compiles "linked" libraries to `delegatecall`. Linking the wrong library (or address) bricks downstream contracts.

`_authorizeUpgrade` missing or weak (CRITICAL)

UUPS upgrade gate. If unguarded, anyone can upgrade the proxy.

function _authorizeUpgrade(address) internal override {}   // ← no auth, anyone upgrades

Cross-contract delegatecall to read-only function (LOW)

Used (rarely) for pattern obfuscation; usually a code smell.

Severity rubric

| Pattern | Severity | |---|---| | delegatecall to user-supplied address | **Critical** | | UUPS `_authorizeUpgrade` unguarded | **Critical** | | Uninitialized UUPS impl with no `_disableInitializers` | **Critical** | | Storage collision proxy/impl | **Critical** | | Forwarding `msg.value` to non-payable target | **High** | | Multicall + signed-msg sender ambiguity | **High** | | Library linkage to wrong address | **High** | | Diamond facet with selfdestruct path | **High** *(see [[selfdestruct-eip6780]])* |

Remediation patterns

  • Never delegatecall a non-allowlisted address. Hardcode or maintain a strict allowlist of valid implementations.
  • UUPS: `_authorizeUpgrade` MUST check `onlyOwner` (or stricter).
  • Implementation constructor: `_disableInitializers()`.
  • Use EIP-1967 / EIP-7201 namespaced slots for proxy admin / impl.
  • Multicall guard: forbid permit/auth-changing functions in batch, or limit to a non-delegatecall variant.
  • After upgrading: verify storage layout via `forge inspect`.

False-positive notes

  • Solady `LibClone.deployERC1967` and similar canonical proxy libraries are well-tested.
  • delegatecall to an `immutable` implementation address (set in constructor, not changeable) is much lower risk.

Related

  • [[initialization]]
  • [[storage-layout]]
  • [[access-control]]
  • [[selfdestruct-eip6780]]
Read more
Ships withrugproof

Rugproof your code before someone else does. 🌐 Live site: omermaksutii.github.io/RugProof 📦 Latest: v1.0.0 — 45 commands · 23 agents · 45 skills · 13 MCP servers · tested, offline-first, with rule packs, a benchmark, non-EVM coverage, and post-deploy

Get the whole plugin

Other skills on rugproof.