-
Notifications
You must be signed in to change notification settings - Fork 7
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
Merged
Merged
Changes from 2 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
0277af3
authorization contract v0
keyleu 40e25a1
unit and e2e tests for authorizations contract
keyleu 34d3bc2
clean up and suggestions
keyleu 85bb59d
Merge branch 'main' into keyne/evm-authorization
keyleu f54c373
zk authorizations
keyleu 2beab7d
add suggested events
keyleu f99d2cf
add flag for block number execution validation
keyleu d682b97
make gateways upgradeable
keyleu 432d3fc
suggestions
keyleu e2b8562
optimization of state write
keyleu 36bf4ca
suggestions
keyleu 71e3f82
Merge branch 'main' into keyne/evm-authorization
keyleu 7014e5c
remove the first 32 bytes from the message before decoding
keyleu 955e4b8
add feature to check authorization contract executing the message
keyleu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,326 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity ^0.8.28; | ||
|
||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import {ProcessorBase} from "../processor/ProcessorBase.sol"; | ||
import {IProcessorMessageTypes} from "../processor/interfaces/IProcessorMessageTypes.sol"; | ||
import {ProcessorMessageDecoder} from "../processor/libs/ProcessorMessageDecoder.sol"; | ||
import {ICallback} from "../processor/interfaces/ICallback.sol"; | ||
import {IProcessor} from "../processor/interfaces/IProcessor.sol"; | ||
|
||
/** | ||
* @title Authorization | ||
* @dev This contract manages authorizations for interactions with a processor contract. | ||
* It provides mechanisms for both standard address-based authorizations and ZK proof-based authorizations. | ||
* @notice The Authorization contract acts as a middleware for managing access control | ||
* to the Processor contract. It controls which addresses can call specific functions | ||
* on specific contracts through the processor. | ||
*/ | ||
contract Authorization is Ownable, ICallback { | ||
// Address of the processor that we will forward batches to | ||
ProcessorBase public processor; | ||
|
||
/** | ||
* @notice Boolean indicating whether to store callbacks or just emit events for them | ||
* @dev If true, the contract will store callback data in the contract's state | ||
*/ | ||
bool public storeCallbacks; | ||
|
||
/** | ||
* @notice Event emitted when a callback is received from the processor | ||
* @dev This event is emitted when the processor sends a callback after executing a message | ||
* @param executionId The ID of the executed message | ||
* @param executionResult The result of the execution (success or failure) | ||
* @param executedCount The number of successfully executed functions | ||
* @param data Additional data related to the callback execution | ||
*/ | ||
event CallbackReceived( | ||
uint64 indexed executionId, IProcessor.ExecutionResult executionResult, uint64 executedCount, bytes data | ||
); | ||
|
||
/** | ||
* @notice Callback data structure for processor callbacks | ||
* @dev This struct is used to store the callback data received from the processor | ||
* @param executionResult The result of the execution | ||
* @param executedCount The number of successfully executed functions | ||
* @param data Additional data related to the callback execution | ||
*/ | ||
struct ProcessorCallback { | ||
IProcessor.ExecutionResult executionResult; | ||
uint64 executedCount; | ||
bytes data; | ||
} | ||
|
||
/** | ||
* @notice Mapping of execution IDs to callback data | ||
* @dev This mapping stores the callback data for each execution ID | ||
* Key: execution ID, Value: Callback information | ||
*/ | ||
mapping(uint64 => ProcessorCallback) public callbacks; | ||
|
||
/** | ||
* @notice Current execution ID for tracking message execution | ||
* @dev This ID is incremented with each message processed and helps track message sequence | ||
*/ | ||
uint64 public executionId; | ||
|
||
// ========================= Standard authorizations ========================= | ||
|
||
/** | ||
* @notice Mapping of addresses that are allowed to perform admin operations | ||
* @dev Admin addresses can perform privileged operations like pausing/unpausing | ||
*/ | ||
mapping(address => bool) public adminAddresses; | ||
|
||
/** | ||
* @notice Multi-dimensional mapping for granular authorization control | ||
* @dev Maps from user address -> contract address -> function signature hash -> boolean | ||
* If address(0) is used as the user address, it indicates permissionless access | ||
* Represents the operations a specific address can execute on a specific contract | ||
*/ | ||
mapping(address => mapping(address => mapping(bytes32 => bool))) public authorizations; | ||
|
||
// ========================= ZK authorizations ========================= | ||
|
||
/** | ||
* @notice Address of the verifier contract used for zero-knowledge proof verification | ||
* @dev If zero-knowledge proofs are not being used, this can be set to address(0) | ||
*/ | ||
address public verifier; | ||
|
||
/** | ||
* @notice Sets up the Authorization contract with initial configuration | ||
* @dev Initializes the contract with owner, processor, and optional verifier | ||
* @param _owner Address that will be set as the owner of this contract | ||
* @param _processor Address of the processor contract that will execute messages | ||
* @param _verifier Address of the ZK verifier contract (can be address(0) if not using ZK proofs) | ||
* @param _storeCallbacks Boolean indicating whether to store callbacks or just emitting events | ||
*/ | ||
constructor(address _owner, address _processor, address _verifier, bool _storeCallbacks) Ownable(_owner) { | ||
if (_processor == address(0)) { | ||
revert("Processor cannot be zero address"); | ||
} | ||
processor = ProcessorBase(_processor); | ||
verifier = _verifier; | ||
executionId = 0; | ||
storeCallbacks = _storeCallbacks; | ||
} | ||
|
||
/** | ||
* @notice Updates the processor contract address | ||
* @dev Can only be called by the owner | ||
* @param _processor New processor contract address | ||
*/ | ||
function updateProcessor(address _processor) external onlyOwner { | ||
if (_processor == address(0)) { | ||
revert("Processor cannot be zero address"); | ||
} | ||
processor = ProcessorBase(_processor); | ||
} | ||
|
||
/** | ||
* @notice Updates the ZK verifier contract address | ||
* @dev Can only be called by the owner | ||
* @param _verifier New verifier contract address | ||
*/ | ||
function updateVerifier(address _verifier) external onlyOwner { | ||
verifier = _verifier; | ||
} | ||
|
||
/** | ||
* @notice Adds an address to the list of admin addresses | ||
* @dev Can only be called by the owner | ||
* @param _admin Address to be granted admin privileges | ||
*/ | ||
function addAdminAddress(address _admin) external onlyOwner { | ||
adminAddresses[_admin] = true; | ||
} | ||
|
||
/** | ||
* @notice Removes an address from the list of admin addresses | ||
* @dev Can only be called by the owner | ||
* @param _admin Address to have admin privileges revoked | ||
*/ | ||
function removeAdminAddress(address _admin) external onlyOwner { | ||
delete adminAddresses[_admin]; | ||
} | ||
|
||
/** | ||
* @notice Grants authorization for a user to call a specific function on a specific contract | ||
* @dev Can only be called by the owner | ||
* @param _user Address of the user being granted authorization, if address(0) is used, then it's permissionless | ||
* @param _contract Address of the contract the user is authorized to interact with | ||
* @param _call Function call data (used to generate a hash for authorization checking) | ||
*/ | ||
function addStandardAuthorization(address _user, address _contract, bytes memory _call) external onlyOwner { | ||
authorizations[_user][_contract][keccak256(_call)] = true; | ||
} | ||
|
||
/** | ||
* @notice Revokes authorization for a user to call a specific function on a specific contract | ||
* @dev Can only be called by the owner | ||
* @param _user Address of the user having authorization revoked | ||
* @param _contract Address of the contract the authorization applies to | ||
* @param call Function call data (used to generate the hash for lookup) | ||
*/ | ||
function removeStandardAuthorization(address _user, address _contract, bytes memory call) external onlyOwner { | ||
delete authorizations[_user][_contract][keccak256(call)]; | ||
} | ||
|
||
/** | ||
* @notice Main function to send messages to the processor after authorization checks | ||
* @dev Handles various message types differently: | ||
* - For SendMsgs: Checks authorization for each function call | ||
* - For InsertMsgs: Requires admin access and sets execution ID | ||
* - For other types: Requires admin access | ||
* @param _message Encoded processor message to be executed | ||
*/ | ||
function sendProcessorMessage(bytes calldata _message) external { | ||
// Make a copy of the message to apply modifications | ||
bytes memory message = _message; | ||
|
||
// Decode the message to check authorization and apply modifications | ||
IProcessorMessageTypes.ProcessorMessage memory decodedMessage = ProcessorMessageDecoder.decode(message); | ||
|
||
// Handle different message types with different authorization requirements | ||
if (decodedMessage.messageType != IProcessorMessageTypes.ProcessorMessageType.SendMsgs) { | ||
bekauz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// For non-SendMsgs messages, only admin addresses are authorized | ||
if (!adminAddresses[msg.sender]) { | ||
revert("Unauthorized access"); | ||
} | ||
|
||
// Special handling for InsertMsgs to set execution ID | ||
if (decodedMessage.messageType == IProcessorMessageTypes.ProcessorMessageType.InsertMsgs) { | ||
IProcessorMessageTypes.InsertMsgs memory insertMsgs = | ||
abi.decode(decodedMessage.message, (IProcessorMessageTypes.InsertMsgs)); | ||
|
||
// Set the execution ID of the message | ||
insertMsgs.executionId = executionId; | ||
|
||
// Encode the message back after modification | ||
decodedMessage.message = abi.encode(insertMsgs); | ||
|
||
// Encode the processor message back to bytes | ||
message = abi.encode(decodedMessage); | ||
} | ||
} else { | ||
// For SendMsgs, check function-level authorizations | ||
|
||
// Decode the SendMsgs message | ||
IProcessorMessageTypes.SendMsgs memory sendMsgs = | ||
abi.decode(decodedMessage.message, (IProcessorMessageTypes.SendMsgs)); | ||
|
||
// Handle different subroutine types (Atomic vs NonAtomic) | ||
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"); | ||
} | ||
} | ||
} | ||
bekauz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Force the priority to Medium for all SendMsgs | ||
sendMsgs.priority = IProcessorMessageTypes.Priority.Medium; | ||
|
||
// Set the execution ID of the message | ||
sendMsgs.executionId = executionId; | ||
|
||
// Encode the message back after modifications | ||
decodedMessage.message = abi.encode(sendMsgs); | ||
|
||
// Encode the processor message back to bytes | ||
message = abi.encode(decodedMessage); | ||
} | ||
|
||
// Increment the execution ID for the next message | ||
executionId++; | ||
|
||
// Forward the validated and modified message to the processor | ||
processor.execute(message); | ||
keyleu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
* @notice Checks if a user is authorized to execute a specific call on a specific contract | ||
* @dev Uses the authorizations mapping to perform the check | ||
* @param _user Address of the user to check authorization for | ||
* @param _contract Address of the contract being called | ||
* @param _call Function call data (used to generate the hash for lookup) | ||
* @return bool True if the user is authorized, false otherwise | ||
*/ | ||
function _checkUserIsAuthorized(address _user, address _contract, bytes memory _call) | ||
bekauz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
internal | ||
view | ||
returns (bool) | ||
{ | ||
// Check if the user is authorized to call the contract with the given call | ||
if (authorizations[_user][_contract][keccak256(_call)]) { | ||
return true; | ||
} else if (authorizations[address(0)][_contract][keccak256(_call)]) { | ||
// If address(0) is used, it indicates permissionless access | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
function handleCallback(bytes memory callbackData) external override { | ||
// Check that the sender is the processor | ||
if (msg.sender != address(processor)) { | ||
revert("Only processor can send callbacks"); | ||
} | ||
// Decode the callback data | ||
IProcessor.Callback memory callback = abi.decode(callbackData, (IProcessor.Callback)); | ||
|
||
// Store the callback data if storeCallbacks is true | ||
if (storeCallbacks) { | ||
callbacks[callback.executionId] = ProcessorCallback({ | ||
executionResult: callback.executionResult, | ||
executedCount: uint64(callback.executedCount), | ||
data: callback.data | ||
}); | ||
} | ||
|
||
emit CallbackReceived( | ||
callback.executionId, callback.executionResult, uint64(callback.executedCount), callback.data | ||
); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.