Cover photo

Aleo Leo Programs in Practice

Hi, my name is Heorhii, and I’ll guide you through working with Leo programs on Aleo—beyond the basics. Let’s explore how to use mappings effectively, understand function types, and structure your program to fit the constraints of the Aleo environment.

Understanding mapping operations. Mappings in Aleo are a form of on-chain key-value storage. You’ll often use them to track balances or user-specific state. These operations are only allowed within async functions and must follow a few defined patterns:

  • get – Retrieves a value from a mapping. Fails if the key doesn't exist.

  • get_or_use – Retrieves the value or initializes it with a default if the key is missing.

  • set – Updates a value in the mapping.

  • contains – Checks if a key exists.

  • remove – Deletes a key-value pair.

Here's a basic example of updating a user counter:

program test.aleo {
    mapping counter: address => u64;

    async transition dubble() -> Future {
        return update_mappings(self.caller);
    }

    async function update_mappings(addr: address) {
        let current_value: u64 = Mapping::get_or_use(counter, addr, 0u64);
        Mapping::set(counter, addr, current_value + 1u64);
        let new_value: u64 = Mapping::get(counter, addr);
        Mapping::set(counter, addr, new_value + 1u64);
    }
}

Working with functions in Leo. Leo supports several types of functions. Each type has specific usage rules and visibility:

1. Transition functions. These are your main entry points and can interact with the Aleo blockchain.

transition foo(public a: field, b: field) -> field {
    return a + b;
}

2. Helper functions. Used internally and do not involve records or state changes.

function add(a: field, b: field) -> field {
    return a + b;
}

3. Inline functions. Great for simple logic. They’re inlined by the compiler and cannot be called directly.

inline square(x: u64) -> u64 {
    return x * x;
}

4. Async functions. Used for asynchronous, on-chain computation. Typically called from async transition functions.

async transition transfer_public_to_private(receiver: address, public amount: u64) -> (token, Future) {
    let new: token = token { owner: receiver, amount };
    return (new, update_public_state(self.caller, amount));
}

async function update_public_state(public sender: address, public amount: u64) {
    let balance: u64 = Mapping::get_or_use(account, sender, 0u64);
    Mapping::set(account, sender, balance - amount);
}

Keeping constraints in mind. When building with Leo, you need to consider limitations defined by snarkVM:

  • Max program size: 100 KB

  • Max mappings: 31

  • Max imports: 64

  • Max functions: 31

  • Max transaction size: 128 KB

  • Max compute budget: 100,000,000 microcredits

If you hit these limits, try modularizing your code or simplifying your data structures. All these parameters exist to ensure on-chain execution remains efficient and scalable.

Learn more:

https://docs.leo-lang.org/language/programs

Final thoughts. As you begin developing with Leo, keep in mind how function visibility, mapping behavior, and on-chain constraints shape your architecture. The async pattern and use of mappings are essential for building any useful ZK-powered app on Aleo.

I hope this article helps you move from writing basic transitions to building real-world ZK apps. More articles coming soon!

Let me know what you'd like me to explore next.

Subscribe

To know more about Aleo, join now!

Prepared by Colliseum