From 7d8776a6d5be0b68c85ca898aeb872f2f9011ca2 Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Wed, 5 Nov 2025 21:18:12 -0800 Subject: [PATCH 01/15] change OPFaultGameFinder when 0 gameTypes --- README.md | 1 + contracts/op/OPFaultGameFinder.sol | 115 ++++++++++++++++------------- contracts/op/OPFaultVerifier.sol | 54 +++++++++----- contracts/op/OPStructs.sol | 5 -- scripts/deploy-gas.ts | 2 +- scripts/l2-primary.test.ts | 8 +- scripts/serve.ts | 55 ++++++++------ scripts/verify-gas.ts | 14 +--- src/op/OPFaultRollup.ts | 80 ++++++-------------- test/bun-describe-fix.ts | 12 +-- test/debug/proxy.ts | 19 ++--- test/gateway/base-sepolia.test.ts | 2 +- test/gateway/common.ts | 9 +-- test/providers.ts | 2 +- test/research/eip-2935/fetch.ts | 32 ++++++++ test/research/eip-4788/fetch.ts | 35 +++++---- test/research/parentBeacon.ts | 28 +++++++ 17 files changed, 246 insertions(+), 227 deletions(-) create mode 100755 test/research/eip-2935/fetch.ts create mode 100755 test/research/parentBeacon.ts diff --git a/README.md b/README.md index e775620c..8f7abf68 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ If you are interested in building a solution for another chain, please take a lo * eg. if `APE`, serves L2 → L3 instead of L1 → L2 → L3 * Include `--depth=#` to adjust commit depth * Include `--step=#` to adjust commit step + * Include `--gameTypes=1,2...` to set allowed game types for `OPFaultRollup` * Use [`PROVIDER_ORDER`](./test/providers.ts#L479) to customize global RPC provider priority. * Use `PROVIDER_ORDER_{CHAIN_NAME}` to customize per-chain RPC provider priority. * Use `PROVIDER_{CHAIN_NAME}` to customize per-chain RPC provider override. diff --git a/contracts/op/OPFaultGameFinder.sol b/contracts/op/OPFaultGameFinder.sol index 531eada8..30aacdec 100755 --- a/contracts/op/OPFaultGameFinder.sol +++ b/contracts/op/OPFaultGameFinder.sol @@ -1,8 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import { IDisputeGameFactory, IDisputeGame, IFaultDisputeGame } from './OPInterfaces.sol'; -import { OPFaultParams, FinalizationParams } from './OPStructs.sol'; +import { + IOptimismPortal, + IDisputeGameFactory, + IDisputeGame, + IFaultDisputeGame +} from './OPInterfaces.sol'; +import {OPFaultParams} from './OPStructs.sol'; // https://github.com/ethereum-optimism/optimism/issues/11269 @@ -10,21 +15,19 @@ import { OPFaultParams, FinalizationParams } from './OPStructs.sol'; uint256 constant CHALLENGER_WINS = 1; uint256 constant DEFENDER_WINS = 2; -// https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/src/dispute/lib/Types.sol -uint32 constant GAME_TYPE_CANNON = 0; -uint32 constant GAME_TYPE_PERMISSIONED_CANNON = 1; - error GameNotFound(); +struct Config { + uint256 finalityDelay; + uint256 respectedGameType; +} + contract OPFaultGameFinder { function findGameIndex( OPFaultParams memory params, uint256 gameCount ) external view virtual returns (uint256) { - FinalizationParams memory finalizationParams = FinalizationParams({ - finalityDelay: params.portal.disputeGameFinalityDelaySeconds(), - gameTypeUpdatedAt: params.portal.respectedGameTypeUpdatedAt() - }); + Config memory config = _config(params.portal); IDisputeGameFactory factory = params.portal.disputeGameFactory(); if (gameCount == 0) gameCount = factory.gameCount(); while (gameCount > 0) { @@ -33,15 +36,7 @@ contract OPFaultGameFinder { uint256 created, IDisputeGame gameProxy ) = factory.gameAtIndex(--gameCount); - if ( - _isGameUsable( - gameProxy, - gameType, - created, - params, - finalizationParams - ) - ) { + if (_isGameUsable(gameProxy, gameType, created, params, config)) { return gameCount; } } @@ -62,10 +57,6 @@ contract OPFaultGameFinder { bytes32 rootClaim ) { - FinalizationParams memory finalizationParams = FinalizationParams({ - finalityDelay: params.portal.disputeGameFinalityDelaySeconds(), - gameTypeUpdatedAt: params.portal.respectedGameTypeUpdatedAt() - }); IDisputeGameFactory factory = params.portal.disputeGameFactory(); (gameType, created, gameProxy) = factory.gameAtIndex(gameIndex); if ( @@ -74,7 +65,7 @@ contract OPFaultGameFinder { gameType, created, params, - finalizationParams + _config(params.portal) ) ) { l2BlockNumber = gameProxy.l2BlockNumber(); @@ -87,48 +78,70 @@ contract OPFaultGameFinder { uint256 gameType, uint256 created, OPFaultParams memory params, - FinalizationParams memory finalizationParams + Config memory config ) internal view returns (bool) { - if (!_isAllowedGameType(gameType, params.allowedGameTypes)) return false; - if (!_isAllowedProposer(gameProxy.gameCreator(), params.allowedProposers)) return false; + // if allowed gameTypes is empty, accept a respected game OR a previously respected game + if ( + params.allowedGameTypes.length == 0 + ? (gameType != config.respectedGameType && + !gameProxy.wasRespectedGameTypeWhenCreated()) + : !_isAllowedGameType(gameType, params.allowedGameTypes) + ) { + return false; + } + if ( + !_isAllowedProposer( + gameProxy.gameCreator(), + params.allowedProposers + ) + ) return false; // https://specs.optimism.io/fault-proof/stage-one/bridge-integration.html#blacklisting-disputegames if (params.portal.disputeGameBlacklist(gameProxy)) return false; - if (!gameProxy.wasRespectedGameTypeWhenCreated()) return false; if (params.minAgeSec > 0) { if (created > block.timestamp - params.minAgeSec) return false; - if ( - gameType == GAME_TYPE_CANNON || - gameType == GAME_TYPE_PERMISSIONED_CANNON - ) { - return IFaultDisputeGame(address(gameProxy)) - .l2BlockNumberChallenged() ? false : true; + (bool ok, bytes memory v) = address(gameProxy).staticcall( + abi.encodeCall(IFaultDisputeGame.l2BlockNumberChallenged, ()) + ); + if (ok && bytes32(v) != bytes32(0)) { + return false; // block number was challenged + } + if (gameProxy.status() != CHALLENGER_WINS) { + return true; // not successfully challenged } - // Testing for an unchallenged game falls back to finalized mode if unknown game type - } - - if ( - created > finalizationParams.gameTypeUpdatedAt && - gameProxy.status() == DEFENDER_WINS - ) { - return ((block.timestamp - gameProxy.resolvedAt()) > - finalizationParams.finalityDelay); } - return false; + // require resolved + sufficiently aged + return + gameProxy.status() == DEFENDER_WINS && + (block.timestamp - gameProxy.resolvedAt()) >= config.finalityDelay; } - function _isAllowedGameType(uint256 gameType, uint256[] memory allowedGameTypes) pure internal returns (bool) { - for (uint i = 0; i < allowedGameTypes.length; i++) { + function _isAllowedGameType( + uint256 gameType, + uint256[] memory allowedGameTypes + ) internal pure returns (bool) { + for (uint256 i; i < allowedGameTypes.length; ++i) { if (allowedGameTypes[i] == gameType) return true; } return false; } - function _isAllowedProposer(address proposer, address[] memory allowedProposers) pure internal returns (bool) { - if (allowedProposers.length == 0) return true; - - for (uint i = 0; i < allowedProposers.length; i++) { + function _isAllowedProposer( + address proposer, + address[] memory allowedProposers + ) internal pure returns (bool) { + for (uint256 i; i < allowedProposers.length; ++i) { if (allowedProposers[i] == proposer) return true; } - return false; + return allowedProposers.length == 0; + } + + function _config( + IOptimismPortal portal + ) internal view returns (Config memory) { + return + Config({ + finalityDelay: portal.disputeGameFinalityDelaySeconds(), + respectedGameType: portal.respectedGameType() + }); } } diff --git a/contracts/op/OPFaultVerifier.sol b/contracts/op/OPFaultVerifier.sol index 57f0c24d..70a18ca1 100755 --- a/contracts/op/OPFaultVerifier.sol +++ b/contracts/op/OPFaultVerifier.sol @@ -3,36 +3,50 @@ pragma solidity ^0.8.0; import {AbstractVerifier, IVerifierHooks} from '../AbstractVerifier.sol'; import {GatewayRequest, GatewayVM, ProofSequence} from '../GatewayVM.sol'; -import {Hashing, Types} from '../../lib/optimism/packages/contracts-bedrock/src/libraries/Hashing.sol'; -import { IOptimismPortal, IOPFaultGameFinder, IDisputeGame, OPFaultParams } from './OPInterfaces.sol'; - - +import { + Hashing, + Types +} from '../../lib/optimism/packages/contracts-bedrock/src/libraries/Hashing.sol'; +import { + IOptimismPortal, + IOPFaultGameFinder, + IDisputeGame, + OPFaultParams +} from './OPInterfaces.sol'; contract OPFaultVerifier is AbstractVerifier { - IOptimismPortal immutable _portal; - IOPFaultGameFinder immutable _gameFinder; + IOPFaultGameFinder public immutable gameFinder; OPFaultParams private _params; constructor( string[] memory urls, uint256 window, IVerifierHooks hooks, - IOPFaultGameFinder gameFinder, + IOPFaultGameFinder gameFinder_, OPFaultParams memory params ) AbstractVerifier(urls, window, hooks) { - _portal = params.portal; - _gameFinder = gameFinder; + gameFinder = gameFinder_; _params = params; } + function portal() external view returns (IOptimismPortal) { + return _params.portal; + } + + function minAgeSec() external view returns (uint256) { + return _params.minAgeSec; + } + + function gameTypes() external view returns (uint256[] memory) { + return _params.allowedGameTypes; + } + + function allowedProposers() external view returns (address[] memory) { + return _params.allowedProposers; + } + function getLatestContext() external view virtual returns (bytes memory) { - return - abi.encode( - _gameFinder.findGameIndex( - _params, - 0 - ) - ); + return abi.encode(gameFinder.findGameIndex(_params, 0)); } struct GatewayProof { @@ -49,11 +63,12 @@ contract OPFaultVerifier is AbstractVerifier { ) external view returns (bytes[] memory, uint8 exitCode) { uint256 gameIndex1 = abi.decode(context, (uint256)); GatewayProof memory p = abi.decode(proof, (GatewayProof)); - (, , IDisputeGame gameProxy, uint256 blockNumber,) = _gameFinder + (, , IDisputeGame gameProxy, uint256 blockNumber, ) = gameFinder .gameAtIndex(_params, p.gameIndex); require(blockNumber != 0, 'OPFault: invalid game'); if (p.gameIndex != gameIndex1) { - (, , IDisputeGame gameProxy1) = _portal + (, , IDisputeGame gameProxy1) = _params + .portal .disputeGameFactory() .gameAtIndex(gameIndex1); _checkWindow(_getGameTime(gameProxy1), _getGameTime(gameProxy)); @@ -77,7 +92,6 @@ contract OPFaultVerifier is AbstractVerifier { } function _getGameTime(IDisputeGame g) internal view returns (uint256) { - return - _params.minAgeSec == 0 ? g.resolvedAt() : g.createdAt(); + return _params.minAgeSec == 0 ? g.resolvedAt() : g.createdAt(); } } diff --git a/contracts/op/OPStructs.sol b/contracts/op/OPStructs.sol index 20132797..fb1a90ca 100644 --- a/contracts/op/OPStructs.sol +++ b/contracts/op/OPStructs.sol @@ -9,8 +9,3 @@ struct OPFaultParams { uint256[] allowedGameTypes; address[] allowedProposers; } - -struct FinalizationParams { - uint256 finalityDelay; - uint64 gameTypeUpdatedAt; -} \ No newline at end of file diff --git a/scripts/deploy-gas.ts b/scripts/deploy-gas.ts index 9a9d1dac..92e28adf 100755 --- a/scripts/deploy-gas.ts +++ b/scripts/deploy-gas.ts @@ -46,7 +46,7 @@ await foundry.deploy({ }); await foundry.deploy({ file: 'OPFaultVerifier', - args: [U, 1, A, [A, A, 0, 0]], + args: [U, 1, A, [A, A, [], []]], libs: { GatewayVM }, }); await foundry.deploy({ diff --git a/scripts/l2-primary.test.ts b/scripts/l2-primary.test.ts index b5337910..598d5b0d 100755 --- a/scripts/l2-primary.test.ts +++ b/scripts/l2-primary.test.ts @@ -173,12 +173,8 @@ async function determineGateway(foundry: Foundry, setup: Setup) { [], rollup.defaultWindow, EthVerifierHooks, - [ - rollup.OptimismPortal, - rollup.GameFinder, - rollup.gameTypeBitMask, - rollup.minAgeSec, - ], + rollup.GameFinder, + rollup.paramTuple, ], libs: { GatewayVM }, }); diff --git a/scripts/serve.ts b/scripts/serve.ts index d2557286..1a840603 100755 --- a/scripts/serve.ts +++ b/scripts/serve.ts @@ -61,6 +61,7 @@ let prefetch = false; let latestBlockTag = ''; let commitDepth: number | undefined = undefined; let commitStep: number | undefined = undefined; +let gameTypes: bigint[] = []; let disableFast = false; let disableCache = false; let disableDouble = false; @@ -82,6 +83,8 @@ const args = process.argv.slice(2).filter((x) => { commitDepth = parseInt(match[1]); } else if ((match = x.match(/^--step=(\d+)$/))) { commitStep = parseInt(match[1]); + } else if ((match = x.match(/^--gameTypes=(\d+(?:,\d+)*)$/))) { + gameTypes = match[1].split(',').map((x) => BigInt(x)); } else if (x === '--dump') { dumpAndExit = true; } else if (x === '--debug') { @@ -375,9 +378,13 @@ async function createGateway(name: string) { (x) => x.chain2 === chain ); if (config) { - return new Gateway( - new OPFaultRollup(createProviderPair(config), config, unfinalized) + const rollup = new OPFaultRollup( + createProviderPair(config), + config, + unfinalized ); + rollup.gameTypes = gameTypes; + return new Gateway(rollup); } } { @@ -534,32 +541,32 @@ function proverDetails(prover: AbstractProver) { }; } +function toJSONValue(x: any): any { + switch (typeof x) { + case 'bigint': + return bigintToJSON(x); + case 'string': + return concealKeys(x); + case 'boolean': + case 'number': + return x; + } + if (Array.isArray(x)) { + return x.map(toJSONValue); + } else if (x && x.constructor === Object) { + return toJSON(x); + } +} + function toJSON(x: object) { - const info: Record = {}; + const json: Record = {}; for (const [k, v] of Object.entries(x)) { - switch (typeof v) { - case 'bigint': { - info[k] = bigintToJSON(v); - break; - } - case 'string': { - info[k] = concealKeys(v); - break; - } - case 'boolean': - case 'number': - info[k] = v; - break; - case 'object': - if (Array.isArray(v)) { - info[k] = v.map(toJSON); - } else if (v && v.constructor === Object) { - info[k] = toJSON(v); - } - break; + const value = toJSONValue(v); + if (value !== undefined) { + json[k] = value; } } - return info; + return json; } // use number when it fits diff --git a/scripts/verify-gas.ts b/scripts/verify-gas.ts index 395e56f4..ed3754ab 100755 --- a/scripts/verify-gas.ts +++ b/scripts/verify-gas.ts @@ -59,19 +59,7 @@ const setups: Setup[] = [ const hooks = await foundry.deploy({ file: 'EthVerifierHooks' }); const verifier = await foundry.deploy({ file: 'OPFaultVerifier', - args: [ - [], - rollup.defaultWindow, - hooks, - gameFinder, - // This is the OPFaultParams struct - [ - rollup.OptimismPortal, - rollup.minAgeSec, - await rollup.gameTypes(), - rollup.allowedProposers(), - ], - ], + args: [[], rollup.defaultWindow, hooks, gameFinder, rollup.paramTuple], libs: { GatewayVM }, }); diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index ac6027f7..8f09543c 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -44,9 +44,8 @@ type ABIFoundGame = { rootClaim: string; }; -const FINDER_MAINNET = '0x165386f8699ce2609a8903e25d00e1debd24a277'; -const FINDER_SEPOLIA = '0xf182be4292749cf414708eb517e858954e09bcb9'; -const FINDER_HOLESKY = ''; +const FINDER_MAINNET = '0x4E63bFa938fA1235A0ad7AaFA5165c6511f970eC'; +const FINDER_SEPOLIA = '0xda1B8f851965BfE75451222a0a898722765747e2'; export class OPFaultRollup extends AbstractOPRollup { static readonly PORTAL_ABI = PORTAL_ABI; @@ -56,7 +55,8 @@ export class OPFaultRollup extends AbstractOPRollup { static readonly FINDERS = new Map([ [CHAINS.MAINNET, FINDER_MAINNET], [CHAINS.SEPOLIA, FINDER_SEPOLIA], - [CHAINS.HOLESKY, FINDER_HOLESKY], + //[CHAINS.HOLESKY, FINDER_HOLESKY], + //[CHAINS.HOODI, FINDER_HOODI], ]); // https://docs.optimism.io/chain/addresses @@ -178,8 +178,8 @@ export class OPFaultRollup extends AbstractOPRollup { // 20240917: delayed constructor not needed readonly OptimismPortal: Contract; readonly GameFinder: Contract; - private _gameTypes: bigint[] = []; - private _allowedProposers: HexAddress[] = []; + public gameTypes: bigint[] = []; + public allowedProposers: HexAddress[] = []; unfinalizedRootClaimTimeoutMs = 30000; constructor( providers: ProviderPair, @@ -203,22 +203,19 @@ export class OPFaultRollup extends AbstractOPRollup { return !!this.minAgeSec; // nonzero => unfinalized } - setGameTypes(gameTypes: bigint[]) { - this._gameTypes = gameTypes; + get paramTuple() { + return [ + this.OptimismPortal.target, + this.minAgeSec, + this.gameTypes, + this.allowedProposers, + ]; } - async gameTypes(): Promise { - return this._gameTypes.length == 0 - ? [await this.fetchRespectedGameType()] - : this._gameTypes; - } - - setAllowedProposers(allowedProposers: HexAddress[]) { - this._allowedProposers = allowedProposers; - } - - allowedProposers(): HexAddress[] { - return this._allowedProposers; + async getGameTypes(): Promise { + return this.gameTypes.length + ? this.gameTypes + : [await this.fetchRespectedGameType()]; } async fetchRespectedGameType(): Promise { @@ -254,15 +251,7 @@ export class OPFaultRollup extends AbstractOPRollup { if (Date.now() > timeout) { throw new Error(`timeout _ensureRootClaim()`); } - index = await this.GameFinder.findGameIndex( - [ - this.OptimismPortal.target, - this.minAgeSec, - await this.gameTypes(), - this.allowedProposers(), - ], - index - ); + index = await this.GameFinder.findGameIndex(this.paramTuple, index); // index = await staticCall( // this.provider1, // this.GameFinder, @@ -286,50 +275,23 @@ export class OPFaultRollup extends AbstractOPRollup { // 20240822: once again uses a helper contract to reduce rpc burden return this._ensureRootClaim( await this.GameFinder.findGameIndex( - [ - this.OptimismPortal.target, - this.minAgeSec, - await this.gameTypes(), - this.allowedProposers(), - ], + this.paramTuple, 0, // most recent game { blockTag: this.latestBlockTag } ) - // this.provider1, - // this.GameFinder, - // FINDER_ABI, - // 'findGameIndex', - // [this.OptimismPortal, this.minAgeSec, this.gameTypeBitMask, 0], - // this.latestBlockTag ); } protected override async _fetchParentCommitIndex( commit: OPFaultCommit ): Promise { return this._ensureRootClaim( - await this.GameFinder.findGameIndex( - [ - this.OptimismPortal.target, - this.minAgeSec, - await this.gameTypes(), - this.allowedProposers(), - ], - commit.index - ) + await this.GameFinder.findGameIndex(this.paramTuple, commit.index) ); } protected override async _fetchCommit(index: bigint) { // note: GameFinder checks isCommitStillValid() const game: ABIFoundGame = ( - await this.GameFinder.gameAtIndex( - [ - this.OptimismPortal.target, - this.minAgeSec, - await this.gameTypes(), - this.allowedProposers(), - ], - index - ) + await this.GameFinder.gameAtIndex(this.paramTuple, index) ).toObject(); if (!game.l2BlockNumber) throw new Error('invalid game'); const commit = await this.createCommit(index, game.l2BlockNumber); diff --git a/test/bun-describe-fix.ts b/test/bun-describe-fix.ts index f9b803b2..2ba5d46b 100755 --- a/test/bun-describe-fix.ts +++ b/test/bun-describe-fix.ts @@ -1,4 +1,4 @@ -import { test, describe as describe0 } from 'bun:test'; +import { describe as describe0 } from 'bun:test'; // bun:test is shit // using a beforeAll() is disgusting for test setup @@ -7,14 +7,8 @@ import { test, describe as describe0 } from 'bun:test'; export function describe(label: string, fn: () => void | Promise) { describe0(label, async () => { - try { - await fn(); // must be awaited - } catch (cause) { - test('init()', () => { - // failure shows up as a synthetic test - throw cause; - }); - } + await fn(); // must be awaited + // 20251105: throw as failure outside of test() instead }); } diff --git a/test/debug/proxy.ts b/test/debug/proxy.ts index 2146a19c..4e16699c 100755 --- a/test/debug/proxy.ts +++ b/test/debug/proxy.ts @@ -18,25 +18,16 @@ const ownerWallet = await foundry.ensureWallet('owner'); const GatewayVM = await foundry.deploy({ file: 'GatewayVM' }); const impl = await foundry.deploy({ file: 'OPFaultVerifier', - args: [ - [], - 0, - ZeroAddress, - [ - ZeroAddress, - ZeroAddress, - 0, - 1, - ], - ], + args: [[], 0, ZeroAddress, [ZeroAddress, ZeroAddress, [], []]], libs: { GatewayVM }, - from: ownerWallet + from: ownerWallet, }); const proxy = await foundry.deploy({ - import: '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol', + import: + '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol', args: [impl, ownerWallet.address, '0x'], - from: ownerWallet + from: ownerWallet, }); console.log(await impl.owner()); diff --git a/test/gateway/base-sepolia.test.ts b/test/gateway/base-sepolia.test.ts index 46983a41..6525dcfb 100755 --- a/test/gateway/base-sepolia.test.ts +++ b/test/gateway/base-sepolia.test.ts @@ -6,5 +6,5 @@ testOPFault(OPFaultRollup.baseSepoliaConfig, { slotDataContract: '0x7AE933cf265B9C7E7Fd43F0D6966E34aaa776411', // https://sepolia.basescan.org/address/0x2D70842D1a1d6413Ce44d0D5FD4AcFDc485540EA#code slotDataPointer: '0x2D70842D1a1d6413Ce44d0D5FD4AcFDc485540EA', - skipCI: true, + skipCI: true }); diff --git a/test/gateway/common.ts b/test/gateway/common.ts index b3c08e4b..994e807d 100755 --- a/test/gateway/common.ts +++ b/test/gateway/common.ts @@ -146,7 +146,7 @@ export function testOPFault( infoLog: !!opts.log, }); await rollup.provider2.getBlockNumber(); // check provider - afterAll(foundry.shutdown); + afterAll(foundry.shutdown); const gateway = new Gateway(rollup); const ccip = await serve(gateway, { protocol: 'raw', log: !!opts.log }); afterAll(ccip.shutdown); @@ -164,12 +164,7 @@ export function testOPFault( opts.window ?? rollup.defaultWindow, hooks, gameFinder, - [ - rollup.OptimismPortal, - rollup.minAgeSec, - await rollup.gameTypes(), - rollup.allowedProposers(), - ], + rollup.paramTuple, ], libs: { GatewayVM }, }); diff --git a/test/providers.ts b/test/providers.ts index 277e58fe..aaf832c7 100755 --- a/test/providers.ts +++ b/test/providers.ts @@ -91,7 +91,7 @@ export const RPC_INFO = new Map( publicHTTP: 'https://mainnet.base.org', //ankr: 'base', // 202405XX: eth_getProof depth is 10000 //infura: 'base-mainnet', // 20250214: eth_getProof depth is still insufficient - //alchemy: 'base-mainnet', // 20251008 eth_getProof depth insufficient.. again + alchemy: 'base-mainnet', drpc: 'base', }, { diff --git a/test/research/eip-2935/fetch.ts b/test/research/eip-2935/fetch.ts new file mode 100755 index 00000000..f6c900b5 --- /dev/null +++ b/test/research/eip-2935/fetch.ts @@ -0,0 +1,32 @@ +// https://eip.tools/eip/eip-2935.md + +import { fetchBlock, fetchStorage, toPaddedHex } from '../../../src/utils.js'; +import { CHAINS } from '../../../src/chains.js'; +import { createProvider } from '../../providers.js'; + +const HISTORY_STORAGE_ADDRESS = '0x0000F90827F1C53a10cb7A02335B175320002935'; +const HISTORY_BUFFER_LENGTH = 8191; +const BLOCK_TAG = 'finalized'; + +const provider = createProvider(CHAINS.MAINNET); + +const blockInfo = await fetchBlock(provider, BLOCK_TAG); +if (!blockInfo) throw new Error('wtf'); + +const blockHash = await provider.call({ + to: HISTORY_STORAGE_ADDRESS, + data: toPaddedHex(blockInfo.number), +}); + +const storage = await fetchStorage( + provider, + HISTORY_STORAGE_ADDRESS, + (parseInt(blockInfo.number) - 1) % HISTORY_BUFFER_LENGTH, + BLOCK_TAG +); + +console.log(blockInfo.hash); +console.log(blockHash); +console.log(); +console.log(blockInfo.parentHash); +console.log(storage); diff --git a/test/research/eip-4788/fetch.ts b/test/research/eip-4788/fetch.ts index b4c8fc06..f9c055af 100755 --- a/test/research/eip-4788/fetch.ts +++ b/test/research/eip-4788/fetch.ts @@ -1,30 +1,33 @@ // https://eips.ethereum.org/EIPS/eip-4788#beacon-block-root-instead-of-state-root -import { toPaddedHex } from '../../../src/utils.js'; +import { fetchBlock, fetchStorage, toPaddedHex } from '../../../src/utils.js'; import { CHAINS } from '../../../src/chains.js'; import { createProvider } from '../../providers.js'; -const provider = createProvider(CHAINS.OP); +const provider = createProvider(CHAINS.MAINNET); // first deployment from 0x0B799C86a49DEeb90402691F1041aa3AF2d3C875 const BEACON_ROOTS_ADDRESS = '0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02'; - const HISTORY_BUFFER_LENGTH = 8191; +const BLOCK_TAG = 'finalized'; -const blockInfo = await provider.getBlock('finalized'); +const blockInfo = await fetchBlock(provider, BLOCK_TAG); if (!blockInfo) throw new Error('wtf'); // these should all be the same -console.log(blockInfo.parentBeaconBlockRoot); -console.log( - await provider.call({ - to: BEACON_ROOTS_ADDRESS, - data: toPaddedHex(blockInfo.timestamp), - }) -); -console.log( - await provider.getStorage( - BEACON_ROOTS_ADDRESS, - (blockInfo.timestamp % HISTORY_BUFFER_LENGTH) + HISTORY_BUFFER_LENGTH - ) +const beaconRoot = await provider.call({ + to: BEACON_ROOTS_ADDRESS, + data: toPaddedHex(blockInfo.timestamp), +}); + +const storage = await fetchStorage( + provider, + BEACON_ROOTS_ADDRESS, + (parseInt(blockInfo.timestamp) % HISTORY_BUFFER_LENGTH) + + HISTORY_BUFFER_LENGTH, + BLOCK_TAG ); + +console.log(blockInfo.parentBeaconBlockRoot); +console.log(beaconRoot); +console.log(storage); diff --git a/test/research/parentBeacon.ts b/test/research/parentBeacon.ts new file mode 100755 index 00000000..06274675 --- /dev/null +++ b/test/research/parentBeacon.ts @@ -0,0 +1,28 @@ +import { chainName, CHAINS } from '../../src/chains.js'; +import { fetchBlock } from '../../src/utils.js'; +import { createProvider } from '../providers.js'; + +console.log( + Object.fromEntries( + await Promise.all( + [ + CHAINS.OP, + CHAINS.BASE, + CHAINS.CELO, + CHAINS.ARB1, + CHAINS.SCROLL, + CHAINS.LINEA, + ].map(async (chain) => { + let ret: string | undefined; + try { + const provider = createProvider(chain); + const block = await fetchBlock(provider); + ret = block?.parentBeaconBlockRoot; + } catch (err) { + ret = String(err); + } + return [chainName(chain), ret]; + }) + ) + ) +); From 8aeced4f0829c4b1c5648d918830c0d48ebb8cd7 Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Wed, 5 Nov 2025 22:21:44 -0800 Subject: [PATCH 02/15] changes for thomas --- contracts/op/OPFaultGameFinder.sol | 46 ++++++++++++++++++------------ scripts/deploy-gas.ts | 6 ++-- test/debug/proxy.ts | 2 +- test/gateway/base-sepolia.test.ts | 2 +- test/gateway/common.ts | 2 +- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/contracts/op/OPFaultGameFinder.sol b/contracts/op/OPFaultGameFinder.sol index 30aacdec..e4f2bc63 100755 --- a/contracts/op/OPFaultGameFinder.sol +++ b/contracts/op/OPFaultGameFinder.sol @@ -17,7 +17,7 @@ uint256 constant DEFENDER_WINS = 2; error GameNotFound(); -struct Config { +struct PortalParams { uint256 finalityDelay; uint256 respectedGameType; } @@ -27,7 +27,7 @@ contract OPFaultGameFinder { OPFaultParams memory params, uint256 gameCount ) external view virtual returns (uint256) { - Config memory config = _config(params.portal); + PortalParams memory portalParams = _portalParams(params.portal); IDisputeGameFactory factory = params.portal.disputeGameFactory(); if (gameCount == 0) gameCount = factory.gameCount(); while (gameCount > 0) { @@ -36,7 +36,15 @@ contract OPFaultGameFinder { uint256 created, IDisputeGame gameProxy ) = factory.gameAtIndex(--gameCount); - if (_isGameUsable(gameProxy, gameType, created, params, config)) { + if ( + _isGameUsable( + gameProxy, + gameType, + created, + params, + portalParams + ) + ) { return gameCount; } } @@ -65,7 +73,7 @@ contract OPFaultGameFinder { gameType, created, params, - _config(params.portal) + _portalParams(params.portal) ) ) { l2BlockNumber = gameProxy.l2BlockNumber(); @@ -78,17 +86,20 @@ contract OPFaultGameFinder { uint256 gameType, uint256 created, OPFaultParams memory params, - Config memory config + PortalParams memory portalParams ) internal view returns (bool) { // if allowed gameTypes is empty, accept a respected game OR a previously respected game if ( - params.allowedGameTypes.length == 0 - ? (gameType != config.respectedGameType && - !gameProxy.wasRespectedGameTypeWhenCreated()) - : !_isAllowedGameType(gameType, params.allowedGameTypes) + !( + params.allowedGameTypes.length == 0 + ? (gameType == portalParams.respectedGameType || + gameProxy.wasRespectedGameTypeWhenCreated()) + : _isAllowedGameType(gameType, params.allowedGameTypes) + ) ) { return false; } + // if no proposer restrictions or proposer is whitelisted if ( !_isAllowedProposer( gameProxy.gameCreator(), @@ -102,17 +113,16 @@ contract OPFaultGameFinder { (bool ok, bytes memory v) = address(gameProxy).staticcall( abi.encodeCall(IFaultDisputeGame.l2BlockNumberChallenged, ()) ); - if (ok && bytes32(v) != bytes32(0)) { - return false; // block number was challenged - } - if (gameProxy.status() != CHALLENGER_WINS) { - return true; // not successfully challenged + // effectively: supportsInterface(IFaultDisputeGame) + if (ok && v.length == 32) { + return bytes32(v) == bytes32(0); // usable if not challenged } } // require resolved + sufficiently aged return gameProxy.status() == DEFENDER_WINS && - (block.timestamp - gameProxy.resolvedAt()) >= config.finalityDelay; + (block.timestamp - gameProxy.resolvedAt()) >= + portalParams.finalityDelay; } function _isAllowedGameType( @@ -135,11 +145,11 @@ contract OPFaultGameFinder { return allowedProposers.length == 0; } - function _config( + function _portalParams( IOptimismPortal portal - ) internal view returns (Config memory) { + ) internal view returns (PortalParams memory) { return - Config({ + PortalParams({ finalityDelay: portal.disputeGameFinalityDelaySeconds(), respectedGameType: portal.respectedGameType() }); diff --git a/scripts/deploy-gas.ts b/scripts/deploy-gas.ts index 92e28adf..9f8d969b 100755 --- a/scripts/deploy-gas.ts +++ b/scripts/deploy-gas.ts @@ -36,17 +36,17 @@ await foundry.deploy({ }); await foundry.deploy({ file: 'DoubleArbitrumVerifier', - args: [U, 1, A, A, 0, true, ['0x']], + args: [U, 1, A, A, 1, true, ['0x']], libs: { GatewayVM, NitroVerifierLib, BoLDVerifierLib }, }); await foundry.deploy({ file: 'OPVerifier', - args: [U, 1, A, A, A, 0], + args: [U, 1, A, A, A, 1], libs: { GatewayVM }, }); await foundry.deploy({ file: 'OPFaultVerifier', - args: [U, 1, A, [A, A, [], []]], + args: [U, 1, A, [A, 1, [], []]], libs: { GatewayVM }, }); await foundry.deploy({ diff --git a/test/debug/proxy.ts b/test/debug/proxy.ts index 4e16699c..6ebf8ff9 100755 --- a/test/debug/proxy.ts +++ b/test/debug/proxy.ts @@ -18,7 +18,7 @@ const ownerWallet = await foundry.ensureWallet('owner'); const GatewayVM = await foundry.deploy({ file: 'GatewayVM' }); const impl = await foundry.deploy({ file: 'OPFaultVerifier', - args: [[], 0, ZeroAddress, [ZeroAddress, ZeroAddress, [], []]], + args: [[], 0, ZeroAddress, [ZeroAddress, 0, [], []]], libs: { GatewayVM }, from: ownerWallet, }); diff --git a/test/gateway/base-sepolia.test.ts b/test/gateway/base-sepolia.test.ts index 6525dcfb..46983a41 100755 --- a/test/gateway/base-sepolia.test.ts +++ b/test/gateway/base-sepolia.test.ts @@ -6,5 +6,5 @@ testOPFault(OPFaultRollup.baseSepoliaConfig, { slotDataContract: '0x7AE933cf265B9C7E7Fd43F0D6966E34aaa776411', // https://sepolia.basescan.org/address/0x2D70842D1a1d6413Ce44d0D5FD4AcFDc485540EA#code slotDataPointer: '0x2D70842D1a1d6413Ce44d0D5FD4AcFDc485540EA', - skipCI: true + skipCI: true, }); diff --git a/test/gateway/common.ts b/test/gateway/common.ts index 994e807d..e9c7faee 100755 --- a/test/gateway/common.ts +++ b/test/gateway/common.ts @@ -146,7 +146,7 @@ export function testOPFault( infoLog: !!opts.log, }); await rollup.provider2.getBlockNumber(); // check provider - afterAll(foundry.shutdown); + afterAll(foundry.shutdown); const gateway = new Gateway(rollup); const ccip = await serve(gateway, { protocol: 'raw', log: !!opts.log }); afterAll(ccip.shutdown); From 5e55e85f9db8f91639ae31f59dab13d5186af7cc Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Thu, 6 Nov 2025 19:55:52 -0800 Subject: [PATCH 03/15] random stuff --- contracts/op/OPFaultVerifier.sol | 2 +- scripts/providers.ts | 30 ++++++++++++++-------- scripts/serve.ts | 4 +++ src/op/OPFaultRollup.ts | 17 +------------ test/providers.ts | 43 +++++++++++++++++++------------- test/rollup/celo-sepolia.ts | 3 +++ 6 files changed, 55 insertions(+), 44 deletions(-) diff --git a/contracts/op/OPFaultVerifier.sol b/contracts/op/OPFaultVerifier.sol index 70a18ca1..008bf16e 100755 --- a/contracts/op/OPFaultVerifier.sol +++ b/contracts/op/OPFaultVerifier.sol @@ -37,7 +37,7 @@ contract OPFaultVerifier is AbstractVerifier { return _params.minAgeSec; } - function gameTypes() external view returns (uint256[] memory) { + function allowedGameTypes() external view returns (uint256[] memory) { return _params.allowedGameTypes; } diff --git a/scripts/providers.ts b/scripts/providers.ts index 6cf8aec0..d30cecaa 100755 --- a/scripts/providers.ts +++ b/scripts/providers.ts @@ -7,20 +7,25 @@ const leftover = new Set(Object.values(CHAINS)); for (const info of RPC_INFO.values()) { leftover.delete(info.chain); - const url = providerURL(info.chain); - console.log( - formatChain(info.chain).padStart(10), - chainName(info.chain).padEnd(16), - `[${info.alchemy ? 'A' : ' '}${info.infura ? 'I' : ' '}${info.ankr ? 'K' : ' '}]`, - url === info.publicHTTP ? '!' : ' ', - url - ); - if (url === info.publicHTTP) { + if (providerURL(info.chain) === info.publicHTTP) { usingPublic.push(info.chain); } } -console.log('\nOrder:', providerOrder()); +console.table( + Array.from(RPC_INFO.values(), (info) => ({ + Chain: formatChain(info.chain), + Name: chainName(info.chain), + Order: providerOrder(info.chain) + .map((x) => (x in info ? abbr(x) : ' ')) + .join(''), + ProviderURL: providerURL(info.chain), + })) +); + +console.log( + `\nDefault Order: ${providerOrder()} [${providerOrder().map(abbr).join('')}]` +); if (usingPublic.length) { console.error(`\n${usingPublic.length} using Public RPC!`); @@ -42,3 +47,8 @@ function formatChain(chain: Chain): string { } return chain.toString(); } + +function abbr(key: string) { + if (key === 'ankr') return 'K'; + return key[0].toUpperCase(); +} diff --git a/scripts/serve.ts b/scripts/serve.ts index 1a840603..1955fa6e 100755 --- a/scripts/serve.ts +++ b/scripts/serve.ts @@ -196,6 +196,10 @@ if (gateway.rollup instanceof TrustedRollup) { config.signer = gateway.rollup.signerAddress; } +if (gateway.rollup instanceof OPFaultRollup) { + config.gameTypes = toJSONValue(await gateway.rollup.getGameTypes()); +} + if (dumpAndExit) { console.log('Config:', config); const t0 = Date.now(); diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index 8f09543c..cfd41b3d 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -180,7 +180,7 @@ export class OPFaultRollup extends AbstractOPRollup { readonly GameFinder: Contract; public gameTypes: bigint[] = []; public allowedProposers: HexAddress[] = []; - unfinalizedRootClaimTimeoutMs = 30000; + unfinalizedRootClaimTimeoutMs = 15000; constructor( providers: ProviderPair, config: OPFaultConfig, @@ -222,14 +222,6 @@ export class OPFaultRollup extends AbstractOPRollup { return this.OptimismPortal.respectedGameType({ blockTag: this.latestBlockTag, }); - // return staticCall( - // this.provider1, - // this.OptimismPortal, - // PORTAL_ABI, - // 'respectedGameType', - // [], - // this.latestBlockTag - // ); } private async _ensureRootClaim(index: bigint) { // dodge canary by requiring a valid root claim @@ -252,13 +244,6 @@ export class OPFaultRollup extends AbstractOPRollup { throw new Error(`timeout _ensureRootClaim()`); } index = await this.GameFinder.findGameIndex(this.paramTuple, index); - // index = await staticCall( - // this.provider1, - // this.GameFinder, - // FINDER_ABI, - // 'findGameIndex', - // [this.OptimismPortal, this.minAgeSec, this.gameTypeBitMask, index] - // ); } } } diff --git a/test/providers.ts b/test/providers.ts index aaf832c7..9ee81180 100755 --- a/test/providers.ts +++ b/test/providers.ts @@ -5,6 +5,9 @@ import { FetchRequest } from 'ethers/utils'; import { GatewayProvider } from '../src/GatewayProvider.js'; import { EventEmitter } from 'node:events'; +// 20240830: so far, alchemy has the best support +const DEFAULT_ORDER = ['alchemy', 'infura', 'ankr', 'drpc', 'public']; + export type RPCInfo = { readonly chain: Chain; readonly publicHTTP: string; @@ -18,6 +21,7 @@ export type RPCInfo = { readonly alchemyPremium?: boolean; readonly drpc?: string; readonly drpcBeacon?: string; + readonly order?: string[]; }; // TODO: this list is incomplete! @@ -89,10 +93,11 @@ export const RPC_INFO = new Map( // https://docs.base.org/docs/network-information#base-mainnet chain: CHAINS.BASE, publicHTTP: 'https://mainnet.base.org', - //ankr: 'base', // 202405XX: eth_getProof depth is 10000 - //infura: 'base-mainnet', // 20250214: eth_getProof depth is still insufficient + ankr: 'base', + infura: 'base-mainnet', alchemy: 'base-mainnet', drpc: 'base', + order: ['drpc', 'alchemy'], }, { // https://docs.base.org/docs/network-information#base-testnet-sepolia @@ -101,7 +106,7 @@ export const RPC_INFO = new Map( ankr: 'base_sepolia', infura: 'base-sepolia', alchemy: 'base-sepolia', // 20250107 eth_getProof depth now seems OK - drpc: 'base-sepolia', // 20250115: no eth_getProof + drpc: 'base-sepolia', }, { // https://docs.arbitrum.io/build-decentralized-apps/reference/node-providers#arbitrum-public-rpc-endpoints @@ -271,11 +276,11 @@ export const RPC_INFO = new Map( publicHTTP: 'https://rpc.redstonechain.com', publicWS: 'wss://rpc.redstonechain.com', }, - // { - // // https://docs.gnosischain.com/about/networks/mainnet - // chain: CHAINS.GNOSIS, - // rpc: 'https://rpc.gnosischain.com', - // }, + { + // https://docs.gnosischain.com/about/networks/mainnet + chain: CHAINS.GNOSIS, + publicHTTP: 'https://rpc.gnosischain.com', + }, { // https://docs.shape.network/documentation/technical-details/network-information chain: CHAINS.SHAPE, @@ -493,17 +498,21 @@ function envForChain(prefix: string, chain: Chain) { } export function providerOrder(chain?: Chain): string[] { - // 20240830: so far, alchemy has the best support - const defaultOrder = ['alchemy', 'infura', 'ankr', 'drpc', 'public']; // global default - const key = 'PROVIDER_ORDER'; - let order: string[] = []; + const orderings = [DEFAULT_ORDER]; // global default let env; - if (chain) env = envForChain(key, chain); - if (!env) env = process.env[key]; // global - if (env) order = env.split(/[,\s+]/).flatMap((x) => x.trim() || []); - - return [...order, ...defaultOrder.filter((x) => !order.includes(x))]; + if (chain) { + const info = RPC_INFO.get(chain); + if (info?.order) { + orderings.push(info.order); // chain-specific default + } + env = envForChain(key, chain); // chain override? + } + if (!env) env = process.env[key]; // global override? + if (env) { + orderings.push(env.split(/[,\s+]/).flatMap((x) => x.trim() || [])); + } + return [...new Set(orderings.reverse().flat())]; } export const PROVIDER_EVENTS = new EventEmitter<{ diff --git a/test/rollup/celo-sepolia.ts b/test/rollup/celo-sepolia.ts index e5d861f3..5d921293 100755 --- a/test/rollup/celo-sepolia.ts +++ b/test/rollup/celo-sepolia.ts @@ -6,6 +6,9 @@ console.log(new Date()); const config = OPFaultRollup.celoSepoliaConfig; const rollup = new OPFaultRollup(createProviderPair(config), config); +// 20251106: OP Succinct => 46 +console.log(await rollup.getGameTypes()); + const commits = await rollup.fetchRecentCommits(8); console.log(commits[0]); const v = commits.map((x) => Number(x.index)); From c2768e030706e874e244808a6d0cdb7585c14160 Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Fri, 7 Nov 2025 13:40:28 -0800 Subject: [PATCH 04/15] bump blocksmith, update finder deployments, add finder deploy script --- bun.lockb | Bin 70827 -> 71192 bytes package.json | 2 +- scripts/deploy-finder.ts | 33 +++++++++++++++++++++++++++++++++ src/op/OPFaultRollup.ts | 4 ++-- 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100755 scripts/deploy-finder.ts diff --git a/bun.lockb b/bun.lockb index 137e8779a5b7fa0203838a501fbd715c31b0fc20..c4b8893bdd91a8c2d9952e398a59fa408de9fa60 100755 GIT binary patch delta 11231 zcmeHNd3;nww!U?f4!MC&LiQ$vB!n$O)+Tfo=?0V~T*4+0f^11>l8}92=^&;v5(PCN z92gb_MVV0)95A9p*%2`;vIv6#1abHcgU`Y7jq@ZZ`o44fwuTvW=KcMw-|y>F=hXV0 zI#qpdcfGd7bM02oXX0bChW6ffEaue}tBfbFPCXU3IoTTWZjf}lre|2w)d%aIeP>Cq zkT~sHksTR2`7JH4e*{UA^8oTbD;oONy^3lbVR%iJq)w06C# z%3f1bWUpEeo$dDOlA^L2Nviddq@K{HLUx0+>hicjk^-T}Ko5tk^p>QqkWsKJk`&+! z!>1n%k3jlBmeF7l8tsGXCTM?0wNS__kUa0*kp7UX$RQ$?I?R~08rmch>P)^aDqHnB zvvaF!DlyF}``qfH*$!zF%6TeP7>=p#h2)X*@WH+GMqQR0@xk@xRL41TOG~8Os)CB# zs%ra6*ty*Y@B_%Ppyv4Cn={;3Ieq_C-`Fht}nQ zM;BO!LNcXgm@ZFaZdFm-oN7s`m|Nz^FP~-Kpxd30n7OkCl7}tSm(jEr>AFU+lun_ZGyV1v%}0-{{A&4uJq zs~u(e#YHvJ6581W(XLr6mhKMNA0r8!p?%$ttK^Q@-2Cz?=H>Rc?my?j?g(29 zxD%4ywF#2fah0ww>FKinMz1#ylGn{e|EDl>N6Ac-he&mja}+Y%Q6mB|7}C85+*^sg z!F!@ttR(e-yaS0Q&P$LW={(s-lCYG{>yU7g^Azknu)fuP+-XN(S75jIb9wGHNQJTt zeMh=Or`ldR&t4@-UZ@FoI#rC!@}Fnm9?3oOPb1PnLwvNmc};>OVXd80A$fxCeKMFj zdqqaM{Q~ORwtM7_v0GkReXi~Kp3Z)K8|-;agS-koo3{I;iq0X1oQTm8kG1^0+n0xi z%{#S9(BK9eZSSoJiE0z9a-g8}PF8uSpxRDWae$gT*<>%7Zo`%;Q@YVAs>xxriMOd4 zpSP&ZXp_wzlJ z+EASW42U+Ame@YLtit*$Kvne}pf{fPs9^A>GN2s?0y#%>03=~@TEWKq^ZV^ zQ@wGHFfCAxwuoA4i?S)ZVM~P#k%hj#hQ>Wh9A`=*j&jyqF*r}x4BGO%L22D>N;nRG zj#8QGgDr-9Xo<9_YlcF|x%#P(NTswGo7}N0)y7zrpG?MZ{82zlub{N?YniCD5 zLrbMQQ5lBrI6el@9sdkvGUreO>jxg2p)q6EPWepN;3*Npemh`52ETC@>SI~KDHAh3^DRyQ&m?hnMZ(XkX-g4D+6wb3e!(F=Y_az{Ay zwE-T|>$7D01b`kfq$rGKbm9oV6+;AGea=Ko?k}J*x*q#e;V#)S=fbFvYPVFVH zQK@EiNv>C>>nyo_g|1gHf38re8`?{@SLt?^?9#aa+vfqCSb74rtRUS>a{Yx`>A%Y0 z_60n%MO=|0?hiF_GxzkIY(;Vc9uzSow2n&~08aOkT>iLL>XN)HPwP5M9%~uE^_Byi z{u9adpV1ByS4Qh-CAUUm$rVVKt8}>08W~uJp)6vfIkTf*E$XGC}#jpEIE8GvVzoJ za&MP4>)n$3`4r&oeI3AakM!RFN5%gD-2XQKC&)T&;5H~+a9fw(>he2C1Lsl59eZ#k zU2@hFA1n>J^kN06y<}4d-OiHx!GkA1VoSjS{5cfDVep2G(JS!_dlIzE@ zQJ0*J*Y)<22TFvUJz;_5b_4Z#EIDhPfKRSCNH+{-D^h!@o3vBZ*HwJiiOK`~dXj2x z{PiStAE3XUq`#h|I2Zo^oTO3z?j$AC&>Aw#Q|aTOMrs_Y3J+=>noUcF8EN1!RTyaL zuxv^lZlu3L>p*G4v*|C;HVjvVLN}nT9bu%b5vu4&>qcbLh)g5hfo7!4%xt;^ZCj=) z{HP7uDF|8B8FO_Eg1)X<5ba;mW~6z@xF9(+(^-z(#C_|c<>vqidebOn5N=4gd5P-P6xl~s!(a&bnu%2 ze$cWga|ZZ9+crZLW2g<K1wrYgqKmYLv}3x2t(c!+Xx!7mT|piLwt5B#8&=BXl^ znxPfugI~TXa;PXD{APh4w1>$w3;duhnx%?q)Bh1f^AjAG8hCs#r=lpslR|zZz9MMeAz7Z!Y*jYoyG%;0JBn zT(xQJyt&3YwQS{E8_gXP?timx!Qk!XpH#elBB*=8r8S$r@{TX;nVVWS-tWRU-~Rs9 zTZWy^K`&L^IJ40Ehvz?!9Q&JXZ;!m>@sBauv)K23-$TBwKX0+{JZO2vSiWW3$Uk&` zwR~K`u+JKD-d*5Wx_jSOqaH{c^W>j)r8foyZt{u^ z#r=c!g(k6dhA?iy>oI<4bT&cyHzhR`i>4P=P7qu*VlieJg0fxFt~&f=>HeqZ9A!1; ziwjLV8q0rb(e(5)w*>h|2hj(9ebCRl-Dzw?PoK4KF<-oDyiN9<>9l!gP%J;cb6Ntx zFWS={n3wKxe=YFarL756}td2wX+IYXDF2 z1z;2KBDKC1UbhvwZNMLZ?Z6J833wCO39wUme!GBofOmo2z#d>f&;~z$)^8 zZ~U(V|3c1x5=ahU0Wc334e*QG`#>4L3Y8;M3h)mUHca(zkY57qxxWAxfm6VTz;A&y zz%k%Bz;Af`*4F|Y1fBs_0Q`dY1i<IKXSgYshby&jO)9El|NVM$y@1f~Ns0PQ^ub=M<54d55wJYXik?{548oDcAO8^5#hd)Q{^ z90r&973O>3Hn6K1A4vQ6avapL*O>&bz#G7ez%pPyz@GF4I7T>DJOGY+bgjL7Vy|c~ zvHX_AA;+QU3H+cZZ{+zUm)q3Q-Oy8wzJNXm#6@k4HN=Jz#M=FDF;e{GN1ye1ge3#0LND& zum97?JP9lX8h|H&$AQJbZvbA7dcX;=x7a%zD~|%~rEb6?U?EToxZ{SIF90}xI40PO z92d-*mp2pOKN+>am;!|((*eor$!qxt!0zOZ-ELzKxxK}cdJN!6vuE6s<4N&EmH<5H zKQG-=uyGw;%D{g3U{|nVB|yL?U^&2c0dUz1z!;jgJ2J^_;o2(z))_oawsHQq0EcNZ z(2ut5j>%=#?*4Zmp9ic0cvox(coqHtxW{*o{Ss_E{_la`0j~p_0Y8B4TYwjUF_g3? zFgOr7uIZk`HsmuXXHVpKuFK=`9C*C;{Md$2_!r(yuyp|51a<-*z~A=b18MUWI=bgR zI|g*R|N8V6(-Y&9;!{$z-G?Q1+8@0!zbfbI%P29&o8yyQYvF?u_ur-{NsBiRh)>kQ zic5}q#qLoP>%aDvOLA-M)isD@vh6kD$uDQGSuCbT7$Zw~Md$aJD>Q{SADegNV z%Y80cjHL?_WRVn~gxTQB0=_V!-)(zl{B_aL=YPi~$rukmJkqp%Ch;oG-`7{{q?h(( zh?{g}pDE=QTKJ;Hsg$4zIhVff5G1SvQ{ofjlO^|!6s7$B_dg%E{#{u-Iv_qN4J8{W zZoe6SQsnM8$8Gg=jo7x}v9JErbEApdr=oqLwxtkt&gb*K_#$`MX(>oFHyzmDC}dne z7<({Gjxtc<*|4r0u z&64E4C$ntR8x!v=Iyn&~{WWh$f2V=(nFhG;6kJ_$v1Z5UquyFM+=|28%XL9#;OGiR zM8m=RQ38@?u(5j4)9;0ayKnZSC#e&<)L+?(3P~mG#(9%?gq9yRi&b>!@JZL& z1bd>38rt^02~Yn=-Y<|%zLa<*Ol+deBU$0@TRTt9TD4>Mg_bL_fakeusfu;}44C!MAiCKShS#?aN6q3Q8PAx+ zH7Y;q`ZUWSVYK;ZGIrMaqgmnZYd1bOvLkFSJbhbt66R7uwqqvjlbmB_Tvlj2W|Hv= z|H`psxnnq8Iu_Q|ebJ_=rEcYrf~`F@7RkJrsONt?3{7K?n=z|t$4z1#%|D(M?!IM{ z@UnIPpBwTPp$Q)&80;EdIc^Gf->w;*aPURb(L+HReXV>X1)ea62SmBHTV7?4frC2w zrJ|hOjv;d?_k>AYp@sOIORGAv1$oi6V_`=vMHOAGe_cq%4wGE3IBcyjp9 zZ@5C*aFRph?UM!imflTir_8c;A|z`kf*F6k^VZRJzdK4@8y|;{^>i|}L*?lLSG0xS zW7qe`=>gxji!d5=rhmBmM%SR=D{IsDdGjjsD#0;K&lK*yqI9O?pYs+{+yR$k($JOW zU%GUr3;vQ8C(ZQzkvPi!DB~Xc2=B|^e)QwkI!>8qf7<*J`0x1iC6l;LJIWrqo#yK= ze)tR3v>DKIm-=h2FzXTVA5ha4M8@|}*(YKDhj6<7Ni?1~l*{?P{IziF3O5i*br*$A1zg+Ou%5FmsA0g@1skd;h8g3ts7 zAqYr3ltrz83zi}lOckhtAc#y@Q)LsB+P?M!t-jT_3qgy%zjNnajV=1=AK?9bzH^@Q ztmm9(nR{pM=9f*ocAC~Z;xhkoyCKKB<@HA!W=**A_=pSEXWv`Yp`6+HMQ%s>`%8XP zWDgS(U;A3~qXVbErls|du-3JMn#Q2Gmn1c3$&%y`nGW3o>CmMWvJdoaXcP=tS6|xD zP*GaH8an5fda5dF8YC&PyCn689tPPL@}Fj(Tn-ru{R*TVGR{wuA|Vf%BuSB^?B;XG z2t>v(XgwhZKn6h0DJ@=5hC!-5_`vlxIgN>|r==xFU9FnTih3t)VKri2+%RP{6*qqYR zy7&r@^se6SHApSLwxprHqNcRev!J1($|KFKsF@SLpiat-Vh8L}bEcMnMe$X&B}EPB zrK+O3qCv`}j&9*iawyO#_k%kXDm{E_L?MPB8#X%>GpP?GvVza1w@ zXxn@h63#ZSL_Q|Fc`GD>)O-&4Y=FMau*)dXoSAJbAWv!a!qR$4I)v(oU2{6RXK4&) z3}bX>bpKtq^tRa%V>E9VB1xFg<{6M|MMYUnZ9NuX0tI)E4#MPV`)uZn2TF0^D+kOsfN2!k@eWZu8V`lH{hf{^{~d-6%WIC12}Cs{>u4nY@8+ zkxxpH+w8^plt-4JbkUbug52V9@&>u(%RQ)PuuF`htYGfBCD<)r^P~1)m*T>59s!pm z3JyvUizzF_Egw>7b%;y;T%q<5mxv@K)NL-sdKyn_1Jae>ATaYtB+d@_($TV0|jd97}x0>{HR6u}e!6#1PX z%C@0d zq$p9?Tim;eIwCO^G@q5ZVJS)rG@p;Tv8jGg9@LuU2B#>Y*rK}Wfhl4Vwe)o>JCMgS zXr>OB{Re1l6*`6%iL-~NN}>*HiZV^tx)bJA*$j;%jWD3+OVGF%nWLwKVb5^89$MMM z&=R#WZnyh84Xf!IG&UBl#-xZu%8GF-4LBcCk*Cds@-j5;S>hF@T-P<<>T}@K_Sw#B zc^k*aqSJdk|U=EdrCFabLc1)Ia4UGif_NC>A#f(0X!wlu^)l_rT)V6osJip7f&*tcK&zc-5g3)a>5R zSR0rh@gR8zxy5Qy2E%KV+=qIY2Fy@*Y8mVnIpoFXN>bw8^2z>`9q*D&1K2ck+5q0v zIbrEaBT^}7hZ`Aqvj^H(tru(t`CkKQwZkQ*k=Nl?Uci@97K-3NWQzRRJ(NAfCEV09 z#4X3iQMSLU?Zl8{iiOvsgiC-V68?j-aANV7k9M)_0kC}~8DN8HS69g``{?;yrJ(rX z(M{b^f(e#jD9@{Qb(P#JY|t*2-2lwiE|%PTHsE^jRJ*!LF3-{P?;xABA}-*Hm^5vG zTrF3USaSYEz=lo*eBDX10n@ZnpX7G@h0Ah=p3jo&%>p`W-c@pbot|IE<8w$ofD0M`Uo1^v5i7XvB)NX0R{G(L$o78PQGs zpdaQua0GChqkuzj9Prgua`_3sK-i-E?VU1y@(eS!O+-CHK<<(jPJul6S2Q(ho95FOO|Pg0EOePWIDfe@JdHh%o zosL(I%umPZPsb^i0iPj19jCZ!Z5-7=qcT-W&9u-jGF8!yZe-@ub!a)GRXk5`8J$m? zM_cGNv>uc-CZEQPvC!@@s!-?_v`%P+S*qwoJG1iXmsu9FWUInL1=;yDHQPc5p#_qn z=98alp=wpdj}-@??S~dMRu!RCF*cvd$6Dw#wBBUP$tP=$g_h>1BAmR?PC$zvrwSW2 zj?1S-<1F+cG&{wO&!>UoEwpyLDx&Bzv`f%3Ca5BsS|;SvstFeQ3|b7O<>pgrF8s?? zMJ(Nbb{$&IL{;>sEfeA2!~ptwVz#)4vL?a5N$_uyD&puCv`%P+lT|T@c20(Wli^>U zD&nai5B}xBKWIZpnF9Z&z`rS~NT37I_Ct%xSA~-*^5I`T{DYQ6wyE%MD*T(OiWKrf zI{_`eKox1!SOEVD;2*Sfikk-iroq2ys>qunpzamxS(gA4up+yy|c>1p>hJVHI4_Y4CO5k4!{3}uM%aRw` z325!?Gm(%xhg+n&V_$-;UBajN-KkZW$>>| z6(w{7+I46-<*F#9E#>g99R5KoqpS+}R{{SjR8c{fm3UDwfh^XqTX6%vVJdwakZq^Wh(~Wt3JA|LWmiy(%81 z8_=#p%kilA!DovH{(0aZw3U?A0RI}`UxO-I=oYk2XoU+@{9dGLnQ+d8U4Ec3${J+0*+oMY*75EB}v1`kNHvuZ~UBvEm-H2Y0ZR zwq@%o1sAqetz0g|KKf$S74c-->6Tf-Qe^f0L|X|Ngo;Py7=A{=18s9%jIW5ln2kS7 zwEn20WfIk`^G6>`*VPJofBEOMe&r%%$|o^w;0` zE>>0nKIqM~_w~?>KX4YV7QnfD$Nmi9i=TfukMG8}1HK*yT5HJbB=&_y+(3#=<)w z5$pgjfnS21;8$Q5*bQC*zXp5wK6)<_uTt>-(5C%J9RR-tZ-6(!A%H31pUt(~Wea2g z2n0bO7=(ZfnE1btpMmS(Phc$Ir=9n}d2j(-0%yP}@DNxCcw+G=*ChQM2`^{|C%|#= zE;tH~fx}=Ocmga1SzrZd0n5Qk@ECXsYyrIiKMZ^c`4zYgz6MpG8dQQsfS(sP@?+8? zNGtKJi4>Kx_un0Y9nE1~UOaR`C;S5#UEEew^Be^bK$oybt~b zI>F)n_+WVuAC#~!wu#;21@bE3DP0RVMhgJPo*!9y0U4lU{u@Jky7YtW4)_6&*CVe} zeNlhIk2qY;ZTN?W`M@X(K$`m;3E)YS789QA3@{wb10`S%NC)G zAR5Gh7;qQ38w>;kK!3pP`hi%$`S^j4c|6l+piQq>I1?z~1ix zJPp4F#`wnA&m)h=-vM3(uYeao0O0(cU>g`m35P;^hakl@4I6eNJ)8;-MNj0qJRaM? z<8}3q^LQ!zyWefd>kfVcUIQ}t{2)GX)qOp+w8{s6uS@3`fTZ_tuM+V!I6lba70n-VVf93yABV+ zUFOBZ>Gn0kce_yTdF7ig19w$~iJ8M3=or&O@keZS<6RAQS#OQXNStY?<-qz+DTo_tb}FA4b!m zjx>(x6%&1W)Ea5La*%iV<3SSC>e7JR%?QiX8 z%ga#0rorvy)O58^UwCP}j+V0XhZB_zix!(_#ks# z4}2ZbvWpRB*oFC;aoiedylvxJmHXVSSyfBW1|jEJ!3zQFk6Xn6s=Z_r!|3R7rx;5& zkDrybneJ^uAA{-RcWroh`|8~?yYVJXbzI-#Uu`HJFALbr0gwu4>4_W>K_^ex?8cil zD|^H}oAdmuFKH$wIMQ$;(Kjca7TNT)*XbLhtJ^V}6Zj%p&P*r;(9xJ!D+XTnlM!#zh5sT=P_L!t(ePV4VT9pr1?Ae9JPV7P$ zXS{WDdDVcgUDH=7C>e_NjFMnVKj{=i_q}Hm&(L(^lioZTA)g7SOD7Y>MCx@aC&_qq zrss|PDEGECUuvCemKpENOcT#s$-KF>4kbyxnCzgnr<`IB9XVx_4@A(_Q;G7?2nsrF z6sR++J3)%^FazN?L|mE7!0jx$yv)95o!xjmYeoUz(%ILNqm$JAe)%liA) zPcSAz#CvEH{rZdzPx@X|#v=S0m5q0;K3;XDVehzhKXi?#B_ePpN;qr7k*1!tMH(-; z*t+NUvkzRj!Dm_$_8m4<^I2=sf1VaN5D+6A?RRV6V9lPfm3gxtcw~jv)agjV7cHGW zYqS6GE+`;z&W7h&J3bfFp!c0rel9}Rj*#B8_9;QyeP{Fd-5n2ocF~rE2+d>SIebw>WELfo~ygOB!!V)%5+_TjwwJOZ%<_v0rlKDUPl6 zD#sT6*nhYm?d5mn$ zJT>W*SQPN}&}+ULp<#6Y?}tY8b%h)sV|y4w{b`@UK_0R;Elm@+WGrt zn*Trcwv8Wu)vf19-_I*U=6R^~ljVWB<`eZLZDD^p80%kLQ8T2$<0-AjD{$PBrhPnO e)2vVtzOP$P(YM#8DZNF1v(wQ~wirJJZT^35DYB>l diff --git a/package.json b/package.json index d2061002..35f93798 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "contracts/" ], "devDependencies": { - "@adraffy/blocksmith": "^0.1.53", + "@adraffy/blocksmith": "^0.1.55", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.36.0", "@types/bun": "latest", diff --git a/scripts/deploy-finder.ts b/scripts/deploy-finder.ts new file mode 100755 index 00000000..30f12ca3 --- /dev/null +++ b/scripts/deploy-finder.ts @@ -0,0 +1,33 @@ +import { FoundryDeployer } from '@adraffy/blocksmith'; +import { createProvider } from '../test/providers.js'; +import { chainFromName, chainName } from '../src/chains.js'; + +async function prompt(q: string) { + process.stdout.write(q); + for await (const line of console) { + return line.trim(); + } + return ''; +} + +const chain = chainFromName(await prompt(`Chain (name or id): `)); +console.log(`Chain: ${chainName(chain)} (${chain})`); + +const deployer = await FoundryDeployer.load({ + provider: createProvider(chain), + privateKey: await prompt('Private Key (empty to simulate): '), +}); + +const deployable = await deployer.prepare({ + file: 'OPFaultGameFinder', +}); + +if (deployer.privateKey) { + await prompt('Ready? (abort to stop) '); + await deployable.deploy(); + const apiKey = + deployer.etherscanApiKey || (await prompt('Etherscan API Key: ')); + if (apiKey) { + await deployable.verifyEtherscan({ apiKey }); + } +} diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index cfd41b3d..b284861f 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -44,8 +44,8 @@ type ABIFoundGame = { rootClaim: string; }; -const FINDER_MAINNET = '0x4E63bFa938fA1235A0ad7AaFA5165c6511f970eC'; -const FINDER_SEPOLIA = '0xda1B8f851965BfE75451222a0a898722765747e2'; +const FINDER_MAINNET = '0xAe212442b5FE242cEedc862995cce0Be0d4D16f7'; // 20251107 +const FINDER_SEPOLIA = '0x4Db32cde04584854955822e081350606b41f472F'; // 20251107 export class OPFaultRollup extends AbstractOPRollup { static readonly PORTAL_ABI = PORTAL_ABI; From 58d942091c88119bb6314006a9f63d669cfb6a4f Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Fri, 7 Nov 2025 14:08:46 -0800 Subject: [PATCH 05/15] remove delayed finality and redeploy --- contracts/op/OPFaultGameFinder.sol | 32 +++++++----------------------- src/op/OPFaultRollup.ts | 4 ++-- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/contracts/op/OPFaultGameFinder.sol b/contracts/op/OPFaultGameFinder.sol index e4f2bc63..f02a438b 100755 --- a/contracts/op/OPFaultGameFinder.sol +++ b/contracts/op/OPFaultGameFinder.sol @@ -17,17 +17,12 @@ uint256 constant DEFENDER_WINS = 2; error GameNotFound(); -struct PortalParams { - uint256 finalityDelay; - uint256 respectedGameType; -} - contract OPFaultGameFinder { function findGameIndex( OPFaultParams memory params, uint256 gameCount ) external view virtual returns (uint256) { - PortalParams memory portalParams = _portalParams(params.portal); + uint256 respectedGameType = params.portal.respectedGameType(); IDisputeGameFactory factory = params.portal.disputeGameFactory(); if (gameCount == 0) gameCount = factory.gameCount(); while (gameCount > 0) { @@ -42,7 +37,7 @@ contract OPFaultGameFinder { gameType, created, params, - portalParams + respectedGameType ) ) { return gameCount; @@ -73,7 +68,7 @@ contract OPFaultGameFinder { gameType, created, params, - _portalParams(params.portal) + params.portal.respectedGameType() ) ) { l2BlockNumber = gameProxy.l2BlockNumber(); @@ -86,13 +81,13 @@ contract OPFaultGameFinder { uint256 gameType, uint256 created, OPFaultParams memory params, - PortalParams memory portalParams + uint256 respectedGameType ) internal view returns (bool) { // if allowed gameTypes is empty, accept a respected game OR a previously respected game if ( !( params.allowedGameTypes.length == 0 - ? (gameType == portalParams.respectedGameType || + ? (gameType == respectedGameType || gameProxy.wasRespectedGameTypeWhenCreated()) : _isAllowedGameType(gameType, params.allowedGameTypes) ) @@ -117,12 +112,9 @@ contract OPFaultGameFinder { if (ok && v.length == 32) { return bytes32(v) == bytes32(0); // usable if not challenged } + // fallthru if not IFaultDisputeGame } - // require resolved + sufficiently aged - return - gameProxy.status() == DEFENDER_WINS && - (block.timestamp - gameProxy.resolvedAt()) >= - portalParams.finalityDelay; + return gameProxy.status() == DEFENDER_WINS; // require resolved } function _isAllowedGameType( @@ -144,14 +136,4 @@ contract OPFaultGameFinder { } return allowedProposers.length == 0; } - - function _portalParams( - IOptimismPortal portal - ) internal view returns (PortalParams memory) { - return - PortalParams({ - finalityDelay: portal.disputeGameFinalityDelaySeconds(), - respectedGameType: portal.respectedGameType() - }); - } } diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index b284861f..c48bd1e5 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -44,8 +44,8 @@ type ABIFoundGame = { rootClaim: string; }; -const FINDER_MAINNET = '0xAe212442b5FE242cEedc862995cce0Be0d4D16f7'; // 20251107 -const FINDER_SEPOLIA = '0x4Db32cde04584854955822e081350606b41f472F'; // 20251107 +const FINDER_MAINNET = '0xdc535021b10995e423607706Bc313F28a95CdB94'; // 20251107 +const FINDER_SEPOLIA = '0xc1c41cC6d3509BA1c580141f4903F05BAF48b0B5'; // 20251107 export class OPFaultRollup extends AbstractOPRollup { static readonly PORTAL_ABI = PORTAL_ABI; From 87d3617252dc187b2f736d8195594ea92be25ca2 Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Fri, 7 Nov 2025 15:16:13 -0800 Subject: [PATCH 06/15] isUnchallenged --- contracts/op/OPFaultGameFinder.sol | 36 ++++++++++++++++++++++-------- contracts/op/OPInterfaces.sol | 30 +++++++++++++++++++++++-- src/op/OPFaultRollup.ts | 4 ++-- test/rollup/celo-sepolia.ts | 2 +- 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/contracts/op/OPFaultGameFinder.sol b/contracts/op/OPFaultGameFinder.sol index f02a438b..ed5a4ae7 100755 --- a/contracts/op/OPFaultGameFinder.sol +++ b/contracts/op/OPFaultGameFinder.sol @@ -5,7 +5,8 @@ import { IOptimismPortal, IDisputeGameFactory, IDisputeGame, - IFaultDisputeGame + IFaultDisputeGame, + IOPSuccinctFaultDisputeGame } from './OPInterfaces.sol'; import {OPFaultParams} from './OPStructs.sol'; @@ -105,14 +106,7 @@ contract OPFaultGameFinder { if (params.portal.disputeGameBlacklist(gameProxy)) return false; if (params.minAgeSec > 0) { if (created > block.timestamp - params.minAgeSec) return false; - (bool ok, bytes memory v) = address(gameProxy).staticcall( - abi.encodeCall(IFaultDisputeGame.l2BlockNumberChallenged, ()) - ); - // effectively: supportsInterface(IFaultDisputeGame) - if (ok && v.length == 32) { - return bytes32(v) == bytes32(0); // usable if not challenged - } - // fallthru if not IFaultDisputeGame + if (_isUnchallenged(gameProxy)) return true; } return gameProxy.status() == DEFENDER_WINS; // require resolved } @@ -136,4 +130,28 @@ contract OPFaultGameFinder { } return allowedProposers.length == 0; } + + /// @dev Attempt to determine if the game is challenged in any sense. + function _isUnchallenged( + IDisputeGame gameProxy + ) internal view returns (bool) { + try + IFaultDisputeGame(address(gameProxy)).l2BlockNumberChallenged() + returns (bool challenged) { + return !challenged; + } catch {} + try IFaultDisputeGame(address(gameProxy)).claimDataLen() returns ( + uint256 claims + ) { + return claims == 1; + } catch {} + try + IOPSuccinctFaultDisputeGame(address(gameProxy)).claimData() + returns (IOPSuccinctFaultDisputeGame.ClaimData memory data) { + return + data.status == + IOPSuccinctFaultDisputeGame.ProposalStatus.Unchallenged; + } catch {} + return false; + } } diff --git a/contracts/op/OPInterfaces.sol b/contracts/op/OPInterfaces.sol index 5dcd460b..09bb63c3 100644 --- a/contracts/op/OPInterfaces.sol +++ b/contracts/op/OPInterfaces.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { OPFaultParams } from './OPStructs.sol'; +import {OPFaultParams} from './OPStructs.sol'; // https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/src/L1/OptimismPortal2.sol interface IOptimismPortal { @@ -59,4 +59,30 @@ interface IOPFaultGameFinder { // https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/interfaces/dispute/IFaultDisputeGame.sol interface IFaultDisputeGame { function l2BlockNumberChallenged() external view returns (bool); -} \ No newline at end of file + function claimDataLen() external view returns (uint256); +} + +// https://github.com/succinctlabs/op-succinct/blob/main/contracts/src/fp/OPSuccinctFaultDisputeGame.sol +interface IOPSuccinctFaultDisputeGame { + enum ProposalStatus { + // The initial state of a new proposal. + Unchallenged, + // A proposal that has been challenged but not yet proven. + Challenged, + // An unchallenged proposal that has been proven valid with a verified proof. + UnchallengedAndValidProofProvided, + // A challenged proposal that has been proven valid with a verified proof. + ChallengedAndValidProofProvided, + // The final state after resolution, either GameStatus.CHALLENGER_WINS or GameStatus.DEFENDER_WINS. + Resolved + } + struct ClaimData { + uint32 parentIndex; + address counteredBy; + address prover; + bytes32 claim; + ProposalStatus status; + uint64 deadline; + } + function claimData() external view returns (ClaimData memory); +} diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index c48bd1e5..7fcf4581 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -44,8 +44,8 @@ type ABIFoundGame = { rootClaim: string; }; -const FINDER_MAINNET = '0xdc535021b10995e423607706Bc313F28a95CdB94'; // 20251107 -const FINDER_SEPOLIA = '0xc1c41cC6d3509BA1c580141f4903F05BAF48b0B5'; // 20251107 +const FINDER_MAINNET = '0xdc535021b10995e423607706Bc313F28a95CdB94'; // 20251107 (not updated yet) +const FINDER_SEPOLIA = '0x98261818bEe2E69866A936564d1aDF760c3e953c'; // 20251107 export class OPFaultRollup extends AbstractOPRollup { static readonly PORTAL_ABI = PORTAL_ABI; diff --git a/test/rollup/celo-sepolia.ts b/test/rollup/celo-sepolia.ts index 5d921293..08db40b8 100755 --- a/test/rollup/celo-sepolia.ts +++ b/test/rollup/celo-sepolia.ts @@ -6,7 +6,7 @@ console.log(new Date()); const config = OPFaultRollup.celoSepoliaConfig; const rollup = new OPFaultRollup(createProviderPair(config), config); -// 20251106: OP Succinct => 46 +// 20251106: OP Succinct => 42 console.log(await rollup.getGameTypes()); const commits = await rollup.fetchRecentCommits(8); From 64b6a0df65ee1bd40db56bd94e4a4cf4ff009840 Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Fri, 7 Nov 2025 17:21:06 -0800 Subject: [PATCH 07/15] working unfinalized succinct --- contracts/op/OPFaultGameFinder.sol | 2 +- contracts/op/OPInterfaces.sol | 1 + test/rollup/celo-sepolia.ts | 23 +++++++++--------- test/rollup/unfinalized-celo-sepolia.ts | 31 +++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 12 deletions(-) create mode 100755 test/rollup/unfinalized-celo-sepolia.ts diff --git a/contracts/op/OPFaultGameFinder.sol b/contracts/op/OPFaultGameFinder.sol index ed5a4ae7..3fd8bdcd 100755 --- a/contracts/op/OPFaultGameFinder.sol +++ b/contracts/op/OPFaultGameFinder.sol @@ -131,7 +131,7 @@ contract OPFaultGameFinder { return allowedProposers.length == 0; } - /// @dev Attempt to determine if the game is challenged in any sense. + /// @dev Attempt to determine if the game is challenged in any sense. function _isUnchallenged( IDisputeGame gameProxy ) internal view returns (bool) { diff --git a/contracts/op/OPInterfaces.sol b/contracts/op/OPInterfaces.sol index 09bb63c3..e2dcb992 100644 --- a/contracts/op/OPInterfaces.sol +++ b/contracts/op/OPInterfaces.sol @@ -59,6 +59,7 @@ interface IOPFaultGameFinder { // https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/interfaces/dispute/IFaultDisputeGame.sol interface IFaultDisputeGame { function l2BlockNumberChallenged() external view returns (bool); + // note: this is also on ISuperFaultDisputeGame function claimDataLen() external view returns (uint256); } diff --git a/test/rollup/celo-sepolia.ts b/test/rollup/celo-sepolia.ts index 08db40b8..9b9f072a 100755 --- a/test/rollup/celo-sepolia.ts +++ b/test/rollup/celo-sepolia.ts @@ -15,20 +15,21 @@ const v = commits.map((x) => Number(x.index)); console.log(v); console.log(v.slice(1).map((x, i) => v[i] - x)); -// 2025-10-28T21:44:47.206Z +// 2025-11-08T00:54:23.537Z +// [ 42n ] // { -// index: 720n, -// blockHash: "0x9d62ad6d8ca9e63aff55ada3e9cce4e0f0f558381edee30ab6d89868192c1d80", -// stateRoot: "0x418d6c135d8a0133e2dd19989edabe344676bb2d3f1d700de8778f34cc0acd06", -// passerRoot: "0x47eb03d8c215efbca17a3698fc1fc893685603d08e1c9240ef5249a59a1e9d78", -// prover: EthProver[block=7792862], +// index: 829n, +// blockHash: "0xe3dd18a8b67879253692ed7e22b913d300b301d0b4f0a80f623981d60d7aebe9", +// stateRoot: "0x492e1c458eba6c74446b1d5deea49bcfce4e444846dfc6c5c32be820634923f3", +// passerRoot: "0x2ccee89e537aa9843e497cdbc5f69c85f4103f758bb1e7c851bfe576a30b178b", +// prover: EthProver[block=8970602], // game: { // gameType: 1n, -// created: 1761079044n, -// gameProxy: "0x1eAc427D298312911bA06126D31c368DFD994E58", -// l2BlockNumber: 7792862n, -// rootClaim: "0x10d4bbfb4e2be93f119f78c884bd3ba34a6a5465588653bb260a92caffc3c8ad", +// created: 1762257576n, +// gameProxy: "0x4ebE05086edcAf69fc71a33012004EDaC365171f", +// l2BlockNumber: 8970602n, +// rootClaim: "0xbb787d616f0dbe98cdba1fb92de4f10075720c77421041b1e29f4e49c892ecfb", // }, // } -// [ 720, 719, 718, 717, 716, 715, 714, 713 ] +// [ 829, 828, 827, 826, 825, 824, 823, 822 ] // [ 1, 1, 1, 1, 1, 1, 1 ] diff --git a/test/rollup/unfinalized-celo-sepolia.ts b/test/rollup/unfinalized-celo-sepolia.ts new file mode 100755 index 00000000..615bf5cb --- /dev/null +++ b/test/rollup/unfinalized-celo-sepolia.ts @@ -0,0 +1,31 @@ +import { OPFaultRollup } from '../../src/op/OPFaultRollup.js'; +import { createProviderPair } from '../providers.js'; + +console.log(new Date()); + +const config = OPFaultRollup.celoSepoliaConfig; +const rollup = new OPFaultRollup(createProviderPair(config), config, 1); + +const commits = await rollup.fetchRecentCommits(8); +console.log(commits[0]); +const v = commits.map((x) => Number(x.index)); +console.log(v); +console.log(v.slice(1).map((x, i) => v[i] - x)); + +// 2025-11-08T00:55:08.548Z +// { +// index: 962n, +// blockHash: "0x44636f50f497a7458f7507a2562f7f5e277421305374f597cd7c00b6907c50f5", +// stateRoot: "0xba59809c4f17180ed8d0616aa160a42ecaca3c51428c6ed8eee75a93de35e5fe", +// passerRoot: "0x72f62ce7c0bba6e8874d8742f9e1ad7057b75f1ab432480ee539d1592dd4ba09", +// prover: EthProver[block=9273448], +// game: { +// gameType: 42n, +// created: 1762559988n, +// gameProxy: "0x9889336882330755c34FBCbfC5867cED8893B011", +// l2BlockNumber: 9273448n, +// rootClaim: "0x33ec082591c7555827a93b32092fea58b6193f8a6e54a004c55bf61115153d17", +// }, +// } +// [ 962, 961, 960, 959, 958, 957, 956, 955 ] +// [ 1, 1, 1, 1, 1, 1, 1 ] From 83fa1d5afeaa7c421b4373cc74de1589c45a3899 Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Fri, 7 Nov 2025 17:59:45 -0800 Subject: [PATCH 08/15] add mainnet finder --- src/op/OPFaultRollup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index 7fcf4581..81b6b564 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -44,7 +44,7 @@ type ABIFoundGame = { rootClaim: string; }; -const FINDER_MAINNET = '0xdc535021b10995e423607706Bc313F28a95CdB94'; // 20251107 (not updated yet) +const FINDER_MAINNET = '0xA6FE3B15286D866CA1747a5C1c125d4dCFF5366e'; // 20251107 const FINDER_SEPOLIA = '0x98261818bEe2E69866A936564d1aDF760c3e953c'; // 20251107 export class OPFaultRollup extends AbstractOPRollup { From 47fa19e57630b41ba2ff947ff411f2982dabac2a Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Thu, 13 Nov 2025 19:50:43 -0800 Subject: [PATCH 09/15] changes for thomas --- contracts/op/OPFaultGameFinder.sol | 38 +++++++++++++++++------------- contracts/op/OPInterfaces.sol | 16 +++++++++---- src/op/OPFaultRollup.ts | 2 +- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/contracts/op/OPFaultGameFinder.sol b/contracts/op/OPFaultGameFinder.sol index 3fd8bdcd..c82df2e4 100755 --- a/contracts/op/OPFaultGameFinder.sol +++ b/contracts/op/OPFaultGameFinder.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.23; import { IOptimismPortal, + IAnchorStateRegistry, IDisputeGameFactory, IDisputeGame, IFaultDisputeGame, @@ -21,27 +22,26 @@ error GameNotFound(); contract OPFaultGameFinder { function findGameIndex( OPFaultParams memory params, - uint256 gameCount + uint256 gameBound ) external view virtual returns (uint256) { - uint256 respectedGameType = params.portal.respectedGameType(); - IDisputeGameFactory factory = params.portal.disputeGameFactory(); - if (gameCount == 0) gameCount = factory.gameCount(); - while (gameCount > 0) { - ( - uint256 gameType, - uint256 created, - IDisputeGame gameProxy - ) = factory.gameAtIndex(--gameCount); + IAnchorStateRegistry asr = params.portal.anchorStateRegistry(); + uint256 respectedGameType = asr.respectedGameType(); + IDisputeGameFactory dgf = params.portal.disputeGameFactory(); + if (gameBound == 0) gameBound = dgf.gameCount(); + while (gameBound > 0) { + (uint256 gameType, uint256 created, IDisputeGame gameProxy) = dgf + .gameAtIndex(--gameBound); if ( _isGameUsable( gameProxy, gameType, created, params, - respectedGameType + respectedGameType, + asr ) ) { - return gameCount; + return gameBound; } } revert GameNotFound(); @@ -61,15 +61,17 @@ contract OPFaultGameFinder { bytes32 rootClaim ) { - IDisputeGameFactory factory = params.portal.disputeGameFactory(); - (gameType, created, gameProxy) = factory.gameAtIndex(gameIndex); + IAnchorStateRegistry asr = params.portal.anchorStateRegistry(); + IDisputeGameFactory dgf = params.portal.disputeGameFactory(); + (gameType, created, gameProxy) = dgf.gameAtIndex(gameIndex); if ( _isGameUsable( gameProxy, gameType, created, params, - params.portal.respectedGameType() + asr.respectedGameType(), + asr ) ) { l2BlockNumber = gameProxy.l2BlockNumber(); @@ -82,7 +84,8 @@ contract OPFaultGameFinder { uint256 gameType, uint256 created, OPFaultParams memory params, - uint256 respectedGameType + uint256 respectedGameType, + IAnchorStateRegistry asr ) internal view returns (bool) { // if allowed gameTypes is empty, accept a respected game OR a previously respected game if ( @@ -103,7 +106,8 @@ contract OPFaultGameFinder { ) ) return false; // https://specs.optimism.io/fault-proof/stage-one/bridge-integration.html#blacklisting-disputegames - if (params.portal.disputeGameBlacklist(gameProxy)) return false; + // https://specs.optimism.io/fault-proof/stage-one/anchor-state-registry.html#proper-game + if (!asr.isGameProper(gameProxy)) return false; if (params.minAgeSec > 0) { if (created > block.timestamp - params.minAgeSec) return false; if (_isUnchallenged(gameProxy)) return true; diff --git a/contracts/op/OPInterfaces.sol b/contracts/op/OPInterfaces.sol index e2dcb992..60ba3602 100644 --- a/contracts/op/OPInterfaces.sol +++ b/contracts/op/OPInterfaces.sol @@ -5,13 +5,19 @@ import {OPFaultParams} from './OPStructs.sol'; // https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/src/L1/OptimismPortal2.sol interface IOptimismPortal { + function anchorStateRegistry() external view returns (IAnchorStateRegistry); function disputeGameFactory() external view returns (IDisputeGameFactory); + // function disputeGameBlacklist( + // IDisputeGame game + // ) external view returns (bool); + // function disputeGameFinalityDelaySeconds() external view returns (uint256); + //function respectedGameTypeUpdatedAt() external view returns (uint64); +} + +// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol +interface IAnchorStateRegistry { + function isGameProper(IDisputeGame) external view returns (bool); function respectedGameType() external view returns (uint256); - function disputeGameBlacklist( - IDisputeGame game - ) external view returns (bool); - function disputeGameFinalityDelaySeconds() external view returns (uint256); - function respectedGameTypeUpdatedAt() external view returns (uint64); } // https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/interfaces/dispute/IDisputeGameFactory.sol diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index 81b6b564..db6bcbf8 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -45,7 +45,7 @@ type ABIFoundGame = { }; const FINDER_MAINNET = '0xA6FE3B15286D866CA1747a5C1c125d4dCFF5366e'; // 20251107 -const FINDER_SEPOLIA = '0x98261818bEe2E69866A936564d1aDF760c3e953c'; // 20251107 +const FINDER_SEPOLIA = '0xD9a5a31A6a789d8dD9A0A2d964BC6F0596341bC2'; // 20251113 export class OPFaultRollup extends AbstractOPRollup { static readonly PORTAL_ABI = PORTAL_ABI; From bdae672afa6fe818c7b087b6ec959476d23f7f08 Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Thu, 13 Nov 2025 23:01:05 -0800 Subject: [PATCH 10/15] switch to AnchorStateRegistry --- contracts/GatewayFetchTarget.sol | 10 +- contracts/GatewayFetcher.sol | 2 +- contracts/InteractiveVerifier.sol | 16 +- contracts/SelfVerifier.sol | 8 +- contracts/arbitrum/ArbitrumVerifier.sol | 8 +- contracts/arbitrum/DoubleArbitrumVerifier.sol | 16 +- contracts/linea/LineaVerifier.sol | 8 +- contracts/linea/UnfinalizedLineaVerifier.sol | 8 +- contracts/op/OPFaultGameFinder.sol | 21 +- contracts/op/OPFaultVerifier.sol | 203 +++++++++--------- contracts/op/OPInterfaces.sol | 29 +-- contracts/op/OPStructs.sol | 4 +- contracts/op/OPVerifier.sol | 35 +-- contracts/op/ReverseOPVerifier.sol | 8 +- contracts/polygon/PolygonPoSVerifier.sol | 8 +- contracts/scroll/ScrollVerifier.sol | 8 +- contracts/starknet/StarknetVerifier.sol | 8 +- contracts/taiko/TaikoVerifier.sol | 8 +- contracts/trusted/LazyTrustedVerifier.sol | 9 +- contracts/unchecked/UncheckedVerifier.sol | 8 +- contracts/zksync/ZKSyncVerifier.sol | 8 +- contracts/zksync/ZKSyncVerifierHooks.sol | 8 +- src/op/OPFaultRollup.ts | 76 ++++--- test/debug/op-fault.ts | 27 +++ test/gateway/common.ts | 14 +- 25 files changed, 355 insertions(+), 203 deletions(-) create mode 100755 test/debug/op-fault.ts diff --git a/contracts/GatewayFetchTarget.sol b/contracts/GatewayFetchTarget.sol index dfec3e48..fd579610 100755 --- a/contracts/GatewayFetchTarget.sol +++ b/contracts/GatewayFetchTarget.sol @@ -47,7 +47,15 @@ abstract contract GatewayFetchTarget { urls, abi.encodeCall(IGatewayProtocol.proveRequest, (context, req)), this.fetchCallback.selector, - abi.encode(Session(verifier, context, req, callback, carry)) + abi.encode( + Session({ + verifier: verifier, + context: context, + req: req, + callback: callback, + carry: carry + }) + ) ); } diff --git a/contracts/GatewayFetcher.sol b/contracts/GatewayFetcher.sol index b60efce8..957aef2f 100755 --- a/contracts/GatewayFetcher.sol +++ b/contracts/GatewayFetcher.sol @@ -25,7 +25,7 @@ library GatewayFetcher { assembly { mstore(v, 0) // length = 0 } - return GatewayRequest(v); + return GatewayRequest({ops: v}); } function encode( diff --git a/contracts/InteractiveVerifier.sol b/contracts/InteractiveVerifier.sol index c46808b9..c6f3d286 100755 --- a/contracts/InteractiveVerifier.sol +++ b/contracts/InteractiveVerifier.sol @@ -27,7 +27,7 @@ contract InteractiveVerifier is AbstractVerifier { function setStateRoot(uint256 index, bytes32 stateRoot) external onlyOwner { require(index > latestIndex, 'out of order'); - commits[index] = Commit(stateRoot, latestIndex); + commits[index] = Commit({stateRoot: stateRoot, prevIndex: latestIndex}); emit NewStateRoot(latestIndex, index, stateRoot); latestIndex = index; } @@ -50,13 +50,13 @@ contract InteractiveVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence( - 0, - commits[index].stateRoot, - proofs, - order, - _hooks - ) + ProofSequence({ + index: 0, + stateRoot: commits[index].stateRoot, + proofs: proofs, + order: order, + hooks: _hooks + }) ); } } diff --git a/contracts/SelfVerifier.sol b/contracts/SelfVerifier.sol index d02759c6..b25630fd 100755 --- a/contracts/SelfVerifier.sol +++ b/contracts/SelfVerifier.sol @@ -48,7 +48,13 @@ contract SelfVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, stateRoot, proofs, order, _hooks) + ProofSequence({ + index: 0, + stateRoot: stateRoot, + proofs: proofs, + order: order, + hooks: _hooks + }) ); } } diff --git a/contracts/arbitrum/ArbitrumVerifier.sol b/contracts/arbitrum/ArbitrumVerifier.sol index ba2eb863..819625fe 100755 --- a/contracts/arbitrum/ArbitrumVerifier.sol +++ b/contracts/arbitrum/ArbitrumVerifier.sol @@ -72,7 +72,13 @@ contract ArbitrumVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, stateRoot, p.proofs, p.order, _hooks) + ProofSequence({ + index: 0, + stateRoot: stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } } diff --git a/contracts/arbitrum/DoubleArbitrumVerifier.sol b/contracts/arbitrum/DoubleArbitrumVerifier.sol index f03131f4..0798c1f5 100755 --- a/contracts/arbitrum/DoubleArbitrumVerifier.sol +++ b/contracts/arbitrum/DoubleArbitrumVerifier.sol @@ -38,7 +38,13 @@ contract DoubleArbitrumVerifier is ArbitrumVerifier { bytes32 stateRoot = _verifyRollup(ps[0], context); (bytes[] memory outputs, ) = GatewayVM.evalRequest( request, - ProofSequence(0, stateRoot, ps[0].proofs, ps[0].order, _hooks) + ProofSequence({ + index: 0, + stateRoot: stateRoot, + proofs: ps[0].proofs, + order: ps[0].order, + hooks: _hooks + }) ); // outputs[0] = node // outputs[1] = confirmData @@ -51,7 +57,13 @@ contract DoubleArbitrumVerifier is ArbitrumVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, stateRoot, ps[1].proofs, ps[1].order, _hooks) + ProofSequence({ + index: 0, + stateRoot: stateRoot, + proofs: ps[1].proofs, + order: ps[1].order, + hooks: _hooks + }) ); } } diff --git a/contracts/linea/LineaVerifier.sol b/contracts/linea/LineaVerifier.sol index d069597d..7327e0a3 100755 --- a/contracts/linea/LineaVerifier.sol +++ b/contracts/linea/LineaVerifier.sol @@ -40,7 +40,13 @@ contract LineaVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, stateRoot, p.proofs, p.order, _hooks) + ProofSequence({ + index: 0, + stateRoot: stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } } diff --git a/contracts/linea/UnfinalizedLineaVerifier.sol b/contracts/linea/UnfinalizedLineaVerifier.sol index 9954ce2e..eb479665 100755 --- a/contracts/linea/UnfinalizedLineaVerifier.sol +++ b/contracts/linea/UnfinalizedLineaVerifier.sol @@ -60,7 +60,13 @@ contract UnfinalizedLineaVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, stateRoot, p.proofs, p.order, _hooks) + ProofSequence({ + index: 0, + stateRoot: stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } diff --git a/contracts/op/OPFaultGameFinder.sol b/contracts/op/OPFaultGameFinder.sol index c82df2e4..57fbd1c7 100755 --- a/contracts/op/OPFaultGameFinder.sol +++ b/contracts/op/OPFaultGameFinder.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.23; import { - IOptimismPortal, - IAnchorStateRegistry, IDisputeGameFactory, IDisputeGame, IFaultDisputeGame, @@ -24,9 +22,8 @@ contract OPFaultGameFinder { OPFaultParams memory params, uint256 gameBound ) external view virtual returns (uint256) { - IAnchorStateRegistry asr = params.portal.anchorStateRegistry(); - uint256 respectedGameType = asr.respectedGameType(); - IDisputeGameFactory dgf = params.portal.disputeGameFactory(); + uint256 respectedGameType = params.asr.respectedGameType(); + IDisputeGameFactory dgf = params.asr.disputeGameFactory(); if (gameBound == 0) gameBound = dgf.gameCount(); while (gameBound > 0) { (uint256 gameType, uint256 created, IDisputeGame gameProxy) = dgf @@ -37,8 +34,7 @@ contract OPFaultGameFinder { gameType, created, params, - respectedGameType, - asr + respectedGameType ) ) { return gameBound; @@ -61,8 +57,7 @@ contract OPFaultGameFinder { bytes32 rootClaim ) { - IAnchorStateRegistry asr = params.portal.anchorStateRegistry(); - IDisputeGameFactory dgf = params.portal.disputeGameFactory(); + IDisputeGameFactory dgf = params.asr.disputeGameFactory(); (gameType, created, gameProxy) = dgf.gameAtIndex(gameIndex); if ( _isGameUsable( @@ -70,8 +65,7 @@ contract OPFaultGameFinder { gameType, created, params, - asr.respectedGameType(), - asr + params.asr.respectedGameType() ) ) { l2BlockNumber = gameProxy.l2BlockNumber(); @@ -84,8 +78,7 @@ contract OPFaultGameFinder { uint256 gameType, uint256 created, OPFaultParams memory params, - uint256 respectedGameType, - IAnchorStateRegistry asr + uint256 respectedGameType ) internal view returns (bool) { // if allowed gameTypes is empty, accept a respected game OR a previously respected game if ( @@ -107,7 +100,7 @@ contract OPFaultGameFinder { ) return false; // https://specs.optimism.io/fault-proof/stage-one/bridge-integration.html#blacklisting-disputegames // https://specs.optimism.io/fault-proof/stage-one/anchor-state-registry.html#proper-game - if (!asr.isGameProper(gameProxy)) return false; + if (!params.asr.isGameProper(gameProxy)) return false; if (params.minAgeSec > 0) { if (created > block.timestamp - params.minAgeSec) return false; if (_isUnchallenged(gameProxy)) return true; diff --git a/contracts/op/OPFaultVerifier.sol b/contracts/op/OPFaultVerifier.sol index 008bf16e..38f746c2 100755 --- a/contracts/op/OPFaultVerifier.sol +++ b/contracts/op/OPFaultVerifier.sol @@ -1,97 +1,106 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {AbstractVerifier, IVerifierHooks} from '../AbstractVerifier.sol'; -import {GatewayRequest, GatewayVM, ProofSequence} from '../GatewayVM.sol'; -import { - Hashing, - Types -} from '../../lib/optimism/packages/contracts-bedrock/src/libraries/Hashing.sol'; -import { - IOptimismPortal, - IOPFaultGameFinder, - IDisputeGame, - OPFaultParams -} from './OPInterfaces.sol'; - -contract OPFaultVerifier is AbstractVerifier { - IOPFaultGameFinder public immutable gameFinder; - OPFaultParams private _params; - - constructor( - string[] memory urls, - uint256 window, - IVerifierHooks hooks, - IOPFaultGameFinder gameFinder_, - OPFaultParams memory params - ) AbstractVerifier(urls, window, hooks) { - gameFinder = gameFinder_; - _params = params; - } - - function portal() external view returns (IOptimismPortal) { - return _params.portal; - } - - function minAgeSec() external view returns (uint256) { - return _params.minAgeSec; - } - - function allowedGameTypes() external view returns (uint256[] memory) { - return _params.allowedGameTypes; - } - - function allowedProposers() external view returns (address[] memory) { - return _params.allowedProposers; - } - - function getLatestContext() external view virtual returns (bytes memory) { - return abi.encode(gameFinder.findGameIndex(_params, 0)); - } - - struct GatewayProof { - uint256 gameIndex; - Types.OutputRootProof outputRootProof; - bytes[] proofs; - bytes order; - } - - function getStorageValues( - bytes memory context, - GatewayRequest memory req, - bytes memory proof - ) external view returns (bytes[] memory, uint8 exitCode) { - uint256 gameIndex1 = abi.decode(context, (uint256)); - GatewayProof memory p = abi.decode(proof, (GatewayProof)); - (, , IDisputeGame gameProxy, uint256 blockNumber, ) = gameFinder - .gameAtIndex(_params, p.gameIndex); - require(blockNumber != 0, 'OPFault: invalid game'); - if (p.gameIndex != gameIndex1) { - (, , IDisputeGame gameProxy1) = _params - .portal - .disputeGameFactory() - .gameAtIndex(gameIndex1); - _checkWindow(_getGameTime(gameProxy1), _getGameTime(gameProxy)); - } - require( - gameProxy.rootClaim() == - Hashing.hashOutputRootProof(p.outputRootProof), - 'OPFault: rootClaim' - ); - return - GatewayVM.evalRequest( - req, - ProofSequence( - 0, - p.outputRootProof.stateRoot, - p.proofs, - p.order, - _hooks - ) - ); - } - - function _getGameTime(IDisputeGame g) internal view returns (uint256) { - return _params.minAgeSec == 0 ? g.resolvedAt() : g.createdAt(); - } -} +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AbstractVerifier, IVerifierHooks} from '../AbstractVerifier.sol'; +import {GatewayRequest, GatewayVM, ProofSequence} from '../GatewayVM.sol'; +import { + Hashing, + Types +} from '../../lib/optimism/packages/contracts-bedrock/src/libraries/Hashing.sol'; +import { + IAnchorStateRegistry, + IDisputeGameFactory, + IOPFaultGameFinder, + IDisputeGame, + OPFaultParams +} from './OPInterfaces.sol'; + +contract OPFaultVerifier is AbstractVerifier { + IOPFaultGameFinder public immutable gameFinder; + OPFaultParams private _params; + + constructor( + string[] memory urls, + uint256 window, + IVerifierHooks hooks, + IOPFaultGameFinder gameFinder_, + OPFaultParams memory params + ) AbstractVerifier(urls, window, hooks) { + gameFinder = gameFinder_; + _params = params; + } + + function anchorStateRegistry() + external + view + returns (IAnchorStateRegistry) + { + return _params.asr; + } + + function disputeGameFactory() external view returns (IDisputeGameFactory) { + return _params.asr.disputeGameFactory(); + } + + function minAgeSec() external view returns (uint256) { + return _params.minAgeSec; + } + + function allowedGameTypes() external view returns (uint256[] memory) { + return _params.allowedGameTypes; + } + + function allowedProposers() external view returns (address[] memory) { + return _params.allowedProposers; + } + + function getLatestContext() external view virtual returns (bytes memory) { + return abi.encode(gameFinder.findGameIndex(_params, 0)); + } + + struct GatewayProof { + uint256 gameIndex; + Types.OutputRootProof outputRootProof; + bytes[] proofs; + bytes order; + } + + function getStorageValues( + bytes memory context, + GatewayRequest memory req, + bytes memory proof + ) external view returns (bytes[] memory, uint8 exitCode) { + uint256 gameIndex1 = abi.decode(context, (uint256)); + GatewayProof memory p = abi.decode(proof, (GatewayProof)); + (, , IDisputeGame gameProxy, uint256 blockNumber, ) = gameFinder + .gameAtIndex(_params, p.gameIndex); + require(blockNumber != 0, 'OPFault: invalid game'); + if (p.gameIndex != gameIndex1) { + (, , IDisputeGame gameProxy1) = _params + .asr + .disputeGameFactory() + .gameAtIndex(gameIndex1); + _checkWindow(_getGameTime(gameProxy1), _getGameTime(gameProxy)); + } + require( + gameProxy.rootClaim() == + Hashing.hashOutputRootProof(p.outputRootProof), + 'OPFault: rootClaim' + ); + return + GatewayVM.evalRequest( + req, + ProofSequence({ + index: 0, + stateRoot: p.outputRootProof.stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) + ); + } + + function _getGameTime(IDisputeGame g) internal view returns (uint256) { + return _params.minAgeSec == 0 ? g.resolvedAt() : g.createdAt(); + } +} diff --git a/contracts/op/OPInterfaces.sol b/contracts/op/OPInterfaces.sol index 60ba3602..435008b3 100644 --- a/contracts/op/OPInterfaces.sol +++ b/contracts/op/OPInterfaces.sol @@ -3,25 +3,28 @@ pragma solidity ^0.8.0; import {OPFaultParams} from './OPStructs.sol'; -// https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/src/L1/OptimismPortal2.sol -interface IOptimismPortal { - function anchorStateRegistry() external view returns (IAnchorStateRegistry); - function disputeGameFactory() external view returns (IDisputeGameFactory); - // function disputeGameBlacklist( - // IDisputeGame game - // ) external view returns (bool); - // function disputeGameFinalityDelaySeconds() external view returns (uint256); - //function respectedGameTypeUpdatedAt() external view returns (uint64); -} - // https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol interface IAnchorStateRegistry { - function isGameProper(IDisputeGame) external view returns (bool); + function isGameProper(IDisputeGame) external view returns (bool); function respectedGameType() external view returns (uint256); + function disputeGameFactory() external view returns (IDisputeGameFactory); + //function portal() external view returns (IOptimismPortal); } +// https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +// interface IOptimismPortal { +// function anchorStateRegistry() external view returns (IAnchorStateRegistry); +// function disputeGameFactory() external view returns (IDisputeGameFactory); +// function disputeGameBlacklist( +// IDisputeGame game +// ) external view returns (bool); +// function disputeGameFinalityDelaySeconds() external view returns (uint256); +// function respectedGameTypeUpdatedAt() external view returns (uint64); +// } + // https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/interfaces/dispute/IDisputeGameFactory.sol interface IDisputeGameFactory { + function portal() external view returns (address); function gameCount() external view returns (uint256); function gameAtIndex( uint256 index @@ -65,7 +68,7 @@ interface IOPFaultGameFinder { // https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/interfaces/dispute/IFaultDisputeGame.sol interface IFaultDisputeGame { function l2BlockNumberChallenged() external view returns (bool); - // note: this is also on ISuperFaultDisputeGame + // note: this is also on ISuperFaultDisputeGame function claimDataLen() external view returns (uint256); } diff --git a/contracts/op/OPStructs.sol b/contracts/op/OPStructs.sol index fb1a90ca..8cd9dcf6 100644 --- a/contracts/op/OPStructs.sol +++ b/contracts/op/OPStructs.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IOptimismPortal } from './OPInterfaces.sol'; +import {IAnchorStateRegistry} from './OPInterfaces.sol'; struct OPFaultParams { - IOptimismPortal portal; + IAnchorStateRegistry asr; uint256 minAgeSec; uint256[] allowedGameTypes; address[] allowedProposers; diff --git a/contracts/op/OPVerifier.sol b/contracts/op/OPVerifier.sol index 18c67efc..b00d9dce 100755 --- a/contracts/op/OPVerifier.sol +++ b/contracts/op/OPVerifier.sol @@ -3,10 +3,16 @@ pragma solidity ^0.8.23; import {AbstractVerifier, IVerifierHooks} from '../AbstractVerifier.sol'; import {GatewayRequest, GatewayVM, ProofSequence} from '../GatewayVM.sol'; -import {Hashing, Types} from '../../lib/optimism/packages/contracts-bedrock/src/libraries/Hashing.sol'; +import { + Hashing, + Types +} from '../../lib/optimism/packages/contracts-bedrock/src/libraries/Hashing.sol'; interface IOPOutputFinder { - function findOutputIndex(address portal, uint256 minAgeSec) external view returns (uint256); + function findOutputIndex( + address portal, + uint256 minAgeSec + ) external view returns (uint256); function getOutput( address portal, uint256 outputIndex @@ -23,11 +29,11 @@ contract OPVerifier is AbstractVerifier { uint256 window, IVerifierHooks hooks, address portal, - IOPOutputFinder outputFinder, + IOPOutputFinder outputFinder, uint256 minAgeSec ) AbstractVerifier(urls, window, hooks) { _portal = portal; - _outputFinder = outputFinder; + _outputFinder = outputFinder; _minAgeSec = minAgeSec; } @@ -50,31 +56,32 @@ contract OPVerifier is AbstractVerifier { uint256 outputIndex1 = abi.decode(context, (uint256)); GatewayProof memory p = abi.decode(proof, (GatewayProof)); Types.OutputProposal memory output = _outputFinder.getOutput( - _portal, + _portal, p.outputIndex ); if (p.outputIndex != outputIndex1) { Types.OutputProposal memory output1 = _outputFinder.getOutput( - _portal, outputIndex1 + _portal, + outputIndex1 ); _checkWindow(output1.timestamp, output.timestamp); // NOTE: no addtional checks are required // newer outputs will fail window check // older outputs will be older (by definition) - // therefore, older finalized outputs are also finalized + // therefore, older finalized outputs are also finalized } bytes32 computedRoot = Hashing.hashOutputRootProof(p.outputRootProof); require(computedRoot == output.outputRoot, 'OP: invalid root'); return GatewayVM.evalRequest( req, - ProofSequence( - 0, - p.outputRootProof.stateRoot, - p.proofs, - p.order, - _hooks - ) + ProofSequence({ + index: 0, + stateRoot: p.outputRootProof.stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } } diff --git a/contracts/op/ReverseOPVerifier.sol b/contracts/op/ReverseOPVerifier.sol index 1c86d765..c61477c1 100755 --- a/contracts/op/ReverseOPVerifier.sol +++ b/contracts/op/ReverseOPVerifier.sol @@ -71,7 +71,13 @@ contract ReverseOPVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, stateRoot, p.proofs, p.order, _hooks) + ProofSequence({ + index: 0, + stateRoot: stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } diff --git a/contracts/polygon/PolygonPoSVerifier.sol b/contracts/polygon/PolygonPoSVerifier.sol index 8ef461a7..fa925523 100755 --- a/contracts/polygon/PolygonPoSVerifier.sol +++ b/contracts/polygon/PolygonPoSVerifier.sol @@ -97,7 +97,13 @@ contract PolygonPoSVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, stateRoot, p.proofs, p.order, _hooks) + ProofSequence({ + index: 0, + stateRoot: stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } diff --git a/contracts/scroll/ScrollVerifier.sol b/contracts/scroll/ScrollVerifier.sol index 26717792..2fdad0fb 100755 --- a/contracts/scroll/ScrollVerifier.sol +++ b/contracts/scroll/ScrollVerifier.sol @@ -46,7 +46,13 @@ contract ScrollVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, stateRoot, p.proofs, p.order, _hooks) + ProofSequence({ + index: 0, + stateRoot: stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } } diff --git a/contracts/starknet/StarknetVerifier.sol b/contracts/starknet/StarknetVerifier.sol index b2860b9b..46c583f6 100755 --- a/contracts/starknet/StarknetVerifier.sol +++ b/contracts/starknet/StarknetVerifier.sol @@ -81,7 +81,13 @@ contract StarknetVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, stateRoot, p.proofs, p.order, _hooks) + ProofSequence({ + index: 0, + stateRoot: stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } } diff --git a/contracts/taiko/TaikoVerifier.sol b/contracts/taiko/TaikoVerifier.sol index ad2d8680..d9061c98 100755 --- a/contracts/taiko/TaikoVerifier.sol +++ b/contracts/taiko/TaikoVerifier.sol @@ -63,7 +63,13 @@ contract TaikoVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, ts.stateRoot, p.proofs, p.order, _hooks) + ProofSequence({ + index: 0, + stateRoot: ts.stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } } diff --git a/contracts/trusted/LazyTrustedVerifier.sol b/contracts/trusted/LazyTrustedVerifier.sol index 7ca905da..e19ff3fb 100755 --- a/contracts/trusted/LazyTrustedVerifier.sol +++ b/contracts/trusted/LazyTrustedVerifier.sol @@ -10,7 +10,6 @@ import {LazyOwnable} from './LazyOwnable.sol'; event TrustedVerifierChanged(); contract LazyTrustedVerifier is LazyOwnable, IGatewayVerifier { - IVerifierHooks _hooks; mapping(address => bool) _signers; uint256 _expSec; @@ -113,7 +112,13 @@ contract LazyTrustedVerifier is LazyOwnable, IGatewayVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, p.stateRoot, p.proofs, p.order, _hooks) + ProofSequence({ + index: 0, + stateRoot: p.stateRoot, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } } diff --git a/contracts/unchecked/UncheckedVerifier.sol b/contracts/unchecked/UncheckedVerifier.sol index 7a85fddd..2d9367e6 100755 --- a/contracts/unchecked/UncheckedVerifier.sol +++ b/contracts/unchecked/UncheckedVerifier.sol @@ -29,7 +29,13 @@ contract UncheckedVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, bytes32(0), proofs, order, _hooks) + ProofSequence({ + index: 0, + stateRoot: bytes32(0), + proofs: proofs, + order: order, + hooks: _hooks + }) ); } } diff --git a/contracts/zksync/ZKSyncVerifier.sol b/contracts/zksync/ZKSyncVerifier.sol index 0847cf96..9fe6d47e 100755 --- a/contracts/zksync/ZKSyncVerifier.sol +++ b/contracts/zksync/ZKSyncVerifier.sol @@ -72,7 +72,13 @@ contract ZKSyncVerifier is AbstractVerifier { return GatewayVM.evalRequest( req, - ProofSequence(0, batchInfo.batchHash, p.proofs, p.order, _hooks) + ProofSequence({ + index: 0, + stateRoot: batchInfo.batchHash, + proofs: p.proofs, + order: p.order, + hooks: _hooks + }) ); } } diff --git a/contracts/zksync/ZKSyncVerifierHooks.sol b/contracts/zksync/ZKSyncVerifierHooks.sol index 307ca729..d3dc13a6 100755 --- a/contracts/zksync/ZKSyncVerifierHooks.sol +++ b/contracts/zksync/ZKSyncVerifierHooks.sol @@ -1,7 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IVerifierHooks, InvalidProof, NOT_A_CONTRACT} from '../IVerifierHooks.sol'; +import { + IVerifierHooks, + InvalidProof, + NOT_A_CONTRACT +} from '../IVerifierHooks.sol'; import {IZKSyncSMT, TreeEntry, ACCOUNT_CODE_HASH} from './IZKSyncSMT.sol'; contract ZKSyncVerifierHooks is IVerifierHooks { @@ -46,7 +50,7 @@ contract ZKSyncVerifierHooks is IVerifierHooks { ); bytes32 computed = _smt.getRootHash( path, - TreeEntry(slot, value, leafIndex), + TreeEntry({key: slot, value: value, leafIndex: leafIndex}), target ); if (root != computed) revert InvalidProof(); diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index db6bcbf8..f5fbe27b 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -13,12 +13,20 @@ import { // https://docs.optimism.io/chain/differences // https://specs.optimism.io/fault-proof/stage-one/bridge-integration.html -const PORTAL_ABI = new Interface([ +const ANCHOR_STATE_REGISTRY_ABI = new Interface([ `function disputeGameFactory() view returns (address)`, `function respectedGameType() view returns (uint32)`, - `function disputeGameBlacklist(address game) view returns (bool)`, + //`function portal() view returns (address)`, + //`function isGameProper(address) view returns (bool)`, + //`function disputeGameBlacklist(address game) view returns (bool)`, ]); +// const PORTAL_ABI = new Interface([ +// `function disputeGameFactory() view returns (address)`, +// `function respectedGameType() view returns (uint32)`, +// `function disputeGameBlacklist(address game) view returns (bool)`, +// ]); + const GAME_ABI = new Interface([`function rootClaim() view returns (bytes32)`]); const FINDER_ABI = new Interface([ @@ -30,7 +38,7 @@ const FINDER_ABI = new Interface([ ]); export type OPFaultConfig = { - OptimismPortal: HexAddress; + AnchorStateRegistry: HexAddress; GameFinder: HexAddress; }; @@ -45,10 +53,10 @@ type ABIFoundGame = { }; const FINDER_MAINNET = '0xA6FE3B15286D866CA1747a5C1c125d4dCFF5366e'; // 20251107 -const FINDER_SEPOLIA = '0xD9a5a31A6a789d8dD9A0A2d964BC6F0596341bC2'; // 20251113 +const FINDER_SEPOLIA = '0xd42962f7FCbe5e19cfF8A48deEAEd89fFb851748'; // 20251113 export class OPFaultRollup extends AbstractOPRollup { - static readonly PORTAL_ABI = PORTAL_ABI; + static readonly ANCHOR_STATE_REGISTRY_ABI = ANCHOR_STATE_REGISTRY_ABI; static readonly GAME_ABI = GAME_ABI; static readonly FINDER_ABI = FINDER_ABI; @@ -63,13 +71,13 @@ export class OPFaultRollup extends AbstractOPRollup { static readonly mainnetConfig: RollupDeployment = { chain1: CHAINS.MAINNET, chain2: CHAINS.OP, - OptimismPortal: '0xbEb5Fc579115071764c7423A4f12eDde41f106Ed', + AnchorStateRegistry: '0x23B2C62946350F4246f9f9D027e071f0264FD113', GameFinder: FINDER_MAINNET, }; static readonly sepoliaConfig: RollupDeployment = { chain1: CHAINS.SEPOLIA, chain2: CHAINS.OP_SEPOLIA, - OptimismPortal: '0x16Fc5058F25648194471939df75CF27A2fdC48BC', + AnchorStateRegistry: '0xa1Cec548926eb5d69aa3B7B57d371EdBdD03e64b', GameFinder: FINDER_SEPOLIA, }; @@ -77,14 +85,14 @@ export class OPFaultRollup extends AbstractOPRollup { static readonly baseMainnetConfig: RollupDeployment = { chain1: CHAINS.MAINNET, chain2: CHAINS.BASE, - OptimismPortal: '0x49048044D57e1C92A77f79988d21Fa8fAF74E97e', + AnchorStateRegistry: '0x909f6cf47ed12f010A796527f562bFc26C7F4E72', GameFinder: FINDER_MAINNET, }; // https://docs.base.org/docs/base-contracts/#ethereum-testnet-sepolia static readonly baseSepoliaConfig: RollupDeployment = { chain1: CHAINS.SEPOLIA, chain2: CHAINS.BASE_SEPOLIA, - OptimismPortal: '0x49f53e41452C74589E85cA1677426Ba426459e85', + AnchorStateRegistry: '0x2fF5cC82dBf333Ea30D8ee462178ab1707315355', GameFinder: FINDER_SEPOLIA, }; @@ -92,13 +100,13 @@ export class OPFaultRollup extends AbstractOPRollup { static readonly inkMainnetConfig: RollupDeployment = { chain1: CHAINS.MAINNET, chain2: CHAINS.INK, - OptimismPortal: '0x5d66c1782664115999c47c9fa5cd031f495d3e4f', + AnchorStateRegistry: '0xEe018bAf058227872540AC60eFbd38b023d9dAe2', GameFinder: FINDER_MAINNET, }; static readonly inkSepoliaConfig: RollupDeployment = { chain1: CHAINS.SEPOLIA, chain2: CHAINS.INK_SEPOLIA, - OptimismPortal: '0x5c1d29C6c9C8b0800692acC95D700bcb4966A1d7', + AnchorStateRegistry: '0x299D7Ea9f0B584cfaF2a5341D151B44967594cA9', GameFinder: FINDER_SEPOLIA, }; @@ -106,13 +114,13 @@ export class OPFaultRollup extends AbstractOPRollup { static readonly unichainMainnetConfig: RollupDeployment = { chain1: CHAINS.MAINNET, chain2: CHAINS.UNICHAIN, - OptimismPortal: '0x0bd48f6B86a26D3a217d0Fa6FfE2B491B956A7a2', + AnchorStateRegistry: '0x27Cf508E4E3Aa8d30b3226aC3b5Ea0e8bcaCAFF9', GameFinder: FINDER_MAINNET, }; static readonly unichainSepoliaConfig: RollupDeployment = { chain1: CHAINS.SEPOLIA, chain2: CHAINS.UNICHAIN_SEPOLIA, - OptimismPortal: '0x0d83dab629f0e0F9d36c0Cbc89B69a489f0751bD', + AnchorStateRegistry: '0xBb6cA820978442750B682663efA851AD4131127b', GameFinder: FINDER_SEPOLIA, }; @@ -120,13 +128,13 @@ export class OPFaultRollup extends AbstractOPRollup { static readonly soneiumMainnetConfig: RollupDeployment = { chain1: CHAINS.MAINNET, chain2: CHAINS.SONEIUM, - OptimismPortal: '0x88e529a6ccd302c948689cd5156c83d4614fae92', + AnchorStateRegistry: '0x4890928941e62e273dA359374b105F803329F473', GameFinder: FINDER_MAINNET, }; static readonly soneiumMinatoConfig: RollupDeployment = { chain1: CHAINS.SEPOLIA, chain2: CHAINS.SONEIUM_SEPOLIA, - OptimismPortal: '0x65ea1489741A5D72fFdD8e6485B216bBdcC15Af3', + AnchorStateRegistry: '0x90066735EE774b405C4f54bfeC05b07f16D67188', GameFinder: FINDER_SEPOLIA, }; @@ -134,13 +142,13 @@ export class OPFaultRollup extends AbstractOPRollup { static readonly swellMainnetConfig: RollupDeployment = { chain1: CHAINS.MAINNET, chain2: CHAINS.SWELL, - OptimismPortal: '0x758E0EE66102816F5C3Ec9ECc1188860fbb87812', + AnchorStateRegistry: '0x511fB9E172f8A180735ACF9c2beeb208cD0061Ac', GameFinder: FINDER_MAINNET, }; static readonly swellSepoliaConfig: RollupDeployment = { chain1: CHAINS.SEPOLIA, chain2: CHAINS.SWELL_SEPOLIA, - OptimismPortal: '0x595329c60c0b9e54a5246e98fb0fa7fcfd454f64', + AnchorStateRegistry: '0x6d1443dd3f58889c6a8de51e74b5fca9c7116513', GameFinder: FINDER_SEPOLIA, }; @@ -148,7 +156,7 @@ export class OPFaultRollup extends AbstractOPRollup { static readonly worldMainnetConfig: RollupDeployment = { chain1: CHAINS.MAINNET, chain2: CHAINS.WORLD, - OptimismPortal: '0xd5ec14a83B7d95BE1E2Ac12523e2dEE12Cbeea6C', + AnchorStateRegistry: '0xD4D7A57DCC563756DeD99e224E144A6Bf0327099', GameFinder: FINDER_MAINNET, }; @@ -156,14 +164,14 @@ export class OPFaultRollup extends AbstractOPRollup { static readonly celoMainnetConfig: RollupDeployment = { chain1: CHAINS.MAINNET, chain2: CHAINS.CELO, - OptimismPortal: '0xc5c5D157928BDBD2ACf6d0777626b6C75a9EAEDC', + AnchorStateRegistry: '0x9F18D91949731E766f294A14027bBFE8F28328CC', GameFinder: FINDER_MAINNET, }; // https://storage.googleapis.com/cel2-rollup-files/celo-sepolia/deployment-l1.json static readonly celoSepoliaConfig: RollupDeployment = { chain1: CHAINS.SEPOLIA, chain2: CHAINS.CELO_SEPOLIA, - OptimismPortal: '0x44ae3d41a335a7d05eb533029917aad35662dcc2', + AnchorStateRegistry: '0xD73BA8168A61F3E917F0930D5C0401aA47e269D6', GameFinder: FINDER_SEPOLIA, }; @@ -171,12 +179,12 @@ export class OPFaultRollup extends AbstractOPRollup { static readonly zoraMainnetConfig: RollupDeployment = { chain1: CHAINS.MAINNET, chain2: CHAINS.ZORA, - OptimismPortal: '0x1a0ad011913A150f69f6A19DF447A0CfD9551054', + AnchorStateRegistry: '0x54027b388330415a34b2dBa9E6d25895649eEFf1', GameFinder: FINDER_MAINNET, }; // 20240917: delayed constructor not needed - readonly OptimismPortal: Contract; + readonly AnchorStateRegistry: Contract; readonly GameFinder: Contract; public gameTypes: bigint[] = []; public allowedProposers: HexAddress[] = []; @@ -187,9 +195,9 @@ export class OPFaultRollup extends AbstractOPRollup { public minAgeSec = 0 ) { super(providers); - this.OptimismPortal = new Contract( - config.OptimismPortal, - PORTAL_ABI, + this.AnchorStateRegistry = new Contract( + config.AnchorStateRegistry, + ANCHOR_STATE_REGISTRY_ABI, this.provider1 ); this.GameFinder = new Contract( @@ -205,7 +213,7 @@ export class OPFaultRollup extends AbstractOPRollup { get paramTuple() { return [ - this.OptimismPortal.target, + this.AnchorStateRegistry.target, this.minAgeSec, this.gameTypes, this.allowedProposers, @@ -219,10 +227,11 @@ export class OPFaultRollup extends AbstractOPRollup { } async fetchRespectedGameType(): Promise { - return this.OptimismPortal.respectedGameType({ + return this.AnchorStateRegistry.respectedGameType({ blockTag: this.latestBlockTag, }); } + private async _ensureRootClaim(index: bigint) { // dodge canary by requiring a valid root claim // finalized claims are assumed valid @@ -249,6 +258,7 @@ export class OPFaultRollup extends AbstractOPRollup { } return index; } + override async fetchLatestCommitIndex(): Promise { // the primary assumption is that the anchor root is the finalized state // however, this is strangely conditional on the gameType @@ -266,6 +276,7 @@ export class OPFaultRollup extends AbstractOPRollup { ) ); } + protected override async _fetchParentCommitIndex( commit: OPFaultCommit ): Promise { @@ -273,8 +284,8 @@ export class OPFaultRollup extends AbstractOPRollup { await this.GameFinder.findGameIndex(this.paramTuple, commit.index) ); } + protected override async _fetchCommit(index: bigint) { - // note: GameFinder checks isCommitStillValid() const game: ABIFoundGame = ( await this.GameFinder.gameAtIndex(this.paramTuple, index) ).toObject(); @@ -288,10 +299,13 @@ export class OPFaultRollup extends AbstractOPRollup { } return { ...commit, game }; } + override async isCommitStillValid(commit: OPFaultCommit): Promise { - return !(await this.OptimismPortal.disputeGameBlacklist( - commit.game.gameProxy - )); + const game: ABIFoundGame = await this.GameFinder.gameAtIndex( + this.paramTuple, + commit.index + ); + return !!game.l2BlockNumber; } override windowFromSec(sec: number): number { diff --git a/test/debug/op-fault.ts b/test/debug/op-fault.ts new file mode 100755 index 00000000..ce9e039a --- /dev/null +++ b/test/debug/op-fault.ts @@ -0,0 +1,27 @@ +import { Foundry } from '@adraffy/blocksmith'; +import { CHAINS } from '../../src/chains.js'; +import { OPFaultRollup } from '../../src/op/OPFaultRollup.js'; +import { providerURL } from '../providers.js'; + +const foundry = await Foundry.launch({ + infoLog: true, + fork: providerURL(CHAINS.SEPOLIA), +}); + +const OPFaultGameFinder = await foundry.deploy({ file: 'OPFaultGameFinder' }); + +const index = await OPFaultGameFinder.findGameIndex( + [OPFaultRollup.sepoliaConfig.AnchorStateRegistry, 21600, [], []], + 0 +); + +console.log({ index }); + +console.log( + await OPFaultGameFinder.gameAtIndex( + [OPFaultRollup.sepoliaConfig.AnchorStateRegistry, 21600, [], []], + index + ) +); + +await foundry.shutdown(); diff --git a/test/gateway/common.ts b/test/gateway/common.ts index e9c7faee..68708750 100755 --- a/test/gateway/common.ts +++ b/test/gateway/common.ts @@ -131,7 +131,7 @@ export function testOP( export function testOPFault( config: RollupDeployment, - opts: TestOptions & { minAgeSec?: number } + opts: TestOptions & { minAgeSec?: number; realFinder?: boolean } ) { describe.skipIf(shouldSkip(opts))( testName(config, { unfinalized: !!opts.minAgeSec }), @@ -151,10 +151,14 @@ export function testOPFault( const ccip = await serve(gateway, { protocol: 'raw', log: !!opts.log }); afterAll(ccip.shutdown); const commit = await gateway.getLatestCommit(); - const gameFinder = await foundry.deploy({ - file: 'FixedOPFaultGameFinder', - args: [commit.index], - }); + const gameFinder = opts.realFinder + ? await foundry.deploy({ + file: 'OPFaultGameFinder', + }) + : await foundry.deploy({ + file: 'FixedOPFaultGameFinder', + args: [commit.index], + }); const GatewayVM = await foundry.deploy({ file: 'GatewayVM' }); const hooks = await foundry.deploy({ file: 'EthVerifierHooks' }); const verifier = await foundry.deploy({ From e042c4ab1c3f29a3ffd7139405cbf054391042c6 Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Fri, 14 Nov 2025 17:34:19 -0800 Subject: [PATCH 11/15] add mainnet deployment, add env:REAL_FINDER --- src/op/OPFaultRollup.ts | 2 +- test/gateway/common.ts | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index f5fbe27b..8211e3c1 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -52,7 +52,7 @@ type ABIFoundGame = { rootClaim: string; }; -const FINDER_MAINNET = '0xA6FE3B15286D866CA1747a5C1c125d4dCFF5366e'; // 20251107 +const FINDER_MAINNET = '0xe7D342866b739950784a2ECBfad58e8C7f03D5F1'; // 20251113 const FINDER_SEPOLIA = '0xd42962f7FCbe5e19cfF8A48deEAEd89fFb851748'; // 20251113 export class OPFaultRollup extends AbstractOPRollup { diff --git a/test/gateway/common.ts b/test/gateway/common.ts index 68708750..871bbbd4 100755 --- a/test/gateway/common.ts +++ b/test/gateway/common.ts @@ -151,14 +151,15 @@ export function testOPFault( const ccip = await serve(gateway, { protocol: 'raw', log: !!opts.log }); afterAll(ccip.shutdown); const commit = await gateway.getLatestCommit(); - const gameFinder = opts.realFinder - ? await foundry.deploy({ - file: 'OPFaultGameFinder', - }) - : await foundry.deploy({ - file: 'FixedOPFaultGameFinder', - args: [commit.index], - }); + const gameFinder = + (opts.realFinder ?? !!process.env.REAL_FINDER) + ? await foundry.deploy({ + file: 'OPFaultGameFinder', + }) + : await foundry.deploy({ + file: 'FixedOPFaultGameFinder', + args: [commit.index], + }); const GatewayVM = await foundry.deploy({ file: 'GatewayVM' }); const hooks = await foundry.deploy({ file: 'EthVerifierHooks' }); const verifier = await foundry.deploy({ From 9f3358d93bbe89f7de9c7de7751361ea8d46a50e Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Sat, 15 Nov 2025 13:30:56 -0800 Subject: [PATCH 12/15] disable old op-fault rollups, add bookkeeping --- src/op/OPFaultRollup.ts | 43 +++++++++++-------- test/gateway/world.test.ts | 18 ++++---- test/research/op-fault/check-real.ts | 26 ++++++++++++ test/research/op-fault/check-version.ts | 56 +++++++++++++++++++++++++ test/research/op-fault/game-finders.md | 33 +++++++++++++++ test/rollup/world.ts | 40 ++++++++++++++++++ 6 files changed, 190 insertions(+), 26 deletions(-) create mode 100755 test/research/op-fault/check-real.ts create mode 100755 test/research/op-fault/check-version.ts create mode 100755 test/research/op-fault/game-finders.md create mode 100755 test/rollup/world.ts diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index 8211e3c1..4a9dc4aa 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -16,15 +16,18 @@ import { const ANCHOR_STATE_REGISTRY_ABI = new Interface([ `function disputeGameFactory() view returns (address)`, `function respectedGameType() view returns (uint32)`, - //`function portal() view returns (address)`, - //`function isGameProper(address) view returns (bool)`, - //`function disputeGameBlacklist(address game) view returns (bool)`, + `function portal() view returns (address)`, + `function isGameProper(address) view returns (bool)`, ]); -// const PORTAL_ABI = new Interface([ +// const OPTIMISM_PORTAL_ABI = new Interface([ // `function disputeGameFactory() view returns (address)`, // `function respectedGameType() view returns (uint32)`, -// `function disputeGameBlacklist(address game) view returns (bool)`, +// ]); + +// const DISPUTE_GAME_FACTORY_ABI = new Interface([ +// `function gameCount() view returns (uint256)`, +// `function gameAtIndex(uint256) view returns (uint256 gameType, uint256 created, address gameProxy)`, // ]); const GAME_ABI = new Interface([`function rootClaim() view returns (bytes32)`]); @@ -145,20 +148,26 @@ export class OPFaultRollup extends AbstractOPRollup { AnchorStateRegistry: '0x511fB9E172f8A180735ACF9c2beeb208cD0061Ac', GameFinder: FINDER_MAINNET, }; - static readonly swellSepoliaConfig: RollupDeployment = { - chain1: CHAINS.SEPOLIA, - chain2: CHAINS.SWELL_SEPOLIA, - AnchorStateRegistry: '0x6d1443dd3f58889c6a8de51e74b5fca9c7116513', - GameFinder: FINDER_SEPOLIA, - }; + // 20251115: ASR is Old + // - OptimismPortal: '0x595329c60c0b9e54a5246e98fb0fa7fcfd454f64' + // - GameFinder: '0x505e1e172667fec4a55514ccfc7fd240b409a299' + // static readonly swellSepoliaConfig: RollupDeployment = { + // chain1: CHAINS.SEPOLIA, + // chain2: CHAINS.SWELL_SEPOLIA, + // AnchorStateRegistry: '0x6D1443dD3f58889C6A8DE51E74b5fCa9c7116513', + // GameFinder: FINDER_SEPOLIA, + // }; // https://docs.worldcoin.org/world-chain/developers/world-chain-contracts - static readonly worldMainnetConfig: RollupDeployment = { - chain1: CHAINS.MAINNET, - chain2: CHAINS.WORLD, - AnchorStateRegistry: '0xD4D7A57DCC563756DeD99e224E144A6Bf0327099', - GameFinder: FINDER_MAINNET, - }; + // 20251115: ASR is Old + // - OptimismPortal: '0xd5ec14a83B7d95BE1E2Ac12523e2dEE12Cbeea6C' + // - GameFinder: '0x61F50A76bfb2Ad8620A3E8F81aa27f3bEb1Db0D7' + // static readonly worldMainnetConfig: RollupDeployment = { + // chain1: CHAINS.MAINNET, + // chain2: CHAINS.WORLD, + // AnchorStateRegistry: '0xD4D7A57DCC563756DeD99e224E144A6Bf0327099', + // GameFinder: FINDER_MAINNET, + // }; // https://storage.googleapis.com/cel2-rollup-files/celo/deployment-l1.json static readonly celoMainnetConfig: RollupDeployment = { diff --git a/test/gateway/world.test.ts b/test/gateway/world.test.ts index 7cf43bd1..82a73988 100755 --- a/test/gateway/world.test.ts +++ b/test/gateway/world.test.ts @@ -1,10 +1,10 @@ -import { OPFaultRollup } from '../../src/op/OPFaultRollup.js'; -import { testOPFault } from './common.js'; +// import { OPFaultRollup } from '../../src/op/OPFaultRollup.js'; +// import { testOPFault } from './common.js'; -testOPFault(OPFaultRollup.worldMainnetConfig, { - // https://worldscan.org/address/0x0d3e01829E8364DeC0e7475ca06B5c73dbA33ef6#code - slotDataContract: '0x0d3e01829E8364DeC0e7475ca06B5c73dbA33ef6', - // https://worldscan.org/address/0x57c2f437e0a5e155ced91a7a17bfc372c0af7b05#code - slotDataPointer: '0x57c2f437e0a5e155ced91a7a17bfc372c0af7b05', - skipCI: true, -}); +// testOPFault(OPFaultRollup.worldMainnetConfig, { +// // https://worldscan.org/address/0x0d3e01829E8364DeC0e7475ca06B5c73dbA33ef6#code +// slotDataContract: '0x0d3e01829E8364DeC0e7475ca06B5c73dbA33ef6', +// // https://worldscan.org/address/0x57c2f437e0a5e155ced91a7a17bfc372c0af7b05#code +// slotDataPointer: '0x57c2f437e0a5e155ced91a7a17bfc372c0af7b05', +// skipCI: true, +// }); diff --git a/test/research/op-fault/check-real.ts b/test/research/op-fault/check-real.ts new file mode 100755 index 00000000..67c814cd --- /dev/null +++ b/test/research/op-fault/check-real.ts @@ -0,0 +1,26 @@ +// run "real" finder on fork +// warning: very slow since it requires many rpc calls + +import { Foundry } from '@adraffy/blocksmith'; +import { CHAINS } from '../../../src/chains.js'; +import { OPFaultRollup } from '../../../src/op/OPFaultRollup.js'; +import { providerURL } from '../../providers.js'; + +const foundry = await Foundry.launch({ + infoLog: true, + fork: providerURL(CHAINS.SEPOLIA), +}); +try { + const OPFaultGameFinder = await foundry.deploy({ file: 'OPFaultGameFinder' }); + const paramTuple = [ + OPFaultRollup.sepoliaConfig.AnchorStateRegistry, + 21600, // minAgeSec + [], + [], + ]; + const gameIndex = await OPFaultGameFinder.findGameIndex(paramTuple, 0); + console.log({ gameIndex }); + console.log(await OPFaultGameFinder.gameAtIndex(paramTuple, gameIndex)); +} finally { + await foundry.shutdown(); +} diff --git a/test/research/op-fault/check-version.ts b/test/research/op-fault/check-version.ts new file mode 100755 index 00000000..cbbc5098 --- /dev/null +++ b/test/research/op-fault/check-version.ts @@ -0,0 +1,56 @@ +// check supported deployments for latest version support + +import { chainName } from '../../../src/chains.js'; +import { OPFaultConfig, OPFaultRollup } from '../../../src/op/OPFaultRollup.js'; +import { createProvider } from '../../providers.js'; +import { Contract } from 'ethers/contract'; +import { RollupDeployment } from '../../../src/rollup.js'; + +console.log(new Date()); + +for (const x of Object.values(OPFaultRollup)) { + if (typeof x !== 'object') continue; + if (typeof x.chain1 !== 'bigint') continue; + const config: RollupDeployment = x; + + const provider = createProvider(config.chain1); + + const asr = new Contract( + config.AnchorStateRegistry, + OPFaultRollup.ANCHOR_STATE_REGISTRY_ABI, + provider + ); + + const dgf = new Contract( + await asr.disputeGameFactory(), + [ + `function gameCount() view returns (uint256)`, + `function gameAtIndex(uint256) view returns (uint256 gameType, uint256 created, address gameProxy)`, + ], + provider + ); + + const gameCount: bigint = await dgf.gameCount(); + const [, , proxy] = await dgf.gameAtIndex(gameCount - 1n); + + console.log( + chainName(config.chain2), + await asr.isGameProper(proxy).catch(() => {}) + ); +} + +// 2025-11-15T21:28:34.048Z +// OP true +// OP_SEPOLIA true +// BASE true +// BASE_SEPOLIA true +// INK true +// INK_SEPOLIA true +// UNICHAIN true +// UNICHAIN_SEPOLIA true +// SONEIUM true +// SONEIUM_SEPOLIA true +// SWELL true +// CELO true +// CELO_SEPOLIA true +// ZORA true diff --git a/test/research/op-fault/game-finders.md b/test/research/op-fault/game-finders.md new file mode 100755 index 00000000..206a3562 --- /dev/null +++ b/test/research/op-fault/game-finders.md @@ -0,0 +1,33 @@ +# `GameFinder` Deployments + +## Mainnet + +* `2025-11-15` — [`0xe7D342866b739950784a2ECBfad58e8C7f03D5F1`](https://etherscan.io/address/0xe7d342866b739950784a2ecbfad58e8c7f03d5f1#code) + * switch to `IAnchorStateRegistry` + * `isGameProper()` +* `2025-11-08` — [`0xA6FE3B15286D866CA1747a5C1c125d4dCFF5366e`](https://etherscan.io/address/0xa6fe3b15286d866ca1747a5c1c125d4dcff5366e#code) + * `_isUnchallenged()` +* `2025-11-08` — [`0xdc535021b10995e423607706Bc313F28a95CdB94`](https://etherscan.io/address/0xdc535021b10995e423607706bc313f28a95cdb94#code) + * removed `finalityDelay` check +* `2025-11-08` — [`0xAe212442b5FE242cEedc862995cce0Be0d4D16f7`](https://etherscan.io/address/0xae212442b5fe242ceedc862995cce0be0d4d16f7#code) + * unfinalized optimistic `l2BlockNumberChallenged()` +* `2025-11-06` — [`0x4E63bFa938fA1235A0ad7AaFA5165c6511f970eC`](https://etherscan.io/address/0x4e63bfa938fa1235a0ad7aafa5165c6511f970ec#code) + * unfinalized optimistic `l2BlockNumberChallenged()` + * unfinalized status check +* `2025-10-14` — [`0x165386F8699ce2609A8903e25d00E1DEbD24A277`](https://etherscan.io/address/0x165386f8699ce2609a8903e25d00e1debd24a277#code) + * `OPFaultParams` +* `2025-03-13` — [`0x61F50A76bfb2Ad8620A3E8F81aa27f3bEb1Db0D7`](https://etherscan.io/address/0x61F50A76bfb2Ad8620A3E8F81aa27f3bEb1Db0D7#code) + * `FinalizationParams` +* `2024-10-05` — [`0x475a86934805eF2C52EF61A8FED644D4C9AC91d8`](https://etherscan.io/address/0x475a86934805ef2c52ef61a8fed644d4c9ac91d8) + * unfinalized +* `2024-08-23` — [`0x5A8E83f0E728bEb821b91bB82cFAE7F67bD36f7e`](https://etherscan.io/address/0x5A8E83f0E728bEb821b91bB82cFAE7F67bD36f7e#code) +* `2024-08-23` — [`0x480AadFd3A42266ec05013D5E2eF85752947D0B6`](https://etherscan.io/address/0x480aadfd3a42266ec05013d5e2ef85752947d0b6#code) +* `2024-08-23` — [`0x480AadFd3A42266ec05013D5E2eF85752947D0B6`](https://etherscan.io/address/0x480aadfd3a42266ec05013d5e2ef85752947d0b6#code) +* `2024-08-23` — [`0x48Af5c560c8393B60dd8792d19378fc8411EBFE1`](https://etherscan.io/address/0x48af5c560c8393b60dd8792d19378fc8411ebfe1#code) + * `_isFinalizedGame()` +* `2024-07-30` — [`0x6CbF8cd866a0FAE64b9C2B007D3D47c4E1B809fF`](https://etherscan.io/address/0x6cbf8cd866a0fae64b9c2b007d3d47c4e1b809ff#code) + * `OPFaultHelper` + +## Sepolia + +*TODO* diff --git a/test/rollup/world.ts b/test/rollup/world.ts new file mode 100755 index 00000000..a6b12fdc --- /dev/null +++ b/test/rollup/world.ts @@ -0,0 +1,40 @@ +import { Contract } from 'ethers'; +import { OPFaultRollup } from '../../src/op/OPFaultRollup.js'; +import { createProvider } from '../providers.js'; +import { CHAINS } from '../../src/chains.js'; + +// console.log(new Date()); + +// const config = OPFaultRollup.worldMainnetConfig; +// const rollup = new OPFaultRollup(createProviderPair(config), config); + +// const commits = await rollup.fetchRecentCommits(8); +// console.log(commits[0]); +// const v = commits.map((x) => Number(x.index)); +// console.log(v); +// console.log(v.slice(1).map((x, i) => v[i] - x)); + +const provider = createProvider(CHAINS.MAINNET); + +const OptimismPortal = new Contract( + '0xd5ec14a83B7d95BE1E2Ac12523e2dEE12Cbeea6C', + OPFaultRollup.ANCHOR_STATE_REGISTRY_ABI, // same as portal + provider +); + +const respectedGameType = await OptimismPortal.respectedGameType(); +const disputeGameFactory = await OptimismPortal.disputeGameFactory(); + +console.log({ respectedGameType, disputeGameFactory }); + +const GameFinder = new Contract( + '0x61F50A76bfb2Ad8620A3E8F81aa27f3bEb1Db0D7', + [ + `function findGameIndex(address portal, uint256 minAgeSec, uint256 gameTypeBitMask, uint256 gameCount) view returns (uint256)`, + ], + provider +); + +const gameIndex = await GameFinder.findGameIndex(OptimismPortal, 1, 0, 0); + +console.log({ gameIndex }); From 685429cabaa9ce3121c70b0b4f7cbd0f0376de02 Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Sat, 15 Nov 2025 15:19:18 -0800 Subject: [PATCH 13/15] bump blocksmith --- bun.lockb | Bin 71192 -> 71192 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index c4b8893bdd91a8c2d9952e398a59fa408de9fa60..f0d2f96758fc65850e48865cfe21621cad57e705 100755 GIT binary patch delta 146 zcmV;D0B!%6tOS^>1duKuhLqIBAsw~@#BOeG7|0jS!(#GLKk@pIFSoqzPYeF8u}+FP z0XUNZ3@ft)I(bS!iyyvibmI@-+t?QqF5ah-6y5kaqa%>*BbW0a8*`V4ZHX;SjLWca zZ@GTZ3_9yXuUApljFg)Fe8s6%8WS$-k_;EK)PyuT0XDM1duKuO+_ca%Ok3hlnu|}T+l6~GCmf&6ae~kB^a~eM1PyQu}+FP z0XLHY3@ft)I(bS!!Z)Z5Z>g Date: Sat, 15 Nov 2025 18:46:30 -0800 Subject: [PATCH 14/15] redeploy with l2BlockNumberChallenged fallthru --- contracts/op/OPFaultGameFinder.sol | 7 ++++- scripts/deploy-finder.ts | 40 +++++++++++++++++++------- src/op/OPFaultRollup.ts | 4 +-- test/research/op-fault/game-finders.md | 19 ++++++------ 4 files changed, 48 insertions(+), 22 deletions(-) diff --git a/contracts/op/OPFaultGameFinder.sol b/contracts/op/OPFaultGameFinder.sol index 57fbd1c7..7a37b37f 100755 --- a/contracts/op/OPFaultGameFinder.sol +++ b/contracts/op/OPFaultGameFinder.sol @@ -135,13 +135,16 @@ contract OPFaultGameFinder { try IFaultDisputeGame(address(gameProxy)).l2BlockNumberChallenged() returns (bool challenged) { - return !challenged; + // this challenge is independent of the game resolution + if (challenged) return false; } catch {} + // if supportsInterface(IFaultDisputeGame) try IFaultDisputeGame(address(gameProxy)).claimDataLen() returns ( uint256 claims ) { return claims == 1; } catch {} + // if supportsInterface(IOPSuccinctFaultDisputeGame) try IOPSuccinctFaultDisputeGame(address(gameProxy)).claimData() returns (IOPSuccinctFaultDisputeGame.ClaimData memory data) { @@ -149,6 +152,8 @@ contract OPFaultGameFinder { data.status == IOPSuccinctFaultDisputeGame.ProposalStatus.Unchallenged; } catch {} + // unknown type + // assume challenged and require resolution return false; } } diff --git a/scripts/deploy-finder.ts b/scripts/deploy-finder.ts index 30f12ca3..1fab252b 100755 --- a/scripts/deploy-finder.ts +++ b/scripts/deploy-finder.ts @@ -1,6 +1,7 @@ import { FoundryDeployer } from '@adraffy/blocksmith'; -import { createProvider } from '../test/providers.js'; +import { createProvider, providerURL } from '../test/providers.js'; import { chainFromName, chainName } from '../src/chains.js'; +import { isAddress } from 'ethers'; async function prompt(q: string) { process.stdout.write(q); @@ -10,24 +11,43 @@ async function prompt(q: string) { return ''; } -const chain = chainFromName(await prompt(`Chain (name or id): `)); -console.log(`Chain: ${chainName(chain)} (${chain})`); +const retry = 50; // why is the indexer so slow? + +const chain = chainFromName( + (await prompt(`Chain (name or id, default: 1): `)) || '1' +); +console.log(`Chain: ${chainName(chain)} (${chain}) ${providerURL(chain)}`); + +const input = await prompt( + 'Private Key (deploy) or Address (verify) or empty (simulate): ' +); const deployer = await FoundryDeployer.load({ provider: createProvider(chain), - privateKey: await prompt('Private Key (empty to simulate): '), + privateKey: isAddress(input) ? undefined : input, }); const deployable = await deployer.prepare({ file: 'OPFaultGameFinder', }); -if (deployer.privateKey) { - await prompt('Ready? (abort to stop) '); +if (isAddress(input)) { + await promptEtherscan(); + await deployable.verifyEtherscan({ address: input, retry }); +} else if (deployer.privateKey) { + await prompt('Deploy? (abort to stop) '); await deployable.deploy(); - const apiKey = - deployer.etherscanApiKey || (await prompt('Etherscan API Key: ')); - if (apiKey) { - await deployable.verifyEtherscan({ apiKey }); + await promptEtherscan(); + if (deployer.etherscanApiKey) { + await deployable.verifyEtherscan({ retry }); + } +} else { + console.log(deployable); + console.log(deployable.deployArgs().join(' ')); +} + +async function promptEtherscan() { + if (!deployer.etherscanApiKey) { + deployer.etherscanApiKey = await prompt('Etherscan API Key: '); } } diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index 4a9dc4aa..ea6c0ab3 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -55,8 +55,8 @@ type ABIFoundGame = { rootClaim: string; }; -const FINDER_MAINNET = '0xe7D342866b739950784a2ECBfad58e8C7f03D5F1'; // 20251113 -const FINDER_SEPOLIA = '0xd42962f7FCbe5e19cfF8A48deEAEd89fFb851748'; // 20251113 +const FINDER_MAINNET = '0x10EEa1d73DF3D8C0DE7d0C85b3Aad1062bF252e8'; // 20251115 +const FINDER_SEPOLIA = '0x8e1af190fB76198b86C83c5EFe2Fb0ADC1cbD50F'; // 20251115 export class OPFaultRollup extends AbstractOPRollup { static readonly ANCHOR_STATE_REGISTRY_ABI = ANCHOR_STATE_REGISTRY_ABI; diff --git a/test/research/op-fault/game-finders.md b/test/research/op-fault/game-finders.md index 206a3562..d670217b 100755 --- a/test/research/op-fault/game-finders.md +++ b/test/research/op-fault/game-finders.md @@ -2,30 +2,31 @@ ## Mainnet +* `2025-11-15` — [`0x10EEa1d73DF3D8C0DE7d0C85b3Aad1062bF252e8`](https://etherscan.io/address/0x10EEa1d73DF3D8C0DE7d0C85b3Aad1062bF252e8#code) + * `l2BlockNumberChallenged()` fallthru * `2025-11-15` — [`0xe7D342866b739950784a2ECBfad58e8C7f03D5F1`](https://etherscan.io/address/0xe7d342866b739950784a2ecbfad58e8c7f03d5f1#code) * switch to `IAnchorStateRegistry` * `isGameProper()` -* `2025-11-08` — [`0xA6FE3B15286D866CA1747a5C1c125d4dCFF5366e`](https://etherscan.io/address/0xa6fe3b15286d866ca1747a5c1c125d4dcff5366e#code) +* `2025-11-08` — [`0xA6FE3B15286D866CA1747a5C1c125d4dCFF5366e`](https://etherscan.io/address/0xA6FE3B15286D866CA1747a5C1c125d4dCFF5366e#code) * `_isUnchallenged()` -* `2025-11-08` — [`0xdc535021b10995e423607706Bc313F28a95CdB94`](https://etherscan.io/address/0xdc535021b10995e423607706bc313f28a95cdb94#code) +* `2025-11-08` — [`0xdc535021b10995e423607706Bc313F28a95CdB94`](https://etherscan.io/address/0xdc535021b10995e423607706Bc313F28a95CdB94#code) * removed `finalityDelay` check -* `2025-11-08` — [`0xAe212442b5FE242cEedc862995cce0Be0d4D16f7`](https://etherscan.io/address/0xae212442b5fe242ceedc862995cce0be0d4d16f7#code) +* `2025-11-08` — [`0xAe212442b5FE242cEedc862995cce0Be0d4D16f7`](https://etherscan.io/address/0xAe212442b5FE242cEedc862995cce0Be0d4D16f7#code) * unfinalized optimistic `l2BlockNumberChallenged()` -* `2025-11-06` — [`0x4E63bFa938fA1235A0ad7AaFA5165c6511f970eC`](https://etherscan.io/address/0x4e63bfa938fa1235a0ad7aafa5165c6511f970ec#code) +* `2025-11-06` — [`0x4E63bFa938fA1235A0ad7AaFA5165c6511f970eC`](https://etherscan.io/address/0x4E63bFa938fA1235A0ad7AaFA5165c6511f970eC#code) * unfinalized optimistic `l2BlockNumberChallenged()` * unfinalized status check -* `2025-10-14` — [`0x165386F8699ce2609A8903e25d00E1DEbD24A277`](https://etherscan.io/address/0x165386f8699ce2609a8903e25d00e1debd24a277#code) +* `2025-10-14` — [`0x165386F8699ce2609A8903e25d00E1DEbD24A277`](https://etherscan.io/address/0x165386F8699ce2609A8903e25d00E1DEbD24A277#code) * `OPFaultParams` * `2025-03-13` — [`0x61F50A76bfb2Ad8620A3E8F81aa27f3bEb1Db0D7`](https://etherscan.io/address/0x61F50A76bfb2Ad8620A3E8F81aa27f3bEb1Db0D7#code) * `FinalizationParams` -* `2024-10-05` — [`0x475a86934805eF2C52EF61A8FED644D4C9AC91d8`](https://etherscan.io/address/0x475a86934805ef2c52ef61a8fed644d4c9ac91d8) +* `2024-10-05` — [`0x475a86934805eF2C52EF61A8FED644D4C9AC91d8`](https://etherscan.io/address/0x475a86934805eF2C52EF61A8FED644D4C9AC91d8#code) * unfinalized * `2024-08-23` — [`0x5A8E83f0E728bEb821b91bB82cFAE7F67bD36f7e`](https://etherscan.io/address/0x5A8E83f0E728bEb821b91bB82cFAE7F67bD36f7e#code) * `2024-08-23` — [`0x480AadFd3A42266ec05013D5E2eF85752947D0B6`](https://etherscan.io/address/0x480aadfd3a42266ec05013d5e2ef85752947d0b6#code) -* `2024-08-23` — [`0x480AadFd3A42266ec05013D5E2eF85752947D0B6`](https://etherscan.io/address/0x480aadfd3a42266ec05013d5e2ef85752947d0b6#code) -* `2024-08-23` — [`0x48Af5c560c8393B60dd8792d19378fc8411EBFE1`](https://etherscan.io/address/0x48af5c560c8393b60dd8792d19378fc8411ebfe1#code) +* `2024-08-23` — [`0x48Af5c560c8393B60dd8792d19378fc8411EBFE1`](https://etherscan.io/address/0x48Af5c560c8393B60dd8792d19378fc8411EBFE1#code) * `_isFinalizedGame()` -* `2024-07-30` — [`0x6CbF8cd866a0FAE64b9C2B007D3D47c4E1B809fF`](https://etherscan.io/address/0x6cbf8cd866a0fae64b9c2b007d3d47c4e1b809ff#code) +* `2024-07-30` — [`0x6CbF8cd866a0FAE64b9C2B007D3D47c4E1B809fF`](https://etherscan.io/address/0x6CbF8cd866a0FAE64b9C2B007D3D47c4E1B809fF#code) * `OPFaultHelper` ## Sepolia From b2c525c82472cf5e23f45bb541092cc27cbcc59a Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Sun, 16 Nov 2025 22:02:40 -0800 Subject: [PATCH 15/15] changes for thomas + more investigation --- contracts/op/OPFaultGameFinder.sol | 70 +++++++++++++++++++++----- contracts/op/OPInterfaces.sol | 5 +- src/op/OPFaultRollup.ts | 4 +- test/research/op-fault/check-real.ts | 2 +- test/research/op-fault/game-finders.md | 2 + 5 files changed, 66 insertions(+), 17 deletions(-) diff --git a/contracts/op/OPFaultGameFinder.sol b/contracts/op/OPFaultGameFinder.sol index 7a37b37f..30c832dd 100755 --- a/contracts/op/OPFaultGameFinder.sol +++ b/contracts/op/OPFaultGameFinder.sol @@ -103,7 +103,7 @@ contract OPFaultGameFinder { if (!params.asr.isGameProper(gameProxy)) return false; if (params.minAgeSec > 0) { if (created > block.timestamp - params.minAgeSec) return false; - if (_isUnchallenged(gameProxy)) return true; + if (_isUnchallenged(params, gameProxy)) return true; } return gameProxy.status() == DEFENDER_WINS; // require resolved } @@ -130,6 +130,7 @@ contract OPFaultGameFinder { /// @dev Attempt to determine if the game is challenged in any sense. function _isUnchallenged( + OPFaultParams memory params, IDisputeGame gameProxy ) internal view returns (bool) { try @@ -142,18 +143,63 @@ contract OPFaultGameFinder { try IFaultDisputeGame(address(gameProxy)).claimDataLen() returns ( uint256 claims ) { - return claims == 1; - } catch {} - // if supportsInterface(IOPSuccinctFaultDisputeGame) - try - IOPSuccinctFaultDisputeGame(address(gameProxy)).claimData() - returns (IOPSuccinctFaultDisputeGame.ClaimData memory data) { - return - data.status == - IOPSuccinctFaultDisputeGame.ProposalStatus.Unchallenged; - } catch {} + if (claims == 1) return true; + } catch { + // else if supportsInterface(IOPSuccinctFaultDisputeGame) + try + IOPSuccinctFaultDisputeGame(address(gameProxy)).claimData() + returns (IOPSuccinctFaultDisputeGame.ClaimData memory data) { + if (_isUnchallengedStatus(data.status)) { + IDisputeGameFactory dgf = params.asr.disputeGameFactory(); + uint256 gameIndex = data.parentIndex; + uint256 gameType0 = gameProxy.gameType(); + while (true) { + if (gameIndex == type(uint32).max) return true; // anchor state is resolved + ( + uint256 gameType, + uint256 created, + IDisputeGame parentGame + ) = dgf.gameAtIndex(gameIndex); + if (gameType != gameType0) { + // this is a different game type + return + _isGameUsable( + parentGame, + gameType, + created, + params, + params.asr.respectedGameType() + ); + } + data = IOPSuccinctFaultDisputeGame(address(parentGame)) + .claimData(); + if (_isUnchallengedStatus(data.status)) { + gameIndex = data.parentIndex; // keep checking ancestry + } else if ( + data.status == + IOPSuccinctFaultDisputeGame.ProposalStatus.Resolved + ) { + return parentGame.status() == DEFENDER_WINS; + } else { + return false; + } + } + } + } catch {} + } // unknown type - // assume challenged and require resolution + // assume challenged and require resolved return false; } + + function _isUnchallengedStatus( + IOPSuccinctFaultDisputeGame.ProposalStatus status + ) internal pure returns (bool) { + return + status == IOPSuccinctFaultDisputeGame.ProposalStatus.Unchallenged || + status == + IOPSuccinctFaultDisputeGame + .ProposalStatus + .UnchallengedAndValidProofProvided; + } } diff --git a/contracts/op/OPInterfaces.sol b/contracts/op/OPInterfaces.sol index 435008b3..d55b1fa0 100644 --- a/contracts/op/OPInterfaces.sol +++ b/contracts/op/OPInterfaces.sol @@ -43,6 +43,7 @@ interface IDisputeGame { function wasRespectedGameTypeWhenCreated() external view returns (bool); function gameCreator() external view returns (address); function createdAt() external view returns (uint64); + function gameType() external view returns (uint256); } interface IOPFaultGameFinder { @@ -66,14 +67,14 @@ interface IOPFaultGameFinder { } // https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/interfaces/dispute/IFaultDisputeGame.sol -interface IFaultDisputeGame { +interface IFaultDisputeGame is IDisputeGame { function l2BlockNumberChallenged() external view returns (bool); // note: this is also on ISuperFaultDisputeGame function claimDataLen() external view returns (uint256); } // https://github.com/succinctlabs/op-succinct/blob/main/contracts/src/fp/OPSuccinctFaultDisputeGame.sol -interface IOPSuccinctFaultDisputeGame { +interface IOPSuccinctFaultDisputeGame is IDisputeGame { enum ProposalStatus { // The initial state of a new proposal. Unchallenged, diff --git a/src/op/OPFaultRollup.ts b/src/op/OPFaultRollup.ts index ea6c0ab3..6bdba46e 100755 --- a/src/op/OPFaultRollup.ts +++ b/src/op/OPFaultRollup.ts @@ -55,8 +55,8 @@ type ABIFoundGame = { rootClaim: string; }; -const FINDER_MAINNET = '0x10EEa1d73DF3D8C0DE7d0C85b3Aad1062bF252e8'; // 20251115 -const FINDER_SEPOLIA = '0x8e1af190fB76198b86C83c5EFe2Fb0ADC1cbD50F'; // 20251115 +const FINDER_MAINNET = '0xD3E53fE8AF05F6a9be11F311B104783ad862D145'; // 20251116 +const FINDER_SEPOLIA = '0x18CB9d88Be9dba20a2843d5e7CEB1658a9b26991'; // 20251116 export class OPFaultRollup extends AbstractOPRollup { static readonly ANCHOR_STATE_REGISTRY_ABI = ANCHOR_STATE_REGISTRY_ABI; diff --git a/test/research/op-fault/check-real.ts b/test/research/op-fault/check-real.ts index 67c814cd..0ef24970 100755 --- a/test/research/op-fault/check-real.ts +++ b/test/research/op-fault/check-real.ts @@ -13,7 +13,7 @@ const foundry = await Foundry.launch({ try { const OPFaultGameFinder = await foundry.deploy({ file: 'OPFaultGameFinder' }); const paramTuple = [ - OPFaultRollup.sepoliaConfig.AnchorStateRegistry, + OPFaultRollup.celoSepoliaConfig.AnchorStateRegistry, 21600, // minAgeSec [], [], diff --git a/test/research/op-fault/game-finders.md b/test/research/op-fault/game-finders.md index d670217b..6343f3eb 100755 --- a/test/research/op-fault/game-finders.md +++ b/test/research/op-fault/game-finders.md @@ -2,6 +2,8 @@ ## Mainnet +* `2025-11-16` — [`0xD3E53fE8AF05F6a9be11F311B104783ad862D145`](https://etherscan.io/address/0xD3E53fE8AF05F6a9be11F311B104783ad862D145#code) + * `UnchallengedAndValidProofProvided` + resolved ancestry * `2025-11-15` — [`0x10EEa1d73DF3D8C0DE7d0C85b3Aad1062bF252e8`](https://etherscan.io/address/0x10EEa1d73DF3D8C0DE7d0C85b3Aad1062bF252e8#code) * `l2BlockNumberChallenged()` fallthru * `2025-11-15` — [`0xe7D342866b739950784a2ECBfad58e8C7f03D5F1`](https://etherscan.io/address/0xe7d342866b739950784a2ecbfad58e8c7f03d5f1#code)