Skip to content

Commit b13bcb4

Browse files
committed
[Tolk] Struct PackOptions and UnpackOptions
Allow to control fromCell/toCell behavior
1 parent 5ae6670 commit b13bcb4

17 files changed

+575
-161
lines changed

crypto/smartcont/tolk-stdlib/common.tolk

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,32 @@ fun commitContractDataAndActions(): void
219219
Auto packing structures to/from cells.
220220
*/
221221

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+
222248
/// Convert anything to a cell (most likely, you'll call it for structures).
223249
/// Example:
224250
/// ```
@@ -228,7 +254,7 @@ fun commitContractDataAndActions(): void
228254
/// Internally, a builder is created, all fields are serialized one by one, and a builder is flushed
229255
/// (beginCell() + serialize fields + endCell()).
230256
@pure
231-
fun T.toCell(self): Cell<T>
257+
fun T.toCell(self, options: PackOptions = {}): Cell<T>
232258
builtin;
233259

234260
/// Parse anything from a cell (most likely, you'll call it for structures).
@@ -239,7 +265,7 @@ fun T.toCell(self): Cell<T>
239265
/// Internally, a cell is unpacked to a slice, and that slice is parsed
240266
/// (packedCell.beginParse() + read from slice).
241267
@pure
242-
fun T.fromCell(packedCell: cell): T
268+
fun T.fromCell(packedCell: cell, options: UnpackOptions = {}): T
243269
builtin;
244270

245271
/// Parse anything from a slice (most likely, you'll call it for structures).
@@ -252,7 +278,7 @@ fun T.fromCell(packedCell: cell): T
252278
/// Note, that a passed slice is NOT mutated, its internal pointer is NOT shifted.
253279
/// If you need to mutate it, like `cs.loadInt()`, consider calling `cs.loadAny<CounterIncrement>()`.
254280
@pure
255-
fun T.fromSlice(rawSlice: slice): T
281+
fun T.fromSlice(rawSlice: slice, options: UnpackOptions = {}): T
256282
builtin;
257283

258284
/// Parse anything from a slice, shifting its internal pointer.
@@ -262,8 +288,10 @@ fun T.fromSlice(rawSlice: slice): T
262288
/// var st: MyStorage = cs.loadAny(); // or cs.loadAny<MyStorage>()
263289
/// ```
264290
/// 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.
265293
@pure
266-
fun slice.loadAny<T>(mutate self): T
294+
fun slice.loadAny<T>(mutate self, options: UnpackOptions = {}): T
267295
builtin;
268296

269297
/// Skip anything in a slice, shifting its internal pointer.
@@ -274,7 +302,7 @@ fun slice.loadAny<T>(mutate self): T
274302
/// cs.skipAny<TwoInts>(); // skips 64 bits
275303
/// ```
276304
@pure
277-
fun slice.skipAny<T>(mutate self): self
305+
fun slice.skipAny<T>(mutate self, options: UnpackOptions = {}): self
278306
builtin;
279307

280308
/// Store anything to a builder.
@@ -284,7 +312,19 @@ fun slice.skipAny<T>(mutate self): self
284312
/// var b = beginCell().storeUint(32).storeAny(msgBody).endCell();
285313
/// ```
286314
@pure
287-
fun builder.storeAny<T>(mutate self, v: T): self
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
288328
builtin;
289329

290330
/// Cell<T> represents a typed cell reference (as opposed to untyped `cell`).
@@ -316,7 +356,7 @@ struct Cell<T> {
316356
/// var extra = st.extra.load(); // it's ExtraData, unpacked from loaded ref
317357
/// ```
318358
@pure
319-
fun Cell<T>.load(self): T
359+
fun Cell<T>.load(self, options: UnpackOptions = {}): T
320360
builtin;
321361

322362
/// Converts a typed cell into a slice.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
struct Point {
2+
x: int8;
3+
y: int8;
4+
}
5+
6+
fun main() {
7+
return Point.getDeclaredPackPrefix();
8+
}
9+
10+
/**
11+
@compilation_should_fail
12+
@stderr type `Point` does not have a serialization prefix
13+
*/

tolk-tester/tests/pack-unpack-1.tolk

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ fun test3() {
4848
@method_id(104)
4949
fun test4() {
5050
var s = stringHexToSlice("000000010000000200000003");
51-
var p = Point.fromSlice(s); // does not mutate s
51+
var p = Point.fromSlice(s, {assertEndAfterReading: false}); // does not mutate s
5252
return (p, s.remainingBitsCount());
5353
}
5454

@@ -114,11 +114,11 @@ fun main(c: cell) {
114114
32 STI // b
115115
ENDC // c
116116
CTOS // s
117-
32 PUSHINT // s '12=32
117+
32 PUSHINT // s '15=32
118118
SDSKIPFIRST // s
119-
32 PUSHINT // s '13=32
119+
32 PUSHINT // s '16=32
120120
SDSKIPFIRST // s
121-
SBITS // '14
121+
SBITS // '17
122122
}>
123123
"""
124124

tolk-tester/tests/pack-unpack-4.tolk

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ fun invalid105_slice(): slice asm "b{1100} PUSHSLICE"; // prefix 0b11 doesn't e
9292
@method_id(105)
9393
fun test5() {
9494
try {
95-
var u = U105.fromSlice(invalid105_slice());
95+
var u = U105.fromSlice(invalid105_slice(), {throwIfOpcodeDoesNotMatch: 9});
9696
return (u is int8) ? 8 : -8;
9797
} catch (excode) {
9898
return excode;
@@ -125,37 +125,37 @@ fun main() {}
125125
"""
126126
test6 PROC:<{ //
127127
b{010000000000001111} PUSHSLICE // s
128-
b{00} SDBEGINSQ // s '6
128+
b{00} SDBEGINSQ // s '8
129129
IF:<{ // s
130-
8 LDI // '10 s
131-
DROP // '10
132-
42 PUSHINT // 'USlot1 'UTag=42
130+
8 LDI // '12 s
131+
42 PUSHINT // 'USlot1 s 'UTag=42
133132
}>ELSE<{ // s
134-
b{01} SDBEGINSQ // s '6
133+
b{01} SDBEGINSQ // s '8
135134
IF:<{ // s
136-
16 LDI // '15 s
137-
DROP // '15
138-
44 PUSHINT // 'USlot1 'UTag=44
135+
16 LDI // '17 s
136+
44 PUSHINT // 'USlot1 s 'UTag=44
139137
}>ELSE<{ // s
140-
b{10} SDBEGINSQ // s '6
138+
b{10} SDBEGINSQ // s '8
141139
IF:<{ // s
142-
32 LDI // '20 s
143-
DROP // '20
144-
46 PUSHINT // 'USlot1 'UTag=46
140+
32 LDI // '22 s
141+
46 PUSHINT // 'USlot1 s 'UTag=46
145142
}>ELSE<{ // s
146-
b{11} SDBEGINSQ // s '6
147-
DROP // s
148-
64 LDI // '25 s
149-
DROP // '25
150-
48 PUSHINT // 'USlot1 'UTag=48
143+
b{11} SDBEGINSQ // s '8
144+
IFNOTJMP:<{ // s
145+
63 THROW
146+
}>
147+
64 LDI // '27 s
148+
48 PUSHINT // 'USlot1 s 'UTag=48
151149
}>
152150
}>
153-
}> // u.USlot1 u.UTag
154-
44 EQINT // u.USlot1 '27
151+
}>
152+
SWAP // 'USlot1 'UTag s
153+
ENDS // u.USlot1 u.UTag
154+
44 EQINT // u.USlot1 '29
155155
IFJMP:<{ // u.USlot1
156156
}> // u.USlot1
157157
DROP //
158-
-1 PUSHINT // '29=-1
158+
-1 PUSHINT // '31=-1
159159
}>
160160
"""
161161
*/

tolk-tester/tests/pack-unpack-5.tolk

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,18 +166,38 @@ fun test9() {
166166
return (Test9_f1.estimatePackSize(), Test9_f2.estimatePackSize(), Test9_f3.estimatePackSize(), Test9_f4.estimatePackSize());
167167
}
168168

169+
struct Test10_1 {
170+
a: int32;
171+
b: builder; // unpredictable
172+
}
173+
174+
type Test10_2 = (Test10_1, bool?, RemainingBitsAndRefs);
175+
176+
@method_id(110)
177+
fun test10() {
178+
return (Test10_1.estimatePackSize(), Test10_2.estimatePackSize());
179+
}
180+
181+
@method_id(120)
182+
fun test20() {
183+
return (Test7_1 .getDeclaredPackPrefixLen(), Test7_1 .getDeclaredPackPrefix(),
184+
CellInner8_1.getDeclaredPackPrefixLen(), CellInner8_1.getDeclaredPackPrefix());
185+
}
186+
169187
fun main() {
170188
__expect_type(int8.estimatePackSize(), "[int, int, int, int]");
171189
}
172190

173191
/**
174-
@testcase | 101 | | [ 32 32 0 0 ] [ 64 64 0 0 ] [ 1 1 0 0 ] [ 1 1 0 0 ] [ 0 0 0 0 ] [ 4 124 0 0 ] [ 6 6 0 0 ] [ 64 64 0 0 ]
192+
@testcase | 101 | | [ 32 32 0 0 ] [ 64 64 0 0 ] [ 1 1 0 0 ] [ 1 1 0 0 ] [ 0 9999 0 4 ] [ 4 124 0 0 ] [ 6 6 0 0 ] [ 64 64 0 0 ]
175193
@testcase | 102 | | [ 32 32 0 0 ] [ 1 33 0 0 ] [ 1 33 0 0 ] [ 17 33 0 0 ]
176194
@testcase | 103 | | [ 67 199 0 0 ] [ 69 599 0 0 ]
177195
@testcase | 104 | | [ 4 534 0 0 ] [ 2 536 0 0 ] [ 6 1070 0 0 ]
178-
@testcase | 105 | | [ 2 2 2 4 ] [ 5 5 0 5 ] [ 4 152 1 4 ]
196+
@testcase | 105 | | [ 2 2 2 4 ] [ 5 9999 0 9 ] [ 4 152 1 4 ]
179197
@testcase | 106 | | [ 64 64 0 0 ] [ 5 37 1 1 ] [ 5 65 0 1 ]
180198
@testcase | 107 | | [ 16 16 0 0 ] [ 16 16 0 0 ]
181199
@testcase | 108 | | [ 10 275 0 0 ] [ 34 158 0 0 ] [ 47 1143 0 1 ]
182200
@testcase | 109 | | [ 34 130 0 0 ] [ 33 159 0 0 ] [ 4 8 0 0 ] [ 3 7 0 0 ]
201+
@testcase | 110 | | [ 32 9999 0 4 ] [ 33 9999 0 8 ]
202+
@testcase | 120 | | 16 4128 1 1
183203
*/

0 commit comments

Comments
 (0)