@@ -31,11 +31,13 @@ interface ErrorSignature {
31
31
name : string ;
32
32
parameters : string ;
33
33
origin : string ; // Name of the origin file
34
+ imported : boolean ;
34
35
}
35
36
interface EventSignature {
36
37
name : string ;
37
38
parameters : string ;
38
39
origin : string ; // Name of the origin file
40
+ imported : boolean ;
39
41
}
40
42
interface FunctionSignature {
41
43
name : string ;
@@ -47,36 +49,29 @@ interface FunctionSignature {
47
49
}
48
50
49
51
const facadeConfigPath = './facade.yaml' ; // Configuration file path
50
- const outputDir = './generated' ; // Output directory for generated files
52
+ const facadeDir = './generated' ; // Output directory for generated files
51
53
52
54
async function main ( ) {
53
55
const projectRoot = path . resolve ( __dirname , '../../' ) ;
54
56
process . chdir ( projectRoot ) ;
55
57
56
58
// Ensure output directory exists
57
- await fs . ensureDir ( path . resolve ( outputDir ) ) ;
59
+ await fs . ensureDir ( path . resolve ( facadeDir ) ) ;
60
+ const facadeFiles = await getSolidityFiles ( facadeDir ) ;
58
61
59
62
// Load facade configuration
60
63
const facadeConfigs : FacadeConfig [ ] = await loadFacadeConfig ( ) ;
61
64
62
65
for ( const facadeConfig of facadeConfigs ) {
63
- // Ensure required fields are present
64
- if ( ! facadeConfig . bundleName || ! facadeConfig . bundleDirName ) {
65
- console . error ( 'Error: bundleName and bundleDirName are required in facade.yaml' ) ;
66
- process . exit ( 1 ) ;
67
- }
66
+ validateFacadeConfig ( facadeConfig ) ;
68
67
69
- if ( ! facadeConfig . facades || facadeConfig . facades . length === 0 ) {
70
- console . error ( 'Error: At least one facade must be defined in facade.yaml' ) ;
71
- process . exit ( 1 ) ;
72
- }
68
+ const functionDir = `./src/${ facadeConfig . bundleDirName } /functions` ; // Adjust the path as needed
69
+ const interfaceDir = `./src/${ facadeConfig . bundleDirName } /interfaces` ; // Adjust the path as needed
70
+ // Recursively read all Solidity files in the source directory
71
+ const functionFiles = await getSolidityFiles ( functionDir ) ;
72
+ const interfaceFiles = await getSolidityFiles ( interfaceDir ) ;
73
73
74
- for ( const facade of facadeConfig . facades ) {
75
- if ( ! facade . name ) {
76
- console . error ( 'Error: Each facade must have a name' ) ;
77
- process . exit ( 1 ) ;
78
- }
79
- }
74
+ const regex = / ^ ( I ) ? .* ( E r r o r s | E v e n t s ) \. s o l $ / ;
80
75
81
76
// Process each facade
82
77
for ( const facade of facadeConfig . facades ) {
@@ -86,19 +81,24 @@ async function main() {
86
81
events : [ ] ,
87
82
functions : [ ]
88
83
} ;
89
- const functionDir = `./src/${ facadeConfig . bundleDirName } /functions` ; // Adjust the path as needed
90
- const interfaceDir = `./src/${ facadeConfig . bundleDirName } /interfaces` ; // Adjust the path as needed
91
- // Recursively read all Solidity files in the source directory
92
- const functionFiles = await getSolidityFiles ( functionDir ) ;
93
- const interfaceFiles = await getSolidityFiles ( interfaceDir ) ;
94
-
95
- const regex = / ^ ( I ) ? .* ( E r r o r s | E v e n t s ) \. s o l $ / ;
96
84
97
85
for ( const file of interfaceFiles ) {
98
86
const fileName = path . basename ( file ) ;
99
87
100
- if ( regex . test ( fileName ) ) {
101
- facadeObject . files . push ( { name : fileName . replace ( / \. s o l $ / , "" ) , origin : file . replace ( / \\ / g, '/' ) } ) ;
88
+ if ( ! regex . test ( fileName ) ) {
89
+ continue ;
90
+ }
91
+
92
+ facadeObject . files . push ( { name : fileName . replace ( / \. s o l $ / , "" ) , origin : file . replace ( / \\ / g, '/' ) } ) ;
93
+
94
+ const content = await fs . readFile ( file , 'utf8' ) ;
95
+ try {
96
+ const ast = parse ( content , { tolerant : true } ) ;
97
+
98
+ // Extract functions from the AST
99
+ traverseASTs ( ast , facadeObject , fileName , facade , true ) ;
100
+ } catch ( err ) {
101
+ console . error ( `Error parsing ${ file } :` , err ) ;
102
102
}
103
103
}
104
104
@@ -122,7 +122,42 @@ async function main() {
122
122
}
123
123
124
124
// Generate facade contract
125
- await generateFacadeContract ( facadeObject , facade , facadeConfig ) ;
125
+ const generatedCode = await generateFacadeContract ( facadeObject , facade , facadeConfig ) ;
126
+
127
+ const latestFacade = getLatestVersionFile ( facadeFiles , facade . name ) ;
128
+ if ( latestFacade === null ) {
129
+ writeFacadeContract ( facade . name , [ 1 , 0 , 0 ] , generatedCode ) ;
130
+ process . exit ( 1 ) ;
131
+ }
132
+
133
+ const latestObject : FacadeObjects = {
134
+ files : [ ] ,
135
+ errors : [ ] ,
136
+ events : [ ] ,
137
+ functions : [ ]
138
+ } ;
139
+ try {
140
+ const generatedAst = parse ( generatedCode , { tolerant : true } ) ;
141
+
142
+ // Extract functions from the AST
143
+ traverseASTs ( generatedAst , latestObject , latestFacade . file , facade ) ;
144
+ } catch ( err ) {
145
+ console . error ( `Error parsing ${ latestFacade . file } :` , err ) ;
146
+ process . exit ( 1 ) ;
147
+ }
148
+ const majorDiff = getSymmetricDifference ( facadeObject . functions , latestObject . functions , [ "name" , "parameters" ] ) ;
149
+ if ( majorDiff . length > 0 ) {
150
+ latestFacade . version [ 0 ] ++ ;
151
+ }
152
+ let minorDiff = getSymmetricDifference ( facadeObject . errors , latestObject . errors , [ "name" , "parameters" ] ) ;
153
+ if ( minorDiff . length > 0 ) {
154
+ latestFacade . version [ 1 ] ++ ;
155
+ }
156
+ minorDiff = getSymmetricDifference ( facadeObject . events , latestObject . events , [ "name" , "parameters" ] ) ;
157
+ if ( minorDiff . length > 0 ) {
158
+ latestFacade . version [ 1 ] ++ ;
159
+ }
160
+ writeFacadeContract ( facade . name , latestFacade . version , generatedCode ) ;
126
161
}
127
162
}
128
163
}
@@ -155,6 +190,26 @@ async function loadFacadeConfig(): Promise<FacadeConfig[]> {
155
190
}
156
191
}
157
192
193
+ function validateFacadeConfig ( facadeConfig : FacadeConfig ) {
194
+ // Ensure required fields are present
195
+ if ( ! facadeConfig . bundleName || ! facadeConfig . bundleDirName ) {
196
+ console . error ( 'Error: bundleName and bundleDirName are required in facade.yaml' ) ;
197
+ process . exit ( 1 ) ;
198
+ }
199
+
200
+ if ( ! facadeConfig . facades || facadeConfig . facades . length === 0 ) {
201
+ console . error ( 'Error: At least one facade must be defined in facade.yaml' ) ;
202
+ process . exit ( 1 ) ;
203
+ }
204
+
205
+ for ( const facade of facadeConfig . facades ) {
206
+ if ( ! facade . name ) {
207
+ console . error ( 'Error: Each facade must have a name' ) ;
208
+ process . exit ( 1 ) ;
209
+ }
210
+ }
211
+ }
212
+
158
213
async function getSolidityFiles ( dir : string ) : Promise < string [ ] > {
159
214
let files : string [ ] = [ ] ;
160
215
const items = await fs . readdir ( dir ) ;
@@ -174,57 +229,109 @@ async function getSolidityFiles(dir: string): Promise<string[]> {
174
229
return files ;
175
230
}
176
231
232
+ function getLatestVersionFile ( files : string [ ] , baseName : string ) : { file : string ; version : number [ ] } | null {
233
+ const regex = new RegExp ( `^${ baseName } (?:V(\\d+)_(\\d+)_(\\d+))?\\.sol$` ) ;
234
+
235
+ return files
236
+ . map ( file => {
237
+ const match = file . match ( regex ) ;
238
+ if ( match ) {
239
+ const [ _ , major , minor , patch ] = match . map ( Number ) ;
240
+ return {
241
+ file,
242
+ version : [ major || 1 , minor || 0 , patch || 0 ] ,
243
+ } ;
244
+ }
245
+ return null ;
246
+ } )
247
+ . filter ( ( item ) : item is { file : string ; version : number [ ] } => item !== null )
248
+ . sort ( ( a , b ) => {
249
+ // バージョン配列を比較 (降順)
250
+ for ( let i = 0 ; i < 3 ; i ++ ) {
251
+ if ( b . version [ i ] !== a . version [ i ] ) {
252
+ return b . version [ i ] - a . version [ i ] ;
253
+ }
254
+ }
255
+ return 0 ;
256
+ } ) [ 0 ] || null ;
257
+ }
258
+
259
+ function getSymmetricDifference < T > (
260
+ array1 : T [ ] ,
261
+ array2 : T [ ] ,
262
+ keys : ( keyof T ) [ ]
263
+ ) : T [ ] {
264
+ const isMatch = ( a : T , b : T ) => keys . every ( key => a [ key ] === b [ key ] ) ;
265
+
266
+ // A ∪ B
267
+ const union = [ ...array1 , ...array2 ] ;
268
+ // A ∩ B
269
+ const intersection = array1 . filter ( item1 =>
270
+ array2 . some ( item2 => isMatch ( item1 , item2 ) )
271
+ ) ;
272
+
273
+ // A ∪ B - A ∩ B
274
+ return union . filter ( item =>
275
+ ! intersection . some ( intersectItem => isMatch ( item , intersectItem ) )
276
+ ) ;
277
+ }
278
+
177
279
function traverseASTs (
178
280
ast : any ,
179
281
facadeObjects : FacadeObjects ,
180
282
origin : string ,
181
- facade : FacadeDefinition
283
+ facade : FacadeDefinition ,
284
+ imported : boolean = false
182
285
) {
183
286
if ( ast . type === 'FunctionDefinition' && ast . isConstructor === false ) {
184
287
extractFunctions ( ast , facadeObjects . functions , origin , facade ) ;
185
288
} else if ( ast . type === 'ContractDefinition' ) {
186
289
// Traverse contract sub-nodes
187
290
for ( const subNode of ast . subNodes ) {
188
- traverseASTs ( subNode , facadeObjects , origin , facade ) ;
291
+ traverseASTs ( subNode , facadeObjects , origin , facade , imported ) ;
189
292
}
190
293
} else if ( ast . type === 'SourceUnit' ) {
191
294
// Traverse source unit nodes
192
295
for ( const child of ast . children ) {
193
- traverseASTs ( child , facadeObjects , origin , facade ) ;
296
+ traverseASTs ( child , facadeObjects , origin , facade , imported ) ;
194
297
}
195
298
} else if ( ast . type === 'CustomErrorDefinition' ) {
196
- extractErrors ( ast , facadeObjects . errors , origin ) ;
299
+ extractErrors ( ast , facadeObjects . errors , origin , imported ) ;
197
300
} else if ( ast . type === 'EventDefinition' ) {
198
- extractEvents ( ast , facadeObjects . events , origin ) ;
301
+ extractEvents ( ast , facadeObjects . events , origin , imported ) ;
199
302
}
200
303
}
201
304
202
305
function extractErrors (
203
306
ast : any ,
204
307
errors : ErrorSignature [ ] ,
205
- origin : string
308
+ origin : string ,
309
+ imported : boolean
206
310
) {
207
311
const error : ErrorSignature = {
208
312
name : ast . name ,
209
313
parameters : ast . parameters
210
314
. map ( ( param : any ) => getParameter ( param ) )
211
315
. join ( ', ' ) ,
212
- origin : origin
316
+ origin : origin ,
317
+ imported : imported
213
318
} ;
214
319
errors . push ( error ) ;
215
320
}
216
321
217
322
function extractEvents (
218
323
ast : any ,
219
324
events : EventSignature [ ] ,
220
- origin : string
325
+ origin : string ,
326
+ imported : boolean
221
327
) {
222
328
const event : EventSignature = {
223
329
name : ast . name ,
224
330
parameters : ast . parameters
225
331
. map ( ( param : any ) => getParameter ( param ) )
226
332
. join ( ', ' ) ,
227
- origin : origin
333
+ origin : origin ,
334
+ imported : imported
228
335
} ;
229
336
events . push ( event ) ;
230
337
}
@@ -296,8 +403,8 @@ async function generateFacadeContract(
296
403
objects : FacadeObjects ,
297
404
facade : FacadeDefinition ,
298
405
config : FacadeConfig
299
- ) {
300
- const facadeFilePath = path . join ( outputDir , `${ facade . name } .sol` ) ;
406
+ ) : Promise < string > {
407
+ const facadeFilePath = path . join ( facadeDir , `${ facade . name } .sol` ) ;
301
408
let code = `// SPDX-License-Identifier: MIT
302
409
pragma solidity ^0.8.24;
303
410
@@ -311,10 +418,16 @@ import {Schema} from "src/${config.bundleDirName}/storage/Schema.sol";
311
418
code += `\ncontract ${ facade . name } is Schema, ${ objects . files . map ( ( file ) => file . name ) . join ( ', ' ) } {\n` ;
312
419
313
420
for ( const error of objects . errors ) {
421
+ if ( error . imported ) {
422
+ continue ;
423
+ }
314
424
code += generateError ( error ) ;
315
425
}
316
426
code += `\n` ;
317
427
for ( const event of objects . events ) {
428
+ if ( event . imported ) {
429
+ continue ;
430
+ }
318
431
code += generateEvent ( event ) ;
319
432
}
320
433
code += `\n` ;
@@ -324,11 +437,19 @@ import {Schema} from "src/${config.bundleDirName}/storage/Schema.sol";
324
437
325
438
code += `}\n` ;
326
439
440
+ return code ;
441
+
327
442
// Write the facade contract to the output file
328
443
await fs . writeFile ( facadeFilePath , code ) ;
329
444
console . log ( `Facade contract generated at ${ facadeFilePath } ` ) ;
330
445
}
331
446
447
+ async function writeFacadeContract ( facadeName : string , version : number [ ] , code : string ) {
448
+ // Write the facade contract to the output file
449
+ const facadeFilePath = path . join ( facadeDir , `${ facadeName } V${ version [ 0 ] } _${ version [ 1 ] } _${ version [ 2 ] } .sol` ) ;
450
+ await fs . writeFile ( facadeFilePath , code ) ;
451
+ console . log ( `Facade contract generated at ${ facadeFilePath } ` ) ;
452
+ }
332
453
function generateError ( error : ErrorSignature ) {
333
454
return ` error ${ error . name } (${ error . parameters } );\n`
334
455
}
0 commit comments