Skip to content

Conversation

forkfury
Copy link

@forkfury forkfury commented Oct 6, 2025

Fix ctest coverage gaps in AccessManaged execution delay handling

  1. Replace magic number 911n with standard time.duration.hours(1) for better
    readability and consistency with other tests in the codebase

  2. Add missing edge case tests for execution delay:

    • Zero execution delay (delay = 0) - ensures immediate execution works
    • Maximum execution delay (uint32 max) - validates system handles extreme values
    • Past timestamp handling - verifies schedule() correctly adjusts when time
      is set to a past timestamp by automatically setting it to minimum required time

These changes are necessary because:

  • The magic number 911n was arbitrary and inconsistent with the codebase standards
  • Missing edge case tests create potential security vulnerabilities by not validating
    boundary conditions that could be exploited
  • The schedule() function has specific logic for handling past timestamps that was
    not being tested, potentially hiding bugs in the automatic time adjustment feature
  • Zero delay and maximum delay scenarios are critical for access control systems
    and must be thoroughly tested to prevent privilege escalation or denial of service

@forkfury forkfury requested a review from a team as a code owner October 6, 2025 14:40
Copy link

changeset-bot bot commented Oct 6, 2025

⚠️ No Changeset found

Latest commit: eb4f7c3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

coderabbitai bot commented Oct 6, 2025

Walkthrough

Updates tests in test/access/manager/AccessManaged.test.js. Replaces a hardcoded executionDelay of 911n with a 1-hour delay for the “when role is granted with execution delay” setup. Adds three test cases: zero execution delay (no scheduling required), maximum uint32 execution delay (scheduling required, expects AccessManagerNotScheduled on unscheduled execution), and scheduling with a past timestamp adjusts to at least now + executionDelay. Retains existing assertions distinguishing scheduled vs. unscheduled operations and corresponding AccessManagerNotScheduled reverts where applicable. No changes to exported or public entities.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately summarizes the main change by indicating that test code is being updated to fix execution delay edge cases in the AccessManaged test suite, which aligns directly with the pull request content.
Description Check ✅ Passed The pull request description clearly outlines the motivation and specific changes, including replacing the magic number and adding edge case tests for zero, maximum, and past execution delays, which directly matches the modifications in the test files.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 765b288 and eb4f7c3.

📒 Files selected for processing (1)
  • test/access/manager/AccessManaged.test.js (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/access/manager/AccessManaged.test.js (3)
test/governance/extensions/GovernorTimelockAccess.test.js (1)
  • time (11-11)
test/access/manager/AccessManager.test.js (1)
  • time (8-8)
test/helpers/access-manager.js (1)
  • time (4-4)
🪛 GitHub Check: codespell
test/access/manager/AccessManaged.test.js

[failure] 129-129:
pastTime ==> pastime


[failure] 126-126:
pastTime ==> pastime

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Redirect rules - solidity-contracts
  • GitHub Check: Header rules - solidity-contracts
  • GitHub Check: Pages changed - solidity-contracts
  • GitHub Check: halmos
  • GitHub Check: slither
  • GitHub Check: coverage
  • GitHub Check: tests-foundry
  • GitHub Check: tests-upgradeable
  • GitHub Check: tests
🔇 Additional comments (2)
test/access/manager/AccessManaged.test.js (2)

68-68: LGTM! Improved readability.

Replacing the magic number 911n with time.duration.hours(1) makes the test more readable and consistent with the codebase standards.


101-107: LGTM! Zero delay edge case covered.

The test correctly validates that when a role is granted with zero execution delay, no scheduling is required for immediate execution.

Comment on lines +109 to +116
it('handles maximum execution delay correctly', async function () {
const maxExecutionDelay = 2n**32n - 1n; // uint32 max
await this.authority.$_grantRole(this.role, this.roleMember, 0, maxExecutionDelay);

// Should require scheduling
await expect(this.managed.connect(this.roleMember)[this.selector]())
.to.be.revertedWithCustomError(this.authority, 'AccessManagerNotScheduled');
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add opId to error assertion for consistency.

The test should calculate the opId and include it in .withArgs(opId) for a more precise assertion, consistent with the similar test at lines 72-79.

Apply this diff to add the missing opId:

 it('handles maximum execution delay correctly', async function () {
   const maxExecutionDelay = 2n**32n - 1n; // uint32 max
   await this.authority.$_grantRole(this.role, this.roleMember, 0, maxExecutionDelay);
   
+  const fn = this.managed.interface.getFunction(this.selector);
+  const calldata = this.managed.interface.encodeFunctionData(fn, []);
+  const opId = await this.authority.hashOperation(this.roleMember, this.managed, calldata);
+  
   // Should require scheduling
   await expect(this.managed.connect(this.roleMember)[this.selector]())
-    .to.be.revertedWithCustomError(this.authority, 'AccessManagerNotScheduled');
+    .to.be.revertedWithCustomError(this.authority, 'AccessManagerNotScheduled')
+    .withArgs(opId);
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('handles maximum execution delay correctly', async function () {
const maxExecutionDelay = 2n**32n - 1n; // uint32 max
await this.authority.$_grantRole(this.role, this.roleMember, 0, maxExecutionDelay);
// Should require scheduling
await expect(this.managed.connect(this.roleMember)[this.selector]())
.to.be.revertedWithCustomError(this.authority, 'AccessManagerNotScheduled');
});
it('handles maximum execution delay correctly', async function () {
const maxExecutionDelay = 2n**32n - 1n; // uint32 max
await this.authority.$_grantRole(this.role, this.roleMember, 0, maxExecutionDelay);
const fn = this.managed.interface.getFunction(this.selector);
const calldata = this.managed.interface.encodeFunctionData(fn, []);
const opId = await this.authority.hashOperation(this.roleMember, this.managed, calldata);
// Should require scheduling
await expect(this.managed.connect(this.roleMember)[this.selector]())
.to.be.revertedWithCustomError(this.authority, 'AccessManagerNotScheduled')
.withArgs(opId);
});
🤖 Prompt for AI Agents
In test/access/manager/AccessManaged.test.js around lines 109 to 116, the
assertion for the revert should include the computed opId like the earlier test;
compute opId the same way as in the test at lines 72-79 (using the role,
roleMember, selector, and delay values used) and change the expect to assert
.to.be.revertedWithCustomError(this.authority,
'AccessManagerNotScheduled').withArgs(opId) so the test verifies the specific
operation id.

Comment on lines +118 to +135
it('adjusts when time to minimum required time if too early', async function () {
const executionDelay = time.duration.hours(1);
await this.authority.$_grantRole(this.role, this.roleMember, 0, executionDelay);

const fn = this.managed.interface.getFunction(this.selector);
const calldata = this.managed.interface.encodeFunctionData(fn, []);

// Try to schedule in the past
const pastTime = (await time.clock.timestamp()) - time.duration.hours(1);

// Should automatically adjust time to minimum required
const { operationId } = await this.authority.connect(this.roleMember).schedule(this.managed, calldata, pastTime);
const scheduledTime = await this.authority.getSchedule(operationId);

// Verify time was adjusted to minimum required
const minTime = (await time.clock.timestamp()) + executionDelay;
expect(scheduledTime).to.be.at.least(minTime);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix timing inconsistency in minTime calculation.

The test calculates minTime at line 133 using the current timestamp, but this occurs after the schedule() call at line 129. The timestamp used for minTime should be captured before or at the time of scheduling to ensure the assertion accurately validates the behavior.

Apply this diff to fix the timing issue:

 it('adjusts when time to minimum required time if too early', async function () {
   const executionDelay = time.duration.hours(1);
   await this.authority.$_grantRole(this.role, this.roleMember, 0, executionDelay);
   
   const fn = this.managed.interface.getFunction(this.selector);
   const calldata = this.managed.interface.encodeFunctionData(fn, []);
   
   // Try to schedule in the past
-  const pastTime = (await time.clock.timestamp()) - time.duration.hours(1);
+  const currentTime = await time.clock.timestamp();
+  const pastTime = currentTime - time.duration.hours(1);
   
   // Should automatically adjust time to minimum required
   const { operationId } = await this.authority.connect(this.roleMember).schedule(this.managed, calldata, pastTime);
   const scheduledTime = await this.authority.getSchedule(operationId);
   
   // Verify time was adjusted to minimum required
-  const minTime = (await time.clock.timestamp()) + executionDelay;
+  const minTime = currentTime + executionDelay;
   expect(scheduledTime).to.be.at.least(minTime);
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('adjusts when time to minimum required time if too early', async function () {
const executionDelay = time.duration.hours(1);
await this.authority.$_grantRole(this.role, this.roleMember, 0, executionDelay);
const fn = this.managed.interface.getFunction(this.selector);
const calldata = this.managed.interface.encodeFunctionData(fn, []);
// Try to schedule in the past
const pastTime = (await time.clock.timestamp()) - time.duration.hours(1);
// Should automatically adjust time to minimum required
const { operationId } = await this.authority.connect(this.roleMember).schedule(this.managed, calldata, pastTime);
const scheduledTime = await this.authority.getSchedule(operationId);
// Verify time was adjusted to minimum required
const minTime = (await time.clock.timestamp()) + executionDelay;
expect(scheduledTime).to.be.at.least(minTime);
});
it('adjusts when time to minimum required time if too early', async function () {
const executionDelay = time.duration.hours(1);
await this.authority.$_grantRole(this.role, this.roleMember, 0, executionDelay);
const fn = this.managed.interface.getFunction(this.selector);
const calldata = this.managed.interface.encodeFunctionData(fn, []);
// Try to schedule in the past
const currentTime = await time.clock.timestamp();
const pastTime = currentTime - time.duration.hours(1);
// Should automatically adjust time to minimum required
const { operationId } = await this.authority.connect(this.roleMember).schedule(this.managed, calldata, pastTime);
const scheduledTime = await this.authority.getSchedule(operationId);
// Verify time was adjusted to minimum required
const minTime = currentTime + executionDelay;
expect(scheduledTime).to.be.at.least(minTime);
});
🧰 Tools
🪛 GitHub Check: codespell

[failure] 129-129:
pastTime ==> pastime


[failure] 126-126:
pastTime ==> pastime

🤖 Prompt for AI Agents
In test/access/manager/AccessManaged.test.js around lines 118 to 135, the
calculation of minTime happens after calling schedule which introduces a timing
race; capture the current timestamp immediately before calling schedule (e.g.,
const now = await time.clock.timestamp()), compute minTime = now +
executionDelay using that captured value, then call schedule and use the
computed minTime in the assertion so the test consistently validates that
scheduledTime >= minTime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant