gen-typescript-from-tolk-dev 0.1.0
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/README.md +47 -0
- package/bin/generator.js +15 -0
- package/package.json +32 -0
- package/src/abi-types.ts +157 -0
- package/src/abi.ts +132 -0
- package/src/cli-generate-from-abi-json.ts +21 -0
- package/src/codegen-ctx.ts +115 -0
- package/src/dynamic-ctx.ts +55 -0
- package/src/dynamic-debug-print.ts +191 -0
- package/src/dynamic-get-methods.ts +454 -0
- package/src/dynamic-serialization.ts +430 -0
- package/src/dynamic-validation.ts +55 -0
- package/src/emit-field-defs.ts +60 -0
- package/src/emit-pack-unpack.ts +280 -0
- package/src/emit-stack-rw.ts +239 -0
- package/src/emit-ts-types.ts +66 -0
- package/src/formatting.ts +98 -0
- package/src/generate-from-abi-json.ts +22 -0
- package/src/generate-ts-wrappers.ts +477 -0
- package/src/out-template.ts +514 -0
- package/src/tolk-to-abi.ts +5 -0
- package/src/types-kernel.ts +215 -0
- package/src/unsupported-errors.ts +82 -0
- package/tsconfig.json +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Tolk-to-TypeScript wrappers generator (alpha)
|
|
2
|
+
|
|
3
|
+
Currently, it uses a patched LSP to parse and Tolk source code (for MVP). Later, the compiler will be able
|
|
4
|
+
to emit ABI of a contract, and LSP won't be used in this process.
|
|
5
|
+
|
|
6
|
+
### Main CLI
|
|
7
|
+
|
|
8
|
+
The main entrypoint accepts contract ABI as a JSON string and prints the generated TypeScript wrapper to stdout:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx gen-typescript-from-tolk '{"contractName":"MyContract", ... }'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This package no longer bundles a Tolk compiler. It only supports `ABI JSON -> TypeScript wrapper`.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Configuring TypeScript output
|
|
18
|
+
|
|
19
|
+
1. All structs/aliases/enums are generated to the output — it's unconfigurable currently.
|
|
20
|
+
If a struct can't be serialized (contains `int` for example, not int32/uint64),
|
|
21
|
+
its `fromSlice` and `store` just contain `throw new Error` with a description.
|
|
22
|
+
|
|
23
|
+
2. Default values of all structs are preserved:
|
|
24
|
+
```
|
|
25
|
+
struct WalletStorage {
|
|
26
|
+
jettonBalance: coins = 0 // <----
|
|
27
|
+
ownerAddress: address
|
|
28
|
+
minterAddress: address
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then, `jettonBalance` key in `WalletStorage.create()` will be optional. If it's a storage or a message, it will also be optional in `fromStorage` and `sendXXX`.
|
|
33
|
+
|
|
34
|
+
3. If your storage change shape after initialization (the case of NFT), you can specify an "uninited storage" shape:
|
|
35
|
+
```
|
|
36
|
+
type NOT_INITIALIZED_STORAGE = StorageStructNotInitialized
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then, `fromStorage` will accept fields from this struct, not from `STORAGE`.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
### Known issues
|
|
43
|
+
|
|
44
|
+
1. Generics behave incorrectly when `T` is used inside unions (because generics are not monomorphic, they are true TypeScript generics). For example, you have `struct A<T> { v: T | int8 }` and instantiate `A<int2 | int4>`, then an actual prefix tree will be '00/01/10', but a TS wrapper serializes '0+T/1'; or, for `A<int8>` actually will be no union (`int8 | int8` = `int8`), but a TS wrapper will still serialize as '0+int8/1'. Similar corner cases appear in get methods that work via the stack. For example, `struct MyNullable<T> { v: T? }`, instantiated as `MyNullable<Point>` has no information about stack layout and type-id.
|
|
45
|
+
2. Non-standard keys for maps are unsupported, like `map<Point, ...>`. Only intN/uintN/address are supported (99% use cases). Others can not be represented via `@ton/core` library at all.
|
|
46
|
+
3. Custom serializers for generic structs `fun Some<T>.packToBuilder` not supported (works perfectly for non-generic structs and aliases, but not for generic).
|
|
47
|
+
4. Default values for struct fields, if a field contains a union, not supported.
|
package/bin/generator.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
require('ts-node').register({
|
|
5
|
+
transpileOnly: true,
|
|
6
|
+
project: path.join(__dirname, '..', 'tsconfig.json'),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
require('../src/cli-generate-from-abi-json')
|
|
10
|
+
.runGeneratorFromAbiCli(process.argv.slice(2))
|
|
11
|
+
.catch((error) => {
|
|
12
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13
|
+
process.stderr.write(`${message}\n`);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gen-typescript-from-tolk-dev",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"author": "TON Core",
|
|
5
|
+
"files": [
|
|
6
|
+
"README.md",
|
|
7
|
+
"bin",
|
|
8
|
+
"src",
|
|
9
|
+
"tsconfig.json"
|
|
10
|
+
],
|
|
11
|
+
"bin": {
|
|
12
|
+
"gen-typescript-from-tolk": "./bin/generator.js"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build:standalone": "node scripts/build-standalone-cli.js",
|
|
16
|
+
"test": "jest --verbose"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@ton/core": "~0",
|
|
20
|
+
"ts-node": "^10.9.2",
|
|
21
|
+
"typescript": "^5.9.2"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@ton/crypto": "^3.3.0",
|
|
25
|
+
"@ton/sandbox": ">=0.37.0",
|
|
26
|
+
"@ton/ton": ">=15.3.1 <16.0.0",
|
|
27
|
+
"@types/jest": "^30.0.0",
|
|
28
|
+
"@types/node": "^22.17.2",
|
|
29
|
+
"jest": "^30.0.5",
|
|
30
|
+
"ts-jest": "^29.4.1"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/abi-types.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/*
|
|
2
|
+
ABI Types — describes the type system and symbols definitions in the Tolk language.
|
|
3
|
+
The goal is to have a full bidirectional mapping from any Tolk struct to ABI:
|
|
4
|
+
all tricky types like generics, aliases, inline unions, etc. should be supported.
|
|
5
|
+
Then, any input could be packed to a cell and unpacked back — exactly as it's done by the compiler.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Since ABI should be JSON-serialized, all 'bigint' numbers are stored as strings.
|
|
9
|
+
type bigint_as_string = string
|
|
10
|
+
|
|
11
|
+
// Ty is an "ABI Type" that fully reflects the type system in Tolk.
|
|
12
|
+
export type Ty =
|
|
13
|
+
| { kind: 'int' }
|
|
14
|
+
| { kind: 'intN'; n: number }
|
|
15
|
+
| { kind: 'uintN'; n: number }
|
|
16
|
+
| { kind: 'varintN', n: number }
|
|
17
|
+
| { kind: 'varuintN', n: number }
|
|
18
|
+
| { kind: 'coins' }
|
|
19
|
+
| { kind: 'bool' }
|
|
20
|
+
| { kind: 'cell' }
|
|
21
|
+
| { kind: 'builder' }
|
|
22
|
+
| { kind: 'slice' }
|
|
23
|
+
| { kind: 'string' }
|
|
24
|
+
| { kind: 'remaining' }
|
|
25
|
+
| { kind: 'address' }
|
|
26
|
+
| { kind: 'addressOpt' }
|
|
27
|
+
| { kind: 'addressExt' }
|
|
28
|
+
| { kind: 'addressAny' }
|
|
29
|
+
| { kind: 'bitsN'; n: number }
|
|
30
|
+
| { kind: 'nullLiteral' }
|
|
31
|
+
| { kind: 'callable' }
|
|
32
|
+
| { kind: 'void' }
|
|
33
|
+
| { kind: 'unknown' }
|
|
34
|
+
| { kind: 'nullable'; inner: Ty; stackTypeId?: number; stackWidth?: number }
|
|
35
|
+
| { kind: 'cellOf'; inner: Ty }
|
|
36
|
+
| { kind: 'arrayOf'; inner: Ty }
|
|
37
|
+
| { kind: 'lispListOf'; inner: Ty }
|
|
38
|
+
| { kind: 'tensor'; items: Ty[] }
|
|
39
|
+
| { kind: 'shapedTuple'; items: Ty[] }
|
|
40
|
+
| { kind: 'mapKV'; k: Ty; v: Ty }
|
|
41
|
+
| { kind: 'EnumRef'; enumName: string }
|
|
42
|
+
| { kind: 'StructRef'; structName: string; typeArgs?: Ty[] }
|
|
43
|
+
| { kind: 'AliasRef'; aliasName: string; typeArgs?: Ty[] }
|
|
44
|
+
| { kind: 'genericT'; nameT: string }
|
|
45
|
+
| { kind: 'union'; variants: UnionVariant[]; stackWidth: number }
|
|
46
|
+
|
|
47
|
+
// UnionVariant exists for every `T_i` in a union type `T1 | T2 | ...`.
|
|
48
|
+
// For binary serialization, a union should have a prefix tree,
|
|
49
|
+
// which is either defined explicitly with struct prefixes: `struct (0x12345678) CounterIncrement`,
|
|
50
|
+
// or auto-generated (implicit), e.g. `int8 | int16 | int32` is serialized as '00'+int8 / '01'+int16 / '10'+int32.
|
|
51
|
+
export interface UnionVariant {
|
|
52
|
+
variantTy: Ty
|
|
53
|
+
prefixStr: string
|
|
54
|
+
prefixLen: number
|
|
55
|
+
isPrefixImplicit?: boolean
|
|
56
|
+
stackTypeId: number
|
|
57
|
+
stackWidth: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ABIConstExpression is "an expression" that occurs in fields defaults.
|
|
61
|
+
// Example:
|
|
62
|
+
// > struct Demo {
|
|
63
|
+
// > initial: int8 = 123
|
|
64
|
+
// > owner: address = address("UQ...")
|
|
65
|
+
// > }
|
|
66
|
+
// Then both fields have a default value:
|
|
67
|
+
// initial: { kind: 'int', v: '123' }
|
|
68
|
+
// owner: { kind: 'address', addr: 'UQ...' }
|
|
69
|
+
export type ABIConstExpression =
|
|
70
|
+
| { kind: 'int'; v: bigint_as_string }
|
|
71
|
+
| { kind: 'bool'; v: boolean }
|
|
72
|
+
| { kind: 'slice'; hex: string }
|
|
73
|
+
| { kind: 'string'; str: string }
|
|
74
|
+
| { kind: 'address'; addr: string }
|
|
75
|
+
| { kind: 'tensor'; items: ABIConstExpression[] }
|
|
76
|
+
| { kind: 'shapedTuple'; items: ABIConstExpression[] }
|
|
77
|
+
| { kind: 'object'; structName: string; fields: ABIConstExpression[] }
|
|
78
|
+
| { kind: 'castTo'; inner: ABIConstExpression; castTo: Ty }
|
|
79
|
+
| { kind: 'null' }
|
|
80
|
+
|
|
81
|
+
// ABICustomSerializers is present in a struct/alias if that type has custom serializers in Tolk code:
|
|
82
|
+
//
|
|
83
|
+
// fun SomeType.packToBuilder(self, mutate b: builder) { ... }
|
|
84
|
+
// fun SomeType.unpackFromSlice(mutate s: slice): SomeAlias { ... }
|
|
85
|
+
//
|
|
86
|
+
// Body of these functions is not a part of ABI (it's arbitrary Tolk code).
|
|
87
|
+
// To make serialization work (e.g., in TypeScript wrappers),
|
|
88
|
+
// one should provide equivalent implementations in a language ABI is applied to.
|
|
89
|
+
export interface ABICustomSerializers {
|
|
90
|
+
packToBuilder: boolean
|
|
91
|
+
unpackFromSlice: boolean
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ABIStruct represents a Tolk struct.
|
|
95
|
+
// Examples:
|
|
96
|
+
//
|
|
97
|
+
// > struct Point { x: int, y: int }
|
|
98
|
+
// A simple struct, not serializable (because 'int', not 'int8' or similar)
|
|
99
|
+
//
|
|
100
|
+
// > struct Wrapper<TItem> { item: TItem }
|
|
101
|
+
// A generic struct (has typeParams), fields[0].ty = { kind: 'genericT', nameT: 'TItem' }
|
|
102
|
+
//
|
|
103
|
+
// > struct (0x12345678) Increment { ... }
|
|
104
|
+
// Has a serialization prefix: prefixStr = '0x12345678', prefixLen = 32
|
|
105
|
+
export interface ABIStruct {
|
|
106
|
+
kind: 'Struct'
|
|
107
|
+
name: string
|
|
108
|
+
typeParams?: string[]
|
|
109
|
+
prefix?: {
|
|
110
|
+
prefixStr: string
|
|
111
|
+
prefixLen: number
|
|
112
|
+
}
|
|
113
|
+
fields: {
|
|
114
|
+
name: string
|
|
115
|
+
ty: Ty
|
|
116
|
+
defaultValue?: ABIConstExpression
|
|
117
|
+
description?: string
|
|
118
|
+
}[]
|
|
119
|
+
customPackUnpack?: ABICustomSerializers
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ABIAlias represents a Tolk type alias.
|
|
123
|
+
// Examples:
|
|
124
|
+
//
|
|
125
|
+
// > type UserId = int32
|
|
126
|
+
// A simple alias, targetTy = { kind: 'intN', n: 32 }
|
|
127
|
+
//
|
|
128
|
+
// > type Maybe<T> = MaybeNothing | MaybeJust<T>
|
|
129
|
+
// A generic alias (has typeParams), targetTy = { kind: 'union', variants: ... }
|
|
130
|
+
//
|
|
131
|
+
// An alias is serialized as its target, unless it has custom serializers in Tolk code.
|
|
132
|
+
export interface ABIAlias {
|
|
133
|
+
kind: 'Alias'
|
|
134
|
+
name: string
|
|
135
|
+
targetTy: Ty
|
|
136
|
+
typeParams?: string[]
|
|
137
|
+
customPackUnpack?: ABICustomSerializers
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ABIEnum represents a Tolk enum.
|
|
141
|
+
// Examples:
|
|
142
|
+
//
|
|
143
|
+
// > enum Color { Red, Green, Blue }
|
|
144
|
+
// Has 3 members (values '0', '1', '2'), encoded as 'uint2' (auto-calculated by the compiler).
|
|
145
|
+
//
|
|
146
|
+
// > enum Mode: int8 { User = 0, Admin = 127 }
|
|
147
|
+
// Has 2 members, encoded as 'int8' (specified manually).
|
|
148
|
+
export interface ABIEnum {
|
|
149
|
+
kind: 'Enum'
|
|
150
|
+
name: string
|
|
151
|
+
encodedAs: Ty
|
|
152
|
+
members: {
|
|
153
|
+
name: string
|
|
154
|
+
value: bigint_as_string
|
|
155
|
+
}[]
|
|
156
|
+
customPackUnpack?: ABICustomSerializers
|
|
157
|
+
}
|
package/src/abi.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/*
|
|
2
|
+
ABI of a contract describes how the contract is "seen" by external observers:
|
|
3
|
+
its get methods, incoming messages, etc.
|
|
4
|
+
Given an ABI, it becomes possible to
|
|
5
|
+
- post messages from a client, since ABI provides sufficient info for serialization
|
|
6
|
+
- generate wrappers for TypeScript and other languages
|
|
7
|
+
- render its storage in the explorer
|
|
8
|
+
- create a UI to interact with a contract from Web/IDE
|
|
9
|
+
- etc.
|
|
10
|
+
The ABI is emitted by the Tolk compiler, along with bytecode.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Ty, ABIStruct, ABIAlias, ABIEnum, ABIConstExpression } from './abi-types'
|
|
14
|
+
|
|
15
|
+
// ABIGetMethod is a "get method" (aka "contract getter").
|
|
16
|
+
// In Tolk code, getters are created with `get fun`.
|
|
17
|
+
// Example:
|
|
18
|
+
// > get fun calcData(owner: address): SomeStruct { ... }
|
|
19
|
+
// It has one parameter with ty = { kind: 'address' },
|
|
20
|
+
// its returnTy is { kind: 'StructRef', structName: 'SomeStruct' }.
|
|
21
|
+
// Note, that getters are called off-chain — via the TVM stack, not via serialization.
|
|
22
|
+
// (For instance, they can return 'int' or 'slice', although they are not serializable)
|
|
23
|
+
export interface ABIGetMethod {
|
|
24
|
+
tvmMethodId: number
|
|
25
|
+
name: string
|
|
26
|
+
parameters: {
|
|
27
|
+
name: string
|
|
28
|
+
ty: Ty
|
|
29
|
+
description?: string
|
|
30
|
+
defaultValue?: ABIConstExpression
|
|
31
|
+
}[]
|
|
32
|
+
returnTy: Ty
|
|
33
|
+
description?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ABIInternalMessage is "an incoming internal message" (handled by `onInternalMessage`).
|
|
37
|
+
// In practice, a user describes each message as a struct:
|
|
38
|
+
// > struct (0x12345678) Increment { ... }
|
|
39
|
+
// > struct (0x23456789) Reset { ... }
|
|
40
|
+
// Then, ABI of a contract will contain those two messages:
|
|
41
|
+
// * bodyTy = { kind: 'StructRef', structName: 'Increment' }
|
|
42
|
+
// * bodyTy = { kind: 'StructRef', structName: 'Reset' }
|
|
43
|
+
// Theoretically, bodyTy can be something else: e.g., instantiation `Transfer<ForwardPayload>`.
|
|
44
|
+
// Description, minimalMsgValue, and other properties may be set with an `@abi` attribute:
|
|
45
|
+
// > @abi(...)
|
|
46
|
+
// > struct (0x12345678) Increment { ... }
|
|
47
|
+
export interface ABIInternalMessage {
|
|
48
|
+
bodyTy: Ty
|
|
49
|
+
description?: string
|
|
50
|
+
minimalMsgValue?: number
|
|
51
|
+
preferredSendMode?: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ABIExternalMessage is "an incoming external message" (handled by `onExternalMessage`).
|
|
55
|
+
// It's either a 'slice' or some struct.
|
|
56
|
+
export interface ABIExternalMessage {
|
|
57
|
+
bodyTy: Ty
|
|
58
|
+
description?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ABIOutgoingMessage is "an outgoing internal/external message".
|
|
62
|
+
// In Tolk code, those are calls to `createMessage`.
|
|
63
|
+
export interface ABIOutgoingMessage {
|
|
64
|
+
bodyTy: Ty
|
|
65
|
+
description?: string
|
|
66
|
+
// todo need BounceMode? if yes, what if 2 messages of type T sent with different modes?
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ABIStorage defines shape of a storage.
|
|
70
|
+
// Most often, it's a regular struct, serializable into a cell.
|
|
71
|
+
// In the case of NFT, when a storage changes its shape (several fields appear after deployment),
|
|
72
|
+
// the "initial storage" can also be expressed: it's called "storage at deployment".
|
|
73
|
+
// The storage is used to visualize current contract state and to calculate its address.
|
|
74
|
+
export interface ABIStorage {
|
|
75
|
+
storageTy?: Ty
|
|
76
|
+
storageAtDeploymentTy?: Ty
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ABIThrownError is an errCode fired by `throw` or `assert` in Tolk code.
|
|
80
|
+
export interface ABIThrownError {
|
|
81
|
+
kind: 'plainInt' | 'constant' | 'enumMember'
|
|
82
|
+
name?: string
|
|
83
|
+
errCode: number
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ContractABI is a final result — the ABI of a TON smart contract.
|
|
87
|
+
//
|
|
88
|
+
// Partially, its properties may be specified by a user manually:
|
|
89
|
+
// > contract MyName {
|
|
90
|
+
// > author: "Dima"
|
|
91
|
+
// > incomingMessages: SomeUnion
|
|
92
|
+
// > }
|
|
93
|
+
//
|
|
94
|
+
// Partially, its properties are automatically calculated by the compiler:
|
|
95
|
+
// - outgoingMessages, via the calls to `createMessage`
|
|
96
|
+
// - thrownErrors, via `throw` and `assert` statements
|
|
97
|
+
// - getMethods, essentially `get fun`
|
|
98
|
+
//
|
|
99
|
+
// Auto-calculated properties may be manually enriched via the `@abi` annotation:
|
|
100
|
+
// > @abi({
|
|
101
|
+
// > description: "...",
|
|
102
|
+
// > minimalMsgValue: ton("0.05"),
|
|
103
|
+
// > })
|
|
104
|
+
// > struct SomeOutgoingMessage { ... }
|
|
105
|
+
//
|
|
106
|
+
// todo opened questions:
|
|
107
|
+
// - clientTy for struct fields
|
|
108
|
+
// - potentially missing struct fields, especially for describing FunC
|
|
109
|
+
// - union types are not supported in get methods now, they have complicated stack layout
|
|
110
|
+
// - TEPs and namespaces?
|
|
111
|
+
export interface ContractABI {
|
|
112
|
+
contractName: string
|
|
113
|
+
author?: string
|
|
114
|
+
version?: string
|
|
115
|
+
description?: string
|
|
116
|
+
|
|
117
|
+
declarations: (ABIStruct | ABIAlias | ABIEnum)[]
|
|
118
|
+
|
|
119
|
+
storage: ABIStorage
|
|
120
|
+
incomingMessages: ABIInternalMessage[]
|
|
121
|
+
incomingExternal: ABIExternalMessage[]
|
|
122
|
+
outgoingMessages: ABIOutgoingMessage[]
|
|
123
|
+
emittedEvents: ABIOutgoingMessage[]
|
|
124
|
+
getMethods: ABIGetMethod[]
|
|
125
|
+
thrownErrors: ABIThrownError[]
|
|
126
|
+
|
|
127
|
+
compilerName: string
|
|
128
|
+
compilerVersion: string
|
|
129
|
+
|
|
130
|
+
// todo is it a part of ABI? the same question for source maps in the future
|
|
131
|
+
codeBoc64: string
|
|
132
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { generateTypeScriptFromAbiJson } from './generate-from-abi-json';
|
|
2
|
+
|
|
3
|
+
const HELP_TEXT = `Usage:
|
|
4
|
+
generator '<contract-abi-json>'
|
|
5
|
+
|
|
6
|
+
Prints the generated TypeScript wrapper to stdout.`;
|
|
7
|
+
|
|
8
|
+
function parseCliArgs(argv: string[]): string {
|
|
9
|
+
if (argv.length !== 1) {
|
|
10
|
+
throw new Error(`Expected a single positional ABI JSON argument.\n\n${HELP_TEXT}`);
|
|
11
|
+
}
|
|
12
|
+
const abiJson = argv[0];
|
|
13
|
+
if (abiJson.length === 0) {
|
|
14
|
+
throw new Error(`ABI JSON argument is required.\n\n${HELP_TEXT}`);
|
|
15
|
+
}
|
|
16
|
+
return abiJson;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function runGeneratorFromAbiCli(argv: string[]): Promise<void> {
|
|
20
|
+
process.stdout.write(generateTypeScriptFromAbiJson(parseCliArgs(argv)));
|
|
21
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { Ty, ABIStruct, ABIAlias, ABIEnum } from './abi-types';
|
|
2
|
+
import { SymbolNotFound } from './unsupported-errors';
|
|
3
|
+
|
|
4
|
+
// Functions implemented in an output template, used to detect name shadowing.
|
|
5
|
+
export const RUNTIME = {
|
|
6
|
+
beginCell: 'beginCell',
|
|
7
|
+
lookupPrefix: 'lookupPrefix',
|
|
8
|
+
lookupPrefixAndEat: 'lookupPrefixAndEat',
|
|
9
|
+
throwNonePrefixMatch: 'throwNonePrefixMatch',
|
|
10
|
+
loadAndCheckPrefix32: 'loadAndCheckPrefix32',
|
|
11
|
+
loadAndCheckPrefix: 'loadAndCheckPrefix',
|
|
12
|
+
loadTolkBitsN: 'loadTolkBitsN',
|
|
13
|
+
storeTolkBitsN: 'storeTolkBitsN',
|
|
14
|
+
loadTolkAddressAny: 'loadTolkAddressAny',
|
|
15
|
+
storeTolkAddressAny: 'storeTolkAddressAny',
|
|
16
|
+
loadTolkRemaining: 'loadTolkRemaining',
|
|
17
|
+
storeTolkRemaining: 'storeTolkRemaining',
|
|
18
|
+
loadCellRef: 'loadCellRef',
|
|
19
|
+
storeCellRef: 'storeCellRef',
|
|
20
|
+
loadArrayOf: 'loadArrayOf',
|
|
21
|
+
storeArrayOf: 'storeArrayOf',
|
|
22
|
+
loadLispListOf: 'loadLispListOf',
|
|
23
|
+
storeLispListOf: 'storeLispListOf',
|
|
24
|
+
storeTolkNullable: 'storeTolkNullable',
|
|
25
|
+
makeCellFrom: 'makeCellFrom',
|
|
26
|
+
createDictionaryValue: 'createDictionaryValue',
|
|
27
|
+
registerCustomPackUnpack: 'registerCustomPackUnpack',
|
|
28
|
+
ExtraSendOptions: 'ExtraSendOptions',
|
|
29
|
+
StackReader: 'StackReader',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// SymTable is an index of top-level declarations.
|
|
33
|
+
export class SymTable {
|
|
34
|
+
private structs = new Map<string, ABIStruct>()
|
|
35
|
+
private aliases = new Map<string, ABIAlias>()
|
|
36
|
+
private enums = new Map<string, ABIEnum>()
|
|
37
|
+
|
|
38
|
+
constructor(declarations: (ABIStruct | ABIAlias | ABIEnum)[]) {
|
|
39
|
+
for (const n of declarations) {
|
|
40
|
+
if (n.kind === 'Struct') this.structs.set(n.name, n);
|
|
41
|
+
else if (n.kind === 'Alias') this.aliases.set(n.name, n);
|
|
42
|
+
else if (n.kind === 'Enum') this.enums.set(n.name, n);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getStruct(structName: string): ABIStruct {
|
|
47
|
+
const s = this.structs.get(structName);
|
|
48
|
+
if (s === undefined)
|
|
49
|
+
throw new SymbolNotFound('struct', structName);
|
|
50
|
+
return s;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getAlias(aliasName: string): ABIAlias {
|
|
54
|
+
const a = this.aliases.get(aliasName);
|
|
55
|
+
if (a === undefined)
|
|
56
|
+
throw new SymbolNotFound('alias', aliasName);
|
|
57
|
+
return a;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getEnum(enumName: string): ABIEnum {
|
|
61
|
+
const e = this.enums.get(enumName);
|
|
62
|
+
if (e === undefined)
|
|
63
|
+
throw new SymbolNotFound('enum', enumName);
|
|
64
|
+
return e;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getAliasTarget(aliasName: string): Ty {
|
|
68
|
+
return this.getAlias(aliasName).targetTy;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// CodegenCtx represents current state of TypeScript code generation.
|
|
73
|
+
// It contains properties that affect final output and help resolve symbols.
|
|
74
|
+
// For example, we want Tolk `int64` not be just TS `bigint`, but
|
|
75
|
+
// to export `type int64 = bigint` and use `int64` directly,
|
|
76
|
+
// that's why we track all occurred intN.
|
|
77
|
+
// Same for other public properties, they are modified while iterating through declarations.
|
|
78
|
+
export class CodegenCtx {
|
|
79
|
+
readonly symbols: SymTable
|
|
80
|
+
|
|
81
|
+
intNOccurred = new Set<string>(['int8', 'int16', 'int32', 'int256'])
|
|
82
|
+
uintNOccurred = new Set<string>(['uint8', 'uint16', 'uint32', 'uint256'])
|
|
83
|
+
varIntNOccurred = new Set<string>()
|
|
84
|
+
bitsNOccurred = new Set<string>()
|
|
85
|
+
|
|
86
|
+
has_RemainingBitsAndRefs = false
|
|
87
|
+
has_customPackUnpack = false
|
|
88
|
+
has_customDictV = false
|
|
89
|
+
has_implicitUnionPrefix = false
|
|
90
|
+
has_non32Prefixes = false
|
|
91
|
+
has_addressAny = false
|
|
92
|
+
has_arrayOf = false
|
|
93
|
+
has_lispListOf = false
|
|
94
|
+
|
|
95
|
+
stackReadsUnknown = false
|
|
96
|
+
stackReadsArrayOf = false
|
|
97
|
+
stackReadsLispListOf = false
|
|
98
|
+
stackReadsSnakeString = false
|
|
99
|
+
stackReadsTuple = false
|
|
100
|
+
stackReadsMapKV = false
|
|
101
|
+
stackReadsBuilder = false
|
|
102
|
+
stackReadsNullable = false
|
|
103
|
+
stackReadsWideNullable = false
|
|
104
|
+
stackReadsUnionType = false
|
|
105
|
+
stackReadsCellRef = false
|
|
106
|
+
stackReadsNullLiteral = false
|
|
107
|
+
|
|
108
|
+
constructor(declarations: (ABIStruct | ABIAlias | ABIEnum)[]) {
|
|
109
|
+
this.symbols = new SymTable(declarations);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
sortOccurred(occurred: Set<string>): string[] {
|
|
113
|
+
return [...occurred].sort((a, b) => a.length - b.length || a.localeCompare(b));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as c from '@ton/core'
|
|
2
|
+
import { SymTable } from './codegen-ctx'
|
|
3
|
+
import { ABIGetMethod, ContractABI } from './abi'
|
|
4
|
+
import { CantPackDynamic, CantUnpackDynamic } from './unsupported-errors'
|
|
5
|
+
|
|
6
|
+
type CustomPackToBuilderFn<T> = (self: T, b: c.Builder) => void
|
|
7
|
+
type CustomUnpackFromSliceFn<T> = (s: c.Slice) => T
|
|
8
|
+
|
|
9
|
+
export class DynamicCtx {
|
|
10
|
+
readonly contractName: string;
|
|
11
|
+
readonly getMethods: ABIGetMethod[];
|
|
12
|
+
readonly symbols: SymTable;
|
|
13
|
+
|
|
14
|
+
private customSerializersRegistry: Map<string, [CustomPackToBuilderFn<any> | null, CustomUnpackFromSliceFn<any> | null]> = new Map;
|
|
15
|
+
|
|
16
|
+
constructor(contract: ContractABI) {
|
|
17
|
+
this.contractName = contract.contractName;
|
|
18
|
+
this.getMethods = contract.getMethods;
|
|
19
|
+
this.symbols = new SymTable(contract.declarations);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
registerCustomPackUnpack<T>(
|
|
23
|
+
typeName: string,
|
|
24
|
+
packToBuilderFn: CustomPackToBuilderFn<T> | null,
|
|
25
|
+
unpackFromSliceFn: CustomUnpackFromSliceFn<T> | null,
|
|
26
|
+
) {
|
|
27
|
+
if (this.customSerializersRegistry.has(typeName)) {
|
|
28
|
+
throw new Error(`Custom pack/unpack for '${typeName}' already registered`);
|
|
29
|
+
}
|
|
30
|
+
this.customSerializersRegistry.set(typeName, [packToBuilderFn, unpackFromSliceFn]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getCustomPackFnOrThrow(typeName: string, fieldPath: string): CustomPackToBuilderFn<any> {
|
|
34
|
+
let packUnpack = this.customSerializersRegistry.get(typeName);
|
|
35
|
+
let packFn = packUnpack ? packUnpack[0] : null;
|
|
36
|
+
if (!packFn) {
|
|
37
|
+
throw new CantPackDynamic(fieldPath, `custom packToBuilder was not registered for '${typeName}'`);
|
|
38
|
+
}
|
|
39
|
+
return packFn;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getCustomUnpackFnOrThrow(typeName: string, fieldPath: string): CustomUnpackFromSliceFn<any> {
|
|
43
|
+
let packUnpack = this.customSerializersRegistry.get(typeName);
|
|
44
|
+
let unpackFn = packUnpack ? packUnpack[1] : null;
|
|
45
|
+
if (!unpackFn) {
|
|
46
|
+
throw new CantUnpackDynamic(fieldPath, `custom unpackFromSlice was not registered for '${typeName}'`);
|
|
47
|
+
}
|
|
48
|
+
return unpackFn;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
findGetMethod(getMethodName: string): ABIGetMethod | undefined {
|
|
52
|
+
return this.getMethods.find(m => m.name === getMethodName);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|