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

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 (125) 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 +117 -17
  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 +107 -68
  17. package/dist/src/contract/contract.js +395 -451
  18. package/dist/src/contract/events.d.ts +4 -4
  19. package/dist/src/contract/events.js +2 -1
  20. package/dist/src/contract/index.js +5 -1
  21. package/dist/src/contract/ralph.d.ts +5 -4
  22. package/dist/src/contract/ralph.js +27 -1
  23. package/dist/src/global.d.ts +4 -0
  24. package/dist/{scripts/stop-devnet.js → src/global.js} +17 -11
  25. package/dist/src/index.d.ts +2 -0
  26. package/dist/src/index.js +23 -1
  27. package/dist/src/signer/index.d.ts +0 -1
  28. package/dist/src/signer/index.js +5 -2
  29. package/dist/src/signer/signer.d.ts +25 -13
  30. package/dist/src/signer/signer.js +51 -16
  31. package/dist/src/transaction/index.d.ts +0 -1
  32. package/dist/src/transaction/index.js +5 -2
  33. package/dist/src/transaction/status.d.ts +2 -1
  34. package/dist/src/transaction/status.js +2 -1
  35. package/dist/src/utils/bs58.d.ts +1 -0
  36. package/dist/src/utils/bs58.js +13 -1
  37. package/dist/src/utils/index.d.ts +0 -1
  38. package/dist/src/utils/index.js +5 -2
  39. package/dist/src/utils/subscription.d.ts +0 -3
  40. package/dist/src/utils/subscription.js +0 -1
  41. package/dist/src/utils/utils.d.ts +4 -9
  42. package/dist/src/utils/utils.js +20 -24
  43. package/jest-config.json +11 -0
  44. package/package.json +11 -47
  45. package/src/api/api-alephium.ts +169 -25
  46. package/src/api/api-explorer.ts +234 -51
  47. package/src/api/index.ts +14 -3
  48. package/src/api/types.ts +233 -0
  49. package/{scripts/rename-gitignore.js → src/api/utils.ts} +7 -6
  50. package/src/contract/contract.ts +579 -545
  51. package/src/contract/events.ts +6 -5
  52. package/src/contract/ralph.ts +29 -4
  53. package/src/{transaction/sign-verify.ts → global.ts} +14 -15
  54. package/src/index.ts +7 -0
  55. package/src/signer/index.ts +0 -1
  56. package/src/signer/signer.ts +79 -27
  57. package/src/transaction/index.ts +0 -1
  58. package/src/transaction/status.ts +5 -2
  59. package/src/utils/bs58.ts +11 -0
  60. package/src/utils/index.ts +0 -1
  61. package/src/utils/subscription.ts +1 -3
  62. package/src/utils/utils.ts +11 -19
  63. package/.eslintrc.json +0 -21
  64. package/LICENSE +0 -165
  65. package/contracts/add/add.ral +0 -16
  66. package/contracts/greeter/greeter.ral +0 -7
  67. package/contracts/greeter/greeter_interface.ral +0 -3
  68. package/contracts/greeter_main.ral +0 -9
  69. package/contracts/main.ral +0 -6
  70. package/contracts/sub/sub.ral +0 -9
  71. package/contracts/test/metadata.ral +0 -17
  72. package/contracts/test/warnings.ral +0 -5
  73. package/dev/user.conf +0 -29
  74. package/dist/scripts/create-project.d.ts +0 -2
  75. package/dist/scripts/create-project.js +0 -125
  76. package/dist/scripts/rename-gitignore.d.ts +0 -1
  77. package/dist/scripts/start-devnet.d.ts +0 -1
  78. package/dist/scripts/start-devnet.js +0 -131
  79. package/dist/scripts/stop-devnet.d.ts +0 -1
  80. package/dist/src/signer/node-wallet.d.ts +0 -13
  81. package/dist/src/signer/node-wallet.js +0 -60
  82. package/dist/src/test/index.d.ts +0 -7
  83. package/dist/src/test/index.js +0 -41
  84. package/dist/src/test/privatekey-wallet.d.ts +0 -12
  85. package/dist/src/test/privatekey-wallet.js +0 -68
  86. package/dist/src/transaction/sign-verify.d.ts +0 -2
  87. package/dist/src/transaction/sign-verify.js +0 -58
  88. package/dist/src/utils/password-crypto.d.ts +0 -2
  89. package/dist/src/utils/password-crypto.js +0 -69
  90. package/gitignore +0 -10
  91. package/scripts/create-project.ts +0 -137
  92. package/scripts/start-devnet.js +0 -141
  93. package/scripts/stop-devnet.js +0 -32
  94. package/src/contract/ralph.test.ts +0 -178
  95. package/src/fixtures/address.json +0 -36
  96. package/src/fixtures/balance.json +0 -9
  97. package/src/fixtures/self-clique.json +0 -19
  98. package/src/fixtures/transaction.json +0 -13
  99. package/src/fixtures/transactions.json +0 -179
  100. package/src/signer/fixtures/genesis.json +0 -26
  101. package/src/signer/fixtures/wallets.json +0 -26
  102. package/src/signer/node-wallet.ts +0 -74
  103. package/src/test/index.ts +0 -32
  104. package/src/test/privatekey-wallet.ts +0 -58
  105. package/src/transaction/sign-verify.test.ts +0 -50
  106. package/src/utils/address.test.ts +0 -47
  107. package/src/utils/djb2.test.ts +0 -35
  108. package/src/utils/password-crypto.test.ts +0 -27
  109. package/src/utils/password-crypto.ts +0 -77
  110. package/src/utils/utils.test.ts +0 -161
  111. package/templates/base/README.md +0 -34
  112. package/templates/base/package.json +0 -35
  113. package/templates/base/src/greeter.ts +0 -41
  114. package/templates/base/tsconfig.json +0 -19
  115. package/templates/react/README.md +0 -34
  116. package/templates/react/config-overrides.js +0 -18
  117. package/templates/react/package.json +0 -66
  118. package/templates/react/src/App.tsx +0 -42
  119. package/templates/react/src/artifacts/greeter.ral.json +0 -26
  120. package/templates/react/src/artifacts/greeter_main.ral.json +0 -22
  121. package/templates/shared/.eslintrc.json +0 -12
  122. package/templates/shared/scripts/header.js +0 -0
  123. package/test/contract.test.ts +0 -197
  124. package/test/events.test.ts +0 -138
  125. package/test/transaction.test.ts +0 -72
@@ -17,172 +17,499 @@ 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
- type FieldsSig = node.FieldsSig
50
- type EventSig = node.EventSig
51
- type FunctionSig = node.FunctionSig
111
+ class Compiled<T extends Artifact> {
112
+ sourceFile: SourceFile
113
+ artifact: T
114
+ warnings: string[]
52
115
 
53
- export abstract class Common {
54
- readonly sourceCodeSha256: string
55
- readonly functions: FunctionSig[]
116
+ constructor(sourceFile: SourceFile, artifact: T, warnings: string[]) {
117
+ this.sourceFile = sourceFile
118
+ this.artifact = artifact
119
+ this.warnings = warnings
120
+ }
121
+ }
56
122
 
57
- static readonly importRegex = new RegExp('^import "([^"/]+/(([^"]+)/)?)?[a-z][a-z_0-9]*.ral"', 'mg')
58
- static readonly contractRegex = new RegExp('^(Abstract[ ]+)?Contract [A-Z][a-zA-Z0-9]*', 'mg')
59
- static readonly interfaceRegex = new RegExp('^Interface [A-Z][a-zA-Z0-9]* \\{', 'mg')
60
- static readonly scriptRegex = new RegExp('^TxScript [A-Z][a-zA-Z0-9]*', 'mg')
61
-
62
- private static _artifactCache: Map<string, Contract | Script> = new Map<string, Contract | Script>()
63
- static artifactCacheCapacity = 20
64
- protected static _getArtifactFromCache(codeHash: string): Contract | Script | undefined {
65
- return this._artifactCache.get(codeHash)
66
- }
67
- protected static _putArtifactToCache(contract: Contract): void {
68
- if (!this._artifactCache.has(contract.codeHash)) {
69
- if (this._artifactCache.size >= this.artifactCacheCapacity) {
70
- const keyToDelete = this._artifactCache.keys().next().value
71
- this._artifactCache.delete(keyToDelete)
72
- }
73
- this._artifactCache.set(contract.codeHash, contract)
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}`)
74
139
  }
75
140
  }
76
141
 
77
- constructor(sourceCodeSha256: string, functions: FunctionSig[]) {
78
- this.sourceCodeSha256 = sourceCodeSha256
79
- this.functions = functions
142
+ constructor(compilerOptionsUsed: node.CompilerOptions, infos: Map<string, CodeInfo>) {
143
+ ProjectArtifact.checkCompilerOptionsParameter(compilerOptionsUsed)
144
+ this.compilerOptionsUsed = compilerOptionsUsed
145
+ this.infos = infos
80
146
  }
81
147
 
82
- protected static _artifactsFolder(): string {
83
- 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)
84
153
  }
85
154
 
86
- static getSourceFile(path: string, _dirs: string[]): SourceFile {
87
- const parts = path.split('/')
88
- const dirs = Array.from(_dirs)
89
- if (parts.length === 1) {
90
- return new SourceFile(dirs, path)
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
91
164
  }
92
- parts.slice(0, parts.length - 1).forEach((part) => {
93
- switch (part) {
94
- case '.': {
95
- break
96
- }
97
- case '..': {
98
- if (dirs.length === 0) {
99
- throw new Error('Invalid file path: ' + path)
100
- }
101
- dirs.pop()
102
- break
103
- }
104
- default: {
105
- dirs.push(part)
106
- }
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
107
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
+ })
108
239
  })
109
- return new SourceFile(dirs, parts[parts.length - 1])
110
- }
111
-
112
- protected static async _handleImports(
113
- pathes: string[],
114
- contractStr: string,
115
- importsCache: string[]
116
- ): Promise<string> {
117
- const localImportsCache: string[] = []
118
- let result = contractStr.replace(Common.importRegex, (match) => {
119
- localImportsCache.push(match)
120
- return ''
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
+ })
121
248
  })
122
- for (const myImport of localImportsCache) {
123
- const relativePath = myImport.slice(8, -1)
124
- const importSourceFile = this.getSourceFile(relativePath, pathes)
125
- if (!importsCache.includes(importSourceFile.contractPath)) {
126
- importsCache.push(importSourceFile.contractPath)
127
- const importContractStr = await Common._loadContractStr(importSourceFile, importsCache, (code) =>
128
- Contract.checkCodeType(importSourceFile.contractPath, code)
129
- )
130
- result = result.concat('\n', importContractStr)
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)
131
293
  }
132
294
  }
133
- return result
134
295
  }
135
296
 
136
- protected static async _loadContractStr(
137
- sourceFile: SourceFile,
138
- importsCache: string[],
139
- validate: (code: string) => void
140
- ): Promise<string> {
141
- const contractPath = sourceFile.contractPath
142
- const contractBuffer = await fsPromises.readFile(contractPath)
143
- const contractStr = contractBuffer.toString()
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
+ }
144
304
 
145
- validate(contractStr)
146
- return Common._handleImports(sourceFile.dirs, contractStr, importsCache)
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
147
311
  }
148
312
 
149
- static checkFileNameExtension(fileName: string): void {
150
- if (!fileName.endsWith('.ral')) {
151
- throw new Error('Smart contract file name should end with ".ral"')
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)
152
328
  }
329
+ await this.projectArtifact.saveToFile(this.artifactsRootDir)
153
330
  }
154
331
 
155
- protected static async _from<T extends { sourceCodeSha256: string }>(
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}`)
338
+ }
339
+ return contract.artifact
340
+ }
341
+
342
+ private static async compile(
156
343
  provider: NodeProvider,
157
- sourceFile: SourceFile,
158
- loadContractStr: (sourceFile: SourceFile, importsCache: string[]) => Promise<string>,
159
- compile: (
160
- provider: NodeProvider,
161
- sourceFile: SourceFile,
162
- contractStr: string,
163
- contractHash: string,
164
- errorOnWarnings: boolean
165
- ) => Promise<T>,
166
- errorOnWarnings: boolean
167
- ): Promise<T> {
168
- Common.checkFileNameExtension(sourceFile.contractPath)
169
-
170
- const contractStr = await loadContractStr(sourceFile, [])
171
- const contractHash = cryptojs.SHA256(contractStr).toString()
172
- const existingContract = this._getArtifactFromCache(contractHash)
173
- if (typeof existingContract !== 'undefined') {
174
- return existingContract as unknown as T
175
- } else {
176
- return compile(provider, sourceFile, contractStr, contractHash, errorOnWarnings)
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
379
+ }
380
+
381
+ private static async loadArtifacts(
382
+ provider: NodeProvider,
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)
177
421
  }
178
422
  }
179
423
 
180
- protected _saveToFile(sourceFile: SourceFile): Promise<void> {
181
- const folder = Common._artifactsFolder() + sourceFile.dirPath
182
- if (!fs.existsSync(folder)) {
183
- fs.mkdirSync(folder, { recursive: true })
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
+ )
184
502
  }
185
- 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
186
513
  }
187
514
 
188
515
  abstract buildByteCodeToDeploy(initialFields?: Fields): string
@@ -198,109 +525,43 @@ export abstract class Common {
198
525
  usingAssetsInContractFunctions(): string[] {
199
526
  return this.functions.filter((func) => func.useAssetsInContract).map((func) => func.name)
200
527
  }
201
-
202
- protected static checkCompilerWarnings(compiled: { warnings: string[] }, errorOnWarnings: boolean): void {
203
- if (compiled.warnings.length !== 0) {
204
- const prefixPerWarning = ' - '
205
- const warningString = prefixPerWarning + compiled.warnings.join('\n' + prefixPerWarning)
206
- const output = 'Compilation warnings:\n' + warningString + '\n'
207
- if (errorOnWarnings) {
208
- throw new Error(output)
209
- } else {
210
- console.log(output)
211
- }
212
- }
213
- }
214
528
  }
215
529
 
216
- export class Contract extends Common {
530
+ export class Contract extends Artifact {
217
531
  readonly bytecode: string
532
+ readonly bytecodeDebugPatch: string
218
533
  readonly codeHash: string
219
534
  readonly fieldsSig: FieldsSig
220
535
  readonly eventsSig: EventSig[]
221
536
 
537
+ readonly bytecodeDebug: string
538
+ readonly codeHashDebug: string
539
+
222
540
  constructor(
223
- sourceCodeSha256: string,
541
+ name: string,
224
542
  bytecode: string,
543
+ bytecodeDebugPatch: string,
225
544
  codeHash: string,
545
+ codeHashDebug: string,
226
546
  fieldsSig: FieldsSig,
227
547
  eventsSig: EventSig[],
228
548
  functions: FunctionSig[]
229
549
  ) {
230
- super(sourceCodeSha256, functions)
550
+ super(name, functions)
231
551
  this.bytecode = bytecode
552
+ this.bytecodeDebugPatch = bytecodeDebugPatch
232
553
  this.codeHash = codeHash
233
554
  this.fieldsSig = fieldsSig
234
555
  this.eventsSig = eventsSig
235
- }
236
556
 
237
- static checkCodeType(fileName: string, contractStr: string): void {
238
- const interfaceMatches = contractStr.match(Contract.interfaceRegex)
239
- const contractMatches = contractStr.match(Contract.contractRegex)
240
- if (interfaceMatches === null && contractMatches === null) {
241
- throw new Error(`No contract found in: ${fileName}`)
242
- }
243
- if (interfaceMatches && contractMatches) {
244
- throw new Error(`Multiple contracts and interfaces in: ${fileName}`)
245
- }
246
- if (interfaceMatches === null) {
247
- if (contractMatches !== null && contractMatches.length > 1) {
248
- throw new Error(`Multiple contracts in: ${fileName}`)
249
- }
250
- }
251
- if (contractMatches === null) {
252
- if (interfaceMatches !== null && interfaceMatches.length > 1) {
253
- throw new Error(`Multiple interfaces in: ${fileName}`)
254
- }
255
- }
256
- }
257
-
258
- private static async loadContractStr(sourceFile: SourceFile): Promise<string> {
259
- return Common._loadContractStr(sourceFile, [], (code) => Contract.checkCodeType(sourceFile.contractPath, code))
260
- }
261
-
262
- static async fromSource(provider: NodeProvider, path: string, errorOnWarnings = true): Promise<Contract> {
263
- if (!fs.existsSync(Common._artifactsFolder())) {
264
- fs.mkdirSync(Common._artifactsFolder(), { recursive: true })
265
- }
266
- const sourceFile = this.getSourceFile(path, [])
267
- const contract = await Common._from(
268
- provider,
269
- sourceFile,
270
- (sourceFile) => Contract.loadContractStr(sourceFile),
271
- Contract.compile,
272
- errorOnWarnings
273
- )
274
- this._putArtifactToCache(contract)
275
- return contract
276
- }
277
-
278
- private static async compile(
279
- provider: NodeProvider,
280
- sourceFile: SourceFile,
281
- contractStr: string,
282
- contractHash: string,
283
- errorOnWarnings: boolean
284
- ): Promise<Contract> {
285
- const compiled = await provider.contracts.postContractsCompileContract({ code: contractStr })
286
- Common.checkCompilerWarnings(compiled, errorOnWarnings)
287
-
288
- const artifact = new Contract(
289
- contractHash,
290
- compiled.bytecode,
291
- compiled.codeHash,
292
- compiled.fields,
293
- compiled.events,
294
- compiled.functions
295
- )
296
- await artifact._saveToFile(sourceFile)
297
- return artifact
557
+ this.bytecodeDebug = ralph.buildDebugBytecode(this.bytecode, this.bytecodeDebugPatch)
558
+ this.codeHashDebug = codeHashDebug
298
559
  }
299
560
 
300
561
  // TODO: safely parse json
301
- static fromJson(artifact: any): Contract {
562
+ static fromJson(artifact: any, bytecodeDebugPatch = '', codeHashDebug = ''): Contract {
302
563
  if (
303
- artifact.sourceCodeSha256 == null ||
564
+ artifact.name == null ||
304
565
  artifact.bytecode == null ||
305
566
  artifact.codeHash == null ||
306
567
  artifact.fieldsSig == null ||
@@ -310,44 +571,55 @@ export class Contract extends Common {
310
571
  throw Error('The artifact JSON for contract is incomplete')
311
572
  }
312
573
  const contract = new Contract(
313
- artifact.sourceCodeSha256,
574
+ artifact.name,
314
575
  artifact.bytecode,
576
+ bytecodeDebugPatch,
315
577
  artifact.codeHash,
578
+ codeHashDebug ? codeHashDebug : artifact.codeHash,
316
579
  artifact.fieldsSig,
317
580
  artifact.eventsSig,
318
581
  artifact.functions
319
582
  )
320
- this._putArtifactToCache(contract)
321
583
  return contract
322
584
  }
323
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
+
324
599
  // support both 'code.ral' and 'code.ral.json'
325
- static async fromArtifactFile(path: string): Promise<Contract> {
326
- const sourceFile = this.getSourceFile(path, [])
327
- const artifactPath = sourceFile.artifactPath
328
- const content = await fsPromises.readFile(artifactPath)
600
+ static async fromArtifactFile(path: string, bytecodeDebugPatch: string, codeHashDebug: string): Promise<Contract> {
601
+ const content = await fsPromises.readFile(path)
329
602
  const artifact = JSON.parse(content.toString())
330
- return Contract.fromJson(artifact)
603
+ return Contract.fromJson(artifact, bytecodeDebugPatch, codeHashDebug)
331
604
  }
332
605
 
333
- async fetchState(provider: NodeProvider, address: string, group: number): Promise<ContractState> {
334
- 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
+ })
335
610
  return this.fromApiContractState(state)
336
611
  }
337
612
 
338
613
  override toString(): string {
339
- return JSON.stringify(
340
- {
341
- sourceCodeSha256: this.sourceCodeSha256,
342
- bytecode: this.bytecode,
343
- codeHash: this.codeHash,
344
- fieldsSig: this.fieldsSig,
345
- eventsSig: this.eventsSig,
346
- functions: this.functions
347
- },
348
- null,
349
- 2
350
- )
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)
351
623
  }
352
624
 
353
625
  toState(fields: Fields, asset: Asset, address?: string): ContractState {
@@ -363,25 +635,37 @@ export class Contract extends Common {
363
635
  }
364
636
  }
365
637
 
638
+ // no need to be cryptographically strong random
366
639
  static randomAddress(): string {
367
- const bytes = crypto.randomBytes(33)
640
+ const bytes = new Uint8Array(33)
641
+ crypto.getRandomValues(bytes)
368
642
  bytes[0] = 3
369
643
  return bs58.encode(bytes)
370
644
  }
371
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
+
372
653
  private async _test(
373
- provider: NodeProvider,
374
654
  funcName: string,
375
655
  params: TestContractParams,
376
656
  expectPublic: boolean,
377
- accessType: string
657
+ accessType: string,
658
+ printDebugMessages: boolean
378
659
  ): Promise<TestContractResult> {
379
660
  const apiParams: node.TestContract = this.toTestContract(funcName, params)
380
- const apiResult = await provider.contracts.postContractsTestContract(apiParams)
661
+ const apiResult = await web3.getCurrentNodeProvider().contracts.postContractsTestContract(apiParams)
381
662
 
382
663
  const methodIndex =
383
664
  typeof params.testMethodIndex !== 'undefined' ? params.testMethodIndex : this.getMethodIndex(funcName)
384
665
  const isPublic = this.functions[`${methodIndex}`].isPublic
666
+ if (printDebugMessages) {
667
+ this._printDebugMessages(funcName, apiResult.debugMessages)
668
+ }
385
669
  if (isPublic === expectPublic) {
386
670
  const result = await this.fromTestContractResult(methodIndex, apiResult)
387
671
  return result
@@ -391,19 +675,19 @@ export class Contract extends Common {
391
675
  }
392
676
 
393
677
  async testPublicMethod(
394
- provider: NodeProvider,
395
678
  funcName: string,
396
- params: TestContractParams
679
+ params: TestContractParams,
680
+ printDebugMessages = true
397
681
  ): Promise<TestContractResult> {
398
- return this._test(provider, funcName, params, true, 'public')
682
+ return this._test(funcName, params, true, 'public', printDebugMessages)
399
683
  }
400
684
 
401
685
  async testPrivateMethod(
402
- provider: NodeProvider,
403
686
  funcName: string,
404
- params: TestContractParams
687
+ params: TestContractParams,
688
+ printDebugMessages = true
405
689
  ): Promise<TestContractResult> {
406
- return this._test(provider, funcName, params, false, 'private')
690
+ return this._test(funcName, params, false, 'private', printDebugMessages)
407
691
  }
408
692
 
409
693
  toApiFields(fields?: Fields): node.Val[] {
@@ -439,7 +723,7 @@ export class Contract extends Common {
439
723
  return {
440
724
  group: params.group,
441
725
  address: params.address,
442
- bytecode: this.bytecode,
726
+ bytecode: this.bytecodeDebug,
443
727
  initialFields: this.toApiFields(params.initialFields),
444
728
  initialAsset: typeof params.initialAsset !== 'undefined' ? toApiAsset(params.initialAsset) : undefined,
445
729
  methodIndex: this.getMethodIndex(funcName),
@@ -449,33 +733,8 @@ export class Contract extends Common {
449
733
  }
450
734
  }
451
735
 
452
- static async fromCodeHash(codeHash: string): Promise<Contract> {
453
- const cached = this._getArtifactFromCache(codeHash)
454
- if (typeof cached !== 'undefined') {
455
- return cached as Contract
456
- }
457
-
458
- const files = await fsPromises.readdir(Common._artifactsFolder())
459
- for (const file of files) {
460
- if (file.endsWith('.ral.json')) {
461
- try {
462
- const contract = await Contract.fromArtifactFile(file)
463
- if (contract.codeHash === codeHash) {
464
- return contract as Contract
465
- }
466
- } catch (_) {}
467
- }
468
- }
469
-
470
- throw new Error(`Unknown code with code hash: ${codeHash}`)
471
- }
472
-
473
- static async getFieldsSig(state: node.ContractState): Promise<FieldsSig> {
474
- return Contract.fromCodeHash(state.codeHash).then((contract) => contract.fieldsSig)
475
- }
476
-
477
736
  async fromApiContractState(state: node.ContractState): Promise<ContractState> {
478
- const contract = await Contract.fromCodeHash(state.codeHash)
737
+ const contract = Project.currentProject.contractByCodeHash(state.codeHash)
479
738
  return {
480
739
  address: state.address,
481
740
  contractId: binToHex(contractIdFromAddress(state.address)),
@@ -483,7 +742,7 @@ export class Contract extends Common {
483
742
  initialStateHash: state.initialStateHash,
484
743
  codeHash: state.codeHash,
485
744
  fields: fromApiFields(state.fields, contract.fieldsSig),
486
- fieldsSig: await Contract.getFieldsSig(state),
745
+ fieldsSig: contract.fieldsSig,
487
746
  asset: fromApiAsset(state.asset)
488
747
  }
489
748
  }
@@ -511,7 +770,7 @@ export class Contract extends Common {
511
770
  } else if (event.eventIndex == -2) {
512
771
  eventSig = this.ContractDestroyedEvent
513
772
  } else {
514
- const contract = await Contract.fromCodeHash(codeHash!)
773
+ const contract = Project.currentProject.contractByCodeHash(codeHash!)
515
774
  eventSig = contract.eventsSig[event.eventIndex]
516
775
  }
517
776
 
@@ -528,8 +787,8 @@ export class Contract extends Common {
528
787
  addressToCodeHash.set(result.address, result.codeHash)
529
788
  result.contracts.forEach((contract) => addressToCodeHash.set(contract.address, contract.codeHash))
530
789
  return {
531
- address: result.address,
532
790
  contractId: binToHex(contractIdFromAddress(result.address)),
791
+ contractAddress: result.address,
533
792
  returns: fromApiArray(result.returns, this.functions[`${methodIndex}`].returnTypes),
534
793
  gasUsed: result.gasUsed,
535
794
  contracts: await Promise.all(result.contracts.map((contract) => this.fromApiContractState(contract))),
@@ -544,7 +803,8 @@ export class Contract extends Common {
544
803
  throw Error(`Cannot find codeHash for the contract address: ${contractAddress}`)
545
804
  }
546
805
  })
547
- )
806
+ ),
807
+ debugMessages: result.debugMessages
548
808
  }
549
809
  }
550
810
 
@@ -555,7 +815,7 @@ export class Contract extends Common {
555
815
  bytecode: bytecode,
556
816
  initialAttoAlphAmount: extractOptionalNumber256(params.initialAttoAlphAmount),
557
817
  issueTokenAmount: extractOptionalNumber256(params.issueTokenAmount),
558
- initialTokenAmounts: params.initialTokenAmounts?.map(toApiToken),
818
+ initialTokenAmounts: toApiTokens(params.initialTokenAmounts),
559
819
  gasAmount: params.gasAmount,
560
820
  gasPrice: extractOptionalNumber256(params.gasPrice)
561
821
  }
@@ -568,7 +828,7 @@ export class Contract extends Common {
568
828
  ): Promise<DeployContractTransaction> {
569
829
  const signerParams = await this.paramsForDeployment({
570
830
  ...params,
571
- signerAddress: (await signer.getAccounts())[0].address
831
+ signerAddress: (await signer.getActiveAccount()).address
572
832
  })
573
833
  const response = await signer.buildContractCreationTx(signerParams)
574
834
  return fromApiDeployContractUnsignedTx(response)
@@ -579,88 +839,61 @@ export class Contract extends Common {
579
839
  }
580
840
  }
581
841
 
582
- export class Script extends Common {
842
+ export class Script extends Artifact {
583
843
  readonly bytecodeTemplate: string
844
+ readonly bytecodeDebugPatch: string
584
845
  readonly fieldsSig: FieldsSig
585
846
 
586
- constructor(sourceCodeSha256: string, bytecodeTemplate: string, fieldsSig: FieldsSig, functions: FunctionSig[]) {
587
- super(sourceCodeSha256, functions)
847
+ constructor(
848
+ name: string,
849
+ bytecodeTemplate: string,
850
+ bytecodeDebugPatch: string,
851
+ fieldsSig: FieldsSig,
852
+ functions: FunctionSig[]
853
+ ) {
854
+ super(name, functions)
588
855
  this.bytecodeTemplate = bytecodeTemplate
856
+ this.bytecodeDebugPatch = bytecodeDebugPatch
589
857
  this.fieldsSig = fieldsSig
590
858
  }
591
859
 
592
- static checkCodeType(fileName: string, contractStr: string): void {
593
- const scriptMatches = contractStr.match(this.scriptRegex)
594
- if (scriptMatches === null) {
595
- throw new Error(`No script found in: ${fileName}`)
596
- } else if (scriptMatches.length > 1) {
597
- throw new Error(`Multiple scripts in: ${fileName}`)
598
- } else {
599
- return
600
- }
601
- }
602
-
603
- private static async loadContractStr(sourceFile: SourceFile): Promise<string> {
604
- return Common._loadContractStr(sourceFile, [], (code) => Script.checkCodeType(sourceFile.contractPath, code))
605
- }
606
-
607
- static async fromSource(provider: NodeProvider, path: string, errorOnWarnings = true): Promise<Script> {
608
- const sourceFile = this.getSourceFile(path, [])
609
- return Common._from(
610
- provider,
611
- sourceFile,
612
- (sourceFile) => Script.loadContractStr(sourceFile),
613
- Script.compile,
614
- errorOnWarnings
615
- )
616
- }
617
-
618
- private static async compile(
619
- provider: NodeProvider,
620
- sourceFile: SourceFile,
621
- scriptStr: string,
622
- contractHash: string,
623
- errorOnWarnings = true
624
- ): Promise<Script> {
625
- const compiled = await provider.contracts.postContractsCompileScript({ code: scriptStr })
626
- Common.checkCompilerWarnings(compiled, errorOnWarnings)
627
- const artifact = new Script(contractHash, compiled.bytecodeTemplate, compiled.fields, compiled.functions)
628
- await artifact._saveToFile(sourceFile)
629
- return artifact
860
+ static fromCompileResult(result: node.CompileScriptResult): Script {
861
+ return new Script(result.name, result.bytecodeTemplate, result.bytecodeDebugPatch, result.fields, result.functions)
630
862
  }
631
863
 
632
864
  // TODO: safely parse json
633
- static fromJson(artifact: any): Script {
865
+ static fromJson(artifact: any, bytecodeDebugPatch = ''): Script {
634
866
  if (
635
- artifact.sourceCodeSha256 == null ||
867
+ artifact.name == null ||
636
868
  artifact.bytecodeTemplate == null ||
637
869
  artifact.fieldsSig == null ||
638
870
  artifact.functions == null
639
871
  ) {
640
872
  throw Error('The artifact JSON for script is incomplete')
641
873
  }
642
- 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
+ )
643
881
  }
644
882
 
645
- static async fromArtifactFile(path: string): Promise<Script> {
646
- const sourceFile = this.getSourceFile(path, [])
647
- const artifactPath = sourceFile.artifactPath
648
- const content = await fsPromises.readFile(artifactPath)
883
+ static async fromArtifactFile(path: string, bytecodeDebugPatch: string): Promise<Script> {
884
+ const content = await fsPromises.readFile(path)
649
885
  const artifact = JSON.parse(content.toString())
650
- return this.fromJson(artifact)
886
+ return this.fromJson(artifact, bytecodeDebugPatch)
651
887
  }
652
888
 
653
889
  override toString(): string {
654
- return JSON.stringify(
655
- {
656
- sourceCodeSha256: this.sourceCodeSha256,
657
- bytecodeTemplate: this.bytecodeTemplate,
658
- fieldsSig: this.fieldsSig,
659
- functions: this.functions
660
- },
661
- null,
662
- 2
663
- )
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)
664
897
  }
665
898
 
666
899
  async paramsForDeployment(params: BuildExecuteScriptTx): Promise<SignExecuteScriptTxParams> {
@@ -668,7 +901,7 @@ export class Script extends Common {
668
901
  signerAddress: params.signerAddress,
669
902
  bytecode: this.buildByteCodeToDeploy(params.initialFields ? params.initialFields : {}),
670
903
  attoAlphAmount: extractOptionalNumber256(params.attoAlphAmount),
671
- tokens: typeof params.tokens !== 'undefined' ? params.tokens.map(toApiToken) : undefined,
904
+ tokens: toApiTokens(params.tokens),
672
905
  gasAmount: params.gasAmount,
673
906
  gasPrice: extractOptionalNumber256(params.gasPrice)
674
907
  }
@@ -681,7 +914,7 @@ export class Script extends Common {
681
914
  ): Promise<BuildScriptTxResult> {
682
915
  const signerParams = await this.paramsForDeployment({
683
916
  ...params,
684
- signerAddress: (await signer.getAccounts())[0].address
917
+ signerAddress: (await signer.getActiveAccount()).address
685
918
  })
686
919
  return await signer.buildScriptTx(signerParams)
687
920
  }
@@ -691,162 +924,8 @@ export class Script extends Common {
691
924
  }
692
925
  }
693
926
 
694
- export type Number256 = number | bigint | string
695
- export type Val = Number256 | boolean | string | Val[]
696
- export type NamedVals = Record<string, Val>
697
- export type Fields = NamedVals
698
- export type Arguments = NamedVals
699
-
700
- function extractBoolean(v: Val): boolean {
701
- if (typeof v === 'boolean') {
702
- return v
703
- } else {
704
- throw new Error(`Invalid boolean value: ${v}`)
705
- }
706
- }
707
-
708
- // TODO: check integer bounds
709
- function extractNumber256(v: Val): string {
710
- if ((typeof v === 'number' && Number.isInteger(v)) || typeof v === 'bigint') {
711
- return v.toString()
712
- } else if (typeof v === 'string') {
713
- return v
714
- } else {
715
- throw new Error(`Invalid 256 bit number: ${v}`)
716
- }
717
- }
718
-
719
927
  function extractOptionalNumber256(v?: Val): string | undefined {
720
- return typeof v !== 'undefined' ? extractNumber256(v) : undefined
721
- }
722
-
723
- // TODO: check hex string
724
- function extractByteVec(v: Val): string {
725
- if (typeof v === 'string') {
726
- // try to convert from address to contract id
727
- try {
728
- const address = bs58.decode(v)
729
- if (address.length == 33 && address[0] == 3) {
730
- return Buffer.from(address.slice(1)).toString('hex')
731
- }
732
- } catch (_) {
733
- return v as string
734
- }
735
- return v as string
736
- } else {
737
- throw new Error(`Invalid string: ${v}`)
738
- }
739
- }
740
-
741
- function extractBs58(v: Val): string {
742
- if (typeof v === 'string') {
743
- try {
744
- bs58.decode(v)
745
- return v as string
746
- } catch (error) {
747
- throw new Error(`Invalid base58 string: ${v}`)
748
- }
749
- } else {
750
- throw new Error(`Invalid string: ${v}`)
751
- }
752
- }
753
-
754
- function decodeNumber256(n: string): Number256 {
755
- if (Number.isSafeInteger(Number.parseInt(n))) {
756
- return Number(n)
757
- } else {
758
- return BigInt(n)
759
- }
760
- }
761
-
762
- export function extractArray(tpe: string, v: Val): node.Val {
763
- if (!Array.isArray(v)) {
764
- throw new Error(`Expected array, got ${v}`)
765
- }
766
-
767
- const semiColonIndex = tpe.lastIndexOf(';')
768
- if (semiColonIndex == -1) {
769
- throw new Error(`Invalid Val type: ${tpe}`)
770
- }
771
-
772
- const subType = tpe.slice(1, semiColonIndex)
773
- const dim = parseInt(tpe.slice(semiColonIndex + 1, -1))
774
- if ((v as Val[]).length != dim) {
775
- throw new Error(`Invalid val dimension: ${v}`)
776
- } else {
777
- return { value: (v as Val[]).map((v) => toApiVal(v, subType)), type: 'Array' }
778
- }
779
- }
780
-
781
- export function toApiVal(v: Val, tpe: string): node.Val {
782
- if (tpe === 'Bool') {
783
- return { value: extractBoolean(v), type: tpe }
784
- } else if (tpe === 'U256' || tpe === 'I256') {
785
- return { value: extractNumber256(v), type: tpe }
786
- } else if (tpe === 'ByteVec') {
787
- return { value: extractByteVec(v), type: tpe }
788
- } else if (tpe === 'Address') {
789
- return { value: extractBs58(v), type: tpe }
790
- } else {
791
- return extractArray(tpe, v)
792
- }
793
- }
794
-
795
- function decodeArrayType(tpe: string): [baseType: string, dims: number[]] {
796
- const semiColonIndex = tpe.lastIndexOf(';')
797
- if (semiColonIndex === -1) {
798
- throw new Error(`Invalid Val type: ${tpe}`)
799
- }
800
-
801
- const subType = tpe.slice(1, semiColonIndex)
802
- const dim = parseInt(tpe.slice(semiColonIndex + 1, -1))
803
- if (subType[0] == '[') {
804
- const [baseType, subDim] = decodeArrayType(subType)
805
- return [baseType, (subDim.unshift(dim), subDim)]
806
- } else {
807
- return [subType, [dim]]
808
- }
809
- }
810
-
811
- function foldVals(vals: Val[], dims: number[]): Val {
812
- if (dims.length == 1) {
813
- return vals
814
- } else {
815
- const result: Val[] = []
816
- const chunkSize = vals.length / dims[0]
817
- const chunkDims = dims.slice(1)
818
- for (let i = 0; i < vals.length; i += chunkSize) {
819
- const chunk = vals.slice(i, i + chunkSize)
820
- result.push(foldVals(chunk, chunkDims))
821
- }
822
- return result
823
- }
824
- }
825
-
826
- function _fromApiVal(vals: node.Val[], valIndex: number, tpe: string): [result: Val, nextIndex: number] {
827
- if (vals.length === 0) {
828
- throw new Error('Not enough Vals')
829
- }
830
-
831
- const firstVal = vals[`${valIndex}`]
832
- if (tpe === 'Bool' && firstVal.type === tpe) {
833
- return [firstVal.value as boolean, valIndex + 1]
834
- } else if ((tpe === 'U256' || tpe === 'I256') && firstVal.type === tpe) {
835
- return [decodeNumber256(firstVal.value as string), valIndex + 1]
836
- } else if ((tpe === 'ByteVec' || tpe === 'Address') && firstVal.type === tpe) {
837
- return [firstVal.value as string, valIndex + 1]
838
- } else {
839
- const [baseType, dims] = decodeArrayType(tpe)
840
- const arraySize = dims.reduce((a, b) => a * b)
841
- const nextIndex = valIndex + arraySize
842
- const valsToUse = vals.slice(valIndex, nextIndex)
843
- if (valsToUse.length == arraySize && valsToUse.every((val) => val.type === baseType)) {
844
- const localVals = valsToUse.map((val) => fromApiVal(val, baseType))
845
- return [foldVals(localVals, dims), nextIndex]
846
- } else {
847
- throw new Error(`Invalid array Val type: ${valsToUse}, ${tpe}`)
848
- }
849
- }
928
+ return typeof v !== 'undefined' ? toApiNumber256(v) : undefined
850
929
  }
851
930
 
852
931
  function fromApiFields(vals: node.Val[], fieldsSig: node.FieldsSig): Fields {
@@ -857,70 +936,22 @@ function fromApiEventFields(vals: node.Val[], eventSig: node.EventSig): Fields {
857
936
  return fromApiVals(vals, eventSig.fieldNames, eventSig.fieldTypes)
858
937
  }
859
938
 
860
- function fromApiVals(vals: node.Val[], names: string[], types: string[]): Fields {
861
- let valIndex = 0
862
- const result: Fields = {}
863
- types.forEach((currentType, index) => {
864
- const currentName = names[`${index}`]
865
- const [val, nextIndex] = _fromApiVal(vals, valIndex, currentType)
866
- valIndex = nextIndex
867
- result[`${currentName}`] = val
868
- })
869
- return result
870
- }
871
-
872
- function fromApiArray(vals: node.Val[], types: string[]): Val[] {
873
- let valIndex = 0
874
- const result: Val[] = []
875
- for (const currentType of types) {
876
- const [val, nextIndex] = _fromApiVal(vals, valIndex, currentType)
877
- result.push(val)
878
- valIndex = nextIndex
879
- }
880
- return result
881
- }
882
-
883
- function fromApiVal(v: node.Val, tpe: string): Val {
884
- if (v.type === 'Bool' && v.type === tpe) {
885
- return v.value as boolean
886
- } else if ((v.type === 'U256' || v.type === 'I256') && v.type === tpe) {
887
- return decodeNumber256(v.value as string)
888
- } else if ((v.type === 'ByteVec' || v.type === 'Address') && v.type === tpe) {
889
- return v.value as string
890
- } else {
891
- throw new Error(`Invalid node.Val type: ${v}`)
892
- }
893
- }
894
-
895
939
  export interface Asset {
896
940
  alphAmount: Number256
897
941
  tokens?: Token[]
898
942
  }
899
943
 
900
- export interface Token {
901
- id: string
902
- amount: Number256
903
- }
904
-
905
- function toApiToken(token: Token): node.Token {
906
- return { id: token.id, amount: extractNumber256(token.amount) }
907
- }
908
-
909
- function fromApiToken(token: node.Token): Token {
910
- return { id: token.id, amount: decodeNumber256(token.amount) }
911
- }
912
-
913
944
  function toApiAsset(asset: Asset): node.AssetState {
914
945
  return {
915
- attoAlphAmount: extractNumber256(asset.alphAmount),
946
+ attoAlphAmount: toApiNumber256(asset.alphAmount),
916
947
  tokens: typeof asset.tokens !== 'undefined' ? asset.tokens.map(toApiToken) : []
917
948
  }
918
949
  }
919
950
 
920
951
  function fromApiAsset(asset: node.AssetState): Asset {
921
952
  return {
922
- alphAmount: decodeNumber256(asset.attoAlphAmount),
923
- tokens: typeof asset.tokens !== 'undefined' ? asset.tokens.map(fromApiToken) : undefined
953
+ alphAmount: fromApiNumber256(asset.attoAlphAmount),
954
+ tokens: fromApiTokens(asset.tokens)
924
955
  }
925
956
  }
926
957
 
@@ -1008,14 +1039,17 @@ export interface ContractEventByTxId {
1008
1039
  fields: Fields
1009
1040
  }
1010
1041
 
1042
+ export type DebugMessage = node.DebugMessage
1043
+
1011
1044
  export interface TestContractResult {
1012
- address: string
1013
1045
  contractId: string
1046
+ contractAddress: string
1014
1047
  returns: Val[]
1015
1048
  gasUsed: number
1016
1049
  contracts: ContractState[]
1017
1050
  txOutputs: Output[]
1018
1051
  events: ContractEventByTxId[]
1052
+ debugMessages: DebugMessage[]
1019
1053
  }
1020
1054
  export declare type Output = AssetOutput | ContractOutput
1021
1055
  export interface AssetOutput extends Asset {
@@ -1028,7 +1062,7 @@ export interface ContractOutput {
1028
1062
  type: string
1029
1063
  address: string
1030
1064
  alphAmount: Number256
1031
- tokens: Token[]
1065
+ tokens?: Token[]
1032
1066
  }
1033
1067
 
1034
1068
  function fromApiOutput(output: node.Output): Output {
@@ -1037,8 +1071,8 @@ function fromApiOutput(output: node.Output): Output {
1037
1071
  return {
1038
1072
  type: 'AssetOutput',
1039
1073
  address: asset.address,
1040
- alphAmount: decodeNumber256(asset.attoAlphAmount),
1041
- tokens: asset.tokens.map(fromApiToken),
1074
+ alphAmount: fromApiNumber256(asset.attoAlphAmount),
1075
+ tokens: fromApiTokens(asset.tokens),
1042
1076
  lockTime: asset.lockTime,
1043
1077
  message: asset.message
1044
1078
  }
@@ -1047,8 +1081,8 @@ function fromApiOutput(output: node.Output): Output {
1047
1081
  return {
1048
1082
  type: 'ContractOutput',
1049
1083
  address: asset.address,
1050
- alphAmount: decodeNumber256(asset.attoAlphAmount),
1051
- tokens: asset.tokens.map(fromApiToken)
1084
+ alphAmount: fromApiNumber256(asset.attoAlphAmount),
1085
+ tokens: fromApiTokens(asset.tokens)
1052
1086
  }
1053
1087
  } else {
1054
1088
  throw new Error(`Unknown output type: ${output}`)