From 53e93d1bcc83854b9e97161b1d8c4247780a08e8 Mon Sep 17 00:00:00 2001 From: TucksonDev Date: Wed, 22 Apr 2026 16:16:10 +0100 Subject: [PATCH 1/3] [ResourceConstraintManager] Set expiryTimestamp on first use --- src/chain/ResourceConstraintManager.sol | 21 ++++- test/foundry/ResourceConstraintManager.t.sol | 94 ++++++++++++++++++-- 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/chain/ResourceConstraintManager.sol b/src/chain/ResourceConstraintManager.sol index 2913e139..9f2a69f1 100644 --- a/src/chain/ResourceConstraintManager.sol +++ b/src/chain/ResourceConstraintManager.sol @@ -23,6 +23,7 @@ contract ResourceConstraintManager is AccessControlEnumerable { uint64 public constant MAX_PRICING_EXPONENT = 8000; // scaled by 1000 to allow for fractional exponents bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); + uint256 public constant TWO_YEARS_IN_SECONDS = 365 days * 2; uint256 public expiryTimestamp; error TooManyConstraints(); @@ -36,15 +37,23 @@ contract ResourceConstraintManager is AccessControlEnumerable { error PricingExponentTooHigh(uint64 pricingExponent); error NotExpired(); - constructor(address admin, address manager, uint256 _expiryTimestamp) { + constructor(address admin, address manager) { _setupRole(DEFAULT_ADMIN_ROLE, admin); _setupRole(MANAGER_ROLE, manager); - expiryTimestamp = _expiryTimestamp; + } + + /// @notice Sets the expiry timestamp for the current manager contract if it has not been set already. + /// This is called at the beginning of the setGasPricingConstraints and setMultiGasPricingConstraints functions + /// to ensure that this contract can only be used for 2 years from the moment it is first used. + function setExpiryTimestamp() internal { + if (expiryTimestamp == 0) { + expiryTimestamp = block.timestamp + TWO_YEARS_IN_SECONDS; + } } /// @notice Removes the contract from the list of chain owners after the expiry timestamp function revoke() external { - if (block.timestamp < expiryTimestamp) { + if (expiryTimestamp == 0 || block.timestamp < expiryTimestamp) { revert NotExpired(); } ARB_OWNER.removeChainOwner(address(this)); @@ -59,6 +68,9 @@ contract ResourceConstraintManager is AccessControlEnumerable { function setGasPricingConstraints( uint64[3][] calldata constraints ) external onlyRole(MANAGER_ROLE) { + // Set the expiry timestamp in the first call to either `set` function + setExpiryTimestamp(); + // If zero constraints are provided, the chain uses the single-constraint pricing model uint256 nConstraints = constraints.length; if (nConstraints > MAX_SINGLE_GAS_CONSTRAINTS) { @@ -112,6 +124,9 @@ contract ResourceConstraintManager is AccessControlEnumerable { function setMultiGasPricingConstraints( ArbMultiGasConstraintsTypes.ResourceConstraint[] calldata constraints ) external onlyRole(MANAGER_ROLE) { + // Set the expiry timestamp in the first call to either `set` function + setExpiryTimestamp(); + // If zero constraints are provided, the chain uses the single-constraint pricing model // Each constraint adds a small amount of overhead to the gas cost of each transaction and block, so we limit the number of constraints that can be set uint256 nConstraints = constraints.length; diff --git a/test/foundry/ResourceConstraintManager.t.sol b/test/foundry/ResourceConstraintManager.t.sol index 020b4950..cede75c4 100644 --- a/test/foundry/ResourceConstraintManager.t.sol +++ b/test/foundry/ResourceConstraintManager.t.sol @@ -10,26 +10,108 @@ contract ResourceConstraintManagerTest is Test { address constant admin = address(1337); address constant manager = address(7331); - uint256 constant expiryTimestamp = 12345678; constructor() { - resourceConstraintManager = new ResourceConstraintManager(admin, manager, expiryTimestamp); + resourceConstraintManager = new ResourceConstraintManager(admin, manager); vm.etch(address(ARB_OWNER), type(ArbOwnerMock).runtimeCode); } function test_revoke() external { - // Test before expiry - vm.warp(expiryTimestamp - 1); + // Trigger the expiry timestamp via the first set call + uint256 startTime = 1_000_000; + vm.warp(startTime); + uint64[3][] memory constraints = new uint64[3][](1); + constraints[0] = [uint64(10_000_000), uint64(100), uint64(0)]; + vm.prank(manager); + resourceConstraintManager.setGasPricingConstraints(constraints); + + uint256 expiry = startTime + resourceConstraintManager.TWO_YEARS_IN_SECONDS(); + assertEq(resourceConstraintManager.expiryTimestamp(), expiry); + + // One second before expiry should revert + vm.warp(expiry - 1); vm.expectRevert(ResourceConstraintManager.NotExpired.selector); resourceConstraintManager.revoke(); - // Test after expiry - vm.warp(expiryTimestamp); + // Exactly at expiry should succeed + vm.warp(expiry); assertFalse(ARB_OWNER.removeChainOwnerCalled()); resourceConstraintManager.revoke(); assertTrue(ARB_OWNER.removeChainOwnerCalled()); } + function test_revoke_neverUsed() external { + // If no set call has been made, expiryTimestamp is 0 and revoke must revert + assertEq(resourceConstraintManager.expiryTimestamp(), 0); + vm.expectRevert(ResourceConstraintManager.NotExpired.selector); + resourceConstraintManager.revoke(); + + // Warping far forward does not change that — expiry was never initialized + vm.warp(type(uint64).max); + vm.expectRevert(ResourceConstraintManager.NotExpired.selector); + resourceConstraintManager.revoke(); + } + + function test_expiryTimestamp_notUpdatedOnSubsequentCalls() external { + uint256 startTime = 1_000_000; + vm.warp(startTime); + + uint64[3][] memory constraints = new uint64[3][](1); + constraints[0] = [uint64(10_000_000), uint64(100), uint64(0)]; + vm.prank(manager); + resourceConstraintManager.setGasPricingConstraints(constraints); + + uint256 originalExpiry = resourceConstraintManager.expiryTimestamp(); + assertEq(originalExpiry, startTime + resourceConstraintManager.TWO_YEARS_IN_SECONDS()); + + // A later call must not shift the expiry forward + vm.warp(startTime + 1 days); + vm.prank(manager); + resourceConstraintManager.setGasPricingConstraints(constraints); + assertEq(resourceConstraintManager.expiryTimestamp(), originalExpiry); + + // Same guarantee after a much larger gap + vm.warp(startTime + 365 days); + vm.prank(manager); + resourceConstraintManager.setGasPricingConstraints(constraints); + assertEq(resourceConstraintManager.expiryTimestamp(), originalExpiry); + } + + function test_expiryTimestamp_sharedAcrossSetters() external { + uint64[3][] memory singleConstraints = new uint64[3][](1); + singleConstraints[0] = [uint64(10_000_000), uint64(100), uint64(0)]; + ArbMultiGasConstraintsTypes.ResourceConstraint[] memory multiConstraints = + new ArbMultiGasConstraintsTypes.ResourceConstraint[](1); + multiConstraints[0] = _createMultiGasConstraint(10_000_000, 100, 0); + + // Case 1: single-dim first, multi-dim second — multi-dim must not reset expiry + uint256 startTime1 = 1_000_000; + vm.warp(startTime1); + vm.prank(manager); + resourceConstraintManager.setGasPricingConstraints(singleConstraints); + uint256 expiry1 = resourceConstraintManager.expiryTimestamp(); + assertEq(expiry1, startTime1 + resourceConstraintManager.TWO_YEARS_IN_SECONDS()); + + vm.warp(startTime1 + 1 days); + vm.prank(manager); + resourceConstraintManager.setMultiGasPricingConstraints(multiConstraints); + assertEq(resourceConstraintManager.expiryTimestamp(), expiry1); + + // Case 2: multi-dim first, single-dim second — single-dim must not reset expiry + uint256 startTime2 = 2_000_000; + vm.warp(startTime2); + ResourceConstraintManager rcm2 = new ResourceConstraintManager(admin, manager); + vm.prank(manager); + rcm2.setMultiGasPricingConstraints(multiConstraints); + uint256 expiry2 = rcm2.expiryTimestamp(); + assertEq(expiry2, startTime2 + rcm2.TWO_YEARS_IN_SECONDS()); + + vm.warp(startTime2 + 1 days); + vm.prank(manager); + rcm2.setGasPricingConstraints(singleConstraints); + assertEq(rcm2.expiryTimestamp(), expiry2); + } + // // --- setGasPricingConstraints tests --- // From 5e6f075aaa8252c072a3c8392958f6f3a9e918ea Mon Sep 17 00:00:00 2001 From: TucksonDev Date: Wed, 22 Apr 2026 16:39:41 +0100 Subject: [PATCH 2/3] Format --- src/chain/ResourceConstraintManager.sol | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/chain/ResourceConstraintManager.sol b/src/chain/ResourceConstraintManager.sol index 9f2a69f1..74853097 100644 --- a/src/chain/ResourceConstraintManager.sol +++ b/src/chain/ResourceConstraintManager.sol @@ -13,13 +13,13 @@ contract ResourceConstraintManager is AccessControlEnumerable { // Constraint parameters boundaries uint256 public constant MAX_SINGLE_GAS_CONSTRAINTS = 10; uint256 public constant MAX_MULTI_GAS_CONSTRAINTS = 100; - uint64 public constant MIN_SINGLE_DIM_GAS_TARGET_PER_SEC = 7_000_000; // 7M gas/sec + uint64 public constant MIN_SINGLE_DIM_GAS_TARGET_PER_SEC = 7_000_000; // 7M gas/sec uint64 public constant MAX_SINGLE_DIM_GAS_TARGET_PER_SEC = 100_000_000; // 100M gas/sec - uint64 public constant MIN_MULTI_DIM_GAS_TARGET_PER_SEC = 10_000_000; // 10M gas/sec - uint64 public constant MAX_MULTI_DIM_GAS_TARGET_PER_SEC = 500_000_000; // 500M gas/sec - uint32 public constant MIN_ADJUSTMENT_WINDOW_SECS = 5; // 5 seconds (valid for both single-dimension and multi-dimension models) - uint32 public constant MAX_SINGLE_DIM_ADJUSTMENT_WINDOW_SECS = 86400; // 24 hours - uint32 public constant MAX_MULTI_DIM_ADJUSTMENT_WINDOW_SECS = 604800; // 7 days + uint64 public constant MIN_MULTI_DIM_GAS_TARGET_PER_SEC = 10_000_000; // 10M gas/sec + uint64 public constant MAX_MULTI_DIM_GAS_TARGET_PER_SEC = 500_000_000; // 500M gas/sec + uint32 public constant MIN_ADJUSTMENT_WINDOW_SECS = 5; // 5 seconds (valid for both single-dimension and multi-dimension models) + uint32 public constant MAX_SINGLE_DIM_ADJUSTMENT_WINDOW_SECS = 86400; // 24 hours + uint32 public constant MAX_MULTI_DIM_ADJUSTMENT_WINDOW_SECS = 604800; // 7 days uint64 public constant MAX_PRICING_EXPONENT = 8000; // scaled by 1000 to allow for fractional exponents bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); @@ -82,7 +82,8 @@ contract ResourceConstraintManager is AccessControlEnumerable { uint64 adjustmentWindowSecs = constraints[i][1]; uint64 startingBacklogValue = constraints[i][2]; if ( - gasTargetPerSec < MIN_SINGLE_DIM_GAS_TARGET_PER_SEC || gasTargetPerSec > MAX_SINGLE_DIM_GAS_TARGET_PER_SEC + gasTargetPerSec < MIN_SINGLE_DIM_GAS_TARGET_PER_SEC + || gasTargetPerSec > MAX_SINGLE_DIM_GAS_TARGET_PER_SEC ) { revert InvalidTarget(gasTargetPerSec, adjustmentWindowSecs, startingBacklogValue); } @@ -141,7 +142,10 @@ contract ResourceConstraintManager is AccessControlEnumerable { uint64 targetPerSec = constraints[i].targetPerSec; uint32 adjustmentWindowSecs = constraints[i].adjustmentWindowSecs; uint64 startingBacklogValue = constraints[i].backlog; - if (targetPerSec < MIN_MULTI_DIM_GAS_TARGET_PER_SEC || targetPerSec > MAX_MULTI_DIM_GAS_TARGET_PER_SEC) { + if ( + targetPerSec < MIN_MULTI_DIM_GAS_TARGET_PER_SEC + || targetPerSec > MAX_MULTI_DIM_GAS_TARGET_PER_SEC + ) { revert InvalidTarget(targetPerSec, adjustmentWindowSecs, startingBacklogValue); } if ( From 18728ceabb9040bfbdba8a2680ac9f35d90660df Mon Sep 17 00:00:00 2001 From: TucksonDev Date: Wed, 22 Apr 2026 16:42:57 +0100 Subject: [PATCH 3/3] Revert format changes --- src/chain/ResourceConstraintManager.sol | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/chain/ResourceConstraintManager.sol b/src/chain/ResourceConstraintManager.sol index 74853097..9f2a69f1 100644 --- a/src/chain/ResourceConstraintManager.sol +++ b/src/chain/ResourceConstraintManager.sol @@ -13,13 +13,13 @@ contract ResourceConstraintManager is AccessControlEnumerable { // Constraint parameters boundaries uint256 public constant MAX_SINGLE_GAS_CONSTRAINTS = 10; uint256 public constant MAX_MULTI_GAS_CONSTRAINTS = 100; - uint64 public constant MIN_SINGLE_DIM_GAS_TARGET_PER_SEC = 7_000_000; // 7M gas/sec + uint64 public constant MIN_SINGLE_DIM_GAS_TARGET_PER_SEC = 7_000_000; // 7M gas/sec uint64 public constant MAX_SINGLE_DIM_GAS_TARGET_PER_SEC = 100_000_000; // 100M gas/sec - uint64 public constant MIN_MULTI_DIM_GAS_TARGET_PER_SEC = 10_000_000; // 10M gas/sec - uint64 public constant MAX_MULTI_DIM_GAS_TARGET_PER_SEC = 500_000_000; // 500M gas/sec - uint32 public constant MIN_ADJUSTMENT_WINDOW_SECS = 5; // 5 seconds (valid for both single-dimension and multi-dimension models) - uint32 public constant MAX_SINGLE_DIM_ADJUSTMENT_WINDOW_SECS = 86400; // 24 hours - uint32 public constant MAX_MULTI_DIM_ADJUSTMENT_WINDOW_SECS = 604800; // 7 days + uint64 public constant MIN_MULTI_DIM_GAS_TARGET_PER_SEC = 10_000_000; // 10M gas/sec + uint64 public constant MAX_MULTI_DIM_GAS_TARGET_PER_SEC = 500_000_000; // 500M gas/sec + uint32 public constant MIN_ADJUSTMENT_WINDOW_SECS = 5; // 5 seconds (valid for both single-dimension and multi-dimension models) + uint32 public constant MAX_SINGLE_DIM_ADJUSTMENT_WINDOW_SECS = 86400; // 24 hours + uint32 public constant MAX_MULTI_DIM_ADJUSTMENT_WINDOW_SECS = 604800; // 7 days uint64 public constant MAX_PRICING_EXPONENT = 8000; // scaled by 1000 to allow for fractional exponents bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); @@ -82,8 +82,7 @@ contract ResourceConstraintManager is AccessControlEnumerable { uint64 adjustmentWindowSecs = constraints[i][1]; uint64 startingBacklogValue = constraints[i][2]; if ( - gasTargetPerSec < MIN_SINGLE_DIM_GAS_TARGET_PER_SEC - || gasTargetPerSec > MAX_SINGLE_DIM_GAS_TARGET_PER_SEC + gasTargetPerSec < MIN_SINGLE_DIM_GAS_TARGET_PER_SEC || gasTargetPerSec > MAX_SINGLE_DIM_GAS_TARGET_PER_SEC ) { revert InvalidTarget(gasTargetPerSec, adjustmentWindowSecs, startingBacklogValue); } @@ -142,10 +141,7 @@ contract ResourceConstraintManager is AccessControlEnumerable { uint64 targetPerSec = constraints[i].targetPerSec; uint32 adjustmentWindowSecs = constraints[i].adjustmentWindowSecs; uint64 startingBacklogValue = constraints[i].backlog; - if ( - targetPerSec < MIN_MULTI_DIM_GAS_TARGET_PER_SEC - || targetPerSec > MAX_MULTI_DIM_GAS_TARGET_PER_SEC - ) { + if (targetPerSec < MIN_MULTI_DIM_GAS_TARGET_PER_SEC || targetPerSec > MAX_MULTI_DIM_GAS_TARGET_PER_SEC) { revert InvalidTarget(targetPerSec, adjustmentWindowSecs, startingBacklogValue); } if (