Skip to content

Commit 1774c05

Browse files
committed
[Tolk] Modern onInternalMessage and onBouncedMessage
Instead of "FunC-style" parsing msg_cell, use `in.senderAddress`, `in.originalForwardFee`, that are mapped onto TVM-11 instructions
1 parent 6354018 commit 1774c05

36 files changed

+825
-14
lines changed

crypto/smartcont/tolk-stdlib/common.tolk

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ fun slice.remainingBitsAndRefsCount(self): (int, int)
843843

844844
/// Checks whether a slice is empty (i.e., contains no bits of data and no cell references).
845845
@pure
846-
fun slice.isEnd(self): bool
846+
fun slice.isEmpty(self): bool
847847
asm "SEMPTY";
848848

849849
/// Checks whether a slice has no bits of data.
@@ -1311,14 +1311,53 @@ fun sendRawMessage(msg: cell, mode: int): void
13111311
Receiving and handling messages.
13121312
*/
13131313

1314+
/// InMessage is an input for `onInternalMessage` — when your contract accepts a non-bounced message.
1315+
/// Internally, some data exist on the stack, some can be acquired with TVM instructions.
1316+
/// The compiler replaces `in.someField` and gets a value correctly.
1317+
/// Example:
1318+
/// ```
1319+
/// fun onInternalMessage(in: InMessage) {
1320+
/// in.senderAddress // actually calls `INMSG_SRC` asm instruction
1321+
/// in.body // actually gets from a TVM stack
1322+
/// ```
1323+
struct InMessage {
1324+
senderAddress: address; // an internal address from which the message arrived
1325+
valueCoins: coins; // ton amount attached to an incoming message
1326+
valueExtra: dict; // extra currencies attached to an incoming message
1327+
originalForwardFee: coins; // fee that was paid by the sender
1328+
createdLt: uint64; // logical time when a message was created
1329+
createdAt: uint32; // unixtime when a message was created
1330+
body: slice; // message body, parse it with `lazy AllowedMsg.fromSlice(in.body)`
1331+
}
1332+
1333+
/// InMessageBounced is an input for `onBouncedMessage`.
1334+
/// Very similar to a non-bounced input [InMessage].
1335+
/// Note, that `bouncedBody` is currently 256 bits: 0xFFFFFFFF + 224 bits from the originally sent message.
1336+
/// Parse it with care!
1337+
/// Example:
1338+
/// ```
1339+
/// fun onBouncedMessage(in: InMessageBounced) {
1340+
/// in.bouncedBody.skipBouncedPrefix();
1341+
/// val originalOpcode = in.bouncedBody.loadUint(32);
1342+
/// ```
1343+
struct InMessageBounced {
1344+
senderAddress: address; // an internal address from which the message was bounced
1345+
valueCoins: coins; // ton amount attached to a message
1346+
valueExtra: dict; // extra currencies attached to a message
1347+
originalForwardFee: coins; // comission that the sender has payed to send this message
1348+
createdLt: uint64; // logical time when a message was created (and bounced)
1349+
createdAt: uint32; // unixtime when a message was created (and bounced)
1350+
bouncedBody: slice; // currently 256 bits: 0xFFFFFFFF + 224 bits sent originally
1351+
}
1352+
13141353
/// Skip 0xFFFFFFFF prefix (when a message is bounced).
13151354
@pure
13161355
fun slice.skipBouncedPrefix(mutate self): self
13171356
asm "32 LDU" "NIP";
13181357

13191358
/// Load msgFlags from incoming message body (4 bits).
13201359
@pure
1321-
@deprecated("use `onInternalMessage(in: InMessage)` and `in.isBounced` instead of parsing msgCell manually")
1360+
@deprecated("use `onBouncedMessage` handler instead of parsing msgCell flags manually")
13221361
fun slice.loadMessageFlags(mutate self): int
13231362
asm( -> 1 0) "4 LDU";
13241363

@@ -1349,3 +1388,4 @@ fun slice.skipMessageQueryId(mutate self): self
13491388
fun builder.storeMessageQueryId(mutate self, queryId: int): self
13501389
asm(queryId self) "64 STU";
13511390

1391+

crypto/smartcont/tolk-stdlib/gas-payments.tolk

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,17 @@ fun calculateGasFeeWithoutFlatPrice(workchain: int8, gasUsed: coins): coins
4545
fun calculateStorageFee(workchain: int8, seconds: int, bits: int, cells: int): coins
4646
asm(cells bits seconds workchain) "GETSTORAGEFEE";
4747

48-
/// Calculates amount of nanotoncoins you should pay to send a message of specified size.
49-
fun calculateMessageFee(workchain: int8, bits: int, cells: int): coins
48+
/// Calculates amount of nanotoncoins you should pay to send a message of a specified size.
49+
fun calculateForwardFee(workchain: int8, bits: int, cells: int): coins
5050
asm(cells bits workchain) "GETFORWARDFEE";
5151

52-
/// Same as [calculateMessageFee], but without lump price (you have supposed to read https://docs.ton.org/develop/howto/fees-low-level)
53-
fun calculateMessageFeeWithoutLumpPrice(workchain: int8, bits: int, cells: int): coins
52+
/// Same as [calculateForwardFee], but without lump price (you have supposed to read https://docs.ton.org/develop/howto/fees-low-level)
53+
fun calculateForwardFeeWithoutLumpPrice(workchain: int8, bits: int, cells: int): coins
5454
asm(cells bits workchain) "GETFORWARDFEESIMPLE";
5555

5656
/// Calculates fee that was paid by the sender of an incoming internal message.
57-
fun calculateOriginalMessageFee(workchain: int8, incomingFwdFee: coins): coins
57+
@deprecated("use modern `onInternalMessage` and access `in.originalForwardFee` directly")
58+
fun calculateOriginalForwardFee(workchain: int8, incomingFwdFee: coins): coins
5859
asm(incomingFwdFee workchain) "GETORIGINALFWDFEE";
5960

6061
/// Returns the amount of nanotoncoins current contract debts for storage. ("due" and "debt" are synonyms)

tolk-tester/tests/cells-slices.tolk

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,11 @@ fun slice.sumNumbersInSlice(mutate self): int {
112112
fun test10() {
113113
var ref = beginCell().storeInt(100, 32).endCell();
114114
var s: slice = beginCell().storeInt(1, 32).storeInt(2, 32).storeRef(ref).endCell().beginParse();
115-
var result = (s.remainingBitsCount(), s.sumNumbersInSlice(), s.remainingBitsCount(), s.isEnd(), s.isEndOfBits(), s.isEndOfRefs());
115+
var result = (s.remainingBitsCount(), s.sumNumbersInSlice(), s.remainingBitsCount(), s.isEmpty(), s.isEndOfBits(), s.isEndOfRefs());
116116
var ref2: cell = s.loadRef();
117117
var s2: slice = ref2.beginParse();
118118
s.assertEnd();
119-
return (result, s2.loadInt(32), s2.isEnd());
119+
return (result, s2.loadInt(32), s2.isEmpty());
120120
}
121121

122122
@method_id(111)
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
2+
fun setTvmRegisterC7(c7: [tuple]): void
3+
asm "c7 POP";
4+
5+
@noinline
6+
fun emulateC7PresentForTests(bounced: bool, senderAddress: address, fwdFee: coins, createdLt: int, createdAt: int, valueCoins: coins, valueExtra: dict) {
7+
var c7_inner = createEmptyTuple();
8+
repeat (17) {
9+
c7_inner.push(null);
10+
}
11+
12+
var inmsgparams = createEmptyTuple();
13+
inmsgparams.push(null); // bounce (not present in InMessage)
14+
inmsgparams.push(bounced);
15+
inmsgparams.push(senderAddress);
16+
inmsgparams.push(fwdFee);
17+
inmsgparams.push(createdLt);
18+
inmsgparams.push(createdAt);
19+
inmsgparams.push(null); // orig value (not present in InMessage)
20+
inmsgparams.push(valueCoins); // in onInternalMessage, from a stack; in onBouncedMessage, from TVM
21+
inmsgparams.push(valueExtra);
22+
inmsgparams.push(null); // state init (not present in InMessage)
23+
c7_inner.push(inmsgparams);
24+
25+
setTvmRegisterC7([c7_inner]);
26+
}
27+
28+
fun invokeTest(body: slice, method_id: int): void
29+
asm "c3 PUSH" "EXECUTE"; // stack: body
30+
31+
@method_id(101)
32+
fun handle1(in: InMessage) {
33+
return in.senderAddress.getWorkchain();
34+
}
35+
36+
@method_id(201)
37+
fun entrypoint1() {
38+
emulateC7PresentForTests(true, address("EQCtrtTXEAoSpoERmiqOnICe9LHxn2N89N4BH9qdHlrG-U0i"), 100, 20, 30, 900, null);
39+
invokeTest("", 101);
40+
}
41+
42+
@method_id(102)
43+
fun handle2(in: InMessage) {
44+
return (in.valueCoins, in.senderAddress.isInternal());
45+
}
46+
47+
@method_id(202)
48+
fun entrypoint2() {
49+
emulateC7PresentForTests(false, address("EQCtrtTXEAoSpoERmiqOnICe9LHxn2N89N4BH9qdHlrG-U0i"), 100, 20, 30, 888, null);
50+
invokeTest("", 102);
51+
}
52+
53+
@method_id(103)
54+
fun handle3(`in()`: InMessage) {
55+
return (
56+
`in()`.senderAddress.getWorkchainAndHash(),
57+
`in()`.valueCoins,
58+
`in()`.valueExtra,
59+
`in()`.createdLt,
60+
`in()`.createdAt,
61+
`in()`.body.isEmpty(),
62+
)
63+
}
64+
65+
@method_id(203)
66+
fun entrypoint3() {
67+
emulateC7PresentForTests(true, address("-1:000000000000000000000000000000000000000000000000000000000000FFFF"), 100, 20, 30, 999, null);
68+
invokeTest("00", 103);
69+
}
70+
71+
fun onInternalMessage(in: InMessage) {
72+
__expect_type(in.body, "slice");
73+
__expect_type(in.senderAddress, "address");
74+
__expect_type(in.valueCoins, "coins");
75+
return in.originalForwardFee;
76+
}
77+
78+
/**
79+
@testcase | 201 | | 0
80+
@testcase | 202 | | 888 -1
81+
@testcase | 203 | | -1 65535 999 (null) 20 30 0
82+
83+
@fif_codegen
84+
"""
85+
handle1() PROC:<{ // in.body
86+
DROP //
87+
INMSG_SRC // '1
88+
REWRITESTDADDR
89+
DROP // '3
90+
}>
91+
"""
92+
93+
@fif_codegen
94+
"""
95+
handle2() PROC:<{ // in.body
96+
DROP //
97+
INMSG_VALUE // '1
98+
INMSG_SRC // '1 '3
99+
b{10} SDBEGINSQ
100+
NIP // '1 '5
101+
}>
102+
"""
103+
104+
@fif_codegen
105+
"""
106+
handle3() PROC:<{
107+
INMSG_SRC
108+
REWRITESTDADDR
109+
INMSG_VALUE
110+
INMSG_VALUEEXTRA
111+
INMSG_LT
112+
INMSG_UTIME
113+
s0 s6 XCHG
114+
SEMPTY
115+
s5 s6 XCHG
116+
s4 s5 XCHG
117+
s3 s4 XCHG
118+
s1 s3 s0 XCHG3
119+
}>
120+
"""
121+
122+
@fif_codegen
123+
"""
124+
onInternalMessage() PROC:<{ // in.body
125+
DROP
126+
INMSG_BOUNCED
127+
0 THROWIF
128+
INMSG_FWDFEE
129+
0 PUSHINT
130+
GETORIGINALFWDFEE
131+
}>
132+
"""
133+
134+
*/
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
@method_id(101)
3+
fun test1() {
4+
return 0;
5+
}
6+
7+
fun onInternalMessage(in: InMessage) {
8+
return in.valueExtra;
9+
}
10+
11+
/**
12+
@testcase | 101 | | 0
13+
14+
@fif_codegen
15+
"""
16+
onInternalMessage() PROC:<{
17+
DROP
18+
INMSG_BOUNCED
19+
0 THROWIF
20+
INMSG_VALUEEXTRA
21+
}>
22+
"""
23+
*/
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
@method_id(101)
3+
fun test1() {
4+
return 0;
5+
}
6+
7+
fun onBouncedMessage(in: InMessageBounced) {
8+
in.bouncedBody.skipBouncedPrefix();
9+
throw in.bouncedBody.loadUint(32);
10+
}
11+
12+
fun onInternalMessage(in: InMessage) {
13+
return in.body;
14+
}
15+
16+
/**
17+
@testcase | 101 | | 0
18+
19+
@fif_codegen
20+
"""
21+
onInternalMessage() PROC:<{
22+
INMSG_BOUNCED
23+
IFJMP:<{
24+
32 LDU
25+
NIP
26+
32 LDU
27+
DROP
28+
THROWANY
29+
}> // in.body
30+
}>
31+
"""
32+
*/
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
@method_id(101)
3+
fun test1() {
4+
return 0;
5+
}
6+
7+
@inline_ref
8+
fun onBouncedMessage(in: InMessageBounced) {
9+
contract.setData(createEmptyCell());
10+
}
11+
12+
fun onInternalMessage(in: InMessage) {
13+
return 123;
14+
}
15+
16+
/**
17+
@testcase | 101 | | 0
18+
19+
@fif_codegen
20+
"""
21+
onBouncedMessage() PROCREF:<{ // in.body
22+
DROP
23+
<b b> PUSHREF
24+
c4 POP
25+
}>
26+
"""
27+
28+
@fif_codegen
29+
"""
30+
onInternalMessage() PROC:<{ // in.body
31+
INMSG_BOUNCED // in.body '1
32+
IFJMP:<{ // in.body
33+
onBouncedMessage() INLINECALLDICT
34+
}>
35+
DROP
36+
123 PUSHINT
37+
}>
38+
"""
39+
40+
*/
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
@method_id(101)
3+
fun test1() {
4+
return 0;
5+
}
6+
7+
@noinline
8+
fun onBouncedMessage(in: InMessageBounced) {
9+
throw in.valueCoins;
10+
}
11+
12+
fun onInternalMessage(in: InMessage) {
13+
return in.valueCoins;
14+
}
15+
16+
/**
17+
@testcase | 101 | | 0
18+
19+
@fif_codegen
20+
"""
21+
onBouncedMessage() PROC:<{
22+
DROP
23+
INMSG_VALUE
24+
THROWANY
25+
}>
26+
"""
27+
28+
@fif_codegen
29+
"""
30+
onInternalMessage() PROC:<{ // in.body
31+
INMSG_BOUNCED // in.body '1
32+
IFJMP:<{ // in.body
33+
onBouncedMessage() CALLDICT //
34+
}> // in.body
35+
DROP //
36+
INMSG_VALUE // '3
37+
}>
38+
"""
39+
*/

0 commit comments

Comments
 (0)