@alephium/web3 0.2.0-rc.4 → 0.2.0-rc.5

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