Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multicall: bubble up revert reason #236

Merged
merged 15 commits into from
Aug 4, 2024
8 changes: 7 additions & 1 deletion src/base/Multicall.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.19;

import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol";

import {IMulticall} from "../interfaces/IMulticall.sol";

import "forge-std/console2.sol";
saucepoint marked this conversation as resolved.
Show resolved Hide resolved

/// @title Multicall
/// @notice Enables calling multiple methods in a single call to the contract
abstract contract Multicall is IMulticall {
using CustomRevert for bytes4;

/// @inheritdoc IMulticall
function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) {
results = new bytes[](data.length);
Expand All @@ -20,7 +26,7 @@ abstract contract Multicall is IMulticall {
}
}
// Next 5 lines from https://ethereum.stackexchange.com/a/83577
if (result.length < 68) revert();
if (result.length < 68) CallFailed.selector.bubbleUpAndRevertWith();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about this 68 length check.. but why wouldnt we want to bubble up if > 68 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because in old-solidity errors couldnt be less than 68 bytes, so it meant there was no error to bubble

assembly {
result := add(result, 0x04)
}
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/IMulticall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ pragma solidity ^0.8.19;
/// @title Multicall interface
/// @notice Enables calling multiple methods in a single call to the contract
interface IMulticall {
error CallFailed(bytes revertReason);
/// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
/// @dev The `msg.value` should not be trusted for any method callable from multicall.
/// @param data The encoded function data for each of the calls to make to this contract
/// @return results The results from each of the calls passed in via data

function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
}
31 changes: 31 additions & 0 deletions test/position-managers/PositionManager.multicall.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,35 @@ contract PositionManagerMulticallTest is Test, PosmTestSetup, LiquidityFuzzers {
assertEq(result.amount0(), amountSpecified);
assertGt(result.amount1(), 0);
}

function test_multicall_bubbleRevert() public {
// charlie will attempt to decrease liquidity without approval
// posm's NotApproved(charlie) should bubble up through Multicall

PositionConfig memory config = PositionConfig({
poolKey: key,
tickLower: TickMath.minUsableTick(key.tickSpacing),
tickUpper: TickMath.maxUsableTick(key.tickSpacing)
});
uint256 tokenId = lpm.nextTokenId();
mint(config, 100e18, address(this), ZERO_BYTES);

Plan memory planner = Planner.init();
planner.add(Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, config, 100e18, ZERO_BYTES));
bytes memory actions = planner.finalizeModifyLiquidity(config.poolKey);

// Use multicall to decrease liquidity
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline);

address charlie = makeAddr("CHARLIE");
vm.startPrank(charlie);
vm.expectRevert(
abi.encodeWithSelector(
IMulticall.CallFailed.selector, abi.encodeWithSelector(IPositionManager.NotApproved.selector, charlie)
)
);
lpm.multicall(calls);
vm.stopPrank();
}
}
Loading