Unchecked external calls refer to a security flaw where a contract makes an external call to another contract or address without properly checking the outcome of that call. In Ethereum, when a contract calls another contract, the called contract can fail silently without throwing an exception. If the calling contract doesn’t check the return value, it might incorrectly assume the call was successful, even if it wasn’t. This can lead to inconsistencies in the contract state and vulnerabilities that attackers can exploit.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Solidity_UncheckedExternalCall {
address public owner;
constructor() {
owner = msg.sender;
}
function forward(address callee, bytes memory _data) public {
callee.delegatecall(_data);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Solidity_CheckedExternalCall {
address public owner;
constructor() {
owner = msg.sender;
}
function forward(address callee, bytes memory _data) public {
// Ensure that delegatecall succeeds
(bool success, ) = callee.delegatecall(_data);
require(success, "Delegatecall failed"); // Check the return value to handle failure
}
}
The two contracts above contain weaknesses beyond an unchecked return value.
msg.sender
, e.g. by comparing it to owner
. Normally, the function forward
should perform some form of authentication.callee
is an address provided by the user. This means that arbitrary code can be executed in the context of this contract, modifying e.g.\ owner
. This is particularly problematic, as forward
does not perform authentication.callee
is not checked for being a contract. If callee
is an address without code, this will go unnoticed, as delegatecall
succeeds. Normally, the function forward
should do basic checks, like verifying that the code size of the called contract is larger than zero.