Skip to content
Merged
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
9 changes: 8 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@
"Bash(bun x:*)",
"Bash(git commit -m ':*)",
"Bash(git push:*)",
"Bash(gh pr:*)"
"Bash(gh pr:*)",
"Bash(sed -i \"57s/title='Pool Registry'/title='No Pools Loaded'/\" ui/ts/components/SecurityPoolsOverviewSection.tsx)",
"Bash(sed -i \"65s|title={<AddressValue address={pool.securityPoolAddress} />}|title={getQuestionTitle\\(pool.marketDetails\\)}|\" ui/ts/components/SecurityPoolsOverviewSection.tsx)",
"Bash(echo \"EXIT:$?\")",
"WebFetch(domain:docs.uniswap.org)",
"Bash(bunx:*)",
"WebFetch(domain:app.uniswap.org)",
"WebFetch(domain:gateway.thegraph.com)"
]
}
}
Empty file added .codex
Empty file.
19 changes: 12 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ Run each command individually and address any issues before proceeding to the ne
bun tsc
```
- **UI-only exception**: If the change only affects `ui/` and does not touch `solidity/`, generated contract artifacts, or anything that depends on refreshed contract output, run:
```bash
bun x tsc --noEmit
```
```bash
bun x tsc
```
(We emit JS even for UI-only changes so the live-reload watcher notices the rebuild.)
- **Contract exception**: If contracts or generated contract outputs changed, run the full `bun tsc`.

2. **Tests**: Run all tests
```bash
bun test
```
- If tests require Anvil and the `anvil` executable is missing, install it with:
```bash
bun run install:anvil
```

3. **Code formatting**:
```bash
Expand Down Expand Up @@ -84,7 +89,7 @@ This test-driven approach ensures:

## Notes

- This is a TypeScript project. Do not inspect or work from `js/` files anywhere in the repository when there is a corresponding TypeScript source file.
- The project compiles TypeScript sources to JavaScript before testing (`bun tsc`). This happens automatically via the test scripts.
- The root `bun tsc` runs the full generation pipeline before type-checking. Use `bun x tsc --noEmit` only for `ui/`-only changes that do not require regenerated contract outputs.
- Never edit files directly in any `js/` directory. Changes may be overwritten by TypeScript compilation. Always use the corresponding `.ts` or `.tsx` source files.
- - This is a TypeScript project. Do not inspect or work from `js/` files anywhere in the repository when there is a corresponding TypeScript source file.
- - The project compiles TypeScript sources to JavaScript before testing (`bun tsc`). This happens automatically via the test scripts.
- - The root `bun tsc` runs the full generation pipeline before type-checking. UI-only changes still need to emit JS so the watcher reloads correctly; use `bun x tsc` instead of `--noEmit`.
- - Never edit files directly in any `js/` directory. Changes may be overwritten by TypeScript compilation. Always use the corresponding `.ts` or `.tsx` source files.
38 changes: 16 additions & 22 deletions solidity/ts/testsuite/simulator/AnvilWindowEthereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,6 @@ function parseSnapshotId(value: unknown) {
return value
}

function parseBlock(value: unknown): GetBlockReturn {
if (typeof value !== 'object' || value === null) {
throw new Error('Invalid eth_getBlockByNumber response: block is not an object')
}
if (!('timestamp' in value) || typeof value.timestamp !== 'string') {
throw new Error('Invalid eth_getBlockByNumber response: missing timestamp')
}
if (!/^0x([a-fA-F0-9]{1,64})$/.test(value.timestamp)) {
throw new Error(`Invalid eth_getBlockByNumber response: invalid timestamp ${value.timestamp}`)
}

return {
timestamp: BigInt(value.timestamp),
}
}

export interface AnvilWindowEthereum {
addStateOverrides: (stateOverrides: StateOverrides) => Promise<void>
manipulateTime: (blockTimeManipulation: BlockTimeManipulation) => Promise<void>
Expand All @@ -94,6 +78,8 @@ export const getDefaultAnvilRpcUrl = (): string => (process.platform === 'win32'

export const getMockedEthSimulateWindowEthereum = async (rpcUrl?: string): Promise<AnvilWindowEthereum> => {
const ANVIL_RPC = rpcUrl ?? process.env['ANVIL_RPC'] ?? getDefaultAnvilRpcUrl()
let currentTimestamp = 0n
let snapshotTimestamp = 0n

// Validate RPC endpoint points to localhost only for test security
const validateLocalhostUrl = (url: string): void => {
Expand All @@ -115,6 +101,7 @@ export const getMockedEthSimulateWindowEthereum = async (rpcUrl?: string): Promi
// Make JSON-RPC request to Anvil
let requestId = 0
const request = async (args: { method: string; params?: unknown[] | unknown | undefined }): Promise<unknown> => {
let nextBlockTimestamp: bigint | undefined
// For eth_sendTransaction, simulate first to catch reverts early
const params = ensureArray(args.params)
if (args.method === 'eth_sendTransaction' && params[0]) {
Expand All @@ -126,10 +113,10 @@ export const getMockedEthSimulateWindowEthereum = async (rpcUrl?: string): Promi
throw simulationError
}

const latestBlock = parseBlock(await request({ method: 'eth_getBlockByNumber', params: ['latest', false] }))
nextBlockTimestamp = currentTimestamp + 1n
await request({
method: 'evm_setNextBlockTimestamp',
params: [`0x${(latestBlock.timestamp + 1n).toString(16)}`],
params: [`0x${nextBlockTimestamp.toString(16)}`],
})
}

Expand Down Expand Up @@ -164,6 +151,9 @@ export const getMockedEthSimulateWindowEthereum = async (rpcUrl?: string): Promi
// For eth_getTransactionReceipt, return the receipt even if status === '0x0' (reverted)
// Callers can check the status field themselves
ensureDefined(json.result, 'json.result is undefined')
if (nextBlockTimestamp !== undefined) {
currentTimestamp = nextBlockTimestamp
}
return json.result
}

Expand Down Expand Up @@ -218,6 +208,7 @@ export const getMockedEthSimulateWindowEthereum = async (rpcUrl?: string): Promi
params: [`0x${blockTimeManipulation.deltaToAdd.toString(16)}`],
})
await request({ method: 'evm_mine', params: [] })
currentTimestamp += blockTimeManipulation.deltaToAdd
} else if (blockTimeManipulation.type === 'SetTimestamp') {
const hexTimestamp = `0x${blockTimeManipulation.timeToSet.toString(16)}`
try {
Expand All @@ -236,17 +227,16 @@ export const getMockedEthSimulateWindowEthereum = async (rpcUrl?: string): Promi
})
}
await request({ method: 'evm_mine', params: [] })
currentTimestamp = blockTimeManipulation.timeToSet
}
}

const getTime = async (): Promise<bigint> => {
const block = await getBlock()
return block.timestamp
return currentTimestamp
}

const getBlock = async (): Promise<GetBlockReturn> => {
const raw = await request({ method: 'eth_getBlockByNumber', params: ['latest', false] })
return parseBlock(raw)
return { timestamp: currentTimestamp }
}

const advanceTime = async (amountInSeconds: bigint) => {
Expand Down Expand Up @@ -275,17 +265,21 @@ export const getMockedEthSimulateWindowEthereum = async (rpcUrl?: string): Promi
}

const anvilSnapshot = async (): Promise<string> => {
snapshotTimestamp = currentTimestamp
const result = await request({ method: 'anvil_snapshot', params: [] })
return parseSnapshotId(result)
}

const anvilRevert = async (snapshotId: string): Promise<void> => {
await request({ method: 'anvil_revert', params: [snapshotId] })
currentTimestamp = snapshotTimestamp
}

const resetToCleanState = async (): Promise<void> => {
await request({ method: 'anvil_reset', params: [] })
await request({ method: 'anvil_setNextBlockBaseFeePerGas', params: ['0x0'] })
currentTimestamp = 0n
snapshotTimestamp = 0n
}

const setNextBlockBaseFeePerGasToZero = async (): Promise<void> => {
Expand Down
8 changes: 7 additions & 1 deletion solidity/ts/testsuite/simulator/useIsolatedAnvilNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,13 @@ export const useIsolatedAnvilNode = () => {
beforeEach(async () => {
const currentEthereum = ensureDefined(anvilWindowEthereum, 'Isolated Anvil node was not initialized')
const currentSnapshotId = ensureDefined(snapshotId, 'Missing Anvil snapshot for test isolation')
await currentEthereum.anvilRevert(currentSnapshotId)
try {
await currentEthereum.anvilRevert(currentSnapshotId)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
if (!errorMessage.includes('Resource not found')) throw error
await currentEthereum.resetToCleanState()
}
await currentEthereum.setNextBlockBaseFeePerGasToZero()
snapshotId = await currentEthereum.anvilSnapshot()
})
Expand Down
Loading