Skip to content

Commit

Permalink
Merge branch 'main' into fast-check
Browse files Browse the repository at this point in the history
  • Loading branch information
anton-trunov authored Dec 28, 2024
2 parents de192e5 + da4b8d8 commit 52c01e9
Show file tree
Hide file tree
Showing 54 changed files with 16,173 additions and 5,157 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ src/test/**/output/
src/func/funcfiftlib.js
**/grammar.ohm*.ts
**/grammar.ohm*.js
src/grammar/next/grammar.ts
jest.setup.js
jest.globalSetup.js
jest.teardown.js
Expand Down
5 changes: 3 additions & 2 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/dist
/src/func/funcfiftlib.js
/src/func/funcfiftlib.wasm.js
/src/grammar/grammar.ohm-bundle.d.ts
/src/grammar/grammar.ohm-bundle.js
/src/grammar/prev/grammar.ohm-bundle.d.ts
/src/grammar/prev/grammar.ohm-bundle.js
src/grammar/next/grammar.ts
/src/imports/stdlib.ts
/grammar
/docs
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ability to specify a compile-time message opcode expression: PR [#1188](https://github.com/tact-lang/tact/pull/1188)
- The `VarInt16`, `VarInt32`, `VarUint16`, `VarUint32` integer serialization types: PR [#1186](https://github.com/tact-lang/tact/pull/1186)
- `unboc`: a standalone CLI utility to expose Tact's TVM disassembler: PR [#1259](https://github.com/tact-lang/tact/pull/1259)
- Added alternative parser: PR [#1258](https://github.com/tact-lang/tact/pull/1258)

### Changed

Expand Down Expand Up @@ -65,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a note on `dump()` being computationally expensive: PR [#1189](https://github.com/tact-lang/tact/pull/1189)
- Fixed links in Chinese translation: PR [#1206](https://github.com/tact-lang/tact/pull/1206)
- Added a note on 255 being the maximum number of messages that can be sent during action phase: PR [#1237](https://github.com/tact-lang/tact/pull/1237)
- Added onchain metadata creation for NFTs and Jettons to the cookbook: PR [#1236](https://github.com/tact-lang/tact/pull/1236)

### Release contributors

Expand Down
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ Updating a subset of the test snapshots can be done like so:
yarn test -u spec-name-pattern1 spec-name-pattern2
```

## Code quality

To pass review, code has to conform to our [styleguide](/STYLEGUIDE.md).

## Linting

To pass CI, one needs to have a warning-free build. To run all the lints described below execute the following command in your terminal:
Expand Down
12 changes: 7 additions & 5 deletions STYLEGUIDE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Styleguide
# TypeScript Styleguide

Due to stringent security and correctness requirements we have to use a subset of TypeScript features that are known to be _less unsound_.

Expand All @@ -22,7 +22,7 @@ Prefer the most simple and basic language features.

- **Don't use `as`**, except `as const`. It was meant for gradually typing legacy code, not for production use in TS-native projects. Often `x as T` actually was meant to be `const y: T = x;` or `x satisfies T`.
- **Don't use `any`.** Its actual meaning is "completely disregard type errors here". Prefer `unknown` or `never`.
- **Don't use guard types `x is T`.** These are just `as` in disguise. Might be used in very simple cases, such as ADT boilerplate.
- **Don't use guard types `x is T`.** These are just `as` in disguise: `const foo = (x: 1 | 2): x is 1 => x === 2;`. Might be used in very simple cases, such as ADT boilerplate.
- **Don't use overloading**. It's almost the same as intersection types, and intersection types are broken.
- **Don't use `@ts-ignore`.** It's a worse version of `as` that can take TS compiler into an arbitrary incorrect state.
- **Don't use `x!` operator.** It does no checks at runtime, and is essentially `x as NotNull<typeof x>`.
Expand Down Expand Up @@ -68,10 +68,10 @@ export const includes = <const K extends string>(

### Don't use JS object "features"

- **Don't use optional fields** `foo?: Bar`. Every `?` doubles number of cases that should be tested. Eventually some combination of non-defined and undefined fields will be non-semantic.
- **Don't use optional fields** `foo?: Bar`. Every `?` doubles number of cases that should be tested. Eventually some combination of non-defined and undefined fields will be non-semantic. Prefer at least an explicit `foo: Bar | undefined`, or better create a type with descriptive name to make it a proper tagged union.
- _**Don't use optional fields**_. In JS there is a distinction between field that is not defined and a field that is `undefined` (sic). `'a' in {} === false`, `'a' in { a: undefined } === true`. TypeScript doesn't handle this properly in its type system.
- **Don't use `Proxy`**. These break type safety, are incorrectly handled in debuggers, lead to very unexpected heisenbugs.
- **Don't use `get` and `set`**. See `Proxy` above.
- **Don't use `get` and `set`**. See `Proxy` above. Use explicit `getFoo` and `setFoo` functions, or just avoid mutable state.
- **Don't use `...` with objects**. It will require intersection types or inheritance to type. Prefer aggregation: `{ ...a, b }``{ a, b }`.
- **Don't use `interface ... extends`**. There is no way to safely distinguish objects supporting parent and child interfaces at runtime.
- Except where it's required to untie type recursion. For example `type Foo = A<Foo>` would only work as `interface Foo extends A<Foo> {}`
Expand Down Expand Up @@ -112,9 +112,11 @@ export const includes = <const K extends string>(

### Other considerations

- **Avoid unnecessary switch**. Switch statements require extra considerations for unexpected passthrough, are bulky, and take an extra function to make exhaustive checks work. Unless use of `switch` can be somehow rationalized, prefer `makeVisitor` from `src/utils/tricks.ts`.
- **Avoid unnecessary bigint**. We have to work with `bigint`, because TVM supports ints of 257 bit length, but in rest of the code `bigint` would only cause issues with debugging it.
- **Beware of `${}`** in template strings. Any inlining succeeds, and there won't be any compile-time errors even if it's a function `${(x: number) => x}`.
- **Avoid `null`**. `typeof null === 'object'`, and there is `undefined` anyway.
- **Avoid exceptions**. Exceptions are untyped.
- **Avoid exceptions**. Exceptions are untyped. Pass error continuations explicitly `(onFooError: () => T) => T`. Example can be found in `src/grammar/parser-error.ts`.
- **Avoid tuples**. TS gives them minimal distinction from arrays, and type system is broken around them. Occasionally for performance reasons tuples might be a better option than objects.
- **Avoid `enum`**. It's equivalent to unions since 5.0, except generates boilerplate JS code. A version that doesn't generate extraneous code, `const enum`, is not properly supported by `babel`.
- **Avoid iterators**. They're untypable unless fixed in JS standard. Prefer generators. Prefer iterating with `for (... of ...)`.
5 changes: 5 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"Korshakov",
"Laika",
"langle",
"langtools",
"Liskov",
"lparen",
"lvalue",
Expand All @@ -87,6 +88,7 @@
"Novus",
"Offchain",
"Parens",
"pgen",
"pinst",
"POSIX",
"postpack",
Expand Down Expand Up @@ -117,6 +119,7 @@
"Topup",
"Toncoin",
"Toncoins",
"tonstudio",
"Trunov",
"typechecker",
"uintptr",
Expand Down Expand Up @@ -155,6 +158,8 @@
"src/grammar/test/items-asm-funs.tact",
"src/grammar/test-asm/*.tact",
"src/grammar/test-failed/funcid-*.tact",
"src/grammar/next/grammar.gg",
"src/grammar/next/grammar.ts",
"src/imports/stdlib.ts",
"/src/test/compilation-failed/const-eval-failed.spec.ts",
"src/test/e2e-emulated/address.spec.ts",
Expand Down
29 changes: 29 additions & 0 deletions docs/src/content/docs/book/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,35 @@ If set to `true{:json}`, enables generation of a [getter](/book/contracts#getter

:::

#### `parser` {#options-parser}

`"new"{:json}` by default.

If set to `new{:json}`, Tact will compile with a new language parser.

If set to `old{:json}`, Tact will compile with an old language parser.

```json filename="tact.config.json" {8,14}
{
"projects": [
{
"name": "some_prefix",
"path": "./contract.tact",
"output": "./contract_output",
"options": {
"debug": true
}
},
{
"name": "ContractUnderBlueprint",
"options": {
"debug": true
}
}
]
}
```

#### `experimental` {#options-experimental}

Experimental options that might be removed in the future. Use with caution!
Expand Down
47 changes: 47 additions & 0 deletions docs/src/content/docs/cookbook/jettons.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,53 @@ fun calculateJettonWalletAddress(
}
```

### Onchain metadata creation

```tact
/// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#jetton-metadata-example-offchain
fun composeJettonMetadata(
name: String, // full name
description: String, // text description of the Jetton
symbol: String, // "stock ticker" symbol without the $ prefix, like USDT or SCALE
image: String, // link to the image
// There could be other data, see:
// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#jetton-metadata-attributes
): Cell {
let dict: map<Int as uint256, Cell> = emptyMap();
dict.set(sha256("name"), name.asMetadataCell());
dict.set(sha256("description"), description.asMetadataCell());
dict.set(sha256("symbol"), symbol.asMetadataCell());
dict.set(sha256("image"), image.asMetadataCell());
return beginCell()
.storeUint(0, 8) // a null byte prefix
.storeMaybeRef(dict.asCell()!!) // 1 as a single bit, then a reference
.endCell();
}
// Taking flight!
fun poorMansLaunchPad() {
let jettonMetadata = composeJettonMetadata(
"Best Jetton",
"A very descriptive description describing the jetton descriptively",
"JETTON",
"...link to ipfs or somewhere trusted...",
);
}
// Prefixes the String with a single null byte and converts it to a Cell
// The null byte prefix is used to express metadata in various standards, like NFT or Jetton
inline extends fun asMetadataCell(self: String): Cell {
return beginTailString().concat(self).toCell();
}
```

:::note[Useful links:]

[Token Data Standard in TEPs](https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#jetton-metadata-attributes)

:::

:::tip[Hey there!]

Didn't find your favorite example of Jetton usage? Have cool implementations in mind? [Contributions are welcome!](https://github.com/tact-lang/tact/issues)
Expand Down
92 changes: 92 additions & 0 deletions docs/src/content/docs/cookbook/nfts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,98 @@ contract Example {
}
```

## Onchain metadata creation

### NFT Collection {#onchain-metadata-nft-collection}

```tact
/// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#nft-metadata-attributes
fun composeCollectionMetadata(
name: String, // full name
description: String, // text description of the NFT
image: String, // link to the image
// There could be other data, see:
// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#nft-metadata-attributes
): Cell {
let dict: map<Int as uint256, Cell> = emptyMap();
dict.set(sha256("name"), name.asMetadataCell());
dict.set(sha256("description"), description.asMetadataCell());
dict.set(sha256("image"), image.asMetadataCell());
return beginCell()
.storeUint(0, 8) // a null byte prefix
.storeMaybeRef(dict.asCell()!!) // 1 as a single bit, then a reference
.endCell();
}
// Taking flight!
fun poorMansLaunchPad() {
let collectionMetadata = composeCollectionMetadata(
"Best Collection",
"A very descriptive description describing the collection descriptively",
"...link to ipfs or somewhere trusted...",
);
}
// Prefixes the String with a single null byte and converts it to a Cell
// The null byte prefix is used to express metadata in various standards, like NFT or Jetton
inline extends fun asMetadataCell(self: String): Cell {
return beginTailString().concat(self).toCell();
}
```

:::note[Useful links:]

[Token Data Standard in TEPs](https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#nft-metadata-attributes)\
[Off-chain NFT metadata by GetGems](https://github.com/getgems-io/nft-contracts/blob/main/docs/metadata.md)

:::

### NFT Item {#onchain-metadata-nft-item}

```tact
/// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#nft-metadata-attributes
fun composeItemMetadata(
name: String, // full name
description: String, // text description of the NFT
image: String, // link to the image
// There could be other data, see:
// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#nft-metadata-attributes
): Cell {
let dict: map<Int as uint256, Cell> = emptyMap();
dict.set(sha256("name"), name.asMetadataCell());
dict.set(sha256("description"), description.asMetadataCell());
dict.set(sha256("image"), image.asMetadataCell());
return beginCell()
.storeUint(0, 8) // a null byte prefix
.storeMaybeRef(dict.asCell()!!) // 1 as a single bit, then a reference
.endCell();
}
// Taking flight!
fun poorMansLaunchPad() {
let itemMetadata = composeItemMetadata(
"Best Item",
"A very descriptive description describing the item descriptively",
"...link to ipfs or somewhere trusted...",
);
}
// Prefixes the String with a single null byte and converts it to a Cell
// The null byte prefix is used to express metadata in various standards, like NFT or Jetton
inline extends fun asMetadataCell(self: String): Cell {
return beginTailString().concat(self).toCell();
}
```

:::note[Useful links:]

[Token Data Standard in TEPs](https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#nft-metadata-attributes)\
[Off-chain NFT metadata by GetGems](https://github.com/getgems-io/nft-contracts/blob/main/docs/metadata.md)

:::

:::tip[Hey there!]

Didn't find your favorite example of a NFT communication? Have cool implementations in mind? [Contributions are welcome!](https://github.com/tact-lang/tact/issues)
Expand Down
1 change: 1 addition & 0 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"src/prettyPrinter.ts",
"src/error/display-to-json.ts",
"src/grammar/src-info.ts",
"src/grammar/next/grammar.ts",
".github/workflows/tact*.yml"
],
"ignoreDependencies": ["@tact-lang/ton-abi"]
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"author": "Steve Korshakov <[email protected]>",
"license": "MIT",
"scripts": {
"gen:grammar": "ohm generateBundles --withTypes src/grammar/*.ohm",
"gen:grammar:old": "ohm generateBundles --withTypes src/grammar/prev/*.ohm",
"gen:grammar:new": "pgen src/grammar/next/grammar.gg -o src/grammar/next/grammar.ts",
"gen:grammar": "yarn gen:grammar:old && yarn gen:grammar:new",
"gen:pack": "ts-node ./scripts/pack.ts",
"gen:compiler": "ts-node ./scripts/prepare.ts",
"gen": "yarn gen:grammar && yarn gen:pack && yarn gen:compiler",
Expand Down Expand Up @@ -50,6 +52,7 @@
"@tact-lang/opcode": "^0.0.16",
"@ton/core": "0.59.1",
"@ton/crypto": "^3.2.0",
"@tonstudio/parser-runtime": "^0.0.1",
"blockstore-core": "1.0.5",
"change-case": "^4.1.2",
"crc-32": "1.2.2",
Expand All @@ -70,6 +73,7 @@
"@tact-lang/ton-jest": "^0.0.4",
"@ton/sandbox": "^0.23.0",
"@ton/test-utils": "^0.4.2",
"@tonstudio/pgen": "^0.0.1",
"@types/glob": "^8.1.0",
"@types/jest": "^29.5.12",
"@types/json-bigint": "^1.0.4",
Expand Down
2 changes: 1 addition & 1 deletion scripts/copy-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const cp = async (fromGlob: string, toPath: string) => {

const main = async () => {
try {
await cp("./src/grammar/grammar.ohm*", "./dist/grammar/");
await cp("./src/grammar/prev/grammar.ohm*", "./dist/grammar/prev/");
await cp("./src/func/funcfiftlib.*", "./dist/func/");
} catch (e) {
console.error(e);
Expand Down
3 changes: 2 additions & 1 deletion src/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getParser } from "./grammar";
import files from "./imports/stdlib";
import { createVirtualFileSystem, TactError, VirtualFileSystem } from "./main";
import { precompile } from "./pipeline/precompile";
import { defaultParser } from "./grammar/grammar";

export type CheckResultItem = {
type: "error" | "warning";
Expand Down Expand Up @@ -37,7 +38,7 @@ export function check(args: {
ctx = featureEnable(ctx, "external"); // Enable external messages flag to avoid external-specific errors

const ast = getAstFactory();
const parser = getParser(ast);
const parser = getParser(ast, defaultParser);

// Execute check
const items: CheckResultItem[] = [];
Expand Down
4 changes: 4 additions & 0 deletions src/config/parseConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export const optionsSchema = z
* Read more: https://docs.tact-lang.org/book/contracts#interfaces
*/
interfacesGetter: z.boolean().optional(),
/**
* If set to "new", uses new parser. If set to "old", uses legacy parser. Default is "old".
*/
parser: z.union([z.literal("new"), z.literal("old")]).optional(),
/**
* Experimental options that might be removed in the future. Use with caution!
*/
Expand Down
Loading

0 comments on commit 52c01e9

Please sign in to comment.