forked from OpenZeppelin/openzeppelin-contracts
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathdraft-AccountERC7579Hooked.sol
More file actions
139 lines (120 loc) · 5.85 KB
/
draft-AccountERC7579Hooked.sol
File metadata and controls
139 lines (120 loc) · 5.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.6.0) (account/extensions/draft-AccountERC7579Hooked.sol)
pragma solidity ^0.8.26;
import {IERC7579Hook, MODULE_TYPE_HOOK} from "../../interfaces/draft-IERC7579.sol";
import {ERC7579Utils, Mode} from "../../account/utils/draft-ERC7579Utils.sol";
import {AccountERC7579} from "./draft-AccountERC7579.sol";
import {LowLevelCall} from "../../utils/LowLevelCall.sol";
/**
* @dev Extension of {AccountERC7579} with support for a single hook module (type 4).
*
* If installed, this extension will call the hook module's {IERC7579Hook-preCheck} before executing any operation
* with {_execute} (including {execute} and {executeFromExecutor} by default) and {IERC7579Hook-postCheck} thereafter.
*
* NOTE: Hook modules break the check-effect-interaction pattern. In particular, the {IERC7579Hook-preCheck} hook can
* lead to potentially dangerous reentrancy. Using the `withHook()` modifier is safe if no effect is performed
* before the preHook or after the postHook. That is the case on all functions here, but it may not be the case if
* functions that have this modifier are overridden. Developers should be extremely careful when implementing hook
* modules or further overriding functions that involve hooks.
*/
abstract contract AccountERC7579Hooked is AccountERC7579 {
address private _hook;
/// @dev A hook module is already present. This contract only supports one hook module.
error ERC7579HookModuleAlreadyPresent(address hook);
/**
* @dev Calls {IERC7579Hook-preCheck} before executing the modified function and {IERC7579Hook-postCheck}
* thereafter.
*/
modifier withHook() {
address hook_ = hook();
bytes memory hookData;
// slither-disable-next-line reentrancy-no-eth
if (hook_ != address(0)) hookData = IERC7579Hook(hook_).preCheck(msg.sender, msg.value, msg.data);
_;
if (hook_ != address(0)) IERC7579Hook(hook_).postCheck(hookData);
}
/// @dev Variant of `withHook` modifier that doesn't revert if the hook reverts. This is useful for uninstalling malicious or bugged hooks.
modifier withHookNoRevert() {
address hook_ = hook();
bytes memory hookData;
bool preCheckSuccess;
// slither-disable-next-line reentrancy-no-eth
if (hook_ != address(0)) {
preCheckSuccess = LowLevelCall.callNoReturn(
hook_,
abi.encodeCall(IERC7579Hook.preCheck, (msg.sender, msg.value, msg.data))
);
if (preCheckSuccess) {
hookData = LowLevelCall.returnData();
}
}
_;
if (hook_ != address(0) && preCheckSuccess) {
LowLevelCall.callNoReturn(hook_, abi.encodeCall(IERC7579Hook.postCheck, (hookData)));
}
}
/// @inheritdoc AccountERC7579
function accountId() public view virtual override returns (string memory) {
// vendorname.accountname.semver
return "@openzeppelin/contracts.AccountERC7579Hooked.v1.0.0";
}
/// @dev Returns the hook module address if installed, or `address(0)` otherwise.
function hook() public view virtual returns (address) {
return _hook;
}
/// @dev Supports hook modules. See {AccountERC7579-supportsModule}
function supportsModule(uint256 moduleTypeId) public view virtual override returns (bool) {
return moduleTypeId == MODULE_TYPE_HOOK || super.supportsModule(moduleTypeId);
}
/// @inheritdoc AccountERC7579
function isModuleInstalled(
uint256 moduleTypeId,
address module,
bytes calldata data
) public view virtual override returns (bool) {
return
(moduleTypeId == MODULE_TYPE_HOOK && module == hook()) ||
super.isModuleInstalled(moduleTypeId, module, data);
}
/// @dev Installs a module with support for hook modules. See {AccountERC7579-_installModule}
function _installModule(
uint256 moduleTypeId,
address module,
bytes memory initData
) internal virtual override withHook {
if (moduleTypeId == MODULE_TYPE_HOOK) {
require(_hook == address(0), ERC7579HookModuleAlreadyPresent(_hook));
_hook = module;
}
super._installModule(moduleTypeId, module, initData);
}
/// @dev Uninstalls a module with support for hook modules. See {AccountERC7579-_uninstallModule}
function _uninstallModule(uint256 moduleTypeId, address module, bytes memory deInitData) internal virtual override {
if (moduleTypeId == MODULE_TYPE_HOOK) {
// includes the `withHookNoRevert` modifier to ensure that the hook can be uninstalled even if it is malicious or bugged and reverts on preCheck or postCheck.
_uninstallHookModule(module, deInitData);
} else {
// calls super with the `withHook` modifier.
_uninstallOtherModule(moduleTypeId, module, deInitData);
}
}
function _uninstallHookModule(address module, bytes memory deInitData) private withHookNoRevert {
require(_hook == module, ERC7579Utils.ERC7579UninstalledModule(MODULE_TYPE_HOOK, module));
_hook = address(0);
super._uninstallModule(MODULE_TYPE_HOOK, module, deInitData);
}
function _uninstallOtherModule(uint256 moduleTypeId, address module, bytes memory deInitData) private withHook {
super._uninstallModule(moduleTypeId, module, deInitData);
}
/// @dev Hooked version of {AccountERC7579-_execute}.
function _execute(
Mode mode,
bytes calldata executionCalldata
) internal virtual override withHook returns (bytes[] memory) {
return super._execute(mode, executionCalldata);
}
/// @dev Hooked version of {AccountERC7579-_fallback}.
function _fallback() internal virtual override withHook returns (bytes memory) {
return super._fallback();
}
}