Delegatecall Explained in Detail
delegatecall runs code from a target contract in the caller's context. The target code can read and write the caller's storage. msg.sender and msg.value remain from the original call.
delegatecall is useful for upgradeable proxies and libraries, but dangerous when users can control the target or calldata.
Smart contract example
contract Proxy {
function forward(address target, bytes calldata data) external {
(bool ok,) = target.delegatecall(data);
require(ok, "delegatecall failed");
}
}
If target is attacker-controlled, the attacker can execute code that writes to the proxy's storage.
Delegatecall in Auditing
Unsafe delegatecall can overwrite ownership, corrupt storage, bypass authorization, execute malicious upgrade code, or break proxy assumptions.
The key question is: whose code runs, and whose storage changes?
Red flags in code
-
User-controlled delegatecall target.
-
Arbitrary calldata forwarded to a privileged implementation.
-
Upgrade functions without strong access control.
-
Delegatecall in business logic that is not a proxy or trusted library pattern.
-
Storage layout mismatch between proxy and implementation.
-
Low-level call return values not checked or bubbled.
How to test or review it
-
Confirm every delegatecall target is immutable, allowlisted, or governed safely.
-
Compare storage layout before upgrades.
-
Test a malicious target that writes to storage slot
0. -
Check whether delegatecall can change owner, implementation, admin, or balances.
-
Verify initialization and upgrade paths cannot be called by unauthorized users.
-
Review fallback functions and calldata forwarding carefully.