Skip to content

Commit 687bda1

Browse files
authored
Merge pull request #2 from wakamex/feat/eulerswap-pool-creation
feat: add EulerSwap pool creation with HookMiner
2 parents a55ec50 + 974882b commit 687bda1

File tree

5 files changed

+320
-3
lines changed

5 files changed

+320
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ out/
1212

1313
/dev-ctx/
1414
/broadcast/
15+
.claude/

POOL_CREATION.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# EulerSwap Pool Creation Guide
2+
3+
This guide explains how to successfully create EulerSwap pools with Uniswap v4 integration.
4+
5+
## Quick Start
6+
7+
### 1. Deploy Pool with Auto Salt Mining
8+
9+
```bash
10+
./deploy-scenario.sh EulerSwapPoolCreation
11+
```
12+
13+
This scenario automatically:
14+
- Deploys USDC and USDT vaults with liquidity
15+
- Mines a valid salt using HookMiner
16+
- Creates the pool successfully
17+
18+
### 2. Mine Salt for Custom Pool
19+
20+
```bash
21+
forge script script/MineSaltForPool.s.sol --rpc-url http://localhost:8545
22+
```
23+
24+
Configure with environment variables:
25+
- `VAULT0`: First vault address
26+
- `VAULT1`: Second vault address
27+
- `EULER_ACCOUNT`: Account that will own the pool
28+
- `FACTORY`: EulerSwapFactory address
29+
- `IMPL`: EulerSwap implementation address
30+
31+
## Background
32+
33+
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.
34+
35+
## Solution: HookMiner
36+
37+
The solution is to use the `HookMiner` utility from the EulerSwap test suite to find a salt that produces a valid hook address.
38+
39+
## How It Works
40+
41+
1. **Define Pool Parameters**: Set up your pool configuration (vaults, reserves, fees, etc.)
42+
43+
2. **Calculate Required Flags**: EulerSwap pools need these permission flags:
44+
- `BEFORE_INITIALIZE_FLAG`
45+
- `BEFORE_SWAP_FLAG`
46+
- `BEFORE_SWAP_RETURNS_DELTA_FLAG`
47+
- `BEFORE_DONATE_FLAG`
48+
- `BEFORE_ADD_LIQUIDITY_FLAG`
49+
50+
3. **Mine Salt**: Use HookMiner to find a salt that produces an address with the correct permission bits:
51+
```solidity
52+
bytes memory creationCode = MetaProxyDeployer.creationCodeMetaProxy(eulerSwapImpl, abi.encode(poolParams));
53+
(address hookAddress, bytes32 salt) = HookMiner.find(address(eulerSwapFactory), flags, creationCode);
54+
```
55+
56+
4. **Deploy Pool**: Use the mined salt in your deployment through EVC batch
57+
58+
## Key Code Example
59+
60+
```solidity
61+
// Mine salt
62+
uint160 flags = uint160(
63+
Hooks.BEFORE_INITIALIZE_FLAG |
64+
Hooks.BEFORE_SWAP_FLAG |
65+
Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG |
66+
Hooks.BEFORE_DONATE_FLAG |
67+
Hooks.BEFORE_ADD_LIQUIDITY_FLAG
68+
);
69+
70+
bytes memory creationCode = MetaProxyDeployer.creationCodeMetaProxy(eulerSwapImpl, abi.encode(poolParams));
71+
(address hookAddress, bytes32 salt) = HookMiner.find(address(eulerSwapFactory), flags, creationCode);
72+
73+
// Deploy pool via EVC
74+
IEVC.BatchItem[] memory items = new IEVC.BatchItem[](2);
75+
items[0] = IEVC.BatchItem({
76+
onBehalfOfAccount: address(0),
77+
targetContract: address(evc),
78+
value: 0,
79+
data: abi.encodeCall(evc.setAccountOperator, (user0, hookAddress, true))
80+
});
81+
items[1] = IEVC.BatchItem({
82+
onBehalfOfAccount: user0,
83+
targetContract: address(eulerSwapFactory),
84+
value: 0,
85+
data: abi.encodeCall(IEulerSwapFactory.deployPool, (poolParams, initialState, salt))
86+
});
87+
evc.batch(items);
88+
```
89+
90+
## Important Notes
91+
92+
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.
93+
94+
2. **Deterministic Addresses**: On fresh anvil with deterministic addresses, the same pool parameters will always require the same salt.
95+
96+
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.
97+
98+
4. **Performance**: Salt mining typically takes a few seconds to find a valid salt among ~80k attempts.
99+
100+
## Troubleshooting
101+
102+
- **"HookAddressNotValid" Error**: The pool address doesn't have the correct permission bits. Use HookMiner to find a valid salt.
103+
104+
- **"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).
105+
106+
- **Deployment Succeeds but Pool Not Found**: Check that you're using the correct euler account and that the operator was set properly.
107+
108+
## Production Recommendations
109+
110+
For production use:
111+
1. Use the EulerSwap UI which handles salt mining automatically
112+
2. Use the EulerSwap SDK which includes salt mining utilities
113+
3. Pre-mine salts for common pool configurations

deploy-scenario.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ SCENARIO=$1
77
rm -rf dev-ctx/
88
mkdir -p dev-ctx/{addresses,labels,priceapi}/31337
99

10-
forge script --rpc-url ${RPC_URL:-http://127.0.0.1:8545} "script/scenarios/$SCENARIO.s.sol" --broadcast --code-size-limit 100000
11-
cast rpc evm_increaseTime 86400
12-
cast rpc evm_mine
10+
forge script --rpc-url ${RPC_URL:-http://127.0.0.1:8545} "script/scenarios/$SCENARIO.s.sol" --broadcast --code-size-limit 100000 -vv
11+
cast rpc evm_increaseTime 86400 || true
12+
cast rpc evm_mine || true
1313

1414
node chains.js

script/MineSaltForPool.s.sol

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.27;
3+
4+
import {Script} from "forge-std/Script.sol";
5+
import {console} from "forge-std/console.sol";
6+
import {IEulerSwap} from "euler-swap/interfaces/IEulerSwap.sol";
7+
import {IEulerSwapFactory} from "euler-swap/interfaces/IEulerSwapFactory.sol";
8+
import {HookMiner} from "../libflat/euler-swap/test/utils/HookMiner.sol";
9+
import {Hooks} from "v4-core/libraries/Hooks.sol";
10+
import {MetaProxyDeployer} from "euler-swap/utils/MetaProxyDeployer.sol";
11+
12+
contract MineSaltForPool is Script {
13+
function run() public view {
14+
// Read configuration from environment or use defaults
15+
address vault0 = vm.envOr("VAULT0", address(0x864516a3e56ab9b821B19F6bfB898FA28f21E0cB));
16+
address vault1 = vm.envOr("VAULT1", address(0xB9D512FAF432Ce6A0e09b1f2B195856F9E5EE822));
17+
address eulerAccount = vm.envOr("EULER_ACCOUNT", address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266));
18+
address eulerSwapFactory = vm.envOr("FACTORY", address(0x55d09f01fBF9D8D891c6608C5a54EA7AD6d528fb));
19+
address eulerSwapImpl = vm.envOr("IMPL", address(0x48d617CA203f53C9909758799af38D1780b157F7));
20+
21+
console.log("Mining salt for EulerSwap pool deployment");
22+
console.log("Vault0:", vault0);
23+
console.log("Vault1:", vault1);
24+
console.log("Euler Account:", eulerAccount);
25+
console.log("Factory:", eulerSwapFactory);
26+
console.log("Implementation:", eulerSwapImpl);
27+
console.log("");
28+
29+
// Create pool parameters
30+
IEulerSwap.Params memory poolParams = IEulerSwap.Params({
31+
vault0: vault0,
32+
vault1: vault1,
33+
eulerAccount: eulerAccount,
34+
equilibriumReserve0: 10000e18,
35+
equilibriumReserve1: 10000e18,
36+
priceX: 1e18,
37+
priceY: 1e18,
38+
concentrationX: 0.5e18,
39+
concentrationY: 0.5e18,
40+
fee: 0.003e18,
41+
protocolFee: 0,
42+
protocolFeeRecipient: address(0)
43+
});
44+
45+
// Define required hook flags
46+
uint160 flags = uint160(
47+
Hooks.BEFORE_INITIALIZE_FLAG |
48+
Hooks.BEFORE_SWAP_FLAG |
49+
Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG |
50+
Hooks.BEFORE_DONATE_FLAG |
51+
Hooks.BEFORE_ADD_LIQUIDITY_FLAG
52+
);
53+
54+
console.log("Required flags:", flags);
55+
console.log("Mining salt...");
56+
57+
// Mine salt
58+
bytes memory creationCode = MetaProxyDeployer.creationCodeMetaProxy(eulerSwapImpl, abi.encode(poolParams));
59+
(address hookAddress, bytes32 salt) = HookMiner.find(eulerSwapFactory, flags, creationCode);
60+
61+
console.log("");
62+
console.log("SUCCESS! Found valid salt:");
63+
console.log("Salt (hex):", vm.toString(salt));
64+
console.log("Salt (decimal):", uint256(salt));
65+
console.log("Hook address:", hookAddress);
66+
console.log("Hook flags:", uint160(hookAddress) & Hooks.ALL_HOOK_MASK);
67+
68+
// Verify the address
69+
address computedAddress = IEulerSwapFactory(eulerSwapFactory).computePoolAddress(poolParams, salt);
70+
console.log("");
71+
console.log("Verification:");
72+
console.log("Computed address:", computedAddress);
73+
console.log("Matches hook address:", computedAddress == hookAddress);
74+
}
75+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.27;
3+
4+
import {DeployScenario} from "../DeployScenario.s.sol";
5+
import {IEulerSwap} from "euler-swap/interfaces/IEulerSwap.sol";
6+
import {IEulerSwapFactory} from "euler-swap/interfaces/IEulerSwapFactory.sol";
7+
import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol";
8+
import {IEVault} from "euler-vault-kit/EVault/IEVault.sol";
9+
import {console} from "forge-std/console.sol";
10+
import {Hooks} from "v4-core/libraries/Hooks.sol";
11+
import {HookMiner} from "../../libflat/euler-swap/test/utils/HookMiner.sol";
12+
import {MetaProxyDeployer} from "euler-swap/utils/MetaProxyDeployer.sol";
13+
14+
import {Test} from "forge-std/Test.sol";
15+
16+
contract EulerSwapPoolCreation is DeployScenario, Test {
17+
function setup() internal virtual override {
18+
vm.startBroadcast(user3PK);
19+
giveLotsOfCash(user0);
20+
vm.stopBroadcast();
21+
22+
vm.startBroadcast(user0PK);
23+
24+
// Deposit liquidity into vaults
25+
assetUSDC.approve(address(eUSDC), type(uint256).max);
26+
eUSDC.deposit(100000e6, user0);
27+
28+
assetUSDT.approve(address(eUSDT), type(uint256).max);
29+
eUSDT.deposit(100000e6, user0);
30+
31+
// Create USDC/USDT pool with 1:1 price
32+
// Note: assets must be ordered (asset0 < asset1)
33+
address vault0 = address(eUSDC);
34+
address vault1 = address(eUSDT);
35+
36+
// Swap if needed to ensure proper ordering
37+
if (IEVault(vault0).asset() > IEVault(vault1).asset()) {
38+
(vault0, vault1) = (vault1, vault0);
39+
}
40+
41+
// Prepare pool parameters
42+
IEulerSwap.Params memory poolParams = IEulerSwap.Params({
43+
vault0: vault0,
44+
vault1: vault1,
45+
eulerAccount: user0,
46+
equilibriumReserve0: 10000e18, // 10k virtual reserves
47+
equilibriumReserve1: 10000e18, // 10k virtual reserves
48+
priceX: 1e18, // 1:1 price
49+
priceY: 1e18,
50+
concentrationX: 0.5e18, // 50% concentration
51+
concentrationY: 0.5e18, // 50% concentration
52+
fee: 0.003e18, // 0.3% fee
53+
protocolFee: 0,
54+
protocolFeeRecipient: address(0)
55+
});
56+
57+
// Prepare initial state
58+
IEulerSwap.InitialState memory initialState = IEulerSwap.InitialState({
59+
currReserve0: 10000e18,
60+
currReserve1: 10000e18
61+
});
62+
63+
// Mine salt using HookMiner to find a valid hook address
64+
console.log("\nMining salt for valid hook address...");
65+
uint160 flags = uint160(
66+
Hooks.BEFORE_INITIALIZE_FLAG |
67+
Hooks.BEFORE_SWAP_FLAG |
68+
Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG |
69+
Hooks.BEFORE_DONATE_FLAG |
70+
Hooks.BEFORE_ADD_LIQUIDITY_FLAG
71+
);
72+
73+
// Prepare creation code
74+
bytes memory creationCode = MetaProxyDeployer.creationCodeMetaProxy(eulerSwapImpl, abi.encode(poolParams));
75+
(address hookAddress, bytes32 salt) = HookMiner.find(address(eulerSwapFactory), flags, creationCode);
76+
77+
console.log("Found salt:", uint256(salt));
78+
console.log("Hook address:", hookAddress);
79+
console.log("Hook address flags:", uint160(hookAddress) & Hooks.ALL_HOOK_MASK);
80+
console.log("Expected flags:", flags);
81+
82+
// Deploy pool via EVC batch
83+
IEVC.BatchItem[] memory items = new IEVC.BatchItem[](2);
84+
items[0] = IEVC.BatchItem({
85+
onBehalfOfAccount: address(0),
86+
targetContract: address(evc),
87+
value: 0,
88+
data: abi.encodeCall(evc.setAccountOperator, (user0, hookAddress, true))
89+
});
90+
items[1] = IEVC.BatchItem({
91+
onBehalfOfAccount: user0,
92+
targetContract: address(eulerSwapFactory),
93+
value: 0,
94+
data: abi.encodeCall(IEulerSwapFactory.deployPool, (poolParams, initialState, salt))
95+
});
96+
console.log("\nDeploying EulerSwap pool...");
97+
console.log("Pool manager address:", address(poolManager));
98+
console.log("EulerSwap implementation:", address(eulerSwapImpl));
99+
evc.batch(items);
100+
console.log("SUCCESS! Pool deployed at:", hookAddress);
101+
102+
// Enable vaults as collateral
103+
evc.enableCollateral(user0, vault0);
104+
evc.enableCollateral(user0, vault1);
105+
vm.stopBroadcast();
106+
107+
// Print results
108+
console.log("");
109+
console.log("===== EulerSwap Pool Creation Scenario =====");
110+
console.log("");
111+
console.log("Setup complete:");
112+
console.log("- User0 deposited 100k USDC and 100k USDT");
113+
console.log("- Both vaults enabled as collateral");
114+
console.log("- USDC Vault:", vault0);
115+
console.log("- USDT Vault:", vault1);
116+
117+
// Check if pool exists
118+
address deployedPool = eulerSwapFactory.poolByEulerAccount(user0);
119+
if (deployedPool != address(0)) {
120+
console.log("- Pool deployed at:", deployedPool);
121+
console.log("");
122+
console.log("Pool is ready for swaps and liquidity provision!");
123+
} else {
124+
console.log("");
125+
console.log("Error: Pool deployment may have failed!");
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)