Skip to content

Commit 17e2249

Browse files
committed
Tolk v0.13 wasm and stdlib
1 parent e2a9638 commit 17e2249

File tree

9 files changed

+261
-48
lines changed

9 files changed

+261
-48
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ton/tolk-js",
3-
"version": "0.12.0",
3+
"version": "0.13.0",
44
"description": "Tolk Language compiler (next-generation FunC)",
55
"main": "dist/index.js",
66
"bin": "./dist/cli.js",

src/tolk-stdlib/common.tolk

Lines changed: 244 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Standard library for Tolk (LGPL licence).
22
// It contains common functions that are available out of the box, the user doesn't have to import anything.
33
// More specific functions are required to be imported explicitly, like "@stdlib/tvm-dicts".
4-
tolk 0.12
4+
tolk 0.13
55

66
/// In Tolk v1.x there would be a type `map<K,V>`.
77
/// Currently, working with dictionaries is still low-level, with raw cells.
@@ -55,6 +55,11 @@ fun tuple.size(self): int
5555
fun tuple.last<T>(self): T
5656
asm "LAST";
5757

58+
/// Pops and returns the last element of a non-empty tuple.
59+
@pure
60+
fun tuple.pop<T>(mutate self): T
61+
asm "TPOP";
62+
5863

5964
/**
6065
Mathematical primitives.
@@ -133,10 +138,10 @@ fun mulDivMod(x: int, y: int, z: int): (int, int)
133138
/// Example: `contract.getCode()` and other methods.
134139
struct contract {}
135140

136-
/// Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`.
137-
/// If necessary, it can be parsed further using primitives such as [parseStandardAddress].
141+
/// Returns the internal address of the current smart contract.
142+
/// If necessary, it can be parsed further using [address.getWorkchain] and others.
138143
@pure
139-
fun contract.getAddress(): slice
144+
fun contract.getAddress(): address
140145
asm "MYADDR";
141146

142147
/// Returns the balance (in nanotoncoins) of the smart contract at the start of Computation Phase.
@@ -210,6 +215,179 @@ fun commitContractDataAndActions(): void
210215
asm "COMMIT";
211216

212217

218+
/**
219+
Auto packing structures to/from cells.
220+
*/
221+
222+
/// PackOptions allows you to control behavior of `obj.toCell()` and similar functions.
223+
struct PackOptions {
224+
/// when a struct has a field of type `bits128` and similar (it's a slice under the hood),
225+
/// by default, compiler inserts runtime checks (get bits/refs count + compare with 128 + compare with 0);
226+
/// these checks ensure that serialized binary data will be correct, but they cost gas;
227+
/// however, if you guarantee that a slice is valid (for example, it comes from trusted sources),
228+
/// set this option to true to disable runtime checks;
229+
/// note: `int32` and other are always validated for overflow without any extra gas,
230+
/// so this flag controls only rarely used `bytesN` / `bitsN` types
231+
skipBitsNFieldsValidation: bool = false,
232+
}
233+
234+
/// UnpackOptions allows you to control behavior of `MyStruct.fromCell(c)` and similar functions.
235+
struct UnpackOptions {
236+
// after finished reading all fields from a cell/slice, call [slice.assertEnd] to ensure no remaining data left;
237+
// it's the default behavior, it ensures that you've fully described data you're reading with a struct;
238+
// example: `struct Point { x: int8; y: int8 }`, input "0102" is ok, "0102FF" will throw excno 9;
239+
// note: setting this to false does not decrease gas (DROP from a stack and ENDS cost the same);
240+
// note: this option controls [T.fromCell] and [T.fromSlice], but is ignored by [slice.loadAny]
241+
assertEndAfterReading: bool = true,
242+
243+
/// this excNo is thrown if a prefix doesn't match, e.g. for `struct (0x01) A` given input "88...";
244+
/// similarly, for a union type, this is thrown when none of the opcodes match
245+
throwIfOpcodeDoesNotMatch: int = 63,
246+
}
247+
248+
/// Convert anything to a cell (most likely, you'll call it for structures).
249+
/// Example:
250+
/// ```
251+
/// var st: MyStorage = { ... };
252+
/// contract.setData(st.toCell());
253+
/// ```
254+
/// Internally, a builder is created, all fields are serialized one by one, and a builder is flushed
255+
/// (beginCell() + serialize fields + endCell()).
256+
@pure
257+
fun T.toCell(self, options: PackOptions = {}): Cell<T>
258+
builtin;
259+
260+
/// Parse anything from a cell (most likely, you'll call it for structures).
261+
/// Example:
262+
/// ```
263+
/// var st = MyStorage.fromCell(contract.getData());
264+
/// ```
265+
/// Internally, a cell is unpacked to a slice, and that slice is parsed
266+
/// (packedCell.beginParse() + read from slice).
267+
@pure
268+
fun T.fromCell(packedCell: cell, options: UnpackOptions = {}): T
269+
builtin;
270+
271+
/// Parse anything from a slice (most likely, you'll call it for structures).
272+
/// Example:
273+
/// ```
274+
/// var msg = CounterIncrement.fromSlice(cs);
275+
/// ```
276+
/// All fields are read from a slice immediately.
277+
/// If a slice is corrupted, an exception is thrown (most likely, excode 9 "cell underflow").
278+
/// Note, that a passed slice is NOT mutated, its internal pointer is NOT shifted.
279+
/// If you need to mutate it, like `cs.loadInt()`, consider calling `cs.loadAny<CounterIncrement>()`.
280+
@pure
281+
fun T.fromSlice(rawSlice: slice, options: UnpackOptions = {}): T
282+
builtin;
283+
284+
/// Parse anything from a slice, shifting its internal pointer.
285+
/// Similar to `slice.loadUint()` and others, but allows loading structures.
286+
/// Example:
287+
/// ```
288+
/// var st: MyStorage = cs.loadAny(); // or cs.loadAny<MyStorage>()
289+
/// ```
290+
/// Similar to `MyStorage.fromSlice(cs)`, but called as a slice method and mutates the slice.
291+
/// Note: [options.assertEndAfterReading] is ignored by this function, because it's actually intended
292+
/// to read data from the middle.
293+
@pure
294+
fun slice.loadAny<T>(mutate self, options: UnpackOptions = {}): T
295+
builtin;
296+
297+
/// Skip anything in a slice, shifting its internal pointer.
298+
/// Similar to `slice.skipBits()` and others, but allows skipping structures.
299+
/// Example:
300+
/// ```
301+
/// struct TwoInts { a: int32; b: int32; }
302+
/// cs.skipAny<TwoInts>(); // skips 64 bits
303+
/// ```
304+
@pure
305+
fun slice.skipAny<T>(mutate self, options: UnpackOptions = {}): self
306+
builtin;
307+
308+
/// Store anything to a builder.
309+
/// Similar to `builder.storeUint()` and others, but allows storing structures.
310+
/// Example:
311+
/// ```
312+
/// var b = beginCell().storeUint(32).storeAny(msgBody).endCell();
313+
/// ```
314+
@pure
315+
fun builder.storeAny<T>(mutate self, v: T, options: PackOptions = {}): self
316+
builtin;
317+
318+
/// Returns serialization prefix of a struct. Works at compile-time.
319+
/// Example: for `struct (0xF0) AssetRegular { ... }` will return `240`.
320+
@pure
321+
fun T.getDeclaredPackPrefix(): int
322+
builtin;
323+
324+
/// Returns serialization prefix length of a struct. Works at compile-time.
325+
/// Example: for `struct (0xF0) AssetRegular { ... }` will return `16`.
326+
@pure
327+
fun T.getDeclaredPackPrefixLen(): int
328+
builtin;
329+
330+
/// Cell<T> represents a typed cell reference (as opposed to untyped `cell`).
331+
/// Example:
332+
/// ```
333+
/// struct ExtraData { ... }
334+
///
335+
/// struct MyStorage {
336+
/// ...
337+
/// extra: Cell<ExtraData>; // TL-B `^ExtraData`
338+
/// optional: Cell<ExtraData>?; // TL-B `(Maybe ^ExtraData)`
339+
/// code: cell; // TL-B `^Cell`
340+
/// data: cell?; // TL-B `(Maybe ^Cell)`
341+
/// }
342+
/// ```
343+
/// Note, that `st = MyStorage.fromSlice(s)` does NOT deep-load any refs; `st.extra` is `Cell<T>`, not `T`;
344+
/// you should manually call `st.extra.load()` to get T (ExtraData in this example).
345+
struct Cell<T> {
346+
tvmCell: cell;
347+
}
348+
349+
/// Parse data from already loaded cell reference.
350+
/// Example:
351+
/// ```
352+
/// struct MyStorage { ... extra: Cell<ExtraData>; }
353+
///
354+
/// var st = MyStorage.fromCell(contract.getData());
355+
/// // st.extra is cell; if we need to unpack it, we do
356+
/// var extra = st.extra.load(); // it's ExtraData, unpacked from loaded ref
357+
/// ```
358+
@pure
359+
fun Cell<T>.load(self, options: UnpackOptions = {}): T
360+
builtin;
361+
362+
/// Converts a typed cell into a slice.
363+
@pure
364+
fun Cell<T>.beginParse(self): slice
365+
asm "CTOS";
366+
367+
/// RemainingBitsAndRefs is a special built-in type to get "all the rest" slice tail on reading.
368+
/// Example:
369+
/// ```
370+
/// struct JettonMessage {
371+
/// ... some fields
372+
/// forwardPayload: RemainingBitsAndRefs;
373+
/// }
374+
/// ```
375+
/// When you deserialize JettonMessage, forwardPayload contains "everything left after reading fields above".
376+
type RemainingBitsAndRefs = slice;
377+
378+
/// Creates a cell with zero bits and references.
379+
/// Equivalent to `beginCell().endCell()` but cheaper.
380+
@pure
381+
fun createEmptyCell(): cell
382+
asm "<b b> PUSHREF";
383+
384+
/// Creates a slice with zero remaining bits and references.
385+
/// Equivalent to `beginCell().endCell().beginParse()` but cheaper.
386+
@pure
387+
fun createEmptySlice(): slice
388+
asm "x{} PUSHSLICE";
389+
390+
213391
/**
214392
Signature checks, hashing, cryptography.
215393
*/
@@ -378,6 +556,14 @@ fun slice.depth(self): int
378556
fun builder.depth(self): int
379557
asm "BDEPTH";
380558

559+
/// Returns the number of stack slots anyVariable occupies (works at compile-time).
560+
/// Example: sizeof(nullableInt) = 1, because `int?` is 1 TVM slot holding either NULL or a value.
561+
/// Example: sizeof(somePoint) = 2 for `struct Point { x:int, y: int }`: two fields one slot per each.
562+
/// Useful for debugging or when preparing stack contents for RUNVM.
563+
@pure
564+
fun sizeof<T>(anyVariable: T): int
565+
builtin;
566+
381567

382568
/**
383569
Debug primitives.
@@ -407,15 +593,6 @@ fun debug.dumpStack(): void
407593
When you _preload_ some data, you just get the result without mutating the slice.
408594
*/
409595

410-
/// Compile-time function that converts a constant string to TL-encoded MsgAddressInt (TVM slice).
411-
/// Example: stringAddressToSlice("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5")
412-
/// Example: stringAddressToSlice("0:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8")
413-
/// Note: it's a compile-time function, `stringAddressToSlice(slice_var)` does not work.
414-
/// Use `parseStandardAddress` to decode a slice at runtime into workchain and hash.
415-
@pure
416-
fun stringAddressToSlice(constStringAddress: slice): slice
417-
builtin;
418-
419596
/// Compile-time function that converts a constant hex-encoded string to N/2 bytes.
420597
/// Example: `const v = stringHexToSlice("abcdef")` = slice with 3 bytes `[ 0xAB, 0xCD, 0xEF ]`
421598
/// Note: stringHexToSlice(slice_var) does not work! It accepts a constant string and works at compile-time.
@@ -430,7 +607,7 @@ fun stringHexToSlice(constStringBytesHex: slice): slice
430607
fun cell.beginParse(self): slice
431608
asm "CTOS";
432609

433-
/// Checks if slice is empty. If not, throws an exception.
610+
/// Checks if slice is empty. If not, throws an exception with code 9.
434611
fun slice.assertEnd(self): void
435612
asm "ENDS";
436613

@@ -573,6 +750,11 @@ fun builder.storeUint(mutate self, x: int, len: int): self
573750
fun builder.storeSlice(mutate self, s: slice): self
574751
asm "STSLICER";
575752

753+
/// Stores an address into a builder.
754+
@pure
755+
fun builder.storeAddress(mutate self, addr: address): self
756+
asm "STSLICER";
757+
576758
/// Stores amount of Toncoins into a builder.
577759
@pure
578760
fun builder.storeCoins(mutate self, x: coins): self
@@ -694,37 +876,64 @@ fun builder.bitsCount(self): int
694876
where `u`, `x`, and `s` have the same meaning as for `addr_std`.
695877
*/
696878

697-
/// Loads from slice [s] the only prefix that is a valid `MsgAddress`,
698-
/// and returns both this prefix `s'` and the remainder `s''` of [s] as slices.
879+
/// Compile-time function that parses a valid contract address.
880+
/// Example: address("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5")
881+
/// Example: address("0:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8")
882+
/// Returns `address`, which can be stored in a builder, compared with `==`, etc.
699883
@pure
700-
fun slice.loadAddress(mutate self): slice
701-
asm( -> 1 0) "LDMSGADDR";
884+
fun address(stdAddress: slice): address
885+
builtin;
886+
887+
/// Creates a slice representing TL addr_none$00 (two `0` bits).
888+
@pure
889+
fun createAddressNone(): address
890+
asm "b{00} PUSHSLICE";
891+
892+
/// Returns if it's an empty address.
893+
/// Don't confuse it with null! Empty address is a slice with two `0` bits.
894+
/// In TL/B, it's addr_none$00.
895+
@pure
896+
fun address.isNone(self): bool
897+
asm "b{00} SDBEGINSQ" "NIP";
702898

703-
/// Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`.
704-
/// If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown.
899+
/// Returns if it's a standard (internal) address. Such addresses contain workchain (8 bits) and hash (256 bits).
900+
/// All contract addresses are internal, so it's the most practical use case.
901+
/// In TL/B it's addr_std$10.
902+
/// For internal addresses, you can call [address.getWorkchain] and [address.getWorkchainAndHash].
705903
@pure
706-
fun parseAddress(s: slice): tuple
707-
asm "PARSEMSGADDR";
904+
fun address.isInternal(self): bool
905+
asm "b{10} SDBEGINSQ" "NIP";
708906

709-
/// Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`),
710-
/// applies rewriting from the anycast (if present) to the same-length prefix of the address,
711-
/// and returns both the workchain and the 256-bit address as integers.
712-
/// If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`,
713-
/// throws a cell deserialization exception.
907+
/// Returns if it's an external address, used to communication with the outside world.
908+
/// In TL/B it's addr_extern$01.
714909
@pure
715-
fun parseStandardAddress(s: slice): (int, int)
910+
fun address.isExternal(self): bool
911+
asm "b{01} SDBEGINSQ" "NIP";
912+
913+
/// Extracts workchain and hash from a standard (internal) address.
914+
/// If the address is not internal, throws a cell deserialization exception.
915+
@pure
916+
fun address.getWorkchainAndHash(self): (int8, uint256)
716917
asm "REWRITESTDADDR";
717918

718-
/// Creates a slice representing TL addr_none$00 (two `0` bits).
919+
/// Extracts workchain from a standard (internal) address.
920+
/// If the address is not internal, throws a cell deserialization exception.
719921
@pure
720-
fun createAddressNone(): slice
721-
asm "b{00} PUSHSLICE";
922+
fun address.getWorkchain(self): int8
923+
asm "REWRITESTDADDR" "DROP";
722924

723-
/// Returns if a slice pointer contains an empty address.
724-
/// In other words, a slice starts with two `0` bits (TL addr_none$00).
925+
/// Checks whether two addresses are equal. Equivalent to `a == b`.
926+
/// Deprecated! Left for smoother transition from FunC, where you used `slice` everywhere.
927+
/// Use just `a == b` and `a != b` to compare addresses, don't use bitsEqual.
725928
@pure
726-
fun addressIsNone(s: slice): bool
727-
asm "2 PLDU" "0 EQINT";
929+
@deprecated("use `senderAddress == ownerAddress`, not `senderAddress.bitsEqual(ownerAddress)`")
930+
fun address.bitsEqual(self, b: address): bool
931+
asm "SDEQ";
932+
933+
/// Loads from slice [s] a valid `MsgAddress` (none/internal/external).
934+
@pure
935+
fun slice.loadAddress(mutate self): address
936+
asm( -> 1 0) "LDMSGADDR";
728937

729938

730939
/**

src/tolk-stdlib/gas-payments.tolk

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// A part of standard library for Tolk
2-
tolk 0.12
2+
tolk 0.13
33

44
/**
55
Gas and payment related primitives.
66
*/
77

88
/// Returns amount of gas (in gas units) consumed in current Computation Phase.
9-
fun getGasConsumedAtTheMoment(): coins
9+
fun getGasConsumedAtTheMoment(): int
1010
asm "GASCONSUMED";
1111

1212
/// This function is required to be called when you process an external message (from an outer world)

src/tolk-stdlib/lisp-lists.tolk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// A part of standard library for Tolk
2-
tolk 0.12
2+
tolk 0.13
33

44
/**
55
Lisp-style lists are nested 2-elements tuples: `[1, [2, [3, null]]]` represents list `[1, 2, 3]`.

src/tolk-stdlib/tvm-dicts.tolk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// A part of standard library for Tolk
2-
tolk 0.12
2+
tolk 0.13
33

44
/**
55
Dictionaries are represented as `cell` data type (cells can store anything, dicts in particular).

src/tolk-stdlib/tvm-lowlevel.tolk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// A part of standard library for Tolk
2-
tolk 0.12
2+
tolk 0.13
33

44
/// Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls.
55
/// The primitive returns the current value of `c3`.

0 commit comments

Comments
 (0)