-
Notifications
You must be signed in to change notification settings - Fork 24
ERC7579DelayedExecutor - Refactored validation & executing logic #152
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
Changes from all commits
9c5162e
684fb0b
da75b18
28cea0e
b369b55
a4f0cc9
e6239c2
b366789
730dcf3
4007471
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,6 +80,9 @@ abstract contract ERC7579DelayedExecutor is ERC7579Executor { | |
/// @dev Emitted when the expiration delay is updated. | ||
event ERC7579ExecutorExpirationUpdated(address indexed account, uint32 newExpiration); | ||
|
||
/// @dev The module is not installed on the account. | ||
error ERC7579ExecutorModuleNotInstalled(); | ||
|
||
/** | ||
* @dev The current state of a operation is not the expected. The `expectedStates` is a bitmap with the | ||
* bits enabled for each OperationState enum position counting from right to left. See {_encodeStateBitmap}. | ||
|
@@ -122,72 +125,106 @@ abstract contract ERC7579DelayedExecutor is ERC7579Executor { | |
return OperationState.Ready; | ||
} | ||
|
||
/// @dev See {ERC7579Executor-canExecute}. Allows anyone to execute an operation if it's `Ready`. | ||
function canExecute( | ||
/** | ||
* @dev See {ERC7579Executor-_validateExecution}. Validates that the operation is in `Ready` state. | ||
* | ||
* Derived contracts can override this function to add additional validation logic. | ||
* | ||
* Example extension: | ||
* | ||
* ```solidity | ||
* function _validateExecution( | ||
* address account, | ||
* Mode mode, | ||
* bytes calldata executionCalldata, | ||
* bytes32 salt | ||
* ) internal view virtual override { | ||
* bool conditionMet = ...; // custom logic to check condition | ||
* require(conditionMet, ERC7579ExecutorConditionNotMet()); | ||
* super._validateExecution(account, mode, executionCalldata, salt); | ||
* } | ||
*``` | ||
*/ | ||
function _validateExecution( | ||
address account, | ||
Mode mode, | ||
bytes calldata executionCalldata, | ||
bytes32 salt | ||
) public view virtual override returns (bool) { | ||
return | ||
state(account, mode, executionCalldata, salt) == OperationState.Ready || | ||
super.canExecute(account, mode, executionCalldata, salt); | ||
) internal view virtual override { | ||
bytes32 id = hashOperation(account, mode, executionCalldata, salt); | ||
_validateStateBitmap(id, _encodeStateBitmap(OperationState.Ready)); | ||
|
||
super._validateExecution(account, mode, executionCalldata, salt); | ||
} | ||
|
||
/** | ||
* @dev Whether the caller is authorized to cancel operations. | ||
* By default, this checks if the caller is the account itself. Derived contracts can | ||
* override this to implement custom authorization logic. | ||
* @dev Validates cancellation. This base implementation validates the caller | ||
* is authorized to cancel and that the operation is in a valid state. | ||
* | ||
* Derived contracts can override this function to add additional validation logic. | ||
* | ||
* Example extension: | ||
* | ||
* ```solidity | ||
* function canCancel( | ||
* function _validateCancel( | ||
* address account, | ||
* Mode mode, | ||
* bytes calldata executionCalldata, | ||
* bytes32 salt | ||
* ) public view virtual returns (bool) { | ||
* bool isAuthorized = ...; // custom logic to check authorization | ||
* return isAuthorized || super.canCancel(account, mode, executionCalldata, salt); | ||
* ) internal view virtual override { | ||
* bool conditionMet = ...; // custom logic to check additional condition | ||
* require(conditionMet, ERC7579ExecutorConditionNotMet()); | ||
* super._validateCancel(account, mode, executionCalldata, salt); | ||
* } | ||
*``` | ||
*/ | ||
function canCancel( | ||
function _validateCancel( | ||
address account, | ||
Mode /* mode */, | ||
bytes calldata /* executionCalldata */, | ||
bytes32 /* salt */ | ||
) public view virtual returns (bool) { | ||
return account == msg.sender; | ||
Mode mode, | ||
bytes calldata executionCalldata, | ||
bytes32 salt | ||
) internal view virtual { | ||
bytes32 id = hashOperation(account, mode, executionCalldata, salt); | ||
bytes32 allowedStates = _encodeStateBitmap(OperationState.Scheduled) | _encodeStateBitmap(OperationState.Ready); | ||
_validateStateBitmap(id, allowedStates); | ||
|
||
require(msg.sender == account, ERC7579ExecutorUnauthorizedCancellation()); | ||
} | ||
|
||
/** | ||
* @dev Whether the caller is authorized to schedule operations. | ||
* By default, this checks if the caller is the account itself. Derived contracts can | ||
* override this to implement custom authorization logic. | ||
* @dev Validates scheduling. This base implementation validates the caller | ||
* is authorized to schedule, the module is installed, and the operation was | ||
* not scheduled before. | ||
* | ||
* Can be overridden by derived contracts to implement custom validation logic. | ||
* | ||
* Example extension: | ||
* | ||
* ```solidity | ||
* function canSchedule( | ||
* function _validateSchedule( | ||
* address account, | ||
* Mode mode, | ||
* bytes calldata executionCalldata, | ||
* bytes32 salt | ||
* ) public view virtual returns (bool) { | ||
* bool isAuthorized = ...; // custom logic to check authorization | ||
* return isAuthorized || super.canSchedule(account, mode, executionCalldata, salt); | ||
* ) internal view virtual override { | ||
* bool conditionMet = ...; // custom logic to check condition | ||
* require(conditionMet, ERC7579ExecutorConditionNotMet()); | ||
* super._validateSchedule(account, mode, executionCalldata, salt); | ||
* } | ||
*``` | ||
*/ | ||
function canSchedule( | ||
function _validateSchedule( | ||
address account, | ||
Mode /* mode */, | ||
bytes calldata /* executionCalldata */, | ||
bytes32 /* salt */ | ||
) public view virtual returns (bool) { | ||
return account == msg.sender; | ||
Mode mode, | ||
bytes calldata executionCalldata, | ||
bytes32 salt | ||
) internal view virtual { | ||
require(_config[account].installed, ERC7579ExecutorModuleNotInstalled()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would insist on keeping this case as a no-op. It's a valid use case to extend and allow scheduling on contracts that haven't installed the module. For example, a factory contract could deploy accounts with create2 and install this module with pre-scheduled payments (ie. subscriptions). In that case it becomes impossible to build that case on top of this contract |
||
|
||
bytes32 id = hashOperation(account, mode, executionCalldata, salt); | ||
_validateStateBitmap(id, _encodeStateBitmap(OperationState.Unknown)); | ||
|
||
require(msg.sender == account, ERC7579ExecutorUnauthorizedSchedule()); | ||
} | ||
|
||
/// @dev Minimum delay after which {setDelay} takes effect. | ||
|
@@ -293,22 +330,19 @@ abstract contract ERC7579DelayedExecutor is ERC7579Executor { | |
/** | ||
* @dev Schedules an operation to be executed after the account's delay period (see {getDelay}). | ||
* Operations are uniquely identified by the combination of `mode`, `executionCalldata`, and `salt`. | ||
* See {canSchedule} for authorization checks. | ||
* See {_validateSchedule} for validation. | ||
*/ | ||
function schedule(address account, Mode mode, bytes calldata executionCalldata, bytes32 salt) public virtual { | ||
bool allowed = canSchedule(account, mode, executionCalldata, salt); | ||
_schedule(account, mode, executionCalldata, salt); // Prioritize errors thrown in _schedule | ||
require(allowed, ERC7579ExecutorUnauthorizedSchedule()); | ||
_validateSchedule(account, mode, executionCalldata, salt); | ||
_schedule(account, mode, executionCalldata, salt); | ||
} | ||
|
||
/** | ||
* @dev Cancels a previously scheduled operation. Can only be called by the account that | ||
* scheduled the operation. See {_cancel}. | ||
* @dev Cancels a scheduled operation. See {_validateCancel} for validation. | ||
*/ | ||
function cancel(address account, Mode mode, bytes calldata executionCalldata, bytes32 salt) public virtual { | ||
bool allowed = canCancel(account, mode, executionCalldata, salt); | ||
_cancel(account, mode, executionCalldata, salt); // Prioritize errors thrown in _cancel | ||
require(allowed, ERC7579ExecutorUnauthorizedCancellation()); | ||
_validateCancel(account, mode, executionCalldata, salt); | ||
_cancel(account, mode, executionCalldata, salt); | ||
} | ||
|
||
/** | ||
|
@@ -354,42 +388,27 @@ abstract contract ERC7579DelayedExecutor is ERC7579Executor { | |
} | ||
|
||
/** | ||
* @dev Internal version of {schedule} that takes an `account` address as an argument. | ||
* | ||
* Requirements: | ||
* | ||
* * The operation must be `Unknown`. | ||
* @dev Schedules an operation. Does not perform any validation checks. | ||
* | ||
* Emits an {ERC7579ExecutorOperationScheduled} event. | ||
* | ||
* NOTE: Calling this function directly will NOT perform any validation checks. | ||
* Scheduling a operation should be done using {schedule}. | ||
*/ | ||
function _schedule( | ||
address account, | ||
Mode mode, | ||
bytes calldata executionCalldata, | ||
bytes32 salt | ||
) internal virtual returns (bytes32 operationId, Schedule memory schedule_) { | ||
function _schedule(address account, Mode mode, bytes calldata executionCalldata, bytes32 salt) internal virtual { | ||
bytes32 id = hashOperation(account, mode, executionCalldata, salt); | ||
_validateStateBitmap(id, _encodeStateBitmap(OperationState.Unknown)); | ||
|
||
(uint32 executableAfter, , ) = getDelay(account); | ||
|
||
uint48 timepoint = Time.timestamp(); | ||
|
||
_schedules[id].scheduledAt = timepoint; | ||
_schedules[id].executableAfter = executableAfter; | ||
_schedules[id].expiresAfter = getExpiration(account); | ||
|
||
emit ERC7579ExecutorOperationScheduled(account, id, mode, executionCalldata, salt, timepoint); | ||
return (id, schedule_); | ||
} | ||
|
||
/** | ||
* @dev See {ERC7579Executor-_execute}. | ||
* | ||
* Requirements: | ||
* | ||
* * The operation must be `Ready`. | ||
* | ||
* NOTE: Anyone can trigger execution once the operation is `Ready`. See {canExecute}. | ||
* @inheritdoc ERC7579Executor | ||
*/ | ||
function _execute( | ||
address account, | ||
|
@@ -398,26 +417,24 @@ abstract contract ERC7579DelayedExecutor is ERC7579Executor { | |
bytes32 salt | ||
) internal virtual override returns (bytes[] memory returnData) { | ||
bytes32 id = hashOperation(account, mode, executionCalldata, salt); | ||
_validateStateBitmap(id, _encodeStateBitmap(OperationState.Ready)); | ||
gonzaotc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
_schedules[id].executed = true; | ||
|
||
return super._execute(account, mode, executionCalldata, salt); | ||
} | ||
|
||
/** | ||
* @dev Internal version of {cancel} that takes an `account` address as an argument. | ||
* @dev Cancels an operation. | ||
* | ||
* Requirements: | ||
* Emits an {ERC7579ExecutorOperationCanceled} event. | ||
* | ||
* * The operation must be `Scheduled` or `Ready`. | ||
* NOTE: Calling this function directly will NOT perform any validation checks. | ||
* Cancelling a operation should be done using {cancel}. | ||
* | ||
* Canceled operations can't be rescheduled. Emits an {ERC7579ExecutorOperationCanceled} event. | ||
* NOTE: Canceled operations can't be rescheduled. | ||
*/ | ||
function _cancel(address account, Mode mode, bytes calldata executionCalldata, bytes32 salt) internal virtual { | ||
bytes32 id = hashOperation(account, mode, executionCalldata, salt); | ||
bytes32 allowedStates = _encodeStateBitmap(OperationState.Scheduled) | _encodeStateBitmap(OperationState.Ready); | ||
_validateStateBitmap(id, allowedStates); | ||
|
||
_schedules[id].canceled = true; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.27; | ||
|
||
import {Mode} from "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; | ||
import {ERC7579Executor} from "../../../account/modules/ERC7579Executor.sol"; | ||
|
||
contract ERC7579ExecutorMock is ERC7579Executor { | ||
function onInstall(bytes calldata) public virtual {} | ||
|
||
function onUninstall(bytes calldata) public virtual {} | ||
|
||
function _validateExecution( | ||
address account, | ||
Mode /* mode */, | ||
bytes calldata /* executionCalldata */, | ||
bytes32 /* salt */ | ||
) internal view override { | ||
if (account != msg.sender) revert ERC7579UnauthorizedExecution(); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.