Skip to content

Add enumerableSet and get pending functionality#17533

Closed
maurelian wants to merge 47 commits intojm/timelock-nonnestedfrom
jm/timelock-getPending
Closed

Add enumerableSet and get pending functionality#17533
maurelian wants to merge 47 commits intojm/timelock-nonnestedfrom
jm/timelock-getPending

Conversation

@maurelian
Copy link
Copy Markdown
Contributor

@maurelian maurelian commented Sep 19, 2025

This is a draft PR intended primarily to get feedback on the proof of concept implementation of a getter that will return all pending transactions.

It uses OZ's Enumerable set, which a well battle tested dependency, including within the LivenessGuard.

maurelian and others added 30 commits September 11, 2025 15:43
- Add configureTimelockGuard function to allow Safes to set timelock delays
- Validate timelock delay between 1 second and 1 year
- Check that guard is properly enabled on calling Safe using getStorageAt()
- Store configuration per Safe with GuardConfigured event emission
- Add comprehensive test suite covering all spec requirements
- Implement IGuard interface for Safe compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add clearTimelockGuard function to allow Safes to clear timelock configuration
- Validate that guard is disabled before allowing configuration clearing
- Check that Safe was previously configured before clearing
- Delete configuration data and emit GuardCleared event
- Add comprehensive test suite covering all spec requirements
- Add new errors: TimelockGuard_GuardNotConfigured, TimelockGuard_GuardStillEnabled

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add internal _getGuard() helper to centralize guard address retrieval
- Update configureTimelockGuard and clearTimelockGuard to use helper
- Reduces code duplication and improves maintainability

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add cancellationThreshold function to return current threshold for a Safe
- Return 0 if guard not enabled or not configured
- Initialize to 1 when Safe configures guard
- Clear threshold when Safe clears guard configuration
- Add comprehensive test suite covering all spec requirements
- Function never reverts as per spec requirements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…lity

- Add scheduleTransaction placeholder (Function 4)
- Add checkPendingTransactions placeholder (Function 6)
- Add rejectTransaction placeholder (Function 7)
- Add rejectTransactionWithSignature placeholder (Function 8)
- Add cancelTransaction placeholder (Function 9)
- Update checkTransaction placeholder (Function 5)
- All placeholders have proper signatures and documentation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Simpler code

Add cancelTransaction function relying on the Safe's internal logic

undo type change

Add note explaining getScheduledTransactions
This is applied to function inputs/outputs as well as mappings and
events
Include transaction hash in cancellation signature data to prevent
signatures from being reused across different transactions with the
same nonce. Updates both contract implementation and tests.
Add CancellationThresholdUpdated event and emit it from threshold
modification functions. Remove threshold parameter from TransactionCancelled
event for cleaner separation of concerns.
We can simply use `timelock > 0` as an indicator of configuration
The right way to do this is now just to set timelockDelay to zero.
Copy link
Copy Markdown
Contributor Author

maurelian commented Sep 19, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@maurelian maurelian mentioned this pull request Sep 19, 2025
uint256 executionTime;
bool cancelled;
bool executed;
ExecTransactionParams params;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a big change, as previously we were not actually storing the unhashed transaction params in state. Though I think it's a necessary improvement unless we're planning on some complex event parsing.

mapping(Safe => EnumerableSet.Bytes32Set) internal safePendingTxHashes;

/// @notice Mapping from Safe to cancellation threshold.
mapping(Safe => uint256) internal safeCancellationThreshold;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I usually think it's a smell when you have several mappings with the same key. I will think a bit about which of the values being accessed these mappings need to be kept separate and which can be combined into a struct.

Comment on lines +115 to +120
/// @dev WARNING: This operation will copy the entire set of pending transactions to memory,
/// which can be quite expensive. This is designed only to be used by view accessors that are
/// queried without any gas fees. Developers should keep in mind that this function has an
/// unbounded cost, and using it as part of a state-changing function may render the function
/// uncallable if the set grows to a point where copying to memory consumes too much gas to fit
/// in a block.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The alternative to this is some kind of pagination like in the Safe's ModuleManager.

I would rather keep it simple and assume that this will only run offchain. If we're very worried about someone reading this on chain, then we could add require(msg.sender == address(0)) which we've done elsewhere to enable/enforce offchain usage.

@codecov
Copy link
Copy Markdown

codecov bot commented Sep 19, 2025

Codecov Report

❌ Patch coverage is 96.47059% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.93%. Comparing base (b7c9755) to head (aba269e).
⚠️ Report is 89 commits behind head on jm/timelock-nonnested.

Files with missing lines Patch % Lines
...kages/contracts-bedrock/src/safe/TimelockGuard.sol 96.47% 3 Missing ⚠️
Additional details and impacted files
@@                  Coverage Diff                   @@
##           jm/timelock-nonnested   #17533   +/-   ##
======================================================
  Coverage                  95.92%   95.93%           
======================================================
  Files                        112      113    +1     
  Lines                       5127     5212   +85     
======================================================
+ Hits                        4918     5000   +82     
- Misses                       209      212    +3     
Flag Coverage Δ
contracts-bedrock-tests 95.93% <96.47%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...kages/contracts-bedrock/src/safe/TimelockGuard.sol 96.47% <96.47%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@maurelian maurelian force-pushed the jm/timelock-nonnested branch from 8f252c5 to 581cab0 Compare September 19, 2025 20:05
@maurelian maurelian closed this Sep 19, 2025
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