Skip to content

Commit

Permalink
optimization: [internal-s47] adjust bubble up revert error to conform…
Browse files Browse the repository at this point in the history
… with ERC7751
  • Loading branch information
chefburger committed Oct 29, 2024
1 parent a08c55f commit 82addd8
Show file tree
Hide file tree
Showing 20 changed files with 122 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
178162
178172
Original file line number Diff line number Diff line change
@@ -1 +1 @@
188425
188435
Original file line number Diff line number Diff line change
@@ -1 +1 @@
184048
184058
2 changes: 1 addition & 1 deletion .forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
311216
311226
2 changes: 1 addition & 1 deletion .forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
189420
189430
2 changes: 1 addition & 1 deletion .forge-snapshots/BinPoolManagerBytecodeSize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
23951
24175
Original file line number Diff line number Diff line change
@@ -1 +1 @@
172921
172918
Original file line number Diff line number Diff line change
@@ -1 +1 @@
178949
178946
Original file line number Diff line number Diff line change
@@ -1 +1 @@
132952
132949
2 changes: 1 addition & 1 deletion .forge-snapshots/CLPoolManagerBytecodeSize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20902
21103
2 changes: 1 addition & 1 deletion .forge-snapshots/CLPoolManagerTest#swap_withHooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
87398
87408
2 changes: 1 addition & 1 deletion .forge-snapshots/VaultBytecodeSize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7614
7792
51 changes: 37 additions & 14 deletions src/libraries/CustomRevert.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,46 @@ pragma solidity ^0.8.0;
/// @notice Contains functions for reverting with custom errors with different argument types efficiently
/// @dev The functions may tamper with the free memory pointer but it is fine since the call context is exited immediately
library CustomRevert {
/// @notice bubble up the revert message returned by a call and revert with the selector provided
/// @dev this function should only be used with custom errors of the type `CustomError(address target, bytes revertReason)`
function bubbleUpAndRevertWith(bytes4 selector, address addr) internal pure {
/// @dev ERC-7751 error for wrapping bubbled up reverts
error WrappedError(address target, bytes4 selector, bytes reason, bytes details);

/// @notice bubble up the revert message returned by a call and revert with a wrapped ERC-7751 error
/// @dev this method can be vulnerable to revert data bombs
function bubbleUpAndRevertWith(
address revertingContract,
bytes4 revertingFunctionSelector,
bytes4 additionalContext
) internal pure {
bytes4 wrappedErrorSelector = WrappedError.selector;
assembly ("memory-safe") {
let size := returndatasize()
let fmp := mload(0x40)
// Ensure the size of the revert data is a multiple of 32 bytes
let encodedDataSize := mul(div(add(returndatasize(), 31), 32), 32)

// Encode selector, address, offset, size, data
mstore(fmp, selector)
mstore(add(fmp, 0x04), addr)
mstore(add(fmp, 0x24), 0x40)
mstore(add(fmp, 0x44), size)
returndatacopy(add(fmp, 0x64), 0, size)
let fmp := mload(0x40)

// Ensure the size is a multiple of 32 bytes
let encodedSize := add(0x64, mul(div(add(size, 31), 32), 32))
revert(fmp, encodedSize)
// Encode wrapped error selector, address, function selector, offset, additional context, size, revert reason
mstore(fmp, wrappedErrorSelector)
mstore(add(fmp, 0x04), and(revertingContract, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(
add(fmp, 0x24),
and(revertingFunctionSelector, 0xffffffff00000000000000000000000000000000000000000000000000000000)
)
// offset revert reason
mstore(add(fmp, 0x44), 0x80)
// offset additional context
mstore(add(fmp, 0x64), add(0xa0, encodedDataSize))
// size revert reason
mstore(add(fmp, 0x84), returndatasize())
// revert reason
returndatacopy(add(fmp, 0xa4), 0, returndatasize())
// size additional context
mstore(add(fmp, add(0xa4, encodedDataSize)), 0x04)
// additional context
mstore(
add(fmp, add(0xc4, encodedDataSize)),
and(additionalContext, 0xffffffff00000000000000000000000000000000000000000000000000000000)
)
revert(fmp, add(0xe4, encodedDataSize))
}
}
}
9 changes: 3 additions & 6 deletions src/libraries/Hooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ library Hooks {
using ParametersHelper for bytes32;
using LPFeeLibrary for uint24;
using ParseBytes for bytes;
using CustomRevert for bytes4;

/// @notice thrown when a hook call fails
/// @param hook the hook address
/// @param revertReason bubbled up revert reason
error Wrap__FailedHookCall(address hook, bytes revertReason);
/// @notice Additional context for ERC-7751 wrapped error when a hook call fails
error HookCallFailed();

/// @notice Hook permissions contain conflict
/// 1. enabled beforeSwapReturnsDelta, but lacking beforeSwap call
Expand Down Expand Up @@ -79,7 +76,7 @@ library Hooks {
success := call(gas(), self, 0, add(data, 0x20), mload(data), 0, 0)
}
// Revert with FailedHookCall, containing any error message to bubble up
if (!success) Wrap__FailedHookCall.selector.bubbleUpAndRevertWith(address(self));
if (!success) CustomRevert.bubbleUpAndRevertWith(address(self), bytes4(data), HookCallFailed.selector);

// The call was successful, fetch the returned data
assembly ("memory-safe") {
Expand Down
21 changes: 10 additions & 11 deletions src/types/Currency.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,12 @@ function greaterThanOrEqualTo(Currency currency, Currency other) pure returns (b
/// @dev This library allows for transferring and holding native tokens and ERC20 tokens
library CurrencyLibrary {
using CurrencyLibrary for Currency;
using CustomRevert for bytes4;

/// @notice Thrown when a native transfer fails
/// @param recipient address that the transfer failed to
/// @param revertReason bubbled up revert reason
error Wrap__NativeTransferFailed(address recipient, bytes revertReason);
/// @notice Additional context for ERC-7751 wrapped error when a native transfer fails
error NativeTransferFailed();

/// @notice Thrown when an ERC20 transfer fails
/// @param token address of the ERC20 token
/// @param revertReason bubbled up revert reason
error Wrap__ERC20TransferFailed(address token, bytes revertReason);
/// @notice Additional context for ERC-7751 wrapped error when an ERC20 transfer fails
error ERC20TransferFailed();

/// @notice A constant to represent the native currency
Currency public constant NATIVE = Currency.wrap(address(0));
Expand All @@ -55,7 +50,7 @@ library CurrencyLibrary {
success := call(gas(), to, amount, 0, 0, 0, 0)
}
// revert with NativeTransferFailed, containing the bubbled up error as an argument
if (!success) Wrap__NativeTransferFailed.selector.bubbleUpAndRevertWith(to);
if (!success) CustomRevert.bubbleUpAndRevertWith(to, bytes4(0), NativeTransferFailed.selector);
} else {
assembly ("memory-safe") {
// Get a pointer to some free memory.
Expand Down Expand Up @@ -84,7 +79,11 @@ library CurrencyLibrary {
mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here
}
// revert with ERC20TransferFailed, containing the bubbled up error as an argument
if (!success) Wrap__ERC20TransferFailed.selector.bubbleUpAndRevertWith(Currency.unwrap(currency));
if (!success) {
CustomRevert.bubbleUpAndRevertWith(
Currency.unwrap(currency), IERC20Minimal.transfer.selector, ERC20TransferFailed.selector
);
}
}
}

Expand Down
17 changes: 14 additions & 3 deletions test/libraries/Currency.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
pragma solidity ^0.8.24;

import {Test} from "forge-std/Test.sol";
import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol";
import {MockERC20, ERC20} from "solmate/src/test/utils/mocks/MockERC20.sol";
import {Currency, CurrencyLibrary} from "../../src/types/Currency.sol";
import {TokenRejecter} from "../helpers/TokenRejecter.sol";
import {TokenSender} from "../helpers/TokenSender.sol";
import {stdError} from "forge-std/StdError.sol";
import {CustomRevert} from "../../src/libraries/CustomRevert.sol";

contract TestCurrency is Test {
using CurrencyLibrary for uint256;
Expand Down Expand Up @@ -110,7 +111,13 @@ contract TestCurrency is Test {
deal(address(sender), 10 ether);

vm.expectRevert(
abi.encodeWithSelector(CurrencyLibrary.Wrap__NativeTransferFailed.selector, otherAddress, new bytes(0))
abi.encodeWithSelector(
CustomRevert.WrappedError.selector,
otherAddress,
bytes4(0),
new bytes(0),
abi.encodeWithSelector(CurrencyLibrary.NativeTransferFailed.selector)
)
);
sender.send(nativeCurrency, otherAddress, 10 ether + 1);
}
Expand All @@ -121,7 +128,11 @@ contract TestCurrency is Test {

vm.expectRevert(
abi.encodeWithSelector(
CurrencyLibrary.Wrap__ERC20TransferFailed.selector, erc20Currency, stdError.arithmeticError
CustomRevert.WrappedError.selector,
erc20Currency,
ERC20.transfer.selector,
stdError.arithmeticError,
abi.encodeWithSelector(CurrencyLibrary.ERC20TransferFailed.selector)
)
);
sender.send(erc20Currency, otherAddress, 101);
Expand Down
18 changes: 15 additions & 3 deletions test/pool-bin/BinHookRevertWithReason.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {BinPoolParametersHelper} from "../../src/pool-bin/libraries/BinPoolParam
import {Hooks} from "../../src/libraries/Hooks.sol";
import {BinRevertHook} from "./helpers/BinRevertHook.sol";
import {BaseBinTestHook} from "./helpers/BaseBinTestHook.sol";
import {IBinHooks} from "../../src/pool-bin/interfaces/IBinHooks.sol";
import {CustomRevert} from "../../src/libraries/CustomRevert.sol";

/// @dev make sure the revert reason is bubbled up
contract BinHookRevertWithReasonTest is Test {
Expand Down Expand Up @@ -52,17 +54,27 @@ contract BinHookRevertWithReasonTest is Test {
}

function testRevertWithNoReason() public {
vm.expectRevert(abi.encodeWithSelector(Hooks.Wrap__FailedHookCall.selector, hook, new bytes(0)));
vm.expectRevert(
abi.encodeWithSelector(
CustomRevert.WrappedError.selector,
hook,
IBinHooks.afterInitialize.selector,
new bytes(0),
abi.encodeWithSelector(Hooks.HookCallFailed.selector)
)
);
poolManager.initialize(key, activeId);
}

function testRevertWithHookNotImplemented() public {
hook.setRevertWithHookNotImplemented(true);
vm.expectRevert(
abi.encodeWithSelector(
Hooks.Wrap__FailedHookCall.selector,
CustomRevert.WrappedError.selector,
hook,
abi.encodeWithSelector(BaseBinTestHook.HookNotImplemented.selector)
IBinHooks.afterInitialize.selector,
abi.encodeWithSelector(BaseBinTestHook.HookNotImplemented.selector),
abi.encodeWithSelector(Hooks.HookCallFailed.selector)
)
);
poolManager.initialize(key, activeId);
Expand Down
14 changes: 10 additions & 4 deletions test/pool-bin/libraries/BinPoolFee.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {BinFeeManagerHook} from "../helpers/BinFeeManagerHook.sol";
import {HOOKS_AFTER_INITIALIZE_OFFSET, HOOKS_BEFORE_MINT_OFFSET} from "../../../src/pool-bin/interfaces/IBinHooks.sol";
import {Hooks} from "../../../src/libraries/Hooks.sol";
import {SortTokens} from "../../helpers/SortTokens.sol";
import {CustomRevert} from "../../../src/libraries/CustomRevert.sol";
import {IBinHooks} from "../../../src/pool-bin/interfaces/IBinHooks.sol";

/**
* @dev tests around fee for mint(), swap() and burn()
Expand Down Expand Up @@ -172,9 +174,11 @@ contract BinPoolFeeTest is BinTestHelper {
bytes memory data = abi.encode(true, uint24(swapFee));
vm.expectRevert(
abi.encodeWithSelector(
Hooks.Wrap__FailedHookCall.selector,
CustomRevert.WrappedError.selector,
binFeeManagerHook,
abi.encodeWithSelector(LPFeeLibrary.LPFeeTooLarge.selector, uint24(swapFee))
IBinHooks.beforeMint.selector,
abi.encodeWithSelector(LPFeeLibrary.LPFeeTooLarge.selector, uint24(swapFee)),
abi.encodeWithSelector(Hooks.HookCallFailed.selector)
)
);
addLiquidityToBin(key, poolManager, bob, activeId, 10_000 ether, 10_000 ether, 1e18, 1e18, data);
Expand Down Expand Up @@ -416,9 +420,11 @@ contract BinPoolFeeTest is BinTestHelper {
bytes memory data = abi.encode(true, uint24(swapFee));
vm.expectRevert(
abi.encodeWithSelector(
Hooks.Wrap__FailedHookCall.selector,
CustomRevert.WrappedError.selector,
binFeeManagerHook,
abi.encodeWithSelector(LPFeeLibrary.LPFeeTooLarge.selector, uint24(swapFee))
IBinHooks.beforeSwap.selector,
abi.encodeWithSelector(LPFeeLibrary.LPFeeTooLarge.selector, uint24(swapFee)),
abi.encodeWithSelector(Hooks.HookCallFailed.selector)
)
);
poolManager.swap(key, true, 1e18, data);
Expand Down
18 changes: 15 additions & 3 deletions test/pool-cl/CLHookRevertWithReason.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {TokenFixture} from "../helpers/TokenFixture.sol";
import {CLRevertHook} from "./helpers/CLRevertHook.sol";
import {CLPoolParametersHelper} from "../../src/pool-cl/libraries/CLPoolParametersHelper.sol";
import {BaseCLTestHook} from "./helpers/BaseCLTestHook.sol";
import {CustomRevert} from "../../src/libraries/CustomRevert.sol";
import {ICLHooks} from "../../src/pool-cl/interfaces/ICLHooks.sol";

/// @dev make sure the revert reason is bubbled up
contract CLHookRevertWithReasonTest is Test, Deployers, TokenFixture {
Expand All @@ -45,17 +47,27 @@ contract CLHookRevertWithReasonTest is Test, Deployers, TokenFixture {
}

function testRevertWithNoReason() public {
vm.expectRevert(abi.encodeWithSelector(Hooks.Wrap__FailedHookCall.selector, hook, new bytes(0)));
vm.expectRevert(
abi.encodeWithSelector(
CustomRevert.WrappedError.selector,
hook,
ICLHooks.afterInitialize.selector,
new bytes(0),
abi.encodeWithSelector(Hooks.HookCallFailed.selector)
)
);
poolManager.initialize(key, SQRT_RATIO_1_1);
}

function testRevertWithHookNotImplemented() public {
hook.setRevertWithHookNotImplemented(true);
vm.expectRevert(
abi.encodeWithSelector(
Hooks.Wrap__FailedHookCall.selector,
CustomRevert.WrappedError.selector,
hook,
abi.encodeWithSelector(BaseCLTestHook.HookNotImplemented.selector)
ICLHooks.afterInitialize.selector,
abi.encodeWithSelector(BaseCLTestHook.HookNotImplemented.selector),
abi.encodeWithSelector(Hooks.HookCallFailed.selector)
)
);
poolManager.initialize(key, SQRT_RATIO_1_1);
Expand Down
8 changes: 6 additions & 2 deletions test/pool-cl/CLPoolManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {NoIsolate} from "../helpers/NoIsolate.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {CLPoolGetter} from "./helpers/CLPoolGetter.sol";
import {CLSlot0} from "../../src/pool-cl/types/CLSlot0.sol";
import {CustomRevert} from "../../src/libraries/CustomRevert.sol";
import {ICLHooks} from "../../src/pool-cl/interfaces/ICLHooks.sol";

contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnapshot {
using CLPoolParametersHelper for bytes32;
Expand Down Expand Up @@ -731,9 +733,11 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps
clFeeManagerHook.setFee(dynamicSwapFee);
vm.expectRevert(
abi.encodeWithSelector(
Hooks.Wrap__FailedHookCall.selector,
CustomRevert.WrappedError.selector,
clFeeManagerHook,
abi.encodeWithSelector(LPFeeLibrary.LPFeeTooLarge.selector, dynamicSwapFee)
ICLHooks.afterInitialize.selector,
abi.encodeWithSelector(LPFeeLibrary.LPFeeTooLarge.selector, dynamicSwapFee),
abi.encodeWithSelector(Hooks.HookCallFailed.selector)
)
);
poolManager.initialize(key, SQRT_RATIO_1_1);
Expand Down

0 comments on commit 82addd8

Please sign in to comment.