@alephium/web3 0.2.0-rc.2 → 0.2.0-rc.21

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.
Files changed (121) hide show
  1. package/.eslintignore +2 -2
  2. package/README.md +2 -135
  3. package/dist/alephium-web3.min.js +1 -1
  4. package/dist/alephium-web3.min.js.LICENSE.txt +0 -17
  5. package/dist/alephium-web3.min.js.map +1 -1
  6. package/dist/src/api/api-alephium.d.ts +126 -22
  7. package/dist/src/api/api-alephium.js +145 -79
  8. package/dist/src/api/api-explorer.d.ts +163 -48
  9. package/dist/src/api/api-explorer.js +157 -34
  10. package/dist/src/api/index.d.ts +3 -2
  11. package/dist/src/api/index.js +22 -3
  12. package/dist/src/api/types.d.ts +23 -0
  13. package/dist/src/api/types.js +240 -0
  14. package/dist/src/api/utils.d.ts +6 -0
  15. package/dist/{scripts/rename-gitignore.js → src/api/utils.js} +11 -6
  16. package/dist/src/contract/contract.d.ts +117 -69
  17. package/dist/src/contract/contract.js +401 -435
  18. package/dist/src/contract/events.d.ts +4 -4
  19. package/dist/src/contract/index.js +5 -1
  20. package/dist/src/contract/ralph.d.ts +5 -4
  21. package/dist/src/contract/ralph.js +27 -1
  22. package/dist/src/global.d.ts +4 -0
  23. package/dist/{scripts/stop-devnet.js → src/global.js} +17 -11
  24. package/dist/src/index.d.ts +2 -0
  25. package/dist/src/index.js +23 -1
  26. package/dist/src/signer/index.d.ts +0 -1
  27. package/dist/src/signer/index.js +5 -2
  28. package/dist/src/signer/signer.d.ts +21 -11
  29. package/dist/src/signer/signer.js +46 -9
  30. package/dist/src/transaction/index.d.ts +0 -1
  31. package/dist/src/transaction/index.js +5 -2
  32. package/dist/src/transaction/status.d.ts +2 -1
  33. package/dist/src/utils/bs58.d.ts +1 -0
  34. package/dist/src/utils/bs58.js +13 -1
  35. package/dist/src/utils/index.d.ts +0 -1
  36. package/dist/src/utils/index.js +5 -2
  37. package/dist/src/utils/subscription.d.ts +0 -1
  38. package/dist/src/utils/subscription.js +2 -1
  39. package/dist/src/utils/utils.d.ts +6 -11
  40. package/dist/src/utils/utils.js +21 -25
  41. package/jest-config.json +11 -0
  42. package/package.json +11 -47
  43. package/src/api/api-alephium.ts +178 -30
  44. package/src/api/api-explorer.ts +234 -51
  45. package/src/api/index.ts +14 -3
  46. package/src/api/types.ts +233 -0
  47. package/{scripts/rename-gitignore.js → src/api/utils.ts} +7 -6
  48. package/src/contract/contract.ts +602 -527
  49. package/src/contract/events.ts +6 -6
  50. package/src/contract/ralph.ts +29 -4
  51. package/src/{transaction/sign-verify.ts → global.ts} +14 -15
  52. package/src/index.ts +7 -0
  53. package/src/signer/index.ts +0 -1
  54. package/src/signer/signer.ts +70 -19
  55. package/src/transaction/index.ts +0 -1
  56. package/src/transaction/status.ts +4 -2
  57. package/src/utils/bs58.ts +11 -0
  58. package/src/utils/index.ts +0 -1
  59. package/src/utils/subscription.ts +3 -3
  60. package/src/utils/utils.ts +14 -22
  61. package/.eslintrc.json +0 -21
  62. package/LICENSE +0 -165
  63. package/contracts/add/add.ral +0 -16
  64. package/contracts/greeter/greeter.ral +0 -7
  65. package/contracts/greeter/greeter_interface.ral +0 -3
  66. package/contracts/greeter_main.ral +0 -9
  67. package/contracts/main.ral +0 -6
  68. package/contracts/sub/sub.ral +0 -9
  69. package/dev/user.conf +0 -29
  70. package/dist/scripts/create-project.d.ts +0 -2
  71. package/dist/scripts/create-project.js +0 -125
  72. package/dist/scripts/rename-gitignore.d.ts +0 -1
  73. package/dist/scripts/start-devnet.d.ts +0 -1
  74. package/dist/scripts/start-devnet.js +0 -131
  75. package/dist/scripts/stop-devnet.d.ts +0 -1
  76. package/dist/src/signer/node-wallet.d.ts +0 -13
  77. package/dist/src/signer/node-wallet.js +0 -60
  78. package/dist/src/test/index.d.ts +0 -7
  79. package/dist/src/test/index.js +0 -41
  80. package/dist/src/test/privatekey-wallet.d.ts +0 -12
  81. package/dist/src/test/privatekey-wallet.js +0 -68
  82. package/dist/src/transaction/sign-verify.d.ts +0 -2
  83. package/dist/src/transaction/sign-verify.js +0 -58
  84. package/dist/src/utils/password-crypto.d.ts +0 -2
  85. package/dist/src/utils/password-crypto.js +0 -69
  86. package/gitignore +0 -10
  87. package/scripts/create-project.ts +0 -137
  88. package/scripts/start-devnet.js +0 -141
  89. package/scripts/stop-devnet.js +0 -32
  90. package/src/contract/ralph.test.ts +0 -178
  91. package/src/fixtures/address.json +0 -36
  92. package/src/fixtures/balance.json +0 -9
  93. package/src/fixtures/self-clique.json +0 -19
  94. package/src/fixtures/transaction.json +0 -13
  95. package/src/fixtures/transactions.json +0 -179
  96. package/src/signer/fixtures/genesis.json +0 -26
  97. package/src/signer/fixtures/wallets.json +0 -26
  98. package/src/signer/node-wallet.ts +0 -74
  99. package/src/test/index.ts +0 -32
  100. package/src/test/privatekey-wallet.ts +0 -58
  101. package/src/transaction/sign-verify.test.ts +0 -50
  102. package/src/utils/address.test.ts +0 -47
  103. package/src/utils/djb2.test.ts +0 -35
  104. package/src/utils/password-crypto.test.ts +0 -27
  105. package/src/utils/password-crypto.ts +0 -77
  106. package/src/utils/utils.test.ts +0 -161
  107. package/templates/base/README.md +0 -34
  108. package/templates/base/package.json +0 -35
  109. package/templates/base/src/greeter.ts +0 -41
  110. package/templates/base/tsconfig.json +0 -19
  111. package/templates/react/README.md +0 -34
  112. package/templates/react/config-overrides.js +0 -18
  113. package/templates/react/package.json +0 -66
  114. package/templates/react/src/App.tsx +0 -42
  115. package/templates/react/src/artifacts/greeter.ral.json +0 -26
  116. package/templates/react/src/artifacts/greeter_main.ral.json +0 -22
  117. package/templates/shared/.eslintrc.json +0 -12
  118. package/templates/shared/scripts/header.js +0 -0
  119. package/test/contract.test.ts +0 -178
  120. package/test/events.test.ts +0 -138
  121. package/test/transaction.test.ts +0 -72
@@ -17,250 +17,551 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
 
19
19
  import { Buffer } from 'buffer/'
20
- import * as cryptojs from 'crypto-js'
21
- import * as crypto from 'crypto'
20
+ import { webcrypto as crypto } from 'crypto'
22
21
  import fs from 'fs'
23
22
  import { promises as fsPromises } from 'fs'
24
- import { NodeProvider } from '../api'
25
- import { node } from '../api'
23
+ import {
24
+ fromApiArray,
25
+ fromApiNumber256,
26
+ toApiNumber256,
27
+ NamedVals,
28
+ node,
29
+ NodeProvider,
30
+ Number256,
31
+ toApiToken,
32
+ toApiVal,
33
+ Token,
34
+ Val,
35
+ toApiTokens,
36
+ fromApiTokens,
37
+ fromApiVals
38
+ } from '../api'
26
39
  import { SignDeployContractTxParams, SignExecuteScriptTxParams, SignerWithNodeProvider } from '../signer'
27
40
  import * as ralph from './ralph'
28
41
  import { bs58, binToHex, contractIdFromAddress, assertType, Eq } from '../utils'
42
+ import { getCurrentNodeProvider } from '../global'
43
+ import { web3 } from '..'
44
+
45
+ export type FieldsSig = node.FieldsSig
46
+ export type EventSig = node.EventSig
47
+ export type FunctionSig = node.FunctionSig
48
+ export type Fields = NamedVals
49
+ export type Arguments = NamedVals
50
+
51
+ enum SourceType {
52
+ Contract = 0,
53
+ Script = 1,
54
+ AbstractContract = 2,
55
+ Interface = 3
56
+ }
57
+
58
+ export type CompilerOptions = node.CompilerOptions & {
59
+ errorOnWarnings: boolean
60
+ }
61
+
62
+ export const DEFAULT_NODE_COMPILER_OPTIONS: node.CompilerOptions = {
63
+ ignoreUnusedConstantsWarnings: false,
64
+ ignoreUnusedVariablesWarnings: false,
65
+ ignoreUnusedFieldsWarnings: false,
66
+ ignoreUnusedPrivateFunctionsWarnings: false,
67
+ ignoreReadonlyCheckWarnings: false,
68
+ ignoreExternalCallCheckWarnings: false
69
+ }
70
+
71
+ export const DEFAULT_COMPILER_OPTIONS: CompilerOptions = { errorOnWarnings: true, ...DEFAULT_NODE_COMPILER_OPTIONS }
72
+
73
+ class TypedMatcher<T extends SourceType> {
74
+ matcher: RegExp
75
+ type: T
76
+
77
+ constructor(pattern: string, type: T) {
78
+ this.matcher = new RegExp(pattern, 'mg')
79
+ this.type = type
80
+ }
81
+
82
+ match(str: string): number {
83
+ const results = str.match(this.matcher)
84
+ return results === null ? 0 : results.length
85
+ }
86
+ }
29
87
 
30
88
  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
- }
89
+ type: SourceType
90
+ contractPath: string
91
+ sourceCode: string
92
+ sourceCodeHash: string
93
+
94
+ getArtifactPath(artifactsRootDir: string): string {
95
+ return artifactsRootDir + this.contractPath.slice(this.contractPath.indexOf('/')) + '.json'
96
+ }
97
+
98
+ constructor(type: SourceType, sourceCode: string, sourceCodeHash: string, contractPath: string) {
99
+ this.type = type
100
+ this.sourceCode = sourceCode
101
+ this.sourceCodeHash = sourceCodeHash
102
+ this.contractPath = contractPath
103
+ }
104
+
105
+ static async from(type: SourceType, sourceCode: string, contractPath: string): Promise<SourceFile> {
106
+ const sourceCodeHash = await crypto.subtle.digest('SHA-256', Buffer.from(sourceCode))
107
+ return new SourceFile(type, sourceCode, Buffer.from(sourceCodeHash).toString('hex'), contractPath)
46
108
  }
47
109
  }
48
110
 
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)
68
- }
69
- this._artifactCache.set(contract.codeHash, contract)
111
+ class Compiled<T extends Artifact> {
112
+ sourceFile: SourceFile
113
+ artifact: T
114
+ warnings: string[]
115
+
116
+ constructor(sourceFile: SourceFile, artifact: T, warnings: string[]) {
117
+ this.sourceFile = sourceFile
118
+ this.artifact = artifact
119
+ this.warnings = warnings
120
+ }
121
+ }
122
+
123
+ type CodeInfo = { sourceCodeHash: string; bytecodeDebugPatch: string; codeHashDebug: string; warnings: string[] }
124
+
125
+ class ProjectArtifact {
126
+ static readonly artifactFileName = '.project.json'
127
+
128
+ compilerOptionsUsed: node.CompilerOptions
129
+ infos: Map<string, CodeInfo>
130
+
131
+ static checkCompilerOptionsParameter(compilerOptions: node.CompilerOptions): void {
132
+ if (Object.keys(compilerOptions).length != Object.keys(DEFAULT_NODE_COMPILER_OPTIONS).length) {
133
+ throw Error(`Not all compiler options are set: ${compilerOptions}`)
134
+ }
135
+
136
+ const combined = { ...compilerOptions, ...DEFAULT_NODE_COMPILER_OPTIONS }
137
+ if (Object.keys(combined).length !== Object.keys(DEFAULT_NODE_COMPILER_OPTIONS).length) {
138
+ throw Error(`There are unknown compiler options: ${compilerOptions}`)
70
139
  }
71
140
  }
72
141
 
73
- constructor(sourceCodeSha256: string, functions: node.FunctionSig[]) {
74
- this.sourceCodeSha256 = sourceCodeSha256
75
- this.functions = functions
142
+ constructor(compilerOptionsUsed: node.CompilerOptions, infos: Map<string, CodeInfo>) {
143
+ ProjectArtifact.checkCompilerOptionsParameter(compilerOptionsUsed)
144
+ this.compilerOptionsUsed = compilerOptionsUsed
145
+ this.infos = infos
76
146
  }
77
147
 
78
- protected static _artifactsFolder(): string {
79
- return './artifacts/'
148
+ async saveToFile(rootPath: string): Promise<void> {
149
+ const filepath = rootPath + '/' + ProjectArtifact.artifactFileName
150
+ const artifact = { compilerOptionsUsed: this.compilerOptionsUsed, infos: Object.fromEntries(this.infos) }
151
+ const content = JSON.stringify(artifact, null, 2)
152
+ return fsPromises.writeFile(filepath, content)
80
153
  }
81
154
 
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)
155
+ needToReCompile(compilerOptions: node.CompilerOptions, files: SourceFile[]): boolean {
156
+ ProjectArtifact.checkCompilerOptionsParameter(compilerOptions)
157
+
158
+ const optionsMatched = Object.entries(compilerOptions).every(([key, inputOption]) => {
159
+ const usedOption = this.compilerOptionsUsed[`${key}`]
160
+ return usedOption === inputOption
161
+ })
162
+ if (!optionsMatched) {
163
+ return true
87
164
  }
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
- }
165
+
166
+ if (files.length !== this.infos.size) {
167
+ return true
168
+ }
169
+ for (const file of files) {
170
+ const info = this.infos.get(file.contractPath)
171
+ if (typeof info === 'undefined' || info.sourceCodeHash !== file.sourceCodeHash) {
172
+ return true
103
173
  }
174
+ }
175
+
176
+ return false
177
+ }
178
+
179
+ static async from(rootPath: string): Promise<ProjectArtifact | undefined> {
180
+ const filepath = rootPath + '/' + ProjectArtifact.artifactFileName
181
+ if (!fs.existsSync(filepath)) {
182
+ return undefined
183
+ }
184
+ const content = await fsPromises.readFile(filepath)
185
+ const json = JSON.parse(content.toString())
186
+ const compilerOptionsUsed = json.compilerOptionsUsed as node.CompilerOptions
187
+ const files = new Map(Object.entries<CodeInfo>(json.infos))
188
+ return new ProjectArtifact(compilerOptionsUsed, files)
189
+ }
190
+ }
191
+
192
+ export class Project {
193
+ sourceFiles: SourceFile[]
194
+ contracts: Compiled<Contract>[]
195
+ scripts: Compiled<Script>[]
196
+ projectArtifact: ProjectArtifact
197
+
198
+ readonly contractsRootDir: string
199
+ readonly artifactsRootDir: string
200
+
201
+ static currentProject: Project
202
+
203
+ static readonly abstractContractMatcher = new TypedMatcher<SourceType>(
204
+ '^Abstract Contract [A-Z][a-zA-Z0-9]*',
205
+ SourceType.AbstractContract
206
+ )
207
+ static readonly contractMatcher = new TypedMatcher('^Contract [A-Z][a-zA-Z0-9]*', SourceType.Contract)
208
+ static readonly interfaceMatcher = new TypedMatcher('^Interface [A-Z][a-zA-Z0-9]* \\{', SourceType.Interface)
209
+ static readonly scriptMatcher = new TypedMatcher('^TxScript [A-Z][a-zA-Z0-9]*', SourceType.Script)
210
+ static readonly matchers = [
211
+ Project.abstractContractMatcher,
212
+ Project.contractMatcher,
213
+ Project.interfaceMatcher,
214
+ Project.scriptMatcher
215
+ ]
216
+
217
+ static buildProjectArtifact(
218
+ sourceFiles: SourceFile[],
219
+ contracts: Compiled<Contract>[],
220
+ scripts: Compiled<Script>[],
221
+ compilerOptions: node.CompilerOptions
222
+ ): ProjectArtifact {
223
+ const files: Map<string, CodeInfo> = new Map()
224
+ contracts.forEach((c) => {
225
+ files.set(c.sourceFile.contractPath, {
226
+ sourceCodeHash: c.sourceFile.sourceCodeHash,
227
+ bytecodeDebugPatch: c.artifact.bytecodeDebugPatch,
228
+ codeHashDebug: c.artifact.codeHashDebug,
229
+ warnings: c.warnings
230
+ })
231
+ })
232
+ scripts.forEach((s) => {
233
+ files.set(s.sourceFile.contractPath, {
234
+ sourceCodeHash: s.sourceFile.sourceCodeHash,
235
+ bytecodeDebugPatch: s.artifact.bytecodeDebugPatch,
236
+ codeHashDebug: '',
237
+ warnings: s.warnings
238
+ })
104
239
  })
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 ''
240
+ const compiledSize = contracts.length + scripts.length
241
+ sourceFiles.slice(compiledSize).forEach((c) => {
242
+ files.set(c.contractPath, {
243
+ sourceCodeHash: c.sourceCodeHash,
244
+ bytecodeDebugPatch: '',
245
+ codeHashDebug: '',
246
+ warnings: []
247
+ })
117
248
  })
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)
249
+ return new ProjectArtifact(compilerOptions, files)
250
+ }
251
+
252
+ private constructor(
253
+ contractsRootDir: string,
254
+ artifactsRootDir: string,
255
+ sourceFiles: SourceFile[],
256
+ contracts: Compiled<Contract>[],
257
+ scripts: Compiled<Script>[],
258
+ errorOnWarnings: boolean,
259
+ projectArtifact: ProjectArtifact
260
+ ) {
261
+ this.contractsRootDir = contractsRootDir
262
+ this.artifactsRootDir = artifactsRootDir
263
+ this.sourceFiles = sourceFiles
264
+ this.contracts = contracts
265
+ this.scripts = scripts
266
+ this.projectArtifact = projectArtifact
267
+
268
+ if (errorOnWarnings) {
269
+ Project.checkCompilerWarnings(
270
+ [...contracts.map((c) => c.warnings).flat(), ...scripts.map((s) => s.warnings).flat()],
271
+ errorOnWarnings
272
+ )
273
+ }
274
+ }
275
+
276
+ private getContractPath(path: string): string {
277
+ return path.startsWith(`./${this.contractsRootDir}`)
278
+ ? path.slice(2)
279
+ : path.startsWith(this.contractsRootDir)
280
+ ? path
281
+ : this.contractsRootDir + '/' + path
282
+ }
283
+
284
+ static checkCompilerWarnings(warnings: string[], errorOnWarnings: boolean): void {
285
+ if (warnings.length !== 0) {
286
+ const prefixPerWarning = ' - '
287
+ const warningString = prefixPerWarning + warnings.join('\n' + prefixPerWarning)
288
+ const output = `Compilation warnings:\n` + warningString + '\n'
289
+ if (errorOnWarnings) {
290
+ throw new Error(output)
291
+ } else {
292
+ console.log(output)
127
293
  }
128
294
  }
129
- return result
130
295
  }
131
296
 
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()
297
+ static contract(name: string): Contract {
298
+ const contract = Project.currentProject.contracts.find((c) => c.artifact.name === name)
299
+ if (typeof contract === 'undefined') {
300
+ throw new Error(`Contract "${name}" does not exist`)
301
+ }
302
+ return contract.artifact
303
+ }
304
+
305
+ static script(name: string): Script {
306
+ const script = Project.currentProject.scripts.find((c) => c.artifact.name === name)
307
+ if (typeof script === 'undefined') {
308
+ throw new Error(`Script "${name}" does not exist`)
309
+ }
310
+ return script.artifact
311
+ }
140
312
 
141
- validate(contractStr)
142
- return Common._handleImports(sourceFile.dirs, contractStr, importsCache)
313
+ private async saveArtifactsToFile(): Promise<void> {
314
+ const artifactsRootDir = this.artifactsRootDir
315
+ const saveToFile = async function (compiled: Compiled<Artifact>): Promise<void> {
316
+ const artifactDir = compiled.sourceFile.getArtifactPath(artifactsRootDir)
317
+ const folder = artifactDir.slice(0, artifactDir.lastIndexOf('/'))
318
+ if (!fs.existsSync(folder)) {
319
+ fs.mkdirSync(folder, { recursive: true })
320
+ }
321
+ return fsPromises.writeFile(artifactDir, compiled.artifact.toString())
322
+ }
323
+ for (const contract of this.contracts) {
324
+ await saveToFile(contract)
325
+ }
326
+ for (const script of this.scripts) {
327
+ await saveToFile(script)
328
+ }
329
+ await this.projectArtifact.saveToFile(this.artifactsRootDir)
143
330
  }
144
331
 
145
- static checkFileNameExtension(fileName: string): void {
146
- if (!fileName.endsWith('.ral')) {
147
- throw new Error('Smart contract file name should end with ".ral"')
332
+ contractByCodeHash(codeHash: string): Contract {
333
+ const contract = this.contracts.find(
334
+ (c) => c.artifact.codeHash === codeHash || c.artifact.codeHashDebug == codeHash
335
+ )
336
+ if (typeof contract === 'undefined') {
337
+ throw new Error(`Unknown code with code hash: ${codeHash}`)
148
338
  }
339
+ return contract.artifact
340
+ }
341
+
342
+ private static async compile(
343
+ provider: NodeProvider,
344
+ files: SourceFile[],
345
+ contractsRootDir: string,
346
+ artifactsRootDir: string,
347
+ errorOnWarnings: boolean,
348
+ compilerOptions: node.CompilerOptions
349
+ ): Promise<Project> {
350
+ const sourceStr = files.map((f) => f.sourceCode).join('\n')
351
+ const result = await provider.contracts.postContractsCompileProject({
352
+ code: sourceStr,
353
+ compilerOptions: compilerOptions
354
+ })
355
+ const contracts: Compiled<Contract>[] = []
356
+ const scripts: Compiled<Script>[] = []
357
+ result.contracts.forEach((contractResult, index) => {
358
+ const sourceFile = files[`${index}`]
359
+ const contract = Contract.fromCompileResult(contractResult)
360
+ contracts.push(new Compiled(sourceFile, contract, contractResult.warnings))
361
+ })
362
+ result.scripts.forEach((scriptResult, index) => {
363
+ const sourceFile = files[index + contracts.length]
364
+ const script = Script.fromCompileResult(scriptResult)
365
+ scripts.push(new Compiled(sourceFile, script, scriptResult.warnings))
366
+ })
367
+ const projectArtifact = Project.buildProjectArtifact(files, contracts, scripts, compilerOptions)
368
+ const project = new Project(
369
+ contractsRootDir,
370
+ artifactsRootDir,
371
+ files,
372
+ contracts,
373
+ scripts,
374
+ errorOnWarnings,
375
+ projectArtifact
376
+ )
377
+ await project.saveArtifactsToFile()
378
+ return project
149
379
  }
150
380
 
151
- protected static async _from<T extends { sourceCodeSha256: string }>(
381
+ private static async loadArtifacts(
152
382
  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
164
- } else {
165
- return compile(provider, sourceFile, contractStr, contractHash)
383
+ files: SourceFile[],
384
+ projectArtifact: ProjectArtifact,
385
+ contractsRootDir: string,
386
+ artifactsRootDir: string,
387
+ errorOnWarnings: boolean,
388
+ compilerOptions: node.CompilerOptions
389
+ ): Promise<Project> {
390
+ try {
391
+ const contracts: Compiled<Contract>[] = []
392
+ const scripts: Compiled<Script>[] = []
393
+ for (const file of files) {
394
+ const info = projectArtifact.infos.get(file.contractPath)
395
+ if (typeof info === 'undefined') {
396
+ throw Error(`Unable to find project info for ${file.contractPath}, please rebuild the project`)
397
+ }
398
+ const warnings = info.warnings
399
+ const artifactDir = file.getArtifactPath(artifactsRootDir)
400
+ if (file.type === SourceType.Contract) {
401
+ const artifact = await Contract.fromArtifactFile(artifactDir, info.bytecodeDebugPatch, info.codeHashDebug)
402
+ contracts.push(new Compiled(file, artifact, warnings))
403
+ } else if (file.type === SourceType.Script) {
404
+ const artifact = await Script.fromArtifactFile(artifactDir, info.bytecodeDebugPatch)
405
+ scripts.push(new Compiled(file, artifact, warnings))
406
+ }
407
+ }
408
+
409
+ return new Project(
410
+ contractsRootDir,
411
+ artifactsRootDir,
412
+ files,
413
+ contracts,
414
+ scripts,
415
+ errorOnWarnings,
416
+ projectArtifact
417
+ )
418
+ } catch (error) {
419
+ console.log(`Failed to load artifacts, error: ${error}, try to re-compile contracts...`)
420
+ return Project.compile(provider, files, contractsRootDir, artifactsRootDir, errorOnWarnings, compilerOptions)
166
421
  }
167
422
  }
168
423
 
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 })
424
+ private static async loadSourceFile(dirPath: string, filename: string): Promise<SourceFile> {
425
+ const contractPath = dirPath + '/' + filename
426
+ if (!filename.endsWith('.ral')) {
427
+ throw new Error(`Invalid filename: ${contractPath}, smart contract file name should end with ".ral"`)
428
+ }
429
+
430
+ const sourceBuffer = await fsPromises.readFile(contractPath)
431
+ const sourceStr = sourceBuffer.toString()
432
+ const results = this.matchers.map((m) => m.match(sourceStr))
433
+ const matchNumber = results.reduce((a, b) => a + b, 0)
434
+ if (matchNumber === 0) {
435
+ throw new Error(`No contract defined in file: ${contractPath}`)
436
+ }
437
+ if (matchNumber > 1) {
438
+ throw new Error(`Multiple definitions in file: ${contractPath}`)
439
+ }
440
+ const matcherIndex = results.indexOf(1)
441
+ const type = this.matchers[`${matcherIndex}`].type
442
+ return SourceFile.from(type, sourceStr, contractPath)
443
+ }
444
+
445
+ private static async loadSourceFiles(contractsRootDir: string): Promise<SourceFile[]> {
446
+ const loadDir = async function (dirPath: string, results: SourceFile[]): Promise<void> {
447
+ const dirents = await fsPromises.readdir(dirPath, { withFileTypes: true })
448
+ for (const dirent of dirents) {
449
+ if (dirent.isFile()) {
450
+ const file = await Project.loadSourceFile(dirPath, dirent.name)
451
+ results.push(file)
452
+ } else {
453
+ const newPath = dirPath + '/' + dirent.name
454
+ await loadDir(newPath, results)
455
+ }
456
+ }
457
+ }
458
+ const sourceFiles: SourceFile[] = []
459
+ await loadDir(contractsRootDir, sourceFiles)
460
+ const contractAndScriptSize = sourceFiles.filter(
461
+ (f) => f.type === SourceType.Contract || f.type === SourceType.Script
462
+ ).length
463
+ if (sourceFiles.length === 0 || contractAndScriptSize === 0) {
464
+ throw new Error('Project have no source files')
465
+ }
466
+ return sourceFiles.sort((a, b) => a.type - b.type)
467
+ }
468
+
469
+ static readonly DEFAULT_CONTRACTS_DIR = 'contracts'
470
+ static readonly DEFAULT_ARTIFACTS_DIR = 'artifacts'
471
+
472
+ static async build(
473
+ compilerOptionsPartial: Partial<CompilerOptions> = {},
474
+ contractsRootDir = Project.DEFAULT_CONTRACTS_DIR,
475
+ artifactsRootDir = Project.DEFAULT_ARTIFACTS_DIR
476
+ ): Promise<void> {
477
+ const provider = getCurrentNodeProvider()
478
+ const sourceFiles = await Project.loadSourceFiles(contractsRootDir)
479
+ const { errorOnWarnings, ...nodeCompilerOptions } = { ...DEFAULT_COMPILER_OPTIONS, ...compilerOptionsPartial }
480
+ const projectArtifact = await ProjectArtifact.from(artifactsRootDir)
481
+ if (typeof projectArtifact === 'undefined' || projectArtifact.needToReCompile(nodeCompilerOptions, sourceFiles)) {
482
+ console.log(`Compiling contracts in folder "${contractsRootDir}"`)
483
+ Project.currentProject = await Project.compile(
484
+ provider,
485
+ sourceFiles,
486
+ contractsRootDir,
487
+ artifactsRootDir,
488
+ errorOnWarnings,
489
+ nodeCompilerOptions
490
+ )
491
+ } else {
492
+ console.log(`Contracts are compiled already. Loading them from folder "${artifactsRootDir}"`)
493
+ Project.currentProject = await Project.loadArtifacts(
494
+ provider,
495
+ sourceFiles,
496
+ projectArtifact,
497
+ contractsRootDir,
498
+ artifactsRootDir,
499
+ errorOnWarnings,
500
+ nodeCompilerOptions
501
+ )
173
502
  }
174
- return fsPromises.writeFile(sourceFile.artifactPath, this.toString())
503
+ }
504
+ }
505
+
506
+ export abstract class Artifact {
507
+ readonly name: string
508
+ readonly functions: FunctionSig[]
509
+
510
+ constructor(name: string, functions: FunctionSig[]) {
511
+ this.name = name
512
+ this.functions = functions
175
513
  }
176
514
 
177
515
  abstract buildByteCodeToDeploy(initialFields?: Fields): string
516
+
517
+ publicFunctions(): string[] {
518
+ return this.functions.filter((func) => func.isPublic).map((func) => func.name)
519
+ }
520
+
521
+ usingPreapprovedAssetsFunctions(): string[] {
522
+ return this.functions.filter((func) => func.usePreapprovedAssets).map((func) => func.name)
523
+ }
524
+
525
+ usingAssetsInContractFunctions(): string[] {
526
+ return this.functions.filter((func) => func.useAssetsInContract).map((func) => func.name)
527
+ }
178
528
  }
179
529
 
180
- export class Contract extends Common {
530
+ export class Contract extends Artifact {
181
531
  readonly bytecode: string
532
+ readonly bytecodeDebugPatch: string
182
533
  readonly codeHash: string
183
- readonly fieldsSig: node.FieldsSig
184
- readonly eventsSig: node.EventSig[]
534
+ readonly fieldsSig: FieldsSig
535
+ readonly eventsSig: EventSig[]
536
+
537
+ readonly bytecodeDebug: string
538
+ readonly codeHashDebug: string
185
539
 
186
540
  constructor(
187
- sourceCodeSha256: string,
541
+ name: string,
188
542
  bytecode: string,
543
+ bytecodeDebugPatch: string,
189
544
  codeHash: string,
190
- fieldsSig: node.FieldsSig,
191
- eventsSig: node.EventSig[],
192
- functions: node.FunctionSig[]
545
+ codeHashDebug: string,
546
+ fieldsSig: FieldsSig,
547
+ eventsSig: EventSig[],
548
+ functions: FunctionSig[]
193
549
  ) {
194
- super(sourceCodeSha256, functions)
550
+ super(name, functions)
195
551
  this.bytecode = bytecode
552
+ this.bytecodeDebugPatch = bytecodeDebugPatch
196
553
  this.codeHash = codeHash
197
554
  this.fieldsSig = fieldsSig
198
555
  this.eventsSig = eventsSig
199
- }
200
-
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
556
 
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
557
+ this.bytecodeDebug = ralph.buildDebugBytecode(this.bytecode, this.bytecodeDebugPatch)
558
+ this.codeHashDebug = codeHashDebug
258
559
  }
259
560
 
260
561
  // TODO: safely parse json
261
- static fromJson(artifact: any): Contract {
562
+ static fromJson(artifact: any, bytecodeDebugPatch = '', codeHashDebug = ''): Contract {
262
563
  if (
263
- artifact.sourceCodeSha256 == null ||
564
+ artifact.name == null ||
264
565
  artifact.bytecode == null ||
265
566
  artifact.codeHash == null ||
266
567
  artifact.fieldsSig == null ||
@@ -270,44 +571,55 @@ export class Contract extends Common {
270
571
  throw Error('The artifact JSON for contract is incomplete')
271
572
  }
272
573
  const contract = new Contract(
273
- artifact.sourceCodeSha256,
574
+ artifact.name,
274
575
  artifact.bytecode,
576
+ bytecodeDebugPatch,
275
577
  artifact.codeHash,
578
+ codeHashDebug ? codeHashDebug : artifact.codeHash,
276
579
  artifact.fieldsSig,
277
580
  artifact.eventsSig,
278
581
  artifact.functions
279
582
  )
280
- this._putArtifactToCache(contract)
281
583
  return contract
282
584
  }
283
585
 
586
+ static fromCompileResult(result: node.CompileContractResult): Contract {
587
+ return new Contract(
588
+ result.name,
589
+ result.bytecode,
590
+ result.bytecodeDebugPatch,
591
+ result.codeHash,
592
+ result.codeHashDebug,
593
+ result.fields,
594
+ result.events,
595
+ result.functions
596
+ )
597
+ }
598
+
284
599
  // support both 'code.ral' and 'code.ral.json'
285
- 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)
600
+ static async fromArtifactFile(path: string, bytecodeDebugPatch: string, codeHashDebug: string): Promise<Contract> {
601
+ const content = await fsPromises.readFile(path)
289
602
  const artifact = JSON.parse(content.toString())
290
- return Contract.fromJson(artifact)
603
+ return Contract.fromJson(artifact, bytecodeDebugPatch, codeHashDebug)
291
604
  }
292
605
 
293
- async fetchState(provider: NodeProvider, address: string, group: number): Promise<ContractState> {
294
- const state = await provider.contracts.getContractsAddressState(address, { group: group })
606
+ async fetchState(address: string, group: number): Promise<ContractState> {
607
+ const state = await web3.getCurrentNodeProvider().contracts.getContractsAddressState(address, {
608
+ group: group
609
+ })
295
610
  return this.fromApiContractState(state)
296
611
  }
297
612
 
298
613
  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
- )
614
+ const object = {
615
+ name: this.name,
616
+ bytecode: this.bytecode,
617
+ codeHash: this.codeHash,
618
+ fieldsSig: this.fieldsSig,
619
+ eventsSig: this.eventsSig,
620
+ functions: this.functions
621
+ }
622
+ return JSON.stringify(object, null, 2)
311
623
  }
312
624
 
313
625
  toState(fields: Fields, asset: Asset, address?: string): ContractState {
@@ -323,25 +635,37 @@ export class Contract extends Common {
323
635
  }
324
636
  }
325
637
 
638
+ // no need to be cryptographically strong random
326
639
  static randomAddress(): string {
327
- const bytes = crypto.randomBytes(33)
640
+ const bytes = new Uint8Array(33)
641
+ crypto.getRandomValues(bytes)
328
642
  bytes[0] = 3
329
643
  return bs58.encode(bytes)
330
644
  }
331
645
 
646
+ private _printDebugMessages(funcName: string, messages: DebugMessage[]) {
647
+ if (messages.length != 0) {
648
+ console.log(`Testing ${this.name}.${funcName}:`)
649
+ messages.forEach((m) => console.log(`Debug - ${m.contractAddress} - ${m.message}`))
650
+ }
651
+ }
652
+
332
653
  private async _test(
333
- provider: NodeProvider,
334
654
  funcName: string,
335
655
  params: TestContractParams,
336
656
  expectPublic: boolean,
337
- accessType: string
657
+ accessType: string,
658
+ printDebugMessages: boolean
338
659
  ): Promise<TestContractResult> {
339
660
  const apiParams: node.TestContract = this.toTestContract(funcName, params)
340
- const apiResult = await provider.contracts.postContractsTestContract(apiParams)
661
+ const apiResult = await web3.getCurrentNodeProvider().contracts.postContractsTestContract(apiParams)
341
662
 
342
663
  const methodIndex =
343
664
  typeof params.testMethodIndex !== 'undefined' ? params.testMethodIndex : this.getMethodIndex(funcName)
344
- const isPublic = this.functions[`${methodIndex}`].signature.indexOf('pub ') !== -1
665
+ const isPublic = this.functions[`${methodIndex}`].isPublic
666
+ if (printDebugMessages) {
667
+ this._printDebugMessages(funcName, apiResult.debugMessages)
668
+ }
345
669
  if (isPublic === expectPublic) {
346
670
  const result = await this.fromTestContractResult(methodIndex, apiResult)
347
671
  return result
@@ -351,19 +675,19 @@ export class Contract extends Common {
351
675
  }
352
676
 
353
677
  async testPublicMethod(
354
- provider: NodeProvider,
355
678
  funcName: string,
356
- params: TestContractParams
679
+ params: TestContractParams,
680
+ printDebugMessages = true
357
681
  ): Promise<TestContractResult> {
358
- return this._test(provider, funcName, params, true, 'public')
682
+ return this._test(funcName, params, true, 'public', printDebugMessages)
359
683
  }
360
684
 
361
685
  async testPrivateMethod(
362
- provider: NodeProvider,
363
686
  funcName: string,
364
- params: TestContractParams
687
+ params: TestContractParams,
688
+ printDebugMessages = true
365
689
  ): Promise<TestContractResult> {
366
- return this._test(provider, funcName, params, false, 'private')
690
+ return this._test(funcName, params, false, 'private', printDebugMessages)
367
691
  }
368
692
 
369
693
  toApiFields(fields?: Fields): node.Val[] {
@@ -399,7 +723,7 @@ export class Contract extends Common {
399
723
  return {
400
724
  group: params.group,
401
725
  address: params.address,
402
- bytecode: this.bytecode,
726
+ bytecode: this.bytecodeDebug,
403
727
  initialFields: this.toApiFields(params.initialFields),
404
728
  initialAsset: typeof params.initialAsset !== 'undefined' ? toApiAsset(params.initialAsset) : undefined,
405
729
  methodIndex: this.getMethodIndex(funcName),
@@ -409,33 +733,8 @@ export class Contract extends Common {
409
733
  }
410
734
  }
411
735
 
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
736
  async fromApiContractState(state: node.ContractState): Promise<ContractState> {
438
- const contract = await Contract.fromCodeHash(state.codeHash)
737
+ const contract = Project.currentProject.contractByCodeHash(state.codeHash)
439
738
  return {
440
739
  address: state.address,
441
740
  contractId: binToHex(contractIdFromAddress(state.address)),
@@ -443,21 +742,19 @@ export class Contract extends Common {
443
742
  initialStateHash: state.initialStateHash,
444
743
  codeHash: state.codeHash,
445
744
  fields: fromApiFields(state.fields, contract.fieldsSig),
446
- fieldsSig: await Contract.getFieldsSig(state),
745
+ fieldsSig: contract.fieldsSig,
447
746
  asset: fromApiAsset(state.asset)
448
747
  }
449
748
  }
450
749
 
451
- static ContractCreatedEvent: node.EventSig = {
750
+ static ContractCreatedEvent: EventSig = {
452
751
  name: 'ContractCreated',
453
- signature: 'event ContractCreated(address:Address)',
454
752
  fieldNames: ['address'],
455
753
  fieldTypes: ['Address']
456
754
  }
457
755
 
458
- static ContractDestroyedEvent: node.EventSig = {
756
+ static ContractDestroyedEvent: EventSig = {
459
757
  name: 'ContractDestroyed',
460
- signature: 'event ContractDestroyed(address:Address)',
461
758
  fieldNames: ['address'],
462
759
  fieldTypes: ['Address']
463
760
  }
@@ -466,14 +763,14 @@ export class Contract extends Common {
466
763
  event: node.ContractEventByTxId,
467
764
  codeHash: string | undefined
468
765
  ): Promise<ContractEventByTxId> {
469
- let eventSig: node.EventSig
766
+ let eventSig: EventSig
470
767
 
471
768
  if (event.eventIndex == -1) {
472
769
  eventSig = this.ContractCreatedEvent
473
770
  } else if (event.eventIndex == -2) {
474
771
  eventSig = this.ContractDestroyedEvent
475
772
  } else {
476
- const contract = await Contract.fromCodeHash(codeHash!)
773
+ const contract = Project.currentProject.contractByCodeHash(codeHash!)
477
774
  eventSig = contract.eventsSig[event.eventIndex]
478
775
  }
479
776
 
@@ -490,8 +787,8 @@ export class Contract extends Common {
490
787
  addressToCodeHash.set(result.address, result.codeHash)
491
788
  result.contracts.forEach((contract) => addressToCodeHash.set(contract.address, contract.codeHash))
492
789
  return {
493
- address: result.address,
494
790
  contractId: binToHex(contractIdFromAddress(result.address)),
791
+ contractAddress: result.address,
495
792
  returns: fromApiArray(result.returns, this.functions[`${methodIndex}`].returnTypes),
496
793
  gasUsed: result.gasUsed,
497
794
  contracts: await Promise.all(result.contracts.map((contract) => this.fromApiContractState(contract))),
@@ -506,7 +803,8 @@ export class Contract extends Common {
506
803
  throw Error(`Cannot find codeHash for the contract address: ${contractAddress}`)
507
804
  }
508
805
  })
509
- )
806
+ ),
807
+ debugMessages: result.debugMessages
510
808
  }
511
809
  }
512
810
 
@@ -517,7 +815,7 @@ export class Contract extends Common {
517
815
  bytecode: bytecode,
518
816
  initialAttoAlphAmount: extractOptionalNumber256(params.initialAttoAlphAmount),
519
817
  issueTokenAmount: extractOptionalNumber256(params.issueTokenAmount),
520
- initialTokenAmounts: params.initialTokenAmounts?.map(toApiToken),
818
+ initialTokenAmounts: toApiTokens(params.initialTokenAmounts),
521
819
  gasAmount: params.gasAmount,
522
820
  gasPrice: extractOptionalNumber256(params.gasPrice)
523
821
  }
@@ -541,85 +839,61 @@ export class Contract extends Common {
541
839
  }
542
840
  }
543
841
 
544
- export class Script extends Common {
842
+ export class Script extends Artifact {
545
843
  readonly bytecodeTemplate: string
546
- readonly fieldsSig: node.FieldsSig
844
+ readonly bytecodeDebugPatch: string
845
+ readonly fieldsSig: FieldsSig
547
846
 
548
847
  constructor(
549
- sourceCodeSha256: string,
848
+ name: string,
550
849
  bytecodeTemplate: string,
551
- fieldsSig: node.FieldsSig,
552
- functions: node.FunctionSig[]
850
+ bytecodeDebugPatch: string,
851
+ fieldsSig: FieldsSig,
852
+ functions: FunctionSig[]
553
853
  ) {
554
- super(sourceCodeSha256, functions)
854
+ super(name, functions)
555
855
  this.bytecodeTemplate = bytecodeTemplate
856
+ this.bytecodeDebugPatch = bytecodeDebugPatch
556
857
  this.fieldsSig = fieldsSig
557
858
  }
558
859
 
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
860
+ static fromCompileResult(result: node.CompileScriptResult): Script {
861
+ return new Script(result.name, result.bytecodeTemplate, result.bytecodeDebugPatch, result.fields, result.functions)
589
862
  }
590
863
 
591
864
  // TODO: safely parse json
592
- static fromJson(artifact: any): Script {
865
+ static fromJson(artifact: any, bytecodeDebugPatch = ''): Script {
593
866
  if (
594
- artifact.sourceCodeSha256 == null ||
867
+ artifact.name == null ||
595
868
  artifact.bytecodeTemplate == null ||
596
869
  artifact.fieldsSig == null ||
597
870
  artifact.functions == null
598
871
  ) {
599
872
  throw Error('The artifact JSON for script is incomplete')
600
873
  }
601
- return new Script(artifact.sourceCodeSha256, artifact.bytecodeTemplate, artifact.fieldsSig, artifact.functions)
874
+ return new Script(
875
+ artifact.name,
876
+ artifact.bytecodeTemplate,
877
+ bytecodeDebugPatch,
878
+ artifact.fieldsSig,
879
+ artifact.functions
880
+ )
602
881
  }
603
882
 
604
- 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)
883
+ static async fromArtifactFile(path: string, bytecodeDebugPatch: string): Promise<Script> {
884
+ const content = await fsPromises.readFile(path)
608
885
  const artifact = JSON.parse(content.toString())
609
- return this.fromJson(artifact)
886
+ return this.fromJson(artifact, bytecodeDebugPatch)
610
887
  }
611
888
 
612
889
  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
- )
890
+ const object = {
891
+ name: this.name,
892
+ bytecodeTemplate: this.bytecodeTemplate,
893
+ fieldsSig: this.fieldsSig,
894
+ functions: this.functions
895
+ }
896
+ return JSON.stringify(object, null, 2)
623
897
  }
624
898
 
625
899
  async paramsForDeployment(params: BuildExecuteScriptTx): Promise<SignExecuteScriptTxParams> {
@@ -627,7 +901,7 @@ export class Script extends Common {
627
901
  signerAddress: params.signerAddress,
628
902
  bytecode: this.buildByteCodeToDeploy(params.initialFields ? params.initialFields : {}),
629
903
  attoAlphAmount: extractOptionalNumber256(params.attoAlphAmount),
630
- tokens: typeof params.tokens !== 'undefined' ? params.tokens.map(toApiToken) : undefined,
904
+ tokens: toApiTokens(params.tokens),
631
905
  gasAmount: params.gasAmount,
632
906
  gasPrice: extractOptionalNumber256(params.gasPrice)
633
907
  }
@@ -650,162 +924,8 @@ export class Script extends Common {
650
924
  }
651
925
  }
652
926
 
653
- export type Number256 = number | bigint | string
654
- export type Val = Number256 | boolean | string | Val[]
655
- export type NamedVals = Record<string, Val>
656
- export type Fields = NamedVals
657
- export type Arguments = NamedVals
658
-
659
- function extractBoolean(v: Val): boolean {
660
- if (typeof v === 'boolean') {
661
- return v
662
- } else {
663
- throw new Error(`Invalid boolean value: ${v}`)
664
- }
665
- }
666
-
667
- // TODO: check integer bounds
668
- function extractNumber256(v: Val): string {
669
- if ((typeof v === 'number' && Number.isInteger(v)) || typeof v === 'bigint') {
670
- return v.toString()
671
- } else if (typeof v === 'string') {
672
- return v
673
- } else {
674
- throw new Error(`Invalid 256 bit number: ${v}`)
675
- }
676
- }
677
-
678
927
  function extractOptionalNumber256(v?: Val): string | undefined {
679
- return typeof v !== 'undefined' ? extractNumber256(v) : undefined
680
- }
681
-
682
- // TODO: check hex string
683
- function extractByteVec(v: Val): string {
684
- if (typeof v === 'string') {
685
- // try to convert from address to contract id
686
- try {
687
- const address = bs58.decode(v)
688
- if (address.length == 33 && address[0] == 3) {
689
- return Buffer.from(address.slice(1)).toString('hex')
690
- }
691
- } catch (_) {
692
- return v as string
693
- }
694
- return v as string
695
- } else {
696
- throw new Error(`Invalid string: ${v}`)
697
- }
698
- }
699
-
700
- function extractBs58(v: Val): string {
701
- if (typeof v === 'string') {
702
- try {
703
- bs58.decode(v)
704
- return v as string
705
- } catch (error) {
706
- throw new Error(`Invalid base58 string: ${v}`)
707
- }
708
- } else {
709
- throw new Error(`Invalid string: ${v}`)
710
- }
711
- }
712
-
713
- function decodeNumber256(n: string): Number256 {
714
- if (Number.isSafeInteger(Number.parseInt(n))) {
715
- return Number(n)
716
- } else {
717
- return BigInt(n)
718
- }
719
- }
720
-
721
- export function extractArray(tpe: string, v: Val): node.Val {
722
- if (!Array.isArray(v)) {
723
- throw new Error(`Expected array, got ${v}`)
724
- }
725
-
726
- const semiColonIndex = tpe.lastIndexOf(';')
727
- if (semiColonIndex == -1) {
728
- throw new Error(`Invalid Val type: ${tpe}`)
729
- }
730
-
731
- const subType = tpe.slice(1, semiColonIndex)
732
- const dim = parseInt(tpe.slice(semiColonIndex + 1, -1))
733
- if ((v as Val[]).length != dim) {
734
- throw new Error(`Invalid val dimension: ${v}`)
735
- } else {
736
- return { value: (v as Val[]).map((v) => toApiVal(v, subType)), type: 'Array' }
737
- }
738
- }
739
-
740
- export function toApiVal(v: Val, tpe: string): node.Val {
741
- if (tpe === 'Bool') {
742
- return { value: extractBoolean(v), type: tpe }
743
- } else if (tpe === 'U256' || tpe === 'I256') {
744
- return { value: extractNumber256(v), type: tpe }
745
- } else if (tpe === 'ByteVec') {
746
- return { value: extractByteVec(v), type: tpe }
747
- } else if (tpe === 'Address') {
748
- return { value: extractBs58(v), type: tpe }
749
- } else {
750
- return extractArray(tpe, v)
751
- }
752
- }
753
-
754
- function decodeArrayType(tpe: string): [baseType: string, dims: number[]] {
755
- const semiColonIndex = tpe.lastIndexOf(';')
756
- if (semiColonIndex === -1) {
757
- throw new Error(`Invalid Val type: ${tpe}`)
758
- }
759
-
760
- const subType = tpe.slice(1, semiColonIndex)
761
- const dim = parseInt(tpe.slice(semiColonIndex + 1, -1))
762
- if (subType[0] == '[') {
763
- const [baseType, subDim] = decodeArrayType(subType)
764
- return [baseType, (subDim.unshift(dim), subDim)]
765
- } else {
766
- return [subType, [dim]]
767
- }
768
- }
769
-
770
- function foldVals(vals: Val[], dims: number[]): Val {
771
- if (dims.length == 1) {
772
- return vals
773
- } else {
774
- const result: Val[] = []
775
- const chunkSize = vals.length / dims[0]
776
- const chunkDims = dims.slice(1)
777
- for (let i = 0; i < vals.length; i += chunkSize) {
778
- const chunk = vals.slice(i, i + chunkSize)
779
- result.push(foldVals(chunk, chunkDims))
780
- }
781
- return result
782
- }
783
- }
784
-
785
- function _fromApiVal(vals: node.Val[], valIndex: number, tpe: string): [result: Val, nextIndex: number] {
786
- if (vals.length === 0) {
787
- throw new Error('Not enough Vals')
788
- }
789
-
790
- const firstVal = vals[`${valIndex}`]
791
- if (tpe === 'Bool' && firstVal.type === tpe) {
792
- return [firstVal.value as boolean, valIndex + 1]
793
- } else if ((tpe === 'U256' || tpe === 'I256') && firstVal.type === tpe) {
794
- return [decodeNumber256(firstVal.value as string), valIndex + 1]
795
- } else if ((tpe === 'ByteVec' || tpe === 'Address') && firstVal.type === tpe) {
796
- return [firstVal.value as string, valIndex + 1]
797
- } else {
798
- const [baseType, dims] = decodeArrayType(tpe)
799
- const arraySize = dims.reduce((a, b) => a * b)
800
- const nextIndex = valIndex + arraySize
801
- const valsToUse = vals.slice(valIndex, nextIndex)
802
- if (valsToUse.length == arraySize && valsToUse.every((val) => val.type === baseType)) {
803
- const localVals = valsToUse.map((val) => fromApiVal(val, baseType))
804
- return [foldVals(localVals, dims), nextIndex]
805
- } else {
806
- throw new Error(`Invalid array Val type: ${valsToUse}, ${tpe}`)
807
- }
808
- }
928
+ return typeof v !== 'undefined' ? toApiNumber256(v) : undefined
809
929
  }
810
930
 
811
931
  function fromApiFields(vals: node.Val[], fieldsSig: node.FieldsSig): Fields {
@@ -816,70 +936,22 @@ function fromApiEventFields(vals: node.Val[], eventSig: node.EventSig): Fields {
816
936
  return fromApiVals(vals, eventSig.fieldNames, eventSig.fieldTypes)
817
937
  }
818
938
 
819
- function fromApiVals(vals: node.Val[], names: string[], types: string[]): Fields {
820
- let valIndex = 0
821
- const result: Fields = {}
822
- types.forEach((currentType, index) => {
823
- const currentName = names[`${index}`]
824
- const [val, nextIndex] = _fromApiVal(vals, valIndex, currentType)
825
- valIndex = nextIndex
826
- result[`${currentName}`] = val
827
- })
828
- return result
829
- }
830
-
831
- function fromApiArray(vals: node.Val[], types: string[]): Val[] {
832
- let valIndex = 0
833
- const result: Val[] = []
834
- for (const currentType of types) {
835
- const [val, nextIndex] = _fromApiVal(vals, valIndex, currentType)
836
- result.push(val)
837
- valIndex = nextIndex
838
- }
839
- return result
840
- }
841
-
842
- function fromApiVal(v: node.Val, tpe: string): Val {
843
- if (v.type === 'Bool' && v.type === tpe) {
844
- return v.value as boolean
845
- } else if ((v.type === 'U256' || v.type === 'I256') && v.type === tpe) {
846
- return decodeNumber256(v.value as string)
847
- } else if ((v.type === 'ByteVec' || v.type === 'Address') && v.type === tpe) {
848
- return v.value as string
849
- } else {
850
- throw new Error(`Invalid node.Val type: ${v}`)
851
- }
852
- }
853
-
854
939
  export interface Asset {
855
940
  alphAmount: Number256
856
941
  tokens?: Token[]
857
942
  }
858
943
 
859
- export interface Token {
860
- id: string
861
- amount: Number256
862
- }
863
-
864
- function toApiToken(token: Token): node.Token {
865
- return { id: token.id, amount: extractNumber256(token.amount) }
866
- }
867
-
868
- function fromApiToken(token: node.Token): Token {
869
- return { id: token.id, amount: decodeNumber256(token.amount) }
870
- }
871
-
872
944
  function toApiAsset(asset: Asset): node.AssetState {
873
945
  return {
874
- attoAlphAmount: extractNumber256(asset.alphAmount),
946
+ attoAlphAmount: toApiNumber256(asset.alphAmount),
875
947
  tokens: typeof asset.tokens !== 'undefined' ? asset.tokens.map(toApiToken) : []
876
948
  }
877
949
  }
878
950
 
879
951
  function fromApiAsset(asset: node.AssetState): Asset {
880
952
  return {
881
- alphAmount: decodeNumber256(asset.attoAlphAmount),
882
- tokens: typeof asset.tokens !== 'undefined' ? asset.tokens.map(fromApiToken) : undefined
953
+ alphAmount: fromApiNumber256(asset.attoAlphAmount),
954
+ tokens: fromApiTokens(asset.tokens)
883
955
  }
884
956
  }
885
957
 
@@ -895,7 +967,7 @@ export interface ContractState {
895
967
  initialStateHash?: string
896
968
  codeHash: string
897
969
  fields: Fields
898
- fieldsSig: node.FieldsSig
970
+ fieldsSig: FieldsSig
899
971
  asset: Asset
900
972
  }
901
973
 
@@ -918,12 +990,12 @@ function toApiContractState(state: ContractState): node.ContractState {
918
990
  }
919
991
  }
920
992
 
921
- function toApiFields(fields: Fields, fieldsSig: node.FieldsSig): node.Val[] {
993
+ function toApiFields(fields: Fields, fieldsSig: FieldsSig): node.Val[] {
922
994
  return toApiVals(fields, fieldsSig.names, fieldsSig.types)
923
995
  }
924
996
 
925
- function toApiArgs(args: Arguments, funcSig: node.FunctionSig): node.Val[] {
926
- return toApiVals(args, funcSig.argNames, funcSig.argTypes)
997
+ function toApiArgs(args: Arguments, funcSig: FunctionSig): node.Val[] {
998
+ return toApiVals(args, funcSig.paramNames, funcSig.paramTypes)
927
999
  }
928
1000
 
929
1001
  function toApiVals(fields: Fields, names: string[], types: string[]): node.Val[] {
@@ -967,14 +1039,17 @@ export interface ContractEventByTxId {
967
1039
  fields: Fields
968
1040
  }
969
1041
 
1042
+ export type DebugMessage = node.DebugMessage
1043
+
970
1044
  export interface TestContractResult {
971
- address: string
972
1045
  contractId: string
1046
+ contractAddress: string
973
1047
  returns: Val[]
974
1048
  gasUsed: number
975
1049
  contracts: ContractState[]
976
1050
  txOutputs: Output[]
977
1051
  events: ContractEventByTxId[]
1052
+ debugMessages: DebugMessage[]
978
1053
  }
979
1054
  export declare type Output = AssetOutput | ContractOutput
980
1055
  export interface AssetOutput extends Asset {
@@ -987,7 +1062,7 @@ export interface ContractOutput {
987
1062
  type: string
988
1063
  address: string
989
1064
  alphAmount: Number256
990
- tokens: Token[]
1065
+ tokens?: Token[]
991
1066
  }
992
1067
 
993
1068
  function fromApiOutput(output: node.Output): Output {
@@ -996,8 +1071,8 @@ function fromApiOutput(output: node.Output): Output {
996
1071
  return {
997
1072
  type: 'AssetOutput',
998
1073
  address: asset.address,
999
- alphAmount: decodeNumber256(asset.attoAlphAmount),
1000
- tokens: asset.tokens.map(fromApiToken),
1074
+ alphAmount: fromApiNumber256(asset.attoAlphAmount),
1075
+ tokens: fromApiTokens(asset.tokens),
1001
1076
  lockTime: asset.lockTime,
1002
1077
  message: asset.message
1003
1078
  }
@@ -1006,8 +1081,8 @@ function fromApiOutput(output: node.Output): Output {
1006
1081
  return {
1007
1082
  type: 'ContractOutput',
1008
1083
  address: asset.address,
1009
- alphAmount: decodeNumber256(asset.attoAlphAmount),
1010
- tokens: asset.tokens.map(fromApiToken)
1084
+ alphAmount: fromApiNumber256(asset.attoAlphAmount),
1085
+ tokens: fromApiTokens(asset.tokens)
1011
1086
  }
1012
1087
  } else {
1013
1088
  throw new Error(`Unknown output type: ${output}`)