Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ out/

/dev-ctx/
/broadcast/
.claude/
113 changes: 113 additions & 0 deletions POOL_CREATION.md
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions deploy-scenario.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
75 changes: 75 additions & 0 deletions script/MineSaltForPool.s.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
128 changes: 128 additions & 0 deletions script/scenarios/EulerSwapPoolCreation.s.sol
Original file line number Diff line number Diff line change
@@ -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!");
}
}
}