Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions contracts/mocks/DispatchModuleMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

contract DispatchModuleMock {
event Call(address sender, uint256 value, bytes data);

fallback() external payable {
emit Call(msg.sender, msg.value, msg.data);
}
}
1 change: 1 addition & 0 deletions contracts/mocks/import.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ import {
} from "@openzeppelin/contracts/mocks/account/AccountMock.sol";
import {ERC1271WalletMock} from "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol";
import {CallReceiverMock} from "@openzeppelin/contracts/mocks/CallReceiverMock.sol";
import {EtherReceiverMock} from "@openzeppelin/contracts/mocks/EtherReceiverMock.sol";
import {ERC7913P256Verifier} from "@openzeppelin/contracts/utils/cryptography/verifiers/ERC7913P256Verifier.sol";
import {ERC7913RSAVerifier} from "@openzeppelin/contracts/utils/cryptography/verifiers/ERC7913RSAVerifier.sol";
37 changes: 37 additions & 0 deletions contracts/proxy/dispatch/DispatchProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol";
import {DispatchUpdateModule} from "./modules/DispatchUpdateModule.sol";
import {Dispatch} from "./utils/Dispatch.sol";

/**
* @title DispatchProxy
* @dev TODO
*/
contract DispatchProxy is Proxy {
using Dispatch for Dispatch.VMT;

bytes4 private constant _FALLBACK_SIG = 0xffffffff;

error DispatchProxyMissingImplementation(bytes4 selector);

constructor(address updateFacet, address initialOwner) {
Dispatch.VMT storage store = Dispatch.instance();
store.setOwner(initialOwner);
store.setFunction(DispatchUpdateModule.updateDispatchTable.selector, updateFacet);
}

function _implementation() internal view virtual override returns (address module) {
Dispatch.VMT storage store = Dispatch.instance();

module = store.getFunction(msg.sig);
if (module != address(0)) return module;

module = store.getFunction(_FALLBACK_SIG);
if (module != address(0)) return module;

revert DispatchProxyMissingImplementation(msg.sig);
}
}
27 changes: 27 additions & 0 deletions contracts/proxy/dispatch/interfaces/IDiamondCut.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IDiamondCut {
enum FacetCutAction {
Add,
Replace,
Remove
}

struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}

event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);

/// @notice Add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external;
}
29 changes: 29 additions & 0 deletions contracts/proxy/dispatch/interfaces/IDiamondLoupe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IDiamondLoupe {
struct Facet {
address facetAddress;
bytes4[] functionSelectors;
}

/// @notice Gets all facet addresses and their four byte function selectors.
/// @return facets_ Facet
function facets() external view returns (Facet[] memory facets_);

/// @notice Gets all the function selectors supported by a specific facet.
/// @param _facet The facet address.
/// @return facetFunctionSelectors_
function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);

/// @notice Get all the facet addresses used by a diamond.
/// @return facetAddresses_
function facetAddresses() external view returns (address[] memory facetAddresses_);

/// @notice Gets the facet that supports the given selector.
/// @dev If facet is not found return address(0).
/// @param _functionSelector The function selector.
/// @return facetAddress_ The facet address.
function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}
42 changes: 42 additions & 0 deletions contracts/proxy/dispatch/modules/DiamondCutFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {IDiamondCut} from "../interfaces/IDiamondCut.sol";
import {Dispatch} from "../utils/Dispatch.sol";

/// @custom:stateless
contract DiamondCutFacet is Context, IDiamondCut {
using Dispatch for Dispatch.VMT;

error DiamondCutFacetAlreadyExist(bytes4 selector);
error DiamondCutFacetAlreadySet(bytes4 selector);
error DiamondCutFacetAlreadyDoesNotExit(bytes4 selector);

function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) public override {
Dispatch.VMT storage store = Dispatch.instance();

store.enforceOwner(_msgSender());
for (uint256 i = 0; i < _diamondCut.length; ++i) {
FacetCut memory facetcut = _diamondCut[i];
for (uint256 j = 0; j < facetcut.functionSelectors.length; ++j) {
bytes4 selector = facetcut.functionSelectors[j];
address currentFacet = store.getFunction(selector);
if (facetcut.action == FacetCutAction.Add && currentFacet != address(0)) {
revert DiamondCutFacetAlreadyExist(selector);
} else if (facetcut.action == FacetCutAction.Replace && currentFacet != facetcut.facetAddress) {
revert DiamondCutFacetAlreadySet(selector);
} else if (facetcut.action == FacetCutAction.Remove && currentFacet == address(0)) {
revert DiamondCutFacetAlreadyDoesNotExit(selector);
}
store.setFunction(selector, facetcut.facetAddress);
}
}
Comment on lines +22 to +36
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix critical validation and update logic errors.

The implementation has multiple critical issues:

  1. Replace validation is incomplete (lines 29-30): It only checks if the new facet differs from the current one but doesn't verify that a facet exists at all. This allows "replacing" non-existent selectors, which should fail or be treated as Add.

  2. Remove action uses wrong address (line 34): For FacetCutAction.Remove, the function should set the selector to address(0), not facetcut.facetAddress. The current code would set it to whatever address is in the struct, which is incorrect.

Apply this diff to fix both issues:

                 bytes4 selector = facetcut.functionSelectors[j];
                 address currentFacet = store.getFunction(selector);
                 if (facetcut.action == FacetCutAction.Add && currentFacet != address(0)) {
                     revert DiamondCutFacetAlreadyExist(selector);
-                } else if (facetcut.action == FacetCutAction.Replace && currentFacet != facetcut.facetAddress) {
+                } else if (facetcut.action == FacetCutAction.Replace && (currentFacet == address(0) || currentFacet == facetcut.facetAddress)) {
                     revert DiamondCutFacetAlreadySet(selector);
                 } else if (facetcut.action == FacetCutAction.Remove && currentFacet == address(0)) {
                     revert DiamondCutFacetAlreadyDoesNotExit(selector);
                 }
-                store.setFunction(selector, facetcut.facetAddress);
+                store.setFunction(
+                    selector,
+                    facetcut.action == FacetCutAction.Remove ? address(0) : facetcut.facetAddress
+                );
📝 Committable suggestion

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

Suggested change
for (uint256 i = 0; i < _diamondCut.length; ++i) {
FacetCut memory facetcut = _diamondCut[i];
for (uint256 j = 0; j < facetcut.functionSelectors.length; ++j) {
bytes4 selector = facetcut.functionSelectors[j];
address currentFacet = store.getFunction(selector);
if (facetcut.action == FacetCutAction.Add && currentFacet != address(0)) {
revert DiamondCutFacetAlreadyExist(selector);
} else if (facetcut.action == FacetCutAction.Replace && currentFacet != facetcut.facetAddress) {
revert DiamondCutFacetAlreadySet(selector);
} else if (facetcut.action == FacetCutAction.Remove && currentFacet == address(0)) {
revert DiamondCutFacetAlreadyDoesNotExit(selector);
}
store.setFunction(selector, facetcut.facetAddress);
}
}
for (uint256 i = 0; i < _diamondCut.length; ++i) {
FacetCut memory facetcut = _diamondCut[i];
for (uint256 j = 0; j < facetcut.functionSelectors.length; ++j) {
bytes4 selector = facetcut.functionSelectors[j];
address currentFacet = store.getFunction(selector);
if (facetcut.action == FacetCutAction.Add && currentFacet != address(0)) {
revert DiamondCutFacetAlreadyExist(selector);
} else if (
facetcut.action == FacetCutAction.Replace &&
(currentFacet == address(0) || currentFacet == facetcut.facetAddress)
) {
revert DiamondCutFacetAlreadySet(selector);
} else if (facetcut.action == FacetCutAction.Remove && currentFacet == address(0)) {
revert DiamondCutFacetAlreadyDoesNotExit(selector);
}
store.setFunction(
selector,
facetcut.action == FacetCutAction.Remove
? address(0)
: facetcut.facetAddress
);
}
}
🤖 Prompt for AI Agents
In contracts/proxy/dispatch/modules/DiamondCutFacet.sol around lines 22 to 36,
the Replace validation only checks equality against the new facet but doesn't
ensure a current facet exists, and the Remove branch incorrectly writes
facetcut.facetAddress instead of clearing the selector. Change the Replace logic
to first require currentFacet != address(0) (revert if missing) and also revert
if currentFacet == facetcut.facetAddress (already set), then call
store.setFunction with the new facet as before; for Remove, keep the existence
check but call store.setFunction(selector, address(0)) to clear the selector
instead of writing facetcut.facetAddress.


emit DiamondCut(_diamondCut, _init, _calldata);

Address.functionCall(_init, _calldata);
}
Comment on lines +38 to +41
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix initialization call logic and use delegatecall.

Two critical issues:

  1. Missing zero-address check: Address.functionCall will revert if _init is address(0). According to the Diamond standard, initialization should be optional and skipped when _init is zero.

  2. Wrong call type: The function should use functionDelegateCall instead of functionCall, as specified in the IDiamondCut interface documentation (line 25: "_calldata is executed with delegatecall on _init").

Apply this diff:

         emit DiamondCut(_diamondCut, _init, _calldata);
 
-        Address.functionCall(_init, _calldata);
+        if (_init != address(0)) {
+            Address.functionDelegateCall(_init, _calldata);
+        }
📝 Committable suggestion

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

Suggested change
emit DiamondCut(_diamondCut, _init, _calldata);
Address.functionCall(_init, _calldata);
}
emit DiamondCut(_diamondCut, _init, _calldata);
if (_init != address(0)) {
Address.functionDelegateCall(_init, _calldata);
}
🤖 Prompt for AI Agents
In contracts/proxy/dispatch/modules/DiamondCutFacet.sol around lines 38 to 41,
the initialization call is incorrect: it currently always invokes
Address.functionCall which reverts on address(0) and uses the wrong call type;
change the logic to skip any call when _init == address(0) and, when _init is
non-zero, invoke Address.functionDelegateCall(_init, _calldata) instead (also
validate that _calldata is non-empty when performing the delegatecall to avoid
no-op calls).

}
32 changes: 32 additions & 0 deletions contracts/proxy/dispatch/modules/DiamondLoupeFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {IDiamondLoupe} from "../interfaces/IDiamondLoupe.sol";
import {Dispatch} from "../utils/Dispatch.sol";

/// @custom:stateless
contract DiamondLoupeFacet is Context, IDiamondLoupe {
using Dispatch for Dispatch.VMT;

function facets() public view override returns (Facet[] memory) {
this;
revert("This implementation doesnt keep an index, use an offchain index instead");

Check failure on line 15 in contracts/proxy/dispatch/modules/DiamondLoupeFacet.sol

View workflow job for this annotation

GitHub Actions / codespell

doesnt ==> doesn't, does not
}
Comment on lines +13 to +16
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix spelling error in revert message.

The revert message contains "doesnt" which should be "doesn't" or "does not".

-        revert("This implementation doesnt keep an index, use an offchain index instead");
+        revert("This implementation doesn't keep an index, use an offchain index instead");

Based on static analysis hints.

📝 Committable suggestion

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

Suggested change
function facets() public view override returns (Facet[] memory) {
this;
revert("This implementation doesnt keep an index, use an offchain index instead");
}
function facets() public view override returns (Facet[] memory) {
this;
revert("This implementation doesn't keep an index, use an offchain index instead");
}
🧰 Tools
🪛 GitHub Check: codespell

[failure] 15-15:
doesnt ==> doesn't, does not

🤖 Prompt for AI Agents
In contracts/proxy/dispatch/modules/DiamondLoupeFacet.sol around lines 13 to 16,
the revert message uses the misspelled contraction "doesnt"; update the revert
string to use proper spelling such as "doesn't" or "does not" (e.g., change
"This implementation doesnt keep an index, use an offchain index instead" to
"This implementation doesn't keep an index, use an offchain index instead" or
the equivalent with "does not").


function facetFunctionSelectors(address _facet) public view override returns (bytes4[] memory) {
this;
_facet;
revert("This implementation doesnt keep an index, use an offchain index instead");

Check failure on line 21 in contracts/proxy/dispatch/modules/DiamondLoupeFacet.sol

View workflow job for this annotation

GitHub Actions / codespell

doesnt ==> doesn't, does not
}
Comment on lines +18 to +22
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix spelling error and remove unnecessary statements.

The revert message contains "doesnt" which should be "doesn't" or "does not". Additionally, the standalone this; and _facet; statements can be removed.

     function facetFunctionSelectors(address _facet) public view override returns (bytes4[] memory) {
-        this;
-        _facet;
-        revert("This implementation doesnt keep an index, use an offchain index instead");
+        revert("This implementation doesn't keep an index, use an offchain index instead");
     }

Based on static analysis hints.

📝 Committable suggestion

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

Suggested change
function facetFunctionSelectors(address _facet) public view override returns (bytes4[] memory) {
this;
_facet;
revert("This implementation doesnt keep an index, use an offchain index instead");
}
function facetFunctionSelectors(address _facet) public view override returns (bytes4[] memory) {
revert("This implementation doesn't keep an index, use an offchain index instead");
}
🧰 Tools
🪛 GitHub Check: codespell

[failure] 21-21:
doesnt ==> doesn't, does not

🤖 Prompt for AI Agents
In contracts/proxy/dispatch/modules/DiamondLoupeFacet.sol around lines 18 to 22,
remove the unnecessary standalone statements `this;` and `_facet;` and update
the revert message to correct spelling/grammar (e.g., change "This
implementation doesnt keep an index, use an offchain index instead" to "This
implementation does not keep an index; use an off-chain index instead" or "This
implementation doesn't keep an index; use an off-chain index instead").


function facetAddresses() public view override returns (address[] memory) {
this;
revert("This implementation doesnt keep an index, use an offchain index instead");

Check failure on line 26 in contracts/proxy/dispatch/modules/DiamondLoupeFacet.sol

View workflow job for this annotation

GitHub Actions / codespell

doesnt ==> doesn't, does not
}
Comment on lines +24 to +27
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix spelling error and remove unnecessary statement.

The revert message contains "doesnt" which should be "doesn't" or "does not". Additionally, the standalone this; statement can be removed.

     function facetAddresses() public view override returns (address[] memory) {
-        this;
-        revert("This implementation doesnt keep an index, use an offchain index instead");
+        revert("This implementation doesn't keep an index, use an offchain index instead");
     }

Based on static analysis hints.

📝 Committable suggestion

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

Suggested change
function facetAddresses() public view override returns (address[] memory) {
this;
revert("This implementation doesnt keep an index, use an offchain index instead");
}
function facetAddresses() public view override returns (address[] memory) {
revert("This implementation doesn't keep an index, use an offchain index instead");
}
🧰 Tools
🪛 GitHub Check: codespell

[failure] 26-26:
doesnt ==> doesn't, does not

🤖 Prompt for AI Agents
In contracts/proxy/dispatch/modules/DiamondLoupeFacet.sol around lines 24 to 27,
remove the unnecessary standalone "this;" statement and fix the revert string
spelling and formatting: replace "doesnt" with "does not" (or "doesn't") and
change "offchain" to "off-chain" so the revert reads e.g. "This implementation
does not keep an index; use an off-chain index instead."


function facetAddress(bytes4 _functionSelector) public view override returns (address) {
return Dispatch.instance().getFunction(_functionSelector);
}
}
47 changes: 47 additions & 0 deletions contracts/proxy/dispatch/modules/DispatchOwnershipModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {Dispatch} from "../utils/Dispatch.sol";

/// @custom:stateless
contract DispatchOwnershipModule is Context {
using Dispatch for Dispatch.VMT;

/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
Dispatch.instance().enforceOwner(_msgSender());
_;
}

/**
* @dev Reads ownership for the vtable
*/
function owner() public view virtual returns (address) {
return Dispatch.instance().getOwner();
}

/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
Dispatch.instance().setOwner(address(0));
}

/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), Ownable.OwnableInvalidOwner(newOwner));
Dispatch.instance().setOwner(newOwner);
}
Comment on lines +43 to +46
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix incorrect error syntax.

Line 44 uses invalid syntax. Custom errors cannot be used as the second argument to require. Use revert with the custom error instead.

Apply this diff:

-        require(newOwner != address(0), Ownable.OwnableInvalidOwner(newOwner));
+        if (newOwner == address(0)) revert Ownable.OwnableInvalidOwner(newOwner);
📝 Committable suggestion

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

Suggested change
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), Ownable.OwnableInvalidOwner(newOwner));
Dispatch.instance().setOwner(newOwner);
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) revert Ownable.OwnableInvalidOwner(newOwner);
Dispatch.instance().setOwner(newOwner);
}
🤖 Prompt for AI Agents
In contracts/proxy/dispatch/modules/DispatchOwnershipModule.sol around lines
43–46, the require call uses a custom error as its second argument which is
invalid; replace the require with an explicit conditional check for the zero
address and, when detected, revert using the custom Ownable error (i.e., use an
if that checks newOwner == address(0) and call revert
Ownable.OwnableInvalidOwner(newOwner)); keep the rest of the function logic
(Dispatch.instance().setOwner(newOwner); and onlyOwner modifier) unchanged.

}
31 changes: 31 additions & 0 deletions contracts/proxy/dispatch/modules/DispatchUpdateModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {Dispatch} from "../utils/Dispatch.sol";

/// @custom:stateless
contract DispatchUpdateModule is Context {
using Dispatch for Dispatch.VMT;

struct ModuleDefinition {
address implementation;
bytes4[] selectors;
}

/**
* @dev Updates the vtable
*/
function updateDispatchTable(ModuleDefinition[] calldata modules) public {
Dispatch.VMT storage store = Dispatch.instance();

store.enforceOwner(_msgSender());
for (uint256 i = 0; i < modules.length; ++i) {
ModuleDefinition memory module = modules[i];
for (uint256 j = 0; j < module.selectors.length; ++j) {
store.setFunction(module.selectors[j], module.implementation);
}
}
}
}
59 changes: 59 additions & 0 deletions contracts/proxy/dispatch/utils/Dispatch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

/**
* @title Dispatch
* @dev TODO
*/
library Dispatch {
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Dispatch.VMT")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant _DISPATCH_VMT_SLOT = 0xe6b1591f932b472559c00c679d5b3da28bf0ed2fd643b2ef77392cbec1743c00;

struct VMT {
address _owner;
mapping(bytes4 => address) _vtable;
}

/**
* @dev Get singleton instance
*/
function instance() internal pure returns (VMT storage store) {
bytes32 position = _DISPATCH_VMT_SLOT;
assembly {
store.slot := position
}
}

/**
* @dev Ownership management
*/
function getOwner(VMT storage store) internal view returns (address) {
return store._owner;
}

function setOwner(VMT storage store, address newOwner) internal {
emit Ownable.OwnershipTransferred(store._owner, newOwner);
store._owner = newOwner;
}

function enforceOwner(VMT storage store, address account) internal view {
require(getOwner(store) == account, Ownable.OwnableUnauthorizedAccount(account));
}
Comment on lines +42 to +44
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix custom error handling in enforceOwner.

require does not accept custom errors as its second argument, so require(..., Ownable.OwnableUnauthorizedAccount(account)) fails to compile. Wrap the check in an if and revert explicitly with the custom error instead.

-        require(getOwner(store) == account, Ownable.OwnableUnauthorizedAccount(account));
+        if (getOwner(store) != account) {
+            revert Ownable.OwnableUnauthorizedAccount(account);
+        }
📝 Committable suggestion

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

Suggested change
function enforceOwner(VMT storage store, address account) internal view {
require(getOwner(store) == account, Ownable.OwnableUnauthorizedAccount(account));
}
function enforceOwner(VMT storage store, address account) internal view {
if (getOwner(store) != account) {
revert Ownable.OwnableUnauthorizedAccount(account);
}
}
🤖 Prompt for AI Agents
In contracts/proxy/dispatch/utils/Dispatch.sol around lines 42-44, the require
call uses a custom error as the second argument which is invalid; replace the
require with an explicit conditional and revert using the custom error: check if
getOwner(store) != account and if so revert
Ownable.OwnableUnauthorizedAccount(account), keeping the function internal view
and otherwise doing nothing.


/**
* @dev Delegation management
*/
event VMTUpdate(bytes4 indexed selector, address oldImplementation, address newImplementation);

function getFunction(VMT storage store, bytes4 selector) internal view returns (address) {
return store._vtable[selector];
}

function setFunction(VMT storage store, bytes4 selector, address module) internal {
emit VMTUpdate(selector, store._vtable[selector], module);
store._vtable[selector] = module;
}
}
Loading
Loading