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