Skip to content
Open
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
2 changes: 2 additions & 0 deletions .changeset/uniswapx-abi-provenance-tooling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
49 changes: 49 additions & 0 deletions sdks/uniswapx-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,52 @@ Returns the domain, types, and values used to generate an EIP-712 signature over

Returns the order hash. This is used as a key to track order fulfillment on-chain. It may also be useful as a unique order identifier for off-chain purposes.

## Maintaining the contract ABIs

The typed contract bindings in [`src/contracts`](./src/contracts) are generated by
`bun run typechain` from the JSON artifacts in [`abis/`](./abis). Those artifacts are
**compiled contract artifacts (or hand-authored ABIs) copied from upstream contract repos**
(mainly [Uniswap/UniswapX](https://github.com/Uniswap/UniswapX), plus
[Uniswap/permit2](https://github.com/Uniswap/permit2) and a few third-party/interface ABIs) —
they are not built from source in this repo. `abis/` is a build-time input only; it is not part
of the published package (`"files": ["dist"]`).

### Why they can silently go stale

Nothing here re-derives the ABIs, and their upstream commit was never recorded, so they can
drift out of sync with the deployed contracts without anyone noticing: an upstream reactor,
resolver, or hook changes its ABI, the copy in `abis/` keeps describing the old interface, and
the generated bindings keep encoding/decoding against the stale shape. There is **no automatic
drift check** — unlike `v2-sdk`/`v3-sdk`, which recompute an init-code hash from a published
`@uniswap/*-core` npm artifact and assert equality, no UniswapX contract artifact is published
to npm to diff against. This is the same failure mode as the liquidity-launcher stale-bytecode
incident fixed in [Uniswap/sdks#648](https://github.com/Uniswap/sdks/pull/648); the blast radius
here is smaller (the embedded bytecode only deploys test mocks), but the mechanism is identical.

### How to refresh them

Provenance for every artifact — upstream repo, source path, contract, solc version, and whether
it is script-refreshed or hand-maintained — is recorded in
[`abis/PROVENANCE.md`](./abis/PROVENANCE.md) and the machine-readable
[`scripts/abis.manifest.json`](./scripts/abis.manifest.json). To refresh the UniswapX/permit2
artifacts from local checkouts and regenerate the bindings in one deterministic step:

```bash
UNISWAPX_REPO=/path/to/UniswapX \
PERMIT2_REPO=/path/to/permit2 \
UNISWAPX_COMMIT=<sha> PERMIT2_COMMIT=<sha> \
bun run regenerate:abis
```

The script checks out the target commit, initializes submodules, runs `forge build`, copies the
mapped `out/<Contract>.sol/<Contract>.json` artifacts into `abis/`, regenerates `src/contracts`
via typechain, and records the resolved commit back into the manifest and `PROVENANCE.md`. It
requires [Foundry](https://getfoundry.sh) plus the upstream repos' submodules, fails loudly and
writes nothing if `forge` is missing / a build fails / an artifact is absent, and exposes escape
hatches (`FORGE_BIN`, `SKIP_CHECKOUT`, `SKIP_SUBMODULES`, `SKIP_BUILD`) for constrained
toolchains. The third-party/interface/hand-authored ABIs listed in `PROVENANCE.md` are left
untouched — update those by hand and record the change. Treat any refresh as a deliberate,
reviewed step: a bump only changes the output when the upstream ABI actually changed (the first
scripted refresh will also normalize the historical hand-saved JSON formatting of some files,
which is formatting-only noise).

85 changes: 85 additions & 0 deletions sdks/uniswapx-sdk/abis/PROVENANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# ABI provenance

The JSON files in this directory are **compiled contract artifacts (or hand-authored ABIs)
copied from upstream contract repos**, not generated from source in this repo. At build time
`bun run typechain` turns them into the ethers bindings committed under
[`src/contracts`](../src/contracts) (see the `typechain` script in `package.json`).

They are **build-time inputs only** — they are not part of the published package
(`"files": ["dist"]`), so reformatting or refreshing them never changes what consumers install
directly; it changes the generated `src/contracts` bindings, which are compiled into `dist`.

## Drift risk

Nothing here re-derives these artifacts and, historically, nothing recorded which upstream
commit each one came from. That means they can **silently go stale**: an upstream reactor,
resolver, or hook can change its ABI while the copy in this repo keeps describing the old
interface, and the generated bindings keep encoding/decoding against the outdated shape. There
is **no automatic upstream-drift check** — there is no published npm artifact to diff against
(unlike `v2-sdk`/`v3-sdk`, which recompute an init-code hash from `@uniswap/v2-core` /
`@uniswap/v3-core` and assert equality in their `constants.test.ts`).

This is the same class of problem as the liquidity-launcher stale-bytecode incident fixed in
[Uniswap/sdks#648](https://github.com/Uniswap/sdks/pull/648) — a hand-copied upstream artifact
pinned to nothing, with no refresh path. The blast radius here is smaller: the embedded
bytecode only deploys mocks in integration tests, and the ABIs drive typed encode/decode rather
than a CREATE2 address prediction that holds user funds. But the mechanism is identical, so this
package gets the same treatment: a recorded manifest + a deterministic refresh script.

See [`../scripts/regenerate-abis.ts`](../scripts/regenerate-abis.ts) and
[`Maintaining the contract ABIs`](../README.md#maintaining-the-contract-abis) in the README.

## Pinned upstream commits

The commit each artifact was copied from was **not recorded** when these files were first added
(the monorepo migration squashed their history), so the pins below are empty until the next
scripted refresh stamps them. `bun run regenerate:abis` writes the resolved commit back here.

- **uniswapx** pinned commit: `` (unrecorded — https://github.com/Uniswap/UniswapX)
- **permit2** pinned commit: `` (unrecorded — https://github.com/Uniswap/permit2)

## What is refreshed by the script vs. hand-maintained

`scripts/regenerate-abis.ts` refreshes only the `scripted` foundry artifacts owned by
Uniswap/UniswapX and Uniswap/permit2. Everything else is maintained by hand — update it
deliberately and record the change here.

### Scripted (refreshed from a `forge build` of the upstream repo)

| ABI file | Upstream repo | Source · contract | solc |
| --- | --- | --- | --- |
| `ExclusiveDutchOrderReactor.json` | UniswapX | `src/reactors/ExclusiveDutchOrderReactor.sol` · `ExclusiveDutchOrderReactor` | 0.8.19 |
| `ExclusiveFillerValidation.json` | UniswapX | `src/sample-validation-contracts/ExclusiveFillerValidation.sol` · `ExclusiveFillerValidation` | 0.8.19 |
| `HybridAuctionResolver.json` | UniswapX | `src/v4/resolvers/HybridAuctionResolver.sol` · `HybridAuctionResolver` | 0.8.30 |
| `ISuperstateTokenV4.json` | UniswapX | `src/interfaces/ISuperstateTokenV4.sol` · `ISuperstateTokenV4` (interface) | 0.8.28 |
| `MockERC20.json` | UniswapX | `test/util/mock/MockERC20.sol` · `MockERC20` | 0.8.16 |
| `OrderQuoter.json` | UniswapX | `src/lens/OrderQuoter.sol` · `OrderQuoter` | 0.8.19 |
| `OrderQuoterV4.json` | UniswapX | `src/v4/lens/OrderQuoterV4.sol` · `OrderQuoterV4` | 0.8.30 |
| `PriorityOrderReactor.json` | UniswapX | `src/reactors/PriorityOrderReactor.sol` · `PriorityOrderReactor` | 0.8.24 |
| `Reactor.json` | UniswapX | `src/v4/Reactor.sol` · `Reactor` | 0.8.30 |
| `RelayOrderReactor.json` | UniswapX | `src/reactors/RelayOrderReactor.sol` · `RelayOrderReactor` | 0.8.24 |
| `SwapRouter02Executor.json` | UniswapX | `src/sample-executors/SwapRouter02Executor.sol` · `SwapRouter02Executor` | 0.8.19 |
| `TokenTransferHook.json` | UniswapX | `src/v4/hooks/TokenTransferHook.sol` · `TokenTransferHook` | 0.8.30 |
| `V2DutchOrderReactor.json` | UniswapX | `src/reactors/V2DutchOrderReactor.sol` · `V2DutchOrderReactor` | 0.8.24 |
| `V3DutchOrderReactor.json` | UniswapX | `src/reactors/V3DutchOrderReactor.sol` · `V3DutchOrderReactor` | 0.8.24 |
| `Permit2.json` | permit2 | `src/Permit2.sol` · `Permit2` | 0.8.17 |

### Hand-maintained (the script leaves these untouched)

| ABI file | Origin | Why not scripted |
| --- | --- | --- |
| `ERC1967Proxy.json` | OpenZeppelin (`contracts/proxy/ERC1967/ERC1967Proxy.sol`, solc 0.8.27) | Third-party `contracts/` layout, not a UniswapX `src/` path. |
| `MockDSTokenInterface.json` | `contracts/MockDSTokenInterface.sol` (solc 0.8.26) | Third-party `contracts/` layout test mock. |
| `Proxy.json` | `contracts/Proxy.sol` (solc 0.8.26) | Third-party `contracts/` layout. |
| `DSTokenInterface.json` | interface ABI, no compiler metadata | Hand-maintained interface consumed by `PermissionedTokenValidator`. |
| `MockSuperstateTokenV4.json` | hardhat-format artifact (`hh-sol-artifact-1`) | Built with a different toolchain; provenance unrecorded. |
| `multicall2.json` | hand-authored ABI array | Standard Multicall2 helper, no bytecode. |
| `deploylessMulticall2.json` | hand-authored ABI array | Standard Multicall helper, no bytecode. |

> Note on stale generated files: `src/contracts` also contains `DutchOrderReactor.ts` and
> `DutchLimitOrderReactor.ts` (plus their factories) that have **no** backing ABI here — they
> are leftovers from ABIs that were removed without regenerating `src/contracts` from scratch
> (typechain does not delete). `EventWatcher.ts` still imports the `FillEvent` type from
> `../contracts/DutchOrderReactor`, so they cannot simply be deleted without a code change.
> This is tracked as a follow-up, not addressed by the refresh script (which is additive, like
> the build's own `typechain` step, and does not remove orphaned bindings).
3 changes: 2 additions & 1 deletion sdks/uniswapx-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"test": "run-s test:unit test:integration",
"test:unit": "bun test src/",
"test:integration": "cd integration && bun install && bun run test",
"typechain": "typechain --target=ethers-v5 --out-dir src/contracts --glob ./abis/**/*.json"
"typechain": "typechain --target=ethers-v5 --out-dir src/contracts --glob ./abis/**/*.json",
"regenerate:abis": "bun run scripts/regenerate-abis.ts"
},
"dependencies": {
"@ethersproject/abi": "^5.5.0",
Expand Down
Loading
Loading