Skip to content

Commit 63670d2

Browse files
committed
[Tolk] Use of AutoDeployAddress for calculation aside createMessage
New functions in stdlib: > AutoDeployAddress.buildAddress > AutoDeployAddress.addressMatches Based on the same philosophy as createMessage
1 parent 8d650aa commit 63670d2

File tree

7 files changed

+576
-131
lines changed

7 files changed

+576
-131
lines changed

crypto/smartcont/tolk-stdlib/common.tolk

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,17 +1056,39 @@ struct AddressShardingOptions {
10561056
/// },
10571057
/// ...
10581058
/// ```
1059-
/// It's often called "deployment", but it's inaccurate. TON Blockchain doesn't have a dedicated
1060-
/// deployment mechanism. You just send a message to some address — and if it doesn't exist, but you've
1061-
/// attached a way to initialize it (code+data) — it's initialized immediately, and accepts your message.
10621059
/// You just provide code+data, and the compiler automatically calculates the destination address,
10631060
/// because in TON, the address of a contract, by definition, is a hash of its initial state.
1061+
/// You can also use it without sending a message. See [buildAddress] and [addressMatches].
10641062
struct AutoDeployAddress {
10651063
workchain: int8 = BASECHAIN;
10661064
stateInit: ContractState | cell;
10671065
toShard: AddressShardingOptions? = null;
10681066
}
10691067

1068+
/// Constructs an address that a deployed contract will have.
1069+
/// For example, from a jetton minter, you want to calculate an address of a jetton wallet:
1070+
/// ```
1071+
/// val jwDeployed = calcDeployedJettonWallet(...);
1072+
/// val jwAddrBuilt = jwDeployed.buildAddress();
1073+
/// ```
1074+
/// Just instead of `dest` for [createMessage], you use it is to calculate a jetton/nft address.
1075+
/// Note: returns `builder`, not `address`! It's cheap.
1076+
/// If you really need `address`, use `address.fromValidBuilder(res)`.
1077+
@pure
1078+
fun AutoDeployAddress.buildAddress(self): builder
1079+
builtin;
1080+
1081+
/// Checks that an address matches a deployed contract.
1082+
/// For example, from a jetton minter, you're checking whether a message is from a jetton wallet:
1083+
/// ```
1084+
/// val jwDeployed = calcDeployedJettonWallet(...);
1085+
/// val fromJW = jwDeployed.addressMatches(senderAddress);
1086+
/// ```
1087+
/// Just instead of `dest` for [createMessage], you use it is to check whether a sender is a jetton/nft.
1088+
@pure
1089+
fun AutoDeployAddress.addressMatches(self, addr: address): bool
1090+
builtin;
1091+
10701092
/// Options for creating an outgoing message.
10711093
/// Consider [createMessage] for examples.
10721094
struct CreateMessageOptions<TBody = never> {
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
const SHARD_DEPTH = 8;
2+
3+
fun getMyAddressDev(): address
4+
asm "x{80194DC6438F99D3D9DBE151944925D90B2492954BF6B9C070FBFF2DDED5F30547D_} PUSHSLICE";
5+
6+
struct WalletStorage {
7+
balance: coins;
8+
ownerAddress: address;
9+
minterAddress: address;
10+
}
11+
12+
fun WalletStorage.generateEmptyData(ownerAddress: address, minterAddress: address) {
13+
val emptyWalletStorage: WalletStorage = {
14+
balance: 0,
15+
ownerAddress,
16+
minterAddress,
17+
};
18+
return emptyWalletStorage.toCell();
19+
}
20+
21+
@inline_ref
22+
fun buildAddrInShard_manual(a: address, options: AddressShardingOptions) {
23+
var sb = options.closeTo as slice;
24+
sb.skipBits(3); // addr_std$10 + anycast 0
25+
val wc_b = sb.loadInt(8);
26+
val shardPrefix = sb.loadUint(options.fixedPrefixLength);
27+
28+
var sa = a as slice;
29+
sa.skipBits(3 + 8 + options.fixedPrefixLength);
30+
31+
return beginCell()
32+
.storeUint(0b100, 3) // addr_std$10 + anycast 0
33+
.storeInt(wc_b, 8)
34+
.storeUint(shardPrefix, options.fixedPrefixLength)
35+
.storeSlice(sa);
36+
}
37+
38+
@method_id(101)
39+
fun test1() {
40+
val a = address("0:00000000000000000000000000000000000000000000000000000000000000FF");
41+
val b = address("1:1100000000000000000000000000000000000000000000000000000000000000");
42+
val dd1 = buildAddrInShard_manual(a, {fixedPrefixLength: 8, closeTo: b});
43+
val dd2 = a.buildSameAddressInAnotherShard({fixedPrefixLength: 8, closeTo: b});
44+
assert(dd1.endCell().hash() == dd2.endCell().hash(), 400);
45+
return dd1.endCell().beginParse() as address
46+
== address("1:11000000000000000000000000000000000000000000000000000000000000FF");
47+
}
48+
49+
@method_id(102)
50+
fun test2() {
51+
val a = address("0:1234aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
52+
val b = address("0:FFFFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
53+
val dd1 = buildAddrInShard_manual(a, {closeTo: b, fixedPrefixLength: 16});
54+
val dd2 = a.buildSameAddressInAnotherShard({fixedPrefixLength: 16, closeTo: b});
55+
assert((a as slice).remainingBitsCount() == 267, 267);
56+
assert((b as slice).remainingBitsCount() == 267, 267);
57+
assert(dd1.endCell().hash() == dd2.endCell().hash(), 400);
58+
return address.fromValidBuilder(dd2)
59+
== address("0:FFFFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
60+
}
61+
62+
@method_id(103)
63+
fun test3() {
64+
var t1_a = address("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5");
65+
var t1_b = address("9:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8");
66+
var t2_a = address("EQCe4AYIBce1pAk2qJJPSs1OzyZRlKjkfq8zuC8D7erv6DUP");
67+
var t2_b = address("EQCtrtTXEAoSpoERmiqOnICe9LHxn2N89N4BH9qdHlrG-U0i");
68+
var t3_a = address("EQAUTbQiM522Y_XJ_T98QPhPhTmb4nV--VSPiha8kC6kRfPO");
69+
var t3_b = address("EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE");
70+
return (
71+
t1_a.buildSameAddressInAnotherShard({fixedPrefixLength: 8, closeTo: t1_b}).endCell().hash() & 0xFFFF,
72+
t1_a.buildSameAddressInAnotherShard({fixedPrefixLength: 4, closeTo: t1_b}).endCell().hash() & 0xFFFF,
73+
t2_a.buildSameAddressInAnotherShard({fixedPrefixLength: 2, closeTo: t2_b}).endCell().hash() & 0xFFFF,
74+
t3_a.buildSameAddressInAnotherShard({fixedPrefixLength: 30, closeTo: t2_b}).endCell().hash() & 0xFFFF,
75+
t2_a.buildSameAddressInAnotherShard({fixedPrefixLength: 1, closeTo: t3_b}).endCell().hash() & 0xFFFF,
76+
t2_a.buildSameAddressInAnotherShard({fixedPrefixLength: 0, closeTo: t3_b}).endCell().hash() & 0xFFFF,
77+
)
78+
}
79+
80+
@method_id(104)
81+
fun test4(shardDepth: int) {
82+
var a = address("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5");
83+
var b = address("9:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8");
84+
return a.buildSameAddressInAnotherShard({fixedPrefixLength: shardDepth, closeTo: b}).endCell().hash() & 0xFFFF;
85+
}
86+
87+
@method_id(105)
88+
fun test5() {
89+
var b = beginCell()
90+
.storeUint(0b100, 3) // std addr no anycast
91+
.storeInt(MASTERCHAIN, 8)
92+
.storeUint(0xFFFF, 256);
93+
val a = address("-1:000000000000000000000000000000000000000000000000000000000000FFFF");
94+
return address.fromValidBuilder(b) == a;
95+
}
96+
97+
@method_id(106)
98+
fun test6() {
99+
val a = address("0:00000000000000000000000000000000000000000000000000000000000000FF");
100+
val b = address("1:1100000000000000000000000000000000000000000000000000000000000000");
101+
return address.fromValidBuilder(a.buildSameAddressInAnotherShard({fixedPrefixLength: 8, closeTo: b})) ==
102+
address("1:11000000000000000000000000000000000000000000000000000000000000FF");
103+
}
104+
105+
106+
fun manual_buildAddressOfJettonWallet_plain(ownerAddress: address, minterAddress: address, jettonWalletCode: cell): builder {
107+
val stateInitHash = StateInit.calcHashCodeData(
108+
jettonWalletCode,
109+
WalletStorage.generateEmptyData(ownerAddress, minterAddress)
110+
);
111+
return beginCell()
112+
.storeUint(0b100, 3)
113+
.storeUint(BASECHAIN, 8)
114+
.storeUint(stateInitHash, 256)
115+
}
116+
117+
fun manual_buildAddressOfJettonWallet_sharded(ownerAddress: address, minterAddress: address, jettonWalletCode: cell): builder {
118+
val stateInitHash = StateInit.calcHashPrefixCodeData(
119+
SHARD_DEPTH,
120+
jettonWalletCode,
121+
WalletStorage.generateEmptyData(ownerAddress, minterAddress)
122+
);
123+
var mask = stateInitHash & ((1 << (256 - SHARD_DEPTH)) - 1);
124+
val shard_prefix = (ownerAddress as slice).getMiddleBits(3 + 8, SHARD_DEPTH);
125+
return beginCell()
126+
.storeUint(0b100, 3)
127+
.storeUint(BASECHAIN, 8)
128+
.storeSlice(shard_prefix)
129+
.storeUint( mask, 256 - SHARD_DEPTH);
130+
}
131+
132+
fun address.manual_isAddressOfJettonWallet_plain(self, ownerAddress: address, minterAddress: address, jettonWalletCode: cell) {
133+
val stateInitHash = StateInit.calcHashCodeData(
134+
jettonWalletCode,
135+
WalletStorage.generateEmptyData(ownerAddress, minterAddress)
136+
);
137+
val (wc, hash) = self.getWorkchainAndHash();
138+
return (stateInitHash == hash) & (BASECHAIN == wc);
139+
}
140+
141+
fun address.manual_isAddressOfJettonWallet_sharded(self, ownerAddress: address, minterAddress: address, jettonWalletCode: cell) {
142+
var stateInitHash = StateInit.calcHashPrefixCodeData(
143+
SHARD_DEPTH,
144+
jettonWalletCode,
145+
WalletStorage.generateEmptyData(ownerAddress, minterAddress)
146+
);
147+
val mask = (1 << (256 - SHARD_DEPTH)) - 1;
148+
stateInitHash = stateInitHash & mask;
149+
var (wc, hash) = self.getWorkchainAndHash();
150+
hash = hash & mask;
151+
return (stateInitHash == hash) & (BASECHAIN == wc);
152+
}
153+
154+
@method_id(110)
155+
fun test10() {
156+
val jwCode: cell = beginCell().storeInt(0x273849723892, 94).endCell();
157+
val ownerAddress = address("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF");
158+
val minterAddress = getMyAddressDev();
159+
160+
val b1 = manual_buildAddressOfJettonWallet_plain(ownerAddress, minterAddress, jwCode);
161+
val si2: AutoDeployAddress = {
162+
stateInit: { code: jwCode, data: WalletStorage.generateEmptyData(ownerAddress, minterAddress) }
163+
};
164+
val b2 = si2.buildAddress();
165+
assert (b1.endCell().hash() == b2.endCell().hash()) throw 123;
166+
167+
val checkedAddr = b1.endCell().beginParse() as address;
168+
assert (checkedAddr.manual_isAddressOfJettonWallet_plain(ownerAddress, minterAddress, jwCode)) throw 123;
169+
assert (si2.addressMatches(checkedAddr)) throw 123;
170+
171+
return (
172+
si2.addressMatches(ownerAddress),
173+
AutoDeployAddress {
174+
workchain: -1,
175+
stateInit: { code: jwCode, data: WalletStorage.generateEmptyData(ownerAddress, minterAddress) }
176+
}.addressMatches(checkedAddr)
177+
)
178+
}
179+
180+
@method_id(111)
181+
fun test11() {
182+
val jwCode: cell = beginCell().storeInt(0x273849723892, 94).endCell();
183+
val ownerAddress = address("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF");
184+
val minterAddress = getMyAddressDev();
185+
186+
val b1 = manual_buildAddressOfJettonWallet_sharded(ownerAddress, minterAddress, jwCode);
187+
val si2: AutoDeployAddress = {
188+
stateInit: { code: jwCode, data: WalletStorage.generateEmptyData(ownerAddress, minterAddress) },
189+
toShard: { fixedPrefixLength: SHARD_DEPTH, closeTo: ownerAddress }
190+
};
191+
val b2 = si2.buildAddress();
192+
assert (b1.endCell().hash() == b2.endCell().hash()) throw 123;
193+
194+
val checkedAddr = b1.endCell().beginParse() as address;
195+
assert (checkedAddr.manual_isAddressOfJettonWallet_sharded(ownerAddress, minterAddress, jwCode)) throw 123;
196+
assert (si2.addressMatches(checkedAddr)) throw 123;
197+
198+
return (
199+
AutoDeployAddress {
200+
stateInit: { code: jwCode, data: WalletStorage.generateEmptyData(ownerAddress, minterAddress) },
201+
}.addressMatches(checkedAddr),
202+
si2.addressMatches(ownerAddress),
203+
AutoDeployAddress {
204+
workchain: -1,
205+
stateInit: { code: jwCode, data: WalletStorage.generateEmptyData(ownerAddress, minterAddress) },
206+
toShard: { fixedPrefixLength: SHARD_DEPTH, closeTo: ownerAddress }
207+
}.addressMatches(checkedAddr)
208+
)
209+
}
210+
211+
@method_id(112)
212+
fun test12() {
213+
val jwCode: cell = beginCell().storeInt(0x273849723892, 94).endCell();
214+
val ownerAddress = address("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF");
215+
var i = 1;
216+
while (i < 10) {
217+
var params: AutoDeployAddress = {
218+
workchain: -1,
219+
stateInit: { code: jwCode, data: createEmptyCell() },
220+
toShard: { fixedPrefixLength: i, closeTo: ownerAddress }
221+
};
222+
val addrBuilt = params.buildAddress();
223+
val addr = address.fromValidBuilder(addrBuilt);
224+
assert (params.addressMatches(addr)) throw 123;
225+
params.workchain = 0;
226+
assert (!params.addressMatches(addr)) throw 123;
227+
params.workchain = -1;
228+
params.toShard!.fixedPrefixLength += 1;
229+
assert (!params.addressMatches(addr)) throw 123;
230+
i += 1
231+
}
232+
return i;
233+
}
234+
235+
@method_id(113)
236+
fun test13() {
237+
val stateInitCell = StateInit {
238+
fixedPrefixLength: null,
239+
code: createEmptyCell(),
240+
data: beginCell().storeUint(123, 32).endCell(),
241+
special: null,
242+
library: null,
243+
}.toCell();
244+
245+
val manualBuilt = beginCell().storeUint(0b100, 3).storeUint(0, 8)
246+
.storeUint(stateInitCell.hash(), 256);
247+
val si: AutoDeployAddress = {
248+
stateInit: stateInitCell
249+
};
250+
val addrBuilt = si.buildAddress();
251+
assert (manualBuilt.endCell().hash() == addrBuilt.endCell().hash()) throw 123;
252+
253+
val addr = addrBuilt.endCell().beginParse() as address;
254+
return (
255+
si.addressMatches(addr),
256+
AutoDeployAddress {workchain: 99, stateInit: stateInitCell}.addressMatches(addr),
257+
)
258+
}
259+
260+
@method_id(114)
261+
fun test14() {
262+
val ownerAddress = address("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF");
263+
val stateInitCell = StateInit {
264+
fixedPrefixLength: 10,
265+
code: createEmptyCell(),
266+
data: beginCell().storeUint(123, 32).endCell(),
267+
special: null,
268+
library: null,
269+
}.toCell();
270+
271+
val manualBuilt = beginCell().storeUint(0b100, 3).storeUint(9, 8)
272+
.storeSlice((ownerAddress as slice).getMiddleBits(3+8, 10))
273+
.storeUint(stateInitCell.hash() & ((1 << 246) - 1), 246);
274+
val si: AutoDeployAddress = {
275+
workchain: 9,
276+
stateInit: stateInitCell,
277+
toShard: { fixedPrefixLength: 10, closeTo: ownerAddress }
278+
};
279+
val addrBuilt = si.buildAddress();
280+
val addr = addrBuilt.endCell().beginParse() as address;
281+
282+
val stateInitHash = stateInitCell.hash();
283+
val (wc, hash) = addr.getWorkchainAndHash();
284+
val manual_isAddrOfContract = ((stateInitHash & ((1 << 246) - 1)) == (hash & ((1 << 246) - 1))) & (9 == wc);
285+
286+
return (
287+
manualBuilt.endCell().hash() == addrBuilt.endCell().hash(),
288+
manual_isAddrOfContract,
289+
si.addressMatches(addr),
290+
)
291+
}
292+
293+
294+
295+
fun main() {}
296+
297+
/**
298+
@testcase | 101 | | -1
299+
@testcase | 102 | | -1
300+
@testcase | 103 | | 39876 24338 15241 50719 11252 15241
301+
@testcase | 104 | 2 | 24338
302+
@testcase | 104 | 9 | 39876
303+
@testcase | 105 | | -1
304+
@testcase | 106 | | -1
305+
@testcase | 110 | | 0 0
306+
@testcase | 111 | | 0 0 0
307+
@testcase | 112 | | 10
308+
@testcase | 113 | | -1 0
309+
@testcase | 114 | | -1 -1 -1
310+
*/

0 commit comments

Comments
 (0)