diff --git a/.gitignore b/.gitignore index ba914e3..8d3ea99 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ out/ /dev-ctx/ /broadcast/ +.claude/ diff --git a/POOL_CREATION.md b/POOL_CREATION.md new file mode 100644 index 0000000..8c8a377 --- /dev/null +++ b/POOL_CREATION.md @@ -0,0 +1,113 @@ +# EulerSwap Pool Creation Guide + +This guide explains how to successfully create EulerSwap pools with Uniswap v4 integration. + +## Quick Start + +### 1. Deploy Pool with Auto Salt Mining + +```bash +./deploy-scenario.sh EulerSwapPoolCreation +``` + +This scenario automatically: +- Deploys USDC and USDT vaults with liquidity +- Mines a valid salt using HookMiner +- Creates the pool successfully + +### 2. Mine Salt for Custom Pool + +```bash +forge script script/MineSaltForPool.s.sol --rpc-url http://localhost:8545 +``` + +Configure with environment variables: +- `VAULT0`: First vault address +- `VAULT1`: Second vault address +- `EULER_ACCOUNT`: Account that will own the pool +- `FACTORY`: EulerSwapFactory address +- `IMPL`: EulerSwap implementation address + +## Background + +EulerSwap pools act as hooks for Uniswap v4. Uniswap v4 requires hook addresses to have specific permission bits encoded in their address. This presents a challenge because EulerSwap uses MetaProxy deployment, which means the actual pool address must have the correct permission bits. + +## Solution: HookMiner + +The solution is to use the `HookMiner` utility from the EulerSwap test suite to find a salt that produces a valid hook address. + +## How It Works + +1. **Define Pool Parameters**: Set up your pool configuration (vaults, reserves, fees, etc.) + +2. **Calculate Required Flags**: EulerSwap pools need these permission flags: + - `BEFORE_INITIALIZE_FLAG` + - `BEFORE_SWAP_FLAG` + - `BEFORE_SWAP_RETURNS_DELTA_FLAG` + - `BEFORE_DONATE_FLAG` + - `BEFORE_ADD_LIQUIDITY_FLAG` + +3. **Mine Salt**: Use HookMiner to find a salt that produces an address with the correct permission bits: + ```solidity + bytes memory creationCode = MetaProxyDeployer.creationCodeMetaProxy(eulerSwapImpl, abi.encode(poolParams)); + (address hookAddress, bytes32 salt) = HookMiner.find(address(eulerSwapFactory), flags, creationCode); + ``` + +4. **Deploy Pool**: Use the mined salt in your deployment through EVC batch + +## Key Code Example + +```solidity +// Mine salt +uint160 flags = uint160( + Hooks.BEFORE_INITIALIZE_FLAG | + Hooks.BEFORE_SWAP_FLAG | + Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG | + Hooks.BEFORE_DONATE_FLAG | + Hooks.BEFORE_ADD_LIQUIDITY_FLAG +); + +bytes memory creationCode = MetaProxyDeployer.creationCodeMetaProxy(eulerSwapImpl, abi.encode(poolParams)); +(address hookAddress, bytes32 salt) = HookMiner.find(address(eulerSwapFactory), flags, creationCode); + +// Deploy pool via EVC +IEVC.BatchItem[] memory items = new IEVC.BatchItem[](2); +items[0] = IEVC.BatchItem({ + onBehalfOfAccount: address(0), + targetContract: address(evc), + value: 0, + data: abi.encodeCall(evc.setAccountOperator, (user0, hookAddress, true)) +}); +items[1] = IEVC.BatchItem({ + onBehalfOfAccount: user0, + targetContract: address(eulerSwapFactory), + value: 0, + data: abi.encodeCall(IEulerSwapFactory.deployPool, (poolParams, initialState, salt)) +}); +evc.batch(items); +``` + +## Important Notes + +1. **Salt is Pool-Specific**: The salt depends on the exact pool parameters. If you change any parameter (vaults, reserves, fees, etc.), you need to mine a new salt. + +2. **Deterministic Addresses**: On fresh anvil with deterministic addresses, the same pool parameters will always require the same salt. + +3. **HookMiner Limits**: HookMiner tries up to 160,444 iterations to find a valid salt. If it fails, you may need to adjust your pool parameters slightly. + +4. **Performance**: Salt mining typically takes a few seconds to find a valid salt among ~80k attempts. + +## Troubleshooting + +- **"HookAddressNotValid" Error**: The pool address doesn't have the correct permission bits. Use HookMiner to find a valid salt. + +- **"Could not find salt" Error**: HookMiner couldn't find a valid salt within its iteration limit. Try adjusting pool parameters slightly (e.g., change fee by 1 wei). + +- **Deployment Succeeds but Pool Not Found**: Check that you're using the correct euler account and that the operator was set properly. + +## Production Recommendations + +For production use: +1. Use the EulerSwap UI which handles salt mining automatically +2. Use the EulerSwap SDK which includes salt mining utilities +3. Pre-mine salts for common pool configurations \ No newline at end of file diff --git a/deploy-scenario.sh b/deploy-scenario.sh index 716e5e4..ba3ba83 100755 --- a/deploy-scenario.sh +++ b/deploy-scenario.sh @@ -7,8 +7,8 @@ SCENARIO=$1 rm -rf dev-ctx/ mkdir -p dev-ctx/{addresses,labels,priceapi}/31337 -forge script --rpc-url ${RPC_URL:-http://127.0.0.1:8545} "script/scenarios/$SCENARIO.s.sol" --broadcast --code-size-limit 100000 -cast rpc evm_increaseTime 86400 -cast rpc evm_mine +forge script --rpc-url ${RPC_URL:-http://127.0.0.1:8545} "script/scenarios/$SCENARIO.s.sol" --broadcast --code-size-limit 100000 -vv +cast rpc evm_increaseTime 86400 || true +cast rpc evm_mine || true node chains.js diff --git a/script/MineSaltForPool.s.sol b/script/MineSaltForPool.s.sol new file mode 100644 index 0000000..77a1b5c --- /dev/null +++ b/script/MineSaltForPool.s.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {IEulerSwap} from "euler-swap/interfaces/IEulerSwap.sol"; +import {IEulerSwapFactory} from "euler-swap/interfaces/IEulerSwapFactory.sol"; +import {HookMiner} from "../libflat/euler-swap/test/utils/HookMiner.sol"; +import {Hooks} from "v4-core/libraries/Hooks.sol"; +import {MetaProxyDeployer} from "euler-swap/utils/MetaProxyDeployer.sol"; + +contract MineSaltForPool is Script { + function run() public view { + // Read configuration from environment or use defaults + address vault0 = vm.envOr("VAULT0", address(0x864516a3e56ab9b821B19F6bfB898FA28f21E0cB)); + address vault1 = vm.envOr("VAULT1", address(0xB9D512FAF432Ce6A0e09b1f2B195856F9E5EE822)); + address eulerAccount = vm.envOr("EULER_ACCOUNT", address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + address eulerSwapFactory = vm.envOr("FACTORY", address(0x55d09f01fBF9D8D891c6608C5a54EA7AD6d528fb)); + address eulerSwapImpl = vm.envOr("IMPL", address(0x48d617CA203f53C9909758799af38D1780b157F7)); + + console.log("Mining salt for EulerSwap pool deployment"); + console.log("Vault0:", vault0); + console.log("Vault1:", vault1); + console.log("Euler Account:", eulerAccount); + console.log("Factory:", eulerSwapFactory); + console.log("Implementation:", eulerSwapImpl); + console.log(""); + + // Create pool parameters + IEulerSwap.Params memory poolParams = IEulerSwap.Params({ + vault0: vault0, + vault1: vault1, + eulerAccount: eulerAccount, + equilibriumReserve0: 10000e18, + equilibriumReserve1: 10000e18, + priceX: 1e18, + priceY: 1e18, + concentrationX: 0.5e18, + concentrationY: 0.5e18, + fee: 0.003e18, + protocolFee: 0, + protocolFeeRecipient: address(0) + }); + + // Define required hook flags + uint160 flags = uint160( + Hooks.BEFORE_INITIALIZE_FLAG | + Hooks.BEFORE_SWAP_FLAG | + Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG | + Hooks.BEFORE_DONATE_FLAG | + Hooks.BEFORE_ADD_LIQUIDITY_FLAG + ); + + console.log("Required flags:", flags); + console.log("Mining salt..."); + + // Mine salt + bytes memory creationCode = MetaProxyDeployer.creationCodeMetaProxy(eulerSwapImpl, abi.encode(poolParams)); + (address hookAddress, bytes32 salt) = HookMiner.find(eulerSwapFactory, flags, creationCode); + + console.log(""); + console.log("SUCCESS! Found valid salt:"); + console.log("Salt (hex):", vm.toString(salt)); + console.log("Salt (decimal):", uint256(salt)); + console.log("Hook address:", hookAddress); + console.log("Hook flags:", uint160(hookAddress) & Hooks.ALL_HOOK_MASK); + + // Verify the address + address computedAddress = IEulerSwapFactory(eulerSwapFactory).computePoolAddress(poolParams, salt); + console.log(""); + console.log("Verification:"); + console.log("Computed address:", computedAddress); + console.log("Matches hook address:", computedAddress == hookAddress); + } +} \ No newline at end of file diff --git a/script/scenarios/EulerSwapPoolCreation.s.sol b/script/scenarios/EulerSwapPoolCreation.s.sol new file mode 100644 index 0000000..ab45776 --- /dev/null +++ b/script/scenarios/EulerSwapPoolCreation.s.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import {DeployScenario} from "../DeployScenario.s.sol"; +import {IEulerSwap} from "euler-swap/interfaces/IEulerSwap.sol"; +import {IEulerSwapFactory} from "euler-swap/interfaces/IEulerSwapFactory.sol"; +import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; +import {IEVault} from "euler-vault-kit/EVault/IEVault.sol"; +import {console} from "forge-std/console.sol"; +import {Hooks} from "v4-core/libraries/Hooks.sol"; +import {HookMiner} from "../../libflat/euler-swap/test/utils/HookMiner.sol"; +import {MetaProxyDeployer} from "euler-swap/utils/MetaProxyDeployer.sol"; + +import {Test} from "forge-std/Test.sol"; + +contract EulerSwapPoolCreation is DeployScenario, Test { + function setup() internal virtual override { + vm.startBroadcast(user3PK); + giveLotsOfCash(user0); + vm.stopBroadcast(); + + vm.startBroadcast(user0PK); + + // Deposit liquidity into vaults + assetUSDC.approve(address(eUSDC), type(uint256).max); + eUSDC.deposit(100000e6, user0); + + assetUSDT.approve(address(eUSDT), type(uint256).max); + eUSDT.deposit(100000e6, user0); + + // Create USDC/USDT pool with 1:1 price + // Note: assets must be ordered (asset0 < asset1) + address vault0 = address(eUSDC); + address vault1 = address(eUSDT); + + // Swap if needed to ensure proper ordering + if (IEVault(vault0).asset() > IEVault(vault1).asset()) { + (vault0, vault1) = (vault1, vault0); + } + + // Prepare pool parameters + IEulerSwap.Params memory poolParams = IEulerSwap.Params({ + vault0: vault0, + vault1: vault1, + eulerAccount: user0, + equilibriumReserve0: 10000e18, // 10k virtual reserves + equilibriumReserve1: 10000e18, // 10k virtual reserves + priceX: 1e18, // 1:1 price + priceY: 1e18, + concentrationX: 0.5e18, // 50% concentration + concentrationY: 0.5e18, // 50% concentration + fee: 0.003e18, // 0.3% fee + protocolFee: 0, + protocolFeeRecipient: address(0) + }); + + // Prepare initial state + IEulerSwap.InitialState memory initialState = IEulerSwap.InitialState({ + currReserve0: 10000e18, + currReserve1: 10000e18 + }); + + // Mine salt using HookMiner to find a valid hook address + console.log("\nMining salt for valid hook address..."); + uint160 flags = uint160( + Hooks.BEFORE_INITIALIZE_FLAG | + Hooks.BEFORE_SWAP_FLAG | + Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG | + Hooks.BEFORE_DONATE_FLAG | + Hooks.BEFORE_ADD_LIQUIDITY_FLAG + ); + + // Prepare creation code + bytes memory creationCode = MetaProxyDeployer.creationCodeMetaProxy(eulerSwapImpl, abi.encode(poolParams)); + (address hookAddress, bytes32 salt) = HookMiner.find(address(eulerSwapFactory), flags, creationCode); + + console.log("Found salt:", uint256(salt)); + console.log("Hook address:", hookAddress); + console.log("Hook address flags:", uint160(hookAddress) & Hooks.ALL_HOOK_MASK); + console.log("Expected flags:", flags); + + // Deploy pool via EVC batch + IEVC.BatchItem[] memory items = new IEVC.BatchItem[](2); + items[0] = IEVC.BatchItem({ + onBehalfOfAccount: address(0), + targetContract: address(evc), + value: 0, + data: abi.encodeCall(evc.setAccountOperator, (user0, hookAddress, true)) + }); + items[1] = IEVC.BatchItem({ + onBehalfOfAccount: user0, + targetContract: address(eulerSwapFactory), + value: 0, + data: abi.encodeCall(IEulerSwapFactory.deployPool, (poolParams, initialState, salt)) + }); + console.log("\nDeploying EulerSwap pool..."); + console.log("Pool manager address:", address(poolManager)); + console.log("EulerSwap implementation:", address(eulerSwapImpl)); + evc.batch(items); + console.log("SUCCESS! Pool deployed at:", hookAddress); + + // Enable vaults as collateral + evc.enableCollateral(user0, vault0); + evc.enableCollateral(user0, vault1); + vm.stopBroadcast(); + + // Print results + console.log(""); + console.log("===== EulerSwap Pool Creation Scenario ====="); + console.log(""); + console.log("Setup complete:"); + console.log("- User0 deposited 100k USDC and 100k USDT"); + console.log("- Both vaults enabled as collateral"); + console.log("- USDC Vault:", vault0); + console.log("- USDT Vault:", vault1); + + // Check if pool exists + address deployedPool = eulerSwapFactory.poolByEulerAccount(user0); + if (deployedPool != address(0)) { + console.log("- Pool deployed at:", deployedPool); + console.log(""); + console.log("Pool is ready for swaps and liquidity provision!"); + } else { + console.log(""); + console.log("Error: Pool deployment may have failed!"); + } + } +} \ No newline at end of file