@alephium/web3 0.2.0-rc.3 → 0.2.0-rc.6

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.
@@ -26,163 +26,382 @@ import { node } from '../api'
26
26
  import { SignDeployContractTxParams, SignExecuteScriptTxParams, SignerWithNodeProvider } from '../signer'
27
27
  import * as ralph from './ralph'
28
28
  import { bs58, binToHex, contractIdFromAddress, assertType, Eq } from '../utils'
29
+ import { CompileContractResult, CompileScriptResult } from '../api/api-alephium'
30
+
31
+ type FieldsSig = node.FieldsSig
32
+ type EventSig = node.EventSig
33
+ type FunctionSig = node.FunctionSig
34
+
35
+ enum SourceType {
36
+ Contract = 0,
37
+ Script = 1,
38
+ AbstractContract = 2,
39
+ Interface = 3
40
+ }
41
+
42
+ export type CompilerOptions = {
43
+ errorOnWarnings: boolean
44
+ ignoreUnusedConstantsWarnings: boolean
45
+ }
46
+
47
+ export const DEFAULT_COMPILER_OPTIONS: CompilerOptions = {
48
+ errorOnWarnings: true,
49
+ ignoreUnusedConstantsWarnings: true
50
+ }
51
+
52
+ class TypedMatcher<T extends SourceType> {
53
+ matcher: RegExp
54
+ type: T
55
+
56
+ constructor(pattern: string, type: T) {
57
+ this.matcher = new RegExp(pattern, 'mg')
58
+ this.type = type
59
+ }
60
+
61
+ match(str: string): number {
62
+ const results = str.match(this.matcher)
63
+ return results === null ? 0 : results.length
64
+ }
65
+ }
29
66
 
30
67
  class SourceFile {
31
- readonly dirs: string[]
32
- readonly dirPath: string
33
- readonly contractPath: string
34
- readonly artifactPath: string
35
-
36
- constructor(dirs: string[], fileName: string) {
37
- this.dirs = dirs
38
- this.dirPath = dirs.length === 0 ? '' : dirs.join('/') + '/'
39
- if (fileName.endsWith('.json')) {
40
- this.contractPath = './contracts/' + this.dirPath + fileName.slice(0, -5)
41
- this.artifactPath = './artifacts/' + this.dirPath + fileName
42
- } else {
43
- this.contractPath = './contracts/' + this.dirPath + fileName
44
- this.artifactPath = './artifacts/' + this.dirPath + fileName + '.json'
45
- }
68
+ type: SourceType
69
+ contractPath: string
70
+ sourceCode: string
71
+ sourceCodeHash: string
72
+
73
+ getArtifactPath(artifactsRootPath: string): string {
74
+ return artifactsRootPath + this.contractPath.slice(this.contractPath.indexOf('/')) + '.json'
75
+ }
76
+
77
+ constructor(type: SourceType, sourceCode: string, contractPath: string) {
78
+ this.type = type
79
+ this.sourceCode = sourceCode
80
+ this.sourceCodeHash = cryptojs.SHA256(sourceCode).toString()
81
+ this.contractPath = contractPath
46
82
  }
47
83
  }
48
84
 
49
- type FieldsSig = node.FieldsSig
50
- type EventSig = node.EventSig
51
- type FunctionSig = node.FunctionSig
85
+ class Compiled<T extends Artifact> {
86
+ sourceFile: SourceFile
87
+ artifact: T
88
+ warnings: string[]
52
89
 
53
- export abstract class Common {
54
- readonly sourceCodeSha256: string
55
- readonly functions: FunctionSig[]
90
+ constructor(sourceFile: SourceFile, artifact: T, warnings: string[]) {
91
+ this.sourceFile = sourceFile
92
+ this.artifact = artifact
93
+ this.warnings = warnings
94
+ }
95
+ }
56
96
 
57
- static readonly importRegex = new RegExp('^import "([^"/]+/(([^"]+)/)?)?[a-z][a-z_0-9]*.ral"', 'mg')
58
- static readonly contractRegex = new RegExp('^(Abstract[ ]+)?Contract [A-Z][a-zA-Z0-9]*', 'mg')
59
- static readonly interfaceRegex = new RegExp('^Interface [A-Z][a-zA-Z0-9]* \\{', 'mg')
60
- static readonly scriptRegex = new RegExp('^TxScript [A-Z][a-zA-Z0-9]*', 'mg')
61
-
62
- private static _artifactCache: Map<string, Contract | Script> = new Map<string, Contract | Script>()
63
- static artifactCacheCapacity = 20
64
- protected static _getArtifactFromCache(codeHash: string): Contract | Script | undefined {
65
- return this._artifactCache.get(codeHash)
66
- }
67
- protected static _putArtifactToCache(contract: Contract): void {
68
- if (!this._artifactCache.has(contract.codeHash)) {
69
- if (this._artifactCache.size >= this.artifactCacheCapacity) {
70
- const keyToDelete = this._artifactCache.keys().next().value
71
- this._artifactCache.delete(keyToDelete)
97
+ class ProjectArtifact {
98
+ static readonly artifactFileName = '.project.json'
99
+
100
+ infos: Map<string, { sourceCodeHash: string; warnings: string[] }>
101
+
102
+ constructor(infos: Map<string, { sourceCodeHash: string; warnings: string[] }>) {
103
+ this.infos = infos
104
+ }
105
+
106
+ async saveToFile(rootPath: string): Promise<void> {
107
+ const filepath = rootPath + '/' + ProjectArtifact.artifactFileName
108
+ const content = JSON.stringify(Object.fromEntries(this.infos), null, 2)
109
+ return fsPromises.writeFile(filepath, content)
110
+ }
111
+
112
+ sourceHasChanged(files: SourceFile[]): boolean {
113
+ if (files.length !== this.infos.size) {
114
+ return true
115
+ }
116
+ for (const file of files) {
117
+ const info = this.infos.get(file.contractPath)
118
+ if (typeof info === 'undefined' || info.sourceCodeHash !== file.sourceCodeHash) {
119
+ return true
72
120
  }
73
- this._artifactCache.set(contract.codeHash, contract)
74
121
  }
122
+ return false
75
123
  }
76
124
 
77
- constructor(sourceCodeSha256: string, functions: FunctionSig[]) {
78
- this.sourceCodeSha256 = sourceCodeSha256
79
- this.functions = functions
125
+ static async from(rootPath: string): Promise<ProjectArtifact | undefined> {
126
+ const filepath = rootPath + '/' + ProjectArtifact.artifactFileName
127
+ if (!fs.existsSync(filepath)) {
128
+ return undefined
129
+ }
130
+ const content = await fsPromises.readFile(filepath)
131
+ const files = new Map(
132
+ Object.entries<{ sourceCodeHash: string; warnings: string[] }>(JSON.parse(content.toString()))
133
+ )
134
+ return new ProjectArtifact(files)
135
+ }
136
+ }
137
+
138
+ export class Project {
139
+ sourceFiles: SourceFile[]
140
+ contracts: Compiled<Contract>[]
141
+ scripts: Compiled<Script>[]
142
+
143
+ readonly contractsRootPath: string
144
+ readonly artifactsRootPath: string
145
+ readonly nodeProvider: NodeProvider
146
+
147
+ static currentProject: Project
148
+
149
+ static readonly abstractContractMatcher = new TypedMatcher<SourceType>(
150
+ '^Abstract Contract [A-Z][a-zA-Z0-9]*',
151
+ SourceType.AbstractContract
152
+ )
153
+ static readonly contractMatcher = new TypedMatcher('^Contract [A-Z][a-zA-Z0-9]*', SourceType.Contract)
154
+ static readonly interfaceMatcher = new TypedMatcher('^Interface [A-Z][a-zA-Z0-9]* \\{', SourceType.Interface)
155
+ static readonly scriptMatcher = new TypedMatcher('^TxScript [A-Z][a-zA-Z0-9]*', SourceType.Script)
156
+ static readonly matchers = [
157
+ Project.abstractContractMatcher,
158
+ Project.contractMatcher,
159
+ Project.interfaceMatcher,
160
+ Project.scriptMatcher
161
+ ]
162
+
163
+ private constructor(
164
+ provider: NodeProvider,
165
+ contractsRootPath: string,
166
+ artifactsRootPath: string,
167
+ sourceFiles: SourceFile[],
168
+ contracts: Compiled<Contract>[],
169
+ scripts: Compiled<Script>[]
170
+ ) {
171
+ this.nodeProvider = provider
172
+ this.contractsRootPath = contractsRootPath
173
+ this.artifactsRootPath = artifactsRootPath
174
+ this.sourceFiles = sourceFiles
175
+ this.contracts = contracts
176
+ this.scripts = scripts
177
+ }
178
+
179
+ private getContractPath(path: string): string {
180
+ return path.startsWith(`./${this.contractsRootPath}`)
181
+ ? path.slice(2)
182
+ : path.startsWith(this.contractsRootPath)
183
+ ? path
184
+ : this.contractsRootPath + '/' + path
185
+ }
186
+
187
+ private static checkCompilerWarnings(warnings: string[], compilerOptions: CompilerOptions): void {
188
+ const remains = compilerOptions.ignoreUnusedConstantsWarnings
189
+ ? warnings.filter((s) => !s.includes('unused constants'))
190
+ : warnings
191
+ if (remains.length !== 0) {
192
+ const prefixPerWarning = ' - '
193
+ const warningString = prefixPerWarning + remains.join('\n' + prefixPerWarning)
194
+ const output = 'Compilation warnings:\n' + warningString + '\n'
195
+ if (compilerOptions.errorOnWarnings) {
196
+ throw new Error(output)
197
+ } else {
198
+ console.log(output)
199
+ }
200
+ }
80
201
  }
81
202
 
82
- protected static _artifactsFolder(): string {
83
- return './artifacts/'
203
+ static contract(path: string, compilerOptions?: Partial<CompilerOptions>): Contract {
204
+ const contractPath = Project.currentProject.getContractPath(path)
205
+ const contract = Project.currentProject.contracts.find((c) => c.sourceFile.contractPath === contractPath)
206
+ if (typeof contract === 'undefined') {
207
+ throw new Error(`Contract ${contractPath} does not exist`)
208
+ }
209
+ Project.checkCompilerWarnings(contract.warnings, { ...DEFAULT_COMPILER_OPTIONS, ...compilerOptions })
210
+ return contract.artifact
84
211
  }
85
212
 
86
- static getSourceFile(path: string, _dirs: string[]): SourceFile {
87
- const parts = path.split('/')
88
- const dirs = Array.from(_dirs)
89
- if (parts.length === 1) {
90
- return new SourceFile(dirs, path)
213
+ static script(path: string, compilerOptions?: Partial<CompilerOptions>): Script {
214
+ const contractPath = Project.currentProject.getContractPath(path)
215
+ const script = Project.currentProject.scripts.find((c) => c.sourceFile.contractPath === contractPath)
216
+ if (typeof script === 'undefined') {
217
+ throw new Error(`Script ${contractPath} does not exist`)
91
218
  }
92
- parts.slice(0, parts.length - 1).forEach((part) => {
93
- switch (part) {
94
- case '.': {
95
- break
96
- }
97
- case '..': {
98
- if (dirs.length === 0) {
99
- throw new Error('Invalid file path: ' + path)
100
- }
101
- dirs.pop()
102
- break
103
- }
104
- default: {
105
- dirs.push(part)
106
- }
219
+ Project.checkCompilerWarnings(script.warnings, { ...DEFAULT_COMPILER_OPTIONS, ...compilerOptions })
220
+ return script.artifact
221
+ }
222
+
223
+ private async saveArtifactsToFile(): Promise<void> {
224
+ const artifactsRootPath = this.artifactsRootPath
225
+ const saveToFile = async function (compiled: Compiled<Artifact>): Promise<void> {
226
+ const artifactPath = compiled.sourceFile.getArtifactPath(artifactsRootPath)
227
+ const folder = artifactPath.slice(0, artifactPath.lastIndexOf('/'))
228
+ if (!fs.existsSync(folder)) {
229
+ fs.mkdirSync(folder, { recursive: true })
107
230
  }
231
+ return fsPromises.writeFile(artifactPath, compiled.artifact.toString())
232
+ }
233
+ for (const contract of this.contracts) {
234
+ await saveToFile(contract)
235
+ }
236
+ for (const script of this.scripts) {
237
+ await saveToFile(script)
238
+ }
239
+ }
240
+
241
+ contractByCodeHash(codeHash: string): Contract {
242
+ const contract = this.contracts.find((c) => c.artifact.codeHash === codeHash)
243
+ if (typeof contract === 'undefined') {
244
+ throw new Error(`Unknown code with code hash: ${codeHash}`)
245
+ }
246
+ return contract.artifact
247
+ }
248
+
249
+ private async saveProjectArtifactToFile(): Promise<void> {
250
+ const files: Map<string, { sourceCodeHash: string; warnings: string[] }> = new Map()
251
+ this.contracts.forEach((c) => {
252
+ files.set(c.sourceFile.contractPath, {
253
+ sourceCodeHash: c.sourceFile.sourceCodeHash,
254
+ warnings: c.warnings
255
+ })
108
256
  })
109
- return new SourceFile(dirs, parts[parts.length - 1])
110
- }
111
-
112
- protected static async _handleImports(
113
- pathes: string[],
114
- contractStr: string,
115
- importsCache: string[]
116
- ): Promise<string> {
117
- const localImportsCache: string[] = []
118
- let result = contractStr.replace(Common.importRegex, (match) => {
119
- localImportsCache.push(match)
120
- return ''
257
+ this.scripts.forEach((s) => {
258
+ files.set(s.sourceFile.contractPath, {
259
+ sourceCodeHash: s.sourceFile.sourceCodeHash,
260
+ warnings: s.warnings
261
+ })
121
262
  })
122
- for (const myImport of localImportsCache) {
123
- const relativePath = myImport.slice(8, -1)
124
- const importSourceFile = this.getSourceFile(relativePath, pathes)
125
- if (!importsCache.includes(importSourceFile.contractPath)) {
126
- importsCache.push(importSourceFile.contractPath)
127
- const importContractStr = await Common._loadContractStr(importSourceFile, importsCache, (code) =>
128
- Contract.checkCodeType(importSourceFile.contractPath, code)
129
- )
130
- result = result.concat('\n', importContractStr)
131
- }
132
- }
133
- return result
263
+ const compiledSize = this.contracts.length + this.scripts.length
264
+ this.sourceFiles.slice(compiledSize).forEach((c) => {
265
+ files.set(c.contractPath, {
266
+ sourceCodeHash: c.sourceCodeHash,
267
+ warnings: []
268
+ })
269
+ })
270
+ const projectArtifact = new ProjectArtifact(files)
271
+ await projectArtifact.saveToFile(this.artifactsRootPath)
134
272
  }
135
273
 
136
- protected static async _loadContractStr(
137
- sourceFile: SourceFile,
138
- importsCache: string[],
139
- validate: (code: string) => void
140
- ): Promise<string> {
141
- const contractPath = sourceFile.contractPath
142
- const contractBuffer = await fsPromises.readFile(contractPath)
143
- const contractStr = contractBuffer.toString()
274
+ private static async compile(
275
+ provider: NodeProvider,
276
+ files: SourceFile[],
277
+ contractsRootPath: string,
278
+ artifactsRootPath: string
279
+ ): Promise<Project> {
280
+ const sourceStr = files.map((f) => f.sourceCode).join('\n')
281
+ const result = await provider.contracts.postContractsCompileProject({
282
+ code: sourceStr
283
+ })
284
+ const contracts: Compiled<Contract>[] = []
285
+ const scripts: Compiled<Script>[] = []
286
+ result.contracts.forEach((contractResult, index) => {
287
+ const sourceFile = files[`${index}`]
288
+ const contract = Contract.fromCompileResult(contractResult)
289
+ contracts.push(new Compiled(sourceFile, contract, contractResult.warnings))
290
+ })
291
+ result.scripts.forEach((scriptResult, index) => {
292
+ const sourceFile = files[index + contracts.length]
293
+ const script = Script.fromCompileResult(scriptResult)
294
+ scripts.push(new Compiled(sourceFile, script, scriptResult.warnings))
295
+ })
296
+ const project = new Project(provider, contractsRootPath, artifactsRootPath, files, contracts, scripts)
297
+ await project.saveArtifactsToFile()
298
+ await project.saveProjectArtifactToFile()
299
+ return project
300
+ }
144
301
 
145
- validate(contractStr)
146
- return Common._handleImports(sourceFile.dirs, contractStr, importsCache)
302
+ private static async loadArtifacts(
303
+ provider: NodeProvider,
304
+ files: SourceFile[],
305
+ projectArtifact: ProjectArtifact,
306
+ contractsRootPath: string,
307
+ artifactsRootPath: string
308
+ ): Promise<Project> {
309
+ try {
310
+ const contracts: Compiled<Contract>[] = []
311
+ const scripts: Compiled<Script>[] = []
312
+ for (const file of files) {
313
+ const info = projectArtifact.infos.get(file.contractPath)
314
+ if (typeof info === 'undefined') {
315
+ throw Error(`Unable to find project info for ${file.contractPath}, please rebuild the project`)
316
+ }
317
+ const warnings = info.warnings
318
+ const artifactPath = file.getArtifactPath(artifactsRootPath)
319
+ if (file.type === SourceType.Contract) {
320
+ const artifact = await Contract.fromArtifactFile(artifactPath)
321
+ contracts.push(new Compiled(file, artifact, warnings))
322
+ } else if (file.type === SourceType.Script) {
323
+ const artifact = await Script.fromArtifactFile(artifactPath)
324
+ scripts.push(new Compiled(file, artifact, warnings))
325
+ }
326
+ }
327
+ return new Project(provider, contractsRootPath, artifactsRootPath, files, contracts, scripts)
328
+ } catch (error) {
329
+ console.log(`Failed to load artifacts, error: ${error}, try to re-compile contracts...`)
330
+ return Project.compile(provider, files, contractsRootPath, artifactsRootPath)
331
+ }
147
332
  }
148
333
 
149
- static checkFileNameExtension(fileName: string): void {
150
- if (!fileName.endsWith('.ral')) {
151
- throw new Error('Smart contract file name should end with ".ral"')
334
+ private static async loadSourceFile(dirPath: string, filename: string): Promise<SourceFile> {
335
+ const contractPath = dirPath + '/' + filename
336
+ if (!filename.endsWith('.ral')) {
337
+ throw new Error(`Invalid filename: ${contractPath}, smart contract file name should end with ".ral"`)
338
+ }
339
+
340
+ const sourceBuffer = await fsPromises.readFile(contractPath)
341
+ const sourceStr = sourceBuffer.toString()
342
+ const results = this.matchers.map((m) => m.match(sourceStr))
343
+ const matchNumber = results.reduce((a, b) => a + b, 0)
344
+ if (matchNumber === 0) {
345
+ throw new Error(`No contract defined in file: ${contractPath}`)
152
346
  }
347
+ if (matchNumber > 1) {
348
+ throw new Error(`Multiple definitions in file: ${contractPath}`)
349
+ }
350
+ const matcherIndex = results.indexOf(1)
351
+ const type = this.matchers[`${matcherIndex}`].type
352
+ return new SourceFile(type, sourceStr, contractPath)
353
+ }
354
+
355
+ private static async loadSourceFiles(contractsRootPath: string): Promise<SourceFile[]> {
356
+ const loadDir = async function (dirPath: string, results: SourceFile[]): Promise<void> {
357
+ const dirents = await fsPromises.readdir(dirPath, { withFileTypes: true })
358
+ for (const dirent of dirents) {
359
+ if (dirent.isFile()) {
360
+ const file = await Project.loadSourceFile(dirPath, dirent.name)
361
+ results.push(file)
362
+ } else {
363
+ const newPath = dirPath + '/' + dirent.name
364
+ await loadDir(newPath, results)
365
+ }
366
+ }
367
+ }
368
+ const sourceFiles: SourceFile[] = []
369
+ await loadDir(contractsRootPath, sourceFiles)
370
+ const contractAndScriptSize = sourceFiles.filter(
371
+ (f) => f.type === SourceType.Contract || f.type === SourceType.Script
372
+ ).length
373
+ if (sourceFiles.length === 0 || contractAndScriptSize === 0) {
374
+ throw new Error('Project have no source files')
375
+ }
376
+ return sourceFiles.sort((a, b) => a.type - b.type)
153
377
  }
154
378
 
155
- protected static async _from<T extends { sourceCodeSha256: string }>(
379
+ static async build(
156
380
  provider: NodeProvider,
157
- sourceFile: SourceFile,
158
- loadContractStr: (sourceFile: SourceFile, importsCache: string[]) => Promise<string>,
159
- compile: (
160
- provider: NodeProvider,
161
- sourceFile: SourceFile,
162
- contractStr: string,
163
- contractHash: string,
164
- errorOnWarnings: boolean
165
- ) => Promise<T>,
166
- errorOnWarnings: boolean
167
- ): Promise<T> {
168
- Common.checkFileNameExtension(sourceFile.contractPath)
169
-
170
- const contractStr = await loadContractStr(sourceFile, [])
171
- const contractHash = cryptojs.SHA256(contractStr).toString()
172
- const existingContract = this._getArtifactFromCache(contractHash)
173
- if (typeof existingContract !== 'undefined') {
174
- return existingContract as unknown as T
381
+ contractsRootPath = 'contracts',
382
+ artifactsRootPath = 'artifacts'
383
+ ): Promise<void> {
384
+ const sourceFiles = await Project.loadSourceFiles(contractsRootPath)
385
+ const projectArtifact = await ProjectArtifact.from(artifactsRootPath)
386
+ if (typeof projectArtifact === 'undefined' || projectArtifact.sourceHasChanged(sourceFiles)) {
387
+ Project.currentProject = await Project.compile(provider, sourceFiles, contractsRootPath, artifactsRootPath)
175
388
  } else {
176
- return compile(provider, sourceFile, contractStr, contractHash, errorOnWarnings)
389
+ Project.currentProject = await Project.loadArtifacts(
390
+ provider,
391
+ sourceFiles,
392
+ projectArtifact,
393
+ contractsRootPath,
394
+ artifactsRootPath
395
+ )
177
396
  }
178
397
  }
398
+ }
179
399
 
180
- protected _saveToFile(sourceFile: SourceFile): Promise<void> {
181
- const folder = Common._artifactsFolder() + sourceFile.dirPath
182
- if (!fs.existsSync(folder)) {
183
- fs.mkdirSync(folder, { recursive: true })
184
- }
185
- return fsPromises.writeFile(sourceFile.artifactPath, this.toString())
400
+ export abstract class Artifact {
401
+ readonly functions: FunctionSig[]
402
+
403
+ constructor(functions: FunctionSig[]) {
404
+ this.functions = functions
186
405
  }
187
406
 
188
407
  abstract buildByteCodeToDeploy(initialFields?: Fields): string
@@ -198,109 +417,31 @@ export abstract class Common {
198
417
  usingAssetsInContractFunctions(): string[] {
199
418
  return this.functions.filter((func) => func.useAssetsInContract).map((func) => func.name)
200
419
  }
201
-
202
- protected static checkCompilerWarnings(compiled: { warnings: string[] }, errorOnWarnings: boolean): void {
203
- if (compiled.warnings.length !== 0) {
204
- const prefixPerWarning = ' - '
205
- const warningString = prefixPerWarning + compiled.warnings.join('\n' + prefixPerWarning)
206
- const output = 'Compilation warnings:\n' + warningString + '\n'
207
- if (errorOnWarnings) {
208
- throw new Error(output)
209
- } else {
210
- console.log(output)
211
- }
212
- }
213
- }
214
420
  }
215
421
 
216
- export class Contract extends Common {
422
+ export class Contract extends Artifact {
217
423
  readonly bytecode: string
218
424
  readonly codeHash: string
219
425
  readonly fieldsSig: FieldsSig
220
426
  readonly eventsSig: EventSig[]
221
427
 
222
428
  constructor(
223
- sourceCodeSha256: string,
224
429
  bytecode: string,
225
430
  codeHash: string,
226
431
  fieldsSig: FieldsSig,
227
432
  eventsSig: EventSig[],
228
433
  functions: FunctionSig[]
229
434
  ) {
230
- super(sourceCodeSha256, functions)
435
+ super(functions)
231
436
  this.bytecode = bytecode
232
437
  this.codeHash = codeHash
233
438
  this.fieldsSig = fieldsSig
234
439
  this.eventsSig = eventsSig
235
440
  }
236
441
 
237
- static checkCodeType(fileName: string, contractStr: string): void {
238
- const interfaceMatches = contractStr.match(Contract.interfaceRegex)
239
- const contractMatches = contractStr.match(Contract.contractRegex)
240
- if (interfaceMatches === null && contractMatches === null) {
241
- throw new Error(`No contract found in: ${fileName}`)
242
- }
243
- if (interfaceMatches && contractMatches) {
244
- throw new Error(`Multiple contracts and interfaces in: ${fileName}`)
245
- }
246
- if (interfaceMatches === null) {
247
- if (contractMatches !== null && contractMatches.length > 1) {
248
- throw new Error(`Multiple contracts in: ${fileName}`)
249
- }
250
- }
251
- if (contractMatches === null) {
252
- if (interfaceMatches !== null && interfaceMatches.length > 1) {
253
- throw new Error(`Multiple interfaces in: ${fileName}`)
254
- }
255
- }
256
- }
257
-
258
- private static async loadContractStr(sourceFile: SourceFile): Promise<string> {
259
- return Common._loadContractStr(sourceFile, [], (code) => Contract.checkCodeType(sourceFile.contractPath, code))
260
- }
261
-
262
- static async fromSource(provider: NodeProvider, path: string, errorOnWarnings = true): Promise<Contract> {
263
- if (!fs.existsSync(Common._artifactsFolder())) {
264
- fs.mkdirSync(Common._artifactsFolder(), { recursive: true })
265
- }
266
- const sourceFile = this.getSourceFile(path, [])
267
- const contract = await Common._from(
268
- provider,
269
- sourceFile,
270
- (sourceFile) => Contract.loadContractStr(sourceFile),
271
- Contract.compile,
272
- errorOnWarnings
273
- )
274
- this._putArtifactToCache(contract)
275
- return contract
276
- }
277
-
278
- private static async compile(
279
- provider: NodeProvider,
280
- sourceFile: SourceFile,
281
- contractStr: string,
282
- contractHash: string,
283
- errorOnWarnings: boolean
284
- ): Promise<Contract> {
285
- const compiled = await provider.contracts.postContractsCompileContract({ code: contractStr })
286
- Common.checkCompilerWarnings(compiled, errorOnWarnings)
287
-
288
- const artifact = new Contract(
289
- contractHash,
290
- compiled.bytecode,
291
- compiled.codeHash,
292
- compiled.fields,
293
- compiled.events,
294
- compiled.functions
295
- )
296
- await artifact._saveToFile(sourceFile)
297
- return artifact
298
- }
299
-
300
442
  // TODO: safely parse json
301
443
  static fromJson(artifact: any): Contract {
302
444
  if (
303
- artifact.sourceCodeSha256 == null ||
304
445
  artifact.bytecode == null ||
305
446
  artifact.codeHash == null ||
306
447
  artifact.fieldsSig == null ||
@@ -310,44 +451,42 @@ export class Contract extends Common {
310
451
  throw Error('The artifact JSON for contract is incomplete')
311
452
  }
312
453
  const contract = new Contract(
313
- artifact.sourceCodeSha256,
314
454
  artifact.bytecode,
315
455
  artifact.codeHash,
316
456
  artifact.fieldsSig,
317
457
  artifact.eventsSig,
318
458
  artifact.functions
319
459
  )
320
- this._putArtifactToCache(contract)
321
460
  return contract
322
461
  }
323
462
 
463
+ static fromCompileResult(result: CompileContractResult): Contract {
464
+ return new Contract(result.bytecode, result.codeHash, result.fields, result.events, result.functions)
465
+ }
466
+
324
467
  // support both 'code.ral' and 'code.ral.json'
325
468
  static async fromArtifactFile(path: string): Promise<Contract> {
326
- const sourceFile = this.getSourceFile(path, [])
327
- const artifactPath = sourceFile.artifactPath
328
- const content = await fsPromises.readFile(artifactPath)
469
+ const content = await fsPromises.readFile(path)
329
470
  const artifact = JSON.parse(content.toString())
330
471
  return Contract.fromJson(artifact)
331
472
  }
332
473
 
333
- async fetchState(provider: NodeProvider, address: string, group: number): Promise<ContractState> {
334
- const state = await provider.contracts.getContractsAddressState(address, { group: group })
474
+ async fetchState(address: string, group: number): Promise<ContractState> {
475
+ const state = await Project.currentProject.nodeProvider.contracts.getContractsAddressState(address, {
476
+ group: group
477
+ })
335
478
  return this.fromApiContractState(state)
336
479
  }
337
480
 
338
481
  override toString(): string {
339
- return JSON.stringify(
340
- {
341
- sourceCodeSha256: this.sourceCodeSha256,
342
- bytecode: this.bytecode,
343
- codeHash: this.codeHash,
344
- fieldsSig: this.fieldsSig,
345
- eventsSig: this.eventsSig,
346
- functions: this.functions
347
- },
348
- null,
349
- 2
350
- )
482
+ const object = {
483
+ bytecode: this.bytecode,
484
+ codeHash: this.codeHash,
485
+ fieldsSig: this.fieldsSig,
486
+ eventsSig: this.eventsSig,
487
+ functions: this.functions
488
+ }
489
+ return JSON.stringify(object, null, 2)
351
490
  }
352
491
 
353
492
  toState(fields: Fields, asset: Asset, address?: string): ContractState {
@@ -370,14 +509,13 @@ export class Contract extends Common {
370
509
  }
371
510
 
372
511
  private async _test(
373
- provider: NodeProvider,
374
512
  funcName: string,
375
513
  params: TestContractParams,
376
514
  expectPublic: boolean,
377
515
  accessType: string
378
516
  ): Promise<TestContractResult> {
379
517
  const apiParams: node.TestContract = this.toTestContract(funcName, params)
380
- const apiResult = await provider.contracts.postContractsTestContract(apiParams)
518
+ const apiResult = await Project.currentProject.nodeProvider.contracts.postContractsTestContract(apiParams)
381
519
 
382
520
  const methodIndex =
383
521
  typeof params.testMethodIndex !== 'undefined' ? params.testMethodIndex : this.getMethodIndex(funcName)
@@ -390,20 +528,12 @@ export class Contract extends Common {
390
528
  }
391
529
  }
392
530
 
393
- async testPublicMethod(
394
- provider: NodeProvider,
395
- funcName: string,
396
- params: TestContractParams
397
- ): Promise<TestContractResult> {
398
- return this._test(provider, funcName, params, true, 'public')
531
+ async testPublicMethod(funcName: string, params: TestContractParams): Promise<TestContractResult> {
532
+ return this._test(funcName, params, true, 'public')
399
533
  }
400
534
 
401
- async testPrivateMethod(
402
- provider: NodeProvider,
403
- funcName: string,
404
- params: TestContractParams
405
- ): Promise<TestContractResult> {
406
- return this._test(provider, funcName, params, false, 'private')
535
+ async testPrivateMethod(funcName: string, params: TestContractParams): Promise<TestContractResult> {
536
+ return this._test(funcName, params, false, 'private')
407
537
  }
408
538
 
409
539
  toApiFields(fields?: Fields): node.Val[] {
@@ -449,33 +579,8 @@ export class Contract extends Common {
449
579
  }
450
580
  }
451
581
 
452
- static async fromCodeHash(codeHash: string): Promise<Contract> {
453
- const cached = this._getArtifactFromCache(codeHash)
454
- if (typeof cached !== 'undefined') {
455
- return cached as Contract
456
- }
457
-
458
- const files = await fsPromises.readdir(Common._artifactsFolder())
459
- for (const file of files) {
460
- if (file.endsWith('.ral.json')) {
461
- try {
462
- const contract = await Contract.fromArtifactFile(file)
463
- if (contract.codeHash === codeHash) {
464
- return contract as Contract
465
- }
466
- } catch (_) {}
467
- }
468
- }
469
-
470
- throw new Error(`Unknown code with code hash: ${codeHash}`)
471
- }
472
-
473
- static async getFieldsSig(state: node.ContractState): Promise<FieldsSig> {
474
- return Contract.fromCodeHash(state.codeHash).then((contract) => contract.fieldsSig)
475
- }
476
-
477
582
  async fromApiContractState(state: node.ContractState): Promise<ContractState> {
478
- const contract = await Contract.fromCodeHash(state.codeHash)
583
+ const contract = Project.currentProject.contractByCodeHash(state.codeHash)
479
584
  return {
480
585
  address: state.address,
481
586
  contractId: binToHex(contractIdFromAddress(state.address)),
@@ -483,7 +588,7 @@ export class Contract extends Common {
483
588
  initialStateHash: state.initialStateHash,
484
589
  codeHash: state.codeHash,
485
590
  fields: fromApiFields(state.fields, contract.fieldsSig),
486
- fieldsSig: await Contract.getFieldsSig(state),
591
+ fieldsSig: contract.fieldsSig,
487
592
  asset: fromApiAsset(state.asset)
488
593
  }
489
594
  }
@@ -511,7 +616,7 @@ export class Contract extends Common {
511
616
  } else if (event.eventIndex == -2) {
512
617
  eventSig = this.ContractDestroyedEvent
513
618
  } else {
514
- const contract = await Contract.fromCodeHash(codeHash!)
619
+ const contract = Project.currentProject.contractByCodeHash(codeHash!)
515
620
  eventSig = contract.eventsSig[event.eventIndex]
516
621
  }
517
622
 
@@ -579,88 +684,41 @@ export class Contract extends Common {
579
684
  }
580
685
  }
581
686
 
582
- export class Script extends Common {
687
+ export class Script extends Artifact {
583
688
  readonly bytecodeTemplate: string
584
689
  readonly fieldsSig: FieldsSig
585
690
 
586
- constructor(sourceCodeSha256: string, bytecodeTemplate: string, fieldsSig: FieldsSig, functions: FunctionSig[]) {
587
- super(sourceCodeSha256, functions)
691
+ constructor(bytecodeTemplate: string, fieldsSig: FieldsSig, functions: FunctionSig[]) {
692
+ super(functions)
588
693
  this.bytecodeTemplate = bytecodeTemplate
589
694
  this.fieldsSig = fieldsSig
590
695
  }
591
696
 
592
- static checkCodeType(fileName: string, contractStr: string): void {
593
- const scriptMatches = contractStr.match(this.scriptRegex)
594
- if (scriptMatches === null) {
595
- throw new Error(`No script found in: ${fileName}`)
596
- } else if (scriptMatches.length > 1) {
597
- throw new Error(`Multiple scripts in: ${fileName}`)
598
- } else {
599
- return
600
- }
601
- }
602
-
603
- private static async loadContractStr(sourceFile: SourceFile): Promise<string> {
604
- return Common._loadContractStr(sourceFile, [], (code) => Script.checkCodeType(sourceFile.contractPath, code))
605
- }
606
-
607
- static async fromSource(provider: NodeProvider, path: string, errorOnWarnings = true): Promise<Script> {
608
- const sourceFile = this.getSourceFile(path, [])
609
- return Common._from(
610
- provider,
611
- sourceFile,
612
- (sourceFile) => Script.loadContractStr(sourceFile),
613
- Script.compile,
614
- errorOnWarnings
615
- )
616
- }
617
-
618
- private static async compile(
619
- provider: NodeProvider,
620
- sourceFile: SourceFile,
621
- scriptStr: string,
622
- contractHash: string,
623
- errorOnWarnings = true
624
- ): Promise<Script> {
625
- const compiled = await provider.contracts.postContractsCompileScript({ code: scriptStr })
626
- Common.checkCompilerWarnings(compiled, errorOnWarnings)
627
- const artifact = new Script(contractHash, compiled.bytecodeTemplate, compiled.fields, compiled.functions)
628
- await artifact._saveToFile(sourceFile)
629
- return artifact
697
+ static fromCompileResult(result: CompileScriptResult): Script {
698
+ return new Script(result.bytecodeTemplate, result.fields, result.functions)
630
699
  }
631
700
 
632
701
  // TODO: safely parse json
633
702
  static fromJson(artifact: any): Script {
634
- if (
635
- artifact.sourceCodeSha256 == null ||
636
- artifact.bytecodeTemplate == null ||
637
- artifact.fieldsSig == null ||
638
- artifact.functions == null
639
- ) {
703
+ if (artifact.bytecodeTemplate == null || artifact.fieldsSig == null || artifact.functions == null) {
640
704
  throw Error('The artifact JSON for script is incomplete')
641
705
  }
642
- return new Script(artifact.sourceCodeSha256, artifact.bytecodeTemplate, artifact.fieldsSig, artifact.functions)
706
+ return new Script(artifact.bytecodeTemplate, artifact.fieldsSig, artifact.functions)
643
707
  }
644
708
 
645
709
  static async fromArtifactFile(path: string): Promise<Script> {
646
- const sourceFile = this.getSourceFile(path, [])
647
- const artifactPath = sourceFile.artifactPath
648
- const content = await fsPromises.readFile(artifactPath)
710
+ const content = await fsPromises.readFile(path)
649
711
  const artifact = JSON.parse(content.toString())
650
712
  return this.fromJson(artifact)
651
713
  }
652
714
 
653
715
  override toString(): string {
654
- return JSON.stringify(
655
- {
656
- sourceCodeSha256: this.sourceCodeSha256,
657
- bytecodeTemplate: this.bytecodeTemplate,
658
- fieldsSig: this.fieldsSig,
659
- functions: this.functions
660
- },
661
- null,
662
- 2
663
- )
716
+ const object = {
717
+ bytecodeTemplate: this.bytecodeTemplate,
718
+ fieldsSig: this.fieldsSig,
719
+ functions: this.functions
720
+ }
721
+ return JSON.stringify(object, null, 2)
664
722
  }
665
723
 
666
724
  async paramsForDeployment(params: BuildExecuteScriptTx): Promise<SignExecuteScriptTxParams> {