11/// <reference path="./solc.d.ts" />
22
3- import {
4- compile ,
5- type SolcOutput ,
6- tryResolveImport ,
7- version ,
8- } from '@parity/resolc'
3+ import * as resolc from '@parity/resolc'
94import solc from 'solc'
105import { basename , join } from '@std/path'
6+ import * as log from '@std/log'
7+ import { parseArgs } from '@std/cli'
8+
9+ type CompileInput = Parameters < typeof resolc . compile > [ 0 ]
10+ const LOG_LEVEL = ( Deno . env . get ( 'LOG_LEVEL' ) ?. toUpperCase ( ) ??
11+ 'INFO' ) as log . LevelName
12+ log . setup ( {
13+ handlers : {
14+ console : new log . ConsoleHandler ( LOG_LEVEL ) ,
15+ } ,
16+ loggers : {
17+ default : {
18+ level : LOG_LEVEL ,
19+ handlers : [ 'console' ] ,
20+ } ,
21+ } ,
22+ } )
23+
24+ const logger = log . getLogger ( )
25+ const { filter, solcOnly, force } = parseArgs ( Deno . args , {
26+ string : [ 'filter' ] ,
27+ boolean : [ 'solcOnly' , 'force' ] ,
28+ } )
29+
30+ async function computeSha256 ( content : string ) : Promise < string > {
31+ const encoder = new TextEncoder ( )
32+ const data = encoder . encode ( content )
33+ const hashBuffer = await crypto . subtle . digest ( 'SHA-256' , data )
34+ const hashArray = Array . from ( new Uint8Array ( hashBuffer ) )
35+ return hashArray . map ( ( b ) => b . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' )
36+ }
1137
12- type CompileInput = Parameters < typeof compile > [ 0 ]
38+ function readCachedHash ( hashFile : string ) : string | null {
39+ try {
40+ return Deno . readTextFileSync ( hashFile ) . trim ( )
41+ } catch {
42+ return null
43+ }
44+ }
1345
14- const args = Deno . args
15- const filter = args . includes ( '-f' ) || args . includes ( '--filter' )
16- ? args [ args . indexOf ( '-f' ) + 1 ] || args [ args . indexOf ( '--filter' ) + 1 ]
17- : undefined
18- const solcOnly = args . includes ( '-s' ) || args . includes ( '--solcOnly' )
46+ function writeCachedHash ( hashFile : string , hash : string ) : void {
47+ Deno . writeTextFileSync ( hashFile , hash )
48+ }
1949
20- function evmCompile ( sources : CompileInput ) {
50+ let resolcVersion = ''
51+ async function pvmCompile ( file : Deno . DirEntry , sources : CompileInput ) {
52+ if ( resolcVersion === '' ) {
53+ if ( Deno . env . get ( 'REVIVE_BIN' ) === undefined ) {
54+ resolcVersion = ` @parity/resolc: ${ resolc . version ( ) . trim ( ) } `
55+ } else {
56+ resolcVersion = new TextDecoder ( )
57+ . decode (
58+ (
59+ await new Deno . Command ( 'resolc' , {
60+ args : [ '--version' ] ,
61+ stdout : 'piped' ,
62+ } ) . output ( )
63+ ) . stdout ,
64+ )
65+ . trim ( )
66+ }
67+ }
68+ logger . info ( `Compiling ${ file . name } with revive ${ resolcVersion } ` )
69+ return await resolc . compile ( sources , {
70+ bin : Deno . env . get ( 'REVIVE_BIN' ) ,
71+ } )
72+ }
73+
74+ let solcVersion = ''
75+ function evmCompile ( file : Deno . DirEntry , sources : CompileInput ) {
76+ if ( solcVersion === '' ) {
77+ solcVersion = solc . version ( )
78+ }
79+ logger . info ( `Compile ${ file . name } with solc ${ solcVersion } ` )
2180 const input = {
2281 language : 'Solidity' ,
2382 sources,
@@ -32,84 +91,87 @@ function evmCompile(sources: CompileInput) {
3291
3392 return solc . compile ( JSON . stringify ( input ) , {
3493 import : ( relativePath : string ) => {
35- const source = Deno . readTextFileSync ( tryResolveImport ( relativePath ) )
94+ const source = Deno . readTextFileSync (
95+ resolc . tryResolveImport ( relativePath ) ,
96+ )
3697 return { contents : source }
3798 } ,
3899 } )
39100}
40101
41- console . log ( 'Compiling contracts...' )
102+ logger . debug ( 'Compiling contracts...' )
42103
43104const currentDir = new URL ( '.' , import . meta. url ) . pathname
44105const rootDir = join ( currentDir , '..' )
45106const contractsDir = join ( rootDir , 'contracts' )
46- const abiDir = join ( rootDir , 'abi' )
47- const pvmDir = join ( rootDir , 'pvm' )
48- const evmDir = join ( rootDir , 'evm' )
107+ const codegenDir = join ( rootDir , 'codegen' )
108+ const abiDir = join ( codegenDir , 'abi' )
109+ const pvmDir = join ( codegenDir , 'pvm' )
110+ const evmDir = join ( codegenDir , 'evm' )
49111
50112const input = Array . from ( Deno . readDirSync ( contractsDir ) )
51113 . filter ( ( f ) => f . isFile && f . name . endsWith ( '.sol' ) )
52114 . filter ( ( f ) => ! filter || f . name . includes ( filter ) )
53115
54116for ( const file of input ) {
55- console . log ( `🔨 Compiling ${ file . name } ...` )
56117 const name = basename ( file . name )
118+ const sourceFilePath = join ( contractsDir , file . name )
119+ const sourceContent = Deno . readTextFileSync ( sourceFilePath )
120+ const sourceHash = await computeSha256 ( sourceContent )
57121 const inputSources = {
58122 [ name ] : {
59- content : Deno . readTextFileSync ( join ( contractsDir , file . name ) ) ,
123+ content : sourceContent ,
60124 } ,
61125 }
62126
63- if ( ! solcOnly ) {
64- if ( Deno . env . get ( 'REVIVE_BIN' ) === undefined ) {
65- console . log ( `Compiling with revive @parity/resolc: ${ version ( ) } ...` )
66- } else {
67- // add the result of resolc --version
68-
69- const output = new TextDecoder ( ) . decode (
70- (
71- await new Deno . Command ( 'resolc' , {
72- args : [ '--version' ] ,
73- stdout : 'piped' ,
74- } ) . output ( )
75- ) . stdout ,
76- )
77- console . log (
78- `Compiling with revive (using ${
79- Deno . env . get (
80- 'REVIVE_BIN' ,
81- )
82- } - ${ output } )...`,
83- )
84- }
85- const reviveOut = await compile ( inputSources , {
86- bin : Deno . env . get ( 'REVIVE_BIN' ) ,
87- } )
127+ // Create marker files to track if this source has been compiled
128+ const pvmSourceMarkerFile = join ( pvmDir , `.${ name } .sha256.txt` )
129+ const pvmSourceMarkerHash = readCachedHash ( pvmSourceMarkerFile )
130+ const needsPvmCompilation = ! solcOnly &&
131+ ( force || pvmSourceMarkerHash !== sourceHash )
132+
133+ const evmSourceMarkerFile = join ( evmDir , `.${ name } .sha256.txt` )
134+ const evmSourceMarkerHash = readCachedHash ( evmSourceMarkerFile )
135+ const needsEvmCompilation = force || evmSourceMarkerHash !== sourceHash
136+
137+ if ( needsPvmCompilation ) {
138+ const reviveOut = await pvmCompile ( file , inputSources )
88139
89140 for ( const contracts of Object . values ( reviveOut . contracts ) ) {
90141 for ( const [ name , contract ] of Object . entries ( contracts ) ) {
91142 if ( contract ?. evm ?. bytecode ?. object ) {
92- console . log ( `📜 Add PVM contract ${ name } ` )
143+ const pvmFile = join ( pvmDir , `${ name } .polkavm` )
144+ logger . info ( `📜 Add PVM contract ${ name } ` )
93145 const bytecode = new Uint8Array (
94146 contract . evm . bytecode . object
95147 . match ( / .{ 1 , 2 } / g) !
96148 . map ( ( byte ) => parseInt ( byte , 16 ) ) ,
97149 )
98- Deno . writeFileSync (
99- join ( pvmDir , `${ name } .polkavm` ) ,
100- bytecode ,
101- )
150+ Deno . writeFileSync ( pvmFile , bytecode )
102151 }
103152 }
104153 }
154+ writeCachedHash ( pvmSourceMarkerFile , sourceHash )
155+ } else if ( ! solcOnly ) {
156+ logger . debug (
157+ `⏭️ Skipping PVM compilation for ${ file . name } (unchanged)` ,
158+ )
159+ }
160+
161+ if ( ! needsEvmCompilation ) {
162+ logger . debug (
163+ `⏭️ Skipping EVM compilation for ${ file . name } (unchanged)` ,
164+ )
165+ continue
105166 }
106167
107- console . log ( `Compile with solc ${ file . name } ` )
108- const evmOut = JSON . parse ( evmCompile ( inputSources ) ) as SolcOutput
168+ const evmOut = JSON . parse (
169+ evmCompile ( file , inputSources ) ,
170+ ) as resolc . SolcOutput
109171
110172 if ( evmOut . errors ) {
111173 for ( const error of evmOut . errors ) {
112- console . error ( error . formattedMessage )
174+ logger . error ( error . formattedMessage )
113175 }
114176
115177 if ( evmOut . errors . some ( ( err ) => err . severity !== 'warning' ) ) {
@@ -119,21 +181,24 @@ for (const file of input) {
119181
120182 for ( const contracts of Object . values ( evmOut . contracts ) ) {
121183 for ( const [ name , contract ] of Object . entries ( contracts ) ) {
122- console . log ( `📜 Add EVM contract ${ name } ` )
184+ const evmFile = join ( evmDir , `${ name } .bin` )
185+ const abiFile = join ( abiDir , `${ name } .ts` )
123186
124187 // Only write bytecode if it exists and is not empty
125188 if ( contract . evm ?. bytecode ?. object ) {
126189 const bytecodeHex = contract . evm . bytecode . object
127190 if ( bytecodeHex . length > 0 ) {
191+ logger . info ( `📜 Add EVM contract ${ name } ` )
128192 const bytecode = new Uint8Array (
129193 bytecodeHex
130194 . match ( / .{ 1 , 2 } / g) !
131195 . map ( ( byte ) => parseInt ( byte , 16 ) ) ,
132196 )
133- Deno . writeFileSync ( join ( evmDir , ` ${ name } .bin` ) , bytecode )
197+ Deno . writeFileSync ( evmFile , bytecode )
134198 }
135199 }
136200
201+ logger . info ( `📜 Add ABI ${ name } ` )
137202 const abi = contract . abi
138203 const abiName = `${ name } Abi`
139204 const tsContent = `export const ${ abiName } = ${
@@ -143,7 +208,14 @@ for (const file of input) {
143208 2 ,
144209 )
145210 } as const\n`
146- Deno . writeTextFileSync ( join ( abiDir , ` ${ name } .ts` ) , tsContent )
211+ Deno . writeTextFileSync ( abiFile , tsContent )
147212 }
148213 }
214+
215+ // Mark that we've compiled this source file for EVM
216+ writeCachedHash ( evmSourceMarkerFile , sourceHash )
217+
218+ if ( needsEvmCompilation || needsPvmCompilation ) {
219+ logger . info ( `✅ Compiled ${ file . name } successfully` )
220+ }
149221}
0 commit comments