diff --git a/CHANGELOG.md b/CHANGELOG.md index c3d8003..c305037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [0.56.0](https://github.com/propeller-heads/tycho-execution/compare/0.55.0...0.56.0) (2025-02-28) + + +### Features + +* update base executor addresses ([bc47c12](https://github.com/propeller-heads/tycho-execution/commit/bc47c12a1a8cae0b9464b4899b52369e5036c9f7)) + + +### Bug Fixes + +* make USV2 factory configurable in Executor ([33973a6](https://github.com/propeller-heads/tycho-execution/commit/33973a65b8486c2c78e68c6a35374bd35775a7e5)) + ## [0.55.0](https://github.com/propeller-heads/tycho-execution/compare/0.54.0...0.55.0) (2025-02-27) diff --git a/Cargo.lock b/Cargo.lock index 4706058..5bbf4c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4340,7 +4340,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.55.0" +version = "0.56.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 1245477..d542afa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.55.0" +version = "0.56.0" edition = "2021" [[bin]] diff --git a/README.md b/README.md index 1c0dbd2..5abdbea 100644 --- a/README.md +++ b/README.md @@ -6,110 +6,9 @@ Tycho Execution makes it easy to trade on different DEXs by handling the complex custom code for each DEX, you get a simple, ready-to-use tool that generates the necessary data to execute trades. It's designed to be safe, straightforward, and quick to set up, so anyone can start trading without extra effort. -## Quickstart +For complete documentation, see Tycho docs [here](https://docs.propellerheads.xyz/tycho/for-solvers/execution). -To get started, have a look at our [Quickstart example](examples/quickstart/README.md). +## Examples -## Bin Usage Guide - -### Installation - -First, build and install the binary: - -```bash -# Build the project -cargo build --release - -# Install the binary to your system -cargo install --path . -``` - -After installation, the `tycho-encode` command will be available to use from any directory in your terminal. - -### Commands - -The command lets you choose the encoding strategy to be used. The available strategies are: - -#### Tycho Router - -`tycho-router`: Encodes a transaction using the Tycho Router encoding strategy. - -Example: - -```bash -echo '' | tycho-encode tycho-router -``` - -#### Tycho Router With Permit2 - -`tycho-router-with-permit2`: Encodes a transaction using the Tycho Router encoding strategy. Requires a private key for -signing -Permit2. - -Example: - -```bash -echo '' | tycho-encode tycho-router --swapper-pk 0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234 -``` - -#### Direct execution - -`direct-execution`: Encodes a transaction using the direct execution encoding strategy. Does not require a private key. - -Example: - -```bash -echo '' | tycho-encode direct-execution -``` - -### Encoding Transactions - -The commands accept the following options: - -- `--config_path`: Path to the executor addresses configuration file (defaults - to `src/encoding/config/executor_addresses.json`) -- `--swapper-pk`: Private key for signing approvals (required when direct_execution is false) - -#### Example - -Here's a complete example that encodes a swap from WETH to DAI using Uniswap V2 and the Tycho Router strategy: - -```bash -echo '{"sender":"0x1234567890123456789012345678901234567890","receiver":"0x1234567890123456789012345678901234567890","given_token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","given_amount":"1000000000000000000","checked_token":"0x6B175474E89094C44Da98b954EedeAC495271d0F","exact_out":false,"slippage":0.01,"expected_amount":"1000000000000000000","checked_amount":"990000000000000000","router_address":"0xaa820C29648D5EA543d712cC928377Bd7206a0E7","swaps":[{"component":{"id":"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640","protocol_system":"uniswap_v2","protocol_type_name":"UniswapV2Pool","chain":"ethereum","tokens":["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"],"contract_ids":["0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"],"static_attributes":{"factory":"0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f"},"change":"Update","creation_tx":"0x0000000000000000000000000000000000000000000000000000000000000000","created_at":"2024-02-28T12:00:00"},"token_in":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","token_out":"0x6B175474E89094C44Da98b954EedeAC495271d0F","split":0.0}],"direct_execution":true}' | tycho-encode tycho-router-permit2 --swapper-pk 0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234 -``` - -#### JSON Payload Structure: Solution struct - -The `Solution` struct is composed of the following fields: - -- `sender`: The address initiating the transaction -- `receiver`: The address receiving the output tokens -- `given_token`: The address of the input token (e.g., WETH) -- `given_amount`: The amount of input tokens (in wei) -- `checked_token`: The address of the output token (e.g., DAI) -- `exact_out`: Boolean indicating if this is an exact output swap -- `slippage`: The maximum allowed slippage (e.g., 0.01 for 1%) -- `expected_amount`: The expected output amount -- `checked_amount`: The minimum acceptable output amount (accounting for slippage) -- `swaps`: Array of swap steps, each containing: - - `component`: Details about the DEX/protocol being used - - `token_in`: Input token address for this step - - `token_out`: Output token address for this step - - `split`: Proportion of tokens to route through this step (1.0 = 100%) -- `router_address`: The address of the protocol's router contract -- `direct_execution`: Boolean indicating if the transaction should be executed directly - -## Contract Analysis - -We use [Slither](https://github.com/crytic/slither) to detect any potential vulnerabilities in our contracts. - -To run locally, simply install Slither in your conda env and run it inside the foundry directory. - -```bash -conda create --name tycho-execution python=3.10 -conda activate tycho-execution - -pip install slither-analyzer -cd foundry -slither . -``` \ No newline at end of file +To get started on encoding, have a look at our [Encoding example](examples/encoding-example/README.md). +For a complete example please refer to the [Tycho Quickstart guide](https://docs.propellerheads.xyz/tycho). diff --git a/config/executor_addresses.json b/config/executor_addresses.json index 53b0161..83b3fab 100644 --- a/config/executor_addresses.json +++ b/config/executor_addresses.json @@ -12,7 +12,10 @@ "vm:balancer_v2": "0x543778987b293C7E8Cf0722BB2e935ba6f4068D4" }, "base": { - "uniswap_v2": "0x2DaE954eCF457276F90B68Cee68981C0aA07f2ef" + "uniswap_v2": "0xa2D6c55676D0d2A1D090e3157aCC9a41bB53BE3B", + "uniswap_v3": "0x374fFd7224422dF5dE83B43C7Fa8379F9CAA826a", + "uniswap_v4": "0xf1531642018C9Ab6a1B07666755d975eae16a01b", + "vm:balancer_v2": "0x6920209CaAF45872006eC507f2f18595af030418" }, "tenderly_base": { "uniswap_v3": "0x7c7E06d7317e620a185078e236879D2a87fC8d22" diff --git a/examples/quickstart/README.md b/examples/encoding-example/README.md similarity index 56% rename from examples/quickstart/README.md rename to examples/encoding-example/README.md index 0978ffd..02af49e 100644 --- a/examples/quickstart/README.md +++ b/examples/encoding-example/README.md @@ -1,6 +1,6 @@ -# QuickStart +# Encoding example -This quickstart guide enables you to: +This guide enables you to: 1. Create a Solution object 2. Encode the solution to interact with the Ethereum blockchain @@ -8,5 +8,5 @@ This quickstart guide enables you to: ## How to run ```bash -cargo run --release --example quickstart +cargo run --release --example encoding-example ``` \ No newline at end of file diff --git a/examples/quickstart/main.rs b/examples/encoding-example/main.rs similarity index 100% rename from examples/quickstart/main.rs rename to examples/encoding-example/main.rs diff --git a/foundry/scripts/deploy-executors.js b/foundry/scripts/deploy-executors.js index e04acef..f3c8217 100644 --- a/foundry/scripts/deploy-executors.js +++ b/foundry/scripts/deploy-executors.js @@ -3,12 +3,21 @@ const {ethers} = require("hardhat"); const hre = require("hardhat"); // Comment out the executors you don't want to deploy -const executors_to_deploy = [ - {exchange: "UniswapV2Executor", args: []}, +const executors_to_deploy = { + "ethereum":[ + {exchange: "UniswapV2Executor", args: ["0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"]}, {exchange: "UniswapV3Executor", args: ["0x1F98431c8aD98523631AE4a59f267346ea31F984"]}, {exchange: "UniswapV4Executor", args: ["0x000000000004444c5dc75cB358380D2e3dE08A90"]}, {exchange: "BalancerV2Executor", args: []}, -] + ], + "base":[ + {exchange: "UniswapV2Executor", args: ["0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6"]}, + {exchange: "UniswapV3Executor", args: ["0x33128a8fC17869897dcE68Ed026d694621f6FDfD"]}, + {exchange: "UniswapV4Executor", args: ["0x498581ff718922c3f8e6a244956af099b2652b2b"]}, + {exchange: "BalancerV2Executor", args: []}, + ], + +} async function main() { const network = hre.network.name; @@ -18,7 +27,7 @@ async function main() { console.log(`Deploying with account: ${deployer.address}`); console.log(`Account balance: ${ethers.utils.formatEther(await deployer.getBalance())} ETH`); - for (const executor of executors_to_deploy) { + for (const executor of executors_to_deploy[network]) { const {exchange, args} = executor; const Executor = await ethers.getContractFactory(exchange); const deployedExecutor = await Executor.deploy(...args); diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 2a40897..6707ce8 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -7,6 +7,7 @@ import "@uniswap-v2/contracts/interfaces/IUniswapV2Pair.sol"; error UniswapV2Executor__InvalidDataLength(); error UniswapV2Executor__InvalidTarget(); +error UniswapV2Executor__InvalidFactory(); contract UniswapV2Executor is IExecutor { using SafeERC20 for IERC20; @@ -14,8 +15,16 @@ contract UniswapV2Executor is IExecutor { bytes32 internal constant POOL_INIT_CODE_HASH = 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f; - address private constant FACTORY = - 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + address public immutable factory; + address private immutable self; + + constructor(address _factory) { + if (_factory == address(0)) { + revert UniswapV2Executor__InvalidFactory(); + } + factory = _factory; + self = address(this); + } // slither-disable-next-line locked-ether function swap(uint256 givenAmount, bytes calldata data) @@ -94,7 +103,7 @@ contract UniswapV2Executor is IExecutor { uint256( keccak256( abi.encodePacked( - hex"ff", FACTORY, salt, POOL_INIT_CODE_HASH + hex"ff", factory, salt, POOL_INIT_CODE_HASH ) ) ) diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index 1f0762d..6a0eb01 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -47,7 +47,8 @@ contract Constants is Test, BaseConstants { address USDC_WBTC_POOL = 0x004375Dff511095CC5A197A54140a24eFEF3A416; // Uniswap v3 - address USV3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + address USV3_FACTORY_ETHEREUM = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + address USV2_FACTORY_ETHEREUM = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; address DAI_WETH_USV3 = 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8; // Uniswap universal router diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 2609fa4..634f814 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -44,7 +44,8 @@ contract TychoRouterTestSetup is Test, Constants { vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); vm.startPrank(ADMIN); - address factoryV3 = USV3_FACTORY; + address factoryV3 = USV3_FACTORY_ETHEREUM; + address factoryV2 = USV2_FACTORY_ETHEREUM; address poolManagerAddress = 0x000000000004444c5dc75cB358380D2e3dE08A90; IPoolManager poolManager = IPoolManager(poolManagerAddress); tychoRouter = new TychoRouterExposed(PERMIT2_ADDRESS, WETH_ADDR); @@ -59,7 +60,7 @@ contract TychoRouterTestSetup is Test, Constants { deployDummyContract(); vm.stopPrank(); - usv2Executor = new UniswapV2Executor(); + usv2Executor = new UniswapV2Executor(factoryV2); usv3Executor = new UniswapV3Executor(factoryV3); usv4Executor = new UniswapV4Executor(poolManager); vm.startPrank(EXECUTOR_SETTER); diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 83baadd..f9b15a4 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -6,6 +6,8 @@ import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { + constructor(address _factory) UniswapV2Executor(_factory) {} + function decodeParams(bytes calldata data) external pure @@ -42,7 +44,7 @@ contract FakeUniswapV2Pool { } } -contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { +contract UniswapV2ExecutorTest is Test, Constants { using SafeERC20 for IERC20; UniswapV2ExecutorExposed uniswapV2Exposed; @@ -52,7 +54,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { function setUp() public { uint256 forkBlock = 17323404; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - uniswapV2Exposed = new UniswapV2ExecutorExposed(); + uniswapV2Exposed = new UniswapV2ExecutorExposed(USV2_FACTORY_ETHEREUM); } function testDecodeParams() public view { diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 42e8e6b..8928627 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -44,7 +44,7 @@ contract UniswapV3ExecutorTest is Test, Constants { uint256 forkBlock = 17323404; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - uniswapV3Exposed = new UniswapV3ExecutorExposed(USV3_FACTORY); + uniswapV3Exposed = new UniswapV3ExecutorExposed(USV3_FACTORY_ETHEREUM); } function testDecodeParams() public view {