@@ -56,12 +56,77 @@ export function buildERC7579(opts: ERC7579Options): Contract {
56
56
57
57
const c = new ContractBuilder ( allOpts . name ) ;
58
58
59
+ // Base parent
60
+ c . addOverride (
61
+ {
62
+ name : 'IERC7579Module' ,
63
+ } ,
64
+ functions . isModuleType ,
65
+ ) ;
66
+
67
+ overrideIsModuleType ( c , allOpts ) ;
59
68
addParents ( c , allOpts ) ;
60
- addMultisig ( c , allOpts ) ;
69
+ overrideValidation ( c , allOpts ) ;
70
+ // addAccess(c, allOpts); TODO
71
+ // addOnInstall(c, allOpts); TODO
61
72
62
73
return c ;
63
74
}
64
75
76
+ type IsModuleTypeImplementation = 'ERC7579Executor' | 'ERC7579Validator' | 'IERC7579Hook' | 'Fallback' ;
77
+
78
+ function overrideIsModuleType ( c : ContractBuilder , opts : ERC7579Options ) : void {
79
+ const implementedIn : IsModuleTypeImplementation [ ] = [ 'ERC7579Executor' , 'ERC7579Validator' ] as const ;
80
+ const types : IsModuleTypeImplementation [ ] = [ ] ;
81
+ const fn = functions . isModuleType ;
82
+
83
+ if ( opts . executor ) {
84
+ types . push ( 'ERC7579Executor' ) ;
85
+ c . addOverride ( { name : 'ERC7579Executor' } , fn ) ;
86
+ }
87
+
88
+ if ( opts . validator ) {
89
+ types . push ( 'ERC7579Validator' ) ;
90
+ c . addOverride ( { name : 'ERC7579Validator' } , fn ) ;
91
+ }
92
+
93
+ if ( opts . hook ) {
94
+ types . push ( 'IERC7579Hook' ) ;
95
+ c . addOverride ( { name : 'IERC7579Hook' } , fn ) ;
96
+ }
97
+
98
+ if ( opts . fallback ) {
99
+ types . push ( 'Fallback' ) ;
100
+ }
101
+
102
+ const implementedOverrides = types . filter ( type => implementedIn . includes ( type ) ) ;
103
+ const unimplementedOverrides = types . filter ( type => ! implementedIn . includes ( type ) ) ;
104
+
105
+ if ( implementedOverrides . length === 0 && unimplementedOverrides . length === 1 ) {
106
+ const importedType =
107
+ unimplementedOverrides [ 0 ] ! === 'IERC7579Hook' ? 'MODULE_TYPE_VALIDATOR' : 'MODULE_TYPE_FALLBACK' ;
108
+ c . setFunctionBody ( [ `return ${ fn . args [ 0 ] ! . name } == ${ importedType } ;` ] , fn ) ;
109
+ } else if (
110
+ implementedOverrides . length >= 2 || // 1 = n/a, 2 = defaults to super
111
+ unimplementedOverrides . length > 0 // Require manual comparison
112
+ ) {
113
+ const body : string [ ] = [ ] ;
114
+ for ( const type of implementedOverrides ) {
115
+ body . push ( `bool is${ type } = ${ type } .isModuleType(${ fn . args [ 0 ] ! . name } )` ) ;
116
+ }
117
+ for ( const type of unimplementedOverrides ) {
118
+ const importedType = type === 'IERC7579Hook' ? 'MODULE_TYPE_VALIDATOR' : 'MODULE_TYPE_FALLBACK' ;
119
+ c . addImportOnly ( {
120
+ name : importedType ,
121
+ path : '@openzeppelin/contracts/interfaces/draft-IERC7579.sol' ,
122
+ } ) ;
123
+ body . push ( `bool is${ type } = ${ fn . args [ 0 ] ! . name } == ${ importedType } ;` ) ;
124
+ }
125
+ body . push ( `return ${ types . map ( type => `is${ type } ` ) . join ( ' || ' ) } ;` ) ;
126
+ c . setFunctionBody ( body , fn ) ;
127
+ }
128
+ }
129
+
65
130
function addParents ( c : ContractBuilder , opts : ERC7579Options ) : void {
66
131
c . addParent ( {
67
132
name : 'IERC7579Module' ,
@@ -88,6 +153,13 @@ function addParents(c: ContractBuilder, opts: ERC7579Options): void {
88
153
path : '@openzeppelin/community-contracts/account/modules/ERC7579Validator.sol' ,
89
154
} ) ;
90
155
156
+ if ( opts . validator . signature ) {
157
+ c . addParent ( {
158
+ name : 'ERC7579Signature' ,
159
+ path : '@openzeppelin/community-contracts/account/modules/ERC7579Signature.sol' ,
160
+ } ) ;
161
+ }
162
+
91
163
if ( opts . validator . multisig ) {
92
164
c . addParent ( {
93
165
name : 'ERC7579Multisig' ,
@@ -118,21 +190,31 @@ function addParents(c: ContractBuilder, opts: ERC7579Options): void {
118
190
}
119
191
120
192
if ( opts . fallback ) {
121
- // NO OP
193
+ // noop
122
194
}
123
195
}
124
196
125
- function addMultisig ( c : ContractBuilder , opts : ERC7579Options ) : void {
197
+ function overrideValidation ( c : ContractBuilder , opts : ERC7579Options ) : void {
126
198
if ( opts . executor ) {
127
- const fn = functions . _validateExecution ;
199
+ const delayed = ! opts . executor . delayed ; // Delayed ensures single execution per operation.
200
+ const fn = delayed ? functions . _validateSchedule : functions . _validateExecution ;
128
201
c . addOverride ( c , fn ) ;
129
202
if ( opts . validator ) {
130
- // _rawERC7579Validation available
203
+ c . addParent (
204
+ {
205
+ name : 'EIP712' ,
206
+ path : '@openzeppelin/contracts/utils/cryptography/EIP712.sol' ,
207
+ } ,
208
+ [ opts . name , '1' ] ,
209
+ ) ;
210
+ c . addVariable (
211
+ `bytes32 public constant EXECUTION_TYPEHASH = "Execute(address account,bytes32 salt,${ delayed ? 'uint256 nonce,' : '' } bytes32 mode,bytes executionCalldata)"` ,
212
+ ) ;
131
213
c . setFunctionBody (
132
214
[
133
215
`uint16 executionCalldataLength = uint16(uint256(bytes32(${ fn . args [ 3 ] ! . name } [0:2]))); // First 2 bytes are the length` ,
134
216
`bytes calldata executionCalldata = ${ fn . args [ 3 ] ! . name } [2:2 + executionCalldataLength]; // Next bytes are the calldata` ,
135
- `bytes32 typeHash = _getExecuteTypeHash( ${ fn . args [ 0 ] ! . name } , salt, mode, executionCalldata);` ,
217
+ `bytes32 typeHash = _hashTypedDataV4(keccak256(abi.encode(EXECUTION_TYPEHASH, ${ fn . args [ 0 ] ! . name } , ${ fn . args [ 1 ] ! . name } , ${ delayed ? ` _useNonce( ${ fn . args [ 0 ] ! . name } ),` : '' } ${ fn . args [ 2 ] ! . name } , executionCalldata)) );` ,
136
218
`require(_rawERC7579Validation(${ fn . args [ 0 ] ! . name } , typeHash, ${ fn . args [ 3 ] ! . name } [2 + executionCalldataLength:])); // Remaining bytes are the signature` ,
137
219
`return executionCalldata;` ,
138
220
] ,
@@ -154,13 +236,30 @@ const functions = {
154
236
...defineFunctions ( {
155
237
_validateExecution : {
156
238
kind : 'internal' as const ,
239
+ mutability : 'view' ,
157
240
args : [
158
241
{ name : 'account' , type : 'address' } ,
159
- { name : 'hash ' , type : 'bytes32' } ,
242
+ { name : 'salt ' , type : 'bytes32' } ,
160
243
{ name : 'mode' , type : 'bytes32' } ,
161
244
{ name : 'data' , type : 'bytes calldata' } ,
162
245
] ,
163
246
returns : [ 'bytes calldata' ] ,
164
247
} ,
248
+ _validateSchedule : {
249
+ kind : 'internal' as const ,
250
+ mutability : 'view' ,
251
+ args : [
252
+ { name : 'account' , type : 'address' } ,
253
+ { name : 'salt' , type : 'bytes32' } ,
254
+ { name : 'mode' , type : 'bytes32' } ,
255
+ { name : 'data' , type : 'bytes calldata' } ,
256
+ ] ,
257
+ } ,
258
+ isModuleType : {
259
+ kind : 'public' as const ,
260
+ mutability : 'pure' ,
261
+ args : [ { name : 'moduleTypeId' , type : 'uint256' } ] ,
262
+ returns : [ 'bool' ] ,
263
+ } ,
165
264
} ) ,
166
265
} ;
0 commit comments