Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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