Skip to content

Commit b69bb36

Browse files
razwwclaude
andcommitted
chore: deploy script, CI layout dump, README refresh
Adds script/Deploy.s.sol for multi-chain TUP-based deploy of Factory, NPM, and SwapRouter (eth / sepolia / bnb / bnb-testnet built in, others via WETH9 env). CI now dumps the proxy storage layout for Factory and NPM so layout drift is visible on every run; drops forge fmt --check since the inherited Uniswap code isn't forge-fmt formatted. README rewritten to describe the actual project, contract roles, and deploy sequence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9c92bae commit b69bb36

3 files changed

Lines changed: 176 additions & 51 deletions

File tree

.github/workflows/test.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,19 @@ jobs:
2828
- name: Show Forge version
2929
run: forge --version
3030

31-
- name: Run Forge fmt
32-
run: forge fmt --check
33-
3431
- name: Run Forge build
3532
run: forge build --sizes
3633

3734
- name: Run Forge tests
3835
run: forge test -vvv
36+
37+
# Surfaces storage-layout drift for the upgradeable contracts on every run
38+
# so proxy-incompatible changes show up in the PR log.
39+
- name: Dump proxy storage layouts
40+
run: |
41+
echo "::group::ListaV3Factory"
42+
forge inspect ListaV3Factory storage-layout
43+
echo "::endgroup::"
44+
echo "::group::NonfungiblePositionManager"
45+
forge inspect NonfungiblePositionManager storage-layout
46+
echo "::endgroup::"

README.md

Lines changed: 32 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,50 @@
1-
## Foundry
1+
# Lista V3
22

3-
**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
3+
A Uniswap V3 fork renamed to Lista V3, with the factory and NFT position manager deployed behind upgradeable proxies.
44

5-
Foundry consists of:
5+
## Contracts
66

7-
- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
8-
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
9-
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
10-
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
7+
### Core (`src/core/`)
118

12-
## Documentation
9+
- `ListaV3Factory` — canonical factory. **Upgradeable** via `TransparentUpgradeableProxy`; initializer replaces the constructor and seeds the 500 / 3000 / 10000 fee tiers.
10+
- `ListaV3Pool` — AMM pool, CREATE2-deployed by the factory, **not upgradeable**.
11+
- `ListaV3PoolDeployer` — base of the factory; writes transient parameters so the pool constructor can read them back, keeping `POOL_INIT_CODE_HASH` constant for off-chain address derivation.
1312

14-
https://book.getfoundry.sh/
13+
### Periphery (`src/periphery/`)
1514

16-
## Usage
15+
- `NonfungiblePositionManager` — wraps positions as ERC-721 NFTs. **Hybrid upgradeable**: the ERC-721 / permit stack uses `ERC721PermitUpgradeable` (storage-backed, behind the proxy), while `factory` and `WETH9` remain constructor-set `immutable`s in the implementation bytecode. Every impl upgrade must re-pass the original `(factory, WETH9)` to the new impl's constructor.
16+
- `SwapRouter`, `V3Migrator`, `Quoter`, `QuoterV2`, `NonfungibleTokenPositionDescriptor`, `PairFlash` — deployed normally (non-upgradeable).
1717

18-
### Build
18+
## Deployment outline
1919

20-
```shell
21-
$ forge build
22-
```
23-
24-
### Test
25-
26-
```shell
27-
$ forge test
28-
```
29-
30-
### Format
31-
32-
```shell
33-
$ forge fmt
34-
```
35-
36-
### Gas Snapshots
20+
1. Deploy `ListaV3Factory` impl (no constructor args).
21+
2. Deploy a shared `ProxyAdmin`.
22+
3. Deploy `TransparentUpgradeableProxy(factoryImpl, proxyAdmin, abi.encodeWithSelector(ListaV3Factory.initialize.selector, owner))`. Treat the proxy address as *the* factory from here on.
23+
4. Deploy `NonfungiblePositionManager` impl with `(factoryProxy, WETH9)`.
24+
5. Deploy `TransparentUpgradeableProxy(npmImpl, proxyAdmin, abi.encodeWithSelector(NonfungiblePositionManager.initialize.selector, tokenDescriptor))`.
25+
6. Deploy `SwapRouter`, `V3Migrator`, etc. against the factory proxy address.
3726

38-
```shell
39-
$ forge snapshot
40-
```
27+
Operational notes:
4128

42-
### Anvil
29+
- The Factory/NPM implementations should have their initializers consumed post-deploy (e.g. `initialize(0xdead)` / `initialize(0xdead, 0xdead)`) to close the Parity-style impl-takeover window.
30+
- Pool addresses are derived from the factory proxy via `PoolAddress.computeAddress`. If `ListaV3Pool` bytecode is ever changed, `PoolAddress.POOL_INIT_CODE_HASH` must be recomputed — the value in `src/periphery/libraries/PoolAddress.sol` is only valid for the currently-checked-in pool source and build settings.
4331

44-
```shell
45-
$ anvil
46-
```
32+
## Build & test
4733

48-
### Deploy
34+
Uses Foundry with Solidity 0.7.6.
4935

50-
```shell
51-
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
36+
```sh
37+
forge build
38+
forge test
5239
```
5340

54-
### Cast
41+
The end-to-end flow (pool creation, mint, swap, increase / decrease liquidity, collect, transfer, burn) runs in `test/periphery/FullFlowTest.t.sol`, which also exercises the proxy wiring and asserts NPM initializer state / ERC-165 registrations.
5542

56-
```shell
57-
$ cast <subcommand>
58-
```
43+
## Dependencies
5944

60-
### Help
45+
Git submodules under `lib/`:
6146

62-
```shell
63-
$ forge --help
64-
$ anvil --help
65-
$ cast --help
66-
```
47+
- `forge-std`
48+
- `openzeppelin-contracts` at `v3.4.2-solc-0.7` — non-upgradeable contracts and proxy wrappers (`TransparentUpgradeableProxy`, `ProxyAdmin`).
49+
- `openzeppelin-contracts-upgradeable` at `v3.4.2-solc-0.7` — upgradeable ERC-721, Initializable, Context, ERC-165.
50+
- `solidity-lib` — Uniswap math helpers.

script/Deploy.s.sol

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity =0.7.6;
3+
pragma abicoder v2;
4+
5+
import 'forge-std/Script.sol';
6+
import 'forge-std/console.sol';
7+
8+
import {ListaV3Factory} from '../src/core/ListaV3Factory.sol';
9+
import {NonfungiblePositionManager} from '../src/periphery/NonfungiblePositionManager.sol';
10+
import {SwapRouter} from '../src/periphery/SwapRouter.sol';
11+
12+
import {ProxyAdmin} from 'lib/openzeppelin-contracts/contracts/proxy/ProxyAdmin.sol';
13+
import {TransparentUpgradeableProxy} from 'lib/openzeppelin-contracts/contracts/proxy/TransparentUpgradeableProxy.sol';
14+
15+
/// @title Deploy Lista V3 factory / NPM / SwapRouter
16+
/// @notice Multi-chain deploy. WETH9 is resolved by chain id with built-in values for
17+
/// common networks; on unknown chains the `WETH9` env var is required. Owner addresses
18+
/// come from env. Run per chain with `--rpc-url $RPC` + either `--private-key`
19+
/// or a `--ledger` / hardware signer.
20+
///
21+
/// Required env:
22+
/// OWNER = factory owner (fee-tier admin)
23+
///
24+
/// Optional env (with defaults):
25+
/// PROXY_ADMIN_OWNER = owner of ProxyAdmin (defaults to OWNER)
26+
/// TOKEN_DESCRIPTOR = NFT position descriptor address (defaults to 0x0, disables tokenURI)
27+
/// WETH9 = WETH9 address (required on unknown chains; overrides built-ins)
28+
///
29+
/// Usage:
30+
/// forge script script/Deploy.s.sol:Deploy --rpc-url $RPC --broadcast --verify
31+
contract Deploy is Script {
32+
struct Deployment {
33+
address proxyAdmin;
34+
address factoryImpl;
35+
address factoryProxy;
36+
address npmImpl;
37+
address npmProxy;
38+
address swapRouter;
39+
}
40+
41+
function run() external returns (Deployment memory out) {
42+
uint256 chainId;
43+
assembly {
44+
chainId := chainid()
45+
}
46+
47+
address weth9 = _resolveWeth9(chainId);
48+
address owner = vm.envAddress('OWNER');
49+
address proxyAdminOwner = vm.envOr('PROXY_ADMIN_OWNER', owner);
50+
address tokenDescriptor = vm.envOr('TOKEN_DESCRIPTOR', address(0));
51+
52+
require(owner != address(0), 'OWNER=0');
53+
require(weth9 != address(0), 'WETH9=0');
54+
55+
console.log('--- Lista V3 deploy ---');
56+
console.log('chainId:', chainId);
57+
console.log('network:', _chainLabel(chainId));
58+
console.log('owner:', owner);
59+
console.log('proxyAdminOwner:', proxyAdminOwner);
60+
console.log('WETH9:', weth9);
61+
console.log('tokenDescriptor:', tokenDescriptor);
62+
63+
vm.startBroadcast();
64+
65+
ProxyAdmin proxyAdmin = new ProxyAdmin();
66+
if (proxyAdminOwner != address(this) && proxyAdminOwner != proxyAdmin.owner()) {
67+
proxyAdmin.transferOwnership(proxyAdminOwner);
68+
}
69+
70+
// Factory: impl + proxy
71+
ListaV3Factory factoryImpl = new ListaV3Factory();
72+
// Consume the impl's initializer so it can't be hijacked on-chain.
73+
factoryImpl.initialize(address(0xdead));
74+
75+
bytes memory factoryInit = abi.encodeWithSelector(ListaV3Factory.initialize.selector, owner);
76+
TransparentUpgradeableProxy factoryProxy =
77+
new TransparentUpgradeableProxy(address(factoryImpl), address(proxyAdmin), factoryInit);
78+
79+
// NPM: impl + proxy. factory/WETH9 are constructor immutables on the impl; every
80+
// future upgrade MUST re-pass the exact same (factoryProxy, WETH9) to the new impl.
81+
NonfungiblePositionManager npmImpl = new NonfungiblePositionManager(address(factoryProxy), weth9);
82+
npmImpl.initialize(address(0xdead));
83+
84+
bytes memory npmInit = abi.encodeWithSelector(NonfungiblePositionManager.initialize.selector, tokenDescriptor);
85+
TransparentUpgradeableProxy npmProxy =
86+
new TransparentUpgradeableProxy(address(npmImpl), address(proxyAdmin), npmInit);
87+
88+
// SwapRouter is not upgradeable; it's a plain deploy against the factory proxy.
89+
SwapRouter swapRouter = new SwapRouter(address(factoryProxy), weth9);
90+
91+
vm.stopBroadcast();
92+
93+
out = Deployment({
94+
proxyAdmin: address(proxyAdmin),
95+
factoryImpl: address(factoryImpl),
96+
factoryProxy: address(factoryProxy),
97+
npmImpl: address(npmImpl),
98+
npmProxy: address(npmProxy),
99+
swapRouter: address(swapRouter)
100+
});
101+
102+
console.log('--- deployed ---');
103+
console.log('ProxyAdmin:', out.proxyAdmin);
104+
console.log('Factory impl:', out.factoryImpl);
105+
console.log('Factory proxy:', out.factoryProxy);
106+
console.log('NPM impl:', out.npmImpl);
107+
console.log('NPM proxy:', out.npmProxy);
108+
console.log('SwapRouter:', out.swapRouter);
109+
}
110+
111+
/// @dev Built-in WETH9 addresses keyed by chain id. Any explicit `WETH9` env var
112+
/// overrides. Add entries here when onboarding a new chain.
113+
function _resolveWeth9(uint256 chainId) internal returns (address) {
114+
address override_ = vm.envOr('WETH9', address(0));
115+
if (override_ != address(0)) return override_;
116+
117+
if (chainId == 1) return 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // Ethereum — WETH
118+
if (chainId == 11155111) return 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14; // Sepolia — WETH (Uniswap canonical)
119+
if (chainId == 56) return 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; // BNB Chain — WBNB
120+
if (chainId == 97) return 0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd; // BNB Chain testnet — WBNB
121+
122+
revert('WETH9 not set: pass WETH9=0x... or add chain id to _resolveWeth9');
123+
}
124+
125+
function _chainLabel(uint256 chainId) internal pure returns (string memory) {
126+
if (chainId == 1) return 'ethereum';
127+
if (chainId == 11155111) return 'sepolia';
128+
if (chainId == 56) return 'bnb';
129+
if (chainId == 97) return 'bnb-testnet';
130+
if (chainId == 31337) return 'anvil';
131+
return 'unknown';
132+
}
133+
}

0 commit comments

Comments
 (0)