Skip to content
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

WIP: 4337 compatibility #127

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
39 changes: 21 additions & 18 deletions contracts/SafeProtocolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/**
Expand Down Expand Up @@ -76,16 +76,16 @@ contract SafeProtocolManager is ISafeProtocolManager, RegistryManager, HooksMana
*/
function executeTransaction(
address account,
SafeTransaction calldata transaction
) external override onlyEnabledPlugin(account) onlyPermittedModule(msg.sender, MODULE_TYPE_PLUGIN) returns (bytes[] memory data) {
address hooksAddress = enabledHooks[account];
bool areHooksEnabled = hooksAddress != address(0);
bytes memory preCheckData;
if (areHooksEnabled) {
// execution metadata for transaction execution through plugin is encoded address of the plugin i.e. msg.sender.
// executionType = 1 for plugin flow
preCheckData = ISafeProtocolHooks(hooksAddress).preCheck(account, transaction, 1, abi.encode(msg.sender));
}
SafeTransaction calldata transaction // onlyEnabledPlugin(account) onlyPermittedModule(msg.sender, MODULE_TYPE_PLUGIN)
) external override returns (bytes[] memory data) {
// address hooksAddress = enabledHooks[account];
// bool areHooksEnabled = hooksAddress != address(0);
// bytes memory preCheckData;
// if (areHooksEnabled) {
// // execution metadata for transaction execution through plugin is encoded address of the plugin i.e. msg.sender.
// // executionType = 1 for plugin flow
// preCheckData = ISafeProtocolHooks(hooksAddress).preCheck(account, transaction, 1, abi.encode(msg.sender));
// }

data = new bytes[](transaction.actions.length);
uint256 length = transaction.actions.length;
Expand All @@ -97,7 +97,7 @@ contract SafeProtocolManager is ISafeProtocolManager, RegistryManager, HooksMana
} else if (safeProtocolAction.to == account) {
checkPermission(account, PLUGIN_PERMISSION_CALL_TO_SELF);
} else {
checkPermission(account, PLUGIN_PERMISSION_EXECUTE_CALL);
// checkPermission(account, PLUGIN_PERMISSION_EXECUTE_CALL);
}

(bool isActionSuccessful, bytes memory resultData) = IAccount(account).execTransactionFromModuleReturnData(
Expand All @@ -114,10 +114,10 @@ contract SafeProtocolManager is ISafeProtocolManager, RegistryManager, HooksMana
data[i] = resultData;
}
}
if (areHooksEnabled) {
// success = true because if transaction is not revereted till here, all actions executed successfully.
ISafeProtocolHooks(hooksAddress).postCheck(account, true, preCheckData);
}
// if (areHooksEnabled) {
// // success = true because if transaction is not revereted till here, all actions executed successfully.
// ISafeProtocolHooks(hooksAddress).postCheck(account, true, preCheckData);
// }
emit ActionsExecuted(account, transaction.metadataHash, transaction.nonce);
}

Expand Down Expand Up @@ -174,7 +174,10 @@ contract SafeProtocolManager is ISafeProtocolManager, RegistryManager, HooksMana
function enablePlugin(
address plugin,
uint8 permissions
) external noZeroOrSentinelPlugin(plugin) onlyPermittedModule(plugin, MODULE_TYPE_PLUGIN) onlyAccount {
)
external
noZeroOrSentinelPlugin(plugin) // onlyPermittedModule(plugin, MODULE_TYPE_PLUGIN)
{
// address(0) check omitted because it is not expected to enable it as a plugin and
// call to it would fail. Additionally, registry should not permit address(0) as an module.
if (!ISafeProtocolPlugin(plugin).supportsInterface(type(ISafeProtocolPlugin).interfaceId))
Expand Down Expand Up @@ -207,7 +210,7 @@ contract SafeProtocolManager is ISafeProtocolManager, RegistryManager, HooksMana
* @notice Disable a plugin. This function should be called by account.
* @param plugin Plugin to be disabled
*/
function disablePlugin(address prevPlugin, address plugin) external noZeroOrSentinelPlugin(plugin) onlyAccount {
function disablePlugin(address prevPlugin, address plugin) external noZeroOrSentinelPlugin(plugin) {
PluginAccessInfo storage prevPluginInfo = enabledPlugins[msg.sender][prevPlugin];
PluginAccessInfo storage pluginInfo = enabledPlugins[msg.sender][plugin];

Expand Down
28 changes: 21 additions & 7 deletions contracts/base/FunctionHandlerManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@
* @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 => address) public validateUserOpHandler;
bytes4 constant validateUserOpSelector = bytes4(0x3a871cdd);

Check warning on line 22 in contracts/base/FunctionHandlerManager.sol

View workflow job for this annotation

GitHub Actions / lint

Explicitly mark visibility of state

Check warning on line 22 in contracts/base/FunctionHandlerManager.sol

View workflow job for this annotation

GitHub Actions / lint

Constant name must be in capitalized SNAKE_CASE

Check warning on line 22 in contracts/base/FunctionHandlerManager.sol

View workflow job for this annotation

GitHub Actions / lint

Explicitly mark visibility of state

Check warning on line 22 in contracts/base/FunctionHandlerManager.sol

View workflow job for this annotation

GitHub Actions / lint

Constant name must be in capitalized SNAKE_CASE

// Events
event FunctionHandlerChanged(address indexed account, bytes4 indexed selector, address indexed functionHandler);

// Errors
error FunctionHandlerNotSet(address account, bytes4 functionSelector);

constructor(address registryAddress, address _initialOwner) RegistryManager(registryAddress, _initialOwner) {}

/**
* @notice Returns the function handler for an account and function selector.
* @param account Address of an account
Expand All @@ -41,6 +46,11 @@
* @param functionHandler Address of the contract to be set as a function handler
*/
function setFunctionHandler(bytes4 selector, address functionHandler) external onlyAccount {
if (selector == validateUserOpSelector) {
validateUserOpHandler[msg.sender] = functionHandler;
emit FunctionHandlerChanged(msg.sender, selector, functionHandler);
return;
}
if (functionHandler != address(0)) {
checkPermittedModule(functionHandler, MODULE_TYPE_FUNCTION_HANDLER);
if (!ISafeProtocolFunctionHandler(functionHandler).supportsInterface(type(ISafeProtocolFunctionHandler).interfaceId))
Expand All @@ -63,19 +73,23 @@
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 && validateUserOpHandler[account] != address(0)) {
return ISafeProtocolFunctionHandler(validateUserOpHandler[account]).handle(account, sender, 0, msg.data);
}

address functionHandler = functionHandlers[account][functionSelector];

// Revert if functionHandler is not set
if (functionHandler == address(0)) {
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.
Expand Down
9 changes: 8 additions & 1 deletion deploy/deploy_protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -18,6 +18,13 @@ const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
log: true,
deterministicDeployment: true,
});

await deploy("FunctionHandlerManager", {
from: deployer,
args: [registry.address, owner],
log: true,
deterministicDeployment: true,
});
};

deploy.tags = ["protocol"];
Expand Down
9 changes: 8 additions & 1 deletion deploy/deploy_protocol_with_test_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -18,6 +18,13 @@ const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
log: true,
deterministicDeployment: true,
});

await deploy("FunctionHandlerManager", {
from: deployer,
args: [testRegistry.address, owner],
log: true,
deterministicDeployment: true,
});
};

deploy.tags = ["test-protocol"];
Expand Down
18 changes: 15 additions & 3 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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));
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
}
};
Expand Down
Loading