Skip to content

Commit 2a9428b

Browse files
committed
propagate error with delegateCallChecked
1 parent ac0b856 commit 2a9428b

File tree

2 files changed

+49
-3
lines changed

2 files changed

+49
-3
lines changed

packages/ethereum-contracts/contracts/libs/CallUtils.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ pragma solidity ^0.8.23;
1010
*/
1111
function delegateCallChecked(address target, bytes memory callData) {
1212
// solhint-disable-next-line avoid-low-level-calls
13-
(bool success,) = target.delegatecall(callData);
14-
require(success, "CallUtils: delegatecall failed");
13+
(bool success, bytes memory returnedData) = target.delegatecall(callData);
14+
if (!success) {
15+
CallUtils.revertFromReturnedData(returnedData);
16+
}
1517
}
1618

1719
/**

packages/ethereum-contracts/test/foundry/libs/CallUtils.t.sol

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ contract DelegateCallTarget {
1717
function revertAlways() external pure {
1818
revert("Target revert");
1919
}
20+
21+
function revertWithCustomError() external pure {
22+
revert CustomError("Custom error message");
23+
}
24+
25+
function panicAlways() external pure {
26+
assert(false);
27+
}
28+
29+
error CustomError(string message);
2030
}
2131

2232
// Contract that uses delegateCallChecked
@@ -30,6 +40,14 @@ contract DelegateCallChecker {
3040
function delegateCallRevert(address target) external {
3141
delegateCallChecked(target, abi.encodeWithSelector(DelegateCallTarget.revertAlways.selector));
3242
}
43+
44+
function delegateCallCustomError(address target) external {
45+
delegateCallChecked(target, abi.encodeWithSelector(DelegateCallTarget.revertWithCustomError.selector));
46+
}
47+
48+
function delegateCallPanic(address target) external {
49+
delegateCallChecked(target, abi.encodeWithSelector(DelegateCallTarget.panicAlways.selector));
50+
}
3351
}
3452

3553
contract CallUtilsAnvil is Test {
@@ -59,10 +77,36 @@ contract CallUtilsAnvil is Test {
5977
DelegateCallTarget target = new DelegateCallTarget();
6078
DelegateCallChecker checker = new DelegateCallChecker();
6179

62-
vm.expectRevert("CallUtils: delegatecall failed");
80+
// Verify that the actual error message is propagated
81+
vm.expectRevert("Target revert");
6382
checker.delegateCallRevert(address(target));
6483
}
6584

85+
function testDelegateCallChecked_CustomError() public {
86+
DelegateCallTarget target = new DelegateCallTarget();
87+
DelegateCallChecker checker = new DelegateCallChecker();
88+
89+
// Verify that custom errors are properly propagated
90+
vm.expectRevert(
91+
abi.encodeWithSelector(
92+
DelegateCallTarget.CustomError.selector,
93+
"Custom error message"
94+
)
95+
);
96+
checker.delegateCallCustomError(address(target));
97+
}
98+
99+
function testDelegateCallChecked_Panic() public {
100+
DelegateCallTarget target = new DelegateCallTarget();
101+
DelegateCallChecker checker = new DelegateCallChecker();
102+
103+
// Verify that panic errors are properly propagated with error information
104+
// Panic code 0x01 is for assert(false)
105+
// The error should be formatted as "CallUtils: target panicked: 0x01"
106+
vm.expectRevert("CallUtils: target panicked: 0x01");
107+
checker.delegateCallPanic(address(target));
108+
}
109+
66110
// TODO this is a hard fuzzing case, because we need to know if there is a case that:
67111
// 1. CallUtils.isValidAbiEncodedBytes returns true
68112
// 2. and abi.decode reverts

0 commit comments

Comments
 (0)