|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +pragma solidity >=0.8.13 <0.9.0; |
| 3 | + |
| 4 | +import {Test, stdError} from "forge-std/Test.sol"; |
| 5 | + |
| 6 | +import {PageIndex, PageHandle, Pages} from "../src/utils/storage/Pages.sol"; |
| 7 | + |
| 8 | +contract PagesHarness { |
| 9 | + PageHandle internal handle; |
| 10 | + mapping(uint256 => PageHandle) internal handles; |
| 11 | + |
| 12 | + function page() external view returns (PageIndex) { |
| 13 | + return handle.page(); |
| 14 | + } |
| 15 | + |
| 16 | + function page(uint256 key) external view returns (PageIndex) { |
| 17 | + return handles[key].page(); |
| 18 | + } |
| 19 | + |
| 20 | + function add(PageIndex base, uint256 pages) external pure returns (PageIndex) { |
| 21 | + return Pages.add(base, pages); |
| 22 | + } |
| 23 | + |
| 24 | + function slot(PageIndex base, uint256 offset) external pure returns (uint256) { |
| 25 | + return Pages.slot(base, offset); |
| 26 | + } |
| 27 | + |
| 28 | + function slotUnbounded(PageIndex base, uint256 index) external pure returns (uint256) { |
| 29 | + return Pages.slotUnbounded(base, index); |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +contract PagesTest is Test { |
| 34 | + PagesHarness internal harness; |
| 35 | + |
| 36 | + function setUp() public { |
| 37 | + harness = new PagesHarness(); |
| 38 | + } |
| 39 | + |
| 40 | + /// @dev Pins the layout to where a dynamic array declared in place of the handle would start its |
| 41 | + /// data, rounded down to the page. The harness declares `handle` at slot 0, so that is |
| 42 | + /// `keccak256(abi.encode(uint256(0)))` with its page offset cleared. |
| 43 | + function testPageStartsAtArrayDataLocation() public view { |
| 44 | + assertEq(harness.page().slot(0), 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e500); |
| 45 | + } |
| 46 | + |
| 47 | + /// @dev The page follows the slot of the handle wherever it is declared. The harness declares |
| 48 | + /// `handles` at slot 1, so `handles[key]` lives at `keccak256(abi.encode(key, uint256(1)))`. |
| 49 | + function testFuzzPageFollowsHandleSlot(uint256 key) public view { |
| 50 | + uint256 handleSlot = uint256(keccak256(abi.encode(key, uint256(1)))); |
| 51 | + PageIndex expected = Pages.fromSlot(uint256(keccak256(abi.encode(handleSlot)))); |
| 52 | + assertEq(PageIndex.unwrap(harness.page(key)), PageIndex.unwrap(expected)); |
| 53 | + } |
| 54 | + |
| 55 | + function testFuzzFromSlotReturnsContainingPage(uint256 storageSlot) public pure { |
| 56 | + assertEq(Pages.fromSlot(storageSlot).slot(storageSlot % Pages.SLOTS_PER_PAGE), storageSlot); |
| 57 | + } |
| 58 | + |
| 59 | + /// @dev Offsets outside a page. The table test below runs once per entry. |
| 60 | + uint256[] public fixtureOffset = [Pages.SLOTS_PER_PAGE, type(uint256).max]; |
| 61 | + |
| 62 | + function tableSlotRejectsOffsetOutsidePage(uint256 offset) public { |
| 63 | + vm.expectRevert(abi.encodeWithSelector(Pages.OffsetOutOfPage.selector, offset)); |
| 64 | + // forge-lint: disable-next-line(unused-return) |
| 65 | + harness.slot(PageIndex.wrap(0), offset); |
| 66 | + } |
| 67 | + |
| 68 | + /// @dev Counting `index` slots from the start of a page ends `index / 128` pages later, at |
| 69 | + /// offset `index % 128`. `lastBase` is the last page the index fits in, so counting from the |
| 70 | + /// page after it must revert. |
| 71 | + function testFuzzSlotUnboundedCrossesPages(uint256 rawBase, uint256 index) public { |
| 72 | + uint256 lastBase = PageIndex.unwrap(Pages.fromSlot(type(uint256).max - index)); |
| 73 | + PageIndex base = PageIndex.wrap(bound(rawBase, 0, lastBase)); |
| 74 | + |
| 75 | + assertEq(base.slotUnbounded(index), base.add(index / Pages.SLOTS_PER_PAGE).slot(index % Pages.SLOTS_PER_PAGE)); |
| 76 | + vm.expectRevert(stdError.arithmeticError); |
| 77 | + // forge-lint: disable-next-line(unused-return) |
| 78 | + harness.slotUnbounded(PageIndex.wrap(lastBase + 1), index); |
| 79 | + } |
| 80 | + |
| 81 | + function testAddRejectsOverflow() public { |
| 82 | + vm.expectRevert(stdError.arithmeticError); |
| 83 | + // forge-lint: disable-next-line(unused-return) |
| 84 | + harness.add(PageIndex.wrap(type(uint256).max), 1); |
| 85 | + } |
| 86 | +} |
0 commit comments