Skip to content

Commit 08c61df

Browse files
committed
[Tolk] Fix createMessage() with a dynamic union destination
1 parent 6f60419 commit 08c61df

File tree

2 files changed

+103
-36
lines changed

2 files changed

+103
-36
lines changed

tolk-tester/tests/send-msg-4.tolk

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
struct LiquidityDepositWithInitData {
2+
code: cell
3+
data: cell
4+
}
5+
6+
type LiquidityDepositAddress = address
7+
8+
type LiquidityDepositDestination = LiquidityDepositWithInitData | LiquidityDepositAddress
9+
10+
struct (0x1b434676) AddLiquidityPartTon {
11+
destination: LiquidityDepositDestination
12+
}
13+
14+
type AllowedMessages = AddLiquidityPartTon
15+
16+
fun main() {
17+
val msgCell = beginCell().
18+
storeUint(0x1b434676, 32)
19+
.storeBool(true) // either right
20+
.storeAddress(address("Ef_vA6yRfmt2P4UHnxlrQUZFcBnKux8mL2eMqBgpeMFPorr4"))
21+
.endCell();
22+
23+
val msg = lazy AllowedMessages.fromCell(msgCell);
24+
25+
match (msg) {
26+
AddLiquidityPartTon => {
27+
// dynamic union: the compiler inserts lots of IFs at runtime to handle this
28+
var destination: address | AutoDeployAddress;
29+
30+
match (msg.destination) {
31+
LiquidityDepositWithInitData => {
32+
destination = {
33+
stateInit: { code: msg.destination.code, data: msg.destination.data },
34+
};
35+
}
36+
LiquidityDepositAddress => {
37+
destination = msg.destination;
38+
}
39+
}
40+
41+
val sendMsg = createMessage({
42+
bounce: false,
43+
dest: destination,
44+
value: 0,
45+
});
46+
sendMsg.send(SEND_MODE_REGULAR);
47+
}
48+
}
49+
50+
return 0;
51+
}
52+
53+
/**
54+
@testcase | 0 | | 0
55+
56+
@fif_codegen ONE HASHEXT_SHA256
57+
@fif_codegen }>ELSE<{
58+
@fif_codegen HASHCU
59+
@fif_codegen 256 STU
60+
*/

tolk/send-message-api.cpp

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -55,39 +55,49 @@ static void append_bitwise_and_shard_mask(CodeBlob& code, SrcLocation loc, var_i
5555

5656
// struct AutoDeployAddress { workchain: int8; stateInit: ContractState | cell; toShard: AddressShardingOptions?; }
5757
struct IR_AutoDeployAddress {
58-
std::vector<var_idx_t> is_ContractState, // stateInit is ContractState
59-
is_AddressSharding; // toShard is not null
58+
std::vector<var_idx_t> ir_stateInitField,
59+
ir_toShardField;
60+
const TypeDataUnion* t_stateInit;
61+
const TypeDataUnion* t_toShard;
6062
var_idx_t workchain, // workchain
6163
stateInitCode, stateInitData, stateInitCell, // stateInit
6264
ir_shardDepth, ir_closeTo; // toShard
6365

6466
IR_AutoDeployAddress(CodeBlob& code, SrcLocation loc, const std::vector<var_idx_t>& ir_vars) {
6567
StructPtr s_AutoDeployAddress = lookup_global_symbol("AutoDeployAddress")->try_as<StructPtr>();
66-
const TypeDataUnion* t_stateInit = s_AutoDeployAddress->find_field("stateInit")->declared_type->try_as<TypeDataUnion>();
67-
const TypeDataUnion* t_toShard = s_AutoDeployAddress->find_field("toShard")->declared_type->try_as<TypeDataUnion>();
68+
t_stateInit = s_AutoDeployAddress->find_field("stateInit")->declared_type->try_as<TypeDataUnion>();
69+
t_toShard = s_AutoDeployAddress->find_field("toShard")->declared_type->try_as<TypeDataUnion>();
6870
tolk_assert(ir_vars.size() == 1 + 3 + 3);
6971
tolk_assert(t_stateInit && t_stateInit->get_width_on_stack() == (2+1) && t_stateInit->size() == 2);
7072
tolk_assert(t_toShard && t_toShard->get_width_on_stack() == (2+1) && t_toShard->or_null);
7173

7274
workchain = ir_vars[0];
7375

74-
std::vector ir_stateInitUnion(ir_vars.begin() + 1, ir_vars.begin() + 1 + 3);
75-
is_ContractState = pre_compile_is_type(code, t_stateInit, t_stateInit->variants[0], ir_stateInitUnion, loc, "(is-ContractState)");
76-
std::vector ir_ContractState = transition_to_target_type(std::vector(ir_stateInitUnion), code, t_stateInit, t_stateInit->variants[0], loc);
76+
ir_stateInitField = std::vector(ir_vars.begin() + 1, ir_vars.begin() + 1 + 3);
77+
std::vector ir_ContractState = transition_to_target_type(std::vector(ir_stateInitField), code, t_stateInit, t_stateInit->variants[0], loc);
7778
stateInitCode = ir_ContractState[0];
7879
stateInitData = ir_ContractState[1];
79-
stateInitCell = transition_to_target_type(std::vector(ir_stateInitUnion), code, t_stateInit, t_stateInit->variants[1], loc)[0];
80+
stateInitCell = transition_to_target_type(std::vector(ir_stateInitField), code, t_stateInit, t_stateInit->variants[1], loc)[0];
8081

81-
std::vector ir_toShardOrNull(ir_vars.begin() + 1 + 3, ir_vars.begin() + 1 + 3 + 3);
82-
is_AddressSharding = pre_compile_is_type(code, t_toShard, t_toShard->or_null, ir_toShardOrNull, loc, "(is-AddressSharding)");
83-
std::vector ir_AddressSharding = transition_to_target_type(std::vector(ir_toShardOrNull), code, t_toShard, t_toShard->or_null, loc);
82+
ir_toShardField = std::vector(ir_vars.begin() + 1 + 3, ir_vars.begin() + 1 + 3 + 3);
83+
std::vector ir_AddressSharding = transition_to_target_type(std::vector(ir_toShardField), code, t_toShard, t_toShard->or_null, loc);
8484
ir_shardDepth = ir_AddressSharding[0];
8585
ir_closeTo = ir_AddressSharding[1];
8686
}
87+
88+
// generate IR vars "stateInit is ContractState"
89+
std::vector<var_idx_t> is_ContractState(CodeBlob& code, SrcLocation loc) const {
90+
return pre_compile_is_type(code, t_stateInit, t_stateInit->variants[0], ir_stateInitField, loc, "(is-ContractState)");
91+
}
92+
93+
// generate IR vars "toShard is not null"
94+
std::vector<var_idx_t> is_AddressSharding(CodeBlob& code, SrcLocation loc) const {
95+
return pre_compile_is_type(code, t_toShard, t_toShard->or_null, ir_toShardField, loc, "(is-AddressSharding)");
96+
}
8797
};
8898

8999
// fun createMessage<TBody>(options: CreateMessageOptions<TBody>): OutMessage
90-
std::vector<var_idx_t> generate_createMessage(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector<std::vector<var_idx_t>>& args) {
100+
std::vector<var_idx_t> generate_createMessage(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector<std::vector<var_idx_t>>& ir_options) {
91101
TypePtr bodyT = called_f->substitutedTs->typeT_at(0);
92102
StructPtr s_Options = lookup_global_symbol("CreateMessageOptions")->try_as<StructPtr>();
93103
StructPtr s_AutoDeployAddress = lookup_global_symbol("AutoDeployAddress")->try_as<StructPtr>();
@@ -100,7 +110,7 @@ std::vector<var_idx_t> generate_createMessage(FunctionPtr called_f, CodeBlob& co
100110
tolk_assert(t_value && t_value->get_width_on_stack() == (2+1) && t_value->size() == 2);
101111

102112
int offset = 0;
103-
std::vector rvect = args[0];
113+
std::vector rvect = ir_options[0];
104114
auto next_slice = [&rvect, &offset](int width) -> std::vector<var_idx_t> {
105115
int start = offset;
106116
offset += width;
@@ -118,14 +128,11 @@ std::vector<var_idx_t> generate_createMessage(FunctionPtr called_f, CodeBlob& co
118128
// struct ContractState { code: cell; data: cell; }
119129
// struct AddressShardingOptions { fixedPrefixLength: uint5; closeTo: address; }
120130
std::vector ir_dest_is_address = pre_compile_is_type(code, t_dest, TypeDataAddress::create(), ir_dest, loc, "(is-address)");
121-
std::vector ir_dest_is_AutoDeploy = pre_compile_is_type(code, t_dest, TypeDataStruct::create(s_AutoDeployAddress), ir_dest, loc, "(is-address)");
131+
std::vector ir_dest_is_AutoDeploy = pre_compile_is_type(code, t_dest, TypeDataStruct::create(s_AutoDeployAddress), ir_dest, loc, "(is-auto)");
122132
std::vector ir_dest_is_builder = pre_compile_is_type(code, t_dest, TypeDataBuilder::create(), ir_dest, loc, "(is-builder)");
123133
std::vector ir_dest_AutoDeployAddress = transition_to_target_type(std::vector(ir_dest), code, t_dest, TypeDataStruct::create(s_AutoDeployAddress), loc);
124134
IR_AutoDeployAddress ir_dest_ad(code, loc, ir_dest_AutoDeployAddress);
125135

126-
// currently, there is no way to pass PackOptions, defaults are used
127-
std::vector ir_options = create_default_PackOptions(code, loc);
128-
129136
FunctionPtr f_beginCell = lookup_function("beginCell");
130137
FunctionPtr f_endCell = lookup_function("builder.endCell");
131138

@@ -146,7 +153,7 @@ std::vector<var_idx_t> generate_createMessage(FunctionPtr called_f, CodeBlob& co
146153
if (body_store_as_ref && !body_already_ref) {
147154
std::vector ir_ref_builder = code.create_var(TypeDataBuilder::create(), loc, "refb");
148155
code.emplace_back(loc, Op::_Call, ir_ref_builder, std::vector<var_idx_t>{}, f_beginCell);
149-
PackContext ref_ctx(code, loc, ir_ref_builder, ir_options);
156+
PackContext ref_ctx(code, loc, ir_ref_builder, create_default_PackOptions(code, loc));
150157
ref_ctx.generate_pack_any(bodyT, std::move(ir_body));
151158
std::vector ir_ref_cell = code.create_tmp_var(TypeDataCell::create(), loc, "(ref-cell)");
152159
code.emplace_back(loc, Op::_Call, ir_ref_cell, std::move(ir_ref_builder), f_endCell);
@@ -155,7 +162,7 @@ std::vector<var_idx_t> generate_createMessage(FunctionPtr called_f, CodeBlob& co
155162

156163
std::vector ir_builder = code.create_var(TypeDataSlice::create(), loc, "b");
157164
code.emplace_back(loc, Op::_Call, ir_builder, std::vector<var_idx_t>{}, f_beginCell);
158-
PackContext ctx(code, loc, ir_builder, ir_options);
165+
PackContext ctx(code, loc, ir_builder, create_default_PackOptions(code, loc));
159166
var_idx_t ir_zero = code.create_int(loc, 0, "(zero)");
160167
var_idx_t ir_one = code.create_int(loc, 1, "(one)");
161168

@@ -188,11 +195,11 @@ std::vector<var_idx_t> generate_createMessage(FunctionPtr called_f, CodeBlob& co
188195
ctx.storeUint(code.create_int(loc, 0b100, "(addr-prefix)"), 3); // addr_std$10 + 0 anycast
189196
ctx.storeInt(ir_dest_ad.workchain, 8);
190197
std::vector ir_hash = code.create_tmp_var(TypeDataInt::create(), loc, "(addr-hash)");
191-
Op& if_ContractState = code.emplace_back(loc, Op::_If, ir_dest_ad.is_ContractState);
198+
Op& if_ContractState = code.emplace_back(loc, Op::_If, ir_dest_ad.is_ContractState(code, loc));
192199
{
193200
// input is `dest: { ... stateInit: { code, data } }`
194201
code.push_set_cur(if_ContractState.block0);
195-
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_dest_ad.is_AddressSharding);
202+
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_dest_ad.is_AddressSharding(code, loc));
196203
{
197204
// input is `dest: { ... stateInit: { code, data }, toShard: { fixedPrefixLength, closeTo } };
198205
// then stateInitHash = (hash of StateInit = 0b1(depth)0110 (prefix + code + data))
@@ -218,7 +225,7 @@ std::vector<var_idx_t> generate_createMessage(FunctionPtr called_f, CodeBlob& co
218225
code.emplace_back(loc, Op::_Call, ir_hash, std::move(args), lookup_function("cell.hash"));
219226
code.close_pop_cur(loc);
220227
}
221-
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_dest_ad.is_AddressSharding);
228+
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_dest_ad.is_AddressSharding(code, loc));
222229
{
223230
// input is `dest: { ... toShard: { fixedPrefixLength, closeTo } }`
224231
// we already calculated stateInitHash (ir_hash): either cell.hash() or based on prefix+code+data;
@@ -306,12 +313,12 @@ std::vector<var_idx_t> generate_createMessage(FunctionPtr called_f, CodeBlob& co
306313
}
307314
{
308315
code.push_set_cur(if_no_init.block0);
309-
Op& if_ContractState = code.emplace_back(loc, Op::_If, ir_dest_ad.is_ContractState);
316+
Op& if_ContractState = code.emplace_back(loc, Op::_If, ir_dest_ad.is_ContractState(code, loc));
310317
{
311318
// input is `dest: { ... stateInit: { code, data } }` and need to compose TL/B StateInit;
312319
// it's either just code+data OR (if `toShard: { ... }` is set) fixedPrefixLength+code+data
313320
code.push_set_cur(if_ContractState.block0);
314-
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_dest_ad.is_AddressSharding);
321+
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_dest_ad.is_AddressSharding(code, loc));
315322
{
316323
// 1 (maybe true) + 0 (either left) + 1 (maybe true of StateInit) + fixedPrefixLength + 0110 + body ref or not
317324
code.push_set_cur(if_sharded.block0);
@@ -530,20 +537,20 @@ std::vector<var_idx_t> generate_address_buildInAnotherShard(FunctionPtr called_f
530537
}
531538

532539
// fun AutoDeployAddress.buildAddress(self): builder
533-
std::vector<var_idx_t> generate_AutoDeployAddress_buildAddress(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector<std::vector<var_idx_t>>& args) {
534-
IR_AutoDeployAddress ir_self(code, loc, args[0]);
540+
std::vector<var_idx_t> generate_AutoDeployAddress_buildAddress(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector<std::vector<var_idx_t>>& ir_options) {
541+
IR_AutoDeployAddress ir_self(code, loc, ir_options[0]);
535542

536543
std::vector ir_builder = code.create_tmp_var(TypeDataSlice::create(), loc, "(addr-b)");
537544
// important! unlike `createMessage()`, we calculate hash and shard prefix BEFORE creating a cell
538545
// (for fewer stack manipulations)
539546

540547
// calculate stateInitHash = (hash of StateInit cell would be, but without constructing a cell)
541548
std::vector ir_hash = code.create_tmp_var(TypeDataInt::create(), loc, "(addr-hash)");
542-
Op& if_ContractState = code.emplace_back(loc, Op::_If, ir_self.is_ContractState);
549+
Op& if_ContractState = code.emplace_back(loc, Op::_If, ir_self.is_ContractState(code, loc));
543550
{
544551
// called `{ ... stateInit: { code, data } }`
545552
code.push_set_cur(if_ContractState.block0);
546-
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_self.is_AddressSharding);
553+
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_self.is_AddressSharding(code, loc));
547554
{
548555
// called `{ ... stateInit: { code, data }, toShard: { fixedPrefixLength, closeTo } }
549556
code.push_set_cur(if_sharded.block0);
@@ -569,7 +576,7 @@ std::vector<var_idx_t> generate_AutoDeployAddress_buildAddress(FunctionPtr calle
569576
}
570577

571578
// now, if toShard, perform bitwise calculations with hashes (order on a stack matters)
572-
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_self.is_AddressSharding);
579+
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_self.is_AddressSharding(code, loc));
573580
{
574581
// called `{ ... toShard: { fixedPrefixLength, closeTo } }`
575582
// we already calculated stateInitHash (ir_hash): either cell.hash() or based on prefix+code+data;
@@ -609,16 +616,16 @@ std::vector<var_idx_t> generate_AutoDeployAddress_buildAddress(FunctionPtr calle
609616
}
610617

611618
// fun AutoDeployAddress.addressMatches(self, addr: address): bool
612-
std::vector<var_idx_t> generate_AutoDeployAddress_addressMatches(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector<std::vector<var_idx_t>>& args) {
613-
IR_AutoDeployAddress ir_self(code, loc, args[0]);
619+
std::vector<var_idx_t> generate_AutoDeployAddress_addressMatches(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector<std::vector<var_idx_t>>& ir_self_and_addr) {
620+
IR_AutoDeployAddress ir_self(code, loc, ir_self_and_addr[0]);
614621

615622
// at first, calculate stateInitHash = (hash of StateInit cell would be, but without constructing a cell)
616623
std::vector ir_hash = code.create_tmp_var(TypeDataInt::create(), loc, "(addr-hash)");
617-
Op& if_ContractState = code.emplace_back(loc, Op::_If, ir_self.is_ContractState);
624+
Op& if_ContractState = code.emplace_back(loc, Op::_If, ir_self.is_ContractState(code, loc));
618625
{
619626
// called `{ ... stateInit: { code, data } }`
620627
code.push_set_cur(if_ContractState.block0);
621-
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_self.is_AddressSharding);
628+
Op& if_sharded = code.emplace_back(loc, Op::_If, ir_self.is_AddressSharding(code, loc));
622629
{
623630
// called `{ ... stateInit: { code, data }, toShard: { fixedPrefixLength, closeTo } }
624631
code.push_set_cur(if_sharded.block0);
@@ -644,7 +651,7 @@ std::vector<var_idx_t> generate_AutoDeployAddress_addressMatches(FunctionPtr cal
644651
}
645652

646653
// now calculate `stateInitHash &= mask` where mask = `(1 << (256 - SHARD_DEPTH)) - 1`
647-
Op& if_sharded1 = code.emplace_back(loc, Op::_If, ir_self.is_AddressSharding);
654+
Op& if_sharded1 = code.emplace_back(loc, Op::_If, ir_self.is_AddressSharding(code, loc));
648655
{
649656
code.push_set_cur(if_sharded1.block0);
650657
append_bitwise_and_shard_mask(code, loc, ir_hash[0], ir_self.ir_shardDepth);
@@ -657,10 +664,10 @@ std::vector<var_idx_t> generate_AutoDeployAddress_addressMatches(FunctionPtr cal
657664

658665
// now do `(wc, hash) = addr.getWorkchainAndHash()`
659666
std::vector ir_addr_wc_hash = code.create_tmp_var(TypeDataTensor::create({TypeDataInt::create(), TypeDataInt::create()}), loc, "(self-wc-hash)");
660-
code.emplace_back(loc, Op::_Call, ir_addr_wc_hash, args[1], lookup_function("address.getWorkchainAndHash"));
667+
code.emplace_back(loc, Op::_Call, ir_addr_wc_hash, ir_self_and_addr[1], lookup_function("address.getWorkchainAndHash"));
661668

662669
// now calculate `hash &= mask` (the same as we did earlier for stateInitHash)
663-
Op& if_sharded2 = code.emplace_back(loc, Op::_If, ir_self.is_AddressSharding);
670+
Op& if_sharded2 = code.emplace_back(loc, Op::_If, ir_self.is_AddressSharding(code, loc));
664671
{
665672
code.push_set_cur(if_sharded2.block0);
666673
append_bitwise_and_shard_mask(code, loc, ir_addr_wc_hash[1], ir_self.ir_shardDepth);

0 commit comments

Comments
 (0)