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
5555fun 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.
134139struct 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
378556fun 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
430607fun 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 .
434611fun slice.assertEnd(self): void
435612 asm "ENDS";
436613
@@ -573,6 +750,11 @@ fun builder.storeUint(mutate self, x: int, len: int): self
573750fun 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
578760fun 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/**
0 commit comments