diff --git a/packages/contracts-bedrock/contracts/test/LibClock.t.sol b/packages/contracts-bedrock/contracts/test/LibClock.t.sol new file mode 100644 index 0000000000000000000000000000000000000000..b17e6cb5442a9cbcba7f940edb5a22da3737d691 --- /dev/null +++ b/packages/contracts-bedrock/contracts/test/LibClock.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { Test } from "forge-std/Test.sol"; +import { LibClock } from "../dispute/lib/LibClock.sol"; +import "../libraries/DisputeTypes.sol"; + +/** + * @notice Tests for `LibClock` + */ +contract LibClock_Test is Test { + /** + * @notice Tests that the `duration` function correctly shifts out the `Duration` from a packed `Clock` type. + */ + function testFuzz_duration_correctness(uint64 _duration, uint64 _timestamp) public { + Clock clock = LibClock.wrap(Duration.wrap(_duration), Timestamp.wrap(_timestamp)); + assertEq(Duration.unwrap(LibClock.duration(clock)), _duration); + } + + /** + * @notice Tests that the `timestamp` function correctly shifts out the `Timestamp` from a packed `Clock` type. + */ + function testFuzz_timestamp_correctness(uint64 _duration, uint64 _timestamp) public { + Clock clock = LibClock.wrap(Duration.wrap(_duration), Timestamp.wrap(_timestamp)); + assertEq(Timestamp.unwrap(LibClock.timestamp(clock)), _timestamp); + } +} diff --git a/packages/contracts-bedrock/contracts/test/LibPosition.t.sol b/packages/contracts-bedrock/contracts/test/LibPosition.t.sol new file mode 100644 index 0000000000000000000000000000000000000000..14c2f58e99f650c5e795e4aab0ea0f84b139c9f7 --- /dev/null +++ b/packages/contracts-bedrock/contracts/test/LibPosition.t.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { Test } from "forge-std/Test.sol"; +import { LibPosition } from "../dispute/lib/LibPosition.sol"; +import "../libraries/DisputeTypes.sol"; + +/** + * @notice Tests for `LibPosition` + */ +contract LibPosition_Test is Test { + /** + * @dev Assumes a MAX depth of 127 for the Position type. Any greater depth can cause overflows. + * @dev At the lowest level of the tree, this allows for 2 ** 127 leaves. In reality, the max game depth + * will likely be much lower. + */ + uint8 internal constant MAX_DEPTH = 127; + + /** + * @notice Tests that the `depth` function correctly shifts out the `depth` from a packed `Position` type. + */ + function testFuzz_depth_correctness(uint128 _depth, uint128 _indexAtDepth) public { + Position position = LibPosition.wrap(_depth, _indexAtDepth); + assertEq(LibPosition.depth(position), _depth); + } + + /** + * @notice Tests that the `indexAtDepth` function correctly shifts out the `indexAtDepth` from a packed `Position` type. + */ + function testFuzz_indexAtDepth_correctness(uint128 _depth, uint128 _indexAtDepth) public { + Position position = LibPosition.wrap(_depth, _indexAtDepth); + assertEq(LibPosition.indexAtDepth(position), _indexAtDepth); + } + + /** + * @notice Tests that the `left` function correctly computes the position of the left child. + */ + function testFuzz_left_correctness(uint8 _depth, uint128 _indexAtDepth) public { + // Depth bound: [0, 127] + _depth = uint8(bound(_depth, 0, MAX_DEPTH)); + // Index at depth bound: [0, 2 ** _depth] + _indexAtDepth = uint128(bound(_indexAtDepth, 0, 2 ** _depth)); + + Position position = LibPosition.wrap(_depth, _indexAtDepth); + Position left = LibPosition.left(position); + + assertEq(LibPosition.depth(left), uint128(_depth) + 1); + assertEq(LibPosition.indexAtDepth(left), _indexAtDepth * 2); + } + + /** + * @notice Tests that the `right` function correctly computes the position of the right child. + */ + function testFuzz_right_correctness(uint8 _depth, uint128 _indexAtDepth) public { + // Depth bound: [0, 127] + _depth = uint8(bound(_depth, 0, MAX_DEPTH)); + // Index at depth bound: [0, 2 ** _depth] + _indexAtDepth = uint128(bound(_indexAtDepth, 0, 2 ** _depth)); + + Position position = LibPosition.wrap(_depth, _indexAtDepth); + Position right = LibPosition.right(position); + + assertEq(LibPosition.depth(right), _depth + 1); + assertEq(LibPosition.indexAtDepth(right), _indexAtDepth * 2 + 1); + } + + /** + * @notice Tests that the `parent` function correctly computes the position of the parent. + */ + function testFuzz_parent_correctness(uint8 _depth, uint128 _indexAtDepth) public { + // Depth bound: [1, 127] + _depth = uint8(bound(_depth, 1, MAX_DEPTH)); + // Index at depth bound: [0, 2 ** _depth] + _indexAtDepth = uint128(bound(_indexAtDepth, 0, 2 ** _depth)); + + Position position = LibPosition.wrap(_depth, _indexAtDepth); + Position parent = LibPosition.parent(position); + + assertEq(LibPosition.depth(parent), _depth - 1); + assertEq(LibPosition.indexAtDepth(parent), _indexAtDepth / 2); + } + + /** + * @notice Tests that the `rightIndex` function correctly computes the deepest, right most index relative + * to a given position. + */ + function testFuzz_rightIndex_correctness(uint8 _maxDepth, uint8 _depth, uint128 _indexAtDepth) public { + // Max depth bound: [1, 127] + // The max game depth MUST be at least 1. + _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 = uint128(bound(_indexAtDepth, 0, 2 ** _depth)); + + Position position = LibPosition.wrap(_depth, _indexAtDepth); + uint128 rightIndex = LibPosition.rightIndex(position, _maxDepth); + + // Find the deepest, rightmost index in Solidity rather than Yul + for (uint256 i = _depth; i < _maxDepth - 1; ++i) { + position = LibPosition.right(position); + } + uint128 _rightIndex = LibPosition.indexAtDepth(position); + + assertEq(rightIndex, _rightIndex); + } + + /** + * @notice Tests that the `attack` function correctly computes the position of the attack relative to + * a given position. + * @dev `attack` is an alias for `left`, but we test it separately for completeness. + */ + function testFuzz_attack_correctness(uint8 _depth, uint128 _indexAtDepth) public { + // Depth bound: [0, 127] + _depth = uint8(bound(_depth, 0, MAX_DEPTH)); + // Index at depth bound: [0, 2 ** _depth] + _indexAtDepth = uint128(bound(_indexAtDepth, 0, 2 ** _depth)); + + Position position = LibPosition.wrap(_depth, _indexAtDepth); + Position attack = LibPosition.attack(position); + + assertEq(LibPosition.depth(attack), _depth + 1); + assertEq(LibPosition.indexAtDepth(attack), _indexAtDepth * 2); + } + + /** + * @notice Tests that the `defend` function correctly computes the position of the defense relative to + * a given position. + * @dev A defense can only be given if the position does not belong to the root claim, hence the bound of [1, 127] + * on the depth. + */ + function testFuzz_defend_correctness(uint8 _depth, uint128 _indexAtDepth) public { + // Depth bound: [1, 127] + _depth = uint8(bound(_depth, 1, MAX_DEPTH)); + // Index at depth bound: [0, 2 ** _depth] + _indexAtDepth = uint128(bound(_indexAtDepth, 0, 2 ** _depth)); + + Position position = LibPosition.wrap(_depth, _indexAtDepth); + Position defend = LibPosition.defend(position); + + assertEq(LibPosition.depth(defend), _depth + 1); + assertEq(LibPosition.indexAtDepth(defend), ((_indexAtDepth / 2) * 2 + 1) * 2); + } +}