Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Binary file modified bun.lockb
Binary file not shown.
10 changes: 9 additions & 1 deletion contracts/GatewayFetchTarget.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
)
);
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/GatewayFetcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ library GatewayFetcher {
assembly {
mstore(v, 0) // length = 0
}
return GatewayRequest(v);
return GatewayRequest({ops: v});
}

function encode(
Expand Down
16 changes: 8 additions & 8 deletions contracts/InteractiveVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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
})
);
}
}
8 changes: 7 additions & 1 deletion contracts/SelfVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
);
}
}
8 changes: 7 additions & 1 deletion contracts/arbitrum/ArbitrumVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
);
}
}
16 changes: 14 additions & 2 deletions contracts/arbitrum/DoubleArbitrumVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
})
);
}
}
8 changes: 7 additions & 1 deletion contracts/linea/LineaVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
);
}
}
8 changes: 7 additions & 1 deletion contracts/linea/UnfinalizedLineaVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
);
}

Expand Down
183 changes: 127 additions & 56 deletions contracts/op/OPFaultGameFinder.sol
Original file line number Diff line number Diff line change
@@ -1,48 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import { IDisputeGameFactory, IDisputeGame, IFaultDisputeGame } from './OPInterfaces.sol';
import { OPFaultParams, FinalizationParams } from './OPStructs.sol';
import {
IDisputeGameFactory,
IDisputeGame,
IFaultDisputeGame,
IOPSuccinctFaultDisputeGame
} from './OPInterfaces.sol';
import {OPFaultParams} from './OPStructs.sol';

// https://github.com/ethereum-optimism/optimism/issues/11269

// https://github.com/ethereum-optimism/optimism/blob/v1.13.7/packages/contracts-bedrock/src/dispute/lib/Types.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();

contract OPFaultGameFinder {
function findGameIndex(
OPFaultParams memory params,
uint256 gameCount
uint256 gameBound
) external view virtual returns (uint256) {
FinalizationParams memory finalizationParams = FinalizationParams({
finalityDelay: params.portal.disputeGameFinalityDelaySeconds(),
gameTypeUpdatedAt: params.portal.respectedGameTypeUpdatedAt()
});
IDisputeGameFactory factory = params.portal.disputeGameFactory();
if (gameCount == 0) gameCount = factory.gameCount();
while (gameCount > 0) {
(
uint256 gameType,
uint256 created,
IDisputeGame gameProxy
) = factory.gameAtIndex(--gameCount);
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
.gameAtIndex(--gameBound);
if (
_isGameUsable(
gameProxy,
gameType,
created,
params,
finalizationParams
respectedGameType
)
) {
return gameCount;
return gameBound;
}
}
revert GameNotFound();
Expand All @@ -62,19 +57,15 @@ 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);
IDisputeGameFactory dgf = params.asr.disputeGameFactory();
(gameType, created, gameProxy) = dgf.gameAtIndex(gameIndex);
if (
_isGameUsable(
gameProxy,
gameType,
created,
params,
finalizationParams
params.asr.respectedGameType()
)
) {
l2BlockNumber = gameProxy.l2BlockNumber();
Expand All @@ -87,48 +78,128 @@ contract OPFaultGameFinder {
uint256 gameType,
uint256 created,
OPFaultParams memory params,
FinalizationParams memory finalizationParams
uint256 respectedGameType
) 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 == respectedGameType ||
gameProxy.wasRespectedGameTypeWhenCreated())
: _isAllowedGameType(gameType, params.allowedGameTypes)
)
) {
return false;
}
// if no proposer restrictions or proposer is whitelisted
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;
// https://specs.optimism.io/fault-proof/stage-one/anchor-state-registry.html#proper-game
if (!params.asr.isGameProper(gameProxy)) 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;
}
// 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);
if (_isUnchallenged(params, gameProxy)) return true;
}
return false;
return gameProxy.status() == DEFENDER_WINS; // require resolved
}

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 allowedProposers.length == 0;
}

/// @dev Attempt to determine if the game is challenged in any sense.
function _isUnchallenged(
OPFaultParams memory params,
IDisputeGame gameProxy
) internal view returns (bool) {
try
IFaultDisputeGame(address(gameProxy)).l2BlockNumberChallenged()
returns (bool 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
) {
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 resolved
return false;
}

function _isUnchallengedStatus(
IOPSuccinctFaultDisputeGame.ProposalStatus status
) internal pure returns (bool) {
return
status == IOPSuccinctFaultDisputeGame.ProposalStatus.Unchallenged ||
status ==
IOPSuccinctFaultDisputeGame
.ProposalStatus
.UnchallengedAndValidProofProvided;
}
}
Loading
Loading