Skip to content

Commit

Permalink
Add 4337 support to 6900 module
Browse files Browse the repository at this point in the history
  • Loading branch information
akshay-ap committed Aug 27, 2024
1 parent 2e3adb3 commit 50833e7
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 30 deletions.
2 changes: 1 addition & 1 deletion modules/4337/contracts/Safe4337Module.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ contract Safe4337Module is IAccount, HandlerContext, CompatibilityFallbackHandle
PackedUserOperation calldata userOp,
bytes32,
uint256 missingAccountFunds
) external onlySupportedEntryPoint returns (uint256 validationData) {
) external onlySupportedEntryPoint virtual returns (uint256 validationData) {
address payable safeAddress = payable(userOp.sender);
// The entry point address is appended to the calldata by the Safe in the `FallbackManager` contract,
// following ERC-2771. Because of this, the relayer may manipulate the entry point address, therefore
Expand Down
18 changes: 3 additions & 15 deletions modules/6900/contracts/Safe6900DelegateCallReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {PluginManifest} from "./interfaces/DataTypes.sol";
import {IPlugin} from "./interfaces/IPlugin.sol";
import {IPluginManager} from "./interfaces/IPluginManager.sol";
import {Base6900} from "./base/Base6900.sol";

abstract contract Safe6900DelegateCallReceiver is SafeStorage, IPluginManager {
abstract contract Safe6900DelegateCallReceiver is SafeStorage, IPluginManager, Base6900 {
struct PluginData {
address plugin;
bytes32 manifestHash;
Expand All @@ -28,11 +29,6 @@ abstract contract Safe6900DelegateCallReceiver is SafeStorage, IPluginManager {
mapping(bytes4 => SelectorData) selectorData;
}

/**
* @notice Address of this contract
*/
address public immutable SELF;

/// @dev Store the installed plugin info for an account.
ERC6900AccountData private accountData;

Expand All @@ -50,17 +46,9 @@ abstract contract Safe6900DelegateCallReceiver is SafeStorage, IPluginManager {
* @notice Constructor
*/
constructor() {
SELF = address(this);
}

/**
* @notice Modifier to make a function callable via delegatecall only.
* If the function is called via a regular call, it will revert.
*/
modifier onlyDelegateCall() {
require(address(this) != SELF, "must only be called via delegatecall");
_;
}


function installPluginDelegateCallReceiver(
address plugin,
Expand Down
6 changes: 4 additions & 2 deletions modules/6900/contracts/Safe6900Module.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ pragma solidity ^0.8.23;

import {PluginManager} from "./base/PluginManager.sol";
import {Executor} from "./base/Executor.sol";
import {Safe4337} from "./base/Safe4337.sol";
import {EntryPointValidator} from "./base/EntryPointValidator.sol";

contract Safe6900Module is PluginManager, Executor {
constructor(address entryPoint) EntryPointValidator(entryPoint) {}

contract Safe6900Module is PluginManager, Executor, Safe4337 {
constructor(address entryPoint) EntryPointValidator(entryPoint) Safe4337(entryPoint) {}
}
25 changes: 25 additions & 0 deletions modules/6900/contracts/base/Base6900.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.23;

abstract contract Base6900 {
/**
* @notice Address of this contract
*/
address public immutable SELF;

/**
* @notice Constructor
*/
constructor() {
SELF = address(this);
}

/**
* @notice Modifier to make a function callable via delegatecall only.
* If the function is called via a regular call, it will revert.
*/
modifier onlyDelegateCall() {
require(address(this) != SELF, "must only be called via delegatecall");
_;
}
}
8 changes: 4 additions & 4 deletions modules/6900/contracts/base/Executor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol";

import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IPlugin} from "../interfaces/IPlugin.sol";
import {IAccount} from "../interfaces/IAccount.sol";
import {ISafe} from "../interfaces/ISafe.sol";
import {HandlerContext} from "@safe-global/safe-contracts/contracts/handler/HandlerContext.sol";
import {EntryPointValidator} from "../base/EntryPointValidator.sol";

abstract contract Executor is IStandardExecutor, HandlerContext, EntryPointValidator {
error CallToPluginNotAllowed(address plugin);
error ExecutionFailed();
error TxExecutionFailed();

function execute(address target, uint256 value, bytes calldata data) external payable override returns (bytes memory) {
return _execute(target, value, data);
Expand All @@ -33,7 +33,7 @@ abstract contract Executor is IStandardExecutor, HandlerContext, EntryPointValid

validateExecution();

(bool isActionSuccessful, bytes memory resultData) = IAccount(msg.sender).execTransactionFromModuleReturnData(
(bool isActionSuccessful, bytes memory resultData) = ISafe(msg.sender).execTransactionFromModuleReturnData(
target,
value,
data,
Expand All @@ -42,7 +42,7 @@ abstract contract Executor is IStandardExecutor, HandlerContext, EntryPointValid
if (isActionSuccessful) {
return resultData;
} else {
revert ExecutionFailed();
revert TxExecutionFailed();
}
}

Expand Down
8 changes: 4 additions & 4 deletions modules/6900/contracts/base/PluginManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {IPlugin} from "../interfaces/IPlugin.sol";
import {OnlyAccountCallable} from "../base/OnlyAccountCallable.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {PluginManifest} from "../interfaces/DataTypes.sol";
import {IAccount} from "../interfaces/IAccount.sol";
import {ISafe} from "../interfaces/ISafe.sol";
import {HandlerContext} from "@safe-global/safe-contracts/contracts/handler/HandlerContext.sol";
import {Safe6900DelegateCallReceiver} from "../Safe6900DelegateCallReceiver.sol";

Expand All @@ -20,7 +20,7 @@ abstract contract PluginManager is IPluginManager, OnlyAccountCallable, HandlerC
) public override onlyAccount {
address safe = _msgSender();

(bool success, bytes memory data) = IAccount(safe).execTransactionFromModuleReturnData(
(bool success, bytes memory data) = ISafe(safe).execTransactionFromModuleReturnData(
payable(address(this)),
0,
abi.encodeWithSelector(
Expand All @@ -44,7 +44,7 @@ abstract contract PluginManager is IPluginManager, OnlyAccountCallable, HandlerC

function uninstallPlugin(address plugin, bytes calldata config, bytes calldata pluginUninstallData) external override onlyAccount {
address safe = _msgSender();
(bool success, bytes memory data) = IAccount(safe).execTransactionFromModuleReturnData(
(bool success, bytes memory data) = ISafe(safe).execTransactionFromModuleReturnData(
payable(address(this)),
0,
abi.encodeWithSelector(
Expand All @@ -66,7 +66,7 @@ abstract contract PluginManager is IPluginManager, OnlyAccountCallable, HandlerC
}

function isPluginInstalled(address safe, address plugin) external returns (bool) {
(bool success, bytes memory data) = IAccount(safe).execTransactionFromModuleReturnData(
(bool success, bytes memory data) = ISafe(safe).execTransactionFromModuleReturnData(
payable(address(this)),
0,
abi.encodeWithSelector(Safe6900DelegateCallReceiver.isPluginInstalledDelegateCallReceiver.selector, plugin),
Expand Down
66 changes: 66 additions & 0 deletions modules/6900/contracts/base/Safe4337.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.23;

import {Safe4337Module} from "@safe-global/safe-4337/contracts/Safe4337Module.sol";
import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol";
import {ISafe} from "../interfaces/ISafe.sol";
import {Base6900} from "./Base6900.sol";

contract Safe4337 is Safe4337Module, Base6900 {
constructor(address _entryPoint) Safe4337Module(_entryPoint) {}

function validateUserOp(
PackedUserOperation calldata userOp,
bytes32,
uint256 missingAccountFunds
) external override onlySupportedEntryPoint returns (uint256 validationData) {
(bool success, bytes memory data) = ISafe(userOp.sender).execTransactionFromModuleReturnData(
payable(address(this)),
0,
abi.encodeWithSelector(Safe4337.validateUserOpDelegatecallReciever.selector, userOp, missingAccountFunds),
1 // delegatecall
);

if (!success) {
if (data.length == 0) revert();
assembly {
// We use Yul's revert() to bubble up errors from the target contract.
revert(add(32, data), mload(data))
}
}

validationData = abi.decode(data, (uint256));
}

function validateUserOpDelegatecallReciever(
PackedUserOperation calldata userOp,
bytes32,
uint256 missingAccountFunds
) external onlyDelegateCall returns (uint256 validationData) {
address payable safeAddress = payable(userOp.sender);
// The entry point address is appended to the calldata by the Safe in the `FallbackManager` contract,
// following ERC-2771. Because of this, the relayer may manipulate the entry point address, therefore
// we have to verify that the sender is the Safe specified in the userOperation.
if (safeAddress != msg.sender) {
revert InvalidCaller();
}

// We check the execution function signature to make sure the entry point can't call any other function
// and make sure the execution of the user operation is handled by the module
bytes4 selector = bytes4(userOp.callData);
if (selector != this.executeUserOp.selector && selector != this.executeUserOpWithErrorString.selector) {
revert UnsupportedExecutionFunction(selector);
}

// The userOp nonce is validated in the entry point (for 0.6.0+), therefore we will not check it again
validationData = _validateSignatures(userOp);

// We trust the entry point to set the correct prefund value, based on the operation params
// We need to perform this even if the signature is not valid, else the simulation function of the entry point will not work.
if (missingAccountFunds != 0) {
// We intentionally ignore errors in paying the missing account funds, as the entry point is responsible for
// verifying the prefund has been paid. This behaviour matches the reference base account implementation.
ISafe(safeAddress).execTransactionFromModule(SUPPORTED_ENTRYPOINT, missingAccountFunds, "", 0);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
pragma solidity ^0.8.23;

/**
* @title IAccount Declares the functions that are called on an account.
* @title ISafe Declares the functions that are called on an Safe account.
*/
interface IAccount {
interface ISafe {
function execTransactionFromModule(
address payable to,
address to,
uint256 value,
bytes calldata data,
uint8 operation
Expand Down
1 change: 1 addition & 0 deletions modules/6900/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@openzeppelin/contracts": "^5.0.2",
"@safe-global/mock-contract": "^4.1.0",
"@safe-global/safe-4337-local-bundler": "workspace:^",
"@safe-global/safe-4337": "workspace:^",
"@types/chai": "^4.3.14",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.30",
Expand Down
107 changes: 106 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 50833e7

Please sign in to comment.