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