Skip to content

Commit b24830c

Browse files
committed
shard-optimization-guidelines-added
1 parent ddd3b6b commit b24830c

File tree

3 files changed

+337
-0
lines changed

3 files changed

+337
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Airdrop Claiming Guidelines
2+
3+
In this article we're going to study imaginary claim solution, try to identify it's performance problems and solve them.
4+
This article focuses on contract interactions and their impact on overall performance.
5+
Code, security aspects, and other nuances left aside.
6+
7+
## Claim Machine
8+
9+
:::info
10+
How pretty much any claim solution works?
11+
Let's think about it.
12+
:::
13+
14+
User send some kind of proof, that he is eligible for the claim
15+
solution checks it and sends jettons back.
16+
In current case `proof` means [merkle proof](https://docs.ton.org/develop/data-formats/exotic-cells#merkle-proof) but it could very well be signed data or whatever else authorization method one could come up with.
17+
Sending jettons, - so there would be a jetton wallet and minter.
18+
And we got to make sure that these sneaky users can't claim twice - double spend protection contract.
19+
Oh, and we probably want to make some money, do we?
20+
So at least one claim wallet.
21+
Let's sum it up:
22+
23+
### Distributor
24+
25+
Takes the proof from the user, checks it, releases the jettons.
26+
State init: `(merkle_root, admin, fee_wallet_address)`.
27+
28+
### Double spend
29+
30+
Receives message, bounces if already used, otherwise passes message further
31+
32+
### Jetton
33+
Jetton wallet where the tokens will be sent from by the *distributor*.
34+
Jetton minter is out of the scope of this article.
35+
36+
### Fee wallet
37+
38+
Any kind of wallet contract
39+
40+
## Architecture
41+
42+
### V1
43+
44+
First desing that comes to mind is something like this:
45+
- User sends proof to the distributor
46+
- Distributor checks proof and deploys `double spend` contract
47+
- Distributor passes message to the double spend.
48+
- Double spend sends `claim_ok` to the distributor if wasn't deployed previously
49+
- Distributor sends claim fee to the fee wallet.
50+
- Distributor releases jettons to the user.
51+
52+
**NAIVE ART AHEAD!**
53+
54+
What's wrong with that?
55+
Looks like a loop is redundant here.
56+
57+
### V2
58+
59+
Linear desing is much better:
60+
- User deploys the `double spend` and it proxies proof to the distributor
61+
- Distributor checks the sending `double spend` address by state init `(distributor_address, user_address?)`
62+
- Distributor checks proof, in this case user index should be part of the proof and releases jettons.
63+
- Distributor sends fee to the fee wallet
64+
**MOAR NAIVE ART**
65+
66+
## Shard optimizations
67+
68+
Ok, we got something going, but what about shard optimizations?
69+
70+
### What are these?
71+
72+
In order to get some very basic grasp of understanding, please take a look at [Wallet Creation for Different shards](https://docs.ton.org/develop/dapps/asset-processing/#wallet-creation-for-different-shards)
73+
Long story short - shard is a 4 bit address prefix. Kind of like in networking.
74+
When contract is in the same network segment, messages get processed without routing and therefore - much faster.
75+
76+
77+
### Identifying what addresses we can control
78+
79+
#### Distributor address
80+
81+
We are in full control of the distributor data, so we must be able to to put it in whatever shard.
82+
How to?
83+
Remember, the contract address is [defined by it's state](https://docs.ton.org/learn/overviews/addresses#account-id).
84+
We should use some of the contract's data field as nonce and keep on trying until we get the desired result.
85+
Example of a good nonce in real contracts can (subwalletId/publicKey) for a wallet contract.
86+
Any field that can be either modified after deployment or does not impact contract logic(like subwalletId) will fit the bill.
87+
One might even create unused field explicitly for this purpose, like [vanity-contract](https://github.com/ton-community/vanity-contract) does
88+
89+
#### Distributor jetton wallet
90+
91+
We can't control resulting jetton wallet address directly.
92+
However, if we control the distributor address, we can pick it so, that the resulting jetton wallet for it would end up in the same shard.
93+
But how to do it? There is a [lib](https://github.com/Trinketer22/turbo-wallet) for it!
94+
It currently supports only wallets, but it's relatively easy to add arbitrary contract support.
95+
Take a look how it is done for [HighloadV3](https://github.com/Trinketer22/turbo-wallet/blob/44fe7ee4300e37e052871275be8dd41035d45c3a/src/lib/contracts/HighloadWalletV3.ts#L20) does.
96+
97+
### Double spend contract
98+
99+
Double spend contract should be unique per proof, so hardly we can shard tune it?
100+
Let's think about it for a bit.
101+
If you think about it, it depends on the proof structure.
102+
First thing that comes to mind is same structure as [mintless jettons](https://github.com/tonkeeper/TEPs2/blob/mintles/text/0177-mintless-jetton-standard.md#handlers)
103+
104+
```
105+
_ amount:Coins start_from:uint48 expired_at:uint48 = AirdropItem;
106+
107+
_ _(HashMap 267 AirdropItem) = Airdrop;
108+
109+
```
110+
In that case, of course it's not tunable, because address distribution is random and all the data fields are meaningful.
111+
But nothing stops us from simply doing this:
112+
```
113+
_ amount:Coins start_from:uint48 expired_at:uint48 nonce:uint64 = AirdropItem;
114+
115+
_ _(HashMap 267 AirdropItem) = Airdrop;
116+
```
117+
or even
118+
```
119+
_ amount:Coins start_from:uint48 expired_at:uint48 addr_hash: uint256 = AirdropItem;
120+
121+
_ _(HashMap 64 AirdropItem) = Airdrop;
122+
123+
```
124+
125+
Where 64 bit index can be used as nonce and address becomes part of data payload for verification.
126+
So, if double spend data is constructed from `(distributor_address, index)` where index is part of the data, we can still have the initial reliability, but now the address shard is tunable via index parameter.
127+
128+
#### User address
129+
130+
Obviously we're not in control of user addresses, do we?
131+
Yes, **BUT** we can group them in such a way that the user address shard matches the distributor shard.
132+
In that case each distributor would process *merkle root* which consists entirely from users originating from it's shard.
133+
134+
#### Summary
135+
136+
We can put `double_spend->dist->dist_jetton` part of the chain in the same shard.
137+
What is left for the other shards is `dist_jetton->user_jetton->user_wallet`.
138+
139+
### How do we actually deploy such setup
140+
Let's see step by step.
141+
One requirement is that *distributor* contract has to have updatable *merkle root*
142+
- Deploy distributor in each shard (0-15) within the same shard as their jetton wallets using initial `merkle_root` as nonce
143+
- Group the users by dist shard
144+
- For each user find such index, so it's *double spend* contract `(distributor, index)` ends up in same shard as the user address.
145+
- Generate *merkle roots* with indexes from step above
146+
- Update *distributors* with according *merkle roots*
147+
148+
149+
Should be good to go now!
150+
151+
### V3
152+
- User deploys *double spend* contract in the same shard using index tuning
153+
- Distributor in user shard checks the sending `double spend` address by state init `(distributor_address, index)`
154+
- Distributor sends fee to the fee wallet
155+
- Distributor checks proof, in this case user index should be part of the proof and releases jettons via jetton wallet in same shard.
156+
157+
**MOAR NAIVE ART**
158+
Is there anything wrong with that? Let's take a good look.
159+
....
160+
Damn right! There is only one fee wallet, and fees are queueing up to a single shard. That could have been a disaster! (Wondering if it ever happened for real?).
161+
162+
### V4
163+
- Same as V3 but 16 wallets now, each in same shard as it's *distributor*.
164+
- Going to have to make *fee wallet* address updatable
165+
166+
**Bit moar art**
167+
168+
How about now? LGTM.
169+
170+
## What's next?
171+
We always can go even further.
172+
Take a look at a custom [jetton wallet](https://github.com/ton-community/mintless-jetton/blob/main/contracts/jetton-utils.fc#L142) which has a built-in shard optimization.
173+
As a result user's jetton wallet ends up in the same shard as a user with 87% probability.
174+
But that's a fairly uncharted territory yet, so you're on your own.
175+
Good luck with TGE!

0 commit comments

Comments
 (0)