Skip to content

Commit 47bf26e

Browse files
committed
feat(contracts): implement ERC-7579 on existing modules
Add ERC-7579 compliance to SpendingLimitHook and RecoveryModule: MpcSpendingLimitHook (Type 4: Hook): - onInstall() with spending config initialization - onUninstall() with state cleanup - isModuleType() returns true for type 4 MpcRecoveryModule (Type 2: Executor): - onInstall() with guardian setup and delay config - onUninstall() with full state cleanup - isModuleType() returns true for type 2 Both modules now support standard lifecycle management.
1 parent 273166e commit 47bf26e

2 files changed

Lines changed: 181 additions & 2 deletions

File tree

contracts/src/modules/MpcRecoveryModule.sol

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ pragma solidity ^0.8.24;
33

44
import { IMpcRecoveryModule } from "../interfaces/IMpcRecoveryModule.sol";
55
import { IMpcSmartAccount } from "../interfaces/IMpcSmartAccount.sol";
6+
import { IERC7579Module } from "../interfaces/IERC7579Module.sol";
67

78
/**
89
* @title MpcRecoveryModule
910
* @author MPC Agent Wallet SDK
1011
* @notice Enables secure MPC public key recovery with time-delayed execution
12+
* @dev Implements IERC7579Module (Type 2: Executor) for ERC-7579 compatibility
1113
*
1214
* @dev Key features:
1315
* - Guardian-initiated recovery (no single point of failure)
@@ -55,11 +57,14 @@ import { IMpcSmartAccount } from "../interfaces/IMpcSmartAccount.sol";
5557
* - Current key holders (AI agent + user) can cancel during delay
5658
* - On-chain visibility ensures transparency
5759
*/
58-
contract MpcRecoveryModule is IMpcRecoveryModule {
60+
contract MpcRecoveryModule is IMpcRecoveryModule, IERC7579Module {
5961
/*//////////////////////////////////////////////////////////////
6062
CONSTANTS
6163
//////////////////////////////////////////////////////////////*/
6264

65+
/// @notice ERC-7579 Executor module type ID
66+
uint256 public constant MODULE_TYPE = 2;
67+
6368
/// @notice Default recovery delay (2 days)
6469
uint256 public constant DEFAULT_RECOVERY_DELAY = 2 days;
6570

@@ -116,6 +121,89 @@ contract MpcRecoveryModule is IMpcRecoveryModule {
116121
_;
117122
}
118123

124+
/*//////////////////////////////////////////////////////////////
125+
ERC-7579 MODULE FUNCTIONS
126+
//////////////////////////////////////////////////////////////*/
127+
128+
/**
129+
* @inheritdoc IERC7579Module
130+
* @dev Initializes recovery for the calling account
131+
* Data format: abi.encode(guardians[], recoveryDelay)
132+
*/
133+
function onInstall(bytes calldata data) external override {
134+
address account = msg.sender;
135+
136+
if (_initialized[account]) {
137+
revert AlreadyInitialized(account);
138+
}
139+
140+
// Decode initialization data
141+
(address[] memory guardians, uint256 recoveryDelay) = abi.decode(data, (address[], uint256));
142+
143+
if (recoveryDelay < MIN_RECOVERY_DELAY || recoveryDelay > MAX_RECOVERY_DELAY) {
144+
revert RecoveryDelayTooShort();
145+
}
146+
147+
_recoveryDelays[account] = recoveryDelay;
148+
_initialized[account] = true;
149+
150+
// Add guardians
151+
for (uint256 i = 0; i < guardians.length; i++) {
152+
if (guardians[i] != address(0) && !_guardians[account][guardians[i]]) {
153+
_guardians[account][guardians[i]] = true;
154+
_guardianList[account].push(guardians[i]);
155+
emit GuardianAdded(account, guardians[i]);
156+
}
157+
}
158+
159+
emit ModuleInstalled(account);
160+
}
161+
162+
/**
163+
* @inheritdoc IERC7579Module
164+
* @dev Cleans up recovery configuration for the calling account
165+
*/
166+
function onUninstall(bytes calldata /* data */) external override {
167+
address account = msg.sender;
168+
169+
if (!_initialized[account]) {
170+
revert NotInitialized(account);
171+
}
172+
173+
// Cancel any pending recovery
174+
if (_recoveryRequests[account].executeAfter > 0 && !_recoveryRequests[account].executed) {
175+
delete _recoveryRequests[account];
176+
}
177+
178+
// Remove all guardians
179+
address[] storage guardians = _guardianList[account];
180+
for (uint256 i = 0; i < guardians.length; i++) {
181+
_guardians[account][guardians[i]] = false;
182+
}
183+
delete _guardianList[account];
184+
185+
// Clean up other state
186+
delete _recoveryDelays[account];
187+
_initialized[account] = false;
188+
189+
emit ModuleUninstalled(account);
190+
}
191+
192+
/**
193+
* @inheritdoc IERC7579Module
194+
* @dev This is an Executor module (Type 2)
195+
*/
196+
function isModuleType(uint256 moduleTypeId) external pure override returns (bool) {
197+
return moduleTypeId == MODULE_TYPE;
198+
}
199+
200+
/**
201+
* @inheritdoc IERC7579Module
202+
*/
203+
function isInitialized(address account) external view override returns (bool) {
204+
return _initialized[account];
205+
}
206+
119207
/*//////////////////////////////////////////////////////////////
120208
INITIALIZATION
121209
//////////////////////////////////////////////////////////////*/

contracts/src/modules/MpcSpendingLimitHook.sol

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
pragma solidity ^0.8.24;
33

44
import { ISpendingLimitHook } from "../interfaces/ISpendingLimitHook.sol";
5+
import { IERC7579Module } from "../interfaces/IERC7579Module.sol";
56

67
/**
78
* @title MpcSpendingLimitHook
89
* @author MPC Agent Wallet SDK
910
* @notice Spending limit enforcement module for MPC smart accounts
11+
* @dev Implements IERC7579Module (Type 4: Hook) for ERC-7579 compatibility
1012
*
1113
* @dev Key features:
1214
* - Per-transaction ETH limits
@@ -48,11 +50,14 @@ import { ISpendingLimitHook } from "../interfaces/ISpendingLimitHook.sol";
4850
* It validates spending against configured limits in preHook and
4951
* records actual spending in postHook.
5052
*/
51-
contract MpcSpendingLimitHook is ISpendingLimitHook {
53+
contract MpcSpendingLimitHook is ISpendingLimitHook, IERC7579Module {
5254
/*//////////////////////////////////////////////////////////////
5355
CONSTANTS
5456
//////////////////////////////////////////////////////////////*/
5557

58+
/// @notice ERC-7579 Hook module type ID
59+
uint256 public constant MODULE_TYPE = 4;
60+
5661
/// @notice Time period for daily limits
5762
uint256 public constant DAILY_PERIOD = 1 days;
5863

@@ -84,6 +89,92 @@ contract MpcSpendingLimitHook is ISpendingLimitHook {
8489
/// @notice Whitelist per account (account => target => allowed)
8590
mapping(address => mapping(address => bool)) internal _whitelists;
8691

92+
/// @notice Tracks which accounts have initialized this module (ERC-7579)
93+
mapping(address => bool) internal _initialized;
94+
95+
/*//////////////////////////////////////////////////////////////
96+
ERC-7579 MODULE FUNCTIONS
97+
//////////////////////////////////////////////////////////////*/
98+
99+
/**
100+
* @inheritdoc IERC7579Module
101+
* @dev Initializes spending limits for the calling account
102+
* Data format: abi.encode(txLimit, dailyLimit, weeklyLimit, whitelistOnly)
103+
*/
104+
function onInstall(bytes calldata data) external override {
105+
address account = msg.sender;
106+
107+
if (_initialized[account]) {
108+
revert AlreadyInitialized(account);
109+
}
110+
111+
_initialized[account] = true;
112+
113+
// Decode and apply initial configuration if provided
114+
if (data.length > 0) {
115+
(uint256 txLimit, uint256 dailyLimit, uint256 weeklyLimit, bool whitelistOnly) =
116+
abi.decode(data, (uint256, uint256, uint256, bool));
117+
118+
// Validate limits
119+
if (dailyLimit > 0 && weeklyLimit > 0 && weeklyLimit < dailyLimit) {
120+
revert InvalidLimit();
121+
}
122+
123+
_configs[account] = SpendingConfig({
124+
txLimit: txLimit,
125+
dailyLimit: dailyLimit,
126+
weeklyLimit: weeklyLimit,
127+
whitelistOnly: whitelistOnly,
128+
enabled: true
129+
});
130+
131+
_trackers[account] = SpendingTracker({
132+
dailySpent: 0,
133+
weeklySpent: 0,
134+
dailyResetTime: block.timestamp + DAILY_PERIOD,
135+
weeklyResetTime: block.timestamp + WEEKLY_PERIOD
136+
});
137+
138+
emit SpendingConfigured(account, txLimit, dailyLimit, weeklyLimit, whitelistOnly);
139+
}
140+
141+
emit ModuleInstalled(account);
142+
}
143+
144+
/**
145+
* @inheritdoc IERC7579Module
146+
* @dev Cleans up spending configuration for the calling account
147+
*/
148+
function onUninstall(bytes calldata /* data */) external override {
149+
address account = msg.sender;
150+
151+
if (!_initialized[account]) {
152+
revert NotInitialized(account);
153+
}
154+
155+
// Clean up all state for this account
156+
delete _configs[account];
157+
delete _trackers[account];
158+
_initialized[account] = false;
159+
160+
emit ModuleUninstalled(account);
161+
}
162+
163+
/**
164+
* @inheritdoc IERC7579Module
165+
* @dev This is a Hook module (Type 4)
166+
*/
167+
function isModuleType(uint256 moduleTypeId) external pure override returns (bool) {
168+
return moduleTypeId == MODULE_TYPE;
169+
}
170+
171+
/**
172+
* @inheritdoc IERC7579Module
173+
*/
174+
function isInitialized(address account) external view override returns (bool) {
175+
return _initialized[account];
176+
}
177+
87178
/*//////////////////////////////////////////////////////////////
88179
CONFIGURATION
89180
//////////////////////////////////////////////////////////////*/

0 commit comments

Comments
 (0)