From 9957620e57874ac251dbff9fddcd859e11ddfc08 Mon Sep 17 00:00:00 2001 From: Francis Li Date: Thu, 8 Oct 2026 15:09:43 -0700 Subject: [PATCH 1/3] refactor(L2): drop named return from BaseTime.timestampMs Amp-Thread-ID: https://ampcode.com/threads/T-01a11ca1-0972-76cc-9a30-05db0799da5a Co-authored-by: Amp --- interfaces/L2/IBaseTime.sol | 2 +- snapshots/abi/BaseTime.json | 2 +- snapshots/semver-lock.json | 2 +- src/L2/BaseTime.sol | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interfaces/L2/IBaseTime.sol b/interfaces/L2/IBaseTime.sol index 6a8d18a74..b3ddbf228 100644 --- a/interfaces/L2/IBaseTime.sol +++ b/interfaces/L2/IBaseTime.sol @@ -17,7 +17,7 @@ interface IBaseTime is ISemver { function timestampMillisPart() external view returns (uint16); /// @notice Returns the current L2 block timestamp in milliseconds. - function timestampMs() external view returns (uint64 timestampMs_); + function timestampMs() external view returns (uint64); /// @notice Updates the millisecond component of the current L2 block timestamp. function setTimestampMillisPart(uint16 _timestampMillisPart) external; diff --git a/snapshots/abi/BaseTime.json b/snapshots/abi/BaseTime.json index e37d75c6f..cc8925ca8 100644 --- a/snapshots/abi/BaseTime.json +++ b/snapshots/abi/BaseTime.json @@ -31,7 +31,7 @@ "outputs": [ { "internalType": "uint64", - "name": "timestampMs_", + "name": "", "type": "uint64" } ], diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index b61d51374..76fc7ef0f 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -57,7 +57,7 @@ }, "src/L2/BaseTime.sol:BaseTime": { "initCodeHash": "0xfcd48af0cfd907d0ce91f61ea437c81c9a98269ca2071d7d27291f20c483ccc0", - "sourceCodeHash": "0x98e246b6ff058ecb8aaeacaa33ce70674f4d3d4a8dcabe91df6f772c6d237667" + "sourceCodeHash": "0xd7dd36a7f1ffcb3ae573a943f21b217093b2698170783c9c49178f552516ff5c" }, "src/L2/FeeDisburser.sol:FeeDisburser": { "initCodeHash": "0x1278027e3756e2989e80c0a7b513e221a5fe0d3dbd9ded108375a29b2c1f3d57", diff --git a/src/L2/BaseTime.sol b/src/L2/BaseTime.sol index 74f2eefa3..18195bc63 100644 --- a/src/L2/BaseTime.sol +++ b/src/L2/BaseTime.sol @@ -20,8 +20,8 @@ contract BaseTime is IBaseTime { uint16 public timestampMillisPart; /// @inheritdoc IBaseTime - function timestampMs() external view returns (uint64 timestampMs_) { - timestampMs_ = uint64(block.timestamp * 1000 + timestampMillisPart); + function timestampMs() external view returns (uint64) { + return uint64(block.timestamp * 1000 + timestampMillisPart); } /// @inheritdoc IBaseTime From 1d2cec5afd15127a7b672ad65ee095bf1d529ad8 Mon Sep 17 00:00:00 2001 From: Francis Li Date: Thu, 8 Oct 2026 15:09:44 -0700 Subject: [PATCH 2/3] docs(L2): complete IBaseTime NatSpec Amp-Thread-ID: https://ampcode.com/threads/T-01a11ca1-0972-76cc-9a30-05db0799da5a Co-authored-by: Amp --- interfaces/L2/IBaseTime.sol | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/interfaces/L2/IBaseTime.sol b/interfaces/L2/IBaseTime.sol index b3ddbf228..270282030 100644 --- a/interfaces/L2/IBaseTime.sol +++ b/interfaces/L2/IBaseTime.sol @@ -10,15 +10,28 @@ interface IBaseTime is ISemver { /// @notice Thrown when a caller other than the protocol depositor attempts to update BaseTime. error BaseTime_NotDepositor(); - /// @notice Thrown when the millisecond component is not aligned to a 200 millisecond interval. + /// @notice Thrown when the millisecond component is above 800 or not a multiple of 200. error BaseTime_InvalidTimestampMillisPart(); /// @notice Returns the millisecond component of the current L2 block timestamp. + /// + /// @return The millisecond component, one of 0, 200, 400, 600, or 800. function timestampMillisPart() external view returns (uint16); /// @notice Returns the current L2 block timestamp in milliseconds. + /// + /// @dev Until the block's `tx[1]` BaseTime update deposit executes (e.g. during the L1-info deposit at `tx[0]`), + /// combines the current block's seconds with the previous block's millisecond component. + /// + /// @return The current L2 block timestamp in milliseconds. function timestampMs() external view returns (uint64); /// @notice Updates the millisecond component of the current L2 block timestamp. + /// + /// @dev Reverts with `BaseTime_NotDepositor` when the caller is not the depositor account. + /// @dev Reverts with `BaseTime_InvalidTimestampMillisPart` when `_timestampMillisPart` is above 800 or not a + /// multiple of 200. + /// + /// @param _timestampMillisPart The millisecond component of the current L2 block timestamp. function setTimestampMillisPart(uint16 _timestampMillisPart) external; } From ef29c4af70daba6ba567809287ff9e71054f1814 Mon Sep 17 00:00:00 2001 From: Francis Li Date: Thu, 8 Oct 2026 15:09:44 -0700 Subject: [PATCH 3/3] test(L2): run BaseTime tests against the genesis predeploy and fuzz by default Amp-Thread-ID: https://ampcode.com/threads/T-01a11ca1-0972-76cc-9a30-05db0799da5a Co-authored-by: Amp --- test/L2/BaseTime.t.sol | 98 ++++++++++++++++++++++++++---------------- test/setup/Setup.sol | 2 + 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/test/L2/BaseTime.t.sol b/test/L2/BaseTime.t.sol index b5106232b..627bbc3af 100644 --- a/test/L2/BaseTime.t.sol +++ b/test/L2/BaseTime.t.sol @@ -2,75 +2,97 @@ pragma solidity 0.8.15; // Testing -import { Test } from "forge-std/Test.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; -// Target contract -import { BaseTime } from "src/L2/BaseTime.sol"; +// Interfaces import { IBaseTime } from "interfaces/L2/IBaseTime.sol"; /// @title BaseTime_TestInit /// @notice Reusable test initialization for BaseTime tests. -abstract contract BaseTime_TestInit is Test { - BaseTime internal baseTime; +abstract contract BaseTime_TestInit is CommonTest { + /// @notice Largest valid millisecond component. + uint16 internal constant MAX_TIMESTAMP_MILLIS_PART = 800; - /// @notice Sets up the test suite. - function setUp() public { - baseTime = new BaseTime(); - } + /// @notice Interval the millisecond component must be aligned to. + uint16 internal constant TIMESTAMP_MILLIS_PART_INTERVAL = 200; /// @notice Sets the millisecond component as the protocol depositor. function setTimestampMillisPart(uint16 _timestampMillisPart) internal { vm.prank(Constants.DEPOSITOR_ACCOUNT); baseTime.setTimestampMillisPart(_timestampMillisPart); } -} -/// @title BaseTime_TimestampMillisPart_Test -/// @notice Tests BaseTime's initial value. -contract BaseTime_TimestampMillisPart_Test is BaseTime_TestInit { - /// @notice Tests that the millisecond component initially equals zero. - function test_initialValue_succeeds() external view { - assertEq(baseTime.timestampMillisPart(), 0); + /// @notice Maps a fuzzed slot to a valid millisecond component. + function validTimestampMillisPart(uint256 _slot) internal pure returns (uint16) { + return uint16( + bound(_slot, 0, MAX_TIMESTAMP_MILLIS_PART / TIMESTAMP_MILLIS_PART_INTERVAL) * TIMESTAMP_MILLIS_PART_INTERVAL + ); + } + + /// @notice Returns whether a millisecond component is invalid. + function isInvalidTimestampMillisPart(uint16 _timestampMillisPart) internal pure returns (bool) { + return + _timestampMillisPart > MAX_TIMESTAMP_MILLIS_PART + || _timestampMillisPart % TIMESTAMP_MILLIS_PART_INTERVAL != 0; } } /// @title BaseTime_SetTimestampMillisPart_Test /// @notice Tests updating BaseTime's millisecond component. contract BaseTime_SetTimestampMillisPart_Test is BaseTime_TestInit { - /// @notice Tests every valid millisecond component. - function test_setTimestampMillisPart_succeeds() external { - uint16[5] memory validValues = [uint16(0), 200, 400, 600, 800]; - - for (uint256 i; i < validValues.length; i++) { - setTimestampMillisPart(validValues[i]); - assertEq(baseTime.timestampMillisPart(), validValues[i]); - } + /// @notice Tests that callers other than the protocol depositor are rejected. + function testFuzz_setTimestampMillisPart_notDepositor_reverts(address _caller, uint256 _slot) external { + vm.assume(_caller != Constants.DEPOSITOR_ACCOUNT); + uint16 part = validTimestampMillisPart(_slot); + + vm.expectRevert(IBaseTime.BaseTime_NotDepositor.selector); + vm.prank(_caller); + baseTime.setTimestampMillisPart(part); + } + + /// @notice Tests that the depositor check runs before the value check. + function testFuzz_setTimestampMillisPart_notDepositorInvalidValue_reverts( + address _caller, + uint16 _timestampMillisPart + ) + external + { + vm.assume(_caller != Constants.DEPOSITOR_ACCOUNT); + vm.assume(isInvalidTimestampMillisPart(_timestampMillisPart)); + + vm.expectRevert(IBaseTime.BaseTime_NotDepositor.selector); + vm.prank(_caller); + baseTime.setTimestampMillisPart(_timestampMillisPart); } /// @notice Tests that an invalid millisecond component is rejected. function testFuzz_setTimestampMillisPart_invalidValue_reverts(uint16 _timestampMillisPart) external { - vm.assume(_timestampMillisPart > 800 || _timestampMillisPart % 200 != 0); + vm.assume(isInvalidTimestampMillisPart(_timestampMillisPart)); + vm.expectRevert(IBaseTime.BaseTime_InvalidTimestampMillisPart.selector); vm.prank(Constants.DEPOSITOR_ACCOUNT); baseTime.setTimestampMillisPart(_timestampMillisPart); } - /// @notice Tests that callers other than the protocol depositor are rejected. - function testFuzz_setTimestampMillisPart_notDepositor_reverts(address _caller) external { - vm.assume(_caller != Constants.DEPOSITOR_ACCOUNT); - vm.expectRevert(IBaseTime.BaseTime_NotDepositor.selector); - vm.prank(_caller); - baseTime.setTimestampMillisPart(200); + /// @notice Tests that the depositor can set any valid millisecond component. + function testFuzz_setTimestampMillisPart_validValue_succeeds(uint256 _slot) external { + uint16 part = validTimestampMillisPart(_slot); + + setTimestampMillisPart(part); + + assertEq(baseTime.timestampMillisPart(), part); } /// @notice Tests that the millisecond component occupies slot zero as a uint16. function test_setTimestampMillisPart_usesSlotZero_succeeds() external { - setTimestampMillisPart(600); + uint16 part = 600; + + setTimestampMillisPart(part); - assertEq(vm.load(address(baseTime), bytes32(0)), bytes32(uint256(600))); + assertEq(vm.load(address(baseTime), bytes32(0)), bytes32(uint256(part))); } } @@ -78,10 +100,12 @@ contract BaseTime_SetTimestampMillisPart_Test is BaseTime_TestInit { /// @notice Tests BaseTime's full millisecond timestamp getter. contract BaseTime_TimestampMs_Test is BaseTime_TestInit { /// @notice Tests that timestampMs combines block.timestamp with the millisecond component. - function test_timestampMs_succeeds() external { - vm.warp(1725); - setTimestampMillisPart(600); + function testFuzz_timestampMs_succeeds(uint256 _timestamp, uint256 _slot) external { + uint256 timestamp = bound(_timestamp, 0, type(uint64).max / 1000 - 1); + uint16 part = validTimestampMillisPart(_slot); + vm.warp(timestamp); + setTimestampMillisPart(part); - assertEq(baseTime.timestampMs(), 1_725_600); + assertEq(baseTime.timestampMs(), timestamp * 1000 + part); } } diff --git a/test/setup/Setup.sol b/test/setup/Setup.sol index 38270530c..cef498fa5 100644 --- a/test/setup/Setup.sol +++ b/test/setup/Setup.sol @@ -46,6 +46,7 @@ import { IL1FeeVault } from "interfaces/L2/IL1FeeVault.sol"; import { IOperatorFeeVault } from "interfaces/L2/IOperatorFeeVault.sol"; import { IGasPriceOracle } from "interfaces/L2/IGasPriceOracle.sol"; import { IL1Block } from "interfaces/L2/IL1Block.sol"; +import { IBaseTime } from "interfaces/L2/IBaseTime.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IWETH98 } from "interfaces/universal/IWETH98.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; @@ -119,6 +120,7 @@ abstract contract Setup is FeatureFlags { IGasPriceOracle gasPriceOracle = IGasPriceOracle(Predeploys.GAS_PRICE_ORACLE); IL1Block l1Block = IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES); IWETH98 weth = IWETH98(payable(Predeploys.WETH)); + IBaseTime baseTime = IBaseTime(Predeploys.BASE_TIME); INitroValidator nitroValidator; TEEProverRegistry teeProverRegistry;