Free snapshot slots open this week info@inversez.com
DetectionVulnerabilitiesToolsAuditsPricingBlogFree monitoring assessmentContact
Home/Vulnerabilities/Unsafe delegatecall
SWC-112 · Typically critical

Unsafe delegatecall

delegatecall executes another contract's code in your contract's storage context. Whatever that code writes, it writes to your variables. If the target address is attacker-controlled, or the storage layouts don't match, the consequences are total.

Vulnerable code

Do not ship this
address public owner;
address public library_;

function execute(bytes calldata data) external {
    (bool ok, ) = library_.delegatecall(data);
    require(ok);
}

function setLibrary(address l) external {
    library_ = l;             // unprotected — attacker points it anywhere
}

Why it breaks

An attacker calls setLibrary with the address of a contract they wrote, then calls execute with calldata for a function that sets slot 0. Slot 0 is owner. They now own the contract. Even with setLibrary protected, a delegatecall to a library whose storage layout differs from yours will corrupt state: the library's first variable writes over your owner.

Watch the attack run

The same bug as a stepped sequence. Play it, or walk through with the arrow keys.

The fix

Corrected
address public immutable library_;   // fixed at deploy

constructor(address l) { library_ = l; }

function execute(bytes calldata data) external onlyOwner {
    require(_isAllowedSelector(bytes4(data)), "selector not allowed");
    (bool ok, ) = library_.delegatecall(data);
    require(ok);
}

The invariant

The fix above is what an audit gives you. This is what monitoring gives you: the thing that should always be true once the contract is live, stated precisely enough to check every block.

InvariantEVM · delegatecall

The implementation slot never changes except in a transaction that is an intentional upgrade.

invariant  sload(EIP1967_IMPL_SLOT)(t)
           == sload(EIP1967_IMPL_SLOT)(t-1)
  unless tx in expected_upgrade_set
Backtest: not yet run. This entry carries the reasoning, not the evidence. When the invariant has been replayed against the real transactions around the exploit, the firing block, how far ahead it fired, and its false-positive rate over a clean control period will be published here, along with the recording of the run.

How to test for it

A finding you cannot reproduce is an opinion. Write the test before you write the fix, watch it fail, then make it pass.

function testCannotHijackViaDelegatecall() public {
    Evil evil = new Evil();
    vm.prank(attacker);
    vm.expectRevert();
    proxy.setLibrary(address(evil));

    assertEq(proxy.owner(), owner);   // ownership intact
}

Where it has caused real losses

The second Parity multisig incident in 2017 froze roughly 513,000 ETH permanently. A shared library contract was left uninitialised; a user claimed ownership of it and then called selfdestruct. Every wallet that delegatecalled into that library became unusable, and the funds inside are still inaccessible.

What we check during review

Rules we apply in review: the delegatecall target should be immutable or governance-controlled with a timelock, never settable by an arbitrary caller. Storage layouts between proxy and implementation must match exactly: use unstructured storage slots (EIP-1967) rather than hoping declaration order stays aligned across upgrades. And any library that can be called directly must have its own initialiser locked in the constructor.

Want us to check your contract for this?

A free monitoring assessment covers one contract up to 200 lines, manually reviewed, findings back within 72 hours. This class is on the checklist for every review we run.

Free monitoring assessment

Found this useful?

We publish these because the alternative, asking you to trust us, is worth less. Send a contract and we’ll apply the same thinking to your code.

  • Reply written by the auditor who read your code
  • No sales sequence, no drip campaign, no retargeting
  • We’ll tell you if you don’t need a paid audit yet
  • Report published only with your written permission

Request your snapshot

We read every submission. If it doesn’t fit the free tier we’ll say so and quote you instead, no obligation.