SC06:2025 Unchecked External Calls
Share
Description:
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.
Example (Vulnerable contract):
// 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);
}
}
Impact:
-
Unchecked external calls can result in failed transactions, causing the intended operations to not be completed successfully. This can lead to the loss of funds, as the contract may proceed under the false assumption that the transfer was successful. Additionally, it can create an incorrect contract state, making the contract vulnerable to further exploits and inconsistencies in its logic.
Remediation:
-
Whenever possible, use transfer() instead of send(), as transfer() reverts the transaction if the external call fails.
-
Always check the return value of send() or call() functions to ensure proper handling if they return false.
Example (Fixed version):
// 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
}
}
Caveats
The two contracts above contain weaknesses beyond an unchecked return value.
-
Authentication is delegated to the callee. The code of the called contract may, or may not, restrict the
msg.sender, e.g. by comparing it toowner. Normally, the functionforwardshould perform some form of authentication. -
calleeis 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, asforwarddoes not perform authentication. -
The address
calleeis not checked for being a contract. Ifcalleeis an address without code, this will go unnoticed, asdelegatecallsucceeds. Normally, the functionforwardshould do basic checks, like verifying that the code size of the called contract is larger than zero.
Verify your solidity code with the Cybercentry Solidity Code Verification (CSCV) on x402scan:
https://www.x402scan.com/server/63fdcd78-4227-4e83-ab3b-07ddf7e6ff7e