Commit 7ce6e3fa authored by Inphi's avatar Inphi Committed by GitHub

feat(ctb): Refactor LibPosition.sol to rep gindex (#6046)

* feat(ctb): Refactor LibPosition.sol to rep gindex

Generalized Indices are a more succinct and easy to understand model for
positions in the dispute game tree. Implementation is often more
efficient too and can avoid branching logic for tree traversal.

* update .gas-snapshot

* update .gas-snapshot

* Fix `FaultDisputeGame` tests

* lint; gas snapshot

* Update packages/contracts-bedrock/contracts/dispute/lib/LibPosition.sol
Co-authored-by: default avatarclabby <ben@clab.by>

* opt rightIndex

* opt rightIndex

* opt depth

* update gas-snapshot

---------
Co-authored-by: default avatarclabby <ben@clab.by>
parent b1683505
......@@ -32,19 +32,19 @@ 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_Test:test_clockTimeExceeded_reverts() (gas: 26413)
FaultDisputeGame_Test:test_clockTimeExceeded_reverts() (gas: 26468)
FaultDisputeGame_Test:test_defendRoot_reverts() (gas: 13258)
FaultDisputeGame_Test:test_duplicateClaim_reverts() (gas: 103259)
FaultDisputeGame_Test:test_duplicateClaim_reverts() (gas: 103369)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17478)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17859)
FaultDisputeGame_Test:test_gameDepthExceeded_reverts() (gas: 5903426)
FaultDisputeGame_Test:test_gameDepthExceeded_reverts() (gas: 5906891)
FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10337)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8194)
FaultDisputeGame_Test:test_initialRootClaimData_succeeds() (gas: 17580)
FaultDisputeGame_Test:test_moveAgainstNonexistentParent_reverts() (gas: 24587)
FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10945)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8191)
FaultDisputeGame_Test:test_simpleAttack_succeeds() (gas: 107391)
FaultDisputeGame_Test:test_simpleAttack_succeeds() (gas: 107361)
FaultDisputeGame_Test:test_version_succeeds() (gas: 9780)
FeeVault_Test:test_constructor_succeeds() (gas: 18185)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352135)
......
......@@ -41,9 +41,9 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
Duration internal constant GAME_DURATION = Duration.wrap(7 days);
/**
* @notice The root claim's position is always at depth 0; index 0.
* @notice The root claim's position is always at gindex 1.
*/
Position internal constant ROOT_POSITION = Position.wrap(0);
Position internal constant ROOT_POSITION = Position.wrap(1);
/**
* @notice The starting timestamp of the game
......
......@@ -10,7 +10,7 @@ import "../../libraries/DisputeTypes.sol";
library LibPosition {
function wrap(uint64 _depth, uint64 _indexAtDepth) internal pure returns (Position position_) {
assembly {
position_ := or(shl(0x40, _depth), _indexAtDepth)
position_ := add(shl(_depth, 1), _indexAtDepth)
}
}
......@@ -18,11 +18,29 @@ library LibPosition {
* @notice Pulls the `depth` out of a packed `Position` type.
* @param _position The position to get the `depth` of.
* @return depth_ The `depth` of the `position`.
* @custom:attribution Solady <https://github.com/Vectorized/Solady>
*/
function depth(Position _position) internal pure returns (uint64 depth_) {
// Shift the high-order 64 bits into the low-order 64 bits, leaving only the `depth`.
// Return the most significant bit position
assembly {
depth_ := shr(0x40, _position)
depth_ := or(depth_, shl(6, lt(0xffffffffffffffff, shr(depth_, _position))))
depth_ := or(depth_, shl(5, lt(0xffffffff, shr(depth_, _position))))
// For the remaining 32 bits, use a De Bruijn lookup.
_position := shr(depth_, _position)
_position := or(_position, shr(1, _position))
_position := or(_position, shr(2, _position))
_position := or(_position, shr(4, _position))
_position := or(_position, shr(8, _position))
_position := or(_position, shr(16, _position))
depth_ := or(
depth_,
byte(
shr(251, mul(_position, shl(224, 0x07c4acdd))),
0x0009010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f
)
)
}
}
......@@ -32,10 +50,10 @@ library LibPosition {
* @return indexAtDepth_ The `indexAtDepth` of the `position`.
*/
function indexAtDepth(Position _position) internal pure returns (uint64 indexAtDepth_) {
// Clean the high-order 192 bits by shifting the position left and then right again, leaving
// only the `indexAtDepth`.
// Return bits p_{msb-1}...p_{0}
uint256 msb = depth(_position);
assembly {
indexAtDepth_ := shr(0xC0, shl(0xC0, _position))
indexAtDepth_ := sub(_position, shl(msb, 1))
}
}
......@@ -45,12 +63,8 @@ library LibPosition {
* @return left_ The position to the left of `position`.
*/
function left(Position _position) internal pure returns (Position left_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Left = { depth: position.depth + 1, indexAtDepth: position.indexAtDepth * 2 }
assembly {
left_ := or(shl(0x40, add(_depth, 0x01)), shl(0x01, _indexAtDepth))
left_ := shl(1, _position)
}
}
......@@ -60,12 +74,8 @@ library LibPosition {
* @return right_ The position to the right of `position`.
*/
function right(Position _position) internal pure returns (Position right_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Right = { depth: position.depth + 1, indexAtDepth: position.indexAtDepth * 2 + 1 }
assembly {
right_ := or(shl(0x40, add(_depth, 0x01)), add(shl(0x01, _indexAtDepth), 0x01))
right_ := add(1, shl(1, _position))
}
}
......@@ -75,12 +85,8 @@ library LibPosition {
* @return parent_ The parent position of `position`.
*/
function parent(Position _position) internal pure returns (Position parent_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Parent = { depth: position.depth - 1, indexAtDepth: position.indexAtDepth / 2 }
assembly {
parent_ := or(shl(0x40, sub(_depth, 0x01)), shr(0x01, _indexAtDepth))
parent_ := shr(1, _position)
}
}
......@@ -90,14 +96,21 @@ library LibPosition {
* @param _maxDepth The maximum depth of the game.
* @return rightIndex_ The deepest, right most index relative to the `position`.
*/
function rightIndex(Position _position, uint256 _maxDepth) internal pure returns (uint64 rightIndex_) {
function rightIndex(
Position _position,
uint256 _maxDepth
) internal pure returns (uint64 rightIndex_) {
uint256 msb = depth(_position);
assembly {
rightIndex_ := shr(0xC0, shl(0xC0, _position))
// Walk down to the max depth by moving right
for { let i := shr(0x40, _position) } lt(i, sub(_maxDepth, 0x01)) { i := add(i, 0x01) } {
rightIndex_ := add(0x01, shl(0x01, rightIndex_))
switch eq(msb, _maxDepth)
case true {
rightIndex_ := _position
}
default {
let remaining := sub(_maxDepth, msb)
rightIndex_ := or(shl(remaining, _position), sub(shl(remaining, 1), 1))
}
rightIndex_ := sub(rightIndex_, shl(_maxDepth, 1))
}
}
......@@ -116,12 +129,8 @@ library LibPosition {
* @return defense_ The defense position relative to `position`.
*/
function defend(Position _position) internal pure returns (Position defense_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Defend = { depth: position.depth + 1, indexAtDepth: ((position.indexAtDepth / 2) * 2 + 1) * 2 }
assembly {
defense_ := or(shl(0x40, add(_depth, 0x01)), shl(0x01, add(0x01, shl(0x01, shr(0x01, _indexAtDepth)))))
defense_ := shl(1, add(1, shl(1, shr(1, _position))))
}
}
}
......@@ -59,13 +59,10 @@ type Clock is uint128;
/**
* @notice A `Position` represents a position of a claim within the game tree.
* @dev The packed layout of this type is as follows:
* ┌────────────┬────────────────┐
* │ Bits │ Value │
* ├────────────┼────────────────┤
* │ [0, 64) │ Depth │
* │ [64, 128) │ Index at depth │
* └────────────┴────────────────┘
* @dev This is represented as a "generalized index" where the high-order bit
* is the level in the tree and the remaining bits is a unique bit pattern, allowing
* a unique identifier for each node in the tree. Mathematically, it is calculated
* as 2^{depth} + indexAtDepth.
*/
type Position is uint128;
......
......@@ -122,7 +122,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
assertEq(parentIndex, type(uint32).max);
assertEq(countered, false);
assertEq(Claim.unwrap(claim), Claim.unwrap(ROOT_CLAIM));
assertEq(Position.unwrap(position), 0);
assertEq(Position.unwrap(position), 1);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))))
......@@ -247,7 +247,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
assertEq(parentIndex, 0);
assertEq(countered, false);
assertEq(Claim.unwrap(claim), Claim.unwrap(counter));
assertEq(Position.unwrap(position), Position.unwrap(LibPosition.attack(Position.wrap(0))));
assertEq(Position.unwrap(position), Position.unwrap(LibPosition.attack(Position.wrap(1))));
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(5), Timestamp.wrap(uint64(block.timestamp))))
......@@ -260,7 +260,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
assertEq(parentIndex, type(uint32).max);
assertEq(countered, true);
assertEq(Claim.unwrap(claim), Claim.unwrap(ROOT_CLAIM));
assertEq(Position.unwrap(position), 0);
assertEq(Position.unwrap(position), 1);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(
......
......@@ -16,10 +16,21 @@ contract LibPosition_Test is Test {
*/
uint8 internal constant MAX_DEPTH = 63;
function boundIndexAtDepth(uint8 _depth, uint64 _indexAtDepth) internal view returns (uint64) {
// Index at depth bound: [0, 2 ** _depth-1]
if (_depth > 0) {
return uint64(bound(_indexAtDepth, 0, 2**(_depth - 1)));
} else {
return 0;
}
}
/**
* @notice Tests that the `depth` function correctly shifts out the `depth` from a packed `Position` type.
*/
function testFuzz_depth_correctness(uint64 _depth, uint64 _indexAtDepth) public {
function testFuzz_depth_correctness(uint8 _depth, uint64 _indexAtDepth) public {
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth);
assertEq(LibPosition.depth(position), _depth);
}
......@@ -27,7 +38,9 @@ contract LibPosition_Test is Test {
/**
* @notice Tests that the `indexAtDepth` function correctly shifts out the `indexAtDepth` from a packed `Position` type.
*/
function testFuzz_indexAtDepth_correctness(uint64 _depth, uint64 _indexAtDepth) public {
function testFuzz_indexAtDepth_correctness(uint8 _depth, uint64 _indexAtDepth) public {
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth);
assertEq(LibPosition.indexAtDepth(position), _indexAtDepth);
}
......@@ -36,10 +49,8 @@ contract LibPosition_Test is Test {
* @notice Tests that the `left` function correctly computes the position of the left child.
*/
function testFuzz_left_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position left = LibPosition.left(position);
......@@ -54,8 +65,7 @@ contract LibPosition_Test is Test {
function testFuzz_right_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position right = LibPosition.right(position);
......@@ -68,10 +78,8 @@ contract LibPosition_Test is Test {
* @notice Tests that the `parent` function correctly computes the position of the parent.
*/
function testFuzz_parent_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [1, 63]
_depth = uint8(bound(_depth, 1, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position parent = LibPosition.parent(position);
......@@ -85,7 +93,7 @@ contract LibPosition_Test is Test {
* to a given position.
*/
function testFuzz_rightIndex_correctness(
uint8 _maxDepth,
uint64 _maxDepth,
uint8 _depth,
uint64 _indexAtDepth
) public {
......@@ -94,14 +102,13 @@ contract LibPosition_Test is Test {
_maxDepth = uint8(bound(_maxDepth, 1, MAX_DEPTH));
// Depth bound: [0, _maxDepth]
_depth = uint8(bound(_depth, 0, _maxDepth));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth);
uint64 rightIndex = LibPosition.rightIndex(position, _maxDepth);
// Find the deepest, rightmost index in Solidity rather than Yul
for (uint256 i = _depth; i < _maxDepth - 1; ++i) {
for (uint256 i = _depth; i < _maxDepth; ++i) {
position = LibPosition.right(position);
}
uint64 _rightIndex = LibPosition.indexAtDepth(position);
......@@ -117,8 +124,7 @@ contract LibPosition_Test is Test {
function testFuzz_attack_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position attack = LibPosition.attack(position);
......@@ -136,8 +142,7 @@ contract LibPosition_Test is Test {
function testFuzz_defend_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [1, 63]
_depth = uint8(bound(_depth, 1, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position defend = LibPosition.defend(position);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment