2. First attempt: one contract
Sketch the obvious single-contract approach for a poll, then discover why it cannot scale on TON.
The obvious design
A poll needs to:
- Accept a
Votemessage from any wallet - Check whether that wallet has voted before
- If not, count the vote and record the wallet
The first instinct is to keep everything in one contract, using a dictionary (hashmap) stored in the contract's persistent data:
struct PollStorage {
owner: address
option0: uint32
option1: uint32
voters: map<address, bool>
}On each Vote, look up the sender in the map. If absent, add them and increment the count:
var storage = lazy PollStorage.load();
assert (msg.option == 0 || msg.option == 1) throw Errors.InvalidOption;
val alreadyVoted = storage.voters.exists(in.senderAddress);
assert (!alreadyVoted) throw Errors.AlreadyVoted;
storage.voters.set(in.senderAddress, true);
if (msg.option == 0) {
storage.option0 += 1;
} else {
storage.option1 += 1;
}
storage.save();With a small number of voters it works correctly.
The wall
TON contracts store persistent data in a cell tree. A single cell holds at most 1023 bits and up to 4 references. The full tree must fit in the TVM stack when the contract runs. One address → bool entry per voter fills up that tree long before a popular poll ends.
More fundamentally: TON itself shards. Different contracts run in parallel on different shards. A single contract holding all voter state becomes a sequential bottleneck — every vote is a write to the same storage.
The target architecture
The canonical solution on TON is per-user contracts. Instead of one large map, deploy a tiny Voter contract per wallet address. The parent Poll counts votes — it never stores who voted. Each Voter tracks whether its owner has already voted. This pattern is called sharding and appears everywhere on TON — Jetton wallets, NFT items, and more.
Pollholds two counters only:option0andoption1- On each
Vote,Polldeploys or messages aVotercontract at the deterministic address derived from the sender's wallet Voterrejects duplicate votes by throwing an error, which bounces the message back toPollPoll'sonBouncedMessagereverts the count increment
The next two pages implement this: first the contracts, then the tests that prove it works.
Last updated on