@alephium/web3 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,6 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
 
19
19
  import { Buffer } from 'buffer/'
20
- import fs from 'fs'
21
20
  import { promises as fsPromises } from 'fs'
22
21
  import {
23
22
  fromApiNumber256,
@@ -37,7 +36,6 @@ import {
37
36
  fromApiPrimitiveVal,
38
37
  tryGetCallResult
39
38
  } from '../api'
40
- import { CompileProjectResult } from '../api/api-alephium'
41
39
  import {
42
40
  SignDeployContractTxParams,
43
41
  SignDeployContractTxResult,
@@ -63,11 +61,9 @@ import {
63
61
  HexString
64
62
  } from '../utils'
65
63
  import { getCurrentNodeProvider } from '../global'
66
- import * as path from 'path'
67
64
  import { EventSubscribeOptions, EventSubscription, subscribeToEvents } from './events'
68
65
  import { ONE_ALPH, TOTAL_NUMBER_OF_GROUPS } from '../constants'
69
66
  import * as blake from 'blakejs'
70
- import { parseError } from '../utils/error'
71
67
  import { isContractDebugMessageEnabled } from '../debug'
72
68
  import {
73
69
  contract,
@@ -96,14 +92,6 @@ export type Enum = node.Enum
96
92
 
97
93
  export const StdIdFieldName = '__stdInterfaceId'
98
94
 
99
- enum SourceKind {
100
- Contract = 0,
101
- Script = 1,
102
- AbstractContract = 2,
103
- Interface = 3,
104
- Struct = 4
105
- }
106
-
107
95
  export type CompilerOptions = node.CompilerOptions & {
108
96
  errorOnWarnings: boolean
109
97
  }
@@ -119,239 +107,6 @@ export const DEFAULT_NODE_COMPILER_OPTIONS: node.CompilerOptions = {
119
107
 
120
108
  export const DEFAULT_COMPILER_OPTIONS: CompilerOptions = { errorOnWarnings: true, ...DEFAULT_NODE_COMPILER_OPTIONS }
121
109
 
122
- class TypedMatcher<T extends SourceKind> {
123
- matcher: RegExp
124
- type: T
125
-
126
- constructor(pattern: string, type: T) {
127
- this.matcher = new RegExp(pattern, 'mg')
128
- this.type = type
129
- }
130
- }
131
-
132
- function removeParentsPrefix(parts: string[]): string {
133
- let index = 0
134
- for (let i = 0; i < parts.length; i++) {
135
- if (parts[`${i}`] === '..') {
136
- index += 1
137
- } else {
138
- break
139
- }
140
- }
141
- return path.join(...parts.slice(index))
142
- }
143
-
144
- class SourceInfo {
145
- type: SourceKind
146
- name: string
147
- contractRelativePath: string
148
- sourceCode: string
149
- sourceCodeHash: string
150
- isExternal: boolean
151
-
152
- getArtifactPath(artifactsRootDir: string): string {
153
- let fullPath: string
154
- if (this.isExternal) {
155
- const relativePath = removeParentsPrefix(this.contractRelativePath.split(path.sep))
156
- const externalPath = path.join('.external', relativePath)
157
- fullPath = path.join(artifactsRootDir, externalPath)
158
- } else {
159
- fullPath = path.join(artifactsRootDir, this.contractRelativePath)
160
- }
161
- return path.join(path.dirname(fullPath), `${this.name}.ral.json`)
162
- }
163
-
164
- constructor(
165
- type: SourceKind,
166
- name: string,
167
- sourceCode: string,
168
- sourceCodeHash: string,
169
- contractRelativePath: string,
170
- isExternal: boolean
171
- ) {
172
- this.type = type
173
- this.name = name
174
- this.sourceCode = sourceCode
175
- this.sourceCodeHash = sourceCodeHash
176
- this.contractRelativePath = contractRelativePath
177
- this.isExternal = isExternal
178
- }
179
-
180
- static async from(
181
- type: SourceKind,
182
- name: string,
183
- sourceCode: string,
184
- contractRelativePath: string,
185
- isExternal: boolean
186
- ): Promise<SourceInfo> {
187
- const sourceCodeHash = await crypto.subtle.digest('SHA-256', Buffer.from(sourceCode))
188
- const sourceCodeHashHex = Buffer.from(sourceCodeHash).toString('hex')
189
- return new SourceInfo(type, name, sourceCode, sourceCodeHashHex, contractRelativePath, isExternal)
190
- }
191
- }
192
-
193
- class Compiled<T extends Artifact> {
194
- sourceInfo: SourceInfo
195
- artifact: T
196
- warnings: string[]
197
-
198
- constructor(sourceInfo: SourceInfo, artifact: T, warnings: string[]) {
199
- this.sourceInfo = sourceInfo
200
- this.artifact = artifact
201
- this.warnings = warnings
202
- }
203
- }
204
-
205
- type CodeInfo = {
206
- sourceFile: string
207
- sourceCodeHash: string
208
- bytecodeDebugPatch: string
209
- codeHashDebug: string
210
- warnings: string[]
211
- }
212
-
213
- type SourceInfoIndexes = {
214
- sourceInfo: SourceInfo
215
- startIndex: number
216
- endIndex: number
217
- }
218
-
219
- function findSourceInfoAtLineNumber(sources: SourceInfo[], line: number): SourceInfoIndexes | undefined {
220
- let currentLine = 0
221
- const sourceInfosWithLine: SourceInfoIndexes[] = sources.map((source) => {
222
- const startIndex = currentLine + 1
223
- currentLine += source.sourceCode.split('\n').length
224
- const endIndex = currentLine
225
- return { sourceInfo: source, startIndex: startIndex, endIndex: endIndex }
226
- })
227
-
228
- const sourceInfo = sourceInfosWithLine.find((sourceInfoWithLine) => {
229
- return line >= sourceInfoWithLine.startIndex && line <= sourceInfoWithLine.endIndex
230
- })
231
-
232
- return sourceInfo
233
- }
234
-
235
- export class ProjectArtifact {
236
- static readonly artifactFileName = '.project.json'
237
-
238
- fullNodeVersion: string
239
- compilerOptionsUsed: node.CompilerOptions
240
- infos: Map<string, CodeInfo>
241
-
242
- static checkCompilerOptionsParameter(compilerOptions: node.CompilerOptions): void {
243
- if (Object.keys(compilerOptions).length != Object.keys(DEFAULT_NODE_COMPILER_OPTIONS).length) {
244
- throw Error(`Not all compiler options are set: ${compilerOptions}`)
245
- }
246
-
247
- const combined = { ...compilerOptions, ...DEFAULT_NODE_COMPILER_OPTIONS }
248
- if (Object.keys(combined).length !== Object.keys(DEFAULT_NODE_COMPILER_OPTIONS).length) {
249
- throw Error(`There are unknown compiler options: ${compilerOptions}`)
250
- }
251
- }
252
-
253
- constructor(fullNodeVersion: string, compilerOptionsUsed: node.CompilerOptions, infos: Map<string, CodeInfo>) {
254
- ProjectArtifact.checkCompilerOptionsParameter(compilerOptionsUsed)
255
- this.fullNodeVersion = fullNodeVersion
256
- this.compilerOptionsUsed = compilerOptionsUsed
257
- this.infos = infos
258
- }
259
-
260
- static isCodeChanged(current: ProjectArtifact, previous: ProjectArtifact): boolean {
261
- if (current.infos.size !== previous.infos.size) {
262
- return true
263
- }
264
- for (const [name, codeInfo] of current.infos) {
265
- const prevCodeInfo = previous.infos.get(name)
266
- if (prevCodeInfo?.codeHashDebug !== codeInfo.codeHashDebug) {
267
- return true
268
- }
269
- }
270
- return false
271
- }
272
-
273
- async saveToFile(rootPath: string): Promise<void> {
274
- const filepath = path.join(rootPath, ProjectArtifact.artifactFileName)
275
- const artifact = {
276
- fullNodeVersion: this.fullNodeVersion,
277
- compilerOptionsUsed: this.compilerOptionsUsed,
278
- infos: Object.fromEntries(new Map([...this.infos].sort()))
279
- }
280
- const content = JSON.stringify(artifact, null, 2)
281
- return fsPromises.writeFile(filepath, content)
282
- }
283
-
284
- getChangedSources(sourceInfos: SourceInfo[]): SourceInfo[] {
285
- const result: SourceInfo[] = []
286
- for (const sourceInfo of sourceInfos) {
287
- const info = this.infos.get(sourceInfo.name)
288
- if (typeof info === 'undefined' || info.sourceCodeHash !== sourceInfo.sourceCodeHash) {
289
- result.push(sourceInfo)
290
- }
291
- }
292
- return result
293
- }
294
-
295
- needToReCompile(compilerOptions: node.CompilerOptions, fullNodeVersion: string): boolean {
296
- ProjectArtifact.checkCompilerOptionsParameter(compilerOptions)
297
- if (this.fullNodeVersion !== fullNodeVersion) {
298
- return true
299
- }
300
-
301
- const optionsMatched = Object.entries(compilerOptions).every(([key, inputOption]) => {
302
- const usedOption = this.compilerOptionsUsed[`${key}`]
303
- return usedOption === inputOption
304
- })
305
- if (!optionsMatched) {
306
- return true
307
- }
308
-
309
- return false
310
- }
311
-
312
- static async from(rootPath: string): Promise<ProjectArtifact | undefined> {
313
- const filepath = path.join(rootPath, ProjectArtifact.artifactFileName)
314
- if (!fs.existsSync(filepath)) {
315
- return undefined
316
- }
317
- try {
318
- const content = await fsPromises.readFile(filepath)
319
- const json = JSON.parse(content.toString())
320
- const fullNodeVersion = json.fullNodeVersion as string
321
- const compilerOptionsUsed = json.compilerOptionsUsed as node.CompilerOptions
322
- const files = new Map(Object.entries<CodeInfo>(json.infos))
323
- return new ProjectArtifact(fullNodeVersion, compilerOptionsUsed, files)
324
- } catch (error) {
325
- console.error(`Failed to load project artifact, error: ${error}`)
326
- return undefined
327
- }
328
- }
329
- }
330
-
331
- function removeOldArtifacts(dir: string, sourceFiles: SourceInfo[]) {
332
- const files = fs.readdirSync(dir)
333
- files.forEach((file) => {
334
- const filePath = path.join(dir, file)
335
- const stat = fs.statSync(filePath)
336
- if (stat.isDirectory()) {
337
- removeOldArtifacts(filePath, sourceFiles)
338
- } else if (filePath.endsWith('.ral.json') || filePath.endsWith('.ral')) {
339
- const filename = path.basename(filePath)
340
- const artifactName = filename.slice(0, filename.indexOf('.'))
341
- const sourceFile = sourceFiles.find(
342
- (s) => s.name === artifactName && (s.type === SourceKind.Contract || s.type === SourceKind.Script)
343
- )
344
- if (sourceFile === undefined) {
345
- fs.unlinkSync(filePath)
346
- }
347
- }
348
- })
349
-
350
- if (fs.readdirSync(dir).length === 0) {
351
- fs.rmdirSync(dir)
352
- }
353
- }
354
-
355
110
  export class Struct {
356
111
  name: string
357
112
  fieldNames: string[]
@@ -386,527 +141,6 @@ export class Struct {
386
141
  }
387
142
  }
388
143
 
389
- export class Project {
390
- sourceInfos: SourceInfo[]
391
- contracts: Map<string, Compiled<Contract>>
392
- scripts: Map<string, Compiled<Script>>
393
- structs: Struct[]
394
- projectArtifact: ProjectArtifact
395
-
396
- readonly contractsRootDir: string
397
- readonly artifactsRootDir: string
398
-
399
- static currentProject: Project
400
-
401
- static readonly importRegex = new RegExp('^import "[^"./]+/[^"]*[a-z][a-z_0-9]*(.ral)?"', 'mg')
402
- static readonly abstractContractMatcher = new TypedMatcher<SourceKind>(
403
- '^Abstract Contract ([A-Z][a-zA-Z0-9]*)',
404
- SourceKind.AbstractContract
405
- )
406
- static readonly contractMatcher = new TypedMatcher('^Contract ([A-Z][a-zA-Z0-9]*)', SourceKind.Contract)
407
- static readonly interfaceMatcher = new TypedMatcher('^Interface ([A-Z][a-zA-Z0-9]*)', SourceKind.Interface)
408
- static readonly scriptMatcher = new TypedMatcher('^TxScript ([A-Z][a-zA-Z0-9]*)', SourceKind.Script)
409
- static readonly structMatcher = new TypedMatcher('struct ([A-Z][a-zA-Z0-9]*)', SourceKind.Struct)
410
- static readonly matchers = [
411
- Project.abstractContractMatcher,
412
- Project.contractMatcher,
413
- Project.interfaceMatcher,
414
- Project.scriptMatcher,
415
- Project.structMatcher
416
- ]
417
-
418
- static buildProjectArtifact(
419
- fullNodeVersion: string,
420
- sourceInfos: SourceInfo[],
421
- contracts: Map<string, Compiled<Contract>>,
422
- scripts: Map<string, Compiled<Script>>,
423
- compilerOptions: node.CompilerOptions
424
- ): ProjectArtifact {
425
- const files: Map<string, CodeInfo> = new Map()
426
- contracts.forEach((c) => {
427
- files.set(c.artifact.name, {
428
- sourceFile: c.sourceInfo.contractRelativePath,
429
- sourceCodeHash: c.sourceInfo.sourceCodeHash,
430
- bytecodeDebugPatch: c.artifact.bytecodeDebugPatch,
431
- codeHashDebug: c.artifact.codeHashDebug,
432
- warnings: c.warnings
433
- })
434
- })
435
- scripts.forEach((s) => {
436
- files.set(s.artifact.name, {
437
- sourceFile: s.sourceInfo.contractRelativePath,
438
- sourceCodeHash: s.sourceInfo.sourceCodeHash,
439
- bytecodeDebugPatch: s.artifact.bytecodeDebugPatch,
440
- codeHashDebug: '',
441
- warnings: s.warnings
442
- })
443
- })
444
- const compiledSize = contracts.size + scripts.size
445
- sourceInfos.slice(compiledSize).forEach((c) => {
446
- files.set(c.name, {
447
- sourceFile: c.contractRelativePath,
448
- sourceCodeHash: c.sourceCodeHash,
449
- bytecodeDebugPatch: '',
450
- codeHashDebug: '',
451
- warnings: []
452
- })
453
- })
454
- return new ProjectArtifact(fullNodeVersion, compilerOptions, files)
455
- }
456
-
457
- private constructor(
458
- contractsRootDir: string,
459
- artifactsRootDir: string,
460
- sourceInfos: SourceInfo[],
461
- contracts: Map<string, Compiled<Contract>>,
462
- scripts: Map<string, Compiled<Script>>,
463
- structs: Struct[],
464
- errorOnWarnings: boolean,
465
- projectArtifact: ProjectArtifact
466
- ) {
467
- this.contractsRootDir = contractsRootDir
468
- this.artifactsRootDir = artifactsRootDir
469
- this.sourceInfos = sourceInfos
470
- this.contracts = contracts
471
- this.scripts = scripts
472
- this.structs = structs
473
- this.projectArtifact = projectArtifact
474
-
475
- if (errorOnWarnings) {
476
- Project.checkCompilerWarnings(
477
- [
478
- ...[...contracts.entries()].map((c) => c[1].warnings).flat(),
479
- ...[...scripts.entries()].map((s) => s[1].warnings).flat()
480
- ],
481
- errorOnWarnings
482
- )
483
- }
484
- }
485
-
486
- static checkCompilerWarnings(warnings: string[], errorOnWarnings: boolean): void {
487
- if (warnings.length !== 0) {
488
- const prefixPerWarning = ' - '
489
- const warningString = prefixPerWarning + warnings.join('\n' + prefixPerWarning)
490
- const output = `Compilation warnings:\n` + warningString + '\n'
491
- if (errorOnWarnings) {
492
- throw new Error(output)
493
- } else {
494
- console.log(output)
495
- }
496
- }
497
- }
498
-
499
- static contract(name: string): Contract {
500
- const contract = Project.currentProject.contracts.get(name)
501
- if (typeof contract === 'undefined') {
502
- throw new Error(`Contract "${name}" does not exist`)
503
- }
504
- return contract.artifact
505
- }
506
-
507
- static script(name: string): Script {
508
- const script = Project.currentProject.scripts.get(name)
509
- if (typeof script === 'undefined') {
510
- throw new Error(`Script "${name}" does not exist`)
511
- }
512
- return script.artifact
513
- }
514
-
515
- private static async loadStructs(artifactsRootDir: string): Promise<Struct[]> {
516
- const filePath = path.join(artifactsRootDir, 'structs.ral.json')
517
- if (!fs.existsSync(filePath)) return []
518
- const content = await fsPromises.readFile(filePath)
519
- const json = JSON.parse(content.toString())
520
- if (!Array.isArray(json)) {
521
- throw Error(`Invalid structs JSON: ${content}`)
522
- }
523
- return Array.from(json).map((item) => Struct.fromJson(item))
524
- }
525
-
526
- private async saveStructsToFile(): Promise<void> {
527
- if (this.structs.length === 0) return
528
- const structs = this.structs.map((s) => s.toJson())
529
- const filePath = path.join(this.artifactsRootDir, 'structs.ral.json')
530
- return fsPromises.writeFile(filePath, JSON.stringify(structs, null, 2))
531
- }
532
-
533
- private async saveArtifactsToFile(
534
- projectRootDir: string,
535
- skipSaveArtifacts: boolean,
536
- changedSources: SourceInfo[]
537
- ): Promise<void> {
538
- const artifactsRootDir = this.artifactsRootDir
539
- const saveToFile = async function (compiled: Compiled<Artifact>): Promise<void> {
540
- const artifactPath = compiled.sourceInfo.getArtifactPath(artifactsRootDir)
541
- const dirname = path.dirname(artifactPath)
542
- if (!fs.existsSync(dirname)) {
543
- fs.mkdirSync(dirname, { recursive: true })
544
- }
545
- return fsPromises.writeFile(artifactPath, compiled.artifact.toString())
546
- }
547
- for (const [_, contract] of this.contracts) {
548
- if (!skipSaveArtifacts || changedSources.find((s) => s.name === contract.sourceInfo.name) !== undefined) {
549
- await saveToFile(contract)
550
- }
551
- }
552
- for (const [_, script] of this.scripts) {
553
- await saveToFile(script)
554
- }
555
- await this.saveStructsToFile()
556
- await this.saveProjectArtifact(projectRootDir, skipSaveArtifacts, changedSources)
557
- }
558
-
559
- private async saveProjectArtifact(projectRootDir: string, skipSaveArtifacts: boolean, changedSources: SourceInfo[]) {
560
- if (skipSaveArtifacts) {
561
- // we should not update the `codeHashDebug` if the `skipSaveArtifacts` is enabled
562
- const prevProjectArtifact = await ProjectArtifact.from(projectRootDir)
563
- if (prevProjectArtifact !== undefined) {
564
- for (const [name, info] of this.projectArtifact.infos) {
565
- if (changedSources.find((s) => s.name === name) === undefined) {
566
- const prevInfo = prevProjectArtifact.infos.get(name)
567
- info.bytecodeDebugPatch = prevInfo?.bytecodeDebugPatch ?? info.bytecodeDebugPatch
568
- info.codeHashDebug = prevInfo?.codeHashDebug ?? info.codeHashDebug
569
- }
570
- }
571
- }
572
- }
573
- await this.projectArtifact.saveToFile(projectRootDir)
574
- }
575
-
576
- contractByCodeHash(codeHash: string): Contract {
577
- const contract = [...this.contracts.values()].find(
578
- (c) => c.artifact.codeHash === codeHash || c.artifact.codeHashDebug == codeHash
579
- )
580
- if (typeof contract === 'undefined') {
581
- throw new Error(`Unknown code with code hash: ${codeHash}`)
582
- }
583
- return contract.artifact
584
- }
585
-
586
- private static async getCompileResult(
587
- provider: NodeProvider,
588
- compilerOptions: node.CompilerOptions,
589
- sources: SourceInfo[]
590
- ): Promise<CompileProjectResult> {
591
- try {
592
- const sourceStr = sources.map((f) => f.sourceCode).join('\n')
593
- return await provider.contracts.postContractsCompileProject({
594
- code: sourceStr,
595
- compilerOptions: compilerOptions
596
- })
597
- } catch (error) {
598
- if (!(error instanceof Error)) {
599
- throw error
600
- }
601
-
602
- const parsed = parseError(error.message)
603
- if (!parsed) {
604
- throw error
605
- }
606
-
607
- const sourceInfo = findSourceInfoAtLineNumber(sources, parsed.lineStart)
608
- if (!sourceInfo) {
609
- throw error
610
- }
611
-
612
- const shiftIndex = parsed.lineStart - sourceInfo.startIndex + 1
613
- const newError = parsed.reformat(shiftIndex, sourceInfo.sourceInfo.contractRelativePath)
614
- throw new Error(newError)
615
- }
616
- }
617
-
618
- private static async compile(
619
- fullNodeVersion: string,
620
- provider: NodeProvider,
621
- sourceInfos: SourceInfo[],
622
- projectRootDir: string,
623
- contractsRootDir: string,
624
- artifactsRootDir: string,
625
- errorOnWarnings: boolean,
626
- compilerOptions: node.CompilerOptions,
627
- changedSources: SourceInfo[],
628
- skipSaveArtifacts = false
629
- ): Promise<Project> {
630
- const removeDuplicates = sourceInfos.reduce((acc: SourceInfo[], sourceInfo: SourceInfo) => {
631
- if (acc.find((info) => info.sourceCodeHash === sourceInfo.sourceCodeHash) === undefined) {
632
- acc.push(sourceInfo)
633
- }
634
- return acc
635
- }, [])
636
-
637
- const result = await Project.getCompileResult(provider, compilerOptions, removeDuplicates)
638
- const contracts = new Map<string, Compiled<Contract>>()
639
- const scripts = new Map<string, Compiled<Script>>()
640
- const structs = result.structs === undefined ? [] : result.structs.map((item) => Struct.fromStructSig(item))
641
- result.contracts.forEach((contractResult) => {
642
- const sourceInfo = sourceInfos.find(
643
- (sourceInfo) => sourceInfo.type === SourceKind.Contract && sourceInfo.name === contractResult.name
644
- )
645
- if (sourceInfo === undefined) {
646
- // this should never happen
647
- throw new Error(`SourceInfo does not exist for contract ${contractResult.name}`)
648
- }
649
- const contract = Contract.fromCompileResult(contractResult, structs)
650
- contracts.set(contract.name, new Compiled(sourceInfo, contract, contractResult.warnings))
651
- })
652
- result.scripts.forEach((scriptResult) => {
653
- const sourceInfo = sourceInfos.find(
654
- (sourceInfo) => sourceInfo.type === SourceKind.Script && sourceInfo.name === scriptResult.name
655
- )
656
- if (sourceInfo === undefined) {
657
- // this should never happen
658
- throw new Error(`SourceInfo does not exist for script ${scriptResult.name}`)
659
- }
660
- const script = Script.fromCompileResult(scriptResult, structs)
661
- scripts.set(script.name, new Compiled(sourceInfo, script, scriptResult.warnings))
662
- })
663
- const projectArtifact = Project.buildProjectArtifact(
664
- fullNodeVersion,
665
- sourceInfos,
666
- contracts,
667
- scripts,
668
- compilerOptions
669
- )
670
- const project = new Project(
671
- contractsRootDir,
672
- artifactsRootDir,
673
- sourceInfos,
674
- contracts,
675
- scripts,
676
- structs,
677
- errorOnWarnings,
678
- projectArtifact
679
- )
680
- await project.saveArtifactsToFile(projectRootDir, skipSaveArtifacts, changedSources)
681
- return project
682
- }
683
-
684
- private static async loadArtifacts(
685
- provider: NodeProvider,
686
- sourceInfos: SourceInfo[],
687
- projectRootDir: string,
688
- contractsRootDir: string,
689
- artifactsRootDir: string,
690
- errorOnWarnings: boolean,
691
- compilerOptions: node.CompilerOptions
692
- ): Promise<Project> {
693
- const projectArtifact = await ProjectArtifact.from(projectRootDir)
694
- if (projectArtifact === undefined) {
695
- throw Error('Failed to load project artifact')
696
- }
697
- try {
698
- const contracts = new Map<string, Compiled<Contract>>()
699
- const scripts = new Map<string, Compiled<Script>>()
700
- const structs = await Project.loadStructs(artifactsRootDir)
701
- for (const sourceInfo of sourceInfos) {
702
- const info = projectArtifact.infos.get(sourceInfo.name)
703
- if (typeof info === 'undefined') {
704
- throw Error(`Unable to find project info for ${sourceInfo.name}, please rebuild the project`)
705
- }
706
- const warnings = info.warnings
707
- const artifactDir = sourceInfo.getArtifactPath(artifactsRootDir)
708
- if (sourceInfo.type === SourceKind.Contract) {
709
- const artifact = await Contract.fromArtifactFile(
710
- artifactDir,
711
- info.bytecodeDebugPatch,
712
- info.codeHashDebug,
713
- structs
714
- )
715
- contracts.set(artifact.name, new Compiled(sourceInfo, artifact, warnings))
716
- } else if (sourceInfo.type === SourceKind.Script) {
717
- const artifact = await Script.fromArtifactFile(artifactDir, info.bytecodeDebugPatch, structs)
718
- scripts.set(artifact.name, new Compiled(sourceInfo, artifact, warnings))
719
- }
720
- }
721
-
722
- return new Project(
723
- contractsRootDir,
724
- artifactsRootDir,
725
- sourceInfos,
726
- contracts,
727
- scripts,
728
- structs,
729
- errorOnWarnings,
730
- projectArtifact
731
- )
732
- } catch (error) {
733
- console.log(`Failed to load artifacts, error: ${error}, try to re-compile contracts...`)
734
- return Project.compile(
735
- projectArtifact.fullNodeVersion,
736
- provider,
737
- sourceInfos,
738
- projectRootDir,
739
- contractsRootDir,
740
- artifactsRootDir,
741
- errorOnWarnings,
742
- compilerOptions,
743
- sourceInfos
744
- )
745
- }
746
- }
747
-
748
- private static getImportSourcePath(projectRootDir: string, importPath: string): string {
749
- const parts = importPath.split('/')
750
- if (parts.length > 1 && parts[0] === 'std') {
751
- const currentDir = path.dirname(__filename)
752
- return path.join(...[currentDir, '..', '..', '..', importPath])
753
- }
754
- let moduleDir = projectRootDir
755
- while (true) {
756
- const expectedPath = path.join(...[moduleDir, 'node_modules', importPath])
757
- if (fs.existsSync(expectedPath)) {
758
- return expectedPath
759
- }
760
- const oldModuleDir = moduleDir
761
- moduleDir = path.join(moduleDir, '..')
762
- if (oldModuleDir === moduleDir) {
763
- throw new Error(`Specified import file does not exist: ${importPath}`)
764
- }
765
- }
766
- }
767
-
768
- private static async handleImports(
769
- projectRootDir: string,
770
- contractRootDir: string,
771
- sourceStr: string,
772
- importsCache: string[]
773
- ): Promise<[string, SourceInfo[]]> {
774
- const localImportsCache: string[] = []
775
- const result = sourceStr.replace(Project.importRegex, (match) => {
776
- localImportsCache.push(match)
777
- return ''
778
- })
779
- const externalSourceInfos: SourceInfo[] = []
780
- for (const myImport of localImportsCache) {
781
- const originImportPath = myImport.slice(8, -1)
782
- const importPath = originImportPath.endsWith('.ral') ? originImportPath : originImportPath + '.ral'
783
- if (!importsCache.includes(importPath)) {
784
- importsCache.push(importPath)
785
- const sourcePath = Project.getImportSourcePath(projectRootDir, importPath)
786
- const sourceInfos = await Project.loadSourceFile(
787
- projectRootDir,
788
- contractRootDir,
789
- sourcePath,
790
- importsCache,
791
- true
792
- )
793
- externalSourceInfos.push(...sourceInfos)
794
- }
795
- }
796
- return [result, externalSourceInfos]
797
- }
798
-
799
- private static async loadSourceFile(
800
- projectRootDir: string,
801
- contractsRootDir: string,
802
- sourcePath: string,
803
- importsCache: string[],
804
- isExternal: boolean
805
- ): Promise<SourceInfo[]> {
806
- const contractRelativePath = path.relative(contractsRootDir, sourcePath)
807
- if (!sourcePath.endsWith('.ral')) {
808
- throw new Error(`Invalid filename: ${sourcePath}, smart contract file name should end with ".ral"`)
809
- }
810
-
811
- const sourceBuffer = await fsPromises.readFile(sourcePath)
812
- const [sourceStr, externalSourceInfos] = await Project.handleImports(
813
- projectRootDir,
814
- contractsRootDir,
815
- sourceBuffer.toString(),
816
- importsCache
817
- )
818
- if (sourceStr.match(new RegExp('^import "', 'mg')) !== null) {
819
- throw new Error(`Invalid import statements, source: ${sourcePath}`)
820
- }
821
- const sourceInfos = externalSourceInfos
822
- for (const matcher of this.matchers) {
823
- const results = sourceStr.matchAll(matcher.matcher)
824
- for (const result of results) {
825
- const sourceInfo = await SourceInfo.from(matcher.type, result[1], sourceStr, contractRelativePath, isExternal)
826
- sourceInfos.push(sourceInfo)
827
- }
828
- }
829
- return sourceInfos
830
- }
831
-
832
- private static async loadSourceFiles(projectRootDir: string, contractsRootDir: string): Promise<SourceInfo[]> {
833
- const importsCache: string[] = []
834
- const sourceInfos: SourceInfo[] = []
835
- const loadDir = async function (dirPath: string): Promise<void> {
836
- const dirents = await fsPromises.readdir(dirPath, { withFileTypes: true })
837
- for (const dirent of dirents) {
838
- if (dirent.isFile()) {
839
- const sourcePath = path.join(dirPath, dirent.name)
840
- const infos = await Project.loadSourceFile(projectRootDir, contractsRootDir, sourcePath, importsCache, false)
841
- sourceInfos.push(...infos)
842
- } else {
843
- const newPath = path.join(dirPath, dirent.name)
844
- await loadDir(newPath)
845
- }
846
- }
847
- }
848
- await loadDir(contractsRootDir)
849
- const contractAndScriptSize = sourceInfos.filter(
850
- (f) => f.type === SourceKind.Contract || f.type === SourceKind.Script
851
- ).length
852
- if (sourceInfos.length === 0 || contractAndScriptSize === 0) {
853
- throw new Error('Project have no source files')
854
- }
855
- return sourceInfos.sort((a, b) => a.type - b.type)
856
- }
857
-
858
- static readonly DEFAULT_CONTRACTS_DIR = 'contracts'
859
- static readonly DEFAULT_ARTIFACTS_DIR = 'artifacts'
860
-
861
- static async build(
862
- compilerOptionsPartial: Partial<CompilerOptions> = {},
863
- projectRootDir = '.',
864
- contractsRootDir = Project.DEFAULT_CONTRACTS_DIR,
865
- artifactsRootDir = Project.DEFAULT_ARTIFACTS_DIR,
866
- defaultFullNodeVersion: string | undefined = undefined,
867
- skipSaveArtifacts = false
868
- ): Promise<void> {
869
- const provider = getCurrentNodeProvider()
870
- const fullNodeVersion = defaultFullNodeVersion ?? (await provider.infos.getInfosVersion()).version
871
- const sourceFiles = await Project.loadSourceFiles(projectRootDir, contractsRootDir)
872
- const { errorOnWarnings, ...nodeCompilerOptions } = { ...DEFAULT_COMPILER_OPTIONS, ...compilerOptionsPartial }
873
- const projectArtifact = await ProjectArtifact.from(projectRootDir)
874
- const changedSources = projectArtifact?.getChangedSources(sourceFiles) ?? sourceFiles
875
- if (
876
- projectArtifact === undefined ||
877
- projectArtifact.needToReCompile(nodeCompilerOptions, fullNodeVersion) ||
878
- changedSources.length > 0
879
- ) {
880
- if (fs.existsSync(artifactsRootDir)) {
881
- removeOldArtifacts(artifactsRootDir, sourceFiles)
882
- }
883
- console.log(`Compiling contracts in folder "${contractsRootDir}"`)
884
- Project.currentProject = await Project.compile(
885
- fullNodeVersion,
886
- provider,
887
- sourceFiles,
888
- projectRootDir,
889
- contractsRootDir,
890
- artifactsRootDir,
891
- errorOnWarnings,
892
- nodeCompilerOptions,
893
- changedSources,
894
- skipSaveArtifacts
895
- )
896
- }
897
- // we need to reload those contracts that did not regenerate bytecode
898
- Project.currentProject = await Project.loadArtifacts(
899
- provider,
900
- sourceFiles,
901
- projectRootDir,
902
- contractsRootDir,
903
- artifactsRootDir,
904
- errorOnWarnings,
905
- nodeCompilerOptions
906
- )
907
- }
908
- }
909
-
910
144
  export abstract class Artifact {
911
145
  readonly version: string
912
146
  readonly name: string
@@ -1202,11 +436,9 @@ export class Contract extends Artifact {
1202
436
 
1203
437
  static fromApiContractState(
1204
438
  state: node.ContractState,
1205
- getContractByCodeHash?: (codeHash: string) => Contract
439
+ getContractByCodeHash: (codeHash: string) => Contract
1206
440
  ): ContractState {
1207
- const contract = getContractByCodeHash
1208
- ? getContractByCodeHash(state.codeHash)
1209
- : Project.currentProject.contractByCodeHash(state.codeHash)
441
+ const contract = getContractByCodeHash(state.codeHash)
1210
442
  return contract.fromApiContractState(state)
1211
443
  }
1212
444
 
@@ -1228,7 +460,7 @@ export class Contract extends Artifact {
1228
460
  event: node.ContractEventByTxId,
1229
461
  codeHash: string | undefined,
1230
462
  txId: string,
1231
- getContractByCodeHash?: (codeHash: string) => Contract
463
+ getContractByCodeHash: (codeHash: string) => Contract
1232
464
  ): ContractEvent {
1233
465
  let fields: Fields
1234
466
  let name: string
@@ -1240,9 +472,7 @@ export class Contract extends Artifact {
1240
472
  fields = fromApiEventFields(event.fields, Contract.ContractDestroyedEvent, true)
1241
473
  name = Contract.ContractDestroyedEvent.name
1242
474
  } else {
1243
- const contract = getContractByCodeHash
1244
- ? getContractByCodeHash(codeHash!)
1245
- : Project.currentProject.contractByCodeHash(codeHash!)
475
+ const contract = getContractByCodeHash(codeHash!)
1246
476
  const eventSig = contract.eventsSig[event.eventIndex]
1247
477
  fields = fromApiEventFields(event.fields, eventSig)
1248
478
  name = eventSig.name
@@ -1261,7 +491,8 @@ export class Contract extends Artifact {
1261
491
  fromApiTestContractResult(
1262
492
  methodName: string,
1263
493
  result: node.TestContractResult,
1264
- txId: string
494
+ txId: string,
495
+ getContractByCodeHash: (codeHash: string) => Contract
1265
496
  ): TestContractResult<unknown> {
1266
497
  const methodIndex = this.functions.findIndex((sig) => sig.name === methodName)
1267
498
  const returnTypes = this.functions[`${methodIndex}`].returnTypes
@@ -1276,9 +507,9 @@ export class Contract extends Artifact {
1276
507
  contractAddress: result.address,
1277
508
  returns: returns,
1278
509
  gasUsed: result.gasUsed,
1279
- contracts: result.contracts.map((contract) => Contract.fromApiContractState(contract)),
510
+ contracts: result.contracts.map((contract) => Contract.fromApiContractState(contract, getContractByCodeHash)),
1280
511
  txOutputs: result.txOutputs.map(fromApiOutput),
1281
- events: Contract.fromApiEvents(result.events, addressToCodeHash, txId),
512
+ events: Contract.fromApiEvents(result.events, addressToCodeHash, txId, getContractByCodeHash),
1282
513
  debugMessages: result.debugMessages
1283
514
  }
1284
515
  }
@@ -1322,7 +553,7 @@ export class Contract extends Artifact {
1322
553
  events: node.ContractEventByTxId[],
1323
554
  addressToCodeHash: Map<string, string>,
1324
555
  txId: string,
1325
- getContractByCodeHash?: (codeHash: string) => Contract
556
+ getContractByCodeHash: (codeHash: string) => Contract
1326
557
  ): ContractEvent[] {
1327
558
  return events.map((event) => {
1328
559
  const contractAddress = event.contractAddress
@@ -1356,7 +587,7 @@ export class Contract extends Artifact {
1356
587
  result: node.CallContractResult,
1357
588
  txId: string,
1358
589
  methodIndex: number,
1359
- getContractByCodeHash?: (codeHash: string) => Contract
590
+ getContractByCodeHash: (codeHash: string) => Contract
1360
591
  ): CallContractResult<unknown> {
1361
592
  const returnTypes = this.functions[`${methodIndex}`].returnTypes
1362
593
  const callResult = tryGetCallResult(result)
@@ -2042,7 +1273,8 @@ export async function testMethod<
2042
1273
  >(
2043
1274
  factory: ContractFactory<I, F>,
2044
1275
  methodName: string,
2045
- params: Optional<TestContractParams<F, A, M>, 'testArgs' | 'initialFields'>
1276
+ params: Optional<TestContractParams<F, A, M>, 'testArgs' | 'initialFields'>,
1277
+ getContractByCodeHash: (codeHash: string) => Contract
2046
1278
  ): Promise<TestContractResult<R, M>> {
2047
1279
  const txId = params?.txId ?? randomTxId()
2048
1280
  const contract = factory.contract
@@ -2061,7 +1293,7 @@ export async function testMethod<
2061
1293
  })
2062
1294
  const apiResult = await getCurrentNodeProvider().contracts.postContractsTestContract(apiParams)
2063
1295
  const maps = existingContractsToMaps(contract, address, group, apiResult, initialMaps)
2064
- const testResult = contract.fromApiTestContractResult(methodName, apiResult, txId)
1296
+ const testResult = contract.fromApiTestContractResult(methodName, apiResult, txId, getContractByCodeHash)
2065
1297
  contract.printDebugMessages(methodName, testResult.debugMessages)
2066
1298
  return {
2067
1299
  ...testResult,
@@ -2362,7 +1594,7 @@ export async function callMethod<I extends ContractInstance, F extends Fields, A
2362
1594
  instance: ContractInstance,
2363
1595
  methodName: string,
2364
1596
  params: Optional<CallContractParams<A>, 'args'>,
2365
- getContractByCodeHash?: (codeHash: string) => Contract
1597
+ getContractByCodeHash: (codeHash: string) => Contract
2366
1598
  ): Promise<CallContractResult<R>> {
2367
1599
  const methodIndex = contract.contract.getMethodIndex(methodName)
2368
1600
  const txId = params?.txId ?? randomTxId()
@@ -2382,7 +1614,7 @@ export async function multicallMethods<I extends ContractInstance, F extends Fie
2382
1614
  contract: ContractFactory<I, F>,
2383
1615
  instance: ContractInstance,
2384
1616
  calls: Record<string, Optional<CallContractParams<any>, 'args'>>,
2385
- getContractByCodeHash?: (codeHash: string) => Contract
1617
+ getContractByCodeHash: (codeHash: string) => Contract
2386
1618
  ): Promise<Record<string, CallContractResult<any>>> {
2387
1619
  const callEntries = Object.entries(calls)
2388
1620
  const callsParams = callEntries.map((entry) => {