-
Notifications
You must be signed in to change notification settings - Fork 25
/
AllowlistModule.sol
155 lines (127 loc) · 5.68 KB
/
AllowlistModule.sol
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
import {IModule} from "../../interfaces/IModule.sol";
import {Call, IModularAccount} from "../../interfaces/IModularAccount.sol";
import {IValidationHookModule} from "../../interfaces/IValidationHookModule.sol";
import {BaseModule} from "../../modules/BaseModule.sol";
contract AllowlistModule is IValidationHookModule, BaseModule {
struct AllowlistInit {
address target;
bool hasSelectorAllowlist;
bytes4[] selectors;
}
struct AllowlistEntry {
bool allowed;
bool hasSelectorAllowlist;
}
mapping(uint32 entityId => mapping(address target => mapping(address account => AllowlistEntry))) public
targetAllowlist;
mapping(
uint32 entityId => mapping(address target => mapping(bytes4 selector => mapping(address account => bool)))
) public selectorAllowlist;
event AllowlistTargetUpdated(
uint32 indexed entityId, address indexed account, address indexed target, AllowlistEntry entry
);
event AllowlistSelectorUpdated(
uint32 indexed entityId, address indexed account, bytes24 indexed targetAndSelector, bool allowed
);
error TargetNotAllowed();
error SelectorNotAllowed();
error NoSelectorSpecified();
function onInstall(bytes calldata data) external override {
(uint32 entityId, AllowlistInit[] memory init) = abi.decode(data, (uint32, AllowlistInit[]));
for (uint256 i = 0; i < init.length; i++) {
setAllowlistTarget(entityId, init[i].target, true, init[i].hasSelectorAllowlist);
if (init[i].hasSelectorAllowlist) {
for (uint256 j = 0; j < init[i].selectors.length; j++) {
setAllowlistSelector(entityId, init[i].target, init[i].selectors[j], true);
}
}
}
}
function onUninstall(bytes calldata data) external override {
(uint32 entityId, AllowlistInit[] memory init) = abi.decode(data, (uint32, AllowlistInit[]));
for (uint256 i = 0; i < init.length; i++) {
setAllowlistTarget(entityId, init[i].target, false, false);
if (init[i].hasSelectorAllowlist) {
for (uint256 j = 0; j < init[i].selectors.length; j++) {
setAllowlistSelector(entityId, init[i].target, init[i].selectors[j], false);
}
}
}
}
function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32)
external
view
override
returns (uint256)
{
checkAllowlistCalldata(entityId, userOp.callData);
return 0;
}
function preRuntimeValidationHook(uint32 entityId, address, uint256, bytes calldata data, bytes calldata)
external
view
override
{
checkAllowlistCalldata(entityId, data);
return;
}
// solhint-disable-next-line no-empty-blocks
function preSignatureValidationHook(uint32, address, bytes32, bytes calldata) external pure override {}
/// @inheritdoc IModule
function moduleId() external pure returns (string memory) {
return "erc6900.allowlist-module.0.0.1";
}
function setAllowlistTarget(uint32 entityId, address target, bool allowed, bool hasSelectorAllowlist) public {
AllowlistEntry memory entry = AllowlistEntry(allowed, hasSelectorAllowlist);
targetAllowlist[entityId][target][msg.sender] = entry;
emit AllowlistTargetUpdated(entityId, msg.sender, target, entry);
}
function setAllowlistSelector(uint32 entityId, address target, bytes4 selector, bool allowed) public {
selectorAllowlist[entityId][target][selector][msg.sender] = allowed;
bytes24 targetAndSelector = bytes24(bytes24(bytes20(target)) | (bytes24(selector) >> 160));
emit AllowlistSelectorUpdated(entityId, msg.sender, targetAndSelector, allowed);
}
function checkAllowlistCalldata(uint32 entityId, bytes calldata callData) public view {
if (bytes4(callData[:4]) == IModularAccount.execute.selector) {
(address target,, bytes memory data) = abi.decode(callData[4:], (address, uint256, bytes));
_checkCallPermission(entityId, msg.sender, target, data);
} else if (bytes4(callData[:4]) == IModularAccount.executeBatch.selector) {
Call[] memory calls = abi.decode(callData[4:], (Call[]));
for (uint256 i = 0; i < calls.length; i++) {
_checkCallPermission(entityId, msg.sender, calls[i].target, calls[i].data);
}
}
}
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(BaseModule, IERC165)
returns (bool)
{
return interfaceId == type(IValidationHookModule).interfaceId || super.supportsInterface(interfaceId);
}
function _checkCallPermission(uint32 entityId, address account, address target, bytes memory data)
internal
view
{
AllowlistEntry storage entry = targetAllowlist[entityId][target][account];
(bool allowed, bool hasSelectorAllowlist) = (entry.allowed, entry.hasSelectorAllowlist);
if (!allowed) {
revert TargetNotAllowed();
}
if (hasSelectorAllowlist) {
if (data.length < 4) {
revert NoSelectorSpecified();
}
bytes4 selector = bytes4(data);
if (!selectorAllowlist[entityId][target][selector][account]) {
revert SelectorNotAllowed();
}
}
}
}