@alephium/web3 0.5.0-rc.2 → 0.5.0-rc.3

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.
@@ -0,0 +1,362 @@
1
+ /*
2
+ Copyright 2018 - 2022 The Alephium Authors
3
+ This file is part of the alephium project.
4
+
5
+ The library is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Lesser General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ The library is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Lesser General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Lesser General Public License
16
+ along with the library. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ import * as prettier from 'prettier'
20
+ import path from 'path'
21
+ import fs from 'fs'
22
+ import { Contract, EventSig, FunctionSig, Project, Script } from './contract'
23
+
24
+ const header = `/* Autogenerated file. Do not edit manually. */\n/* tslint:disable */\n/* eslint-disable */\n\n`
25
+
26
+ function array(str: string, size: number): string {
27
+ const result = Array(size).fill(str).join(', ')
28
+ return `[${result}]`
29
+ }
30
+
31
+ function parseArrayType(tpe: string): string {
32
+ const ignored = '[;]'
33
+ const tokens: string[] = []
34
+ let acc = ''
35
+ for (let index = 0; index < tpe.length; index++) {
36
+ if (!ignored.includes(tpe.charAt(index))) {
37
+ acc = acc + tpe.charAt(index)
38
+ } else if (acc !== '') {
39
+ tokens.push(acc)
40
+ acc = ''
41
+ }
42
+ }
43
+ const baseTsType = toTsType(tokens[0])
44
+ const sizes = tokens.slice(1).map((str) => parseInt(str))
45
+ return sizes.reduce((acc, size) => array(acc, size), baseTsType)
46
+ }
47
+
48
+ function toTsType(ralphType: string): string {
49
+ switch (ralphType) {
50
+ case 'U256':
51
+ case 'I256':
52
+ return 'bigint'
53
+ case 'Bool':
54
+ return 'boolean'
55
+ case 'Address':
56
+ case 'ByteVec':
57
+ return 'HexString'
58
+ default: // array type
59
+ return parseArrayType(ralphType)
60
+ }
61
+ }
62
+
63
+ function formatParameters(fieldsSig: { names: string[]; types: string[] }): string {
64
+ return fieldsSig.names.map((name, idx) => `${name}: ${toTsType(fieldsSig.types[`${idx}`])}`).join(', ')
65
+ }
66
+
67
+ function genCallMethod(contractName: string, functionSig: FunctionSig): string {
68
+ if (!functionSig.isPublic || functionSig.returnTypes.length === 0) {
69
+ return ''
70
+ }
71
+ const funcName = functionSig.name.charAt(0).toUpperCase() + functionSig.name.slice(1)
72
+ const funcHasArgs = functionSig.paramNames.length > 0
73
+ const params = funcHasArgs
74
+ ? `params: CallContractParams<{${formatParameters({
75
+ names: functionSig.paramNames,
76
+ types: functionSig.paramTypes
77
+ })}}>`
78
+ : `params?: Omit<CallContractParams<{}>, 'args'>`
79
+ const tsReturnTypes = functionSig.returnTypes.map((tpe) => toTsType(tpe))
80
+ const retType =
81
+ tsReturnTypes.length === 0
82
+ ? `CallContractResult<null>`
83
+ : tsReturnTypes.length === 1
84
+ ? `CallContractResult<${tsReturnTypes[0]}>`
85
+ : `CallContractResult<[${tsReturnTypes.join(', ')}]>`
86
+ const callParams = funcHasArgs ? 'params' : 'params === undefined ? {} : params'
87
+ return `
88
+ async call${funcName}Method(${params}): Promise<${retType}> {
89
+ return callMethod(${contractName}, this, "${functionSig.name}", ${callParams})
90
+ }
91
+ `
92
+ }
93
+
94
+ function getInstanceName(contract: Contract): string {
95
+ return `${contract.name}Instance`
96
+ }
97
+
98
+ function genAttach(instanceName: string): string {
99
+ return `
100
+ at(address: string): ${instanceName} {
101
+ return new ${instanceName}(address)
102
+ }
103
+ `
104
+ }
105
+
106
+ function contractTypes(contractName: string): string {
107
+ return `${contractName}Types`
108
+ }
109
+
110
+ function contractFieldType(contract: Contract): string {
111
+ const hasFields = contract.fieldsSig.names.length > 0
112
+ return hasFields ? `${contractTypes(contract.name)}.Fields` : '{}'
113
+ }
114
+
115
+ function genFetchState(contract: Contract): string {
116
+ return `
117
+ async fetchState(): Promise<${contractTypes(contract.name)}.State> {
118
+ return fetchContractState(${contract.name}, this)
119
+ }
120
+ `
121
+ }
122
+
123
+ function getEventType(event: EventSig): string {
124
+ return event.name + 'Event'
125
+ }
126
+
127
+ function genEventType(event: EventSig): string {
128
+ if (event.fieldNames.length === 0) {
129
+ return `export type ${getEventType(event)} = Omit<ContractEvent, 'fields'>`
130
+ }
131
+ const fieldsType = `{${formatParameters({ names: event.fieldNames, types: event.fieldTypes })}}`
132
+ return `export type ${getEventType(event)} = ContractEvent<${fieldsType}>`
133
+ }
134
+
135
+ function genSubscribeSystemEvent(event: SystemEvent): string {
136
+ return `
137
+ subscribe${event.eventSig.name}Event(options: SubscribeOptions<${event.eventType}>, fromCount?: number): EventSubscription {
138
+ return subscribe${event.eventSig.name}Event(this, options, fromCount)
139
+ }
140
+ `
141
+ }
142
+
143
+ function genSubscribeEvent(contractName: string, event: EventSig): string {
144
+ const eventType = getEventType(event)
145
+ const scopedEventType = `${contractTypes(contractName)}.${eventType}`
146
+ return `
147
+ subscribe${eventType}(options: SubscribeOptions<${scopedEventType}>, fromCount?: number): EventSubscription {
148
+ return subscribeContractEvent(${contractName}.contract, this, options, "${event.name}", fromCount)
149
+ }
150
+ `
151
+ }
152
+
153
+ function genSubscribeAllEvents(contract: Contract, systemEvents: SystemEvent[]): string {
154
+ const contractEventTypes = contract.eventsSig.map((e) => `${contractTypes(contract.name)}.${getEventType(e)}`)
155
+ const systemEventTypes = systemEvents.map((e) => e.eventType)
156
+ const eventTypes = contractEventTypes.concat(systemEventTypes).join(' | ')
157
+ return `
158
+ subscribeAllEvents(options: SubscribeOptions<${eventTypes}>, fromCount?: number): EventSubscription {
159
+ return subscribeAllEvents(${contract.name}.contract, this, options, fromCount)
160
+ }
161
+ `
162
+ }
163
+
164
+ function genContractStateType(contract: Contract): string {
165
+ if (contract.fieldsSig.names.length === 0) {
166
+ return `export type State = Omit<ContractState<any>, 'fields'>`
167
+ }
168
+ return `
169
+ export type Fields = {
170
+ ${formatParameters(contract.fieldsSig)}
171
+ }
172
+
173
+ export type State = ContractState<Fields>
174
+ `
175
+ }
176
+
177
+ function genTestMethod(contract: Contract, functionSig: FunctionSig): string {
178
+ const funcName = functionSig.name.charAt(0).toUpperCase() + functionSig.name.slice(1)
179
+ const funcHasArgs = functionSig.paramNames.length > 0
180
+ const contractHasFields = contract.fieldsSig.names.length > 0
181
+ const argsType = funcHasArgs
182
+ ? `{${formatParameters({ names: functionSig.paramNames, types: functionSig.paramTypes })}}`
183
+ : 'never'
184
+ const fieldsType = contractHasFields ? `${contractFieldType(contract)}` : 'never'
185
+ const params =
186
+ funcHasArgs && contractHasFields
187
+ ? `params: TestContractParams<${fieldsType}, ${argsType}>`
188
+ : funcHasArgs
189
+ ? `params: Omit<TestContractParams<${fieldsType}, ${argsType}>, 'initialFields'>`
190
+ : contractHasFields
191
+ ? `params: Omit<TestContractParams<${fieldsType}, ${argsType}>, 'testArgs'>`
192
+ : `params?: Omit<TestContractParams<${fieldsType}, ${argsType}>, 'testArgs' | 'initialFields'>`
193
+ const tsReturnTypes = functionSig.returnTypes.map((tpe) => toTsType(tpe))
194
+ const retType =
195
+ tsReturnTypes.length === 0
196
+ ? `TestContractResult<null>`
197
+ : tsReturnTypes.length === 1
198
+ ? `TestContractResult<${tsReturnTypes[0]}>`
199
+ : `TestContractResult<[${tsReturnTypes.join(', ')}]>`
200
+ const callParams = funcHasArgs || contractHasFields ? 'params' : 'params === undefined ? {} : params'
201
+ return `
202
+ async test${funcName}Method(${params}): Promise<${retType}> {
203
+ return testMethod(this, "${functionSig.name}", ${callParams})
204
+ }
205
+ `
206
+ }
207
+
208
+ type SystemEvent = {
209
+ eventSig: EventSig
210
+ eventIndex: number
211
+ eventType: string
212
+ }
213
+
214
+ function genContract(project: Project, contract: Contract, artifactRelativePath: string): string {
215
+ const contractInfo = project.projectArtifact.infos.get(contract.name)
216
+ if (contractInfo === undefined) {
217
+ throw new Error(`Contract info does not exist: ${contract.name}`)
218
+ }
219
+ const systemEvents: SystemEvent[] = [
220
+ {
221
+ eventSig: Contract.ContractCreatedEvent,
222
+ eventIndex: Contract.ContractCreatedEventIndex,
223
+ eventType: 'ContractCreatedEvent'
224
+ },
225
+ {
226
+ eventSig: Contract.ContractDestroyedEvent,
227
+ eventIndex: Contract.ContractDestroyedEventIndex,
228
+ eventType: 'ContractDestroyedEvent'
229
+ }
230
+ ]
231
+ const source = `
232
+ ${header}
233
+
234
+ import {
235
+ Address, Contract, ContractState, TestContractResult, HexString, ContractFactory,
236
+ SubscribeOptions, EventSubscription, CallContractParams, CallContractResult,
237
+ TestContractParams, ContractEvent, subscribeContractCreatedEvent, subscribeContractDestroyedEvent, subscribeContractEvent, subscribeAllEvents, testMethod, callMethod, fetchContractState,
238
+ ContractCreatedEvent, ContractDestroyedEvent, ContractInstance
239
+ } from '@alephium/web3'
240
+ import { default as ${contract.name}ContractJson } from '../${artifactRelativePath}'
241
+
242
+ // Custom types for the contract
243
+ export namespace ${contract.name}Types {
244
+ ${genContractStateType(contract)}
245
+ ${contract.eventsSig.map((e) => genEventType(e)).join('\n')}
246
+ }
247
+
248
+ class Factory extends ContractFactory<${contract.name}Instance, ${contractFieldType(contract)}> {
249
+ ${genAttach(getInstanceName(contract))}
250
+ ${contract.functions.map((f) => genTestMethod(contract, f)).join('\n')}
251
+ }
252
+
253
+ // Use this object to test and deploy the contract
254
+ export const ${contract.name} = new Factory(Contract.fromJson(
255
+ ${contract.name}ContractJson,
256
+ '${contractInfo.bytecodeDebugPatch}',
257
+ '${contractInfo.codeHashDebug}',
258
+ ))
259
+
260
+ // Use this class to interact with the blockchain
261
+ export class ${contract.name}Instance extends ContractInstance {
262
+ constructor(address: Address) {
263
+ super(address)
264
+ }
265
+
266
+ ${genFetchState(contract)}
267
+ ${systemEvents.map((e) => genSubscribeSystemEvent(e)).join('\n')}
268
+ ${contract.eventsSig.map((e) => genSubscribeEvent(contract.name, e)).join('\n')}
269
+ ${genSubscribeAllEvents(contract, systemEvents)}
270
+ ${contract.functions.map((f) => genCallMethod(contract.name, f)).join('\n')}
271
+ }
272
+ `
273
+ return prettier.format(source, { parser: 'typescript' })
274
+ }
275
+
276
+ function genScript(script: Script): string {
277
+ console.log(`Generating code for script ${script.name}`)
278
+ const usePreapprovedAssets = script.functions[0].usePreapprovedAssets
279
+ const fieldsType = script.fieldsSig.names.length > 0 ? `{${formatParameters(script.fieldsSig)}}` : '{}'
280
+ const paramsType = usePreapprovedAssets
281
+ ? `ExecuteScriptParams<${fieldsType}>`
282
+ : `Omit<ExecuteScriptParams<${fieldsType}>, 'attoAlphAmount' | 'tokens'>`
283
+ return `
284
+ export namespace ${script.name} {
285
+ export async function execute(signer: SignerProvider, params: ${paramsType}): Promise<ExecuteScriptResult> {
286
+ const signerParams = await script.txParamsForExecution(signer, params)
287
+ return await signer.signAndSubmitExecuteScriptTx(signerParams)
288
+ }
289
+
290
+ export const script = Script.fromJson(${script.name}ScriptJson)
291
+ }
292
+ `
293
+ }
294
+
295
+ function genScripts(project: Project, outDir: string, exports: string[]) {
296
+ const artifactDir = project.artifactsRootDir
297
+ exports.push('./scripts')
298
+ const scriptPath = path.join(outDir, 'scripts.ts')
299
+ const scripts = Array.from(project.scripts.values())
300
+ const importArtifacts = Array.from(scripts)
301
+ .map((s) => {
302
+ const artifactPath = s.sourceInfo.getArtifactPath(artifactDir)
303
+ const artifactRelativePath = path.relative(artifactDir, artifactPath)
304
+ return `import { default as ${s.artifact.name}ScriptJson } from '../${artifactRelativePath}'`
305
+ })
306
+ .join('\n')
307
+ const scriptsSource = scripts.map((s) => genScript(s.artifact)).join('\n')
308
+ const source = `
309
+ ${header}
310
+
311
+ import {
312
+ ExecuteScriptParams,
313
+ ExecuteScriptResult,
314
+ Script,
315
+ SignerProvider,
316
+ HexString
317
+ } from '@alephium/web3'
318
+ ${importArtifacts}
319
+
320
+ ${scriptsSource}
321
+ `
322
+ const formatted = prettier.format(source, { parser: 'typescript' })
323
+ fs.writeFileSync(scriptPath, formatted, 'utf8')
324
+ }
325
+
326
+ function genIndexTs(outDir: string, exports: string[]) {
327
+ const indexPath = path.join(outDir, 'index.ts')
328
+ const exportStatements = exports.map((e) => `export * from "${e}"`).join('\n')
329
+ const source = prettier.format(header + exportStatements, { parser: 'typescript' })
330
+ fs.writeFileSync(indexPath, source, 'utf8')
331
+ }
332
+
333
+ function genContracts(project: Project, outDir: string, exports: string[]) {
334
+ const artifactDir = project.artifactsRootDir
335
+ Array.from(project.contracts.values()).forEach((c) => {
336
+ console.log(`Generating code for contract ${c.artifact.name}`)
337
+ exports.push(`./${c.artifact.name}`)
338
+ const filename = `${c.artifact.name}.ts`
339
+ const sourcePath = path.join(outDir, filename)
340
+ const artifactPath = c.sourceInfo.getArtifactPath(artifactDir)
341
+ const artifactRelativePath = path.relative(artifactDir, artifactPath)
342
+ const sourceCode = genContract(project, c.artifact, artifactRelativePath)
343
+ fs.writeFileSync(sourcePath, sourceCode, 'utf8')
344
+ })
345
+ }
346
+
347
+ export function codegen(project: Project) {
348
+ const outDirTemp = path.join(project.artifactsRootDir, 'ts')
349
+ const outDir = path.isAbsolute(outDirTemp) ? outDirTemp : path.resolve(outDirTemp)
350
+ if (!fs.existsSync(outDir)) {
351
+ fs.mkdirSync(outDir, { recursive: true })
352
+ }
353
+
354
+ const exports: string[] = []
355
+ try {
356
+ genContracts(project, outDir, exports)
357
+ genScripts(project, outDir, exports)
358
+ genIndexTs(outDir, exports)
359
+ } catch (error) {
360
+ console.log(`Failed to generate code: ${error}`)
361
+ }
362
+ }
@@ -59,6 +59,7 @@ import { getCurrentNodeProvider } from '../global'
59
59
  import * as path from 'path'
60
60
  import { EventSubscription, subscribeToEvents } from './events'
61
61
  import { ONE_ALPH } from '../constants'
62
+ import { codegen } from './codegen'
62
63
 
63
64
  export type FieldsSig = node.FieldsSig
64
65
  export type EventSig = node.EventSig
@@ -429,6 +430,7 @@ export class Project {
429
430
  projectArtifact
430
431
  )
431
432
  await project.saveArtifactsToFile(projectRootDir)
433
+ codegen(project)
432
434
  return project
433
435
  }
434
436