Summary
The Nexus::execute()
handle the execution but does not emit the result when the execution with the EXECTYPE_TRY
type fails.
As a result this lack of emission makes it untrackable when EXECTYPE_TRY
executions fail.
Vulnerability Details
The Nexus::execute()
uses the
ExecutionHelper::_handleSingleExecution()
,
ExecutionHelper::_handleBatchExecution()
ExecutionHelper::_handleDelegateCallExecution()
to handle executions that allows 2 execution types: EXECTYPE_DEFAULT
(revert on failure) and EXECTYPE_TRY
(allow failure).
\\Location Nexus.sol::execute()
function execute(ExecutionMode mode, bytes calldata executionCalldata) external payable onlyEntryPointOrSelf withHook {
(CallType callType, ExecType execType) = mode.decodeBasic();
if (callType == CALLTYPE_SINGLE) {
@> _handleSingleExecution(executionCalldata, execType);
} else if (callType == CALLTYPE_BATCH) {
@> _handleBatchExecution(executionCalldata, execType);
} else if (callType == CALLTYPE_DELEGATECALL) {
@> _handleDelegateCallExecution(executionCalldata, execType);
} else {
revert UnsupportedCallType(callType);
}
}
However, when executed with EXECTYPE_TRY
, the functions _handleSingleExecution()
and _handleDelegateCallExecution()
do not handle or emit success conditions or return data for the call.
\\Location ExecutionHelper.sol::_handleSingleExecution()
function _tryExecute(address target, uint256 value, bytes calldata callData) internal virtual returns (bool success, bytes memory result) {
assembly {
result := mload(0x40)
calldatacopy(result, callData.offset, callData.length)
success := call(gas(), target, value, result, callData.length, codesize(), 0x00)
mstore(result, returndatasize())
let o := add(result, 0x20)
returndatacopy(o, 0x00, returndatasize())
mstore(0x40, add(o, returndatasize()))
}
}
function _handleSingleExecution(bytes calldata executionCalldata, ExecType execType) internal {
(address target, uint256 value, bytes calldata callData) = executionCalldata.decodeSingle();
if (execType == EXECTYPE_DEFAULT) _execute(target, value, callData);
@> else if (execType == EXECTYPE_TRY) _tryExecute(target, value, callData);
else revert UnsupportedExecType(execType);
}
\\Location ExecutionHelper.sol::_handleDelegateCallExecution()
function _tryExecuteDelegatecall(address delegate, bytes calldata callData) internal returns (bool success, bytes memory result) {
assembly {
result := mload(0x40)
calldatacopy(result, callData.offset, callData.length)
success := delegatecall(gas(), delegate, result, callData.length, codesize(), 0x00)
mstore(result, returndatasize())
let o := add(result, 0x20)
returndatacopy(o, 0x00, returndatasize())
mstore(0x40, add(o, returndatasize()))
}
}
function _handleDelegateCallExecution(bytes calldata executionCalldata, ExecType execType) internal {
(address delegate, bytes calldata callData) = executionCalldata.decodeDelegateCall();
if (execType == EXECTYPE_DEFAULT) _executeDelegatecall(delegate, callData);
@> else if (execType == EXECTYPE_TRY) _tryExecuteDelegatecall(delegate, callData);
else revert UnsupportedExecType(execType);
}
Impact
Without emitted results, tracking and debugging execution failures become challenging, especially when EntryPoint or Smart Accounts themselves utilize the Nexus::execute()
with the EXECTYPE_TRY
type.
Tools Used
Manual Review
Recommendations
Modify _handleSingleExecution()
and _handleDelegateCallExecution()
functions in ExecutionHelper.sol
to emit events for failure scenarios when using EXECTYPE_TRY
.
Consider using _handleSingleExecutionAndReturnData()
, _handleBatchExecutionAndReturnData()
, and _handleDelegateCallExecutionAndReturnData()
instead, as these functions already implement emission for failure scenarios.