Skip to content

Commit b086bc7

Browse files
committed
[Tolk] Type coins, function ton("0.05")
A new compile-time function is introduced. It converts a constant floating-point string to nanotoncoins. Example: `ton("0.05")` is equal to 50000000. It requires a constant string; `ton(some_var)` is an error. Type of its return value is `coins`, not `int`. Like `intN`, it's backed by a general int at TVM level, all arithmetics (except addition) degrade to int. Type `coins` will be serialized as varint16 in the future.
1 parent 620a12d commit b086bc7

17 files changed

+320
-5
lines changed

crypto/smartcont/tolk-stdlib/common.tolk

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ fun tupleLast<T>(self: tuple): T
5454
Mathematical primitives.
5555
*/
5656

57+
/// Converts a constant floating-point string to nanotoncoins.
58+
/// Example: `ton("0.05")` is equal to 50000000.
59+
/// Note, that `ton()` requires a constant string; `ton(some_var)` is an error
60+
@pure
61+
fun ton(floatString: slice): coins
62+
builtin;
63+
5764
/// Computes the minimum of two integers.
5865
@pure
5966
fun min(x: int, y: int): int
@@ -386,7 +393,7 @@ fun preloadBits(self: slice, len: int): slice
386393

387394
/// Loads serialized amount of Toncoins (any unsigned integer up to `2^120 - 1`).
388395
@pure
389-
fun loadCoins(mutate self: slice): int
396+
fun loadCoins(mutate self: slice): coins
390397
asm( -> 1 0) "LDGRAMS";
391398

392399
/// Loads bool (-1 or 0) from a slice
@@ -485,7 +492,7 @@ fun storeSlice(mutate self: builder, s: slice): self
485492

486493
/// Stores amount of Toncoins into a builder.
487494
@pure
488-
fun storeCoins(mutate self: builder, x: int): self
495+
fun storeCoins(mutate self: builder, x: coins): self
489496
asm "STGRAMS";
490497

491498
/// Stores bool (-1 or 0) into a builder.

tolk-tester/tests/intN-tests.tolk

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ fun getNullableVarInt32(): varint32? { return 32; }
55

66
const someN_u128: uint128 = 128;
77
const someN_i32: int32 = 20 + (12 as int32) * (1 as uint8);
8+
const someN_coins = 10 as coins;
9+
10+
const cost1 = ton("1.234");
11+
const cost2 = ton("0.05") + ton("0.001");
812

913

1014
fun autoInferInt8(x: int8) {
@@ -54,6 +58,7 @@ fun test1(x: int8) {
5458
var z = x + y;
5559
__expect_type(z, "int");
5660
__expect_type(someN_u128, "uint128");
61+
__expect_type(someN_coins, "coins");
5762
return x / y + someN_u128;
5863
}
5964

@@ -141,6 +146,65 @@ fun test7() {
141146
__expect_type((n as varint16) as int, "int");
142147
}
143148

149+
fun test8(amount1: coins, amount2: coins) {
150+
__expect_type(amount1 + 0, "coins");
151+
__expect_type(amount1 + amount2, "coins");
152+
__expect_type(amount1 += amount2, "coins");
153+
__expect_type(amount1 - 10, "coins");
154+
__expect_type(amount1 &= 1, "coins");
155+
__expect_type(amount1 &= (1 as coins), "coins");
156+
157+
__expect_type(amount1 & 1, "int");
158+
__expect_type(amount1 * 10, "int");
159+
__expect_type(amount1 << amount2, "int");
160+
}
161+
162+
@method_id(109)
163+
fun test9(c: coins, i: int8) {
164+
__expect_type(c as int8, "int8");
165+
__expect_type(i as coins, "coins");
166+
__expect_type(c + i, "coins");
167+
168+
var amount: coins = 1000;
169+
var percent: uint8 = 50;
170+
var new = amount * percent / 100;
171+
amount = new;
172+
__expect_type(amount, "coins");
173+
if (!amount) { while (amount) { amount -= 1; } }
174+
175+
takeAnyInt(amount);
176+
takeAnyInt(percent);
177+
takeAnyInt(true as int3);
178+
takeAnyInt(amount as int3);
179+
180+
return (amount, (true as int8) as coins);
181+
}
182+
183+
@method_id(110)
184+
fun test10() {
185+
return (ton("0.05"), ton("0.05") + 100, cost1, cost2);
186+
}
187+
188+
@method_id(111)
189+
fun test11() {
190+
__expect_type(ton("0"), "coins");
191+
__expect_type(ton("0") + ton("0"), "coins");
192+
__expect_type(ton("0") * 20, "int");
193+
return [
194+
ton("1"),
195+
ton("1.0"),
196+
ton("1.00000"),
197+
ton("-321.123456789"),
198+
ton("+321.123456789876"),
199+
ton("0001.1000")
200+
];
201+
}
202+
203+
fun test12() {
204+
var a = ton("1") + ton("2"); // check absence in fif codegen
205+
return ton("0.1");
206+
}
207+
144208
fun test13(x1: int?, x2: int8?, x3: int, x4: int8): (int8?, int8?, int8?, int8?, int32?, int32?) {
145209
return (x1, x2, x3, x4, x4 as int32, x4 as int32?);
146210
}
@@ -165,6 +229,9 @@ fun main() {
165229
__expect_type(0 as int32, "int32");
166230
__expect_type((0 as int32) as uint64?, "uint64?");
167231
__expect_type(null as (int8, [uint16?])?, "(int8, [uint16?])?");
232+
__expect_type(0 as coins, "coins");
233+
__expect_type(someN_coins as coins?, "coins?");
234+
__expect_type(someN_coins as int8?, "int8?");
168235
__expect_type(10>3 ? 0 as uint8 : 0 as uint16, "int");
169236
return t;
170237
}
@@ -174,10 +241,21 @@ fun main() {
174241
@testcase | 101 | 0 | 129
175242
@testcase | 104 | | 64 64 -1 0
176243
@testcase | 105 | | 3 -1 0
244+
@testcase | 109 | 4 4 | 500 -1
245+
@testcase | 110 | | 50000000 50000100 1234000000 51000000
246+
@testcase | 111 | | [ 1000000000 1000000000 1000000000 -321123456789 321123456789 1100000000 ]
177247
@testcase | 114 | 5 | (null) (null) 0
178248
@testcase | 114 | 15 | 15 2 -1
179249

180250
@fif_codegen DECLPROC assign0<int8>
181251
@fif_codegen DECLPROC assign0<uint16>
182252
@fif_codegen DECLPROC assign0<int>
253+
254+
@fif_codegen
255+
"""
256+
test12 PROC:<{
257+
//
258+
100000000 PUSHINT // '4=100000000
259+
}>
260+
"""
183261
*/
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fun main(am: slice) {
2+
return ton(am);
3+
}
4+
5+
/**
6+
@compilation_should_fail
7+
@stderr function `ton` requires a constant string, like `ton("0.05")`
8+
*/
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fun main() {
2+
return ton(123);
3+
}
4+
5+
/**
6+
@compilation_should_fail
7+
@stderr function `ton` requires a constant string, like `ton("0.05")`
8+
*/
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const ss = ton("0.0.0");
2+
3+
/**
4+
@compilation_should_fail
5+
@stderr argument is not a valid number like "0.05"
6+
*/
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
fun cantCastBoolToCoins() {
2+
var k1 = true as int8; // ok
3+
var k2 = true as int1; // ok
4+
var k3 = (true as int) as coins; // ok
5+
var k4 = true as coins;
6+
}
7+
8+
/**
9+
@compilation_should_fail
10+
@stderr type `bool` can not be cast to `coins`
11+
@stderr true as coins
12+
*/
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
fun cantAssignIntNToCoins(n: int8, c: coins) {
2+
n = c as int8; // ok
3+
n = c as int; // ok
4+
n = c;
5+
}
6+
7+
/**
8+
@compilation_should_fail
9+
@stderr can not assign `coins` to variable of type `int8`
10+
@stderr n = c;
11+
*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
fun cantUnifyCoinsAndUInt8(n: int8, c: coins) {
2+
__expect_type(random() ? n : c as int8, "int8");
3+
__expect_type(random() ? n : c as int16, "int");
4+
random() ? n : c;
5+
}
6+
7+
/**
8+
@compilation_should_fail
9+
@stderr types of ternary branches are incompatible: `int8` and `coins`
10+
*/

tolk/builtins.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,13 @@ AsmOp compile_tuple_set_at(std::vector<VarDescr>& res, std::vector<VarDescr>& ar
10711071
return exec_op("SETINDEXVAR", 2, 1);
10721072
}
10731073

1074+
// fun ton(amount: slice): coins; ton("0.05") replaced by 50000000 at compile-time
1075+
AsmOp compile_int_to_ton_coins(std::vector<VarDescr>&, std::vector<VarDescr>&, SrcLocation) {
1076+
// all ton() invocations are constants, replaced by integers; no dynamic values allowed, no work at runtime
1077+
tolk_assert(false);
1078+
return AsmOp::Nop();
1079+
}
1080+
10741081
// fun __isNull<X>(X arg): bool
10751082
AsmOp compile_is_null(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
10761083
tolk_assert(args.size() == 1 && res.size() == 1);
@@ -1214,6 +1221,9 @@ void define_builtins() {
12141221

12151222
// functions from stdlib marked as `builtin`, implemented at compiler level for optimizations
12161223
// (for example, `loadInt(1)` is `1 LDI`, but `loadInt(n)` for non-constant requires it be on a stack and `LDIX`)
1224+
define_builtin_func("ton", {TypeDataUnknown::create()}, TypeDataCoins::create(), nullptr, // `unknown` to pass inferring for `ton(1)` (to fire a better error later)
1225+
compile_int_to_ton_coins,
1226+
FunctionData::flagMarkedAsPure);
12171227
define_builtin_func("mulDivFloor", ParamsInt3, Int, nullptr,
12181228
std::bind(compile_muldiv, _1, _2, _3, -1),
12191229
FunctionData::flagMarkedAsPure);

tolk/constant-evaluator.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,67 @@ static ConstantValue parse_vertex_string_const(V<ast_string_const> v) {
153153
: ConstantValue(parse_vertex_string_const_as_int(v));
154154
}
155155

156+
// given `ton("0.05")` evaluate it to 50000000
157+
static ConstantValue parse_vertex_call_to_ton_function(V<ast_function_call> v) {
158+
tolk_assert(v->get_num_args() == 1); // checked by type inferring
159+
AnyExprV v_arg = v->get_arg(0)->get_expr();
160+
161+
std::string_view str;
162+
if (auto as_string = v_arg->try_as<ast_string_const>(); as_string && !as_string->modifier) {
163+
str = as_string->str_val;
164+
} else {
165+
// ton(SOME_CONST) is not supported
166+
// ton(0.05) is not supported (it can't be represented in AST even)
167+
}
168+
if (str.empty()) {
169+
v->error("function `ton` requires a constant string, like `ton(\"0.05\")`");
170+
}
171+
172+
bool is_negative = false;
173+
size_t i = 0;
174+
175+
// handle optional leading sign
176+
if (str[0] == '-') {
177+
is_negative = true;
178+
i++;
179+
} else if (str[0] == '+') {
180+
i++;
181+
}
182+
183+
// parse "0.05" into integer part (before dot) and fractional (after)
184+
int64_t integer_part = 0;
185+
int64_t fractional_part = 0;
186+
int fractional_digits = 0;
187+
bool seen_dot = false;
188+
189+
for (; i < str.size(); ++i) {
190+
char c = str[i];
191+
if (c == '.') {
192+
if (seen_dot) {
193+
v_arg->error("argument is not a valid number like \"0.05\"");
194+
}
195+
seen_dot = true;
196+
} else if (c >= '0' && c <= '9') {
197+
if (!seen_dot) {
198+
integer_part = integer_part * 10 + (c - '0');
199+
} else if (fractional_digits < 9) {
200+
fractional_part = fractional_part * 10 + (c - '0');
201+
fractional_digits++;
202+
}
203+
} else {
204+
v_arg->error("argument is not a valid number like \"0.05\"");
205+
}
206+
}
207+
208+
while (fractional_digits < 9) { // after "0.05" fractional_digits is 2, scale up to 9
209+
fractional_part *= 10;
210+
fractional_digits++;
211+
}
212+
213+
int64_t result = integer_part * 1000000000LL + fractional_part;
214+
return ConstantValue(td::make_refint(is_negative ? -result : result));
215+
}
216+
156217

157218
struct ConstantEvaluator {
158219
static bool is_overflow(const td::RefInt256& intval) {
@@ -269,6 +330,14 @@ struct ConstantEvaluator {
269330
return const_ref->value;
270331
}
271332

333+
// `const a = ton("0.05")`, we met `ton("0.05")`
334+
static ConstantValue handle_function_call(V<ast_function_call> v) {
335+
if (v->fun_maybe && v->fun_maybe->is_builtin_function() && v->fun_maybe->name == "ton") {
336+
return parse_vertex_call_to_ton_function(v);
337+
}
338+
v->error("not a constant expression");
339+
}
340+
272341
static ConstantValue visit(AnyExprV v) {
273342
if (auto v_int = v->try_as<ast_int_const>()) {
274343
return ConstantValue(v_int->intval);
@@ -285,6 +354,9 @@ struct ConstantEvaluator {
285354
if (auto v_ref = v->try_as<ast_reference>()) {
286355
return handle_reference(v_ref);
287356
}
357+
if (auto v_call = v->try_as<ast_function_call>()) {
358+
return handle_function_call(v_call);
359+
}
288360
if (auto v_par = v->try_as<ast_parenthesized_expression>()) {
289361
return visit(v_par->get_expr());
290362
}
@@ -311,6 +383,11 @@ ConstantValue eval_string_const_considering_modifier(AnyExprV v_string) {
311383
return parse_vertex_string_const(v_string->as<ast_string_const>());
312384
}
313385

386+
ConstantValue eval_call_to_ton_function(AnyExprV v_call) {
387+
tolk_assert(v_call->type == ast_function_call && v_call->as<ast_function_call>()->fun_maybe->is_builtin_function());
388+
return parse_vertex_call_to_ton_function(v_call->as<ast_function_call>());
389+
}
390+
314391
void eval_and_assign_const_init_value(GlobalConstPtr const_ref) {
315392
ConstantValue init_value = ConstantEvaluator::eval_const_init_value(const_ref);
316393
const_ref->mutate()->assign_const_value(std::move(init_value));

0 commit comments

Comments
 (0)