From 5b5be8f18730315ee2d8a671656d448ec6572b18 Mon Sep 17 00:00:00 2001 From: Akshay Date: Fri, 27 Oct 2023 14:29:03 +0200 Subject: [PATCH] Add userop validator handler --- contracts/SafeProtocolManager.sol | 2 +- contracts/base/FunctionHandlerManager.sol | 34 ++++++++++++++++---- deploy/deploy_protocol.ts | 10 +++++- deploy/deploy_protocol_with_test_registry.ts | 9 +++++- hardhat.config.ts | 18 +++++++++-- 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/contracts/SafeProtocolManager.sol b/contracts/SafeProtocolManager.sol index 7012b720..6a858ddc 100644 --- a/contracts/SafeProtocolManager.sol +++ b/contracts/SafeProtocolManager.sol @@ -19,7 +19,7 @@ import {MODULE_TYPE_PLUGIN} from "./common/Constants.sol"; * plugins through a Manager rather than directly enabling plugins in their Account. * Users have to first enable SafeProtocolManager as a plugin on their Account and then enable other plugins through the manager. */ -contract SafeProtocolManager is ISafeProtocolManager, RegistryManager, HooksManager, FunctionHandlerManager, IERC165 { +contract SafeProtocolManager is ISafeProtocolManager, RegistryManager, HooksManager, IERC165 { address internal constant SENTINEL_MODULES = address(0x1); /** diff --git a/contracts/base/FunctionHandlerManager.sol b/contracts/base/FunctionHandlerManager.sol index 984565a6..15880cd5 100644 --- a/contracts/base/FunctionHandlerManager.sol +++ b/contracts/base/FunctionHandlerManager.sol @@ -12,12 +12,20 @@ import {MODULE_TYPE_FUNCTION_HANDLER} from "../common/Constants.sol"; * @notice This contract manages the function handlers for an Account. The contract stores the * information about an account, bytes4 function selector and the function handler contract address. */ -abstract contract FunctionHandlerManager is RegistryManager { +contract FunctionHandlerManager is RegistryManager { // Storage /** @dev Mapping that stores information about an account, function selector, and address of the account. */ mapping(address => mapping(bytes4 => address)) public functionHandlers; + mapping(address => bool) public isValidateUserOpHandlerEnabled; + address public validateUserOpHandler; + bytes4 constant validateUserOpSelector = bytes4(0x3a871cdd); + + constructor(address _validateUserOpHandler, address registry, address initialOwner) RegistryManager(registry, initialOwner) { + validateUserOpHandler = _validateUserOpHandler; + } + // Events event FunctionHandlerChanged(address indexed account, bytes4 indexed selector, address indexed functionHandler); @@ -52,6 +60,14 @@ abstract contract FunctionHandlerManager is RegistryManager { emit FunctionHandlerChanged(msg.sender, selector, functionHandler); } + function setValidateUserOpHandler(address newHandler) external onlyOwner { + validateUserOpHandler = newHandler; + } + + function enableUserOpValidator() external onlyAccount { + isValidateUserOpHandlerEnabled[msg.sender] = true; + } + /** * @notice This fallback handler function checks if an account (msg.sender) has a function handler enabled. * If enabled, calls handle function and returns the result back. @@ -63,6 +79,16 @@ abstract contract FunctionHandlerManager is RegistryManager { address account = msg.sender; bytes4 functionSelector = bytes4(msg.data); + address sender; + // solhint-disable-next-line no-inline-assembly + assembly { + sender := shr(96, calldataload(sub(calldatasize(), 20))) + } + + if (validateUserOpSelector == functionSelector && isValidateUserOpHandlerEnabled[account]) { + return ISafeProtocolFunctionHandler(validateUserOpHandler).handle(account, sender, 0, msg.data); + } + address functionHandler = functionHandlers[account][functionSelector]; // Revert if functionHandler is not set @@ -70,12 +96,6 @@ abstract contract FunctionHandlerManager is RegistryManager { revert FunctionHandlerNotSet(account, functionSelector); } - address sender; - // solhint-disable-next-line no-inline-assembly - assembly { - sender := shr(96, calldataload(sub(calldatasize(), 20))) - } - // With a Safe{Core} Account v1.x, msg.data contains 20 bytes of sender address. Read the sender address by loading last 20 bytes. // remove last 20 bytes from calldata and store it in `data`. // Keep first 4 bytes (i.e function signature) so that handler contract can infer function identifier. diff --git a/deploy/deploy_protocol.ts b/deploy/deploy_protocol.ts index 39e1187d..94e7ba33 100644 --- a/deploy/deploy_protocol.ts +++ b/deploy/deploy_protocol.ts @@ -3,7 +3,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { deployments, getNamedAccounts } = hre; - const { deployer, owner } = await getNamedAccounts(); + const { deployer, owner, userOpValidatorHandler } = await getNamedAccounts(); const { deploy } = deployments; const registry = await deploy("SafeProtocolRegistry", { from: deployer, @@ -18,6 +18,14 @@ const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { log: true, deterministicDeployment: true, }); + + + await deploy("FunctionHandlerManager", { + from: deployer, + args: [userOpValidatorHandler, registry.address, owner], + log: true, + deterministicDeployment: true, + }); }; deploy.tags = ["protocol"]; diff --git a/deploy/deploy_protocol_with_test_registry.ts b/deploy/deploy_protocol_with_test_registry.ts index 3fbb00bd..6536f3a2 100644 --- a/deploy/deploy_protocol_with_test_registry.ts +++ b/deploy/deploy_protocol_with_test_registry.ts @@ -3,7 +3,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { deployments, getNamedAccounts } = hre; - const { deployer, owner } = await getNamedAccounts(); + const { deployer, owner, userOpValidatorHandler } = await getNamedAccounts(); const { deploy } = deployments; const testRegistry = await deploy("TestSafeProtocolRegistryUnrestricted", { from: deployer, @@ -18,6 +18,13 @@ const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { log: true, deterministicDeployment: true, }); + + await deploy("FunctionHandlerManager", { + from: deployer, + args: [userOpValidatorHandler, testRegistry.address, owner], + log: true, + deterministicDeployment: true, + }); }; deploy.tags = ["test-protocol"]; diff --git a/hardhat.config.ts b/hardhat.config.ts index 75913490..2ee5676e 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -7,7 +7,7 @@ import { HttpNetworkUserConfig } from "hardhat/types"; import "hardhat-deploy"; import { DeterministicDeploymentInfo } from "hardhat-deploy/dist/types"; import { getSingletonFactoryInfo } from "@safe-global/safe-singleton-factory"; -import { ethers } from "ethers"; +import { ZeroAddress, ethers } from "ethers"; import "./src/tasks/generate_deployments_markdown"; import "./src/tasks/show_codesize"; @@ -22,7 +22,7 @@ const argv : any = yargs .help(false) .version(false).argv; -const { NODE_URL, MNEMONIC, INFURA_KEY, ETHERSCAN_API_KEY, SAFE_CORE_PROTOCOL_OWNER_ADDRESS } = process.env; +const { NODE_URL, MNEMONIC, INFURA_KEY, ETHERSCAN_API_KEY, SAFE_CORE_PROTOCOL_OWNER_ADDRESS, SAFE_CORE_PROTOCOL_4337_USER_OP_VALIDATOR_HANDLER_ADDRESS } = process.env; const deterministicDeployment = (network: string): DeterministicDeploymentInfo => { const info = getSingletonFactoryInfo(parseInt(network)); @@ -52,10 +52,19 @@ sharedNetworkConfig.accounts = { } const config: HardhatUserConfig = { - solidity: "0.8.18", + solidity: { + version: "0.8.18", + settings: { + optimizer: { + enabled: true, + runs: 2000 + }, + } + }, gasReporter: { enabled: (process.env.REPORT_GAS) ? true : false }, + networks: { hardhat: { allowUnlimitedContractSize: true, @@ -109,6 +118,9 @@ const config: HardhatUserConfig = { }, owner: { default: SAFE_CORE_PROTOCOL_OWNER_ADDRESS || 1 + }, + userOpValidatorHandler: { + default: SAFE_CORE_PROTOCOL_4337_USER_OP_VALIDATOR_HANDLER_ADDRESS || ZeroAddress } } };