Skip to content

Commit d2541c2

Browse files
committed
fix: cleanup
1 parent 161b987 commit d2541c2

12 files changed

Lines changed: 206 additions & 219 deletions

LOCAL_DEPLOYMENT.md

Lines changed: 51 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,83 @@
1-
# Numo Deployment Guide
1+
# ForexSwap Local Deployment Guide
22

3-
## Local Development with Anvil
3+
## Context
44

5-
This repository includes scripts for deploying the Numo hook contract to a local Anvil node for development and testing.
5+
ForexSwap is a Uniswap v4 hook, not a standalone pool contract. In v4, `PoolManager` is the singleton that owns pool state and invokes hooks during pool initialization, liquidity changes, swaps, and donations. This repo's local deployment flow sets up a minimal v4 environment around the hook.
66

7-
### Prerequisites
7+
Reference:
8+
- [Uniswap v4 overview](https://docs.uniswap.org/contracts/v4/overview)
89

9-
- [Foundry](https://book.getfoundry.sh/getting-started/installation) installed
10-
- Anvil (comes with Foundry)
10+
## Prerequisites
1111

12-
### Quick Start
12+
- Foundry installed
13+
- Bun installed
14+
- git submodules initialized
1315

14-
**Manual Deployment (Recommended):**
15-
16-
1. **Start Anvil in one terminal:**
17-
18-
```bash
19-
anvil
20-
```
21-
22-
2. **In another terminal, deploy:**
23-
24-
```bash
25-
# Set required environment variable
26-
export API_KEY_ETHERSCAN="not_needed_for_local"
27-
28-
# Deploy the complete ecosystem
29-
forge script script/Anvil.s.sol:AnvilScript \
30-
--rpc-url http://localhost:8545 \
31-
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
32-
--broadcast -vv
33-
```
34-
35-
**Alternative: Use deployment script (if your terminal supports it):**
36-
37-
```bash
38-
./deploy-local.sh
16+
```sh
17+
git submodule update --init --recursive
18+
bun install
3919
```
4020

41-
### What Gets Deployed
42-
43-
The `Anvil.s.sol` script deploys a complete Uniswap V4 ecosystem including:
44-
45-
- **PoolManager** - Core V4 pool management contract
46-
- **Numo Hook** - Your hook contract (deployed with proper address mining)
47-
- **PositionManager** - For managing liquidity positions
48-
- **Test Routers** - For liquidity and swap operations
49-
- **Permit2** - For token approvals
50-
- **Mock Tokens** - ERC20 tokens for testing
51-
52-
### Testing the Deployment
21+
Without the submodules, Foundry cannot resolve the v4 and OpenZeppelin hook dependencies under `lib/`.
5322

54-
The script automatically:
23+
## Recommended Local Flow
5524

56-
1. Creates a pool with the Numo hook
57-
2. Adds initial liquidity
58-
3. Performs a test swap
59-
4. Logs all deployed contract addresses
25+
1. Start Anvil:
6026

61-
### Manual Deployment
62-
63-
If you prefer to deploy manually:
64-
65-
```bash
66-
# Start Anvil
27+
```sh
6728
anvil
29+
```
6830

69-
# In another terminal, deploy
31+
2. In another terminal, deploy the local v4 stack:
32+
33+
```sh
7034
forge script script/Anvil.s.sol:AnvilScript \
7135
--rpc-url http://localhost:8545 \
7236
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
73-
--broadcast -vvv
37+
--broadcast -vv
7438
```
7539

76-
### Environment Variables
40+
You can also use:
7741

78-
For mainnet or testnet deployments, you can use:
42+
```sh
43+
./deploy-local.sh
44+
```
7945

80-
```bash
81-
# Using mnemonic
82-
export MNEMONIC="your twelve word mnemonic phrase here"
46+
## What the Script Deploys
8347

84-
# Or using specific address
85-
export ETH_FROM="0xYourAddressHere"
48+
[`script/Anvil.s.sol`](script/Anvil.s.sol) currently deploys:
8649

87-
# Then deploy
88-
forge script script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --broadcast
89-
```
50+
- `PoolManager`
51+
- `ForexSwap`
52+
- `PoolModifyLiquidityTest`
53+
- `PoolSwapTest`
54+
- `PoolDonateTest`
55+
- two mock ERC20 tokens
9056

91-
### Deployed Addresses
57+
It then:
9258

93-
After deployment, you'll see output like:
59+
- mines a CREATE2 salt so the hook address has the required permission flags
60+
- initializes a pool
61+
- adds initial liquidity through the hook
9462

95-
```
96-
=== Deployed Addresses ===
97-
PoolManager: 0x5FbDB2315678afecb367f032d93F642f64180aa3
98-
Numo Hook: 0x[computed_hook_address]
99-
PositionManager: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
100-
LiquidityRouter: 0x[router_address]
101-
SwapRouter: 0x[swap_router_address]
102-
Permit2: 0x000000000022D473030F116dDEE9F6B43aC78BA3
103-
```
63+
## Important v4 Details
10464

105-
### Troubleshooting
65+
- Hook permissions are encoded into the hook address, so `ForexSwap` must be deployed at a mined address.
66+
- `PoolManager` owns lifecycle execution. The hook supplies logic but does not own pool state the way a standalone AMM would.
67+
- Flash accounting is a v4 execution optimization handled by core contracts, not by this deployment script.
10668

107-
1. **Hook address mismatch error**: This means the CREATE2 mining didn't work correctly. The script should handle this
108-
automatically.
69+
## Current Limitations
10970

110-
2. **Permit2 deployment issues**: The script tries to use the canonical Permit2 address first, then deploys a local
111-
version if needed.
71+
- The script is designed for local development on chain ID `31337`.
72+
- The sample swap path in the script is currently disabled.
73+
- [`script/Deploy.s.sol`](script/Deploy.s.sol) is only a stub for non-local deployment because it uses a placeholder `PoolManager` address.
11274

113-
3. **Transaction failures**: Make sure Anvil is running and you're using the correct RPC URL.
75+
## Troubleshooting
11476

115-
### Next Steps
77+
If `forge build` or `forge script` fails with missing imports:
11678

117-
After deployment, you can:
79+
```sh
80+
git submodule update --init --recursive
81+
```
11882

119-
- Interact with your hook through the deployed routers
120-
- Test different scenarios with the mock tokens
121-
- Develop additional functionality using the deployed addresses
83+
If the hook deployment fails with an address mismatch, the CREATE2 salt mining step did not produce the required permission bits.

README.md

Lines changed: 88 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,121 @@
11
# ForexSwap
22

3-
[gitpod]: https://gitpod.io/#https://github.com/robertleifke/forex-swap
4-
[gitpod-badge]: https://img.shields.io/badge/Gitpod-Open%20in%20Gitpod-FFB45B?logo=gitpod
5-
[gha]: https://github.com/robertleifke/forex-swap/actions
6-
[gha-badge]: https://github.com/robertleifke/forex-swap/actions/workflows/ci.yml/badge.svg
7-
[foundry]: https://getfoundry.sh/
8-
[foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg
9-
[license]: https://opensource.org/licenses/MIT
10-
[license-badge]: https://img.shields.io/badge/License-MIT-blue.svg
3+
> WARNING: This code has not been audited. Treat it as experimental.
114
12-
> ⚠️ **WARNING:** This code has not yet been audited. Use at your own risk.
5+
ForexSwap is a Uniswap v4 hook prototype for FX-style pools. It plugs custom accounting into the v4 swap and liquidity lifecycle so the pool can use a custom curve instead of the default concentrated-liquidity math.
136

14-
## Overview
7+
The implementation in this repo follows the v4 architecture described in the official overview:
158

16-
ForexSwap is a Uniswap v4 hook implementation of a [log normal](https://en.wikipedia.org/wiki/Log-normal_distribution) market maker. It's statistical curve that makes liquidity provisioning more passive and capital efficient on frontier FX pairs such as USDC/cNGN.
9+
- `PoolManager` is the singleton that owns pool state and calls hooks.
10+
- Hooks are permissioned contracts whose callback surface is encoded into the hook address.
11+
- Dynamic fees are pool-level behavior in v4, but the fee policy itself is application-defined.
12+
- Flash accounting is handled by v4; this repo focuses on custom accounting and curve logic.
1713

18-
## Quick Start
14+
Official reference:
15+
- [Uniswap v4 overview](https://docs.uniswap.org/contracts/v4/overview)
1916

20-
Clone and set up the project:
17+
## Repo Layout
2118

22-
```sh
23-
$ git clone https://github.com/robertleifke/forex-swap
24-
$ cd forex-swap
25-
$ bun install
26-
$ forge build
27-
```
19+
- [`src/ForexSwap.sol`](src/ForexSwap.sol): main `ForexSwap` hook contract
20+
- [`tests/ForexSwap.t.sol`](tests/ForexSwap.t.sol): Foundry tests
21+
- [`script/Anvil.s.sol`](script/Anvil.s.sol): local Anvil deployment and lifecycle script
22+
- [`script/Deploy.s.sol`](script/Deploy.s.sol): minimal non-local deploy stub
2823

29-
## Deploy pools
24+
## Contract Model
3025

31-
```solidity
32-
IPoolManager poolManager =
33-
ForexSwap forexSwapHook = new ForexSwap(poolManager);
26+
`ForexSwap` inherits `BaseCustomCurve`, so it opts into v4 custom accounting rather than implementing a standalone pool. The important contract responsibilities are:
3427

35-
PoolKey memory poolKey = PoolKey({
36-
currency0: Currency.wrap(address(token0)),
37-
currency1: Currency.wrap(address(token1)),
38-
fee: 0,
39-
tickSpacing: 0,
40-
hooks: IHooks(address(forexSwapHook))
41-
});
42-
forexSwapHook.initializePool(poolKey);
28+
- custom swap math via `_getUnspecifiedAmount`, `_computeAmountOut`, and `_computeAmountIn`
29+
- owner-managed parameters for `mean`, `width`, and `swapFee`
30+
- pause and unpause controls
31+
- hook-local liquidity share accounting for deposits and withdrawals
4332

44-
forexSwapHook.updateForexSwapParams(
45-
1.1e18, // mu = 1.1 (10% mean premium)
46-
2.5e17, // sigma = 0.25 (25% volatility)
47-
5e15 // swapFee = 0.5%
48-
);
49-
```
33+
The current implementation is intentionally conservative. It uses simplified reserve assumptions and approximations rather than a production-ready statistical engine.
5034

51-
## Testing
35+
## Prerequisites
5236

53-
Run comprehensive tests for the ForexSwap implementation:
37+
This repo expects git submodules to be present. A plain clone is not enough.
5438

5539
```sh
56-
# Run all tests
57-
$ forge test
40+
git clone https://github.com/robertleifke/forex-swap
41+
cd forex-swap
42+
git submodule update --init --recursive
43+
bun install
44+
forge build
45+
```
46+
47+
If `forge build` fails with missing imports under `lib/`, the submodules were not initialized correctly.
5848

59-
# Run with detailed output
60-
$ forge test -vvv
49+
## Local Development
6150

62-
# Run gas reporting
63-
$ forge test --gas-report
51+
The quickest path is a local Anvil deployment:
6452

65-
# Run specific ForexSwap tests
66-
$ forge test --match-contract ForexSwap -vv
53+
```sh
54+
anvil
55+
forge script script/Anvil.s.sol:AnvilScript \
56+
--rpc-url http://localhost:8545 \
57+
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
58+
--broadcast -vv
6759
```
6860

61+
Or use:
62+
63+
```sh
64+
./deploy-local.sh
65+
```
6966

70-
## Routing
67+
The Anvil script deploys:
7168

72-
### Inverse Normal CDF Implementation
69+
- a local v4 `PoolManager`
70+
- the `ForexSwap` hook at a mined address with the required hook permission bits
71+
- test routers
72+
- mock ERC20s
73+
- a sample pool and initial liquidity
7374

74-
ForexSwap uses the Beasley-Springer-Moro algorithm for computing Φ⁻¹(u):
75+
## Using the Hook
76+
77+
Example setup:
7578

7679
```solidity
77-
function _improvedInverseNormalCDF(uint256 u) internal pure returns (int256) {
78-
// Bounded to [-6σ, +6σ] for numerical stability
79-
}
80+
IPoolManager poolManager = /* deployed v4 PoolManager */;
81+
ForexSwap forexSwap = new ForexSwap(poolManager);
82+
83+
PoolKey memory poolKey = PoolKey({
84+
currency0: Currency.wrap(address(token0)),
85+
currency1: Currency.wrap(address(token1)),
86+
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
87+
tickSpacing: 60,
88+
hooks: forexSwap
89+
});
90+
91+
poolManager.initialize(poolKey, TickMath.getSqrtPriceAtTick(0));
92+
93+
forexSwap.updateLogNormalParams(
94+
1.1e18, // mean
95+
2.5e17, // width
96+
5e15 // 0.5% swap fee
97+
);
8098
```
8199

82-
### Newton-Raphson Iteration
100+
Two important v4-specific notes:
83101

84-
For swap calculations, ForexSwap employs iterative solving:
102+
- The hook address must be mined so its low bits advertise the enabled callbacks.
103+
- `PoolManager` owns execution. The hook is not a standalone AMM contract.
85104

86-
```solidity
87-
function _solveExactInputWithLiquidity(...) internal view returns (...) {
88-
// Initial guess using constant product
89-
// Newton-Raphson iteration to solve: Φ⁻¹(x'/L) + Φ⁻¹(y'/L) = k
90-
// Convergence threshold: 1e-6 in WAD precision
91-
// Maximum iterations: 50
92-
}
105+
## Testing
106+
107+
```sh
108+
forge test
109+
forge test -vvv
110+
forge test --gas-report
111+
forge test --match-contract ForexSwapCorrectTest -vv
93112
```
94-
## 📄 License
95113

96-
This project is licensed under MIT - see the [LICENSE](LICENSE) file for details.
114+
## Current Caveats
115+
116+
- The non-local deploy script uses a placeholder `PoolManager` address and is not production-ready.
117+
- The pricing logic is simplified. It should be treated as a prototype, not a validated FX curve.
118+
119+
## License
97120

98-
---
121+
MIT. See [`LICENSE.md`](LICENSE.md).

deploy-anvil.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/bash
22

3-
# Deploy Numo to local Anvil node
3+
# Deploy ForexSwap to local Anvil node
44
# Usage: ./deploy-anvil.sh
55

66
set -e
@@ -12,7 +12,7 @@ ANVIL_PID=$!
1212
# Wait for anvil to start
1313
sleep 3
1414

15-
echo "🚀 Deploying Numo to Anvil..."
15+
echo "🚀 Deploying ForexSwap to Anvil..."
1616

1717
# Deploy using the Anvil script
1818
forge script script/Anvil.s.sol:AnvilScript --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast -vvv
@@ -23,4 +23,4 @@ echo "🔗 Anvil node running at http://localhost:8545"
2323
echo "💀 To stop Anvil, run: kill $ANVIL_PID"
2424

2525
# Keep the script running so anvil stays alive
26-
wait $ANVIL_PID
26+
wait $ANVIL_PID

0 commit comments

Comments
 (0)