chore: merge main into dev-2.3.0, keeping the 2.3.0 work intact - #519
Merged
Merged
Conversation
…514) * revert: bound router command input decoding (#497) Reverts d203e7f. This change was deployed to mainnet at 0x0542093271A31f6FC1DADB232bd59eeb27de780F but was never intended to ship. Removes the checkInputLength calldata bounds checks, the BytesLib hardening, the SWEEP uint160 truncation fix, the UNWRAP_WETH_EXACT command (0x0f), and unwrapWETH9Exact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * revert: allow V4_SWAP within an existing PoolManager unlock (#491) Reverts 9e9a780. This change was deployed to mainnet at 0x0542093271A31f6FC1DADB232bd59eeb27de780F but was never intended to ship. It is the root cause of audit finding M-01: V4 deltas accrue under the router regardless of which contract called execute(), so within one foreign unlock, one call can leave debt that a later call settles from a different msgSender(). Removing the fast path restores the pre-#491 behaviour: a nested V4_SWAP reverts with AlreadyUnlocked. This supersedes #502, #512 and #513, which existed only to gate the fast path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: regenerate UniversalRouter bytecode size snapshot 23705 -> 23303 after reverting #491 and #497. The hardhat gas snapshots under test/integration-tests/gas-tests/__snapshots__/ are deliberately left untouched: they require a mainnet fork (FORK_URL, block 20010000) to regenerate and cannot be derived by hand. They must be regenerated with 'UPDATE_SNAPSHOT=1 yarn test:hardhat --grep gas' before this merges. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: regenerate hardhat gas snapshots after reverting #491 and #497 65 snapshots updated across 5 suites. One snapshot removed: 'UNWRAP_WETH_EXACT partial amount', whose command (0x0f) no longer exists after the #497 revert. Regenerated with UPDATE_SNAPSHOT=1 against a mainnet fork at block 20010000. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * build: bump v4-periphery to main with the #584 revert Moves lib/v4-periphery 07336f2 -> a7af5b3, which is v4-periphery main after #601 merged (revert of #584, tolerate hook-funded input on exact-output swaps). #564's exact-output partial-fill revert is retained; the only src/ change against the previous pin is V4Router.sol (+4/-23). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: regenerate bytecode snapshot after v4-periphery bump 23303 -> 23233. Hardhat gas snapshots unchanged: the #584 revert only removes hook-funded exact-output paths, which the gas suite does not exercise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: record 2.1.2 deployment addresses Record the UniversalRouter 2.1.2 addresses deployed across all 24 chains. Every address was verified onchain before recording: 24,380-byte runtime, all ten constructor parameters matching the deploy script, immutables embedded in the deployed bytecode, and source verified (Etherscan V2 where available, Sourcify otherwise; Tempo on its own Sourcify instance). Four chains additionally record an UnsupportedProtocolV2_1_2 entry. Their deploy scripts left `unsupported` unset, so the script deployed a throwaway revert stub first, which consumed a nonce and shifted the router address. Recording it explains why those routers do not share an address with the rest of their nonce group. arc.json, megaeth.json and robinhood.json are new; these chains had never been recorded. Their pre-existing routers were read from the SDK and confirmed onchain before being included. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(script): point base-sepolia and unichain-sepolia at live v4 deployments Both scripts referenced PoolManager and PositionManager addresses that have no recent onchain activity. A router deployed against them cannot execute any v4 command. Confirmed by log activity rather than by documentation, which has been unreliable here. On Base Sepolia the previous PoolManager (0xf7F5aB3D) has produced no logs in the last 50k blocks, while 0x05E73354 is actively emitting. The same holds on Unichain Sepolia for 0x9cB26A71 versus 0x00B036B5. Sepolia needed no change; it was corrected on main in 020e1b7. These addresses are what UniversalRouter 2.1.2 was actually deployed with on both chains, so main's scripts now describe what is live. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(script): add arc, megaeth and robinhood params; fill in unsupported Two gaps surfaced while deploying 2.1.2. Ink, Unichain, Worldchain and Zora left `unsupported` unset, so each deploy created a fresh UnsupportedProtocol stub before the router. That consumed a nonce and shifted the router off the address its nonce group would otherwise share, and left an orphaned stub that nothing references. Filling in the stubs deployed during 2.1.2 stops the next deploy repeating it. Each address was confirmed onchain to be a 60-byte contract that reverts with UnsupportedProtocolError. Arc, MegaETH and Robinhood had no deploy parameters on main at all; they were added on release/2.1.x and never ported. Without them a deploy from main silently skips those chains. Ported with permissionsAdapterFactory added to match the RouterParameters shape on main. All 24 chains' parameters now match the constructor arguments of the routers actually deployed onchain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: fix forge fmt violation in PermissionedV4 test Pre-existing on main and unrelated to this branch, but it fails `yarn lint:check` and so would show a red CI run on any PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Revert "style: fix forge fmt violation in PermissionedV4 test" This reverts commit 8ec8ca8. * chore: drop the mislabelled 2.1.1 entry for megaeth 0x47837eb80db5908eabba9105626d9b348bea7b02 was recorded as UniversalRouterV2_1_1, taken from universal-router-sdk's CHAIN_CONFIGS. It cannot be 2.1.1: it was deployed 2026-01-30, and tag 2.1.1 was not cut until 2026-05-22. It is on the 2.1 line — SPOKE_POOL() resolves, which the 2.0 router on that chain does not — but at 21,738 bytes it is well short of 2.1.1's 24,546, consistent with a build predating the per-hop slippage work. The 2.1.1 release notes warn about exactly this: "Do not identify existing 2.1.0 bytecode as 2.1.1." There is no 2.1.0 tag to bytecode-match against, so rather than invent a label the entry is dropped. UniversalRouterV2 (0x48fd0352...) is kept: verified on Etherscan as UniversalRouter, no SPOKE_POOL(), and 19,499 bytes matching the 2.0 routers on ink, tempo, unichain and zora. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Used for the 2.2.0 deployment at 0x9aFe3C497e19501DB228F28CdBdD29bC98F65DBa on 2026-09-18 (tx 0xd9eda6c912e49c0ce7e15bfd7a6d5dc91de796cc08231aec80df191341244f34), built at tag 2.2.0 (64027f3). Across SpokePool and PermissionsAdapterFactory wired; reuses the UnsupportedProtocol at 0xEEE3…4BcF. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Brings main's deploy-address records and deploy scripts (#516, #517) onto the 2.3.0 line without letting main's #514 revert delete the work this branch is built on. ## Why this needs a deliberate resolution main's `aebfa39` (#514) reverted #491 (nested V4_SWAP) and #497 (bounded command input decoding). This branch forked at `fb25ff0`, before that, so it still contains both -- and #502's nested-unlock opt-in gate is 10 lines that sit *inside* the `if (poolManager.isUnlocked())` block #491 added. #502 cannot exist without #491. A plain merge re-applies the revert. Nine files conflict, which is the visible half. The dangerous half is silent: git cleanly auto-merges `Commands.sol` and `Payments.sol`, deleting command `0x0f` (`UNWRAP_WETH_EXACT` / `unwrapWETH9Exact`) with no conflict marker at all. Resolution: - `contracts/`, `test/`, `snapshots/`, `README.md`, `lib/v4-periphery` -> keep this branch's version - `deploy-addresses/`, `script/` -> take main's Note this is deliberately not `git revert aebfa39`. That would restore #491 and #497 but not #502, which was authored against them. ## Verified - `contracts/`, `test/`, `snapshots/`, `README.md` and the submodule pin are **byte-identical to dev-2.3.0 (8b8595a)** -- this merge changes no code - the 34 incoming files are all under `deploy-addresses/` (24) or `script/` (10), and each is **byte-identical to origin/main (b67f125)** - #491's nested branch, #502's opt-in guard, #497's `checkInputLength` (17 call sites) and #497's `0x0f` command are all still present - `RouterParameters.sol`, the struct the new deploy scripts construct, is identical on main and this branch, so the scripts compile against these contracts `lib/v4-periphery` stays pinned at `0cc6e164`. The bump to the restored-#584 periphery stack is a separate commit, once Uniswap/v4-periphery#604 lands. After this, #512's diff stops understating the change: GitHub was diffing against merge-base `fb25ff0`, which predates the revert, so it showed 16 files / +687 -71 when what would actually land on main is 59 files / +1854 -239. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gretzke
approved these changes
Sep 22, 2026
dianakocsis
added a commit
that referenced
this pull request
Sep 28, 2026
#519 brought main in as a squash merge, so git still saw main's commits as new, including revert #514. A plain merge would re-apply that revert and silently delete the #491/#497 work (Payments, BytesLib, V3ToV4Migrator and their tests) in files where nothing conflicts. Resolve to the dev-2.3.0 tree and take only what main has that dev-2.3.0 lacks, #520 and #521: - deploy-addresses/hyperevm.json and its CLAUDE.md entry - permissionsAdapterFactory for Base, Ink and HyperEVM lib/v4-periphery stays at dev-2.3.0's 0cc6e16; the bump to dev-ur-2.3.0 happens in #508. With main now an ancestor, later merges of main will not re-apply #514. Bytecode is unchanged at 23,657. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR for the reviewer
This merge changes no contract code. The diff is 34 files: 24 deploy-address records and 10 deploy scripts, all from #516 and #517, each byte-identical to
origin/main. If you see anything undercontracts/,test/orsnapshots/in this diff, something is wrong — there should be nothing.That is the entire review. The rest of this description explains why a merge this boring needed a hand-written resolution.
The problem
main's
aebfa39(#514) reverted #491 (nestedV4_SWAP) and #497 (bounded command input decoding).dev-2.3.0forked atfb25ff0, before that revert, so it still contains both.That matters because #502 depends on #491. #502 is +10 lines / −0 in
Dispatcher.sol— a guard that sits inside theif (poolManager.isUnlocked())block #491 created:Remove #491 and there is no branch for #502 to guard. They are one unit: #491 added the feature and carried audit finding M-01; #502 fixes M-01 by replacing chain-state inference with an explicit opt-in. main took the other remedy for M-01 — delete the feature outright — which is correct for main, since main tracks the deployed 2.1.2 router. 2.3.0 takes the keep-and-gate path.
Why a plain merge is unsafe
A default merge re-applies the revert. Nine files conflict:
Those are the visible half. The dangerous half is silent: git cleanly auto-merges
contracts/libraries/Commands.solandcontracts/modules/Payments.sol, deleting command0x0f(UNWRAP_WETH_EXACT/unwrapWETH9Exact) with no conflict marker. Resolving only the nine conflicts and committing would ship that loss invisibly.Worse, resolving the nine "sensibly" — taking main's side, since main is the release branch — yields
#491: 0, #502: 0, #497: 0. All three gone, including the audited work.Resolution applied
Deliberately not
git revert aebfa39— that would restore #491 and #497 but not #502, which was authored on top of them.Verification
contracts/,test/,snapshots/,README.md, submodule pin vsdev-2.3.0(8b8595a)deploy-addresses/(24) orscript/(10)origin/main(b67f125)0x0f/ #497checkInputLengthRouterParameters.sol(the struct the new scripts construct)Merge parents:
8b8595a(dev-2.3.0) andb67f125(main, including #517).Follow-ups
lib/v4-peripherystays pinned at0cc6e164. The bump to the restored-#584 periphery stack is a separate commit once Uniswap/v4-periphery#604 lands.Once this merges, #512's diff stops understating the change. GitHub was diffing #512 against merge-base
fb25ff0, which predates the revert, so it rendered as 16 files / +687 −71. What would actually land on main is 59 files / +1854 −239. Anyone auditing #512 before now was reviewing roughly a third of it.This also makes
dev-2.3.0a true superset of main, so future main→dev merges are conflict-free.Moving
dev-2.3.0makes #507 stale; the restack (#507 → #508 → #509, with gas snapshots regenerated at each level) follows.🤖 Generated with Claude Code
AI-Generated Description
TL;DR for the reviewer
This merge changes no contract code. The diff is 34 files: 24 deploy-address records and 10 deploy scripts, all from #516 and #517, each byte-identical to
origin/main. If you see anything undercontracts/,test/orsnapshots/in this diff, something is wrong — there should be nothing.That is the entire review. The rest of this description explains why a merge this boring needed a hand-written resolution.
The problem
main's
aebfa39(#514) reverted #491 (nestedV4_SWAP) and #497 (bounded command input decoding).dev-2.3.0forked atfb25ff0, before that revert, so it still contains both.That matters because #502 depends on #491. #502 is +10 lines / −0 in
Dispatcher.sol— a guard that sits inside theif (poolManager.isUnlocked())block #491 created:Remove #491 and there is no branch for #502 to guard. They are one unit: #491 added the feature and carried audit finding M-01; #502 fixes M-01 by replacing chain-state inference with an explicit opt-in. main took the other remedy for M-01 — delete the feature outright — which is correct for main, since main tracks the deployed 2.1.2 router. 2.3.0 takes the keep-and-gate path.
Why a plain merge is unsafe
A default merge re-applies the revert. Nine files conflict:
Those are the visible half. The dangerous half is silent: git cleanly auto-merges
contracts/libraries/Commands.solandcontracts/modules/Payments.sol, deleting command0x0f(UNWRAP_WETH_EXACT/unwrapWETH9Exact) with no conflict marker. Resolving only the nine conflicts and committing would ship that loss invisibly.Worse, resolving the nine "sensibly" — taking main's side, since main is the release branch — yields
#491: 0, #502: 0, #497: 0. All three gone, including the audited work.Resolution applied
Deliberately not
git revert aebfa39— that would restore #491 and #497 but not #502, which was authored on top of them.Changes
deploy-addresses/(24 files): RecordUniversalRouterV2_1_2addresses across all networks, add new chain files (arc.json,megaeth.json,robinhood.json)script/deployParameters/(10 files): Add deploy scripts for Arc, HyperEVM, MegaETH, Robinhood; update BaseSepolia, Ink, Unichain, UnichainSepolia, Worldchain, Zora with live addressesVerification
contracts/,test/,snapshots/,README.md, submodule pin vsdev-2.3.0(8b8595a)deploy-addresses/(24) orscript/(10)origin/main(b67f125)0x0f/ #497checkInputLengthRouterParameters.sol(the struct the new scripts construct)8b8595a(dev-2.3.0) andb67f125(main, including #517).Follow-ups
lib/v4-peripherystays pinned at0cc6e164. The bump to the restored-#584 periphery stack is a separate commit once Uniswap/v4-periphery#604 lands.Once this merges, #512's diff stops understating the change. GitHub was diffing #512 against merge-base
fb25ff0, which predates the revert, so it rendered as 16 files / +687 −71. What would actually land on main is 59 files / +1854 −239. Anyone auditing #512 before now was reviewing roughly a third of it.This also makes
dev-2.3.0a true superset of main, so future main→dev merges are conflict-free.Moving
dev-2.3.0makes #507 stale; the restack (#507 → #508 → #509, with gas snapshots regenerated at each level) follows.