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 ...@@ -32,19 +32,19 @@ DisputeGameFactory_SetImplementation_Test:test_setImplementation_notOwner_revert
DisputeGameFactory_SetImplementation_Test:test_setImplementation_succeeds() (gas: 44243) DisputeGameFactory_SetImplementation_Test:test_setImplementation_succeeds() (gas: 44243)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_notOwner_reverts() (gas: 15950) DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_notOwner_reverts() (gas: 15950)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_succeeds() (gas: 18642) 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_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_extraData_succeeds() (gas: 17478)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17859) 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_gameStart_succeeds() (gas: 10337)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8194) FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8194)
FaultDisputeGame_Test:test_initialRootClaimData_succeeds() (gas: 17580) FaultDisputeGame_Test:test_initialRootClaimData_succeeds() (gas: 17580)
FaultDisputeGame_Test:test_moveAgainstNonexistentParent_reverts() (gas: 24587) FaultDisputeGame_Test:test_moveAgainstNonexistentParent_reverts() (gas: 24587)
FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10945) FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10945)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8191) 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) FaultDisputeGame_Test:test_version_succeeds() (gas: 9780)
FeeVault_Test:test_constructor_succeeds() (gas: 18185) FeeVault_Test:test_constructor_succeeds() (gas: 18185)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352135) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352135)
......
...@@ -41,9 +41,9 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -41,9 +41,9 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
Duration internal constant GAME_DURATION = Duration.wrap(7 days); 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 * @notice The starting timestamp of the game
......
...@@ -10,7 +10,7 @@ import "../../libraries/DisputeTypes.sol"; ...@@ -10,7 +10,7 @@ import "../../libraries/DisputeTypes.sol";
library LibPosition { library LibPosition {
function wrap(uint64 _depth, uint64 _indexAtDepth) internal pure returns (Position position_) { function wrap(uint64 _depth, uint64 _indexAtDepth) internal pure returns (Position position_) {
assembly { assembly {
position_ := or(shl(0x40, _depth), _indexAtDepth) position_ := add(shl(_depth, 1), _indexAtDepth)
} }
} }
...@@ -18,11 +18,29 @@ library LibPosition { ...@@ -18,11 +18,29 @@ library LibPosition {
* @notice Pulls the `depth` out of a packed `Position` type. * @notice Pulls the `depth` out of a packed `Position` type.
* @param _position The position to get the `depth` of. * @param _position The position to get the `depth` of.
* @return depth_ The `depth` of the `position`. * @return depth_ The `depth` of the `position`.
* @custom:attribution Solady <https://github.com/Vectorized/Solady>
*/ */
function depth(Position _position) internal pure returns (uint64 depth_) { 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 { 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 { ...@@ -32,10 +50,10 @@ library LibPosition {
* @return indexAtDepth_ The `indexAtDepth` of the `position`. * @return indexAtDepth_ The `indexAtDepth` of the `position`.
*/ */
function indexAtDepth(Position _position) internal pure returns (uint64 indexAtDepth_) { 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 // Return bits p_{msb-1}...p_{0}
// only the `indexAtDepth`. uint256 msb = depth(_position);
assembly { assembly {
indexAtDepth_ := shr(0xC0, shl(0xC0, _position)) indexAtDepth_ := sub(_position, shl(msb, 1))
} }
} }
...@@ -45,12 +63,8 @@ library LibPosition { ...@@ -45,12 +63,8 @@ library LibPosition {
* @return left_ The position to the left of `position`. * @return left_ The position to the left of `position`.
*/ */
function left(Position _position) internal pure returns (Position left_) { 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 { assembly {
left_ := or(shl(0x40, add(_depth, 0x01)), shl(0x01, _indexAtDepth)) left_ := shl(1, _position)
} }
} }
...@@ -60,12 +74,8 @@ library LibPosition { ...@@ -60,12 +74,8 @@ library LibPosition {
* @return right_ The position to the right of `position`. * @return right_ The position to the right of `position`.
*/ */
function right(Position _position) internal pure returns (Position right_) { 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 { 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 { ...@@ -75,12 +85,8 @@ library LibPosition {
* @return parent_ The parent position of `position`. * @return parent_ The parent position of `position`.
*/ */
function parent(Position _position) internal pure returns (Position parent_) { 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 { assembly {
parent_ := or(shl(0x40, sub(_depth, 0x01)), shr(0x01, _indexAtDepth)) parent_ := shr(1, _position)
} }
} }
...@@ -90,14 +96,21 @@ library LibPosition { ...@@ -90,14 +96,21 @@ library LibPosition {
* @param _maxDepth The maximum depth of the game. * @param _maxDepth The maximum depth of the game.
* @return rightIndex_ The deepest, right most index relative to the `position`. * @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 { assembly {
rightIndex_ := shr(0xC0, shl(0xC0, _position)) switch eq(msb, _maxDepth)
case true {
// Walk down to the max depth by moving right rightIndex_ := _position
for { let i := shr(0x40, _position) } lt(i, sub(_maxDepth, 0x01)) { i := add(i, 0x01) } {
rightIndex_ := add(0x01, shl(0x01, rightIndex_))
} }
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 { ...@@ -116,12 +129,8 @@ library LibPosition {
* @return defense_ The defense position relative to `position`. * @return defense_ The defense position relative to `position`.
*/ */
function defend(Position _position) internal pure returns (Position defense_) { 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 { 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; ...@@ -59,13 +59,10 @@ type Clock is uint128;
/** /**
* @notice A `Position` represents a position of a claim within the game tree. * @notice A `Position` represents a position of a claim within the game tree.
* @dev The packed layout of this type is as follows: * @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
* │ Bits │ Value │ * a unique identifier for each node in the tree. Mathematically, it is calculated
* ├────────────┼────────────────┤ * as 2^{depth} + indexAtDepth.
* │ [0, 64) │ Depth │
* │ [64, 128) │ Index at depth │
* └────────────┴────────────────┘
*/ */
type Position is uint128; type Position is uint128;
......
...@@ -122,7 +122,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -122,7 +122,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
assertEq(parentIndex, type(uint32).max); assertEq(parentIndex, type(uint32).max);
assertEq(countered, false); assertEq(countered, false);
assertEq(Claim.unwrap(claim), Claim.unwrap(ROOT_CLAIM)); assertEq(Claim.unwrap(claim), Claim.unwrap(ROOT_CLAIM));
assertEq(Position.unwrap(position), 0); assertEq(Position.unwrap(position), 1);
assertEq( assertEq(
Clock.unwrap(clock), Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp)))) Clock.unwrap(LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))))
...@@ -247,7 +247,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -247,7 +247,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
assertEq(parentIndex, 0); assertEq(parentIndex, 0);
assertEq(countered, false); assertEq(countered, false);
assertEq(Claim.unwrap(claim), Claim.unwrap(counter)); 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( assertEq(
Clock.unwrap(clock), Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(5), Timestamp.wrap(uint64(block.timestamp)))) Clock.unwrap(LibClock.wrap(Duration.wrap(5), Timestamp.wrap(uint64(block.timestamp))))
...@@ -260,7 +260,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -260,7 +260,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
assertEq(parentIndex, type(uint32).max); assertEq(parentIndex, type(uint32).max);
assertEq(countered, true); assertEq(countered, true);
assertEq(Claim.unwrap(claim), Claim.unwrap(ROOT_CLAIM)); assertEq(Claim.unwrap(claim), Claim.unwrap(ROOT_CLAIM));
assertEq(Position.unwrap(position), 0); assertEq(Position.unwrap(position), 1);
assertEq( assertEq(
Clock.unwrap(clock), Clock.unwrap(clock),
Clock.unwrap( Clock.unwrap(
......
...@@ -16,10 +16,21 @@ contract LibPosition_Test is Test { ...@@ -16,10 +16,21 @@ contract LibPosition_Test is Test {
*/ */
uint8 internal constant MAX_DEPTH = 63; 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. * @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); Position position = LibPosition.wrap(_depth, _indexAtDepth);
assertEq(LibPosition.depth(position), _depth); assertEq(LibPosition.depth(position), _depth);
} }
...@@ -27,7 +38,9 @@ contract LibPosition_Test is Test { ...@@ -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. * @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); Position position = LibPosition.wrap(_depth, _indexAtDepth);
assertEq(LibPosition.indexAtDepth(position), _indexAtDepth); assertEq(LibPosition.indexAtDepth(position), _indexAtDepth);
} }
...@@ -36,10 +49,8 @@ contract LibPosition_Test is Test { ...@@ -36,10 +49,8 @@ contract LibPosition_Test is Test {
* @notice Tests that the `left` function correctly computes the position of the left child. * @notice Tests that the `left` function correctly computes the position of the left child.
*/ */
function testFuzz_left_correctness(uint8 _depth, uint64 _indexAtDepth) public { function testFuzz_left_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH)); _depth = uint8(bound(_depth, 0, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth] _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position left = LibPosition.left(position); Position left = LibPosition.left(position);
...@@ -54,8 +65,7 @@ contract LibPosition_Test is Test { ...@@ -54,8 +65,7 @@ contract LibPosition_Test is Test {
function testFuzz_right_correctness(uint8 _depth, uint64 _indexAtDepth) public { function testFuzz_right_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63] // Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH)); _depth = uint8(bound(_depth, 0, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth] _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position right = LibPosition.right(position); Position right = LibPosition.right(position);
...@@ -68,10 +78,8 @@ contract LibPosition_Test is Test { ...@@ -68,10 +78,8 @@ contract LibPosition_Test is Test {
* @notice Tests that the `parent` function correctly computes the position of the parent. * @notice Tests that the `parent` function correctly computes the position of the parent.
*/ */
function testFuzz_parent_correctness(uint8 _depth, uint64 _indexAtDepth) public { function testFuzz_parent_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [1, 63]
_depth = uint8(bound(_depth, 1, MAX_DEPTH)); _depth = uint8(bound(_depth, 1, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth] _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position parent = LibPosition.parent(position); Position parent = LibPosition.parent(position);
...@@ -85,7 +93,7 @@ contract LibPosition_Test is Test { ...@@ -85,7 +93,7 @@ contract LibPosition_Test is Test {
* to a given position. * to a given position.
*/ */
function testFuzz_rightIndex_correctness( function testFuzz_rightIndex_correctness(
uint8 _maxDepth, uint64 _maxDepth,
uint8 _depth, uint8 _depth,
uint64 _indexAtDepth uint64 _indexAtDepth
) public { ) public {
...@@ -94,14 +102,13 @@ contract LibPosition_Test is Test { ...@@ -94,14 +102,13 @@ contract LibPosition_Test is Test {
_maxDepth = uint8(bound(_maxDepth, 1, MAX_DEPTH)); _maxDepth = uint8(bound(_maxDepth, 1, MAX_DEPTH));
// Depth bound: [0, _maxDepth] // Depth bound: [0, _maxDepth]
_depth = uint8(bound(_depth, 0, _maxDepth)); _depth = uint8(bound(_depth, 0, _maxDepth));
// Index at depth bound: [0, 2 ** _depth] _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
uint64 rightIndex = LibPosition.rightIndex(position, _maxDepth); uint64 rightIndex = LibPosition.rightIndex(position, _maxDepth);
// Find the deepest, rightmost index in Solidity rather than Yul // 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); position = LibPosition.right(position);
} }
uint64 _rightIndex = LibPosition.indexAtDepth(position); uint64 _rightIndex = LibPosition.indexAtDepth(position);
...@@ -117,8 +124,7 @@ contract LibPosition_Test is Test { ...@@ -117,8 +124,7 @@ contract LibPosition_Test is Test {
function testFuzz_attack_correctness(uint8 _depth, uint64 _indexAtDepth) public { function testFuzz_attack_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63] // Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH)); _depth = uint8(bound(_depth, 0, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth] _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position attack = LibPosition.attack(position); Position attack = LibPosition.attack(position);
...@@ -136,8 +142,7 @@ contract LibPosition_Test is Test { ...@@ -136,8 +142,7 @@ contract LibPosition_Test is Test {
function testFuzz_defend_correctness(uint8 _depth, uint64 _indexAtDepth) public { function testFuzz_defend_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [1, 63] // Depth bound: [1, 63]
_depth = uint8(bound(_depth, 1, MAX_DEPTH)); _depth = uint8(bound(_depth, 1, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth] _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position defend = LibPosition.defend(position); 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