@alephium/web3 0.2.0-rc.2 → 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,241 +26,414 @@ 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
- export abstract class Common {
50
- readonly sourceCodeSha256: string
51
- readonly functions: node.FunctionSig[]
52
-
53
- static readonly importRegex = new RegExp('^import "([^"/]+/(([^"]+)/)?)?[a-z][a-z_0-9]*.ral"', 'mg')
54
- static readonly contractRegex = new RegExp('^(Abstract[ ]+)?Contract [A-Z][a-zA-Z0-9]*', 'mg')
55
- static readonly interfaceRegex = new RegExp('^Interface [A-Z][a-zA-Z0-9]* \\{', 'mg')
56
- static readonly scriptRegex = new RegExp('^TxScript [A-Z][a-zA-Z0-9]*', 'mg')
57
-
58
- private static _artifactCache: Map<string, Contract | Script> = new Map<string, Contract | Script>()
59
- static artifactCacheCapacity = 20
60
- protected static _getArtifactFromCache(codeHash: string): Contract | Script | undefined {
61
- return this._artifactCache.get(codeHash)
62
- }
63
- protected static _putArtifactToCache(contract: Contract): void {
64
- if (!this._artifactCache.has(contract.codeHash)) {
65
- if (this._artifactCache.size >= this.artifactCacheCapacity) {
66
- const keyToDelete = this._artifactCache.keys().next().value
67
- this._artifactCache.delete(keyToDelete)
75
+ class Compiled<T extends Artifact> {
76
+ sourceFile: SourceFile
77
+ artifact: T
78
+ warnings: string[]
79
+
80
+ constructor(sourceFile: SourceFile, artifact: T, warnings: string[]) {
81
+ this.sourceFile = sourceFile
82
+ this.artifact = artifact
83
+ this.warnings = warnings
84
+ }
85
+ }
86
+
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
68
110
  }
69
- this._artifactCache.set(contract.codeHash, contract)
70
111
  }
112
+ return false
71
113
  }
72
114
 
73
- constructor(sourceCodeSha256: string, functions: node.FunctionSig[]) {
74
- this.sourceCodeSha256 = sourceCodeSha256
75
- 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)
76
125
  }
126
+ }
77
127
 
78
- protected static _artifactsFolder(): string {
79
- 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
175
+ }
176
+
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
+ }
192
+ }
80
193
  }
81
194
 
82
- static getSourceFile(path: string, _dirs: string[]): SourceFile {
83
- const parts = path.split('/')
84
- const dirs = Array.from(_dirs)
85
- if (parts.length === 1) {
86
- return new SourceFile(dirs, path)
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`)
87
200
  }
88
- parts.slice(0, parts.length - 1).forEach((part) => {
89
- switch (part) {
90
- case '.': {
91
- break
92
- }
93
- case '..': {
94
- if (dirs.length === 0) {
95
- throw new Error('Invalid file path: ' + path)
96
- }
97
- dirs.pop()
98
- break
99
- }
100
- default: {
101
- dirs.push(part)
102
- }
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 })
103
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
+ })
104
248
  })
105
- return new SourceFile(dirs, parts[parts.length - 1])
106
- }
107
-
108
- protected static async _handleImports(
109
- pathes: string[],
110
- contractStr: string,
111
- importsCache: string[]
112
- ): Promise<string> {
113
- const localImportsCache: string[] = []
114
- let result = contractStr.replace(Common.importRegex, (match) => {
115
- localImportsCache.push(match)
116
- return ''
249
+ this.scripts.forEach((s) => {
250
+ files.set(s.sourceFile.contractPath, {
251
+ sourceCodeHash: s.sourceFile.sourceCodeHash,
252
+ warnings: s.warnings
253
+ })
117
254
  })
118
- for (const myImport of localImportsCache) {
119
- const relativePath = myImport.slice(8, -1)
120
- const importSourceFile = this.getSourceFile(relativePath, pathes)
121
- if (!importsCache.includes(importSourceFile.contractPath)) {
122
- importsCache.push(importSourceFile.contractPath)
123
- const importContractStr = await Common._loadContractStr(importSourceFile, importsCache, (code) =>
124
- Contract.checkCodeType(importSourceFile.contractPath, code)
125
- )
126
- result = result.concat('\n', importContractStr)
127
- }
128
- }
129
- 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)
130
264
  }
131
265
 
132
- protected static async _loadContractStr(
133
- sourceFile: SourceFile,
134
- importsCache: string[],
135
- validate: (code: string) => void
136
- ): Promise<string> {
137
- const contractPath = sourceFile.contractPath
138
- const contractBuffer = await fsPromises.readFile(contractPath)
139
- 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
+ }
140
293
 
141
- validate(contractStr)
142
- 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
+ }
143
324
  }
144
325
 
145
- static checkFileNameExtension(fileName: string): void {
146
- if (!fileName.endsWith('.ral')) {
147
- 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}`)
148
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')
367
+ }
368
+ return sourceFiles.sort((a, b) => a.type - b.type)
149
369
  }
150
370
 
151
- protected static async _from<T extends { sourceCodeSha256: string }>(
371
+ static async build(
152
372
  provider: NodeProvider,
153
- sourceFile: SourceFile,
154
- loadContractStr: (sourceFile: SourceFile, importsCache: string[]) => Promise<string>,
155
- compile: (provider: NodeProvider, sourceFile: SourceFile, contractStr: string, contractHash: string) => Promise<T>
156
- ): Promise<T> {
157
- Common.checkFileNameExtension(sourceFile.contractPath)
158
-
159
- const contractStr = await loadContractStr(sourceFile, [])
160
- const contractHash = cryptojs.SHA256(contractStr).toString()
161
- const existingContract = this._getArtifactFromCache(contractHash)
162
- if (typeof existingContract !== 'undefined') {
163
- 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)
164
380
  } else {
165
- return compile(provider, sourceFile, contractStr, contractHash)
381
+ Project.currentProject = await Project.loadArtifacts(
382
+ provider,
383
+ sourceFiles,
384
+ projectArtifact,
385
+ contractsRootPath,
386
+ artifactsRootPath
387
+ )
166
388
  }
167
389
  }
390
+ }
168
391
 
169
- protected _saveToFile(sourceFile: SourceFile): Promise<void> {
170
- const folder = Common._artifactsFolder() + sourceFile.dirPath
171
- if (!fs.existsSync(folder)) {
172
- fs.mkdirSync(folder, { recursive: true })
173
- }
174
- 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
175
397
  }
176
398
 
177
399
  abstract buildByteCodeToDeploy(initialFields?: Fields): string
400
+
401
+ publicFunctions(): string[] {
402
+ return this.functions.filter((func) => func.isPublic).map((func) => func.name)
403
+ }
404
+
405
+ usingPreapprovedAssetsFunctions(): string[] {
406
+ return this.functions.filter((func) => func.usePreapprovedAssets).map((func) => func.name)
407
+ }
408
+
409
+ usingAssetsInContractFunctions(): string[] {
410
+ return this.functions.filter((func) => func.useAssetsInContract).map((func) => func.name)
411
+ }
178
412
  }
179
413
 
180
- export class Contract extends Common {
414
+ export class Contract extends Artifact {
181
415
  readonly bytecode: string
182
416
  readonly codeHash: string
183
- readonly fieldsSig: node.FieldsSig
184
- readonly eventsSig: node.EventSig[]
417
+ readonly fieldsSig: FieldsSig
418
+ readonly eventsSig: EventSig[]
185
419
 
186
420
  constructor(
187
- sourceCodeSha256: string,
188
421
  bytecode: string,
189
422
  codeHash: string,
190
- fieldsSig: node.FieldsSig,
191
- eventsSig: node.EventSig[],
192
- functions: node.FunctionSig[]
423
+ fieldsSig: FieldsSig,
424
+ eventsSig: EventSig[],
425
+ functions: FunctionSig[]
193
426
  ) {
194
- super(sourceCodeSha256, functions)
427
+ super(functions)
195
428
  this.bytecode = bytecode
196
429
  this.codeHash = codeHash
197
430
  this.fieldsSig = fieldsSig
198
431
  this.eventsSig = eventsSig
199
432
  }
200
433
 
201
- static checkCodeType(fileName: string, contractStr: string): void {
202
- const interfaceMatches = contractStr.match(Contract.interfaceRegex)
203
- const contractMatches = contractStr.match(Contract.contractRegex)
204
- if (interfaceMatches === null && contractMatches === null) {
205
- throw new Error(`No contract found in: ${fileName}`)
206
- }
207
- if (interfaceMatches && contractMatches) {
208
- throw new Error(`Multiple contracts and interfaces in: ${fileName}`)
209
- }
210
- if (interfaceMatches === null) {
211
- if (contractMatches !== null && contractMatches.length > 1) {
212
- throw new Error(`Multiple contracts in: ${fileName}`)
213
- }
214
- }
215
- if (contractMatches === null) {
216
- if (interfaceMatches !== null && interfaceMatches.length > 1) {
217
- throw new Error(`Multiple interfaces in: ${fileName}`)
218
- }
219
- }
220
- }
221
-
222
- private static async loadContractStr(sourceFile: SourceFile): Promise<string> {
223
- return Common._loadContractStr(sourceFile, [], (code) => Contract.checkCodeType(sourceFile.contractPath, code))
224
- }
225
-
226
- static async fromSource(provider: NodeProvider, path: string): Promise<Contract> {
227
- if (!fs.existsSync(Common._artifactsFolder())) {
228
- fs.mkdirSync(Common._artifactsFolder(), { recursive: true })
229
- }
230
- const sourceFile = this.getSourceFile(path, [])
231
- const contract = await Common._from(
232
- provider,
233
- sourceFile,
234
- (sourceFile) => Contract.loadContractStr(sourceFile),
235
- Contract.compile
236
- )
237
- this._putArtifactToCache(contract)
238
- return contract
239
- }
240
-
241
- private static async compile(
242
- provider: NodeProvider,
243
- sourceFile: SourceFile,
244
- contractStr: string,
245
- contractHash: string
246
- ): Promise<Contract> {
247
- const compiled = await provider.contracts.postContractsCompileContract({ code: contractStr })
248
- const artifact = new Contract(
249
- contractHash,
250
- compiled.bytecode,
251
- compiled.codeHash,
252
- compiled.fields,
253
- compiled.events,
254
- compiled.functions
255
- )
256
- await artifact._saveToFile(sourceFile)
257
- return artifact
258
- }
259
-
260
434
  // TODO: safely parse json
261
435
  static fromJson(artifact: any): Contract {
262
436
  if (
263
- artifact.sourceCodeSha256 == null ||
264
437
  artifact.bytecode == null ||
265
438
  artifact.codeHash == null ||
266
439
  artifact.fieldsSig == null ||
@@ -270,44 +443,42 @@ export class Contract extends Common {
270
443
  throw Error('The artifact JSON for contract is incomplete')
271
444
  }
272
445
  const contract = new Contract(
273
- artifact.sourceCodeSha256,
274
446
  artifact.bytecode,
275
447
  artifact.codeHash,
276
448
  artifact.fieldsSig,
277
449
  artifact.eventsSig,
278
450
  artifact.functions
279
451
  )
280
- this._putArtifactToCache(contract)
281
452
  return contract
282
453
  }
283
454
 
455
+ static fromCompileResult(result: CompileContractResult): Contract {
456
+ return new Contract(result.bytecode, result.codeHash, result.fields, result.events, result.functions)
457
+ }
458
+
284
459
  // support both 'code.ral' and 'code.ral.json'
285
460
  static async fromArtifactFile(path: string): Promise<Contract> {
286
- const sourceFile = this.getSourceFile(path, [])
287
- const artifactPath = sourceFile.artifactPath
288
- const content = await fsPromises.readFile(artifactPath)
461
+ const content = await fsPromises.readFile(path)
289
462
  const artifact = JSON.parse(content.toString())
290
463
  return Contract.fromJson(artifact)
291
464
  }
292
465
 
293
- async fetchState(provider: NodeProvider, address: string, group: number): Promise<ContractState> {
294
- 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
+ })
295
470
  return this.fromApiContractState(state)
296
471
  }
297
472
 
298
473
  override toString(): string {
299
- return JSON.stringify(
300
- {
301
- sourceCodeSha256: this.sourceCodeSha256,
302
- bytecode: this.bytecode,
303
- codeHash: this.codeHash,
304
- fieldsSig: this.fieldsSig,
305
- eventsSig: this.eventsSig,
306
- functions: this.functions
307
- },
308
- null,
309
- 2
310
- )
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)
311
482
  }
312
483
 
313
484
  toState(fields: Fields, asset: Asset, address?: string): ContractState {
@@ -330,18 +501,17 @@ export class Contract extends Common {
330
501
  }
331
502
 
332
503
  private async _test(
333
- provider: NodeProvider,
334
504
  funcName: string,
335
505
  params: TestContractParams,
336
506
  expectPublic: boolean,
337
507
  accessType: string
338
508
  ): Promise<TestContractResult> {
339
509
  const apiParams: node.TestContract = this.toTestContract(funcName, params)
340
- const apiResult = await provider.contracts.postContractsTestContract(apiParams)
510
+ const apiResult = await Project.currentProject.nodeProvider.contracts.postContractsTestContract(apiParams)
341
511
 
342
512
  const methodIndex =
343
513
  typeof params.testMethodIndex !== 'undefined' ? params.testMethodIndex : this.getMethodIndex(funcName)
344
- const isPublic = this.functions[`${methodIndex}`].signature.indexOf('pub ') !== -1
514
+ const isPublic = this.functions[`${methodIndex}`].isPublic
345
515
  if (isPublic === expectPublic) {
346
516
  const result = await this.fromTestContractResult(methodIndex, apiResult)
347
517
  return result
@@ -350,20 +520,12 @@ export class Contract extends Common {
350
520
  }
351
521
  }
352
522
 
353
- async testPublicMethod(
354
- provider: NodeProvider,
355
- funcName: string,
356
- params: TestContractParams
357
- ): Promise<TestContractResult> {
358
- 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')
359
525
  }
360
526
 
361
- async testPrivateMethod(
362
- provider: NodeProvider,
363
- funcName: string,
364
- params: TestContractParams
365
- ): Promise<TestContractResult> {
366
- 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')
367
529
  }
368
530
 
369
531
  toApiFields(fields?: Fields): node.Val[] {
@@ -409,33 +571,8 @@ export class Contract extends Common {
409
571
  }
410
572
  }
411
573
 
412
- static async fromCodeHash(codeHash: string): Promise<Contract> {
413
- const cached = this._getArtifactFromCache(codeHash)
414
- if (typeof cached !== 'undefined') {
415
- return cached as Contract
416
- }
417
-
418
- const files = await fsPromises.readdir(Common._artifactsFolder())
419
- for (const file of files) {
420
- if (file.endsWith('.ral.json')) {
421
- try {
422
- const contract = await Contract.fromArtifactFile(file)
423
- if (contract.codeHash === codeHash) {
424
- return contract as Contract
425
- }
426
- } catch (_) {}
427
- }
428
- }
429
-
430
- throw new Error(`Unknown code with code hash: ${codeHash}`)
431
- }
432
-
433
- static async getFieldsSig(state: node.ContractState): Promise<node.FieldsSig> {
434
- return Contract.fromCodeHash(state.codeHash).then((contract) => contract.fieldsSig)
435
- }
436
-
437
574
  async fromApiContractState(state: node.ContractState): Promise<ContractState> {
438
- const contract = await Contract.fromCodeHash(state.codeHash)
575
+ const contract = Project.currentProject.contractByCodeHash(state.codeHash)
439
576
  return {
440
577
  address: state.address,
441
578
  contractId: binToHex(contractIdFromAddress(state.address)),
@@ -443,21 +580,19 @@ export class Contract extends Common {
443
580
  initialStateHash: state.initialStateHash,
444
581
  codeHash: state.codeHash,
445
582
  fields: fromApiFields(state.fields, contract.fieldsSig),
446
- fieldsSig: await Contract.getFieldsSig(state),
583
+ fieldsSig: contract.fieldsSig,
447
584
  asset: fromApiAsset(state.asset)
448
585
  }
449
586
  }
450
587
 
451
- static ContractCreatedEvent: node.EventSig = {
588
+ static ContractCreatedEvent: EventSig = {
452
589
  name: 'ContractCreated',
453
- signature: 'event ContractCreated(address:Address)',
454
590
  fieldNames: ['address'],
455
591
  fieldTypes: ['Address']
456
592
  }
457
593
 
458
- static ContractDestroyedEvent: node.EventSig = {
594
+ static ContractDestroyedEvent: EventSig = {
459
595
  name: 'ContractDestroyed',
460
- signature: 'event ContractDestroyed(address:Address)',
461
596
  fieldNames: ['address'],
462
597
  fieldTypes: ['Address']
463
598
  }
@@ -466,14 +601,14 @@ export class Contract extends Common {
466
601
  event: node.ContractEventByTxId,
467
602
  codeHash: string | undefined
468
603
  ): Promise<ContractEventByTxId> {
469
- let eventSig: node.EventSig
604
+ let eventSig: EventSig
470
605
 
471
606
  if (event.eventIndex == -1) {
472
607
  eventSig = this.ContractCreatedEvent
473
608
  } else if (event.eventIndex == -2) {
474
609
  eventSig = this.ContractDestroyedEvent
475
610
  } else {
476
- const contract = await Contract.fromCodeHash(codeHash!)
611
+ const contract = Project.currentProject.contractByCodeHash(codeHash!)
477
612
  eventSig = contract.eventsSig[event.eventIndex]
478
613
  }
479
614
 
@@ -541,85 +676,41 @@ export class Contract extends Common {
541
676
  }
542
677
  }
543
678
 
544
- export class Script extends Common {
679
+ export class Script extends Artifact {
545
680
  readonly bytecodeTemplate: string
546
- readonly fieldsSig: node.FieldsSig
681
+ readonly fieldsSig: FieldsSig
547
682
 
548
- constructor(
549
- sourceCodeSha256: string,
550
- bytecodeTemplate: string,
551
- fieldsSig: node.FieldsSig,
552
- functions: node.FunctionSig[]
553
- ) {
554
- super(sourceCodeSha256, functions)
683
+ constructor(bytecodeTemplate: string, fieldsSig: FieldsSig, functions: FunctionSig[]) {
684
+ super(functions)
555
685
  this.bytecodeTemplate = bytecodeTemplate
556
686
  this.fieldsSig = fieldsSig
557
687
  }
558
688
 
559
- static checkCodeType(fileName: string, contractStr: string): void {
560
- const scriptMatches = contractStr.match(this.scriptRegex)
561
- if (scriptMatches === null) {
562
- throw new Error(`No script found in: ${fileName}`)
563
- } else if (scriptMatches.length > 1) {
564
- throw new Error(`Multiple scripts in: ${fileName}`)
565
- } else {
566
- return
567
- }
568
- }
569
-
570
- private static async loadContractStr(sourceFile: SourceFile): Promise<string> {
571
- return Common._loadContractStr(sourceFile, [], (code) => Script.checkCodeType(sourceFile.contractPath, code))
572
- }
573
-
574
- static async fromSource(provider: NodeProvider, path: string): Promise<Script> {
575
- const sourceFile = this.getSourceFile(path, [])
576
- return Common._from(provider, sourceFile, (sourceFile) => Script.loadContractStr(sourceFile), Script.compile)
577
- }
578
-
579
- private static async compile(
580
- provider: NodeProvider,
581
- sourceFile: SourceFile,
582
- scriptStr: string,
583
- contractHash: string
584
- ): Promise<Script> {
585
- const compiled = await provider.contracts.postContractsCompileScript({ code: scriptStr })
586
- const artifact = new Script(contractHash, compiled.bytecodeTemplate, compiled.fields, compiled.functions)
587
- await artifact._saveToFile(sourceFile)
588
- return artifact
689
+ static fromCompileResult(result: CompileScriptResult): Script {
690
+ return new Script(result.bytecodeTemplate, result.fields, result.functions)
589
691
  }
590
692
 
591
693
  // TODO: safely parse json
592
694
  static fromJson(artifact: any): Script {
593
- if (
594
- artifact.sourceCodeSha256 == null ||
595
- artifact.bytecodeTemplate == null ||
596
- artifact.fieldsSig == null ||
597
- artifact.functions == null
598
- ) {
695
+ if (artifact.bytecodeTemplate == null || artifact.fieldsSig == null || artifact.functions == null) {
599
696
  throw Error('The artifact JSON for script is incomplete')
600
697
  }
601
- return new Script(artifact.sourceCodeSha256, artifact.bytecodeTemplate, artifact.fieldsSig, artifact.functions)
698
+ return new Script(artifact.bytecodeTemplate, artifact.fieldsSig, artifact.functions)
602
699
  }
603
700
 
604
701
  static async fromArtifactFile(path: string): Promise<Script> {
605
- const sourceFile = this.getSourceFile(path, [])
606
- const artifactPath = sourceFile.artifactPath
607
- const content = await fsPromises.readFile(artifactPath)
702
+ const content = await fsPromises.readFile(path)
608
703
  const artifact = JSON.parse(content.toString())
609
704
  return this.fromJson(artifact)
610
705
  }
611
706
 
612
707
  override toString(): string {
613
- return JSON.stringify(
614
- {
615
- sourceCodeSha256: this.sourceCodeSha256,
616
- bytecodeTemplate: this.bytecodeTemplate,
617
- fieldsSig: this.fieldsSig,
618
- functions: this.functions
619
- },
620
- null,
621
- 2
622
- )
708
+ const object = {
709
+ bytecodeTemplate: this.bytecodeTemplate,
710
+ fieldsSig: this.fieldsSig,
711
+ functions: this.functions
712
+ }
713
+ return JSON.stringify(object, null, 2)
623
714
  }
624
715
 
625
716
  async paramsForDeployment(params: BuildExecuteScriptTx): Promise<SignExecuteScriptTxParams> {
@@ -895,7 +986,7 @@ export interface ContractState {
895
986
  initialStateHash?: string
896
987
  codeHash: string
897
988
  fields: Fields
898
- fieldsSig: node.FieldsSig
989
+ fieldsSig: FieldsSig
899
990
  asset: Asset
900
991
  }
901
992
 
@@ -918,12 +1009,12 @@ function toApiContractState(state: ContractState): node.ContractState {
918
1009
  }
919
1010
  }
920
1011
 
921
- function toApiFields(fields: Fields, fieldsSig: node.FieldsSig): node.Val[] {
1012
+ function toApiFields(fields: Fields, fieldsSig: FieldsSig): node.Val[] {
922
1013
  return toApiVals(fields, fieldsSig.names, fieldsSig.types)
923
1014
  }
924
1015
 
925
- function toApiArgs(args: Arguments, funcSig: node.FunctionSig): node.Val[] {
926
- return toApiVals(args, funcSig.argNames, funcSig.argTypes)
1016
+ function toApiArgs(args: Arguments, funcSig: FunctionSig): node.Val[] {
1017
+ return toApiVals(args, funcSig.paramNames, funcSig.paramTypes)
927
1018
  }
928
1019
 
929
1020
  function toApiVals(fields: Fields, names: string[], types: string[]): node.Val[] {