-
Notifications
You must be signed in to change notification settings - Fork 6
feat: EVM Authorizations contract #343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🔭 Outside diff range comments (1)
solidity/src/libraries/Forwarder.sol (1)
40-86
:⚠️ Potential issue
validateConfig
usesuint8
loop counters – overflow risks for >255 tokens
i
andj
are declared asuint8
.
IfforwardingConfigs.length
≥ 256, the counters overflow to0
, causing an infinite loop or silently skipping duplicate detection.- for (uint8 i = 0; i < len - 1; i++) { + for (uint256 i = 0; i < len - 1; i++) { ... - for (uint8 j = i + 1; j < len; j++) { + for (uint256 j = i + 1; j < len; j++) {Alternatively, enforce an explicit cap:
require(len < 256, "Max 255 forwarding configs supported");
🧹 Nitpick comments (10)
solidity/src/processor/libs/ProcessorEvents.sol (1)
17-25
: Indexaddr
in the newly-added events for efficient filteringWithout the
indexed
modifier, off-chain services (sub-graphs, UIs, analytics) have to scan every log to discover additions/removals. Making the address parameter indexed matches common practice and the existing events’ design.-event AuthorizedAddressAdded(address addr); -event AuthorizedAddressRemoved(address addr); +event AuthorizedAddressAdded(address indexed addr); +event AuthorizedAddressRemoved(address indexed addr);solidity/src/libraries/Forwarder.sol (2)
112-114
: Minor gas tweak: cacheinputAccount
/outputAccount
as immutables if static
input
andoutput
are re-loaded from storage on everyforward()
call.
If these accounts never change, consider making them immutable constructor parameters or separate storage slots to save twoSLOAD
s per call.
5-6
: Prefer an interface (IBaseAccount
) over the concreteBaseAccount
typeImporting the implementation tightens coupling and prevents use with alternative account contracts. An interface containing just
execute
would future-proof the Forwarder with zero runtime overhead.solidity/test/authorization/Authorization.t.sol (1)
70-79
: Use custom-error selectors inexpectRevert
to avoid brittle string checksHard-coding
"Unauthorized access"
couples the test to an exact revert string.
If the contract later adopts custom errors or refactors the wording, the test will fail even though behaviour is unchanged.// e.g. if the contract defines `error UnauthorizedAccess();` vm.expectRevert(Authorization.UnauthorizedAccess.selector);Using selectors keeps the test stable and self-documenting.
solidity/src/authorization/Authorization.sol (6)
100-102
: Consider using custom errors instead of string error messages.Using custom errors instead of string error messages is more gas-efficient in Solidity 0.8.4+.
- if (_processor == address(0)) { - revert("Processor cannot be zero address"); - } + if (_processor == address(0)) { + revert ProcessorCannotBeZeroAddress(); + }
115-117
: Reuse the same custom error for consistent validation.Same validation logic appears in both the constructor and updateProcessor function.
- if (_processor == address(0)) { - revert("Processor cannot be zero address"); - } + if (_processor == address(0)) { + revert ProcessorCannotBeZeroAddress(); + }
135-137
: Consider validating the admin address.While the contract allows address(0) for permissionless access in authorizations, you might want to prevent accidentally adding address(0) as an admin.
+ if (_admin == address(0)) { + revert("Admin address cannot be zero address"); + } adminAddresses[_admin] = true;
214-259
: Reduce code duplication in atomic and non-atomic subroutine handling.Both subroutine branches perform nearly identical operations: decoding the subroutine, verifying lengths match, and checking authorization for each function. This could be refactored to improve maintainability.
- if (sendMsgs.subroutine.subroutineType == IProcessorMessageTypes.SubroutineType.Atomic) { - IProcessorMessageTypes.AtomicSubroutine memory atomicSubroutine = - abi.decode(sendMsgs.subroutine.subroutine, (IProcessorMessageTypes.AtomicSubroutine)); - - // Verify message and function array lengths match - if ( - atomicSubroutine.functions.length > 0 - && atomicSubroutine.functions.length != sendMsgs.messages.length - ) { - revert("Subroutine functions length does not match messages length"); - } - - // Check authorization for each function in the atomic subroutine - for (uint256 i = 0; i < atomicSubroutine.functions.length; i++) { - if ( - !_checkUserIsAuthorized( - msg.sender, atomicSubroutine.functions[i].contractAddress, sendMsgs.messages[i] - ) - ) { - revert("Unauthorized access"); - } - } - } else { - // Handle NonAtomic subroutine - IProcessorMessageTypes.NonAtomicSubroutine memory nonAtomicSubroutine = - abi.decode(sendMsgs.subroutine.subroutine, (IProcessorMessageTypes.NonAtomicSubroutine)); - - // Verify message and function array lengths match - if ( - nonAtomicSubroutine.functions.length > 0 - && nonAtomicSubroutine.functions.length != sendMsgs.messages.length - ) { - revert("Subroutine functions length does not match messages length"); - } - - // Check authorization for each function in the non-atomic subroutine - for (uint256 i = 0; i < nonAtomicSubroutine.functions.length; i++) { - if ( - !_checkUserIsAuthorized( - msg.sender, nonAtomicSubroutine.functions[i].contractAddress, sendMsgs.messages[i] - ) - ) { - revert("Unauthorized access"); - } - } - } + IProcessorMessageTypes.Function[] memory functions; + + if (sendMsgs.subroutine.subroutineType == IProcessorMessageTypes.SubroutineType.Atomic) { + IProcessorMessageTypes.AtomicSubroutine memory atomicSubroutine = + abi.decode(sendMsgs.subroutine.subroutine, (IProcessorMessageTypes.AtomicSubroutine)); + functions = atomicSubroutine.functions; + } else { + // Handle NonAtomic subroutine + IProcessorMessageTypes.NonAtomicSubroutine memory nonAtomicSubroutine = + abi.decode(sendMsgs.subroutine.subroutine, (IProcessorMessageTypes.NonAtomicSubroutine)); + functions = nonAtomicSubroutine.functions; + } + + // Verify message and function array lengths match + if ( + functions.length > 0 && functions.length != sendMsgs.messages.length + ) { + revert("Subroutine functions length does not match messages length"); + } + + // Check authorization for each function + for (uint256 i = 0; i < functions.length; i++) { + if ( + !_checkUserIsAuthorized( + msg.sender, functions[i].contractAddress, sendMsgs.messages[i] + ) + ) { + revert("Unauthorized access"); + } + }
1-326
: Consider adding a mechanism to pause operations in emergency situations.While the contract has good access controls, it lacks a mechanism to pause operations in case of emergency. Consider implementing OpenZeppelin's Pausable contract for an additional layer of security.
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; import {ProcessorBase} from "../processor/ProcessorBase.sol"; ... -contract Authorization is Ownable, ICallback { +contract Authorization is Ownable, Pausable, ICallback { ... - function sendProcessorMessage(bytes calldata _message) external { + function sendProcessorMessage(bytes calldata _message) external whenNotPaused { ... + function pause() external onlyOwner { + _pause(); + } + + function unpause() external onlyOwner { + _unpause(); + }
155-168
: Consider adding batch operations for authorization management.For scenarios where many authorizations need to be added or removed, batch operations would be more gas-efficient. Consider adding functions to handle multiple authorizations in a single transaction.
function addStandardAuthorizations( address[] memory _users, address[] memory _contracts, bytes[] memory _calls ) external onlyOwner { require(_users.length == _contracts.length && _contracts.length == _calls.length, "Array lengths must match"); for (uint256 i = 0; i < _users.length; i++) { authorizations[_users[i]][_contracts[i]][keccak256(_calls[i])] = true; } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
solidity/src/authorization/Authorization.sol
(1 hunks)solidity/src/libraries/Forwarder.sol
(4 hunks)solidity/src/processor/ProcessorBase.sol
(3 hunks)solidity/src/processor/libs/ProcessorErrors.sol
(0 hunks)solidity/src/processor/libs/ProcessorEvents.sol
(1 hunks)solidity/test/authorization/Authorization.t.sol
(1 hunks)solidity/test/processor/LiteProcessor.t.sol
(0 hunks)solidity/test/processor/Processor.t.sol
(0 hunks)
💤 Files with no reviewable changes (3)
- solidity/src/processor/libs/ProcessorErrors.sol
- solidity/test/processor/Processor.t.sol
- solidity/test/processor/LiteProcessor.t.sol
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: setup-local-ic
- GitHub Check: build-and-deploy
🔇 Additional comments (4)
solidity/test/authorization/Authorization.t.sol (1)
321-335
: Encoding structs containing contract types can be fragile across compiler upgrades
abi.encode(config)
storesBaseAccount
fields as raw addresses, which is fine today.
IfBaseAccount
later adds immutable variables or new state, two semantically identical configs might encode differently and break comparison logic. Consider storing the plainaddress
instead of the contract type when persisting configs long-term.Please verify whether cross-version compatibility is a requirement.
solidity/src/authorization/Authorization.sol (3)
1-19
: Well-structured contract with good documentation.The contract's purpose and responsibilities are clearly documented. It inherits correctly from OpenZeppelin's Ownable for permission control and implements ICallback for processor interactions.
294-302
: The permissionless authorization check is well implemented.The function correctly checks for both direct user authorization and permissionless authorization (via address(0)). This implementation supports the contract's flexibility goals.
305-325
: Callback handling implementation is secure and well-structured.The function properly verifies the sender is the processor contract, handles the callback data appropriately, and emits an event with all relevant information. The conditional storage of callbacks based on the
storeCallbacks
flag provides good flexibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
solidity/src/authorization/Authorization.sol (1)
155-157
: Well-implemented permissionless access functionalityThe contract effectively implements permissionless access through the
addStandardAuthorization
function when using address(0) as the user. This provides flexibility in authorization control.
🧹 Nitpick comments (5)
solidity/src/authorization/Authorization.sol (5)
126-128
: Consider validating the verifier addressUnlike the
updateProcessor
function which validates that the processor address is not zero, this function allows setting the verifier to address(0). While the comments indicate this is intentional (to disable ZK verification), consider adding a comment in this function to explicitly document this behavior.function updateVerifier(address _verifier) external onlyOwner { + // Zero address is allowed and represents disabled ZK verification verifier = _verifier; }
101-102
: Use custom errors instead of string literalsUsing string literals for error messages is more gas-expensive than using custom error types. Consider defining custom errors for better gas efficiency and error reporting.
// At the top of the contract, define custom errors +error ProcessorCannotBeZeroAddress(); +error UnauthorizedAccess(); +error SubroutineLengthMismatch(); +error OnlyProcessorCanSendCallbacks(); // Then replace string errors with custom errors function updateProcessor(address _processor) external onlyOwner { if (_processor == address(0)) { - revert("Processor cannot be zero address"); + revert ProcessorCannotBeZeroAddress(); } processor = ProcessorBase(_processor); }Also applies to: 115-117
214-272
: Refactor duplicate code in subroutine handlingThe code for handling Atomic and NonAtomic subroutines contains significant duplication. Both sections perform the same general operations: decode the subroutine, verify message/function array lengths match, and check authorization for each function.
Consider extracting the common logic into a helper function to reduce code duplication and improve maintainability:
+/** + * @dev Helper function to check authorizations for a list of functions + * @param _sender The address attempting to call the functions + * @param _functions Array of function information + * @param _messages Array of message data + */ +function _checkSubroutineAuthorizations( + address _sender, + IProcessorMessageTypes.Function[] memory _functions, + bytes[] memory _messages +) internal view { + // Verify message and function array lengths match + if (_functions.length > 0 && _functions.length != _messages.length) { + revert("Subroutine functions length does not match messages length"); + } + + // Check authorization for each function + for (uint256 i = 0; i < _functions.length; i++) { + if (!_checkUserIsAuthorized(_sender, _functions[i].contractAddress, _messages[i])) { + revert("Unauthorized access"); + } + } +}Then use this helper function in both subroutine handlers.
262-262
: Document why priority is forced to MediumThe contract forces the priority to Medium for all SendMsgs regardless of the input priority. Consider adding a comment to explain why this design choice was made, as it may not be immediately obvious to other developers.
// Force the priority to Medium for all SendMsgs +// Medium priority ensures consistent processing without overwhelming the system sendMsgs.priority = IProcessorMessageTypes.Priority.Medium;
312-324
: Consider handling callback data sizeThe callback data is stored directly without any size validation. For large callbacks, this could lead to high gas costs or even transaction failures. Consider adding validation or limits to the size of callback data that can be stored.
// Store the callback data if storeCallbacks is true if (storeCallbacks) { + // Optional: Add size validation to prevent excessive gas costs + if (callback.data.length > MAX_CALLBACK_SIZE) { + callback.data = callback.data[:MAX_CALLBACK_SIZE]; + } callbacks[callback.executionId] = ProcessorCallback({ executionResult: callback.executionResult, executedCount: uint64(callback.executedCount), data: callback.data }); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (3)
solidity/src/authorization/Authorization.sol
(1 hunks)solidity/src/processor/ProcessorBase.sol
(3 hunks)solidity/src/processor/libs/ProcessorErrors.sol
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- solidity/src/processor/libs/ProcessorErrors.sol
- solidity/src/processor/ProcessorBase.sol
🔇 Additional comments (2)
solidity/src/authorization/Authorization.sol (2)
275-278
: Good implementation of Checks-Effects-Interactions patternThe execution ID is incremented after the external call to the processor, which follows the Checks-Effects-Interactions pattern. This is a good security practice that helps prevent reentrancy attacks.
1-326
: Comprehensive authorization implementationOverall, this contract provides a well-structured and comprehensive implementation of an authorization layer for processor interactions. The contract:
- Manages both standard and ZK-based authorizations
- Provides granular control over which functions can be called by which addresses
- Supports permissionless access when needed
- Handles callback data appropriately
- Follows security best practices like the Checks-Effects-Interactions pattern
The implementation aligns well with the stated PR objectives of introducing an Authorizations contract for the EVM environment without ZK configuration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (7)
solidity/src/authorization/Authorization.sol (7)
171-173
: Consider adding a zero-address check for the verification gatewayThe
updateVerificationGateway
function allows setting the verification gateway to the zero address, which is mentioned as potentially valid in the docs. However, for consistency withupdateProcessor
and to prevent accidental misconfiguration, consider adding a conditional check for the zero address with a clear comment indicating when this would be valid.function updateVerificationGateway(address _verificationGateway) external onlyOwner { + // Zero address is allowed only when not using ZK proofs verificationGateway = VerificationGateway(_verificationGateway); }
182-184
: Add event emission for admin address changesAdding or removing admin addresses are significant security-related actions that should be logged. Consider adding events for these operations to improve auditability and off-chain monitoring.
+// Add this event to the contract declarations +event AdminAddressAdded(address indexed admin); +event AdminAddressRemoved(address indexed admin); function addAdminAddress(address _admin) external onlyOwner { adminAddresses[_admin] = true; + emit AdminAddressAdded(_admin); } function removeAdminAddress(address _admin) external onlyOwner { delete adminAddresses[_admin]; + emit AdminAddressRemoved(_admin); }
208-210
: Consider adding event emission for authorization changesSimilar to admin address changes, adding or removing authorizations are security-critical operations that should be logged with events for better off-chain tracking and auditability.
+// Add this event to the contract declarations +event AuthorizationAdded(address indexed user, address indexed contractAddress, bytes32 indexed callHash); +event AuthorizationRemoved(address indexed user, address indexed contractAddress, bytes32 indexed callHash); // In addStandardAuthorizations: for (uint256 i = 0; i < _users.length; i++) { bytes32 callHash = keccak256(_calls[i]); authorizations[_users[i]][_contracts[i]][callHash] = true; + emit AuthorizationAdded(_users[i], _contracts[i], callHash); } // Similarly for removeStandardAuthorizations
465-473
: Follow Checks-Effects-Interactions pattern consistentlyWell done moving the
executionId++
to after theprocessor.execute
call. To fully adhere to the Checks-Effects-Interactions pattern, consider also updatingzkAuthorizationLastExecutionBlock
before theprocessor.execute
call to minimize reentrancy risks, even with trusted contracts.// Update state variables before external calls +// Update the last execution block for the registry +zkAuthorizationLastExecutionBlock[decodedZKMessage.registry] = uint64(block.number); +// Increment the execution ID for the next message +uint64 currentExecutionId = executionId; +executionId++; // Execute the message using the processor processor.execute(abi.encode(decodedZKMessage.processorMessage)); -// Increment the execution ID for the next message -executionId++; -// Update the last execution block for the registry -zkAuthorizationLastExecutionBlock[decodedZKMessage.registry] = uint64(block.number);
323-323
: Document the reasoning for forcing Medium priorityThe code forces all
SendMsgs
to useMedium
priority. It would be helpful to include a comment explaining why this decision was made, especially since it overrides any priority specified by the user.// Force the priority to Medium for all SendMsgs +// Medium priority ensures consistent handling and prevents abuse of high priority access sendMsgs.priority = IProcessorMessageTypes.Priority.Medium;
150-151
: Consider making the execution ID start value configurableThe contract initializes
executionId
to 0 in the constructor. For flexibility in migrations or upgrades, consider making this initial value configurable.-constructor(address _owner, address _processor, address _verificationGateway, bool _storeCallbacks) +constructor(address _owner, address _processor, address _verificationGateway, bool _storeCallbacks, uint64 _initialExecutionId) Ownable(_owner) { if (_processor == address(0)) { revert("Processor cannot be zero address"); } processor = ProcessorBase(_processor); verificationGateway = VerificationGateway(_verificationGateway); - executionId = 0; + executionId = _initialExecutionId; storeCallbacks = _storeCallbacks; }
92-92
: Consider adding a method to check multiple authorizations at onceFor clients that need to check multiple authorizations before sending a transaction, it would be helpful to provide a view function that can check multiple authorizations in a single call.
/** * @notice Checks if a user is authorized for multiple function calls * @dev Batch checks for authorization to reduce the number of calls needed * @param _user Address of the user to check * @param _contracts Array of contract addresses * @param _calls Array of function call data * @return results Array of booleans indicating authorization for each call */ function checkBatchAuthorizations( address _user, address[] calldata _contracts, bytes[] calldata _calls ) external view returns (bool[] memory results) { require(_contracts.length == _calls.length, "Array lengths must match"); results = new bool[](_calls.length); for (uint256 i = 0; i < _calls.length; i++) { results[i] = _checkUserIsAuthorized(_user, _contracts[i], _calls[i]); } return results; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
⛔ Files ignored due to path filters (1)
solidity/soldeer.lock
is excluded by!**/*.lock
📒 Files selected for processing (7)
solidity/foundry.toml
(1 hunks)solidity/remappings.txt
(1 hunks)solidity/src/authorization/Authorization.sol
(1 hunks)solidity/src/verification/SP1VerificationGateway.sol
(1 hunks)solidity/src/verification/VerificationGateway.sol
(1 hunks)solidity/test/authorization/AuthorizationStandard.t.sol
(1 hunks)solidity/test/authorization/AuthorizationZK.t.sol
(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- solidity/remappings.txt
- solidity/foundry.toml
🧰 Additional context used
🧠 Learnings (1)
solidity/src/authorization/Authorization.sol (1)
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:278-278
Timestamp: 2025-05-08T18:52:46.603Z
Learning: The processor contract is considered trusted within the Valence Protocol ecosystem and is not expected to attack the authorizations contract.
🔇 Additional comments (44)
solidity/src/verification/SP1VerificationGateway.sol (4)
1-5
: Well-structured imports.The imports are appropriately defined with specific paths. The contract makes use of the abstract VerificationGateway as a base and properly imports the ISP1Verifier interface from the succinctlabs-sp1-contracts dependency.
16-18
: Clean implementation of getVerifier function.The function correctly casts the stored verifier address to the ISP1Verifier interface, providing a type-safe way to interact with the verifier.
25-25
: Simple constructor implementation.The constructor properly calls the parent VerificationGateway constructor with the provided parameters and doesn't add unnecessary complexity.
33-51
:❓ Verification inconclusive
Verify the behavior of verifyProof function.
The implementation calls
sp1Verifier.verifyProof()
but doesn't check its return value, and always returns true. This suggests an assumption thatverifyProof()
will revert on failure rather than returning a boolean status.
🏁 Script executed:
#!/bin/bash # Check if the SP1Verifier's verifyProof reverts on failure or returns a boolean # Search for the ISP1Verifier interface definition echo "Searching for ISP1Verifier interface..." rg -A 10 "interface ISP1Verifier" --type solidityLength of output: 173
🏁 Script executed:
#!/bin/bash echo "Searching for ISP1Verifier interface in .sol files..." rg -A 5 "interface ISP1Verifier" --glob '*.sol' echo echo "Searching for all verifyProof definitions in .sol files..." rg -n "verifyProof" --glob '*.sol'Length of output: 445
Verify verifyProof Behavior
I couldn’t locate the
ISP1Verifier
interface or itsverifyProof
implementation in the codebase, so it’s unclear whether:
verifyProof(...)
reverts on failure (void)- or returns a
bool
status that needs checking.Please confirm and address one of the following:
- If
verifyProof
reverts on invalid proofs, the current pattern is fine.- If it returns
bool
, capture andrequire
its result instead of unconditionally returningtrue
.Key location:
- solidity/src/verification/SP1VerificationGateway.sol:48
solidity/src/verification/VerificationGateway.sol (7)
1-11
: Well-defined abstract contract.The contract is properly defined as abstract with clear documentation on its purpose as a base for verification gateways.
12-22
: Well-structured state variables.The contract clearly defines its state variables with appropriate visibility and documentation comments. The nested mapping for programVKs is well-designed to associate verification keys with user addresses and registry IDs.
29-32
: Constructor with proper initialization.The constructor correctly initializes the state variables and calls the Ownable constructor with the msg.sender.
39-41
: Secured updateVerifier function.The function is properly restricted to the owner using the onlyOwner modifier, preventing unauthorized changes to the verifier address.
49-51
: User-specific registry management.The function allows users to add verification keys only for their own address, enforcing a natural access control mechanism.
58-60
: Secure registry removal.Similar to addRegistry, this function allows users to remove registries only for their own address, maintaining proper access control.
70-70
: Well-defined abstract function.The verify function is properly declared as external virtual, clearly indicating that derived contracts must implement it.
solidity/test/authorization/AuthorizationZK.t.sol (10)
1-15
: Comprehensive test imports.The test file imports all necessary contracts and interfaces needed for testing the ZK authorization flow.
16-33
: Well-organized test state variables.The test contract defines clear state variables including contract instances and test addresses, with constants for registry configuration to improve test readability.
34-50
: Thorough test setup.The setUp function deploys and configures all necessary contracts for testing, establishing a realistic test environment.
54-105
: Comprehensive registry addition tests.The test thoroughly verifies registry addition functionality, checking both the storage of verification keys and authorized users across different scenarios including permissionless registries.
110-157
: Complete registry removal verification.The test properly verifies that removed registries clear all associated data, including verification keys, authorized users, and last execution blocks.
162-179
: Access control verification for registry addition.The test verifies that unauthorized users cannot add registries, ensuring proper access control.
184-194
: Access control verification for registry removal.The test verifies that unauthorized users cannot remove registries, ensuring proper access control.
199-219
: Input validation testing.The test checks that the contract properly validates input array lengths, preventing potential issues from mismatched arrays.
226-256
: Unauthorized access verification for ZK messages.The test validates that unauthorized addresses cannot execute ZK messages for protected registries.
265-294
: Helpful test utility functions.The helper functions for creating test ZK messages enhance test readability and maintainability.
solidity/test/authorization/AuthorizationStandard.t.sol (20)
1-18
: Comprehensive test imports.The test file correctly imports all necessary contracts and interfaces for testing the standard authorization flow.
19-74
: Thorough test setup with required contract instances.The setUp function correctly deploys and configures all necessary contracts, creating accounts and forwarder configurations for comprehensive testing.
78-89
: Unauthorized access prevention test.The test verifies that unauthorized users cannot send processor messages, ensuring access control works correctly.
91-121
: Admin permissions verification.The test confirms that admin users can send admin-only messages like Pause and Resume, validating role-based access control.
123-138
: Non-admin restrictions test.The test verifies that non-admin users cannot send admin-only messages, completing the access control test suite.
142-153
: Processor update functionality test.The test validates that the owner can update the processor address successfully.
155-159
: Zero address validation test.The test confirms that the contract rejects zero address when updating the processor, preventing potential issues.
164-172
: Verification gateway update test.The test validates the owner can update the verification gateway address correctly.
174-191
: Callback handler access control test.The test verifies that only the processor can call the callback handler, preventing unauthorized callback processing.
198-202
: Admin address addition test.The test confirms that admin addresses can be added successfully by the owner.
207-219
: Admin address removal test.The test verifies that admin addresses can be removed successfully by the owner.
224-228
: Admin management access control test.The test confirms that unauthorized users cannot add admin addresses, maintaining proper access control.
235-243
: Standard authorization addition test.The test validates that user authorizations can be added correctly for specific contract calls.
248-257
: Permissionless authorization test.The test confirms that permissionless authorizations (address zero) work correctly, allowing any address to execute specific calls.
259-268
: Invalid authorization input validation.The test verifies that the contract rejects mismatched array lengths when adding authorizations.
273-287
: Authorization removal test.The test confirms that authorizations can be removed successfully after being added.
291-316
: Authorized processor message test.The test validates the complete flow of sending an authorized processor message, including execution ID increment, configuration updates, and callback verification.
318-334
: Permissionless processor message test.The test confirms that permissionless messages can be sent by otherwise unauthorized addresses when configured with address(0).
342-357
: Forwarder configuration helper function.The helper function creates a well-structured forwarder configuration for testing, enhancing test readability and maintainability.
364-411
: Processor message creation helper.The helper function properly constructs a complete SendMsgs processor message with all required components, facilitating thorough testing.
solidity/src/authorization/Authorization.sol (3)
277-297
: LGTM: Thorough authorization checkingThe code properly verifies that the message length matches the number of functions in the subroutine and checks authorization for each function call. This is a robust approach that ensures granular access control.
336-340
: LGTM: Appropriate application of Checks-Effects-Interactions patternGood implementation of the Checks-Effects-Interactions pattern by moving
executionId++
to after the external call toprocessor.execute()
. This helps prevent potential reentrancy issues as noted in past review comments.
483-498
: LGTM: Proper callback handling with configurabilityThe callback handling logic is well-designed with the flexibility to either store data in state or just emit events, which can help optimize gas costs based on the use case. The
onlyProcessor
modifier ensures only the trusted processor can trigger callbacks.
WalkthroughThis update introduces a new Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Authorization
participant Processor
participant VerificationGateway
User->>Authorization: sendProcessorMessage(_message)
alt Non-SendMsgs
Authorization->>Authorization: Check admin rights
Authorization->>Processor: Forward message
else SendMsgs (standard)
Authorization->>Authorization: Check user authorization
Authorization->>Processor: Forward message
end
Processor-->>Authorization: handleCallback(callbackData)
Authorization->>Authorization: Store/emit callback
Note over User,Authorization: For ZK flow:
User->>Authorization: executeZKMessage(_message, _proof)
Authorization->>VerificationGateway: verify(registry, proof, message)
VerificationGateway-->>Authorization: Verification result
Authorization->>Processor: Forward message if verified
Poem
Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (4)
solidity/src/verification/VerificationGateway.sol (1)
43-60
: Self-service registry management for users.The
addRegistry
andremoveRegistry
functions allow users to manage their own verification keys without requiring owner intervention, which is a good design for scalability and user autonomy.One potential enhancement to consider:
Consider emitting events when registries are added or removed to improve observability and enable off-chain services to track changes:
function addRegistry(uint64 registry, bytes32 vk) external { programVKs[msg.sender][registry] = vk; + emit RegistryAdded(msg.sender, registry, vk); } function removeRegistry(uint64 registry) external { delete programVKs[msg.sender][registry]; + emit RegistryRemoved(msg.sender, registry); } +// Events should be defined at the contract level +event RegistryAdded(address indexed user, uint64 indexed registry, bytes32 vk); +event RegistryRemoved(address indexed user, uint64 indexed registry);solidity/src/authorization/Authorization.sol (3)
203-205
: Missing validation in updateVerificationGateway.Unlike updateProcessor, the updateVerificationGateway function doesn't validate that the address is not zero. While a zero address might be valid in some cases (to disable ZK verification), this inconsistency should be documented or handled consistently.
Consider either:
- Adding validation if zero address is not allowed:
function updateVerificationGateway(address _verificationGateway) external onlyOwner { + if (_verificationGateway == address(0)) { + revert("Verification gateway cannot be zero address"); + } verificationGateway = VerificationGateway(_verificationGateway); }
- Or add a comment if zero address is intentionally allowed:
function updateVerificationGateway(address _verificationGateway) external onlyOwner { + // Zero address is allowed to disable ZK verification functionality verificationGateway = VerificationGateway(_verificationGateway); }
277-378
: Consider protecting against reentrancy in sendProcessorMessage.The function increments the executionId after calling processor.execute(), which could be vulnerable to reentrancy if the processor is untrusted. While I see from the learnings that "The processor contract is considered trusted within the Valence Protocol ecosystem", it's still better to follow the checks-effects-interactions pattern.
function sendProcessorMessage(bytes calldata _message) external { // ... existing code ... + // Increment the execution ID for the next message + executionId++; // Forward the validated and modified message to the processor processor.execute(message); - // Increment the execution ID for the next message - executionId++; }
459-516
: Consider protecting against reentrancy in executeZKMessage.Similar to sendProcessorMessage, this function updates state after making an external call to processor.execute(). While the processor is trusted, following the checks-effects-interactions pattern would be more defensive.
function executeZKMessage(bytes calldata _message, bytes calldata _proof) external { // ... existing code ... + // Increment the execution ID for the next message + executionId++; + + // Update the last execution block for the registry + zkAuthorizationLastExecutionBlock[decodedZKMessage.registry] = decodedZKMessage.blockNumber; // Execute the message using the processor processor.execute(abi.encode(decodedZKMessage.processorMessage)); - // Increment the execution ID for the next message - executionId++; - - // Update the last execution block for the registry - zkAuthorizationLastExecutionBlock[decodedZKMessage.registry] = decodedZKMessage.blockNumber; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
⛔ Files ignored due to path filters (1)
solidity/soldeer.lock
is excluded by!**/*.lock
📒 Files selected for processing (13)
solidity/foundry.toml
(1 hunks)solidity/remappings.txt
(1 hunks)solidity/src/authorization/Authorization.sol
(1 hunks)solidity/src/libraries/Forwarder.sol
(4 hunks)solidity/src/processor/ProcessorBase.sol
(3 hunks)solidity/src/processor/libs/ProcessorErrors.sol
(1 hunks)solidity/src/processor/libs/ProcessorEvents.sol
(1 hunks)solidity/src/verification/SP1VerificationGateway.sol
(1 hunks)solidity/src/verification/VerificationGateway.sol
(1 hunks)solidity/test/authorization/AuthorizationStandard.t.sol
(1 hunks)solidity/test/authorization/AuthorizationZK.t.sol
(1 hunks)solidity/test/processor/LiteProcessor.t.sol
(0 hunks)solidity/test/processor/Processor.t.sol
(0 hunks)
💤 Files with no reviewable changes (2)
- solidity/test/processor/LiteProcessor.t.sol
- solidity/test/processor/Processor.t.sol
🧰 Additional context used
🧠 Learnings (1)
solidity/src/authorization/Authorization.sol (3)
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:278-278
Timestamp: 2025-05-08T18:52:46.603Z
Learning: The processor contract is considered trusted within the Valence Protocol ecosystem and is not expected to attack the authorizations contract.
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:423-443
Timestamp: 2025-05-09T16:21:07.545Z
Learning: In the Valence Protocol authorization system, permissioned registries typically have very few authorized users (1-2), making array iteration a reasonable approach for authorization checks.
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:423-443
Timestamp: 2025-05-09T16:21:07.545Z
Learning: For small arrays (1-2 elements) in Ethereum contracts, using array iteration is more gas-efficient than creating additional mappings for O(1) lookups, as storage operations are significantly more expensive than a few iterations.
🔇 Additional comments (55)
solidity/remappings.txt (1)
9-9
: New dependency mapping for SP1 ZK proof verificationAdding the remapping for
succinctlabs-sp1-contracts
is necessary to support the new ZK proof verification functionality described in the PR objectives. This correctly maps to the dependency version specified in the foundry.toml file.solidity/foundry.toml (1)
19-19
: Added SP1 contracts dependencyThis dependency addition aligns with the ZK verification functionality mentioned in the PR objectives. The SP1 verification framework will be used to verify zero-knowledge proofs submitted with processor messages.
solidity/src/processor/libs/ProcessorEvents.sol (1)
17-25
: New events for tracking authorization changesThese two new events work well with the Owner role mentioned in the PR objectives, properly documenting when addresses are authorized or deauthorized to interact with the processor. The events are clearly named and well-documented with appropriate comments.
solidity/src/processor/libs/ProcessorErrors.sol (1)
10-12
: New error types for authorization managementThese error types support the authorization functionality described in the PR objectives. They provide clear error messages for scenarios where an address is already authorized or not authorized, improving the contract's error handling and user experience.
solidity/src/libraries/Forwarder.sol (4)
5-5
: Refactoring from Account to BaseAccount ensures consistency across the codebase.The import statement has been updated to use
BaseAccount
instead ofAccount
, which aligns with the broader architectural changes in the system.
40-42
: Type change from Account to BaseAccount in ForwarderConfig struct.The change from
Account
toBaseAccount
for both input and output accounts ensures consistency with the new account abstraction pattern used throughout the codebase.
112-114
: Updated variable types to use BaseAccount.The variable declarations now properly use the
BaseAccount
type, maintaining type consistency with the struct definition.
145-145
: Function parameter types updated to BaseAccount.The parameter types in the
_forwardToken
function have been updated to useBaseAccount
, completing the type refactoring throughout the contract.solidity/src/processor/ProcessorBase.sol (5)
4-4
: Added Ownable inheritance for better access control.The contract now inherits from OpenZeppelin's
Ownable
, which provides standardized ownership management functionality, including ownership transfer capabilities.Also applies to: 12-12
43-46
: Improved documentation for optional Hyperlane integration.The updated comments clearly explain that Hyperlane integration is optional, which aligns with the PR objective of removing the hard requirement for Hyperlane on the processor.
53-53
: Constructor updated to initialize Ownable contract.The constructor now properly initializes the
Ownable
contract withmsg.sender
as the owner, ensuring ownership is set during deployment.
296-314
: Implementation of address authorization management with proper validations.The
addAuthorizedAddress
function properly implements the required validations:
- Prevents authorizing the zero address
- Prevents redundant authorization of already authorized addresses
- Emits an event upon successful authorization
This implementation follows the suggested improvements from previous reviews and aligns with best practices for state-changing operations.
316-329
: Implementation of address deauthorization with proper validations.The
removeAuthorizedAddress
function includes validation to ensure the address is currently authorized before attempting removal, preventing potential confusion from emitting events when no state change occurs.solidity/src/verification/SP1VerificationGateway.sol (1)
1-26
: Well-structured implementation of SP1-specific verification gateway.The contract properly inherits from the abstract
VerificationGateway
and implements a helper methodgetVerifier()
to cast the generic verifier address to the SP1-specific interface. The constructor correctly initializes the base contract.solidity/src/verification/VerificationGateway.sol (3)
1-32
: Well-designed abstract contract for verification gateways.The contract:
- Properly inherits from OpenZeppelin's
Ownable
for access control- Defines clear state variables for coprocessor root and verifier address
- Uses a nested mapping for efficiently storing verification keys by user and registry
- Initializes state in the constructor and sets the deployer as the owner
The design follows best practices for contract inheritance and state management.
34-41
: Owner-restricted verifier update functionality.The
updateVerifier
function properly uses theonlyOwner
modifier to restrict access to the contract owner, ensuring that the critical verifier address cannot be modified by unauthorized parties.
62-70
: Well-defined abstract verification interface.The abstract
verify
function provides a clear contract for derived classes to implement. The function is appropriately marked asvirtual
and has clear documentation about the parameters and expected return value.solidity/test/authorization/AuthorizationZK.t.sol (5)
1-15
: Well-structured ZK authorization test contract.The test contract has a clear structure with a good separation of concerns between registry management and message execution tests. The documentation is thorough and helps explain the purpose of each test section.
38-47
: Good test setup with proper initialization of dependencies.The setUp function correctly initializes the test environment with appropriate mocking and contract deployment. The processor authorization is properly configured to accept messages from the auth contract.
54-105
: Comprehensive registry management test.The test thoroughly validates that registries are correctly added with their verification keys and authorized users. It properly tests both permissioned (specific users) and permissionless (address(0)) access patterns.
107-157
: Well-implemented registry removal test.This test properly verifies that registries can be removed, including checking that the verification key is cleared from the gateway, authorization data is removed, and last execution block is reset.
277-311
: Well-implemented helper functions for test message creation.The helper functions provide a clean way to generate test messages with appropriate parameters, making the tests more readable and maintainable.
solidity/test/authorization/AuthorizationStandard.t.sol (20)
1-17
: Well-structured standard authorization test contract.The test contract provides comprehensive test coverage for the standard (non-ZK) authorization flows with clear documentation.
40-74
: Good test setup with proper initialization and test data.The setUp function initializes the test environment correctly and creates appropriate test data for subsequent tests. The contracts are deployed and configured properly.
78-89
: Good unauthorized access test.This test correctly verifies that unauthorized users cannot send processor messages, reinforcing the security model.
91-121
: Well-structured admin authorization test.This test properly validates that admin users can send administrative messages (Pause/Resume), verifying both state changes and permissions.
123-138
: Good non-admin restriction test.This test ensures that non-admin users cannot send administrative messages, which is an important security constraint.
142-153
: Good processor update test.The test verifies that the processor address can be updated correctly by the owner.
155-159
: Good input validation test for processor update.The test ensures that the processor address cannot be set to the zero address, preventing invalid configurations.
161-172
: Good verification gateway update test.The test verifies that the verification gateway address can be updated correctly by the owner.
174-191
: Good unauthorized callback handler test.This test verifies that only the processor can call the callback handler, preventing potential security issues.
198-202
: Good admin address addition test.The test verifies that admin addresses can be added correctly by the owner.
204-219
: Good admin address removal test.The test thoroughly verifies that admin addresses can be added and then removed correctly.
221-228
: Good unauthorized admin addition test.The test ensures that unauthorized users cannot add admin addresses, preserving the security model.
235-243
: Good standard authorization addition test.The test validates that standard authorizations can be added correctly.
245-257
: Good permissionless authorization test.This test properly verifies that permissionless authorization (using address(0)) works as expected.
259-268
: Good invalid authorization test.The test ensures that authorization arrays with mismatched lengths are rejected, preventing potential issues.
270-287
: Good standard authorization removal test.The test thoroughly verifies that standard authorizations can be added and then removed correctly.
291-316
: Good authorized processor message test.This test comprehensively verifies the entire authorized message flow, including authorization checks, message execution, and callback handling.
318-334
: Good permissionless processor message test.This test verifies that permissionless authorizations allow unauthorized users to send messages, confirming the intended behavior.
342-357
: Well-implemented forwarder configuration helper.The helper function creates appropriate test fixtures for forwarder configurations, making the tests more readable and maintainable.
359-411
: Well-implemented message creation helper.The helper function creates appropriate processor messages with retry logic and atomic subroutines, making the tests more readable and maintainable.
solidity/src/authorization/Authorization.sol (13)
1-31
: Well-structured contract with appropriate inheritance.The contract inherits from Ownable for access control and ICallback for callback handling. The onlyProcessor modifier is correctly implemented to restrict certain functions to be called only by the processor.
33-102
: Good state variable organization and event definitions.The contract clearly organizes its storage variables and events with comprehensive documentation. Events are properly defined with indexed parameters where appropriate.
104-162
: Well-designed authorization storage structures.The contract maintains clear separation between standard and ZK authorization data structures. The multi-dimensional mapping for standard authorizations and the registry-based approach for ZK authorizations are well-designed for their respective use cases.
174-184
: Good constructor with input validation.The constructor properly initializes the contract with required parameters and includes input validation to ensure the processor is not the zero address.
191-196
: Good processor update function with input validation.The updateProcessor function includes proper validation to prevent setting the processor to the zero address.
214-227
: Good admin address management functions.The addAdminAddress and removeAdminAddress functions properly update the admin status and emit appropriate events.
236-266
: Good standard authorization management functions.The addStandardAuthorizations and removeStandardAuthorizations functions include input validation and proper event emissions.
388-402
: Well-implemented authorization check logic.The _checkUserIsAuthorized function correctly handles both specific user authorizations and permissionless authorizations (using address(0)).
413-424
: Good registry management with array validation.The addRegistries function properly validates that input arrays have matching lengths before processing them.
431-439
: Well-implemented registry removal.The removeRegistries function properly cleans up all registry-related data, including verification keys, authorizations, and last execution blocks.
448-450
: Good helper function for retrieving authorization lists.The getZkAuthorizationsList function provides a convenient way to retrieve the list of authorized addresses for a specific registry.
469-478
: Efficient ZK authorization check for small user lists.The loop to check if a sender is authorized is appropriate given the context that "permissioned registries typically have very few authorized users (1-2)" as noted in the learnings. For this scale, array iteration is more gas-efficient than mapping lookups.
525-541
: Good callback handling with configurable storage.The handleCallback function correctly processes callbacks from the processor and provides flexibility with the storeCallbacks flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (1)
solidity/src/authorization/Authorization.sol (1)
379-385
: 🛠️ Refactor suggestionMove executionId increment before processor.execute call
Following the Checks-Effects-Interactions pattern, it's safer to increment the executionId before making external calls. This prevents potential issues if the processor.execute call allows reentrancy.
// Forward the validated and modified message to the processor +// Increment the execution ID for the next message +executionId++; processor.execute(message); -// Increment the execution ID for the next message -executionId++;
🧹 Nitpick comments (8)
solidity/src/verification/VerificationGateway.sol (1)
61-63
: Missing events for registry changesThe addRegistry and removeRegistry functions should emit events to make it easier to track when registries are added or removed, which is important for transparency and off-chain monitoring.
// Add these event declarations to the contract +event RegistryAdded(address indexed user, uint64 indexed registry, bytes32 vk); +event RegistryRemoved(address indexed user, uint64 indexed registry); function addRegistry(uint64 registry, bytes32 vk) external { programVKs[msg.sender][registry] = vk; + emit RegistryAdded(msg.sender, registry, vk); } function removeRegistry(uint64 registry) external { delete programVKs[msg.sender][registry]; + emit RegistryRemoved(msg.sender, registry); }Also applies to: 70-72
solidity/src/authorization/Authorization.sol (7)
181-191
: Constructor could validate the verification gateway addressWhile the constructor checks that the processor address is not zero, it doesn't validate that the verification gateway is valid when provided. Consider validating that it's not the zero address when it's intended to be used.
constructor(address _owner, address _processor, address _verificationGateway, bool _storeCallbacks) Ownable(_owner) { if (_processor == address(0)) { revert("Processor cannot be zero address"); } + if (_verificationGateway != address(0)) { + // Only validate if a non-zero address is provided + // Consider additional checks like verificationGateway.supportsInterface(<interface id>) + } processor = ProcessorBase(_processor); verificationGateway = VerificationGateway(_verificationGateway); executionId = 0; storeCallbacks = _storeCallbacks; }
479-538
: ZK message execution uses logical rather than lexical scopingThe
executeZKMessage
function follows a logical flow but could be improved by organizing the code into lexical scopes for better readability and maintainability:
- Validation checks
- Authorization checks
- Proof verification
- Message execution
- State updates
This would make the function more maintainable and easier to understand.
22-23
: Consider integrating a re-entrancy guardSince the contract makes external calls to the processor, consider adding a re-entrancy guard by inheriting from OpenZeppelin's ReentrancyGuardUpgradeable, especially for functions like sendProcessorMessage and executeZKMessage.
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -contract Authorization is Ownable, ICallback { +contract Authorization is Ownable, ReentrancyGuard, ICallback {
284-385
: Complex sendProcessorMessage function could be refactoredThis function is quite complex and handles multiple message types and subroutine types. Consider breaking it down into smaller, focused functions for better readability and maintainability.
Examples of potential helper functions:
_handleNonSendMsgs
_handleInsertMsgs
_handleSendMsgs
_handleAtomicSubroutine
_handleNonAtomicSubroutine
413-443
: Registry management lacks an update methodThe contract allows adding and removing registries, but there's no way to update existing registries without removing and re-adding them. Consider adding an update function for efficiency.
+/** + * @notice Updates users for an existing registry + * @dev This function allows the owner to update the users for an existing registry + * @param registry The registry ID to update + * @param users The new array of user addresses for the registry + */ +function updateRegistryUsers(uint64 registry, address[] memory users) external onlyOwner { + // Update the users for the registry + zkAuthorizations[registry] = users; +}
152-153
: Consider a more gas-efficient approach for ZK authorizationsThe current implementation stores an array of addresses for each registry, requiring iteration to check authorization. While this works well for small arrays as noted in the previous discussion, consider documenting this design decision in the code comments.
/** * @notice Mapping of what addresses are authorized to send messages for a specific registry ID * @dev This mapping is used to check if a user is authorized to send a message for a specific registry ID * @dev The mapping is structured as follows: * registry ID -> user addresses * If address(0) is used as the user address, it indicates permissionless access + * @dev Using an array instead of a mapping is more gas-efficient for the expected small number of authorized addresses (typically 1-2) */ mapping(uint64 => address[]) public zkAuthorizations;
1-564
: Missing function to toggle storeCallbacks after initializationThe contract initializes the storeCallbacks flag in the constructor but doesn't provide a way to change it later. Consider adding a function to toggle this setting.
+/** + * @notice Toggles whether to store callbacks or just emit events + * @dev Can only be called by the owner + * @param _storeCallbacks Boolean indicating whether to store callbacks + */ +function setStoreCallbacks(bool _storeCallbacks) external onlyOwner { + storeCallbacks = _storeCallbacks; +}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (4)
solidity/src/authorization/Authorization.sol
(1 hunks)solidity/src/verification/SP1VerificationGateway.sol
(1 hunks)solidity/src/verification/VerificationGateway.sol
(1 hunks)solidity/test/authorization/AuthorizationZK.t.sol
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- solidity/test/authorization/AuthorizationZK.t.sol
- solidity/src/verification/SP1VerificationGateway.sol
🔇 Additional comments (2)
solidity/src/verification/VerificationGateway.sol (2)
13-19
: Well-structured base contract for verification gatewaysThe abstract contract properly inherits from the OpenZeppelin upgradeability patterns with clear state variables that provide a solid foundation for specialized verification gateway implementations.
29-32
: Correctly implementing upgradeable contract patternsThe constructor properly disables initializers, following the best practice for OpenZeppelin's upgradeable contracts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
solidity/src/authorization/Authorization.sol (2)
491-496
: Array iteration for ZK authorization checks is appropriateThe authorization check uses a loop through an array instead of a mapping lookup. This is appropriate since, as previously discussed, with small arrays (1-2 elements) this approach is more gas-efficient than using additional mappings which would increase storage costs.
402-404
: Clear implementation of permissionless authorizationThe implementation correctly handles permissionless access by checking for address(0) authorizations. This allows for flexibility in creating permissionless functions while maintaining control over which specific functions can be called without authorization.
As previously noted in review comments, ensure this behavior is clearly documented in your external documentation.
🧹 Nitpick comments (2)
solidity/src/authorization/Authorization.sol (2)
28-29
: Consider using custom errors instead of string literalsUsing string literals for error messages is less gas-efficient than using custom errors with descriptive names. Custom errors are a more modern approach in Solidity and provide better gas optimization.
- if (msg.sender != address(processor)) { - revert("Only processor can call this function"); - } + error OnlyProcessorError(); + // Add this to the top of the contract + + // And change the implementation to: + if (msg.sender != address(processor)) { + revert OnlyProcessorError(); + }
243-254
: Consider adding a single element authorization helper functionFor the common case of adding a single authorization, consider adding a helper function to avoid creating arrays with single elements.
+/** + * @notice Grants authorization for a single user to call a specific function on a specific contract + * @dev Can only be called by the owner + * @param _user Address being granted authorization, if address(0) is used, then it's permissionless + * @param _contract Contract address the user is authorized to interact with + * @param _call Function call data (used to generate hash for authorization checking) + */ +function addStandardAuthorization(address _user, address _contract, bytes memory _call) + external + onlyOwner +{ + bytes32 callHash = keccak256(_call); + authorizations[_user][_contract][callHash] = true; + emit AuthorizationAdded(_user, _contract, callHash); +}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (1)
solidity/src/authorization/Authorization.sol
(1 hunks)
🔇 Additional comments (3)
solidity/src/authorization/Authorization.sol (3)
1-570
: Well-structured and comprehensive authorization contract implementationThis is a well-structured implementation of an authorization contract that provides both standard and ZK-based authorization mechanisms for the Valence Protocol. The code follows good practices with clear separation of concerns, proper access control, and comprehensive event emission.
The code correctly implements the requested features from the PR description:
- Standard authorizations for specific calls
- ZK provable messages with registry-based verification
- Processor authorization management
- Permissionless authorization via address(0)
381-381
: State update after external call follows security best practicesFollowing our previous discussion, you've correctly placed the state update (
executionId++
) after the external call to the processor, implementing the Checks-Effects-Interactions pattern. This is a good security practice even with trusted contracts.
284-385
: Comprehensive authorization and message handling logicThe
sendProcessorMessage
function effectively handles different message types with appropriate authorization checks:
- Admin-only access for non-SendMsgs
- Function-level authorization checks for SendMsgs
- Proper execution ID assignment
- Setting medium priority for all SendMsgs
The implementation follows good practices including:
- Creating a copy of the message for modification
- Proper decoding and encoding
- Careful validation of message structure
- Following Checks-Effects-Interactions pattern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good, left a few questions and pointers, will revisit later today/tomorrow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
solidity/src/authorization/Authorization.sol (3)
250-251
: Enhance validation comment for clarityThe array length validation lacks a clear explanation of why this check is necessary.
- // because for each registry we have a list of users, a verification key and a boolean + // because each registry (i-th element) must have a corresponding entry in each array: + // users[i] contains authorized addresses, calls[i] contains the function data, + // and contracts[i] contains the target contract address
476-480
: Add clarifying comment for array length validationWhile the validation is correct, the comment could be clearer about what exactly is being checked.
require( users.length == registries.length && users.length == vks.length && users.length == validateBlockNumber.length, - "Array lengths must match" + "All arrays must have the same length to ensure complete data for each registry" );
538-545
: Document registry authorization check to prevent bypass concernsThe code correctly checks authorization by registry, but lacks documentation explaining how registry validation prevents authorization bypass.
// Check that sender is authorized to send this message + // We validate authorization based on the registry specified in the message + // This is secure because the message itself is verified by the ZK proof, + // meaning the sender cannot tamper with the registry ID to bypass authorization address[] memory authorizedAddresses = zkAuthorizations[decodedZKMessage.registry]; bool isAuthorized = false; for (uint256 i = 0; i < authorizedAddresses.length; i++) { if (authorizedAddresses[i] == msg.sender || authorizedAddresses[i] == address(0)) { isAuthorized = true; break; } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (1)
solidity/src/authorization/Authorization.sol
(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
solidity/src/authorization/Authorization.sol (4)
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:278-278
Timestamp: 2025-05-08T18:52:46.603Z
Learning: The processor contract is considered trusted within the Valence Protocol ecosystem and is not expected to attack the authorizations contract.
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:423-443
Timestamp: 2025-05-09T16:21:07.573Z
Learning: In the Valence Protocol authorization system, permissioned registries typically have very few authorized users (1-2), making array iteration a reasonable approach for authorization checks.
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:423-443
Timestamp: 2025-05-09T16:21:07.573Z
Learning: For small arrays (1-2 elements) in Ethereum contracts, using array iteration is more gas-efficient than creating additional mappings for O(1) lookups, as storage operations are significantly more expensive than a few iterations.
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:210-212
Timestamp: 2025-05-12T13:30:49.063Z
Learning: The `updateVerificationGateway` function in the `Authorization` contract intentionally does not validate if the new address is a proper verification gateway, as this is a design decision allowing for flexibility in the system. Unlike the processor address which is required for basic functionality, the verification gateway can be disabled by setting it to address(0).
🔇 Additional comments (9)
solidity/src/authorization/Authorization.sol (9)
4-11
: Comprehensive import strategy with appropriate security measuresThe contract correctly imports both functional dependencies (ProcessorBase, verification components) and security primitives (Ownable, ReentrancyGuard) needed for secure operations. The inclusion of ReentrancyGuard is particularly important as it protects against reentrancy attacks when interacting with external contracts.
23-23
: Appropriate inheritance structureThe contract inherits from Ownable for access control, ICallback for processor interaction, and ReentrancyGuard for security protection. This combination provides a solid foundation for the authorization functionality.
124-124
: Well-designed authorization mapping structureThe multi-dimensional mapping provides granular control over which addresses can execute specific functions on specific contracts. The permissionless access support using address(0) adds flexibility but should be used with caution.
210-213
: Verification gateway management allows flexible configurationThe design decision to not validate the verification gateway address is intentional, allowing for flexibility in the system. Unlike the processor which is required for basic functionality, the verification gateway can be disabled by setting it to address(0).
287-308
: Well-structured message processing with proper security controlsThe
sendProcessorMessage
function:
- Correctly uses nonReentrant modifier to prevent reentrancy
- Delegates to specialized helper functions based on message type
- Follows checks-effects-interactions pattern by executing state changes after external calls
This implementation aligns with security best practices.
423-431
: Clean implementation of admin access checkThe _requireAdminAccess function is simple, focused, and reusable across the contract, promoting DRY principles.
583-591
: Correct order of operations follows security best practicesThe code properly follows the Checks-Effects-Interactions pattern by:
- First checking all conditions (authorization, proof verification)
- Then making state changes (incrementing executionId, updating last execution block)
- Finally making external calls (processor.execute)
This ordering protects against potential reentrancy attacks.
601-617
: Well-implemented callback handling with storage optimizationThe handleCallback function correctly:
- Restricts access to only the processor contract
- Conditionally stores callback data based on the storeCallbacks flag, saving gas when storage isn't needed
- Always emits an event for off-chain observability
This implementation balances gas efficiency with functionality.
1-619
: Overall: Robust authorization system with dual authorization pathsThe contract successfully implements a comprehensive authorization system with both standard address-based and ZK proof-based authorization paths. Key strengths include:
- Clear separation of concerns between standard and ZK authorization flows
- Proper access controls for privileged operations
- Support for permissionless access via address(0)
- Replay attack prevention for ZK authorizations
- Comprehensive event emission for tracking changes
- Strong security measures including ReentrancyGuard and proper ordering of operations
The implementation aligns well with the PR objectives of providing flexible authorization options while maintaining security.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
solidity/src/authorization/Authorization.sol (2)
476-480
: Consider more descriptive error messages for array validationWhile the array length validation is correct, the error message could be more specific about which arrays don't match, especially since there are four different arrays being compared.
- require( - users.length == registries.length && users.length == vks.length - && users.length == validateBlockNumber.length, - "Array lengths must match" - ); + require( + users.length == registries.length && users.length == vks.length + && users.length == validateBlockNumber.length, + "Array lengths must match: registries, users, vks, and validateBlockNumber arrays must be the same length" + );
552-560
: Add a clearer error message for replay protectionThe replay protection logic is sound, but the error message could be more descriptive to help users understand why their transaction was rejected.
- if (decodedZKMessage.blockNumber <= zkAuthorizationLastExecutionBlock[decodedZKMessage.registry]) { - revert("Proof no longer valid"); - } + if (decodedZKMessage.blockNumber <= zkAuthorizationLastExecutionBlock[decodedZKMessage.registry]) { + revert("Proof no longer valid: message block number must be greater than the last executed block for this registry"); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (2)
solidity/src/authorization/Authorization.sol
(1 hunks)solidity/test/authorization/AuthorizationZK.t.sol
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- solidity/test/authorization/AuthorizationZK.t.sol
🧰 Additional context used
🧠 Learnings (1)
solidity/src/authorization/Authorization.sol (4)
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:278-278
Timestamp: 2025-05-08T18:52:46.603Z
Learning: The processor contract is considered trusted within the Valence Protocol ecosystem and is not expected to attack the authorizations contract.
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:423-443
Timestamp: 2025-05-09T16:21:07.573Z
Learning: In the Valence Protocol authorization system, permissioned registries typically have very few authorized users (1-2), making array iteration a reasonable approach for authorization checks.
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:423-443
Timestamp: 2025-05-09T16:21:07.573Z
Learning: For small arrays (1-2 elements) in Ethereum contracts, using array iteration is more gas-efficient than creating additional mappings for O(1) lookups, as storage operations are significantly more expensive than a few iterations.
Learnt from: keyleu
PR: timewave-computer/valence-protocol#343
File: solidity/src/authorization/Authorization.sol:210-212
Timestamp: 2025-05-12T13:30:49.063Z
Learning: The `updateVerificationGateway` function in the `Authorization` contract intentionally does not validate if the new address is a proper verification gateway, as this is a design decision allowing for flexibility in the system. Unlike the processor address which is required for basic functionality, the verification gateway can be disabled by setting it to address(0).
🔇 Additional comments (8)
solidity/src/authorization/Authorization.sol (8)
1-23
: Well-structured contract with appropriate imports and inheritanceThe contract is properly structured with relevant imports and necessary inheritance: Ownable for access control, ICallback for processor interactions, and ReentrancyGuard for security. The detailed NatSpec comments provide clear documentation of the contract's purpose and behavior.
211-213
: Intentional absence of verification gateway validation is consistent with project requirementsThe function doesn't validate if the new gateway address is a proper verification gateway, which is intentional as noted in the retrieved learnings. Unlike the processor address which is essential for functionality, the verification gateway can be disabled by setting it to address(0), providing flexibility in the system.
276-278
: Processor is treated as a trusted contractAs noted in the retrieved learnings, the processor contract is considered trusted within the Valence Protocol ecosystem. While external calls typically pose reentrancy risks, this design decision is intentional given the trust relationship between these contracts.
303-307
: Following Checks-Effects-Interactions pattern for reentrancy protectionThe state change (incrementing executionId) occurs after the external call to processor.execute, which correctly follows the Checks-Effects-Interactions pattern. This is a security best practice to prevent potential reentrancy vulnerabilities.
423-455
: Efficient permission checking with address(0) representing permissionless accessThe authorization check correctly handles both specific user authorizations and permissionless access through address(0). This is a powerful but intentional feature allowing for flexible permission models where certain functions can be made available to everyone.
539-546
: Appropriate authorization check for small authorized user listsThe loop-based authorization check is gas-efficient for this use case. As noted in the retrieved learnings, permissioned registries typically have very few authorized users (1-2), making array iteration more gas-efficient than creating additional mappings for O(1) lookups, as storage operations are significantly more expensive than a few iterations.
583-592
: Updated execution flow follows Checks-Effects-Interactions patternThe state updates (incrementing executionId and updating lastExecutionBlock) now occur before the external call to processor.execute, which correctly follows the Checks-Effects-Interactions pattern. This change addresses the concerns raised in previous reviews about potential reentrancy vulnerabilities.
594-619
: Clean callback handling with appropriate access controlThe handleCallback function is properly restricted to the processor contract and correctly processes callback data based on the storeCallbacks flag. The emission of events provides transparency for off-chain monitoring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (2)
solidity/test/authorization/AuthorizationZK.t.sol (2)
368-374
: Missing verification of ZKMessage structure.The test uses a simplified ZKMessage structure with minimal fields, but there should be more tests to verify the handling of different processing message types and contents. Consider expanding the tests to include a variety of message types and contents to ensure proper encoding and handling.
/** * @notice Test ZK message with different processor message types */ function testDifferentZKMessageTypes() public { // Add test setup code here // Test with SendMessage message type bytes memory subroutine = abi.encode("test_subroutine"); IProcessorMessageTypes.ProcessorMessage memory sendMessage = IProcessorMessageTypes.ProcessorMessage({ messageType: IProcessorMessageTypes.ProcessorMessageType.SendMessage, message: subroutine }); Authorization.ZKMessage memory zkSendMessage = Authorization.ZKMessage({ registry: registryId1, blockNumber: uint64(block.number + 1), authorizationContract: address(auth), processorMessage: sendMessage }); bytes memory rootBytes = abi.encodePacked(coprocessorRoot); bytes memory encodedSendMessage = bytes.concat(rootBytes, abi.encode(zkSendMessage)); // Add verification code here // Test with Administrative message type IProcessorMessageTypes.ProcessorMessage memory adminMessage = IProcessorMessageTypes.ProcessorMessage({ messageType: IProcessorMessageTypes.ProcessorMessageType.Administrative, message: abi.encode(uint8(1)) // Example admin action }); Authorization.ZKMessage memory zkAdminMessage = Authorization.ZKMessage({ registry: registryId1, blockNumber: uint64(block.number + 1), authorizationContract: address(auth), processorMessage: adminMessage }); bytes memory encodedAdminMessage = bytes.concat(rootBytes, abi.encode(zkAdminMessage)); // Add verification code here }
258-258
: Consider using more realistic proof data.While
hex"deadbeef"
works as a dummy proof for testing error conditions, consider using more structured mock data that matches the expected proof format for positive test cases when they're added.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (2)
solidity/src/authorization/Authorization.sol
(1 hunks)solidity/test/authorization/AuthorizationZK.t.sol
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- solidity/src/authorization/Authorization.sol
🔇 Additional comments (3)
solidity/test/authorization/AuthorizationZK.t.sol (3)
37-60
: LGTM - Setup function covers necessary components.The setup function properly initializes all the necessary components for testing including the processor, verification gateway with proxy, and the authorization contract.
1-16
: LGTM - Good documentation.The file has clear documentation with proper SPDX license and NatSpec comments explaining the test suite's purpose and focus.
194-196
:⚠️ Potential issueFix array length mismatch in test_RevertWhen_AddingRegistriesWithInvalidArrayLengths.
The
validateBlockNumbers
array has 2 elements, but it should match the length ofusers
array (1 element) to make the error condition consistent with the test intention. Since the test expects a revert due to mismatched array lengths betweenregistries
(2 elements) andusers
(1 element), having a correctly sizedvalidateBlockNumbers
array doesn't align with the test case.-bool[] memory validateBlockNumbers = new bool[](2); -validateBlockNumbers[0] = validateBlockNumber1; -validateBlockNumbers[1] = validateBlockNumber2; +bool[] memory validateBlockNumbers = new bool[](1); +validateBlockNumbers[0] = validateBlockNumber1;Likely an incorrect or invalid review comment.
Authorization contract on EVM, for both standard authorizations (for specific calls) and ZK provable messages.
This contract will be authorized by the processor to send batches to it. The owner of the authorizations contract will allow exact payloads on a specific contract to be sent by a specific address (or permissionless if anyone can send them).
It will also register specific registries (IDs) with a specific Verification Key that will be used to verify the proof and the message sent to the contract. If the proof verification is successful, it means our guest program successfully created a valid proof for the message sent. This message includes, among other things, the processor message to be executed.
For the normal flow:
The user will send a specific ProcessorMessage to the contract and this message will be validated against the current authorizations contract state: it will verify if the user is registered as an admin for admin operations (e.g. pause) or, for normal send operations, it will verify that all the user is allowed to call all the functions in message's subroutine.
For ZK flow:
The user will send the message and the proof. This message contains the registry and block number used during proof generation so we will check that the user sending it is authorized to execute a message for that specific registry. This could be removed by making everything permissionless and doing the signature verification in the guest program, but adding this feature here gives more flexibility to avoid doing signature verification and instead check here.
Additional:
Added an Owner to the processor so that we can add new authorized addresses and remove them (so that we can do it after deploying the contract, not only when deploying, because we won't know the authorization contract before deploying the processor). This processor owner can do just that, and can eventually be transferred or removed.
Additionally removed the hard requirement for Hyperlane on the processor, so that we can just pass address 0 there and then Hyperlane would effectively be disabled.
adding @vlopes11 to review the ZK part according to the discussion we had
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores