From 4f9785fdacb309feb689abc874f458eb06540a1b Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Fri, 18 Apr 2025 16:23:03 -0400 Subject: [PATCH 1/3] fix: Configurable fee on USV2 executor. - This fee is not the same depending on the fork. For example, Pancake V2 uses a 0.25% fee, while the USV2 fee is 0.3%. We were wrongly hard-coding this fee to 0.3%. --- foundry/scripts/deploy-executors.js | 35 +++++++++++-------- foundry/src/executors/UniswapV2Executor.sol | 12 +++++-- foundry/test/TychoRouterTestSetup.sol | 2 +- .../test/executors/UniswapV2Executor.t.sol | 12 ++++--- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/foundry/scripts/deploy-executors.js b/foundry/scripts/deploy-executors.js index 7149d63..9aa242f 100644 --- a/foundry/scripts/deploy-executors.js +++ b/foundry/scripts/deploy-executors.js @@ -5,25 +5,28 @@ const hre = require("hardhat"); // Comment out the executors you don't want to deploy const executors_to_deploy = { "ethereum": [ - // USV2 - Args: Factory, Pool Init Code Hash + // USV2 - Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", - "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" + "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f", + 30 ] }, - // SUSHISWAP - Args: Factory, Pool Init Code Hash + // SUSHISWAP - Args: Factory, Pool Init Code Hash, Fee BPS, Fee BPS { exchange: "UniswapV2Executor", args: [ "0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac", - "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303" + "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303", + 30 ] }, - // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash + // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x1097053Fd2ea711dad45caCcc45EfF7548fCB362", - "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d" + "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d", + 25 ] }, // USV3 -Args: Factory, Pool Init Code Hash @@ -57,25 +60,28 @@ const executors_to_deploy = { } ], "base": [ - // Args: Factory, Pool Init Code Hash + // Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6", - "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" + "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f", + 30 ] }, - // SUSHISWAP V2 - Args: Factory, Pool Init Code Hash + // SUSHISWAP V2 - Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x71524B4f93c58fcbF659783284E38825f0622859", - "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303" + "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303", + 30 ] }, - // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash + // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x02a84c1b3BBD7401a5f7fa98a384EBC70bB5749E", - "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d" + "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d", + 25 ] }, // USV3 - Args: Factory, Pool Init Code Hash @@ -97,11 +103,12 @@ const executors_to_deploy = { {exchange: "BalancerV2Executor", args: []}, ], "unichain": [ - // Args: Factory, Pool Init Code Hash + // Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x1f98400000000000000000000000000000000002", - "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" + "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f", + 30 ] }, // USV3 - Args: Factory, Pool Init Code Hash diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 863216d..ce172a7 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -10,6 +10,7 @@ error UniswapV2Executor__InvalidDataLength(); error UniswapV2Executor__InvalidTarget(); error UniswapV2Executor__InvalidFactory(); error UniswapV2Executor__InvalidInitCode(); +error UniswapV2Executor__InvalidFee(); contract UniswapV2Executor is IExecutor, TokenTransfer { using SafeERC20 for IERC20; @@ -17,8 +18,9 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { address public immutable factory; bytes32 public immutable initCode; address private immutable self; + uint256 public immutable feeBps; - constructor(address _factory, bytes32 _initCode, address _permit2) + constructor(address _factory, bytes32 _initCode, address _permit2, uint256 _feeBps) TokenTransfer(_permit2) { if (_factory == address(0)) { @@ -29,6 +31,10 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { } factory = _factory; initCode = _initCode; + if (_feeBps > 10000) { + revert UniswapV2Executor__InvalidFee(); + } + feeBps = _feeBps; self = address(this); } @@ -100,9 +106,9 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { } require(reserveIn > 0 && reserveOut > 0, "L"); - uint256 amountInWithFee = amountIn * 997; + uint256 amountInWithFee = amountIn * (10000 - feeBps); uint256 numerator = amountInWithFee * uint256(reserveOut); - uint256 denominator = (uint256(reserveIn) * 1000) + amountInWithFee; + uint256 denominator = (uint256(reserveIn) * 10000) + amountInWithFee; amount = numerator / denominator; } diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index c97cb56..0252c2b 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -99,7 +99,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { IPoolManager poolManager = IPoolManager(poolManagerAddress); usv2Executor = - new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS); + new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS, 30); usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3, PERMIT2_ADDRESS); usv4Executor = new UniswapV4Executor(poolManager, PERMIT2_ADDRESS); diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 64be1d2..70f16c8 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -8,8 +8,8 @@ import {Constants} from "../Constants.sol"; import {Permit2TestHelper} from "../Permit2TestHelper.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { - constructor(address _factory, bytes32 _initCode, address _permit2) - UniswapV2Executor(_factory, _initCode, _permit2) + constructor(address _factory, bytes32 _initCode, address _permit2, uint256 _feeBps) + UniswapV2Executor(_factory, _initCode, _permit2, _feeBps) {} function decodeParams(bytes calldata data) @@ -63,17 +63,19 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { uint256 forkBlock = 17323404; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); uniswapV2Exposed = new UniswapV2ExecutorExposed( - USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS + USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS, 30 ); sushiswapV2Exposed = new UniswapV2ExecutorExposed( SUSHISWAPV2_FACTORY_ETHEREUM, SUSHIV2_POOL_CODE_INIT_HASH, - PERMIT2_ADDRESS + PERMIT2_ADDRESS, + 30 ); pancakeswapV2Exposed = new UniswapV2ExecutorExposed( PANCAKESWAPV2_FACTORY_ETHEREUM, PANCAKEV2_POOL_CODE_INIT_HASH, - PERMIT2_ADDRESS + PERMIT2_ADDRESS, + 25 ); permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } From af68016223cb74b4896ae627be798e6d211ef4e6 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Tue, 22 Apr 2025 17:45:56 -0400 Subject: [PATCH 2/3] fix: Tighten max feeBps in USV2 executor Value was too lenient. We are assuming no forks will have higher fees than the original USV2. --- foundry/src/executors/UniswapV2Executor.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index ce172a7..518b761 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -31,7 +31,7 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { } factory = _factory; initCode = _initCode; - if (_feeBps > 10000) { + if (_feeBps > 30) { revert UniswapV2Executor__InvalidFee(); } feeBps = _feeBps; From d5d6e37041c316c460a3cf216c71eae1987a953f Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Tue, 22 Apr 2025 17:50:09 -0400 Subject: [PATCH 3/3] fix: Changes after rebase --- foundry/src/executors/UniswapV2Executor.sol | 9 ++++++--- foundry/test/executors/UniswapV2Executor.t.sol | 13 ++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 518b761..dd04dd1 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -20,9 +20,12 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { address private immutable self; uint256 public immutable feeBps; - constructor(address _factory, bytes32 _initCode, address _permit2, uint256 _feeBps) - TokenTransfer(_permit2) - { + constructor( + address _factory, + bytes32 _initCode, + address _permit2, + uint256 _feeBps + ) TokenTransfer(_permit2) { if (_factory == address(0)) { revert UniswapV2Executor__InvalidFactory(); } diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 70f16c8..ea0d825 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -8,9 +8,12 @@ import {Constants} from "../Constants.sol"; import {Permit2TestHelper} from "../Permit2TestHelper.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { - constructor(address _factory, bytes32 _initCode, address _permit2, uint256 _feeBps) - UniswapV2Executor(_factory, _initCode, _permit2, _feeBps) - {} + constructor( + address _factory, + bytes32 _initCode, + address _permit2, + uint256 _feeBps + ) UniswapV2Executor(_factory, _initCode, _permit2, _feeBps) {} function decodeParams(bytes calldata data) external @@ -69,13 +72,13 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { SUSHISWAPV2_FACTORY_ETHEREUM, SUSHIV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS, - 30 + 30 ); pancakeswapV2Exposed = new UniswapV2ExecutorExposed( PANCAKESWAPV2_FACTORY_ETHEREUM, PANCAKEV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS, - 25 + 25 ); permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); }