github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/dispute/lib/LibPosition.t.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity ^0.8.15; 3 4 import { Test } from "forge-std/Test.sol"; 5 import { LibPosition } from "src/dispute/lib/LibPosition.sol"; 6 import "src/libraries/DisputeTypes.sol"; 7 8 /// @notice Tests for `LibPosition` 9 contract LibPosition_Test is Test { 10 /// @dev Assumes a MAX depth of 63 for the Position type. Any greater depth can cause overflows. 11 /// @dev At the lowest level of the tree, this allows for 2 ** 63 leaves. In reality, the max game depth 12 /// will likely be much lower. 13 uint8 internal constant MAX_DEPTH = 63; 14 /// @dev Arbitrary split depth around half way down the tree. 15 uint8 internal constant SPLIT_DEPTH = 30; 16 17 function boundIndexAtDepth(uint8 _depth, uint64 _indexAtDepth) internal pure returns (uint64) { 18 // Index at depth bound: [0, 2 ** _depth-1] 19 if (_depth > 0) { 20 return uint64(bound(_indexAtDepth, 0, 2 ** (_depth - 1))); 21 } else { 22 return 0; 23 } 24 } 25 26 /// @notice Tests that the `depth` function correctly shifts out the `depth` from a packed `Position` type. 27 function testFuzz_depth_correctness_succeeds(uint8 _depth, uint64 _indexAtDepth) public { 28 _depth = uint8(bound(_depth, 0, MAX_DEPTH)); 29 _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); 30 Position position = LibPosition.wrap(_depth, _indexAtDepth); 31 assertEq(position.depth(), _depth); 32 } 33 34 /// @notice Tests that the `indexAtDepth` function correctly shifts out the `indexAtDepth` from a packed `Position` 35 /// type. 36 function testFuzz_indexAtDepth_correctness_succeeds(uint8 _depth, uint64 _indexAtDepth) public { 37 _depth = uint8(bound(_depth, 0, MAX_DEPTH)); 38 _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); 39 Position position = LibPosition.wrap(_depth, _indexAtDepth); 40 assertEq(position.indexAtDepth(), _indexAtDepth); 41 } 42 43 /// @notice Tests that the `left` function correctly computes the position of the left child. 44 function testFuzz_left_correctness_succeeds(uint8 _depth, uint64 _indexAtDepth) public { 45 _depth = uint8(bound(_depth, 0, MAX_DEPTH)); 46 _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); 47 48 Position position = LibPosition.wrap(_depth, _indexAtDepth); 49 Position left = position.left(); 50 51 assertEq(left.depth(), uint64(_depth) + 1); 52 assertEq(left.indexAtDepth(), _indexAtDepth * 2); 53 } 54 55 /// @notice Tests that the `right` function correctly computes the position of the right child. 56 function testFuzz_right_correctness_succeeds(uint8 _depth, uint64 _indexAtDepth) public { 57 // Depth bound: [0, 63] 58 _depth = uint8(bound(_depth, 0, MAX_DEPTH)); 59 _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); 60 61 Position position = LibPosition.wrap(_depth, _indexAtDepth); 62 Position right = position.right(); 63 64 assertEq(right.depth(), _depth + 1); 65 assertEq(right.indexAtDepth(), _indexAtDepth * 2 + 1); 66 } 67 68 /// @notice Tests that the `parent` function correctly computes the position of the parent. 69 function testFuzz_parent_correctness_succeeds(uint8 _depth, uint64 _indexAtDepth) public { 70 _depth = uint8(bound(_depth, 1, MAX_DEPTH)); 71 _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); 72 73 Position position = LibPosition.wrap(_depth, _indexAtDepth); 74 Position parent = position.parent(); 75 76 assertEq(parent.depth(), _depth - 1); 77 assertEq(parent.indexAtDepth(), _indexAtDepth / 2); 78 } 79 80 /// @notice Tests that the `traceAncestor` function correctly computes the position of the 81 /// highest ancestor that commits to the same trace index. 82 function testFuzz_traceAncestor_correctness_succeeds(uint8 _depth, uint64 _indexAtDepth) public { 83 _depth = uint8(bound(_depth, 1, MAX_DEPTH)); 84 _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); 85 86 Position position = LibPosition.wrap(_depth, _indexAtDepth); 87 Position ancestor = position.traceAncestor(); 88 Position loopAncestor = position; 89 while (loopAncestor.parent().traceIndex(MAX_DEPTH) == position.traceIndex(MAX_DEPTH)) { 90 loopAncestor = loopAncestor.parent(); 91 } 92 93 assertEq(Position.unwrap(ancestor), Position.unwrap(loopAncestor)); 94 } 95 96 /// @notice Tests that the `traceAncestorBounded` function correctly computes the position of the 97 /// highest ancestor (below `SPLIT_DEPTH`) that commits to the same trace index. 98 function testFuzz_traceAncestorBounded_correctness_succeeds(uint8 _depth, uint64 _indexAtDepth) public { 99 _depth = uint8(bound(_depth, SPLIT_DEPTH + 1, MAX_DEPTH)); 100 _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); 101 102 Position position = LibPosition.wrap(_depth, _indexAtDepth); 103 Position ancestor = position.traceAncestorBounded(SPLIT_DEPTH); 104 Position loopAncestor = position; 105 106 // Stop at 1 below the split depth. 107 while ( 108 loopAncestor.parent().traceIndex(MAX_DEPTH) == position.traceIndex(MAX_DEPTH) 109 && loopAncestor.depth() != SPLIT_DEPTH + 1 110 ) { 111 loopAncestor = loopAncestor.parent(); 112 } 113 114 assertEq(Position.unwrap(ancestor), Position.unwrap(loopAncestor)); 115 } 116 117 /// @notice Tests that the `rightIndex` function correctly computes the deepest, right most index relative 118 /// to a given position. 119 function testFuzz_rightIndex_correctness_succeeds(uint64 _maxDepth, uint8 _depth, uint64 _indexAtDepth) public { 120 // Max depth bound: [1, 63] 121 // The max game depth MUST be at least 1. 122 _maxDepth = uint8(bound(_maxDepth, 1, MAX_DEPTH)); 123 // Depth bound: [0, _maxDepth] 124 _depth = uint8(bound(_depth, 0, _maxDepth)); 125 _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); 126 127 Position position = LibPosition.wrap(_depth, _indexAtDepth); 128 Position rightIndex = position.rightIndex(_maxDepth); 129 130 // Find the deepest, rightmost index in Solidity rather than Yul 131 for (uint256 i = _depth; i < _maxDepth; ++i) { 132 position = position.right(); 133 } 134 135 assertEq(Position.unwrap(rightIndex), Position.unwrap(position)); 136 } 137 138 /// @notice Tests that the `attack` function correctly computes the position of the attack relative to 139 /// a given position. 140 /// @dev `attack` is an alias for `left`, but we test it separately for completeness. 141 function testFuzz_attack_correctness_succeeds(uint8 _depth, uint64 _indexAtDepth) public { 142 // Depth bound: [0, 63] 143 _depth = uint8(bound(_depth, 0, MAX_DEPTH)); 144 _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); 145 146 Position position = LibPosition.wrap(_depth, _indexAtDepth); 147 Position attack = position.move(true); 148 149 assertEq(attack.depth(), _depth + 1); 150 assertEq(attack.indexAtDepth(), _indexAtDepth * 2); 151 } 152 153 /// @notice Tests that the `defend` function correctly computes the position of the defense relative to 154 /// a given position. 155 /// @dev A defense can only be given if the position does not belong to the root claim, hence the bound of [1, 127] 156 /// on the depth. 157 function testFuzz_defend_correctness_succeeds(uint8 _depth, uint64 _indexAtDepth) public { 158 // Depth bound: [1, 63] 159 _depth = uint8(bound(_depth, 1, MAX_DEPTH)); 160 _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); 161 162 Position position = LibPosition.wrap(_depth, _indexAtDepth); 163 Position defend = position.move(false); 164 165 assertEq(defend.depth(), _depth + 1); 166 assertEq(defend.indexAtDepth(), ((_indexAtDepth / 2) * 2 + 1) * 2); 167 } 168 169 /// @notice A static unit test for the correctness of all gindicies, (depth, index) combos, 170 /// and the trace index in a tree of max depth = 4. 171 function test_pos_correctness_succeeds() public { 172 uint256 maxDepth = 4; 173 174 Position p = LibPosition.wrap(0, 0); 175 assertEq(Position.unwrap(p), 1); // gindex = 1 176 assertEq(p.depth(), 0); // depth = 0 177 assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0 178 Position r = p.rightIndex(maxDepth); 179 assertEq(Position.unwrap(r), 31); // right gindex = 31 180 assertEq(r.indexAtDepth(), 15); // trace index = 15 181 182 p = LibPosition.wrap(1, 0); 183 assertEq(Position.unwrap(p), 2); // gindex = 2 184 assertEq(p.depth(), 1); // depth = 1 185 assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0 186 r = p.rightIndex(maxDepth); 187 assertEq(Position.unwrap(r), 23); // right gindex = 23 188 assertEq(r.indexAtDepth(), 7); // trace index = 7 189 190 p = LibPosition.wrap(1, 1); 191 assertEq(Position.unwrap(p), 3); // gindex = 3 192 assertEq(p.depth(), 1); // depth = 1 193 assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1 194 r = p.rightIndex(maxDepth); 195 assertEq(Position.unwrap(r), 31); // right gindex = 31 196 assertEq(r.indexAtDepth(), 15); // trace index = 15 197 198 p = LibPosition.wrap(2, 0); 199 assertEq(Position.unwrap(p), 4); // gindex = 4 200 assertEq(p.depth(), 2); // depth = 2 201 assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0 202 r = p.rightIndex(maxDepth); 203 assertEq(Position.unwrap(r), 19); // right gindex = 19 204 assertEq(r.indexAtDepth(), 3); // trace index = 3 205 206 p = LibPosition.wrap(2, 1); 207 assertEq(Position.unwrap(p), 5); // gindex = 5 208 assertEq(p.depth(), 2); // depth = 2 209 assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1 210 r = p.rightIndex(maxDepth); 211 assertEq(Position.unwrap(r), 23); // right gindex = 23 212 assertEq(r.indexAtDepth(), 7); // trace index = 7 213 214 p = LibPosition.wrap(2, 2); 215 assertEq(Position.unwrap(p), 6); // gindex = 6 216 assertEq(p.depth(), 2); // depth = 2 217 assertEq(p.indexAtDepth(), 2); // indexAtDepth = 2 218 r = p.rightIndex(maxDepth); 219 assertEq(Position.unwrap(r), 27); // right gindex = 27 220 assertEq(r.indexAtDepth(), 11); // trace index = 11 221 222 p = LibPosition.wrap(2, 3); 223 assertEq(Position.unwrap(p), 7); // gindex = 7 224 assertEq(p.depth(), 2); // depth = 2 225 assertEq(p.indexAtDepth(), 3); // indexAtDepth = 3 226 r = p.rightIndex(maxDepth); 227 assertEq(Position.unwrap(r), 31); // right gindex = 31 228 assertEq(r.indexAtDepth(), 15); // trace index = 15 229 230 p = LibPosition.wrap(3, 0); 231 assertEq(Position.unwrap(p), 8); // gindex = 8 232 assertEq(p.depth(), 3); // depth = 3 233 assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0 234 r = p.rightIndex(maxDepth); 235 assertEq(Position.unwrap(r), 17); // right gindex = 17 236 assertEq(r.indexAtDepth(), 1); // trace index = 1 237 238 p = LibPosition.wrap(3, 1); 239 assertEq(Position.unwrap(p), 9); // gindex = 9 240 assertEq(p.depth(), 3); // depth = 3 241 assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1 242 r = p.rightIndex(maxDepth); 243 assertEq(Position.unwrap(r), 19); // right gindex = 19 244 assertEq(r.indexAtDepth(), 3); // trace index = 3 245 246 p = LibPosition.wrap(3, 2); 247 assertEq(Position.unwrap(p), 10); // gindex = 10 248 assertEq(p.depth(), 3); // depth = 3 249 assertEq(p.indexAtDepth(), 2); // indexAtDepth = 2 250 r = p.rightIndex(maxDepth); 251 assertEq(Position.unwrap(r), 21); // right gindex = 21 252 assertEq(r.indexAtDepth(), 5); // trace index = 5 253 254 p = LibPosition.wrap(3, 3); 255 assertEq(Position.unwrap(p), 11); // gindex = 11 256 assertEq(p.depth(), 3); // depth = 3 257 assertEq(p.indexAtDepth(), 3); // indexAtDepth = 3 258 r = p.rightIndex(maxDepth); 259 assertEq(Position.unwrap(r), 23); // right gindex = 23 260 assertEq(r.indexAtDepth(), 7); // trace index = 7 261 262 p = LibPosition.wrap(3, 4); 263 assertEq(Position.unwrap(p), 12); // gindex = 12 264 assertEq(p.depth(), 3); // depth = 3 265 assertEq(p.indexAtDepth(), 4); // indexAtDepth = 4 266 r = p.rightIndex(maxDepth); 267 assertEq(Position.unwrap(r), 25); // right gindex = 25 268 assertEq(r.indexAtDepth(), 9); // trace index = 9 269 270 p = LibPosition.wrap(3, 5); 271 assertEq(Position.unwrap(p), 13); // gindex = 13 272 assertEq(p.depth(), 3); // depth = 3 273 assertEq(p.indexAtDepth(), 5); // indexAtDepth = 5 274 r = p.rightIndex(maxDepth); 275 assertEq(Position.unwrap(r), 27); // right gindex = 27 276 assertEq(r.indexAtDepth(), 11); // trace index = 11 277 278 p = LibPosition.wrap(3, 6); 279 assertEq(Position.unwrap(p), 14); // gindex = 14 280 assertEq(p.depth(), 3); // depth = 3 281 assertEq(p.indexAtDepth(), 6); // indexAtDepth = 6 282 r = p.rightIndex(maxDepth); 283 assertEq(Position.unwrap(r), 29); // right gindex = 29 284 assertEq(r.indexAtDepth(), 13); // trace index = 13 285 286 p = LibPosition.wrap(3, 7); 287 assertEq(Position.unwrap(p), 15); // gindex = 15 288 assertEq(p.depth(), 3); // depth = 3 289 assertEq(p.indexAtDepth(), 7); // indexAtDepth = 7 290 r = p.rightIndex(maxDepth); 291 assertEq(Position.unwrap(r), 31); // right gindex = 31 292 assertEq(r.indexAtDepth(), 15); // trace index = 15 293 294 p = LibPosition.wrap(4, 0); 295 assertEq(Position.unwrap(p), 16); // gindex = 16 296 assertEq(p.depth(), 4); // depth = 4 297 assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0 298 r = p.rightIndex(maxDepth); 299 assertEq(Position.unwrap(r), 16); // right gindex = 16 300 assertEq(r.indexAtDepth(), 0); // trace index = 0 301 302 p = LibPosition.wrap(4, 1); 303 assertEq(Position.unwrap(p), 17); // gindex = 17 304 assertEq(p.depth(), 4); // depth = 4 305 assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1 306 r = p.rightIndex(maxDepth); 307 assertEq(Position.unwrap(r), 17); // right gindex = 17 308 assertEq(r.indexAtDepth(), 1); // trace index = 1 309 310 p = LibPosition.wrap(4, 2); 311 assertEq(Position.unwrap(p), 18); // gindex = 18 312 assertEq(p.depth(), 4); // depth = 4 313 assertEq(p.indexAtDepth(), 2); // indexAtDepth = 2 314 r = p.rightIndex(maxDepth); 315 assertEq(Position.unwrap(r), 18); // right gindex = 18 316 assertEq(r.indexAtDepth(), 2); // trace index = 2 317 318 p = LibPosition.wrap(4, 3); 319 assertEq(Position.unwrap(p), 19); // gindex = 19 320 assertEq(p.depth(), 4); // depth = 4 321 assertEq(p.indexAtDepth(), 3); // indexAtDepth = 3 322 r = p.rightIndex(maxDepth); 323 assertEq(Position.unwrap(r), 19); // right gindex = 19 324 assertEq(r.indexAtDepth(), 3); // trace index = 3 325 326 p = LibPosition.wrap(4, 4); 327 assertEq(Position.unwrap(p), 20); // gindex = 20 328 assertEq(p.depth(), 4); // depth = 4 329 assertEq(p.indexAtDepth(), 4); // indexAtDepth = 4 330 r = p.rightIndex(maxDepth); 331 assertEq(Position.unwrap(r), 20); // right gindex = 20 332 assertEq(r.indexAtDepth(), 4); // trace index = 4 333 334 p = LibPosition.wrap(4, 5); 335 assertEq(Position.unwrap(p), 21); // gindex = 21 336 assertEq(p.depth(), 4); // depth = 4 337 assertEq(p.indexAtDepth(), 5); // indexAtDepth = 5 338 r = p.rightIndex(maxDepth); 339 assertEq(Position.unwrap(r), 21); // right gindex = 21 340 assertEq(r.indexAtDepth(), 5); // trace index = 5 341 342 p = LibPosition.wrap(4, 6); 343 assertEq(Position.unwrap(p), 22); // gindex = 22 344 assertEq(p.depth(), 4); // depth = 4 345 assertEq(p.indexAtDepth(), 6); // indexAtDepth = 6 346 r = p.rightIndex(maxDepth); 347 assertEq(Position.unwrap(r), 22); // right gindex = 22 348 assertEq(r.indexAtDepth(), 6); // trace index = 6 349 350 p = LibPosition.wrap(4, 7); 351 assertEq(Position.unwrap(p), 23); // gindex = 23 352 assertEq(p.depth(), 4); // depth = 4 353 assertEq(p.indexAtDepth(), 7); // indexAtDepth = 7 354 r = p.rightIndex(maxDepth); 355 assertEq(Position.unwrap(r), 23); // right gindex = 23 356 assertEq(r.indexAtDepth(), 7); // trace index = 7 357 358 p = LibPosition.wrap(4, 8); 359 assertEq(Position.unwrap(p), 24); // gindex = 24 360 assertEq(p.depth(), 4); // depth = 4 361 assertEq(p.indexAtDepth(), 8); // indexAtDepth = 8 362 r = p.rightIndex(maxDepth); 363 assertEq(Position.unwrap(r), 24); // right gindex = 24 364 assertEq(r.indexAtDepth(), 8); // trace index = 8 365 366 p = LibPosition.wrap(4, 9); 367 assertEq(Position.unwrap(p), 25); // gindex = 25 368 assertEq(p.depth(), 4); // depth = 4 369 assertEq(p.indexAtDepth(), 9); // indexAtDepth = 9 370 r = p.rightIndex(maxDepth); 371 assertEq(Position.unwrap(r), 25); // right gindex = 25 372 assertEq(r.indexAtDepth(), 9); // trace index = 9 373 374 p = LibPosition.wrap(4, 10); 375 assertEq(Position.unwrap(p), 26); // gindex = 26 376 assertEq(p.depth(), 4); // depth = 4 377 assertEq(p.indexAtDepth(), 10); // indexAtDepth = 10 378 r = p.rightIndex(maxDepth); 379 assertEq(Position.unwrap(r), 26); // right gindex = 26 380 assertEq(r.indexAtDepth(), 10); // trace index = 10 381 382 p = LibPosition.wrap(4, 11); 383 assertEq(Position.unwrap(p), 27); // gindex = 27 384 assertEq(p.depth(), 4); // depth = 4 385 assertEq(p.indexAtDepth(), 11); // indexAtDepth = 11 386 r = p.rightIndex(maxDepth); 387 assertEq(Position.unwrap(r), 27); // right gindex = 27 388 assertEq(r.indexAtDepth(), 11); // trace index = 11 389 390 p = LibPosition.wrap(4, 12); 391 assertEq(Position.unwrap(p), 28); // gindex = 28 392 assertEq(p.depth(), 4); // depth = 4 393 assertEq(p.indexAtDepth(), 12); // indexAtDepth = 12 394 r = p.rightIndex(maxDepth); 395 assertEq(Position.unwrap(r), 28); // right gindex = 28 396 assertEq(r.indexAtDepth(), 12); // trace index = 12 397 398 p = LibPosition.wrap(4, 13); 399 assertEq(Position.unwrap(p), 29); // gindex = 29 400 assertEq(p.depth(), 4); // depth = 4 401 assertEq(p.indexAtDepth(), 13); // indexAtDepth = 13 402 r = p.rightIndex(maxDepth); 403 assertEq(Position.unwrap(r), 29); // right gindex = 29 404 assertEq(r.indexAtDepth(), 13); // trace index = 13 405 406 p = LibPosition.wrap(4, 14); 407 assertEq(Position.unwrap(p), 30); // gindex = 30 408 assertEq(p.depth(), 4); // depth = 4 409 assertEq(p.indexAtDepth(), 14); // indexAtDepth = 14 410 r = p.rightIndex(maxDepth); 411 assertEq(Position.unwrap(r), 30); // right gindex = 30 412 assertEq(r.indexAtDepth(), 14); // trace index = 14 413 414 p = LibPosition.wrap(4, 15); 415 assertEq(Position.unwrap(p), 31); // gindex = 31 416 assertEq(p.depth(), 4); // depth = 4 417 assertEq(p.indexAtDepth(), 15); // indexAtDepth = 15 418 r = p.rightIndex(maxDepth); 419 assertEq(Position.unwrap(r), 31); // right gindex = 31 420 assertEq(r.indexAtDepth(), 15); // trace index = 15 421 } 422 }