Skip to content

Commit

Permalink
feat: gas optimization (#28)
Browse files Browse the repository at this point in the history
* feat: update universal router with new action and position call protection

* test: fix test case

* feat: reduce optimizer runs

* cleanup: remove unused code

* refactor 3: move v3 position manager refactoring to v3tov4migrator

* refactor 4: tweak permit2 call
  • Loading branch information
ChefMist authored Nov 18, 2024
1 parent 9e5a6f3 commit 0dcafa4
Show file tree
Hide file tree
Showing 41 changed files with 96 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
146503
146493
Original file line number Diff line number Diff line change
@@ -1 +1 @@
123561
123551
Original file line number Diff line number Diff line change
@@ -1 +1 @@
138906
138896
Original file line number Diff line number Diff line change
@@ -1 +1 @@
147468
147458
Original file line number Diff line number Diff line change
@@ -1 +1 @@
178629
178619
Original file line number Diff line number Diff line change
@@ -1 +1 @@
149270
149260
Original file line number Diff line number Diff line change
@@ -1 +1 @@
182474
182464
Original file line number Diff line number Diff line change
@@ -1 +1 @@
153645
153635
Original file line number Diff line number Diff line change
@@ -1 +1 @@
159096
159086
Original file line number Diff line number Diff line change
@@ -1 +1 @@
151845
151835
Original file line number Diff line number Diff line change
@@ -1 +1 @@
172182
172172
Original file line number Diff line number Diff line change
@@ -1 +1 @@
174508
174498
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140212
140202
Original file line number Diff line number Diff line change
@@ -1 +1 @@
181622
181612
Original file line number Diff line number Diff line change
@@ -1 +1 @@
246450
246440
Original file line number Diff line number Diff line change
@@ -1 +1 @@
183168
183158
Original file line number Diff line number Diff line change
@@ -1 +1 @@
185957
185947
Original file line number Diff line number Diff line change
@@ -1 +1 @@
250209
250199
Original file line number Diff line number Diff line change
@@ -1 +1 @@
187500
187490
Original file line number Diff line number Diff line change
@@ -1 +1 @@
160352
160342
Original file line number Diff line number Diff line change
@@ -1 +1 @@
100195
100185
Original file line number Diff line number Diff line change
@@ -1 +1 @@
100807
100797
Original file line number Diff line number Diff line change
@@ -1 +1 @@
151937
151904
Original file line number Diff line number Diff line change
@@ -1 +1 @@
154851
154817
Original file line number Diff line number Diff line change
@@ -1 +1 @@
243405
243354
Original file line number Diff line number Diff line change
@@ -1 +1 @@
150632
150606
Original file line number Diff line number Diff line change
@@ -1 +1 @@
254310
254284
Original file line number Diff line number Diff line change
@@ -1 +1 @@
194005
194002
Original file line number Diff line number Diff line change
@@ -1 +1 @@
194073
194070
2 changes: 1 addition & 1 deletion .forge-snapshots/UniversalRouterBytecodeSize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
24411
24246
2 changes: 1 addition & 1 deletion .forge-snapshots/UniversalRouterTest#test_sweep_token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
55456
55435
Original file line number Diff line number Diff line change
@@ -1 +1 @@
561402
561379
Original file line number Diff line number Diff line change
@@ -1 +1 @@
291611
291572
Original file line number Diff line number Diff line change
@@ -1 +1 @@
597077
597025
Original file line number Diff line number Diff line change
@@ -1 +1 @@
572762
572739
Original file line number Diff line number Diff line change
@@ -1 +1 @@
585727
585675
59 changes: 18 additions & 41 deletions src/base/Dispatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {Commands} from "../libraries/Commands.sol";
import {Lock} from "./Lock.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IERC721Permit} from "pancake-v4-periphery/src/pool-cl/interfaces/IERC721Permit.sol";
import {ActionConstants} from "pancake-v4-periphery/src/libraries/ActionConstants.sol";
import {BaseActionsRouter} from "pancake-v4-periphery/src/base/BaseActionsRouter.sol";
import {CalldataDecoder} from "pancake-v4-periphery/src/libraries/CalldataDecoder.sol";
Expand Down Expand Up @@ -120,11 +119,14 @@ abstract contract Dispatcher is
permitBatch := add(inputs.offset, calldataload(inputs.offset))
}
bytes calldata data = inputs.toBytes(1);
try PERMIT2.permit(msgSender(), permitBatch, data) {}
catch (bytes memory reason) {
output = reason;
success = false;
}
(success, output) = address(PERMIT2).call(
abi.encodeWithSignature(
"permit(address,((address,uint160,uint48,uint48)[],address,uint256),bytes)",
msgSender(),
permitBatch,
data
)
);
return (success, output);
} else if (command == Commands.SWEEP) {
// equivalent: abi.decode(inputs, (address, address, uint256))
Expand Down Expand Up @@ -209,11 +211,14 @@ abstract contract Dispatcher is
permitSingle := inputs.offset
}
bytes calldata data = inputs.toBytes(6); // PermitSingle takes first 6 slots (0..5)
try PERMIT2.permit(msgSender(), permitSingle, data) {}
catch (bytes memory reason) {
output = reason;
success = false;
}
(success, output) = address(PERMIT2).call(
abi.encodeWithSignature(
"permit(address,((address,uint160,uint48,uint48),address,uint256),bytes)",
msgSender(),
permitSingle,
data
)
);
return (success, output);
} else if (command == Commands.WRAP_ETH) {
// equivalent: abi.decode(inputs, (address, uint256))
Expand Down Expand Up @@ -270,39 +275,11 @@ abstract contract Dispatcher is
return (success, output);
// This contract MUST be approved to spend the token since its going to be doing the call on the position manager
} else if (command == Commands.V3_POSITION_MANAGER_PERMIT) {
bytes4 selector;
assembly {
selector := calldataload(inputs.offset)
}
if (selector != IERC721Permit.permit.selector) {
revert InvalidAction(selector);
}

_checkV3PermitCall(inputs);
(success, output) = address(V3_POSITION_MANAGER).call(inputs);
return (success, output);
} else if (command == Commands.V3_POSITION_MANAGER_CALL) {
bytes4 selector;
assembly {
selector := calldataload(inputs.offset)
}
if (!isValidAction(selector)) {
revert InvalidAction(selector);
}

uint256 tokenId;
assembly {
// tokenId is always the first parameter in the valid actions
tokenId := calldataload(add(inputs.offset, 0x04))
}

// If any other address that is not the owner wants to call this function, it also needs to be approved (in addition to this contract)
// This can be done in 2 ways:
// 1. This contract is permitted for the specific token and the caller is approved for ALL of the owner's tokens
// 2. This contract is permitted for ALL of the owner's tokens and the caller is permitted for the specific token
if (!isAuthorizedForToken(msgSender(), tokenId)) {
revert NotAuthorizedForToken(tokenId);
}

_checkV3PositionManagerCall(inputs, msgSender());
/// @dev ensure there's follow-up action if v3 position's removed token are sent to router contract
(success, output) = address(V3_POSITION_MANAGER).call(inputs);
return (success, output);
Expand Down
2 changes: 1 addition & 1 deletion src/base/RouterImmutables.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {IV3NonfungiblePositionManager} from
"pancake-v4-periphery/src/interfaces/external/IV3NonfungiblePositionManager.sol";
import {IPositionManager} from "pancake-v4-periphery/src/interfaces/IPositionManager.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {IWETH9} from "../interfaces/IWETH9.sol";
import {IWETH9} from "pancake-v4-periphery/src/interfaces/external/IWETH9.sol";

struct RouterParameters {
// Payment parameters
Expand Down
13 changes: 0 additions & 13 deletions src/interfaces/IWETH9.sol

This file was deleted.

2 changes: 1 addition & 1 deletion src/libraries/Commands.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ library Commands {
uint256 constant V4_BIN_INITIALIZE_POOL = 0x14;
uint256 constant V4_CL_POSITION_CALL = 0x15;
uint256 constant V4_BIN_POSITION_CALL = 0x16;
// COMMAND_PLACEHOLDER = 0x17 -> 0x20
// COMMAND_PLACEHOLDER = 0x15 -> 0x20

// Command Types where 0x21<=value<=0x3f
uint256 constant EXECUTE_SUB_PLAN = 0x21;
Expand Down
43 changes: 40 additions & 3 deletions src/modules/V3ToV4Migrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {IV3NonfungiblePositionManager} from
import {Actions} from "pancake-v4-periphery/src/libraries/Actions.sol";
import {CalldataDecoder} from "pancake-v4-periphery/src/libraries/CalldataDecoder.sol";
import {IPositionManager} from "pancake-v4-periphery/src/interfaces/IPositionManager.sol";
import {console2} from "forge-std/console2.sol";
import {IERC721Permit} from "pancake-v4-periphery/src/pool-cl/interfaces/IERC721Permit.sol";

/// @title V3 to V4 Migrator
/// @notice A contract that migrates liquidity from PancakeSwap V3 to V4
Expand All @@ -20,19 +20,56 @@ abstract contract V3ToV4Migrator is RouterImmutables {
error OnlyAddLiqudityAllowed();

/// @dev validate if an action is decreaseLiquidity, collect, or burn
function isValidAction(bytes4 selector) internal pure returns (bool) {
function _isValidAction(bytes4 selector) private pure returns (bool) {
return selector == IV3NonfungiblePositionManager.decreaseLiquidity.selector
|| selector == IV3NonfungiblePositionManager.collect.selector
|| selector == IV3NonfungiblePositionManager.burn.selector;
}

/// @dev the caller is authorized for the token if its the owner, spender, or operator
function isAuthorizedForToken(address caller, uint256 tokenId) internal view returns (bool) {
function _isAuthorizedForToken(address caller, uint256 tokenId) private view returns (bool) {
address owner = V3_POSITION_MANAGER.ownerOf(tokenId);
return caller == owner || V3_POSITION_MANAGER.getApproved(tokenId) == caller
|| V3_POSITION_MANAGER.isApprovedForAll(owner, caller);
}

/// @dev check that a call is to the ERC721 permit function
function _checkV3PermitCall(bytes calldata inputs) internal pure {
bytes4 selector;
assembly {
selector := calldataload(inputs.offset)
}

if (selector != IERC721Permit.permit.selector) {
revert InvalidAction(selector);
}
}

/// @dev check that the v3 position manager call is a safe call
function _checkV3PositionManagerCall(bytes calldata inputs, address caller) internal view {
bytes4 selector;
assembly {
selector := calldataload(inputs.offset)
}

if (!_isValidAction(selector)) {
revert InvalidAction(selector);
}

uint256 tokenId;
assembly {
// tokenId is always the first parameter in the valid actions
tokenId := calldataload(add(inputs.offset, 0x04))
}
// If any other address that is not the owner wants to call this function, it also needs to be approved (in addition to this contract)
// This can be done in 2 ways:
// 1. This contract is permitted for the specific token and the caller is approved for ALL of the owner's tokens
// 2. This contract is permitted for ALL of the owner's tokens and the caller is permitted for the specific token
if (!_isAuthorizedForToken(caller, tokenId)) {
revert NotAuthorizedForToken(tokenId);
}
}

/// @dev check that the v4 position manager call is a safe call
/// of the position-altering Actions, we only allow Actions.MINT
/// this is because, if a user could be tricked into approving the UniversalRouter for
Expand Down

0 comments on commit 0dcafa4

Please sign in to comment.