Skip to main content
Version: 4.0.2

The Finalization Model

Background​

Leo programs run in two distinct contexts:

  • Proof context — private, off-chain execution that generates ZK proofs. Regular fn declarations run here. Inputs can be private, and the computation is not visible on-chain.
  • Finalization context — public, on-chain execution that modifies state. final { } blocks run here. All inputs and operations are publicly visible.

Managing Public State​

On-chain data is stored publicly in one of three data structures: mappings, storage variables, and storage vectors. Any logic that reads from or updates the state of these structures must be contained within a final { } block or inside a final fn:

program first_public_state.aleo {
mapping accumulator: u8 => u64;
storage count: u8;
storage queue: [u8];

//=============================================================
// MAPPING MODIFICATION
//=============================================================
fn increment_accumulator() -> Final {
return final {
let current_count: u64 = accumulator.get_or_use(0u8, 0u64); // Get current value, defaults to 0
let new_count: u64 = current_count + 1u64;
accumulator.set(0u8, new_count);
};
}

//=============================================================
// STORAGE VARIABLE MODIFICATION
//=============================================================
fn increment_count() -> Final {
return final {
let current_count: u8 = count.unwrap_or(0u8); // Get current value, defaults to 0
count = current_count + 1u8;
};
}

//=============================================================
// STORAGE VECTOR MODIFICATION
//=============================================================
fn add_to_queue(val: u8) -> Final {
return final {
queue.push(val); // Push to end of queue
};
}
}

Entry functions with on-chain logic return Final and embed the finalization code in an inline final { } block. A few additional rules apply:

  • Entry functions can return additional data types in a tuple, including Records, along with a Final.
  • Only one Final can be returned.
  • If multiple types are returned, the Final must be the last type in the tuple.

External Calls​

Leo enables developers to call entry functions from imported programs. A call to an external entry function that returns Final produces a Final value which can be composed inside a final { } block using the .run() method:

import first_public_storage.aleo;

program second_public_storage.aleo {
mapping hashes: u8 => scalar;

fn two_mappings(value: u8) -> Final {
let increment_final: Final = first_public_storage.aleo::increment();
return final {
increment_final.run();
let hash: scalar = BHP256::hash_to_scalar(value);
hashes.set(value, hash);
};
}
}

You can access the inputs to an external Final using the following syntax:

let f = imported_program.aleo::some_function();
let value = f.0; // or f.1, f.2, f.3 and so on depending on the input index

Managing Both Public and Private State​

Records are private state and can be created or consumed in the proof context (the entry fn body), but not inside final { } blocks or final fn. The finalization context runs purely on-chain and only has access to public on-chain state.

Public StatePrivate State
Where it runsfinal { } blockEntry fn body
Data Storagemapping, storagerecord
Visibilityeveryonevisible if you have the viewkey