@erc6900/reference-implementation 0.8.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @erc6900/reference-implementation might be problematic. Click here for more details.
- package/package.json +5 -26
- package/preinstall.js +110 -0
- package/LICENSE +0 -21
- package/README.md +0 -76
- package/src/account/AccountExecutor.sol +0 -20
- package/src/account/AccountFactory.sol +0 -126
- package/src/account/AccountStorage.sol +0 -99
- package/src/account/AccountStorageInitializable.sol +0 -67
- package/src/account/ModularAccountView.sol +0 -66
- package/src/account/ModuleManagerInternals.sol +0 -325
- package/src/account/ReferenceModularAccount.sol +0 -769
- package/src/account/SemiModularAccount.sol +0 -214
- package/src/account/SemiModularAccount7702.sol +0 -38
- package/src/helpers/CollectReturnData.sol +0 -14
- package/src/helpers/Constants.sol +0 -11
- package/src/helpers/EmptyCalldataSlice.sol +0 -12
- package/src/helpers/ValidationResHelpers.sol +0 -50
- package/src/interfaces/IERC6900Account.sol +0 -127
- package/src/interfaces/IERC6900AccountView.sol +0 -52
- package/src/interfaces/IERC6900ExecutionHookModule.sol +0 -26
- package/src/interfaces/IERC6900ExecutionModule.sol +0 -37
- package/src/interfaces/IERC6900Module.sol +0 -24
- package/src/interfaces/IERC6900ValidationHookModule.sol +0 -46
- package/src/interfaces/IERC6900ValidationModule.sol +0 -53
- package/src/libraries/HookConfigLib.sol +0 -135
- package/src/libraries/KnownSelectorsLib.sol +0 -65
- package/src/libraries/ModuleEntityLib.sol +0 -37
- package/src/libraries/ModuleStorageLib.sol +0 -62
- package/src/libraries/SparseCalldataSegmentLib.sol +0 -95
- package/src/libraries/ValidationConfigLib.sol +0 -119
- package/src/modules/BaseModule.sol +0 -54
- package/src/modules/ModuleEIP712.sol +0 -29
- package/src/modules/ReplaySafeWrapper.sol +0 -38
- package/src/modules/TokenReceiverModule.sol +0 -87
- package/src/modules/permissions/AllowlistModule.sol +0 -156
- package/src/modules/permissions/ERC20TokenLimitModule.sol +0 -145
- package/src/modules/permissions/NativeTokenLimitModule.sol +0 -154
- package/src/modules/validation/ISingleSignerValidationModule.sol +0 -22
- package/src/modules/validation/SingleSignerValidationModule.sol +0 -138
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.20;
|
|
3
|
-
|
|
4
|
-
import {ModuleEIP712} from "./ModuleEIP712.sol";
|
|
5
|
-
|
|
6
|
-
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
|
7
|
-
|
|
8
|
-
// A contract mixin for modules that wish to use EIP-712 to wrap the hashes sent to the EIP-1271 function
|
|
9
|
-
// `isValidSignature`.
|
|
10
|
-
// This makes signatures generated by owners of contract accounts non-replayable across multiple accounts owned by
|
|
11
|
-
// the same owner.
|
|
12
|
-
abstract contract ReplaySafeWrapper is ModuleEIP712 {
|
|
13
|
-
// keccak256("ReplaySafeHash(bytes32 hash)")
|
|
14
|
-
bytes32 private constant _REPLAY_SAFE_HASH_TYPEHASH =
|
|
15
|
-
0x294a8735843d4afb4f017c76faf3b7731def145ed0025fc9b1d5ce30adf113ff;
|
|
16
|
-
|
|
17
|
-
/// @notice Wraps a hash in an EIP-712 envelope to prevent cross-account replay attacks.
|
|
18
|
-
/// Uses the ModuleEIP712 domain separator, which includes the chainId, module address, and account address.
|
|
19
|
-
/// @param account The account that will validate the message hash.
|
|
20
|
-
/// @param hash The hash to wrap.
|
|
21
|
-
/// @return The the replay-safe hash, computed by wrapping the input hash in an EIP-712 struct.
|
|
22
|
-
function replaySafeHash(address account, bytes32 hash) public view virtual returns (bytes32) {
|
|
23
|
-
return MessageHashUtils.toTypedDataHash({
|
|
24
|
-
domainSeparator: _domainSeparator(account),
|
|
25
|
-
structHash: _hashStruct(hash)
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function _hashStruct(bytes32 hash) internal pure virtual returns (bytes32) {
|
|
30
|
-
bytes32 res;
|
|
31
|
-
assembly ("memory-safe") {
|
|
32
|
-
mstore(0x00, _REPLAY_SAFE_HASH_TYPEHASH)
|
|
33
|
-
mstore(0x20, hash)
|
|
34
|
-
res := keccak256(0x00, 0x40)
|
|
35
|
-
}
|
|
36
|
-
return res;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.20;
|
|
3
|
-
|
|
4
|
-
import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol";
|
|
5
|
-
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
6
|
-
|
|
7
|
-
import {ExecutionManifest, ManifestExecutionFunction} from "../interfaces/IERC6900ExecutionModule.sol";
|
|
8
|
-
import {ExecutionManifest, IERC6900ExecutionModule} from "../interfaces/IERC6900ExecutionModule.sol";
|
|
9
|
-
import {IERC6900Module} from "../interfaces/IERC6900Module.sol";
|
|
10
|
-
import {BaseModule} from "./BaseModule.sol";
|
|
11
|
-
|
|
12
|
-
/// @title Token Receiver Module
|
|
13
|
-
/// @author ERC-6900 Authors
|
|
14
|
-
/// @notice This module allows modular accounts to receive various types of tokens by implementing
|
|
15
|
-
/// required token receiver interfaces.
|
|
16
|
-
contract TokenReceiverModule is BaseModule, IERC6900ExecutionModule, IERC721Receiver, IERC1155Receiver {
|
|
17
|
-
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
18
|
-
// ┃ Execution functions ┃
|
|
19
|
-
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
20
|
-
|
|
21
|
-
function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
|
|
22
|
-
return IERC721Receiver.onERC721Received.selector;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function onERC1155Received(address, address, uint256, uint256, bytes calldata)
|
|
26
|
-
external
|
|
27
|
-
pure
|
|
28
|
-
override
|
|
29
|
-
returns (bytes4)
|
|
30
|
-
{
|
|
31
|
-
return IERC1155Receiver.onERC1155Received.selector;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)
|
|
35
|
-
external
|
|
36
|
-
pure
|
|
37
|
-
override
|
|
38
|
-
returns (bytes4)
|
|
39
|
-
{
|
|
40
|
-
return IERC1155Receiver.onERC1155BatchReceived.selector;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
44
|
-
// ┃ Module interface functions ┃
|
|
45
|
-
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
46
|
-
|
|
47
|
-
/// @inheritdoc IERC6900Module
|
|
48
|
-
// solhint-disable-next-line no-empty-blocks
|
|
49
|
-
function onInstall(bytes calldata) external pure override {}
|
|
50
|
-
|
|
51
|
-
/// @inheritdoc IERC6900Module
|
|
52
|
-
// solhint-disable-next-line no-empty-blocks
|
|
53
|
-
function onUninstall(bytes calldata) external pure override {}
|
|
54
|
-
|
|
55
|
-
/// @inheritdoc IERC6900ExecutionModule
|
|
56
|
-
function executionManifest() external pure override returns (ExecutionManifest memory) {
|
|
57
|
-
ExecutionManifest memory manifest;
|
|
58
|
-
|
|
59
|
-
manifest.executionFunctions = new ManifestExecutionFunction[](3);
|
|
60
|
-
manifest.executionFunctions[0] = ManifestExecutionFunction({
|
|
61
|
-
executionSelector: this.onERC721Received.selector,
|
|
62
|
-
skipRuntimeValidation: true,
|
|
63
|
-
allowGlobalValidation: false
|
|
64
|
-
});
|
|
65
|
-
manifest.executionFunctions[1] = ManifestExecutionFunction({
|
|
66
|
-
executionSelector: this.onERC1155Received.selector,
|
|
67
|
-
skipRuntimeValidation: true,
|
|
68
|
-
allowGlobalValidation: false
|
|
69
|
-
});
|
|
70
|
-
manifest.executionFunctions[2] = ManifestExecutionFunction({
|
|
71
|
-
executionSelector: this.onERC1155BatchReceived.selector,
|
|
72
|
-
skipRuntimeValidation: true,
|
|
73
|
-
allowGlobalValidation: false
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
manifest.interfaceIds = new bytes4[](2);
|
|
77
|
-
manifest.interfaceIds[0] = type(IERC721Receiver).interfaceId;
|
|
78
|
-
manifest.interfaceIds[1] = type(IERC1155Receiver).interfaceId;
|
|
79
|
-
|
|
80
|
-
return manifest;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/// @inheritdoc IERC6900Module
|
|
84
|
-
function moduleId() external pure returns (string memory) {
|
|
85
|
-
return "erc6900.token-receiver-module.1.0.0";
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.20;
|
|
3
|
-
|
|
4
|
-
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
|
|
5
|
-
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
|
|
6
|
-
|
|
7
|
-
import {IERC6900Module} from "../../interfaces/IERC6900Module.sol";
|
|
8
|
-
|
|
9
|
-
import {Call, IERC6900Account} from "../../interfaces/IERC6900Account.sol";
|
|
10
|
-
import {IERC6900ValidationHookModule} from "../../interfaces/IERC6900ValidationHookModule.sol";
|
|
11
|
-
import {BaseModule} from "../../modules/BaseModule.sol";
|
|
12
|
-
|
|
13
|
-
contract AllowlistModule is IERC6900ValidationHookModule, BaseModule {
|
|
14
|
-
struct AllowlistInit {
|
|
15
|
-
address target;
|
|
16
|
-
bool hasSelectorAllowlist;
|
|
17
|
-
bytes4[] selectors;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
struct AllowlistEntry {
|
|
21
|
-
bool allowed;
|
|
22
|
-
bool hasSelectorAllowlist;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
mapping(uint32 entityId => mapping(address target => mapping(address account => AllowlistEntry))) public
|
|
26
|
-
targetAllowlist;
|
|
27
|
-
mapping(
|
|
28
|
-
uint32 entityId => mapping(address target => mapping(bytes4 selector => mapping(address account => bool)))
|
|
29
|
-
) public selectorAllowlist;
|
|
30
|
-
|
|
31
|
-
event AllowlistTargetUpdated(
|
|
32
|
-
uint32 indexed entityId, address indexed account, address indexed target, AllowlistEntry entry
|
|
33
|
-
);
|
|
34
|
-
event AllowlistSelectorUpdated(
|
|
35
|
-
uint32 indexed entityId, address indexed account, bytes24 indexed targetAndSelector, bool allowed
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
error TargetNotAllowed();
|
|
39
|
-
error SelectorNotAllowed();
|
|
40
|
-
error NoSelectorSpecified();
|
|
41
|
-
|
|
42
|
-
function onInstall(bytes calldata data) external override {
|
|
43
|
-
(uint32 entityId, AllowlistInit[] memory init) = abi.decode(data, (uint32, AllowlistInit[]));
|
|
44
|
-
|
|
45
|
-
for (uint256 i = 0; i < init.length; i++) {
|
|
46
|
-
setAllowlistTarget(entityId, init[i].target, true, init[i].hasSelectorAllowlist);
|
|
47
|
-
|
|
48
|
-
if (init[i].hasSelectorAllowlist) {
|
|
49
|
-
for (uint256 j = 0; j < init[i].selectors.length; j++) {
|
|
50
|
-
setAllowlistSelector(entityId, init[i].target, init[i].selectors[j], true);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function onUninstall(bytes calldata data) external override {
|
|
57
|
-
(uint32 entityId, AllowlistInit[] memory init) = abi.decode(data, (uint32, AllowlistInit[]));
|
|
58
|
-
|
|
59
|
-
for (uint256 i = 0; i < init.length; i++) {
|
|
60
|
-
setAllowlistTarget(entityId, init[i].target, false, false);
|
|
61
|
-
|
|
62
|
-
if (init[i].hasSelectorAllowlist) {
|
|
63
|
-
for (uint256 j = 0; j < init[i].selectors.length; j++) {
|
|
64
|
-
setAllowlistSelector(entityId, init[i].target, init[i].selectors[j], false);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32)
|
|
71
|
-
external
|
|
72
|
-
view
|
|
73
|
-
override
|
|
74
|
-
returns (uint256)
|
|
75
|
-
{
|
|
76
|
-
checkAllowlistCalldata(entityId, userOp.callData);
|
|
77
|
-
return 0;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function preRuntimeValidationHook(uint32 entityId, address, uint256, bytes calldata data, bytes calldata)
|
|
81
|
-
external
|
|
82
|
-
view
|
|
83
|
-
override
|
|
84
|
-
{
|
|
85
|
-
checkAllowlistCalldata(entityId, data);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// solhint-disable-next-line no-empty-blocks
|
|
90
|
-
function preSignatureValidationHook(uint32, address, bytes32, bytes calldata) external pure override {}
|
|
91
|
-
|
|
92
|
-
/// @inheritdoc IERC6900Module
|
|
93
|
-
function moduleId() external pure returns (string memory) {
|
|
94
|
-
return "erc6900.allowlist-module.0.0.1";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function setAllowlistTarget(uint32 entityId, address target, bool allowed, bool hasSelectorAllowlist) public {
|
|
98
|
-
AllowlistEntry memory entry = AllowlistEntry(allowed, hasSelectorAllowlist);
|
|
99
|
-
targetAllowlist[entityId][target][msg.sender] = entry;
|
|
100
|
-
emit AllowlistTargetUpdated(entityId, msg.sender, target, entry);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function setAllowlistSelector(uint32 entityId, address target, bytes4 selector, bool allowed) public {
|
|
104
|
-
selectorAllowlist[entityId][target][selector][msg.sender] = allowed;
|
|
105
|
-
bytes24 targetAndSelector = bytes24(bytes24(bytes20(target)) | (bytes24(selector) >> 160));
|
|
106
|
-
emit AllowlistSelectorUpdated(entityId, msg.sender, targetAndSelector, allowed);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function checkAllowlistCalldata(uint32 entityId, bytes calldata callData) public view {
|
|
110
|
-
if (bytes4(callData[:4]) == IERC6900Account.execute.selector) {
|
|
111
|
-
(address target,, bytes memory data) = abi.decode(callData[4:], (address, uint256, bytes));
|
|
112
|
-
_checkCallPermission(entityId, msg.sender, target, data);
|
|
113
|
-
} else if (bytes4(callData[:4]) == IERC6900Account.executeBatch.selector) {
|
|
114
|
-
Call[] memory calls = abi.decode(callData[4:], (Call[]));
|
|
115
|
-
|
|
116
|
-
for (uint256 i = 0; i < calls.length; i++) {
|
|
117
|
-
_checkCallPermission(entityId, msg.sender, calls[i].target, calls[i].data);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function supportsInterface(bytes4 interfaceId)
|
|
123
|
-
public
|
|
124
|
-
view
|
|
125
|
-
virtual
|
|
126
|
-
override(BaseModule, IERC165)
|
|
127
|
-
returns (bool)
|
|
128
|
-
{
|
|
129
|
-
return
|
|
130
|
-
interfaceId == type(IERC6900ValidationHookModule).interfaceId || super.supportsInterface(interfaceId);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function _checkCallPermission(uint32 entityId, address account, address target, bytes memory data)
|
|
134
|
-
internal
|
|
135
|
-
view
|
|
136
|
-
{
|
|
137
|
-
AllowlistEntry storage entry = targetAllowlist[entityId][target][account];
|
|
138
|
-
(bool allowed, bool hasSelectorAllowlist) = (entry.allowed, entry.hasSelectorAllowlist);
|
|
139
|
-
|
|
140
|
-
if (!allowed) {
|
|
141
|
-
revert TargetNotAllowed();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (hasSelectorAllowlist) {
|
|
145
|
-
if (data.length < 4) {
|
|
146
|
-
revert NoSelectorSpecified();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
bytes4 selector = bytes4(data);
|
|
150
|
-
|
|
151
|
-
if (!selectorAllowlist[entityId][target][selector][account]) {
|
|
152
|
-
revert SelectorNotAllowed();
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.20;
|
|
3
|
-
|
|
4
|
-
import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol";
|
|
5
|
-
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
|
|
6
|
-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
7
|
-
|
|
8
|
-
import {Call, IERC6900Account} from "../../interfaces/IERC6900Account.sol";
|
|
9
|
-
|
|
10
|
-
import {IERC6900ExecutionHookModule} from "../../interfaces/IERC6900ExecutionHookModule.sol";
|
|
11
|
-
import {IERC6900Module} from "../../interfaces/IERC6900Module.sol";
|
|
12
|
-
|
|
13
|
-
import {BaseModule, IERC165} from "../BaseModule.sol";
|
|
14
|
-
|
|
15
|
-
/// @title ERC20 Token Limit Module
|
|
16
|
-
/// @author ERC-6900 Authors
|
|
17
|
-
/// @notice This module supports an ERC20 token spend limit. This should be combined with a contract whitelist
|
|
18
|
-
/// module to make sure that token transfers not tracked by the module don't happen.
|
|
19
|
-
/// Note: this module is opinionated on what selectors can be called for token contracts to guard against weird
|
|
20
|
-
/// edge cases like DAI. You wouldn't be able to use uni v2 pairs directly as the pair contract is also the LP
|
|
21
|
-
/// token contract
|
|
22
|
-
contract ERC20TokenLimitModule is BaseModule, IERC6900ExecutionHookModule {
|
|
23
|
-
using UserOperationLib for PackedUserOperation;
|
|
24
|
-
|
|
25
|
-
struct ERC20SpendLimit {
|
|
26
|
-
address token;
|
|
27
|
-
uint256 limit;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
struct SpendLimit {
|
|
31
|
-
bool hasLimit;
|
|
32
|
-
uint256 limit;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
mapping(uint32 entityId => mapping(address token => mapping(address account => SpendLimit))) public limits;
|
|
36
|
-
|
|
37
|
-
error ERC20NotAllowed(address);
|
|
38
|
-
error ExceededTokenLimit();
|
|
39
|
-
error InvalidCalldataLength();
|
|
40
|
-
error SelectorNotAllowed();
|
|
41
|
-
error SpendingRequestNotAllowed(bytes4);
|
|
42
|
-
|
|
43
|
-
/// @inheritdoc IERC6900ExecutionHookModule
|
|
44
|
-
function preExecutionHook(uint32 entityId, address, uint256, bytes calldata data)
|
|
45
|
-
external
|
|
46
|
-
override
|
|
47
|
-
returns (bytes memory)
|
|
48
|
-
{
|
|
49
|
-
(bytes4 selector, bytes memory callData) = _getSelectorAndCalldata(data);
|
|
50
|
-
|
|
51
|
-
if (selector == IERC6900Account.execute.selector) {
|
|
52
|
-
// when calling execute or ERC20 functions directly
|
|
53
|
-
(address token,, bytes memory innerCalldata) = abi.decode(callData, (address, uint256, bytes));
|
|
54
|
-
_decrementLimitIfApplies(entityId, token, innerCalldata);
|
|
55
|
-
} else if (selector == IERC6900Account.executeBatch.selector) {
|
|
56
|
-
Call[] memory calls = abi.decode(callData, (Call[]));
|
|
57
|
-
for (uint256 i = 0; i < calls.length; i++) {
|
|
58
|
-
_decrementLimitIfApplies(entityId, calls[i].target, calls[i].data);
|
|
59
|
-
}
|
|
60
|
-
} else {
|
|
61
|
-
revert SpendingRequestNotAllowed(selector);
|
|
62
|
-
}
|
|
63
|
-
return "";
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/// @inheritdoc IERC6900Module
|
|
67
|
-
/// @param data should be encoded with the entityId of the validation and a list of ERC20 spend limits
|
|
68
|
-
function onInstall(bytes calldata data) external override {
|
|
69
|
-
(uint32 entityId, ERC20SpendLimit[] memory spendLimits) = abi.decode(data, (uint32, ERC20SpendLimit[]));
|
|
70
|
-
|
|
71
|
-
for (uint8 i = 0; i < spendLimits.length; i++) {
|
|
72
|
-
address token = spendLimits[i].token;
|
|
73
|
-
updateLimits(entityId, token, true, spendLimits[i].limit);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/// @inheritdoc IERC6900Module
|
|
78
|
-
/// @notice uninstall this module can only clear limit for one token of one entity. To clear all limits, users
|
|
79
|
-
/// are recommended to use updateLimit for each token and entityId.
|
|
80
|
-
/// @param data should be encoded with the entityId of the validation and the token address to be uninstalled
|
|
81
|
-
function onUninstall(bytes calldata data) external override {
|
|
82
|
-
(address token, uint32 entityId) = abi.decode(data, (address, uint32));
|
|
83
|
-
delete limits[entityId][token][msg.sender];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/// @inheritdoc IERC6900ExecutionHookModule
|
|
87
|
-
function postExecutionHook(uint32, bytes calldata) external pure override {
|
|
88
|
-
revert NotImplemented();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/// @inheritdoc IERC6900Module
|
|
92
|
-
function moduleId() external pure returns (string memory) {
|
|
93
|
-
return "erc6900.erc20-token-limit-module.1.0.0";
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/// @notice Update the token limit of a validation
|
|
97
|
-
/// @param entityId The validation entityId to update
|
|
98
|
-
/// @param token The token address whose limit will be updated
|
|
99
|
-
/// @param newLimit The new limit of the token for the validation
|
|
100
|
-
function updateLimits(uint32 entityId, address token, bool hasLimit, uint256 newLimit) public {
|
|
101
|
-
if (token == address(0)) {
|
|
102
|
-
revert ERC20NotAllowed(address(0));
|
|
103
|
-
}
|
|
104
|
-
limits[entityId][token][msg.sender] = SpendLimit({hasLimit: hasLimit, limit: newLimit});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/// @inheritdoc BaseModule
|
|
108
|
-
function supportsInterface(bytes4 interfaceId) public view override(BaseModule, IERC165) returns (bool) {
|
|
109
|
-
return interfaceId == type(IERC6900ExecutionHookModule).interfaceId || super.supportsInterface(interfaceId);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function _decrementLimitIfApplies(uint32 entityId, address token, bytes memory innerCalldata) internal {
|
|
113
|
-
SpendLimit storage spendLimit = limits[entityId][token][msg.sender];
|
|
114
|
-
|
|
115
|
-
if (!spendLimit.hasLimit) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (innerCalldata.length < 68) {
|
|
120
|
-
revert InvalidCalldataLength();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
bytes4 selector;
|
|
124
|
-
uint256 spend;
|
|
125
|
-
assembly ("memory-safe") {
|
|
126
|
-
selector := mload(add(innerCalldata, 32)) // 0:32 is arr len, 32:36 is selector
|
|
127
|
-
spend := mload(add(innerCalldata, 68)) // 36:68 is recipient, 68:100 is spend
|
|
128
|
-
}
|
|
129
|
-
if (_isAllowedERC20Function(selector)) {
|
|
130
|
-
uint256 limit = spendLimit.limit;
|
|
131
|
-
if (spend > limit) {
|
|
132
|
-
revert ExceededTokenLimit();
|
|
133
|
-
}
|
|
134
|
-
unchecked {
|
|
135
|
-
spendLimit.limit = limit - spend;
|
|
136
|
-
}
|
|
137
|
-
} else {
|
|
138
|
-
revert SelectorNotAllowed();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function _isAllowedERC20Function(bytes4 selector) internal pure returns (bool) {
|
|
143
|
-
return selector == IERC20.transfer.selector || selector == IERC20.approve.selector;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.20;
|
|
3
|
-
|
|
4
|
-
import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol";
|
|
5
|
-
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
|
|
6
|
-
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
7
|
-
|
|
8
|
-
import {Call, IERC6900Account} from "../../interfaces/IERC6900Account.sol";
|
|
9
|
-
|
|
10
|
-
import {IERC6900ExecutionHookModule} from "../../interfaces/IERC6900ExecutionHookModule.sol";
|
|
11
|
-
import {IERC6900Module} from "../../interfaces/IERC6900Module.sol";
|
|
12
|
-
import {IERC6900ValidationHookModule} from "../../interfaces/IERC6900ValidationHookModule.sol";
|
|
13
|
-
import {BaseModule, IERC165} from "../BaseModule.sol";
|
|
14
|
-
|
|
15
|
-
/// @title Native Token Limit Module
|
|
16
|
-
/// @author ERC-6900 Authors
|
|
17
|
-
/// @notice This module supports a single total native token spend limit.
|
|
18
|
-
/// It tracks a total spend limit across UserOperation gas limits and native token transfers.
|
|
19
|
-
/// If a non whitelisted paymaster is used, UO gas would not cause the limit to decrease.
|
|
20
|
-
/// If a whitelisted paymaster is used, gas is still counted towards the limit
|
|
21
|
-
contract NativeTokenLimitModule is BaseModule, IERC6900ExecutionHookModule, IERC6900ValidationHookModule {
|
|
22
|
-
using UserOperationLib for PackedUserOperation;
|
|
23
|
-
using EnumerableSet for EnumerableSet.Bytes32Set;
|
|
24
|
-
|
|
25
|
-
mapping(uint256 funcIds => mapping(address account => uint256 limit)) public limits;
|
|
26
|
-
// Accounts should add paymasters that still use the accounts tokens here
|
|
27
|
-
// E.g. ERC20 paymasters that pull funds from the account
|
|
28
|
-
mapping(address paymaster => mapping(address account => bool allowed)) public specialPaymasters;
|
|
29
|
-
|
|
30
|
-
error ExceededNativeTokenLimit();
|
|
31
|
-
error ExceededNumberOfEntities();
|
|
32
|
-
|
|
33
|
-
function updateLimits(uint32 entityId, uint256 newLimit) external {
|
|
34
|
-
limits[entityId][msg.sender] = newLimit;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function updateSpecialPaymaster(address paymaster, bool allowed) external {
|
|
38
|
-
specialPaymasters[paymaster][msg.sender] = allowed;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/// @inheritdoc IERC6900ValidationHookModule
|
|
42
|
-
function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32)
|
|
43
|
-
external
|
|
44
|
-
returns (uint256)
|
|
45
|
-
{
|
|
46
|
-
// Decrease limit only if no paymaster is used, or if its a special paymaster
|
|
47
|
-
if (
|
|
48
|
-
userOp.paymasterAndData.length == 0
|
|
49
|
-
|| specialPaymasters[address(bytes20(userOp.paymasterAndData[:20]))][msg.sender]
|
|
50
|
-
) {
|
|
51
|
-
uint256 vgl = UserOperationLib.unpackVerificationGasLimit(userOp);
|
|
52
|
-
uint256 cgl = UserOperationLib.unpackCallGasLimit(userOp);
|
|
53
|
-
uint256 pvgl;
|
|
54
|
-
uint256 ppogl;
|
|
55
|
-
if (userOp.paymasterAndData.length > 0) {
|
|
56
|
-
// Can skip the EP length check here since it would have reverted there if it was invalid
|
|
57
|
-
(, pvgl, ppogl) = UserOperationLib.unpackPaymasterStaticFields(userOp.paymasterAndData);
|
|
58
|
-
}
|
|
59
|
-
uint256 totalGas = userOp.preVerificationGas + vgl + cgl + pvgl + ppogl;
|
|
60
|
-
uint256 usage = totalGas * UserOperationLib.unpackMaxFeePerGas(userOp);
|
|
61
|
-
|
|
62
|
-
uint256 limit = limits[entityId][msg.sender];
|
|
63
|
-
if (usage > limit) {
|
|
64
|
-
revert ExceededNativeTokenLimit();
|
|
65
|
-
}
|
|
66
|
-
limits[entityId][msg.sender] = limit - usage;
|
|
67
|
-
}
|
|
68
|
-
return 0;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/// @inheritdoc IERC6900ExecutionHookModule
|
|
72
|
-
function preExecutionHook(uint32 entityId, address, uint256, bytes calldata data)
|
|
73
|
-
external
|
|
74
|
-
override
|
|
75
|
-
returns (bytes memory)
|
|
76
|
-
{
|
|
77
|
-
return _checkAndDecrementLimit(entityId, data);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/// @inheritdoc IERC6900Module
|
|
81
|
-
function onInstall(bytes calldata data) external override {
|
|
82
|
-
(uint32 startEntityId, uint256[] memory spendLimits) = abi.decode(data, (uint32, uint256[]));
|
|
83
|
-
|
|
84
|
-
if (startEntityId + spendLimits.length > type(uint32).max) {
|
|
85
|
-
revert ExceededNumberOfEntities();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
for (uint256 i = 0; i < spendLimits.length; i++) {
|
|
89
|
-
limits[i + startEntityId][msg.sender] = spendLimits[i];
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/// @inheritdoc IERC6900Module
|
|
94
|
-
function onUninstall(bytes calldata data) external override {
|
|
95
|
-
// This is the highest entityId that's being used by the account
|
|
96
|
-
uint32 entityId = abi.decode(data, (uint32));
|
|
97
|
-
for (uint256 i = 0; i < entityId; i++) {
|
|
98
|
-
delete limits[i][msg.sender];
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/// @inheritdoc IERC6900ExecutionHookModule
|
|
103
|
-
function postExecutionHook(uint32, bytes calldata) external pure override {
|
|
104
|
-
revert NotImplemented();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// No implementation, no revert
|
|
108
|
-
// Runtime spends no account gas, and we check native token spend limits in exec hooks
|
|
109
|
-
function preRuntimeValidationHook(uint32, address, uint256, bytes calldata, bytes calldata)
|
|
110
|
-
external
|
|
111
|
-
pure
|
|
112
|
-
override
|
|
113
|
-
{} // solhint-disable-line no-empty-blocks
|
|
114
|
-
|
|
115
|
-
// solhint-disable-next-line no-empty-blocks
|
|
116
|
-
function preSignatureValidationHook(uint32, address, bytes32, bytes calldata) external pure override {}
|
|
117
|
-
|
|
118
|
-
/// @inheritdoc IERC6900Module
|
|
119
|
-
function moduleId() external pure returns (string memory) {
|
|
120
|
-
return "erc6900.native-token-limit-module.1.0.0";
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ┏━━━━━━━━━━━━━━━┓
|
|
124
|
-
// ┃ EIP-165 ┃
|
|
125
|
-
// ┗━━━━━━━━━━━━━━━┛
|
|
126
|
-
|
|
127
|
-
/// @inheritdoc BaseModule
|
|
128
|
-
function supportsInterface(bytes4 interfaceId) public view override(BaseModule, IERC165) returns (bool) {
|
|
129
|
-
return interfaceId == type(IERC6900ExecutionHookModule).interfaceId || super.supportsInterface(interfaceId);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function _checkAndDecrementLimit(uint32 entityId, bytes calldata data) internal returns (bytes memory) {
|
|
133
|
-
(bytes4 selector, bytes memory callData) = _getSelectorAndCalldata(data);
|
|
134
|
-
|
|
135
|
-
uint256 value;
|
|
136
|
-
// Get value being sent
|
|
137
|
-
if (selector == IERC6900Account.execute.selector) {
|
|
138
|
-
(, value) = abi.decode(callData, (address, uint256));
|
|
139
|
-
} else if (selector == IERC6900Account.executeBatch.selector) {
|
|
140
|
-
Call[] memory calls = abi.decode(callData, (Call[]));
|
|
141
|
-
for (uint256 i = 0; i < calls.length; i++) {
|
|
142
|
-
value += calls[i].value;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
uint256 limit = limits[entityId][msg.sender];
|
|
147
|
-
if (value > limit) {
|
|
148
|
-
revert ExceededNativeTokenLimit();
|
|
149
|
-
}
|
|
150
|
-
limits[entityId][msg.sender] = limit - value;
|
|
151
|
-
|
|
152
|
-
return "";
|
|
153
|
-
}
|
|
154
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.20;
|
|
3
|
-
|
|
4
|
-
import {IERC6900ValidationModule} from "../../interfaces/IERC6900ValidationModule.sol";
|
|
5
|
-
|
|
6
|
-
interface ISingleSignerValidationModule is IERC6900ValidationModule {
|
|
7
|
-
/// @notice This event is emitted when Signer of the account's validation changes.
|
|
8
|
-
/// @param account The account whose validation Signer changed.
|
|
9
|
-
/// @param entityId The entityId for the account and the signer.
|
|
10
|
-
/// @param newSigner The address of the new signer.
|
|
11
|
-
/// @param previousSigner The address of the previous signer.
|
|
12
|
-
event SignerTransferred(
|
|
13
|
-
address indexed account, uint32 indexed entityId, address indexed newSigner, address previousSigner
|
|
14
|
-
) anonymous;
|
|
15
|
-
|
|
16
|
-
error NotAuthorized();
|
|
17
|
-
|
|
18
|
-
/// @notice Transfer Signer of the account's validation to `newSigner`.
|
|
19
|
-
/// @param entityId The entityId for the account and the signer.
|
|
20
|
-
/// @param newSigner The address of the new signer.
|
|
21
|
-
function transferSigner(uint32 entityId, address newSigner) external;
|
|
22
|
-
}
|