@alephium/web3 0.2.0-rc.0 → 0.2.0-rc.11

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 (98) hide show
  1. package/.eslintignore +2 -2
  2. package/README.md +2 -135
  3. package/contracts/greeter/greeter.ral +3 -3
  4. package/contracts/greeter/greeter_interface.ral +1 -0
  5. package/contracts/greeter_main.ral +3 -5
  6. package/dist/alephium-web3.min.js +1 -1
  7. package/dist/alephium-web3.min.js.LICENSE.txt +0 -17
  8. package/dist/alephium-web3.min.js.map +1 -1
  9. package/dist/scripts/create-project.js +3 -2
  10. package/dist/src/api/api-alephium.d.ts +46 -13
  11. package/dist/src/api/api-alephium.js +19 -3
  12. package/dist/src/api/api-explorer.d.ts +32 -16
  13. package/dist/src/api/api-explorer.js +26 -0
  14. package/dist/src/api/index.d.ts +3 -2
  15. package/dist/src/api/index.js +22 -3
  16. package/dist/src/api/types.d.ts +23 -0
  17. package/dist/src/api/types.js +240 -0
  18. package/dist/src/contract/contract.d.ts +104 -64
  19. package/dist/src/contract/contract.js +370 -426
  20. package/dist/src/contract/events.d.ts +4 -4
  21. package/dist/src/contract/index.js +5 -1
  22. package/dist/src/contract/ralph.d.ts +4 -4
  23. package/dist/src/global.d.ts +4 -0
  24. package/dist/src/global.js +38 -0
  25. package/dist/src/index.d.ts +2 -0
  26. package/dist/src/index.js +20 -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 +21 -11
  30. package/dist/src/signer/signer.js +46 -9
  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/utils/index.d.ts +0 -1
  35. package/dist/src/utils/index.js +5 -2
  36. package/dist/src/utils/subscription.d.ts +0 -1
  37. package/dist/src/utils/subscription.js +2 -1
  38. package/dist/src/utils/utils.d.ts +5 -4
  39. package/dist/src/utils/utils.js +18 -17
  40. package/jest-config.json +11 -0
  41. package/package.json +7 -35
  42. package/scripts/create-project.ts +3 -2
  43. package/src/api/api-alephium.ts +56 -8
  44. package/src/api/api-explorer.ts +30 -0
  45. package/src/api/index.ts +14 -3
  46. package/src/api/types.ts +233 -0
  47. package/src/contract/contract.ts +537 -526
  48. package/src/contract/events.ts +6 -6
  49. package/src/contract/ralph.ts +4 -4
  50. package/src/{transaction/sign-verify.ts → global.ts} +14 -15
  51. package/src/index.ts +3 -0
  52. package/src/signer/index.ts +0 -1
  53. package/src/signer/signer.ts +70 -19
  54. package/src/transaction/index.ts +0 -1
  55. package/src/transaction/status.ts +4 -2
  56. package/src/utils/index.ts +0 -1
  57. package/src/utils/subscription.ts +3 -3
  58. package/src/utils/utils.ts +11 -11
  59. package/templates/base/package.json +2 -2
  60. package/templates/base/src/greeter.ts +10 -9
  61. package/templates/react/package.json +2 -2
  62. package/templates/react/src/App.tsx +2 -2
  63. package/.eslintrc.json +0 -21
  64. package/LICENSE +0 -165
  65. package/contracts/add/add.ral +0 -16
  66. package/contracts/main.ral +0 -6
  67. package/contracts/sub/sub.ral +0 -9
  68. package/dist/src/signer/node-wallet.d.ts +0 -13
  69. package/dist/src/signer/node-wallet.js +0 -60
  70. package/dist/src/test/index.d.ts +0 -7
  71. package/dist/src/test/index.js +0 -41
  72. package/dist/src/test/privatekey-wallet.d.ts +0 -12
  73. package/dist/src/test/privatekey-wallet.js +0 -68
  74. package/dist/src/transaction/sign-verify.d.ts +0 -2
  75. package/dist/src/transaction/sign-verify.js +0 -58
  76. package/dist/src/utils/password-crypto.d.ts +0 -2
  77. package/dist/src/utils/password-crypto.js +0 -69
  78. package/gitignore +0 -10
  79. package/src/contract/ralph.test.ts +0 -178
  80. package/src/fixtures/address.json +0 -36
  81. package/src/fixtures/balance.json +0 -9
  82. package/src/fixtures/self-clique.json +0 -19
  83. package/src/fixtures/transaction.json +0 -13
  84. package/src/fixtures/transactions.json +0 -179
  85. package/src/signer/fixtures/genesis.json +0 -26
  86. package/src/signer/fixtures/wallets.json +0 -26
  87. package/src/signer/node-wallet.ts +0 -74
  88. package/src/test/index.ts +0 -32
  89. package/src/test/privatekey-wallet.ts +0 -58
  90. package/src/transaction/sign-verify.test.ts +0 -50
  91. package/src/utils/address.test.ts +0 -47
  92. package/src/utils/djb2.test.ts +0 -35
  93. package/src/utils/password-crypto.test.ts +0 -27
  94. package/src/utils/password-crypto.ts +0 -77
  95. package/src/utils/utils.test.ts +0 -161
  96. package/test/contract.test.ts +0 -178
  97. package/test/events.test.ts +0 -138
  98. package/test/transaction.test.ts +0 -72
@@ -17,250 +17,535 @@ 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
+
44
+ export type FieldsSig = node.FieldsSig
45
+ export type EventSig = node.EventSig
46
+ export type FunctionSig = node.FunctionSig
47
+ export type Fields = NamedVals
48
+ export type Arguments = NamedVals
49
+
50
+ enum SourceType {
51
+ Contract = 0,
52
+ Script = 1,
53
+ AbstractContract = 2,
54
+ Interface = 3
55
+ }
56
+
57
+ export type CompilerOptions = node.CompilerOptions & {
58
+ errorOnWarnings: boolean
59
+ }
60
+
61
+ export const DEFAULT_NODE_COMPILER_OPTIONS: node.CompilerOptions = {
62
+ ignoreUnusedConstantsWarnings: false,
63
+ ignoreUnusedVariablesWarnings: false,
64
+ ignoreUnusedFieldsWarnings: false,
65
+ ignoreUnusedPrivateFunctionsWarnings: false,
66
+ ignoreReadonlyCheckWarnings: false,
67
+ ignoreExternalCallCheckWarnings: false
68
+ }
69
+
70
+ export const DEFAULT_COMPILER_OPTIONS: CompilerOptions = { errorOnWarnings: true, ...DEFAULT_NODE_COMPILER_OPTIONS }
71
+
72
+ class TypedMatcher<T extends SourceType> {
73
+ matcher: RegExp
74
+ type: T
75
+
76
+ constructor(pattern: string, type: T) {
77
+ this.matcher = new RegExp(pattern, 'mg')
78
+ this.type = type
79
+ }
80
+
81
+ match(str: string): number {
82
+ const results = str.match(this.matcher)
83
+ return results === null ? 0 : results.length
84
+ }
85
+ }
29
86
 
30
87
  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
- }
88
+ type: SourceType
89
+ contractPath: string
90
+ sourceCode: string
91
+ sourceCodeHash: string
92
+
93
+ getArtifactPath(artifactsRootPath: string): string {
94
+ return artifactsRootPath + this.contractPath.slice(this.contractPath.indexOf('/')) + '.json'
95
+ }
96
+
97
+ constructor(type: SourceType, sourceCode: string, sourceCodeHash: string, contractPath: string) {
98
+ this.type = type
99
+ this.sourceCode = sourceCode
100
+ this.sourceCodeHash = sourceCodeHash
101
+ this.contractPath = contractPath
102
+ }
103
+
104
+ static async from(type: SourceType, sourceCode: string, contractPath: string): Promise<SourceFile> {
105
+ const sourceCodeHash = await crypto.subtle.digest('SHA-256', Buffer.from(sourceCode))
106
+ return new SourceFile(type, sourceCode, Buffer.from(sourceCodeHash).toString('hex'), contractPath)
46
107
  }
47
108
  }
48
109
 
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('^TxContract [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)
110
+ class Compiled<T extends Artifact> {
111
+ sourceFile: SourceFile
112
+ artifact: T
113
+ warnings: string[]
114
+
115
+ constructor(sourceFile: SourceFile, artifact: T, warnings: string[]) {
116
+ this.sourceFile = sourceFile
117
+ this.artifact = artifact
118
+ this.warnings = warnings
119
+ }
120
+ }
121
+
122
+ class ProjectArtifact {
123
+ static readonly artifactFileName = '.project.json'
124
+
125
+ compilerOptionsUsed: node.CompilerOptions
126
+ infos: Map<string, { sourceCodeHash: string; warnings: string[] }>
127
+
128
+ static checkCompilerOptionsParameter(compilerOptions: node.CompilerOptions): void {
129
+ if (Object.keys(compilerOptions).length != Object.keys(DEFAULT_NODE_COMPILER_OPTIONS).length) {
130
+ throw Error(`Not all compiler options are set: ${compilerOptions}`)
131
+ }
132
+
133
+ const combined = { ...compilerOptions, ...DEFAULT_NODE_COMPILER_OPTIONS }
134
+ if (Object.keys(combined).length !== Object.keys(DEFAULT_NODE_COMPILER_OPTIONS).length) {
135
+ throw Error(`There are unknown compiler options: ${compilerOptions}`)
70
136
  }
71
137
  }
72
138
 
73
- constructor(sourceCodeSha256: string, functions: node.FunctionSig[]) {
74
- this.sourceCodeSha256 = sourceCodeSha256
75
- this.functions = functions
139
+ constructor(
140
+ compilerOptionsUsed: node.CompilerOptions,
141
+ infos: Map<string, { sourceCodeHash: string; warnings: string[] }>
142
+ ) {
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<{ sourceCodeHash: string; warnings: string[] }>(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 contractsRootPath: string
199
+ readonly artifactsRootPath: string
200
+ readonly nodeProvider: NodeProvider
201
+
202
+ static currentProject: Project
203
+
204
+ static readonly abstractContractMatcher = new TypedMatcher<SourceType>(
205
+ '^Abstract Contract [A-Z][a-zA-Z0-9]*',
206
+ SourceType.AbstractContract
207
+ )
208
+ static readonly contractMatcher = new TypedMatcher('^Contract [A-Z][a-zA-Z0-9]*', SourceType.Contract)
209
+ static readonly interfaceMatcher = new TypedMatcher('^Interface [A-Z][a-zA-Z0-9]* \\{', SourceType.Interface)
210
+ static readonly scriptMatcher = new TypedMatcher('^TxScript [A-Z][a-zA-Z0-9]*', SourceType.Script)
211
+ static readonly matchers = [
212
+ Project.abstractContractMatcher,
213
+ Project.contractMatcher,
214
+ Project.interfaceMatcher,
215
+ Project.scriptMatcher
216
+ ]
217
+
218
+ static buildProjectArtifact(
219
+ sourceFiles: SourceFile[],
220
+ contracts: Compiled<Contract>[],
221
+ scripts: Compiled<Script>[],
222
+ compilerOptions: node.CompilerOptions
223
+ ): ProjectArtifact {
224
+ const files: Map<string, { sourceCodeHash: string; warnings: string[] }> = new Map()
225
+ contracts.forEach((c) => {
226
+ files.set(c.sourceFile.contractPath, {
227
+ sourceCodeHash: c.sourceFile.sourceCodeHash,
228
+ warnings: c.warnings
229
+ })
104
230
  })
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 ''
231
+ scripts.forEach((s) => {
232
+ files.set(s.sourceFile.contractPath, {
233
+ sourceCodeHash: s.sourceFile.sourceCodeHash,
234
+ warnings: s.warnings
235
+ })
117
236
  })
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)
237
+ const compiledSize = contracts.length + scripts.length
238
+ sourceFiles.slice(compiledSize).forEach((c) => {
239
+ files.set(c.contractPath, {
240
+ sourceCodeHash: c.sourceCodeHash,
241
+ warnings: []
242
+ })
243
+ })
244
+ return new ProjectArtifact(compilerOptions, files)
245
+ }
246
+
247
+ private constructor(
248
+ provider: NodeProvider,
249
+ contractsRootPath: string,
250
+ artifactsRootPath: string,
251
+ sourceFiles: SourceFile[],
252
+ contracts: Compiled<Contract>[],
253
+ scripts: Compiled<Script>[],
254
+ errorOnWarnings: boolean,
255
+ projectArtifact: ProjectArtifact
256
+ ) {
257
+ this.nodeProvider = provider
258
+ this.contractsRootPath = contractsRootPath
259
+ this.artifactsRootPath = artifactsRootPath
260
+ this.sourceFiles = sourceFiles
261
+ this.contracts = contracts
262
+ this.scripts = scripts
263
+ this.projectArtifact = projectArtifact
264
+
265
+ if (errorOnWarnings) {
266
+ Project.checkCompilerWarnings(
267
+ [...contracts.map((c) => c.warnings).flat(), ...scripts.map((s) => s.warnings).flat()],
268
+ errorOnWarnings
269
+ )
270
+ }
271
+ }
272
+
273
+ private getContractPath(path: string): string {
274
+ return path.startsWith(`./${this.contractsRootPath}`)
275
+ ? path.slice(2)
276
+ : path.startsWith(this.contractsRootPath)
277
+ ? path
278
+ : this.contractsRootPath + '/' + path
279
+ }
280
+
281
+ static checkCompilerWarnings(warnings: string[], errorOnWarnings: boolean): void {
282
+ if (warnings.length !== 0) {
283
+ const prefixPerWarning = ' - '
284
+ const warningString = prefixPerWarning + warnings.join('\n' + prefixPerWarning)
285
+ const output = `Compilation warnings:\n` + warningString + '\n'
286
+ if (errorOnWarnings) {
287
+ throw new Error(output)
288
+ } else {
289
+ console.log(output)
127
290
  }
128
291
  }
129
- return result
130
292
  }
131
293
 
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()
294
+ static contract(name: string): Contract {
295
+ const contract = Project.currentProject.contracts.find((c) => c.artifact.name === name)
296
+ if (typeof contract === 'undefined') {
297
+ throw new Error(`Contract "${name}" does not exist`)
298
+ }
299
+ return contract.artifact
300
+ }
301
+
302
+ static script(name: string): Script {
303
+ const script = Project.currentProject.scripts.find((c) => c.artifact.name === name)
304
+ if (typeof script === 'undefined') {
305
+ throw new Error(`Script "${name}" does not exist`)
306
+ }
307
+ return script.artifact
308
+ }
140
309
 
141
- validate(contractStr)
142
- return Common._handleImports(sourceFile.dirs, contractStr, importsCache)
310
+ private async saveArtifactsToFile(): Promise<void> {
311
+ const artifactsRootPath = this.artifactsRootPath
312
+ const saveToFile = async function (compiled: Compiled<Artifact>): Promise<void> {
313
+ const artifactPath = compiled.sourceFile.getArtifactPath(artifactsRootPath)
314
+ const folder = artifactPath.slice(0, artifactPath.lastIndexOf('/'))
315
+ if (!fs.existsSync(folder)) {
316
+ fs.mkdirSync(folder, { recursive: true })
317
+ }
318
+ return fsPromises.writeFile(artifactPath, compiled.artifact.toString())
319
+ }
320
+ for (const contract of this.contracts) {
321
+ await saveToFile(contract)
322
+ }
323
+ for (const script of this.scripts) {
324
+ await saveToFile(script)
325
+ }
326
+ await this.projectArtifact.saveToFile(this.artifactsRootPath)
143
327
  }
144
328
 
145
- static checkFileNameExtension(fileName: string): void {
146
- if (!fileName.endsWith('.ral')) {
147
- throw new Error('Smart contract file name should end with ".ral"')
329
+ contractByCodeHash(codeHash: string): Contract {
330
+ const contract = this.contracts.find((c) => c.artifact.codeHash === codeHash)
331
+ if (typeof contract === 'undefined') {
332
+ throw new Error(`Unknown code with code hash: ${codeHash}`)
148
333
  }
334
+ return contract.artifact
149
335
  }
150
336
 
151
- protected static async _from<T extends { sourceCodeSha256: string }>(
337
+ private static async compile(
152
338
  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)
339
+ files: SourceFile[],
340
+ contractsRootPath: string,
341
+ artifactsRootPath: string,
342
+ errorOnWarnings: boolean,
343
+ compilerOptions: node.CompilerOptions
344
+ ): Promise<Project> {
345
+ const sourceStr = files.map((f) => f.sourceCode).join('\n')
346
+ const result = await provider.contracts.postContractsCompileProject({
347
+ code: sourceStr,
348
+ compilerOptions: compilerOptions
349
+ })
350
+ const contracts: Compiled<Contract>[] = []
351
+ const scripts: Compiled<Script>[] = []
352
+ result.contracts.forEach((contractResult, index) => {
353
+ const sourceFile = files[`${index}`]
354
+ const contract = Contract.fromCompileResult(contractResult)
355
+ contracts.push(new Compiled(sourceFile, contract, contractResult.warnings))
356
+ })
357
+ result.scripts.forEach((scriptResult, index) => {
358
+ const sourceFile = files[index + contracts.length]
359
+ const script = Script.fromCompileResult(scriptResult)
360
+ scripts.push(new Compiled(sourceFile, script, scriptResult.warnings))
361
+ })
362
+ const projectArtifact = Project.buildProjectArtifact(files, contracts, scripts, compilerOptions)
363
+ const project = new Project(
364
+ provider,
365
+ contractsRootPath,
366
+ artifactsRootPath,
367
+ files,
368
+ contracts,
369
+ scripts,
370
+ errorOnWarnings,
371
+ projectArtifact
372
+ )
373
+ await project.saveArtifactsToFile()
374
+ return project
375
+ }
376
+
377
+ private static async loadArtifacts(
378
+ provider: NodeProvider,
379
+ files: SourceFile[],
380
+ projectArtifact: ProjectArtifact,
381
+ contractsRootPath: string,
382
+ artifactsRootPath: string,
383
+ errorOnWarnings: boolean,
384
+ compilerOptions: node.CompilerOptions
385
+ ): Promise<Project> {
386
+ try {
387
+ const contracts: Compiled<Contract>[] = []
388
+ const scripts: Compiled<Script>[] = []
389
+ for (const file of files) {
390
+ const info = projectArtifact.infos.get(file.contractPath)
391
+ if (typeof info === 'undefined') {
392
+ throw Error(`Unable to find project info for ${file.contractPath}, please rebuild the project`)
393
+ }
394
+ const warnings = info.warnings
395
+ const artifactPath = file.getArtifactPath(artifactsRootPath)
396
+ if (file.type === SourceType.Contract) {
397
+ const artifact = await Contract.fromArtifactFile(artifactPath)
398
+ contracts.push(new Compiled(file, artifact, warnings))
399
+ } else if (file.type === SourceType.Script) {
400
+ const artifact = await Script.fromArtifactFile(artifactPath)
401
+ scripts.push(new Compiled(file, artifact, warnings))
402
+ }
403
+ }
404
+
405
+ return new Project(
406
+ provider,
407
+ contractsRootPath,
408
+ artifactsRootPath,
409
+ files,
410
+ contracts,
411
+ scripts,
412
+ errorOnWarnings,
413
+ projectArtifact
414
+ )
415
+ } catch (error) {
416
+ console.log(`Failed to load artifacts, error: ${error}, try to re-compile contracts...`)
417
+ return Project.compile(provider, files, contractsRootPath, artifactsRootPath, errorOnWarnings, compilerOptions)
166
418
  }
167
419
  }
168
420
 
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 })
421
+ private static async loadSourceFile(dirPath: string, filename: string): Promise<SourceFile> {
422
+ const contractPath = dirPath + '/' + filename
423
+ if (!filename.endsWith('.ral')) {
424
+ throw new Error(`Invalid filename: ${contractPath}, smart contract file name should end with ".ral"`)
425
+ }
426
+
427
+ const sourceBuffer = await fsPromises.readFile(contractPath)
428
+ const sourceStr = sourceBuffer.toString()
429
+ const results = this.matchers.map((m) => m.match(sourceStr))
430
+ const matchNumber = results.reduce((a, b) => a + b, 0)
431
+ if (matchNumber === 0) {
432
+ throw new Error(`No contract defined in file: ${contractPath}`)
433
+ }
434
+ if (matchNumber > 1) {
435
+ throw new Error(`Multiple definitions in file: ${contractPath}`)
436
+ }
437
+ const matcherIndex = results.indexOf(1)
438
+ const type = this.matchers[`${matcherIndex}`].type
439
+ return SourceFile.from(type, sourceStr, contractPath)
440
+ }
441
+
442
+ private static async loadSourceFiles(contractsRootPath: string): Promise<SourceFile[]> {
443
+ const loadDir = async function (dirPath: string, results: SourceFile[]): Promise<void> {
444
+ const dirents = await fsPromises.readdir(dirPath, { withFileTypes: true })
445
+ for (const dirent of dirents) {
446
+ if (dirent.isFile()) {
447
+ const file = await Project.loadSourceFile(dirPath, dirent.name)
448
+ results.push(file)
449
+ } else {
450
+ const newPath = dirPath + '/' + dirent.name
451
+ await loadDir(newPath, results)
452
+ }
453
+ }
454
+ }
455
+ const sourceFiles: SourceFile[] = []
456
+ await loadDir(contractsRootPath, sourceFiles)
457
+ const contractAndScriptSize = sourceFiles.filter(
458
+ (f) => f.type === SourceType.Contract || f.type === SourceType.Script
459
+ ).length
460
+ if (sourceFiles.length === 0 || contractAndScriptSize === 0) {
461
+ throw new Error('Project have no source files')
462
+ }
463
+ return sourceFiles.sort((a, b) => a.type - b.type)
464
+ }
465
+
466
+ static async build(
467
+ compilerOptionsPartial: Partial<CompilerOptions> = {},
468
+ contractsRootPath = 'contracts',
469
+ artifactsRootPath = 'artifacts'
470
+ ): Promise<void> {
471
+ const provider = getCurrentNodeProvider()
472
+ const sourceFiles = await Project.loadSourceFiles(contractsRootPath)
473
+ const { errorOnWarnings, ...nodeCompilerOptions } = { ...DEFAULT_COMPILER_OPTIONS, ...compilerOptionsPartial }
474
+ const projectArtifact = await ProjectArtifact.from(artifactsRootPath)
475
+ if (typeof projectArtifact === 'undefined' || projectArtifact.needToReCompile(nodeCompilerOptions, sourceFiles)) {
476
+ console.log(`Compile contracts in folder "${contractsRootPath}"`)
477
+ Project.currentProject = await Project.compile(
478
+ provider,
479
+ sourceFiles,
480
+ contractsRootPath,
481
+ artifactsRootPath,
482
+ errorOnWarnings,
483
+ nodeCompilerOptions
484
+ )
485
+ } else {
486
+ console.log(`Load compiled contracts from folder "${artifactsRootPath}"`)
487
+ Project.currentProject = await Project.loadArtifacts(
488
+ provider,
489
+ sourceFiles,
490
+ projectArtifact,
491
+ contractsRootPath,
492
+ artifactsRootPath,
493
+ errorOnWarnings,
494
+ nodeCompilerOptions
495
+ )
173
496
  }
174
- return fsPromises.writeFile(sourceFile.artifactPath, this.toString())
497
+ }
498
+ }
499
+
500
+ export abstract class Artifact {
501
+ readonly name: string
502
+ readonly functions: FunctionSig[]
503
+
504
+ constructor(name: string, functions: FunctionSig[]) {
505
+ this.name = name
506
+ this.functions = functions
175
507
  }
176
508
 
177
509
  abstract buildByteCodeToDeploy(initialFields?: Fields): string
510
+
511
+ publicFunctions(): string[] {
512
+ return this.functions.filter((func) => func.isPublic).map((func) => func.name)
513
+ }
514
+
515
+ usingPreapprovedAssetsFunctions(): string[] {
516
+ return this.functions.filter((func) => func.usePreapprovedAssets).map((func) => func.name)
517
+ }
518
+
519
+ usingAssetsInContractFunctions(): string[] {
520
+ return this.functions.filter((func) => func.useAssetsInContract).map((func) => func.name)
521
+ }
178
522
  }
179
523
 
180
- export class Contract extends Common {
524
+ export class Contract extends Artifact {
181
525
  readonly bytecode: string
182
526
  readonly codeHash: string
183
- readonly fieldsSig: node.FieldsSig
184
- readonly eventsSig: node.EventSig[]
527
+ readonly fieldsSig: FieldsSig
528
+ readonly eventsSig: EventSig[]
185
529
 
186
530
  constructor(
187
- sourceCodeSha256: string,
531
+ name: string,
188
532
  bytecode: string,
189
533
  codeHash: string,
190
- fieldsSig: node.FieldsSig,
191
- eventsSig: node.EventSig[],
192
- functions: node.FunctionSig[]
534
+ fieldsSig: FieldsSig,
535
+ eventsSig: EventSig[],
536
+ functions: FunctionSig[]
193
537
  ) {
194
- super(sourceCodeSha256, functions)
538
+ super(name, functions)
195
539
  this.bytecode = bytecode
196
540
  this.codeHash = codeHash
197
541
  this.fieldsSig = fieldsSig
198
542
  this.eventsSig = eventsSig
199
543
  }
200
544
 
201
- static checkCodeType(fileName: string, contractStr: string): void {
202
- const interfaceMatches = contractStr.match(Contract.interfaceRegex)
203
- const contractMatches = contractStr.match(Contract.contractRegex)
204
- if (interfaceMatches === null && contractMatches === null) {
205
- throw new Error(`No contract found in: ${fileName}`)
206
- }
207
- if (interfaceMatches && contractMatches) {
208
- throw new Error(`Multiple contracts and interfaces in: ${fileName}`)
209
- }
210
- if (interfaceMatches === null) {
211
- if (contractMatches !== null && contractMatches.length > 1) {
212
- throw new Error(`Multiple contracts in: ${fileName}`)
213
- }
214
- }
215
- if (contractMatches === null) {
216
- if (interfaceMatches !== null && interfaceMatches.length > 1) {
217
- throw new Error(`Multiple interfaces in: ${fileName}`)
218
- }
219
- }
220
- }
221
-
222
- private static async loadContractStr(sourceFile: SourceFile): Promise<string> {
223
- return Common._loadContractStr(sourceFile, [], (code) => Contract.checkCodeType(sourceFile.contractPath, code))
224
- }
225
-
226
- static async fromSource(provider: NodeProvider, path: string): Promise<Contract> {
227
- if (!fs.existsSync(Common._artifactsFolder())) {
228
- fs.mkdirSync(Common._artifactsFolder(), { recursive: true })
229
- }
230
- const sourceFile = this.getSourceFile(path, [])
231
- const contract = await Common._from(
232
- provider,
233
- sourceFile,
234
- (sourceFile) => Contract.loadContractStr(sourceFile),
235
- Contract.compile
236
- )
237
- this._putArtifactToCache(contract)
238
- return contract
239
- }
240
-
241
- private static async compile(
242
- provider: NodeProvider,
243
- sourceFile: SourceFile,
244
- contractStr: string,
245
- contractHash: string
246
- ): Promise<Contract> {
247
- const compiled = await provider.contracts.postContractsCompileContract({ code: contractStr })
248
- const artifact = new Contract(
249
- contractHash,
250
- compiled.bytecode,
251
- compiled.codeHash,
252
- compiled.fields,
253
- compiled.events,
254
- compiled.functions
255
- )
256
- await artifact._saveToFile(sourceFile)
257
- return artifact
258
- }
259
-
260
545
  // TODO: safely parse json
261
546
  static fromJson(artifact: any): Contract {
262
547
  if (
263
- artifact.sourceCodeSha256 == null ||
548
+ artifact.name == null ||
264
549
  artifact.bytecode == null ||
265
550
  artifact.codeHash == null ||
266
551
  artifact.fieldsSig == null ||
@@ -270,44 +555,44 @@ export class Contract extends Common {
270
555
  throw Error('The artifact JSON for contract is incomplete')
271
556
  }
272
557
  const contract = new Contract(
273
- artifact.sourceCodeSha256,
558
+ artifact.name,
274
559
  artifact.bytecode,
275
560
  artifact.codeHash,
276
561
  artifact.fieldsSig,
277
562
  artifact.eventsSig,
278
563
  artifact.functions
279
564
  )
280
- this._putArtifactToCache(contract)
281
565
  return contract
282
566
  }
283
567
 
568
+ static fromCompileResult(result: node.CompileContractResult): Contract {
569
+ return new Contract(result.name, result.bytecode, result.codeHash, result.fields, result.events, result.functions)
570
+ }
571
+
284
572
  // support both 'code.ral' and 'code.ral.json'
285
573
  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)
574
+ const content = await fsPromises.readFile(path)
289
575
  const artifact = JSON.parse(content.toString())
290
576
  return Contract.fromJson(artifact)
291
577
  }
292
578
 
293
- async fetchState(provider: NodeProvider, address: string, group: number): Promise<ContractState> {
294
- const state = await provider.contracts.getContractsAddressState(address, { group: group })
579
+ async fetchState(address: string, group: number): Promise<ContractState> {
580
+ const state = await Project.currentProject.nodeProvider.contracts.getContractsAddressState(address, {
581
+ group: group
582
+ })
295
583
  return this.fromApiContractState(state)
296
584
  }
297
585
 
298
586
  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
- )
587
+ const object = {
588
+ name: this.name,
589
+ bytecode: this.bytecode,
590
+ codeHash: this.codeHash,
591
+ fieldsSig: this.fieldsSig,
592
+ eventsSig: this.eventsSig,
593
+ functions: this.functions
594
+ }
595
+ return JSON.stringify(object, null, 2)
311
596
  }
312
597
 
313
598
  toState(fields: Fields, asset: Asset, address?: string): ContractState {
@@ -323,25 +608,26 @@ export class Contract extends Common {
323
608
  }
324
609
  }
325
610
 
611
+ // no need to be cryptographically strong random
326
612
  static randomAddress(): string {
327
- const bytes = crypto.randomBytes(33)
613
+ const bytes = new Uint8Array(33)
614
+ crypto.getRandomValues(bytes)
328
615
  bytes[0] = 3
329
616
  return bs58.encode(bytes)
330
617
  }
331
618
 
332
619
  private async _test(
333
- provider: NodeProvider,
334
620
  funcName: string,
335
621
  params: TestContractParams,
336
622
  expectPublic: boolean,
337
623
  accessType: string
338
624
  ): Promise<TestContractResult> {
339
625
  const apiParams: node.TestContract = this.toTestContract(funcName, params)
340
- const apiResult = await provider.contracts.postContractsTestContract(apiParams)
626
+ const apiResult = await Project.currentProject.nodeProvider.contracts.postContractsTestContract(apiParams)
341
627
 
342
628
  const methodIndex =
343
629
  typeof params.testMethodIndex !== 'undefined' ? params.testMethodIndex : this.getMethodIndex(funcName)
344
- const isPublic = this.functions[`${methodIndex}`].signature.indexOf('pub ') !== -1
630
+ const isPublic = this.functions[`${methodIndex}`].isPublic
345
631
  if (isPublic === expectPublic) {
346
632
  const result = await this.fromTestContractResult(methodIndex, apiResult)
347
633
  return result
@@ -350,20 +636,12 @@ export class Contract extends Common {
350
636
  }
351
637
  }
352
638
 
353
- async testPublicMethod(
354
- provider: NodeProvider,
355
- funcName: string,
356
- params: TestContractParams
357
- ): Promise<TestContractResult> {
358
- return this._test(provider, funcName, params, true, 'public')
639
+ async testPublicMethod(funcName: string, params: TestContractParams): Promise<TestContractResult> {
640
+ return this._test(funcName, params, true, 'public')
359
641
  }
360
642
 
361
- async testPrivateMethod(
362
- provider: NodeProvider,
363
- funcName: string,
364
- params: TestContractParams
365
- ): Promise<TestContractResult> {
366
- return this._test(provider, funcName, params, false, 'private')
643
+ async testPrivateMethod(funcName: string, params: TestContractParams): Promise<TestContractResult> {
644
+ return this._test(funcName, params, false, 'private')
367
645
  }
368
646
 
369
647
  toApiFields(fields?: Fields): node.Val[] {
@@ -409,33 +687,8 @@ export class Contract extends Common {
409
687
  }
410
688
  }
411
689
 
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
690
  async fromApiContractState(state: node.ContractState): Promise<ContractState> {
438
- const contract = await Contract.fromCodeHash(state.codeHash)
691
+ const contract = Project.currentProject.contractByCodeHash(state.codeHash)
439
692
  return {
440
693
  address: state.address,
441
694
  contractId: binToHex(contractIdFromAddress(state.address)),
@@ -443,21 +696,19 @@ export class Contract extends Common {
443
696
  initialStateHash: state.initialStateHash,
444
697
  codeHash: state.codeHash,
445
698
  fields: fromApiFields(state.fields, contract.fieldsSig),
446
- fieldsSig: await Contract.getFieldsSig(state),
699
+ fieldsSig: contract.fieldsSig,
447
700
  asset: fromApiAsset(state.asset)
448
701
  }
449
702
  }
450
703
 
451
- static ContractCreatedEvent: node.EventSig = {
704
+ static ContractCreatedEvent: EventSig = {
452
705
  name: 'ContractCreated',
453
- signature: 'event ContractCreated(address:Address)',
454
706
  fieldNames: ['address'],
455
707
  fieldTypes: ['Address']
456
708
  }
457
709
 
458
- static ContractDestroyedEvent: node.EventSig = {
710
+ static ContractDestroyedEvent: EventSig = {
459
711
  name: 'ContractDestroyed',
460
- signature: 'event ContractDestroyed(address:Address)',
461
712
  fieldNames: ['address'],
462
713
  fieldTypes: ['Address']
463
714
  }
@@ -466,14 +717,14 @@ export class Contract extends Common {
466
717
  event: node.ContractEventByTxId,
467
718
  codeHash: string | undefined
468
719
  ): Promise<ContractEventByTxId> {
469
- let eventSig: node.EventSig
720
+ let eventSig: EventSig
470
721
 
471
722
  if (event.eventIndex == -1) {
472
723
  eventSig = this.ContractCreatedEvent
473
724
  } else if (event.eventIndex == -2) {
474
725
  eventSig = this.ContractDestroyedEvent
475
726
  } else {
476
- const contract = await Contract.fromCodeHash(codeHash!)
727
+ const contract = Project.currentProject.contractByCodeHash(codeHash!)
477
728
  eventSig = contract.eventsSig[event.eventIndex]
478
729
  }
479
730
 
@@ -517,7 +768,7 @@ export class Contract extends Common {
517
768
  bytecode: bytecode,
518
769
  initialAttoAlphAmount: extractOptionalNumber256(params.initialAttoAlphAmount),
519
770
  issueTokenAmount: extractOptionalNumber256(params.issueTokenAmount),
520
- initialTokenAmounts: params.initialTokenAmounts?.map(toApiToken),
771
+ initialTokenAmounts: toApiTokens(params.initialTokenAmounts),
521
772
  gasAmount: params.gasAmount,
522
773
  gasPrice: extractOptionalNumber256(params.gasPrice)
523
774
  }
@@ -541,85 +792,47 @@ export class Contract extends Common {
541
792
  }
542
793
  }
543
794
 
544
- export class Script extends Common {
795
+ export class Script extends Artifact {
545
796
  readonly bytecodeTemplate: string
546
- readonly fieldsSig: node.FieldsSig
797
+ readonly fieldsSig: FieldsSig
547
798
 
548
- constructor(
549
- sourceCodeSha256: string,
550
- bytecodeTemplate: string,
551
- fieldsSig: node.FieldsSig,
552
- functions: node.FunctionSig[]
553
- ) {
554
- super(sourceCodeSha256, functions)
799
+ constructor(name: string, bytecodeTemplate: string, fieldsSig: FieldsSig, functions: FunctionSig[]) {
800
+ super(name, functions)
555
801
  this.bytecodeTemplate = bytecodeTemplate
556
802
  this.fieldsSig = fieldsSig
557
803
  }
558
804
 
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
805
+ static fromCompileResult(result: node.CompileScriptResult): Script {
806
+ return new Script(result.name, result.bytecodeTemplate, result.fields, result.functions)
589
807
  }
590
808
 
591
809
  // TODO: safely parse json
592
810
  static fromJson(artifact: any): Script {
593
811
  if (
594
- artifact.sourceCodeSha256 == null ||
812
+ artifact.name == null ||
595
813
  artifact.bytecodeTemplate == null ||
596
814
  artifact.fieldsSig == null ||
597
815
  artifact.functions == null
598
816
  ) {
599
817
  throw Error('The artifact JSON for script is incomplete')
600
818
  }
601
- return new Script(artifact.sourceCodeSha256, artifact.bytecodeTemplate, artifact.fieldsSig, artifact.functions)
819
+ return new Script(artifact.name, artifact.bytecodeTemplate, artifact.fieldsSig, artifact.functions)
602
820
  }
603
821
 
604
822
  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)
823
+ const content = await fsPromises.readFile(path)
608
824
  const artifact = JSON.parse(content.toString())
609
825
  return this.fromJson(artifact)
610
826
  }
611
827
 
612
828
  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
- )
829
+ const object = {
830
+ name: this.name,
831
+ bytecodeTemplate: this.bytecodeTemplate,
832
+ fieldsSig: this.fieldsSig,
833
+ functions: this.functions
834
+ }
835
+ return JSON.stringify(object, null, 2)
623
836
  }
624
837
 
625
838
  async paramsForDeployment(params: BuildExecuteScriptTx): Promise<SignExecuteScriptTxParams> {
@@ -627,7 +840,7 @@ export class Script extends Common {
627
840
  signerAddress: params.signerAddress,
628
841
  bytecode: this.buildByteCodeToDeploy(params.initialFields ? params.initialFields : {}),
629
842
  attoAlphAmount: extractOptionalNumber256(params.attoAlphAmount),
630
- tokens: typeof params.tokens !== 'undefined' ? params.tokens.map(toApiToken) : undefined,
843
+ tokens: toApiTokens(params.tokens),
631
844
  gasAmount: params.gasAmount,
632
845
  gasPrice: extractOptionalNumber256(params.gasPrice)
633
846
  }
@@ -650,162 +863,8 @@ export class Script extends Common {
650
863
  }
651
864
  }
652
865
 
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
866
  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
- }
867
+ return typeof v !== 'undefined' ? toApiNumber256(v) : undefined
809
868
  }
810
869
 
811
870
  function fromApiFields(vals: node.Val[], fieldsSig: node.FieldsSig): Fields {
@@ -816,70 +875,22 @@ function fromApiEventFields(vals: node.Val[], eventSig: node.EventSig): Fields {
816
875
  return fromApiVals(vals, eventSig.fieldNames, eventSig.fieldTypes)
817
876
  }
818
877
 
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
878
  export interface Asset {
855
879
  alphAmount: Number256
856
880
  tokens?: Token[]
857
881
  }
858
882
 
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
883
  function toApiAsset(asset: Asset): node.AssetState {
873
884
  return {
874
- attoAlphAmount: extractNumber256(asset.alphAmount),
885
+ attoAlphAmount: toApiNumber256(asset.alphAmount),
875
886
  tokens: typeof asset.tokens !== 'undefined' ? asset.tokens.map(toApiToken) : []
876
887
  }
877
888
  }
878
889
 
879
890
  function fromApiAsset(asset: node.AssetState): Asset {
880
891
  return {
881
- alphAmount: decodeNumber256(asset.attoAlphAmount),
882
- tokens: typeof asset.tokens !== 'undefined' ? asset.tokens.map(fromApiToken) : undefined
892
+ alphAmount: fromApiNumber256(asset.attoAlphAmount),
893
+ tokens: fromApiTokens(asset.tokens)
883
894
  }
884
895
  }
885
896
 
@@ -895,7 +906,7 @@ export interface ContractState {
895
906
  initialStateHash?: string
896
907
  codeHash: string
897
908
  fields: Fields
898
- fieldsSig: node.FieldsSig
909
+ fieldsSig: FieldsSig
899
910
  asset: Asset
900
911
  }
901
912
 
@@ -918,12 +929,12 @@ function toApiContractState(state: ContractState): node.ContractState {
918
929
  }
919
930
  }
920
931
 
921
- function toApiFields(fields: Fields, fieldsSig: node.FieldsSig): node.Val[] {
932
+ function toApiFields(fields: Fields, fieldsSig: FieldsSig): node.Val[] {
922
933
  return toApiVals(fields, fieldsSig.names, fieldsSig.types)
923
934
  }
924
935
 
925
- function toApiArgs(args: Arguments, funcSig: node.FunctionSig): node.Val[] {
926
- return toApiVals(args, funcSig.argNames, funcSig.argTypes)
936
+ function toApiArgs(args: Arguments, funcSig: FunctionSig): node.Val[] {
937
+ return toApiVals(args, funcSig.paramNames, funcSig.paramTypes)
927
938
  }
928
939
 
929
940
  function toApiVals(fields: Fields, names: string[], types: string[]): node.Val[] {
@@ -987,7 +998,7 @@ export interface ContractOutput {
987
998
  type: string
988
999
  address: string
989
1000
  alphAmount: Number256
990
- tokens: Token[]
1001
+ tokens?: Token[]
991
1002
  }
992
1003
 
993
1004
  function fromApiOutput(output: node.Output): Output {
@@ -996,8 +1007,8 @@ function fromApiOutput(output: node.Output): Output {
996
1007
  return {
997
1008
  type: 'AssetOutput',
998
1009
  address: asset.address,
999
- alphAmount: decodeNumber256(asset.attoAlphAmount),
1000
- tokens: asset.tokens.map(fromApiToken),
1010
+ alphAmount: fromApiNumber256(asset.attoAlphAmount),
1011
+ tokens: fromApiTokens(asset.tokens),
1001
1012
  lockTime: asset.lockTime,
1002
1013
  message: asset.message
1003
1014
  }
@@ -1006,8 +1017,8 @@ function fromApiOutput(output: node.Output): Output {
1006
1017
  return {
1007
1018
  type: 'ContractOutput',
1008
1019
  address: asset.address,
1009
- alphAmount: decodeNumber256(asset.attoAlphAmount),
1010
- tokens: asset.tokens.map(fromApiToken)
1020
+ alphAmount: fromApiNumber256(asset.attoAlphAmount),
1021
+ tokens: fromApiTokens(asset.tokens)
1011
1022
  }
1012
1023
  } else {
1013
1024
  throw new Error(`Unknown output type: ${output}`)