diff --git a/contracts/base/Dispatcher.sol b/contracts/base/Dispatcher.sol index 37730d60..6af9a075 100644 --- a/contracts/base/Dispatcher.sol +++ b/contracts/base/Dispatcher.sol @@ -107,7 +107,14 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, V4SwapRout permitBatch := add(inputs.offset, calldataload(inputs.offset)) } bytes calldata data = inputs.toBytes(1); - PERMIT2.permit(msgSender(), permitBatch, data); + (success, output) = address(PERMIT2).call( + abi.encodeWithSignature( + 'permit(address,((address,uint160,uint48,uint48)[],address,uint256),bytes)', + msgSender(), + permitBatch, + data + ) + ); } else if (command == Commands.SWEEP) { // equivalent: abi.decode(inputs, (address, address, uint256)) address token; @@ -186,7 +193,14 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, V4SwapRout permitSingle := inputs.offset } bytes calldata data = inputs.toBytes(6); // PermitSingle takes first 6 slots (0..5) - PERMIT2.permit(msgSender(), permitSingle, data); + (success, output) = address(PERMIT2).call( + abi.encodeWithSignature( + 'permit(address,((address,uint160,uint48,uint48),address,uint256),bytes)', + msgSender(), + permitSingle, + data + ) + ); } else if (command == Commands.WRAP_ETH) { // equivalent: abi.decode(inputs, (address, uint256)) address recipient; diff --git a/test/integration-tests/UniswapMixed.test.ts b/test/integration-tests/UniswapMixed.test.ts index 0c7e6681..2780684d 100644 --- a/test/integration-tests/UniswapMixed.test.ts +++ b/test/integration-tests/UniswapMixed.test.ts @@ -279,6 +279,39 @@ describe('Uniswap V2, V3, and V4 Tests:', () => { expect(wethBalanceAfter.sub(wethBalanceBefore)).to.be.gte(minAmountOut1.add(minAmountOut2)) }) + it('PERMIT2 batch can silently fail', async () => { + const v2AmountIn1: BigNumber = expandTo18DecimalsBN(20) + const v2AmountIn2: BigNumber = expandTo18DecimalsBN(5) + + const BATCH_PERMIT = { + details: [ + { + token: DAI.address, + amount: v2AmountIn1, + expiration: 0, // expiration of 0 is block.timestamp + nonce: 0, // this is his first trade + }, + { + token: WETH.address, + amount: v2AmountIn2, + expiration: 0, // expiration of 0 is block.timestamp + nonce: 0, // this is his first trade + }, + ], + spender: router.address, + sigDeadline: DEADLINE, + } + + const sig = await getPermitBatchSignature(BATCH_PERMIT, bob, permit2) + + // transfer funds into DAI-USDC and DAI-USDT pairs to trade + planner.addCommand(CommandType.PERMIT2_PERMIT_BATCH, [BATCH_PERMIT, sig]) + + planner.addCommand(CommandType.PERMIT2_PERMIT_BATCH_NO_REVERT, [BATCH_PERMIT, sig]) + + await executeRouter(planner, bob, router, wethContract, daiContract, usdcContract) + }) + it('ERC20 --> ERC20 split V2 and V2 different routes, different input tokens, each two hop, with batch permit', async () => { const route1 = [DAI.address, WETH.address, USDC.address] const route2 = [WETH.address, DAI.address, USDC.address] diff --git a/test/integration-tests/UniswapV2.test.ts b/test/integration-tests/UniswapV2.test.ts index 7ecd34c3..d4a18efe 100644 --- a/test/integration-tests/UniswapV2.test.ts +++ b/test/integration-tests/UniswapV2.test.ts @@ -20,7 +20,7 @@ import { import { expandTo18DecimalsBN, expandTo6DecimalsBN } from './shared/helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import deployUniversalRouter from './shared/deployUniversalRouter' -import { RoutePlanner, CommandType } from './shared/planner' +import { RoutePlanner, CommandType, ALLOW_REVERT_FLAG } from './shared/planner' import hre from 'hardhat' import { executeRouter } from './shared/executeRouter' import { getPermitSignature, PermitSingle } from './shared/protocolHelpers/permit2' @@ -77,6 +77,31 @@ describe('Uniswap V2 Tests:', () => { await permit2.approve(DAI.address, ADDRESS_ZERO, 0, 0) }) + it('Permit2 can silently fail', async () => { + const amountInDAI = expandTo18DecimalsBN(100) + + // bob signs a permit to allow the router to access his DAI + permit = { + details: { + token: DAI.address, + amount: amountInDAI, + expiration: 0, // expiration of 0 is block.timestamp + nonce: 0, // this is his first trade + }, + spender: router.address, + sigDeadline: DEADLINE, + } + const sig = await getPermitSignature(permit, bob, permit2) + + // 1) permit the router to access funds, not allowing revert + planner.addCommand(CommandType.PERMIT2_PERMIT, [permit, sig]) + + // 2) permit the router to access funds again, allowing revert + planner.addCommand(CommandType.PERMIT2_NO_REVERT, [permit, sig]) + + await executeRouter(planner, bob, router, wethContract, daiContract, usdcContract) + }) + it('V2 exactIn, permiting the exact amount', async () => { const amountInDAI = expandTo18DecimalsBN(100) const minAmountOutWETH = expandTo18DecimalsBN(0.02) diff --git a/test/integration-tests/gas-tests/__snapshots__/CheckOwnership.gas.test.ts.snap b/test/integration-tests/gas-tests/__snapshots__/CheckOwnership.gas.test.ts.snap index 6cdf8ef0..bf0cb358 100644 --- a/test/integration-tests/gas-tests/__snapshots__/CheckOwnership.gas.test.ts.snap +++ b/test/integration-tests/gas-tests/__snapshots__/CheckOwnership.gas.test.ts.snap @@ -3,6 +3,6 @@ exports[`Check Ownership Gas gas: balance check ERC20 1`] = ` Object { "calldataByteLength": 356, - "gasUsed": 37698, + "gasUsed": 37740, } `; diff --git a/test/integration-tests/gas-tests/__snapshots__/Payments.gas.test.ts.snap b/test/integration-tests/gas-tests/__snapshots__/Payments.gas.test.ts.snap index 6d5cb6df..0007e23c 100644 --- a/test/integration-tests/gas-tests/__snapshots__/Payments.gas.test.ts.snap +++ b/test/integration-tests/gas-tests/__snapshots__/Payments.gas.test.ts.snap @@ -3,48 +3,48 @@ exports[`Payments Gas Tests Individual Command Tests gas: SWEEP with ERC20 1`] = ` Object { "calldataByteLength": 356, - "gasUsed": 37012, + "gasUsed": 37042, } `; exports[`Payments Gas Tests Individual Command Tests gas: SWEEP_WITH_FEE 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 65726, + "gasUsed": 65786, } `; exports[`Payments Gas Tests Individual Command Tests gas: TRANSFER with ERC20 1`] = ` Object { "calldataByteLength": 356, - "gasUsed": 36040, + "gasUsed": 36070, } `; exports[`Payments Gas Tests Individual Command Tests gas: TRANSFER with ETH 1`] = ` Object { "calldataByteLength": 356, - "gasUsed": 31605, + "gasUsed": 31635, } `; exports[`Payments Gas Tests Individual Command Tests gas: UNWRAP_WETH 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 44579, + "gasUsed": 44621, } `; exports[`Payments Gas Tests Individual Command Tests gas: UNWRAP_WETH_WITH_FEE 1`] = ` Object { "calldataByteLength": 644, - "gasUsed": 51006, + "gasUsed": 51108, } `; exports[`Payments Gas Tests Individual Command Tests gas: WRAP_ETH 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 53389, + "gasUsed": 53423, } `; diff --git a/test/integration-tests/gas-tests/__snapshots__/Uniswap.gas.test.ts.snap b/test/integration-tests/gas-tests/__snapshots__/Uniswap.gas.test.ts.snap index b23cde60..e9994006 100644 --- a/test/integration-tests/gas-tests/__snapshots__/Uniswap.gas.test.ts.snap +++ b/test/integration-tests/gas-tests/__snapshots__/Uniswap.gas.test.ts.snap @@ -3,63 +3,63 @@ exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Batch reverts gas: 2 sub-plans, both fail but the transaction succeeds 1`] = ` Object { "calldataByteLength": 1764, - "gasUsed": 270118, + "gasUsed": 270192, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Batch reverts gas: 2 sub-plans, neither fails 1`] = ` Object { "calldataByteLength": 1764, - "gasUsed": 245841, + "gasUsed": 245915, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Batch reverts gas: 2 sub-plans, second sub plan fails 1`] = ` Object { "calldataByteLength": 1764, - "gasUsed": 245841, + "gasUsed": 245915, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Batch reverts gas: 2 sub-plans, the first fails 1`] = ` Object { "calldataByteLength": 1764, - "gasUsed": 270118, + "gasUsed": 270192, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Interleaving routes gas: V2, then V3 1`] = ` Object { "calldataByteLength": 836, - "gasUsed": 189511, + "gasUsed": 189533, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Interleaving routes gas: V3, then V2 1`] = ` Object { "calldataByteLength": 836, - "gasUsed": 177094, + "gasUsed": 177116, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Split routes gas: ERC20 --> ERC20 split V2 and V2 different routes, different input tokens, each two hop, with batch permit 1`] = ` Object { "calldataByteLength": 1540, - "gasUsed": 297208, + "gasUsed": 297144, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Split routes gas: ERC20 --> ERC20 split V2 and V2 different routes, each two hop, with explicit permit 1`] = ` Object { "calldataByteLength": 1220, - "gasUsed": 308080, + "gasUsed": 308074, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Split routes gas: ERC20 --> ERC20 split V2 and V2 different routes, each two hop, with explicit permit transfer from batch 1`] = ` Object { "calldataByteLength": 1284, - "gasUsed": 309182, + "gasUsed": 309224, } `; @@ -73,35 +73,35 @@ Object { exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Split routes gas: ERC20 --> ERC20 split V2 and V3, one hop 1`] = ` Object { "calldataByteLength": 996, - "gasUsed": 176938, + "gasUsed": 176990, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Split routes gas: ERC20 --> ERC20 split V2 and V3, one hop, ADDRESS_THIS flag 1`] = ` Object { "calldataByteLength": 996, - "gasUsed": 176713, + "gasUsed": 176765, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Split routes gas: ERC20 --> ETH split V2 and V3, exactOut, one hop 1`] = ` Object { "calldataByteLength": 964, - "gasUsed": 192148, + "gasUsed": 192212, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Split routes gas: ERC20 --> ETH split V2 and V3, one hop 1`] = ` Object { "calldataByteLength": 964, - "gasUsed": 184812, + "gasUsed": 184876, } `; exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Split routes gas: ETH --> ERC20 split V2 and V3, one hop 1`] = ` Object { "calldataByteLength": 1124, - "gasUsed": 191854, + "gasUsed": 191948, } `; @@ -143,7 +143,7 @@ Object { exports[`Uniswap Gas Tests Trade on UniswapV2 with Universal Router. ERC20 --> ERC20 gas: exactIn trade, where an output fee is taken 1`] = ` Object { "calldataByteLength": 836, - "gasUsed": 126705, + "gasUsed": 126765, } `; @@ -206,35 +206,35 @@ Object { exports[`Uniswap Gas Tests Trade on UniswapV2 with Universal Router. ERC20 --> ETH gas: exactIn, one trade, one hop 1`] = ` Object { "calldataByteLength": 644, - "gasUsed": 123152, + "gasUsed": 123194, } `; exports[`Uniswap Gas Tests Trade on UniswapV2 with Universal Router. ERC20 --> ETH gas: exactOut, one trade, one hop 1`] = ` Object { "calldataByteLength": 804, - "gasUsed": 127990, + "gasUsed": 128062, } `; exports[`Uniswap Gas Tests Trade on UniswapV2 with Universal Router. ERC20 --> ETH gas: exactOut, with ETH fee 1`] = ` Object { "calldataByteLength": 964, - "gasUsed": 136008, + "gasUsed": 136110, } `; exports[`Uniswap Gas Tests Trade on UniswapV2 with Universal Router. ETH --> ERC20 gas: exactIn, one trade, one hop 1`] = ` Object { "calldataByteLength": 644, - "gasUsed": 106745, + "gasUsed": 106787, } `; exports[`Uniswap Gas Tests Trade on UniswapV2 with Universal Router. ETH --> ERC20 gas: exactOut, one trade, one hop 1`] = ` Object { "calldataByteLength": 772, - "gasUsed": 125179, + "gasUsed": 125263, } `; @@ -283,69 +283,69 @@ Object { exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ERC20 --> ERC20 gas: exactIn, one trade, one hop 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 105477, + "gasUsed": 105499, } `; exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ERC20 --> ERC20 gas: exactIn, one trade, three hops 1`] = ` Object { "calldataByteLength": 548, - "gasUsed": 253814, + "gasUsed": 253880, } `; exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ERC20 --> ERC20 gas: exactIn, one trade, two hops 1`] = ` Object { "calldataByteLength": 548, - "gasUsed": 177031, + "gasUsed": 177075, } `; exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ERC20 --> ERC20 gas: exactOut, one trade, one hop 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 112912, + "gasUsed": 112934, } `; exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ERC20 --> ERC20 gas: exactOut, one trade, three hops 1`] = ` Object { "calldataByteLength": 548, - "gasUsed": 248860, + "gasUsed": 248926, } `; exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ERC20 --> ERC20 gas: exactOut, one trade, two hops 1`] = ` Object { "calldataByteLength": 548, - "gasUsed": 172590, + "gasUsed": 172634, } `; exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ERC20 --> ETH gas: exactIn swap 1`] = ` Object { "calldataByteLength": 644, - "gasUsed": 121807, + "gasUsed": 121871, } `; exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ERC20 --> ETH gas: exactOut swap 1`] = ` Object { "calldataByteLength": 644, - "gasUsed": 129314, + "gasUsed": 129378, } `; exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ETH --> ERC20 gas: exactIn swap 1`] = ` Object { "calldataByteLength": 644, - "gasUsed": 215359, + "gasUsed": 215423, } `; exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ETH --> ERC20 gas: exactOut swap 1`] = ` Object { "calldataByteLength": 772, - "gasUsed": 124495, + "gasUsed": 124601, } `; diff --git a/test/integration-tests/gas-tests/__snapshots__/UniversalRouter.gas.test.ts.snap b/test/integration-tests/gas-tests/__snapshots__/UniversalRouter.gas.test.ts.snap index 535edaa9..c5f4372c 100644 --- a/test/integration-tests/gas-tests/__snapshots__/UniversalRouter.gas.test.ts.snap +++ b/test/integration-tests/gas-tests/__snapshots__/UniversalRouter.gas.test.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UniversalRouter Gas Tests gas: bytecode size 1`] = `19173`; +exports[`UniversalRouter Gas Tests gas: bytecode size 1`] = `19129`; diff --git a/test/integration-tests/gas-tests/__snapshots__/UniversalVSSwapRouter.gas.test.ts.snap b/test/integration-tests/gas-tests/__snapshots__/UniversalVSSwapRouter.gas.test.ts.snap index ad0a15e7..348ca7d0 100644 --- a/test/integration-tests/gas-tests/__snapshots__/UniversalVSSwapRouter.gas.test.ts.snap +++ b/test/integration-tests/gas-tests/__snapshots__/UniversalVSSwapRouter.gas.test.ts.snap @@ -7,32 +7,32 @@ Object { } `; -exports[`Uniswap UX Tests gas: Comparisons Casual Swapper - 3 swaps Permit2 Max Approval Swap 1`] = `1104136`; +exports[`Uniswap UX Tests gas: Comparisons Casual Swapper - 3 swaps Permit2 Max Approval Swap 1`] = `1104332`; -exports[`Uniswap UX Tests gas: Comparisons Casual Swapper - 3 swaps Permit2 Sign Per Swap 1`] = `1138498`; +exports[`Uniswap UX Tests gas: Comparisons Casual Swapper - 3 swaps Permit2 Sign Per Swap 1`] = `1138643`; exports[`Uniswap UX Tests gas: Comparisons Casual Swapper - 3 swaps SwapRouter02 1`] = `1124979`; -exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper - 10 swaps Permit2 Max Approval Swap 1`] = `3081019`; +exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper - 10 swaps Permit2 Max Approval Swap 1`] = `3081655`; -exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper - 10 swaps Permit2 Sign Per Swap 1`] = `3234344`; +exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper - 10 swaps Permit2 Sign Per Swap 1`] = `3234749`; exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper - 10 swaps SwapRouter02 1`] = `3195011`; -exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper across 3 swap router versions - 15 swaps across 3 versions Permit2 Max Approval Swap 1`] = `4102561`; +exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper across 3 swap router versions - 15 swaps across 3 versions Permit2 Max Approval Swap 1`] = `4103363`; -exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper across 3 swap router versions - 15 swaps across 3 versions Permit2 Sign Per Swap 1`] = `4306287`; +exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper across 3 swap router versions - 15 swaps across 3 versions Permit2 Sign Per Swap 1`] = `4306777`; exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper across 3 swap router versions - 15 swaps across 3 versions SwapRouter02 1`] = `4282374`; -exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Complex Swap Permit2 Max Approval Swap 1`] = `508748`; +exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Complex Swap Permit2 Max Approval Swap 1`] = `508812`; -exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Complex Swap Permit2 Sign Per Swap 1`] = `509066`; +exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Complex Swap Permit2 Sign Per Swap 1`] = `509130`; exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Complex Swap SwapRouter02 1`] = `500008`; -exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Simple Swap Permit2 Max Approval Swap 1`] = `299568`; +exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Simple Swap Permit2 Max Approval Swap 1`] = `299585`; -exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Simple Swap Permit2 Sign Per Swap 1`] = `299504`; +exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Simple Swap Permit2 Sign Per Swap 1`] = `299521`; exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Simple Swap SwapRouter02 1`] = `270033`; diff --git a/test/integration-tests/gas-tests/__snapshots__/V3ToV4Migration.gas.test.ts.snap b/test/integration-tests/gas-tests/__snapshots__/V3ToV4Migration.gas.test.ts.snap index 00d7c729..49b8bd52 100644 --- a/test/integration-tests/gas-tests/__snapshots__/V3ToV4Migration.gas.test.ts.snap +++ b/test/integration-tests/gas-tests/__snapshots__/V3ToV4Migration.gas.test.ts.snap @@ -52,7 +52,7 @@ Object { exports[`V3 to V4 Migration Gas Tests V4 Commands mint gas: migrate weth position into eth position with forwarding 1`] = ` Object { "calldataByteLength": 2788, - "gasUsed": 595274, + "gasUsed": 595332, } `; diff --git a/test/integration-tests/shared/planner.ts b/test/integration-tests/shared/planner.ts index 19aac888..3bcbd131 100644 --- a/test/integration-tests/shared/planner.ts +++ b/test/integration-tests/shared/planner.ts @@ -28,6 +28,9 @@ export enum CommandType { V4_POSITION_MANAGER_CALL = 0x13, EXECUTE_SUB_PLAN = 0x21, + + PERMIT2_NO_REVERT = 0x8a, // ALLOW_FLAG_REVERT | PERMIT2_PERMIT + PERMIT2_PERMIT_BATCH_NO_REVERT = 0x83, // ALLOW_FLAG_REVERT | PERMIT2_PERMIT_BATCH } const ALLOW_REVERT_FLAG = 0x80 @@ -71,6 +74,9 @@ const ABI_DEFINITION: { [key in CommandType]: string[] } = { [CommandType.V3_POSITION_MANAGER_PERMIT]: ['bytes'], [CommandType.V3_POSITION_MANAGER_CALL]: ['bytes'], [CommandType.V4_POSITION_MANAGER_CALL]: ['bytes'], + + [CommandType.PERMIT2_NO_REVERT]: [PERMIT_STRUCT, 'bytes'], + [CommandType.PERMIT2_PERMIT_BATCH_NO_REVERT]: [PERMIT_BATCH_STRUCT, 'bytes'], } export class RoutePlanner {