Unbounded Arrays - Todo List

In general, infinite data structures that can grow to billions of elements are very difficult to implement on a blockchain. As the contract's persistent state grows in size, read and write operations become more expensive in terms of gas. In extreme cases, they may cost more than a transaction's gas limit, rendering the contract unusable.

Therefore, it's important to design contracts with an upper bound on state size. So, how would we implement a to-do list that can scale to billions of items?

Infinitely scalable todo list

The secret to achieving infinite scalability on TON lies in sharding the data across multiple contracts. We can utilize the parent-child design pattern to achieve this.

In this example, each new todo item is deployed as a new child contract. Users interact with the child contracts through the TodoParent contract.

When the user sends the NewTodo message to the parent, the parent deploys a new child to hold the new item. If users want to query the item details, they can call the parent getter todoAddress() and then call the details() getter on the child.

Info: This example also handles gas efficiently. The excess gas from every operation is refunded to the original sender.
All Examples
import "@stdlib/deploy";

struct Metadata {
    symbol: String;
    totalSupply: Int;
}

message Transfer {
    amount: Int as coins;
    to: Address;
}

// the token parent, mostly used to query general metadata and get children addresses
contract TokenParent with Deployable {
 
    symbol: String;
    totalSupply: Int as coins;

    init() {
        self.symbol = "SHIB";
        self.totalSupply = 500 * pow(10,9);
        self.mint(self.totalSupply, sender()); // mint the entire total supply to deployer
    }

    fun mint(amount: Int, to: Address) {
        let init: StateInit = initOf TokenChild(myAddress(), to);
        send(SendParameters{
            to: contractAddress(init),
            body: InternalAddTokens{amount: amount, origin: myAddress()}.toCell(),
            value: ton("0.03"),             // pay for the deployment and leave some TON in the child for storage
            mode: SendIgnoreErrors,
            code: init.code,                // deploy the child if needed
            data: init.data
        });
    }

    get fun metadata(): Metadata {
        return Metadata{symbol: self.symbol, totalSupply: self.totalSupply};
    }

    get fun childAddress(owner: Address): Address {
        return contractAddress(initOf TokenChild(myAddress(), owner));
    }
}

////////////////////////////////////////////////////////////////////////////
// child contract - the Transfer message is sent by users directly to a child

message InternalAddTokens {
    amount: Int as coins;
    origin: Address;
}

contract TokenChild {

    parent: Address;
    owner: Address;         // every child holds the balance of a different owner
    balance: Int as coins;  // this is the balance of the owner
 
    init(parent: Address, owner: Address) {
        self.parent = parent;
        self.owner = owner;
        self.balance = 0;
    }

    // sent by users to initiate a new transfer
    receive(msg: Transfer) {
        require(sender() == self.owner, "Access denied");
        require(self.balance >= msg.amount, "Insufficient balance");
        self.balance = self.balance - msg.amount;
        let init: StateInit = initOf TokenChild(self.parent, msg.to);
        send(SendParameters{
            to: contractAddress(init),
            body: InternalAddTokens{amount: msg.amount, origin: self.owner}.toCell(),
            value: ton("0.03"),             // pay for the deployment and leave some TON in the child for storage
            mode: SendIgnoreErrors,
            code: init.code,                // deploy the child if needed
            data: init.data
        });
        self.reply("transferred".asComment());
    }

    // internal message sent by one child to another to update balances
    receive(msg: InternalAddTokens) {
        if (msg.origin == self.parent) { // tokens originate in a mint
            require(sender() == self.parent, "Parent only");
        } else { // tokens originate in a Transfer
            require(sender() == contractAddress(initOf TokenChild(self.parent, msg.origin)), "Sibling only");
        }
        self.balance = self.balance + msg.amount;
    }

     get fun balance(): Int {
        return self.balance;
    }
}