@typemove/move 1.0.0-rc.4

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,394 @@
1
+ import {
2
+ accountAddressString,
3
+ moduleQname,
4
+ SPLITTER,
5
+ VECTOR_STR,
6
+ } from './utils.js'
7
+ import {
8
+ DecodedStruct,
9
+ matchType,
10
+ parseMoveType,
11
+ TypeDescriptor,
12
+ } from './types.js'
13
+ import {
14
+ InternalMoveFunction,
15
+ InternalMoveModule,
16
+ InternalMoveStruct,
17
+ } from './internal-models.js'
18
+ // import { bytesToBigInt } from '../utils/index.js'
19
+ import { ChainAdapter } from './chain-adapter.js'
20
+
21
+ export abstract class AbstractMoveCoder<Network, ModuleType, StructType> {
22
+ protected moduleMapping = new Map<string, InternalMoveModule>()
23
+ private typeMapping = new Map<string, InternalMoveStruct>()
24
+ private funcMapping = new Map<string, InternalMoveFunction>()
25
+ network: Network
26
+ adapter: ChainAdapter<Network, ModuleType, StructType>
27
+
28
+ protected constructor(network: Network) {
29
+ this.network = network
30
+ }
31
+
32
+ contains(account: string, name: string) {
33
+ return this.moduleMapping.has(moduleQname({ address: account, name }))
34
+ }
35
+
36
+ abstract load(module: ModuleType): InternalMoveModule
37
+
38
+ protected loadInternal(module: InternalMoveModule) {
39
+ const account = accountAddressString(module.address)
40
+ if (this.contains(account, module.name)) {
41
+ return
42
+ }
43
+ this.moduleMapping.set(
44
+ moduleQname({ address: account, name: module.name }),
45
+ module
46
+ )
47
+
48
+ for (const struct of module.structs) {
49
+ // TODO move to util
50
+ const key = [account, module.name, struct.name].join(SPLITTER)
51
+ this.typeMapping.set(key, struct)
52
+ }
53
+
54
+ for (const func of module.exposedFunctions) {
55
+ // if (!func.isEntry) {
56
+ // continue
57
+ // }
58
+ const key = [account, module.name, func.name].join(SPLITTER)
59
+ this.funcMapping.set(key, func)
60
+ }
61
+ }
62
+
63
+ protected decodeBigInt(data: any): bigint {
64
+ if (Array.isArray(data)) {
65
+ throw new Error('data is in byte array')
66
+ // Only sui function need this, strange
67
+ // const bytes = data as number[]
68
+ // return bytesToBigInt(new Uint8Array(bytes.slice().reverse()))
69
+ }
70
+
71
+ return BigInt(data)
72
+ }
73
+
74
+ protected encodeBigInt(data: bigint): any {
75
+ return '0x' + data.toString(16)
76
+ }
77
+
78
+ private requestMap = new Map<string, Promise<InternalMoveModule>>()
79
+
80
+ async getMoveStruct(type: string): Promise<InternalMoveStruct> {
81
+ const [account_, module, typeName] = type.split(SPLITTER)
82
+ const account = accountAddressString(account_)
83
+ type = [account, module, typeName].join(SPLITTER)
84
+
85
+ let struct = this.typeMapping.get(type)
86
+ if (struct) {
87
+ return struct
88
+ }
89
+ const key = account + SPLITTER + module
90
+ let resp = this.requestMap.get(account + SPLITTER + module)
91
+ if (!resp) {
92
+ resp = this.adapter
93
+ .fetchModule(account, module, this.network)
94
+ .then((m) => {
95
+ return this.load(m)
96
+ })
97
+ this.requestMap.set(key, resp)
98
+ }
99
+ await resp
100
+ struct = this.typeMapping.get(type)
101
+ if (struct) {
102
+ return struct
103
+ }
104
+ throw new Error(
105
+ 'Failed to load function ' + type + ' type are not imported anywhere'
106
+ )
107
+ }
108
+
109
+ async getMoveFunction(type: string): Promise<InternalMoveFunction> {
110
+ const [account_, module, typeName] = type.split(SPLITTER)
111
+ const account = accountAddressString(account_)
112
+ type = [account, module, typeName].join(SPLITTER)
113
+
114
+ let func = this.funcMapping.get(type)
115
+ if (func) {
116
+ return func
117
+ }
118
+ const key = account + SPLITTER + module
119
+ let resp = this.requestMap.get(account + SPLITTER + module)
120
+ if (!resp) {
121
+ resp = this.adapter
122
+ .fetchModule(account, module, this.network)
123
+ .then((m) => {
124
+ return this.load(m)
125
+ })
126
+ this.requestMap.set(key, resp)
127
+ }
128
+ await resp
129
+ func = this.funcMapping.get(type)
130
+ if (func) {
131
+ return func
132
+ }
133
+ throw new Error(
134
+ 'Failed to load function ' + type + ' type are not imported anywhere'
135
+ )
136
+ }
137
+
138
+ protected async decode<T>(data: any, type: TypeDescriptor<T>): Promise<T> {
139
+ // process simple type
140
+ if (type.reference) {
141
+ return data
142
+ }
143
+ switch (type.qname) {
144
+ case 'signer': // TODO check this, aptos only
145
+ case 'address':
146
+ case 'Address':
147
+ case '0x1::string::String':
148
+ case 'bool':
149
+ case 'Bool':
150
+ case 'u8':
151
+ case 'U8':
152
+ case 'u16':
153
+ case 'U16':
154
+ case 'u32':
155
+ case 'U32':
156
+ return data
157
+ case 'u64':
158
+ case 'U64':
159
+ case 'u128':
160
+ case 'U128':
161
+ case 'u256':
162
+ case 'U256':
163
+ return this.decodeBigInt(data) as any
164
+ }
165
+
166
+ // process vector
167
+ if (type.qname.toLowerCase() === VECTOR_STR) {
168
+ // vector<u8> as hex string
169
+ if (type.typeArgs[0].qname === 'u8' || type.typeArgs[0].qname === 'U8') {
170
+ return data
171
+ }
172
+
173
+ const res = []
174
+ for (const entry of data) {
175
+ res.push(await this.decode(entry, type.typeArgs[0]))
176
+ }
177
+ return res as any
178
+ }
179
+
180
+ // Process complex type
181
+ const struct = await this.getMoveStruct(type.qname)
182
+
183
+ const typeCtx = new Map<string, TypeDescriptor>()
184
+ for (const [idx, typeArg] of type.typeArgs.entries()) {
185
+ typeCtx.set('T' + idx, typeArg)
186
+ }
187
+
188
+ const typedData: any = {}
189
+
190
+ for (const field of struct.fields) {
191
+ let filedType = field.type
192
+ filedType = filedType.applyTypeArgs(typeCtx)
193
+ const fieldValue = this.adapter.getData(data)[field.name]
194
+ const value = await this.decode(fieldValue, filedType)
195
+ typedData[field.name] = value
196
+ }
197
+ return typedData
198
+ }
199
+
200
+ protected async encode(data: any, type: TypeDescriptor): Promise<any> {
201
+ // process simple type
202
+ if (type.reference) {
203
+ return data
204
+ }
205
+ switch (type.qname) {
206
+ case 'signer': // TODO check this, aptos only
207
+ case 'address':
208
+ case 'Address':
209
+ case '0x2::object::ID':
210
+ case '0x2::coin::Coin':
211
+ case '0x1::string::String':
212
+ case 'bool':
213
+ case 'Bool':
214
+ case 'u8':
215
+ case 'U8':
216
+ case 'u16':
217
+ case 'U16':
218
+ case 'u32':
219
+ case 'U32':
220
+ return data
221
+ case 'u64':
222
+ case 'U64':
223
+ case 'u128':
224
+ case 'U128':
225
+ case 'u256':
226
+ case 'U256':
227
+ return this.encodeBigInt(data)
228
+ }
229
+
230
+ // process vector
231
+ if (type.qname.toLowerCase() === VECTOR_STR) {
232
+ // vector<u8> as hex string
233
+ if (type.typeArgs[0].qname === 'u8' || type.typeArgs[0].qname === 'U8') {
234
+ return data
235
+ }
236
+
237
+ const res = []
238
+ for (const entry of data) {
239
+ res.push(this.encode(entry, type.typeArgs[0]))
240
+ }
241
+ return res
242
+ }
243
+
244
+ // Process complex type
245
+ const struct = await this.getMoveStruct(type.qname)
246
+
247
+ const typeCtx = new Map<string, TypeDescriptor>()
248
+ for (const [idx, typeArg] of type.typeArgs.entries()) {
249
+ typeCtx.set('T' + idx, typeArg)
250
+ }
251
+
252
+ const typedData: any = {}
253
+
254
+ for (const field of struct.fields) {
255
+ let filedType = field.type
256
+ filedType = filedType.applyTypeArgs(typeCtx)
257
+ const value = await this.encode(data[field.name], filedType)
258
+ typedData[field.name] = value
259
+ }
260
+ return typedData
261
+ }
262
+
263
+ async decodeArray(
264
+ entries: any[],
265
+ types: TypeDescriptor[],
266
+ strict = true
267
+ ): Promise<any[]> {
268
+ const entriesDecoded: any[] = []
269
+ for (const [idx, arg] of entries.entries()) {
270
+ // TODO consider apply payload.type_arguments, but this might be hard since we don't code gen for them
271
+ const argType = types[idx]
272
+ try {
273
+ if (!strict && arg === undefined) {
274
+ entriesDecoded.push(arg)
275
+ } else {
276
+ entriesDecoded.push(await this.decode(arg, argType))
277
+ }
278
+ } catch (e) {
279
+ throw Error(
280
+ 'Decoding error for ' +
281
+ JSON.stringify(arg) +
282
+ 'using type' +
283
+ argType +
284
+ e.toString()
285
+ )
286
+ }
287
+ }
288
+ return entriesDecoded
289
+ }
290
+
291
+ async encodeArray(
292
+ entriesDecoded: any[],
293
+ types: TypeDescriptor[]
294
+ ): Promise<any[]> {
295
+ const entries: any[] = []
296
+ for (const [idx, arg] of entriesDecoded.entries()) {
297
+ // TODO consider apply payload.type_arguments, but this might be hard since we don't code gen for them
298
+ const argType = types[idx]
299
+ try {
300
+ entries.push(await this.encode(arg, argType))
301
+ } catch (e) {
302
+ throw Error(
303
+ 'Decoding error for ' +
304
+ JSON.stringify(arg) +
305
+ 'using type' +
306
+ argType +
307
+ e.toString()
308
+ )
309
+ }
310
+ }
311
+ return entries
312
+ }
313
+
314
+ async encodeCallArgs(args: any[], func: string): Promise<any[]> {
315
+ const f = await this.getMoveFunction(func)
316
+ return this.encodeArray(
317
+ args,
318
+ this.adapter.getMeaningfulFunctionParams(f.params)
319
+ )
320
+ }
321
+
322
+ async decodeCallResult(res: any[], func: string): Promise<any[]> {
323
+ const f = await this.getMoveFunction(func)
324
+ return this.decodeArray(res, f.return)
325
+ }
326
+
327
+ async filterAndDecodeStruct<T, ST extends StructType>(
328
+ typeMatcher: TypeDescriptor<T>,
329
+ structsWithTags: ST[]
330
+ ): Promise<DecodedStruct<ST, T>[]> {
331
+ if (!structsWithTags) {
332
+ return [] as any
333
+ }
334
+ // const typeMatcherDescriptor = parseMoveType(typeMatcher)
335
+ const results: DecodedStruct<ST, T>[] = []
336
+ for (const resource of structsWithTags) {
337
+ const resourceType = this.adapter.getType(resource)
338
+ const resourceTypeDescriptor = parseMoveType(resourceType)
339
+ if (!matchType(typeMatcher, resourceTypeDescriptor)) {
340
+ continue
341
+ }
342
+
343
+ const result = await this.decodedStruct<T, ST>(resource)
344
+ if (result) {
345
+ results.push(result)
346
+ } else {
347
+ console.error('decoding error')
348
+ }
349
+ }
350
+ return results
351
+ }
352
+
353
+ protected async decodedStruct<T, ST extends StructType>(
354
+ typeStruct: ST
355
+ ): Promise<DecodedStruct<ST, T> | undefined> {
356
+ const typeDescriptor = parseMoveType(this.adapter.getType(typeStruct))
357
+ const typeArguments = typeDescriptor.typeArgs.map((t) => t.getSignature())
358
+
359
+ let dataTyped = undefined
360
+ try {
361
+ dataTyped = await this.decode(typeStruct, typeDescriptor)
362
+ } catch (e) {
363
+ throw Error(
364
+ 'Decoding error for struct' + JSON.stringify(typeStruct) + e.toString()
365
+ )
366
+ // return undefined
367
+ }
368
+ return {
369
+ ...typeStruct,
370
+ data_decoded: dataTyped,
371
+ type_arguments: typeArguments,
372
+ }
373
+ }
374
+ async decodedType<T, ST>(
375
+ typeStruct: ST,
376
+ type: TypeDescriptor<T>
377
+ ): Promise<T | undefined> {
378
+ if (typeStruct === null || typeStruct == undefined) {
379
+ return typeStruct as any
380
+ }
381
+ if (typeof typeStruct === 'object') {
382
+ if ('type' in typeStruct) {
383
+ const typeInStruct = parseMoveType(
384
+ (typeStruct.type as any).toString() || ''
385
+ )
386
+ if (!matchType(type, typeInStruct)) {
387
+ return undefined
388
+ }
389
+ }
390
+ }
391
+
392
+ return await this.decode(typeStruct, type)
393
+ }
394
+ }
package/src/account.ts ADDED
@@ -0,0 +1,93 @@
1
+ import { moduleQname, moduleQnameForType } from './utils.js'
2
+ import { InternalMoveModule } from './internal-models.js'
3
+
4
+ export class AccountModulesImportInfo {
5
+ // account to module
6
+ imports: Map<string, Set<string>>
7
+ account: string
8
+ moduleName: string
9
+
10
+ constructor(account: string, tsModuleName: string) {
11
+ this.account = account
12
+ this.moduleName = tsModuleName
13
+ this.imports = new Map<string, Set<string>>()
14
+ }
15
+
16
+ addImport(account: string, module: string) {
17
+ if (account === this.account) {
18
+ return
19
+ }
20
+ let accountModules = this.imports.get(account)
21
+ if (!accountModules) {
22
+ accountModules = new Set<string>()
23
+ this.imports.set(account, accountModules)
24
+ }
25
+ accountModules.add(module)
26
+ }
27
+ }
28
+
29
+ export class AccountRegister {
30
+ accountImports = new Map<string, AccountModulesImportInfo>()
31
+ pendingAccounts = new Set<string>()
32
+
33
+ register(
34
+ module: InternalMoveModule,
35
+ tsModuleName: string
36
+ ): AccountModulesImportInfo {
37
+ const currentModuleFqn = moduleQname(module)
38
+
39
+ let accountModuleImports = this.accountImports.get(module.address)
40
+ if (!accountModuleImports) {
41
+ accountModuleImports = new AccountModulesImportInfo(
42
+ module.address,
43
+ tsModuleName
44
+ )
45
+ this.accountImports.set(module.address, accountModuleImports)
46
+ // the account has already be processed, delete pending task
47
+ this.pendingAccounts.delete(module.address)
48
+ }
49
+
50
+ this.registerStruct(module, accountModuleImports)
51
+ this.registerFunctions(module, accountModuleImports)
52
+
53
+ this.accountImports.set(currentModuleFqn, accountModuleImports)
54
+ return accountModuleImports
55
+ }
56
+
57
+ private registerFunctions(
58
+ module: InternalMoveModule,
59
+ accountModuleImports: AccountModulesImportInfo
60
+ ): void {
61
+ for (const func of module.exposedFunctions) {
62
+ if (!func.isEntry) {
63
+ continue
64
+ }
65
+ for (const param of func.params) {
66
+ for (const type of param.dependedTypes()) {
67
+ const [account, module] = moduleQnameForType(type)
68
+ accountModuleImports.addImport(account, module)
69
+ if (!this.accountImports.has(account)) {
70
+ this.pendingAccounts.add(account)
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ private registerStruct(
78
+ module: InternalMoveModule,
79
+ accountModuleImports: AccountModulesImportInfo
80
+ ): void {
81
+ for (const struct of module.structs) {
82
+ for (const field of struct.fields) {
83
+ for (const type of field.type.dependedTypes()) {
84
+ const [account, module] = moduleQnameForType(type)
85
+ accountModuleImports.addImport(account, module)
86
+ if (!this.accountImports.has(account)) {
87
+ this.pendingAccounts.add(account)
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,30 @@
1
+ import { InternalMoveModule, InternalMoveStruct } from './internal-models.js'
2
+ import { TypeDescriptor } from './types.js'
3
+
4
+ export abstract class ChainAdapter<NetworkType, ModuleType, StructType> {
5
+ abstract fetchModule(
6
+ account: string,
7
+ module: string,
8
+ network: NetworkType
9
+ ): Promise<ModuleType>
10
+
11
+ abstract fetchModules(
12
+ account: string,
13
+ network: NetworkType
14
+ ): Promise<ModuleType[]>
15
+ abstract toInternalModules(modules: ModuleType[]): InternalMoveModule[]
16
+
17
+ // Get all structs that represent Events
18
+ abstract getAllEventStructs(
19
+ module: InternalMoveModule[]
20
+ ): Map<string, InternalMoveStruct>
21
+
22
+ // Get the parameters that actually have arguments in runtime
23
+ // Aptos first signer and Sui's last TxContext are no use
24
+ abstract getMeaningfulFunctionParams(
25
+ params: TypeDescriptor[]
26
+ ): TypeDescriptor[]
27
+
28
+ abstract getType(base: StructType): string
29
+ abstract getData<T>(base: StructType): any
30
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './types.js'
2
+ export * from './utils.js'
3
+ export * from './account.js'
4
+ export * from './chain-adapter.js'
@@ -0,0 +1,40 @@
1
+ import { TypeDescriptor } from './types.js'
2
+
3
+ export interface InternalMoveModule {
4
+ address: string
5
+ name: string
6
+ exposedFunctions: InternalMoveFunction[]
7
+ structs: InternalMoveStruct[]
8
+ }
9
+
10
+ export interface InternalMoveFunction {
11
+ name: string
12
+ visibility: InternalMoveFunctionVisibility
13
+ isEntry: boolean
14
+ typeParams: InternalMoveTypeParam[]
15
+ params: TypeDescriptor[]
16
+ return: TypeDescriptor[]
17
+ }
18
+
19
+ export interface InternalMoveStruct {
20
+ name: string
21
+ isNative: boolean
22
+ abilities: string[]
23
+ typeParams: InternalMoveTypeParam[]
24
+ fields: InternalMoveStructField[]
25
+ }
26
+
27
+ export interface InternalMoveStructField {
28
+ name: string
29
+ type: TypeDescriptor
30
+ }
31
+
32
+ export enum InternalMoveFunctionVisibility {
33
+ PRIVATE = 'private',
34
+ PUBLIC = 'public',
35
+ FRIEND = 'friend',
36
+ }
37
+
38
+ export type InternalMoveTypeParam = {
39
+ constraints: string[]
40
+ }