@@ -56,12 +56,77 @@ export function buildERC7579(opts: ERC7579Options): Contract {
5656
5757 const c = new ContractBuilder ( allOpts . name ) ;
5858
59+ // Base parent
60+ c . addOverride (
61+ {
62+ name : 'IERC7579Module' ,
63+ } ,
64+ functions . isModuleType ,
65+ ) ;
66+
67+ overrideIsModuleType ( c , allOpts ) ;
5968 addParents ( c , allOpts ) ;
60- addMultisig ( c , allOpts ) ;
69+ overrideValidation ( c , allOpts ) ;
70+ // addAccess(c, allOpts); TODO
71+ // addOnInstall(c, allOpts); TODO
6172
6273 return c ;
6374}
6475
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+
65130function addParents ( c : ContractBuilder , opts : ERC7579Options ) : void {
66131 c . addParent ( {
67132 name : 'IERC7579Module' ,
@@ -88,6 +153,13 @@ function addParents(c: ContractBuilder, opts: ERC7579Options): void {
88153 path : '@openzeppelin/community-contracts/account/modules/ERC7579Validator.sol' ,
89154 } ) ;
90155
156+ if ( opts . validator . signature ) {
157+ c . addParent ( {
158+ name : 'ERC7579Signature' ,
159+ path : '@openzeppelin/community-contracts/account/modules/ERC7579Signature.sol' ,
160+ } ) ;
161+ }
162+
91163 if ( opts . validator . multisig ) {
92164 c . addParent ( {
93165 name : 'ERC7579Multisig' ,
@@ -118,21 +190,31 @@ function addParents(c: ContractBuilder, opts: ERC7579Options): void {
118190 }
119191
120192 if ( opts . fallback ) {
121- // NO OP
193+ // noop
122194 }
123195}
124196
125- function addMultisig ( c : ContractBuilder , opts : ERC7579Options ) : void {
197+ function overrideValidation ( c : ContractBuilder , opts : ERC7579Options ) : void {
126198 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 ;
128201 c . addOverride ( c , fn ) ;
129202 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+ ) ;
131213 c . setFunctionBody (
132214 [
133215 `uint16 executionCalldataLength = uint16(uint256(bytes32(${ fn . args [ 3 ] ! . name } [0:2]))); // First 2 bytes are the length` ,
134216 `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)) );` ,
136218 `require(_rawERC7579Validation(${ fn . args [ 0 ] ! . name } , typeHash, ${ fn . args [ 3 ] ! . name } [2 + executionCalldataLength:])); // Remaining bytes are the signature` ,
137219 `return executionCalldata;` ,
138220 ] ,
@@ -154,13 +236,30 @@ const functions = {
154236 ...defineFunctions ( {
155237 _validateExecution : {
156238 kind : 'internal' as const ,
239+ mutability : 'view' ,
157240 args : [
158241 { name : 'account' , type : 'address' } ,
159- { name : 'hash ' , type : 'bytes32' } ,
242+ { name : 'salt ' , type : 'bytes32' } ,
160243 { name : 'mode' , type : 'bytes32' } ,
161244 { name : 'data' , type : 'bytes calldata' } ,
162245 ] ,
163246 returns : [ 'bytes calldata' ] ,
164247 } ,
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+ } ,
165264 } ) ,
166265} ;
0 commit comments