🎓 A1Learner
🔍
0 of 48 lessons completed

Solidity Delegatecall Dangers

Delegatecall dangers are the storage risks of running another contract code as your own. The called code writes to your storage slots, so a layout mismatch corrupts your values.

Aligned layouts let delegatecall update the caller value

Both contracts put the value first. The delegatecall writes into the caller value and leaves the logic value untouched.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract Logic {
    uint256 public value;

    function setValue(uint256 v) public {
        value = v;
    }
}

contract AlignedCaller {
    uint256 public value;

    function run(address logic, uint256 v) public {
        (bool ok, ) = logic.delegatecall(abi.encodeWithSignature("setValue(uint256)", v));
        require(ok, "delegatecall failed");
    }
}

Mismatched layouts corrupt the wrong variable

The caller puts the owner first and the value second. The logic code writes its value into the first slot, which is the owner.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract Logic {
    uint256 public value;

    function setValue(uint256 v) public {
        value = v;
    }
}

contract Caller {
    address public owner;
    uint256 public value;

    constructor() {
        owner = msg.sender;
    }

    function run(address logic, uint256 v) public {
        (bool ok, ) = logic.delegatecall(abi.encodeWithSignature("setValue(uint256)", v));
        require(ok, "delegatecall failed");
    }
}

Untrusted code can take over through delegatecall

The evil logic writes the caller address into the first slot. Through delegatecall that slot is the caller owner.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract EvilLogic {
    address public owner;

    function takeover() public {
        owner = msg.sender;
    }
}

contract Caller {
    address public owner;
    uint256 public value;

    constructor(address firstOwner) {
        owner = firstOwner;
    }

    function run(address evil) public {
        (bool ok, ) = evil.delegatecall(abi.encodeWithSignature("takeover()"));
        require(ok, "delegatecall failed");
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract Logic {
    uint256 public value;

    function setValue(uint256 v) public {
        value = v;
    }
}

contract AlignedCaller {
    uint256 public value;

    function run(address logic, uint256 v) public {
        (bool ok, ) = logic.delegatecall(abi.encodeWithSignature("setValue(uint256)", v));
        require(ok, "delegatecall failed");
    }
}

contract EvilLogic {
    address public owner;

    function takeover() public {
        owner = msg.sender;
    }
}

contract RiskyCaller {
    address public owner;
    uint256 public value;

    constructor(address firstOwner) {
        owner = firstOwner;
    }

    function runValue(address logic, uint256 v) public {
        (bool ok, ) = logic.delegatecall(abi.encodeWithSignature("setValue(uint256)", v));
        require(ok, "delegatecall failed");
    }

    function runTakeover(address evil) public {
        (bool ok, ) = evil.delegatecall(abi.encodeWithSignature("takeover()"));
        require(ok, "delegatecall failed");
    }
}

Key points

  • Delegatecall: a call that runs another contract code in the caller storage.
  • Storage context: the called code reads and writes the caller slots, not its own.
  • Slot order: values sit in slots in declaration order, so the two contracts must match.
  • Corruption: a mismatch writes a value into the wrong variable.
  • Takeover: untrusted code can rewrite the owner variable and seize control.

In this example, the aligned caller updates its own value through delegatecall and the logic contract stays untouched. The risky caller puts its owner first, so the same logic call writes the value into the owner slot. An evil logic contract then uses the same path to replace the owner outright.

Note

delegatecall keeps the original caller as msg.sender. It does not change who called.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

Why can delegatecall corrupt storage?