Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
84a57d43
Unverified
Commit
84a57d43
authored
Jul 10, 2023
by
clabby
Committed by
GitHub
Jul 10, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6227 from ethereum-optimism/clabby/ctb/fix-step-bug
fixup(ctb): `step` bug
parents
2cb88656
227eaa6c
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
132 additions
and
94 deletions
+132
-94
faultdisputegame.go
op-bindings/bindings/faultdisputegame.go
+14
-14
faultdisputegame_more.go
op-bindings/bindings/faultdisputegame_more.go
+1
-1
.gas-snapshot
packages/contracts-bedrock/.gas-snapshot
+6
-6
FaultDisputeGame.sol
.../contracts-bedrock/contracts/dispute/FaultDisputeGame.sol
+51
-33
IFaultDisputeGame.sol
...edrock/contracts/dispute/interfaces/IFaultDisputeGame.sol
+0
-2
LibPosition.sol
...s/contracts-bedrock/contracts/dispute/lib/LibPosition.sol
+21
-0
FaultDisputeGame.t.sol
...s/contracts-bedrock/contracts/test/FaultDisputeGame.t.sol
+21
-38
LibPosition.t.sol
packages/contracts-bedrock/contracts/test/LibPosition.t.sol
+18
-0
No files found.
op-bindings/bindings/faultdisputegame.go
View file @
84a57d43
This diff is collapsed.
Click to expand it.
op-bindings/bindings/faultdisputegame_more.go
View file @
84a57d43
This diff is collapsed.
Click to expand it.
packages/contracts-bedrock/.gas-snapshot
View file @
84a57d43
...
...
@@ -32,12 +32,12 @@ DisputeGameFactory_SetImplementation_Test:test_setImplementation_notOwner_revert
DisputeGameFactory_SetImplementation_Test:test_setImplementation_succeeds() (gas: 44243)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_notOwner_reverts() (gas: 15950)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_succeeds() (gas: 18642)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot
2:test_resolvesCorrectly_succeeds() (gas: 50216
9)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot
3:test_resolvesCorrectly_succeeds() (gas: 504048
)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot
:test_resolvesCorrectly_succeeds() (gas: 491512
)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot
2:test_resolvesCorrectly_succeeds() (gas: 500932
)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot
3:test_resolvesCorrectly_succeeds() (gas: 502811
)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot
:test_resolvesCorrectly_succeeds() (gas: 490275
)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot
1:test_resolvesCorrectly_succeeds() (gas: 49183
9)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot
2:test_resolvesCorrectly_succeeds() (gas: 495751
)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot
3:test_resolvesCorrectly_succeeds() (gas: 495049
)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot
1:test_resolvesCorrectly_succeeds() (gas: 490600
)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot
2:test_resolvesCorrectly_succeeds() (gas: 494512
)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot
3:test_resolvesCorrectly_succeeds() (gas: 493810
)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17426)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17917)
FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10315)
...
...
packages/contracts-bedrock/contracts/dispute/FaultDisputeGame.sol
View file @
84a57d43
...
...
@@ -71,7 +71,6 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
/// @inheritdoc IFaultDisputeGame
function step(
uint256 _stateIndex,
uint256 _claimIndex,
bool _isAttack,
bytes calldata _stateData,
...
...
@@ -94,43 +93,36 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
// Determine the expected pre & post states of the step.
Claim preStateClaim;
Claim postStateClaim;
if (stepPos.indexAtDepth() == 0) {
// If the step position's index at depth is 0, the prestate is the absolute prestate
// and the post state is the parent claim.
preStateClaim = ABSOLUTE_PRESTATE;
postStateClaim = claimData[_claimIndex].claim;
} else {
Position preStatePos;
Position postStatePos;
if (_isAttack) {
// If the step is an attack, the prestate exists elsewhere in the game state,
// and the parent claim is the expected post-state.
preStatePos = claimData[_stateIndex].position;
preStateClaim = claimData[_stateIndex].claim;
postStatePos = parentPos;
postStateClaim = parent.claim;
if (_isAttack) {
if (stepPos.indexAtDepth() == 0) {
// If the step position's index at depth is 0, the prestate is the absolute
// prestate.
preStateClaim = ABSOLUTE_PRESTATE;
} else {
// If the step is a
defense, the poststate exists elsewhere in the game state,
//
and the parent claim is the expected pre-
state.
preState
Pos = parent.position;
preStateClaim = parent.claim;
postStatePos = claimData[_stateIndex].position;
postStateClaim = claimData[_stateIndex].claim
;
// If the step is a
n attack at a trace index > 0, the prestate exists elsewhere in
//
the game
state.
preState
Claim = findTraceAncestor(
Position.wrap(Position.unwrap(parentPos) - 1),
parent.parentIndex
)
;
}
// INVARIANT: The prestate is always invalid if its gindex is not one less than that
// of the post state.
// INVARIANT: The prestate is always invalid if the passed `_stateData` is not the
// preimage of the prestate claim hash.
if (
Position.unwrap(preStatePos.rightIndex(MAX_GAME_DEPTH)) !=
Position.unwrap(postStatePos.rightIndex(MAX_GAME_DEPTH)) - 1 ||
keccak256(_stateData) != Claim.unwrap(preStateClaim)
) {
revert InvalidPrestate();
}
// For all attacks, the poststate is the parent claim.
postStateClaim = parent.claim;
} else {
// If the step is a defense, the poststate exists elsewhere in the game state,
// and the parent claim is the expected pre-state.
preStateClaim = parent.claim;
postStateClaim = findTraceAncestor(
Position.wrap(Position.unwrap(parentPos) + 1),
parent.parentIndex
);
}
// INVARIANT: The prestate is always invalid if the passed `_stateData` is not the
// preimage of the prestate claim hash.
if (keccak256(_stateData) != Claim.unwrap(preStateClaim)) revert InvalidPrestate();
// INVARIANT: A VM step can never counter a parent claim unless it produces a poststate
// that is not equal to the claim at `_parentIndex` if the step is an attack,
// or the claim at `_stateIndex` if the step is a defense.
...
...
@@ -379,4 +371,30 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
function claimDataLen() external view returns (uint256 len_) {
len_ = claimData.length;
}
////////////////////////////////////////////////////////////////
// HELPERS //
////////////////////////////////////////////////////////////////
/// @notice Finds the trace ancestor of a given position within the DAG.
/// @param _pos The position to find the trace ancestor claim of.
/// @param _start The index to start searching from.
/// @return ancestor_ The ancestor claim that commits to the same trace index as `_pos`.
// TODO: Can we form a relationship between the trace path and the position to avoid looping?
function findTraceAncestor(Position _pos, uint256 _start)
internal
view
returns (Claim ancestor_)
{
// Grab the trace ancestor's expected position.
Position preStateTraceAncestor = _pos.traceAncestor();
// Walk up the DAG to find a claim that commits to the same trace index as `_pos`. It is
// guaranteed that such a claim exists.
ClaimData storage ancestor = claimData[_start];
while (Position.unwrap(ancestor.position) != Position.unwrap(preStateTraceAncestor)) {
ancestor = claimData[ancestor.parentIndex];
}
ancestor_ = ancestor.claim;
}
}
packages/contracts-bedrock/contracts/dispute/interfaces/IFaultDisputeGame.sol
View file @
84a57d43
...
...
@@ -39,7 +39,6 @@ interface IFaultDisputeGame is IDisputeGame {
/// a step in the fault proof program on-chain. The interface of the fault proof
/// processor contract should be generic enough such that we can use different
/// fault proof VMs (MIPS, RiscV5, etc.)
/// @param _stateIndex The index of the pre/post state of the step within `claimData`.
/// @param _claimIndex The index of the challenged claim within `claimData`.
/// @param _isAttack Whether or not the step is an attack or a defense.
/// @param _stateData The stateData of the step is the preimage of the claim at the given
...
...
@@ -48,7 +47,6 @@ interface IFaultDisputeGame is IDisputeGame {
/// the absolute prestate of the fault proof VM.
/// @param _proof Proof to access memory leaf nodes in the VM.
function step(
uint256 _stateIndex,
uint256 _claimIndex,
bool _isAttack,
bytes calldata _stateData,
...
...
packages/contracts-bedrock/contracts/dispute/lib/LibPosition.sol
View file @
84a57d43
...
...
@@ -124,6 +124,27 @@ library LibPosition {
}
}
/// @notice Gets the position of the highest ancestor of `_position` that commits to the same
/// trace index.
/// @param _position The position to get the highest ancestor of.
/// @return ancestor_ The highest ancestor of `position` that commits to the same trace index.
function traceAncestor(Position _position) internal pure returns (Position ancestor_) {
// Create a field with only the lowest unset bit of `_position` set.
Position lsb;
assembly {
lsb := and(not(_position), add(_position, 1))
}
// Find the index of the lowest unset bit within the field.
uint256 msb = depth(lsb);
// The highest ancestor that commits to the same trace index is the original position
// shifted right by the index of the lowest unset bit.
assembly {
let a := shr(msb, _position)
// Bound the ancestor to the minimum gindex, 1.
ancestor_ := or(a, iszero(a))
}
}
/// @notice Get the move position of `_position`, which is the left child of:
/// 1. `_position + 1` if `_isAttack` is true.
/// 1. `_position` if `_isAttack` is false.
...
...
packages/contracts-bedrock/contracts/test/FaultDisputeGame.t.sol
View file @
84a57d43
...
...
@@ -424,7 +424,6 @@ contract GamePlayer {
// If we are past the maximum depth, break the recursion and step.
if (movePos.depth() > maxDepth) {
uint256 stateIndex;
bytes memory preStateTrace;
// First, we need to find the pre/post state index depending on whether we
...
...
@@ -435,27 +434,7 @@ contract GamePlayer {
Position leafPos = isAttack
? Position.wrap(Position.unwrap(parentPos) - 1)
: Position.wrap(Position.unwrap(parentPos) + 1);
Position statePos = leafPos;
// Walk up until the valid position that commits to the prestate's
// trace index is found.
while (
Position.unwrap(statePos.parent().rightIndex(maxDepth)) ==
Position.unwrap(leafPos)
) {
statePos = statePos.parent();
}
// Now, search for the index of the claim that commits to the prestate's trace
// index.
uint256 len = gameProxy.claimDataLen();
for (uint256 i = 0; i < len; i++) {
(, , , Position pos, ) = gameProxy.claimData(i);
if (Position.unwrap(pos) == Position.unwrap(statePos)) {
stateIndex = i;
break;
}
}
Position statePos = leafPos.traceAncestor();
// Grab the trace up to the prestate's trace index.
if (isAttack) {
...
...
@@ -463,10 +442,12 @@ contract GamePlayer {
} else {
preStateTrace = abi.encode(parentPos.traceIndex(maxDepth), traceAt(parentPos));
}
} else {
preStateTrace = abi.encode(15);
}
// Perform the step and halt recursion.
try gameProxy.step(
stateIndex,
_parentIndex, isAttack, preStateTrace, hex"") {
try gameProxy.step(_parentIndex, isAttack, preStateTrace, hex"") {
// Do nothing, step succeeded.
} catch {
failedToStep = true;
...
...
@@ -523,7 +504,9 @@ contract GamePlayer {
contract OneVsOne_Arena is FaultDisputeGame_Init {
/// @dev The absolute prestate of the trace.
Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32(uint256(15)));
bytes ABSOLUTE_PRESTATE = abi.encode(15);
/// @dev The absolute prestate claim.
Claim internal constant ABSOLUTE_PRESTATE_CLAIM = Claim.wrap(keccak256(abi.encode(15)));
/// @dev The defender.
GamePlayer internal defender;
/// @dev The challenger.
...
...
@@ -531,7 +514,7 @@ contract OneVsOne_Arena is FaultDisputeGame_Init {
function init(GamePlayer _defender, GamePlayer _challenger) public {
Claim rootClaim = Claim.wrap(keccak256(abi.encode(15, _defender.traceAt(15))));
super.init(rootClaim, ABSOLUTE_PRESTATE);
super.init(rootClaim, ABSOLUTE_PRESTATE
_CLAIM
);
defender = _defender;
challenger = _challenger;
...
...
@@ -545,7 +528,7 @@ contract OneVsOne_Arena is FaultDisputeGame_Init {
}
}
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena {
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot
1
is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new FullyDivergentPlayer(ABSOLUTE_PRESTATE);
...
...
@@ -566,7 +549,7 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena {
}
}
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot is OneVsOne_Arena {
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot
1
is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new FullyDivergentPlayer(ABSOLUTE_PRESTATE);
...
...
@@ -676,8 +659,8 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot3 is OneVsOne_Arena {
////////////////////////////////////////////////////////////////
contract HonestPlayer is GamePlayer {
constructor(
Claim
_absolutePrestate) {
uint8 absolutePrestate = uint8(
uint256(Claim.unwrap(_absolutePrestate))
);
constructor(
bytes memory
_absolutePrestate) {
uint8 absolutePrestate = uint8(
_absolutePrestate[31]
);
bytes memory honestTrace = new bytes(16);
for (uint8 i = 0; i < honestTrace.length; i++) {
honestTrace[i] = bytes1(absolutePrestate + i + 1);
...
...
@@ -687,8 +670,8 @@ contract HonestPlayer is GamePlayer {
}
contract FullyDivergentPlayer is GamePlayer {
constructor(
Claim
_absolutePrestate) {
uint8 absolutePrestate = uint8(
uint256(Claim.unwrap(_absolutePrestate))
);
constructor(
bytes memory
_absolutePrestate) {
uint8 absolutePrestate = uint8(
_absolutePrestate[31]
);
bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the honest trace by 1.
...
...
@@ -699,8 +682,8 @@ contract FullyDivergentPlayer is GamePlayer {
}
contract HalfDivergentPlayer is GamePlayer {
constructor(
Claim
_absolutePrestate) {
uint8 absolutePrestate = uint8(
uint256(Claim.unwrap(_absolutePrestate))
);
constructor(
bytes memory
_absolutePrestate) {
uint8 absolutePrestate = uint8(
_absolutePrestate[31]
);
bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the trace after the first half.
...
...
@@ -711,11 +694,11 @@ contract HalfDivergentPlayer is GamePlayer {
}
contract EarlyDivergentPlayer is GamePlayer {
constructor(
Claim
_absolutePrestate) {
uint8 absolutePrestate = uint8(
uint256(Claim.unwrap(_absolutePrestate))
);
constructor(
bytes memory
_absolutePrestate) {
uint8 absolutePrestate = uint8(
_absolutePrestate[31]
);
bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the trace after the first
half
.
// Offset the trace after the first
3 instructions
.
dishonestTrace[i] = i > 2 ? bytes1(i) : bytes1(absolutePrestate + i + 1);
}
trace = dishonestTrace;
...
...
@@ -741,10 +724,10 @@ contract AlphabetVM is IBigStepper {
{
uint256 traceIndex;
uint256 claim;
if (
_stateData.length == 0
) {
if (
keccak256(_stateData) == Claim.unwrap(ABSOLUTE_PRESTATE)
) {
// If the state data is empty, then the absolute prestate is the claim.
traceIndex = 0;
claim = uint256(Claim.unwrap(ABSOLUTE_PRESTATE
));
(claim) = abi.decode(_stateData, (uint256
));
} else {
// Otherwise, decode the state data.
(traceIndex, claim) = abi.decode(_stateData, (uint256, uint256));
...
...
packages/contracts-bedrock/contracts/test/LibPosition.t.sol
View file @
84a57d43
...
...
@@ -74,6 +74,24 @@ contract LibPosition_Test is Test {
assertEq(parent.indexAtDepth(), _indexAtDepth / 2);
}
/// @notice Tests that the `traceAncestor` function correctly computes the position of the
/// highest ancestor that commits to the same trace index.
function testFuzz_traceAncestor_correctness_succeeds(uint8 _depth, uint64 _indexAtDepth)
public
{
_depth = uint8(bound(_depth, 1, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position ancestor = position.traceAncestor();
Position loopAncestor = position;
while (loopAncestor.parent().traceIndex(MAX_DEPTH) == position.traceIndex(MAX_DEPTH)) {
loopAncestor = loopAncestor.parent();
}
assertEq(Position.unwrap(ancestor), Position.unwrap(loopAncestor));
}
/// @notice Tests that the `rightIndex` function correctly computes the deepest, right most index relative
/// to a given position.
function testFuzz_rightIndex_correctness_succeeds(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment