Aleo Language Guide: Finalize and Program Interoperability

This article provides a comprehensive overview of the finalize construct in the Aleo programming language. It explains its significance in executing functions on the Aleo blockchain platform and ensuring security and transparency. The document also covers various commands and constructs related to finalization and program interoperability.
Finalize
A finalize in Aleo is a crucial construct that plays a vital role in the execution of program functions on the Aleo blockchain platform. Here are some key points about finalize:
Declaration of finalize
- A finalize is declared as finalize {name}:. It must have the same name as a preceding function and must immediately follow that function.
- The finalize is associated with the function it follows, indicating that it is responsible for completing or finalizing the execution of that function.
Execution on Chain
A finalize executes on the blockchain, specifically after the zero-knowledge proof of the execution of the associated function is verified. This ensures that the finalize carries out its tasks with the security and transparency inherent to the Aleo platform.
Success and Failure
- The outcome of the finalize function is critical.
- Upon the successful execution of the finalize function, the program logic proceeds to execute further instructions.
- In the event of a failure in the finalize function, the program logic is reverted. This means that any changes made during the execution of the associated function and its finalize will be undone, leaving the blockchain in a consistent state.
Example Usage
- In the provided example, there is a function called transfer_public_to_private, which is designed to move a specified amount from the account mapping into a record for a specified receiver.
- This function prioritizes the privacy of the receiver’s record but publicly reveals the sender and the specified amount.
- The finalize block following this function handles the task of decrementing the sender’s balance publicly. It achieves this by first retrieving the balance of the sender from the account mapping. If the balance does not exist (i.e., account[r0] is not found), it uses 0u64. The finalize also checks for underflows to ensure that the sender’s balance is decremented correctly without violating constraints.
Additional Commands
The Aleo Instructions language also supports additional commands within a finalize block. These commands enhance the functionality of programs and allow them to perform a wide range of operations, including block height checks, random number generation, and various types of hashing.
In conclusion, the finalize function in Aleo is pivotal for completing and verifying the execution of functions on the Aleo blockchain platform. It provides the necessary checks and balances to ensure the integrity of on-chain operations and, in case of issues, enables a seamless rollback to maintain a consistent blockchain state.
// The `transfer_public_to_private` function turns a specified amount
// from the mapping `account` into a record for the specified receiver.
//
// This function preserves privacy for the receiver's record, however
// it publicly reveals the sender and the specified amount.
function transfer_public_to_private:
// Input the receiver.
input r0 as address.public;
// Input the amount.
input r1 as u64.public;
// Construct a record for the receiver.
cast r0 r1 into r2 as credits.record;
// Output the record of the receiver.
output r2 as credits.record;
// Decrement the balance of the sender publicly.
finalize self.signer r1;
finalize transfer_public_to_private:
// Input the sender.
input r0 as address.public;
// Input the amount.
input r1 as u64.public;
// Retrieve the balance of the sender.
// If `account[r0]` does not exist, 0u64 is used.
get.or_use account[r0] 0u64 into r2;
// Decrements `account[r0]` by `r1`.
// If `r2 - r1` underflows, `trasfer_public_to_private` is reverted.
sub r2 r1 into r3;
// Updates the balance of the sender.
set r3 into account[r0];
Finalize Commands
Aleo Instructions provide several useful commands within the finalize block that allow you to extend the functionality of your programs. Here are some of the key commands available:
block.height
- The block.height command returns the height of the block in which the program is executed. It essentially provides the latest block height plus one.
- This command can be particularly valuable for managing time-based access control within a program. You can use it to verify the timing of certain operations and ensure they occur at specific block heights.
assert.eq block.height 100u64;
In this example, the assert.eq command is used to assert that the block height is equal to 100u64.
rand.chacha
- The rand.chacha command generates random numbers using the ChaCha20 algorithm.
- It allows you to sample random values of various types, including address, boolean, field, group, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, and scalar.
- You can also provide up to two additional seeds to this command.
rand.chacha into r0 as field;
rand.chacha r0 into r1 as field;
rand.chacha r0 r1 into r2 as field;
In these examples, the rand.chacha command is used to generate random values and store them in different registers for further use within the program. The generated values can be of type field, but you can adapt the command for other types as needed.
These commands provide enhanced flexibility when designing your programs and can be particularly useful for creating dynamic or randomized behavior within your blockchain applications.
Hash
Aleo Instructions provide a variety of hashing commands that allow you to hash data into different standard types. Here’s an overview of these hashing commands and their usage:
Supported Syntax for Hashing to Standard Types:
- hash.bhp256
- You can use this command to hash data into the following standard types: address, field, group, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, and scalar.
hash.bhp256 r0 into r1 as address;
hash.bhp256 r0 into r1 as field;
hash.bhp256 r0 into r1 as group;
hash.bhp256 r0 into r1 as i8;
hash.bhp256 r0 into r1 as i16;
hash.bhp256 r0 into r1 as i32;
hash.bhp256 r0 into r1 as i64;
hash.bhp256 r0 into r1 as i128;
hash.bhp256 r0 into r1 as u8;
hash.bhp256 r0 into r1 as u16;
hash.bhp256 r0 into r1 as u32;
hash.bhp256 r0 into r1 as u64;
hash.bhp256 r0 into r1 as u128;
hash.bhp256 r0 into r1 as scalar;
Additional Hashing Commands:
- Apart from hash.bhp256, Aleo Instructions provide other hashing commands such as hash.bhp512, hash.bhp768, hash.bhp1024, hash.ped64, hash.ped128, hash.psd2, hash.psd4, and hash.psd8.
- These commands allow you to perform various hashing operations with specific algorithms and data types. The exact usage may vary depending on your program’s requirements.
These hashing commands are essential for securely processing and verifying data within Aleo programs. You can choose the appropriate command based on the data type and the desired hash algorithm for your use case. For a full list of supported hashing algorithms and their details, you can refer to the Aleo Instructions opcodes documentation.
Commit
Aleo Instructions also provide a variety of commitment commands that allow you to commit to data into different standard types.
Supported Syntax for Committing to Standard Types: commit.bhp256. You can use this command to commit to data into the following standard types: address, field, group.
commit.bhp256 r0 r1 into r2 as address;
commit.bhp256 r0 r1 into r2 as field;
commit.bhp256 r0 r1 into r2 as group;
commit.bhp512 ...;
commit.bhp768 ...;
commit.bhp1024 ...;
commit.ped64 ...;
commit.ped128 ...;
Additional Commitment Commands
- Similar to the hashing commands, Aleo Instructions provide other commitment commands such as commit.bhp512, commit.bhp768, commit.bhp1024, commit.ped64, and commit.ped128.
- These commands allow you to perform various commitment operations with specific algorithms and data types. The exact usage may vary depending on your program’s requirements.
These commitment commands are crucial for ensuring data integrity and privacy within Aleo programs. You can choose the appropriate command based on the data type and the desired commitment algorithm for your use case. For a full list of supported commitment algorithms and their details, you can refer to the Aleo Instructions opcodes documentation.
position, branch.eq, branch.neq
In Aleo Instructions, you can control the flow of program execution using commands like position
, branch.eq
, and branch.neq
. Here's a brief explanation of these commands and an example to illustrate their usage:
position
: Theposition
command is used to indicate a point in the program where execution can branch to. It essentially marks a specific location in the program code.branch.eq
: Thebranch.eq
command is used to branch execution to a specific position (indicated by theto
keyword) if two values are equal. If the condition specified in the command is met, execution will proceed to the specified position; otherwise, it continues sequentially.branch.neq
: Thebranch.neq
command is similar tobranch.eq
, but it branches execution if the two values are not equal.
Here’s an example program to illustrate the usage of these commands:
program test_branch.aleo;
function run_test:
input r0 as u8.public;
finalize r0;
finalize run_test:
input r0 as u8.public;
// Branch execution to the "exit" position if r0 is equal to 0u8.
branch.eq r0 0u8 to exit;
// If r0 is not equal to 0u8, this assert will fail, and execution will not reach "exit."
assert.eq true false;
// Mark the "exit" position for branching.
position exit;
In this example, the program test_branch.aleo
defines a run_test
function. In the finalize
block, it checks the value of r0
. If r0
is equal to 0, it branches to the exit
position. If r0
is not equal to 0, it encounters an assert
command that will fail, and the program logic will not reach the exit
position. This demonstrates how branch.eq
and position
can be used to control program execution based on conditions.
self.signer
The self.signer
command in Aleo Instructions returns the user address that originated the transition, i.e., the sender's address. This is a fundamental feature that allows you to identify and manage access control to a program.
In the provided example of the transfer_public_to_private
function, it is used to identify the sender or the person initiating the transaction. Here's a recap of how it works:
// The `transfer_public_to_private` function turns a specified amount
// from the mapping `account` into a record for the specified receiver.
//
// This function preserves privacy for the receiver's record, however
// it publicly reveals the sender and the specified amount.
function transfer_public_to_private:
// Input the receiver.
input r0 as address.public;
// Input the amount.
input r1 as u64.public;
// Construct a record for the receiver.
cast r0 r1 into r2 as credits.record;
// Output the record of the receiver.
output r2 as credits.record;
// Decrement the balance of the sender publicly using self.signer.
finalize self.signer r1;
In this code, self.signer
identifies the sender, and the sender's balance is publicly decremented by the amount specified in r1
. The purpose of revealing the sender's address and the transaction amount publicly might be for transparency or auditing reasons, and this demonstrates how self.signer
is utilized for access control and accounting within the Aleo Instructions framework.
self.caller
In Aleo Instructions, the self.caller
command is used to retrieve the address of the immediate caller of the program. This can be particularly useful for managing and verifying the identity of the entity that initiated the execution of the program.
By using self.caller
, you can access information about the sender of a transaction or the entity that is invoking the program. This allows you to perform various checks and access control mechanisms based on the caller's identity and privileges.
For example, you might want to implement specific actions or restrictions based on who is calling your program. This can be an essential feature for enforcing security and access control in your Aleo-based applications and smart contracts.
- Caller Identification:
self.caller
provides the address of the immediate caller. In the context of blockchain and smart contracts, a "caller" refers to the entity or account that initiates the execution of a particular function or program. Aleo Instructions give you the ability to ascertain the identity of this caller. - Access Control: Understanding who is calling a program is crucial for implementing access control. By checking the caller’s address, you can make decisions on whether they have permission to perform certain actions. For instance, in a token transfer function, you might use
self.caller
to verify that the caller is the rightful owner of the tokens and has the authority to transfer them. - Security: Caller identification is integral to maintaining the security of a decentralized application. You can use
self.caller
to prevent unauthorized access and ensure that only authorized users can perform sensitive operations within your application. - Multisignature Transactions: In scenarios where multiple parties need to collectively approve or sign a transaction (multisignature transactions),
self.caller
can help identify each of the parties involved. This is especially valuable for financial applications where multiple signatures are required for significant transfers. - Custom Logic: The caller’s address can also be used to trigger specific logic based on their identity. For example, you might have different behavior within your smart contract based on whether the caller is an administrator, a regular user, or a specific smart contract.
- Logging and Auditing: Knowing who called a specific function is essential for auditing and logging purposes. By recording the caller’s address along with the action they performed, you can maintain a transparent and verifiable transaction history.
- Interactions with Other Contracts: When you need to interact with other smart contracts, knowing the identity of the caller is important. It can help in coordinating and authenticating interactions between different parts of your decentralized application.
Overall, self.caller
is a fundamental command in Aleo Instructions that empowers developers to create secure, access-controlled, and customized blockchain applications by understanding the identity of those interacting with their programs.
Program Interoperability
Program interoperability is a crucial aspect of blockchain development that enables different smart contracts or programs to work together seamlessly. Aleo Instructions, the programming language for the Aleo blockchain, provides a robust framework for achieving program interoperability. In this article, we’ll explore how to ensure your Aleo programs can effectively communicate and collaborate on the blockchain.
The Prerequisite Environment
NETWORK=testnet3
PRIVATE_KEY=APrivateKey1zkpE37QxQynZuEGg3XxYrTuvhzWbkVaN5NgzCdEGzS43Ms5 # user private key
ADDRESS=aleo1p2h0p8mr2pwrvd0llf2rz6gvtunya8alc49xldr8ajmk3p2c0sqs4fl5mm # user address
- NETWORK=testnet3: We’ll be operating in the testnet3 network, a development environment that mirrors the real Aleo blockchain.
- PRIVATE_KEY=APrivateKey1zkpE37QxQynZuEGg3XxYrTuvhzWbkVaN5NgzCdEGzS43Ms5: This is your user’s private key, which is required for transaction signing and interaction with the blockchain.
- ADDRESS=aleo1p2h0p8mr2pwrvd0llf2rz6gvtunya8alc49xldr8ajmk3p2c0sqs4fl5mm: Your user’s address is their unique identifier on the Aleo blockchain. This is crucial for specifying the source and destination of transactions.
Interacting with Other Programs
A fundamental aspect of program interoperability in Aleo Instructions is the ability to interact with other programs. This interaction often involves calling functions or methods in external programs, similar to how libraries or APIs are used in traditional software development. Let’s explore how you can achieve this within Aleo.
Importing External Programs
The first step in interacting with other programs is importing them. This ensures that your program can access the functions and data provided by the imported program. Here’s an example of importing an external program named foo.aleo
:
import foo.aleo;
By importing foo.aleo
, you gain access to the functions and data structures defined within it, allowing you to call its functions and make use of its capabilities.
Calling Functions in External Programs
Once you’ve imported an external program, you can call its functions or methods. This is a pivotal aspect of program interoperability. The call
command is used to invoke functions in external programs. Here's an example of how you can call a function named baz
in the foo.aleo
program:
call foo.aleo/baz r0 into r1;
In this example, we’re calling the baz
function from foo.aleo
and passing r0
as an argument. The result is stored in r1
, allowing your program to work with the data produced by the external program.
Practical Use Cases
Program interoperability in Aleo Instructions opens the door to numerous practical use cases. Here are a few examples:
- Delegated Actions: You can delegate certain actions or processes to external programs, enhancing the modularity and flexibility of your blockchain application.
// Delegating an action to an external program.
call external_program/delegated_action r0 into r1;
- Oracles: External programs can act as oracles, providing real-world data to your smart contracts.
// Using an external program as an oracle.
call price_oracle/get_price r0 into r1;
- Decentralized Finance (DeFi): DeFi applications often rely on multiple smart contracts working together. Program interoperability is crucial for creating complex DeFi protocols.
// Interacting with multiple DeFi smart contracts.
call lending_protocol/borrow r0 into r1;
call yield_farm/stake r1 into r2;
- Cross-Chain Communication: In scenarios where different blockchains are involved, Aleo Instructions can be used to interact with other blockchain networks, enabling cross-chain functionality.
// Communicating with an external blockchain.
call ethereum_bridge/lock_tokens r0 into r1;
call ethereum_bridge/unlock_tokens r2 into r3;
Child and Parent Program
In this example, we’ll explore how a parent program, parent.aleo
, can call a function from another program, child.aleo
. This demonstrates the concept of child and parent programs and how they can interact on the Aleo blockchain.
child.aleo:
program child.aleo;
function foo:
output self.caller as address.public;
output self.signer as address.public;
Here, in the child.aleo
program, there's a function named foo
. This function outputs the caller's address and the signer's address.
parent.aleo:
import child.aleo;
program parent.aleo;
// Make an external program call from `parent.aleo` to `function foo` in `child.aleo`.
function foo:
call child.aleo/foo into r0 r1;
output r0 as address.public;
output r1 as address.public;
output self.caller as address.public;
output self.signer as address.public;
In the parent.aleo
program, child.aleo
is imported using import child.aleo;
. This allows parent.aleo
to access the functions and data defined in child.aleo
.
Within the parent.aleo
program, a function named foo
is defined. In this function, a call is made to the foo
function of child.aleo
using the call
command:
call child.aleo/foo into r0 r1;
This line calls the foo
function in child.aleo
and captures its outputs in registers r0
and r1
. Additionally, it outputs the caller's address and the signer's address from the parent.aleo
program using self.caller
and self.signer
.
When you execute this program using snarkvm execute foo
, you'll obtain the following results:
Outputs:
- The address of the caller of
child.aleo/foo
fromprogram.aleo
- The address that originated the sequence of calls leading up to
child.aleo/foo
(user address) - The address of the caller of
program.aleo/foo
(user address) - The address that originated the sequence of calls leading up to
program.aleo/foo
(user address)
This demonstrates how a parent program can successfully call a function from a child program and interact with its data and results, showcasing program interoperability within the Aleo blockchain.
User Callable Program
In this example, we’ll explore how a user-callable program works by restricting a function to only be callable by users. The assert.eq self.caller self.signer
condition is used to ensure that a function can only be called by the user who signs the transaction.
child.aleo:
program child.aleo;
function foo:
assert.eq self.caller self.signer; // This check should fail if called by another program.
output self.caller as address.public;
output self.signer as address.public;
In the child.aleo
program, there's a function named foo
. Inside this function, an assert.eq
statement is used to compare self.caller
and self.signer
. The intention here is to ensure that the caller (self.caller
) is the same as the signer (self.signer
). If they are not equal, the assertion will fail, indicating that the function should only be callable by the user.
parent.aleo:
import child.aleo;
program parent.aleo;
// Make an external program call from `parent.aleo` to `function foo` in `child.aleo`.
function foo:
call child.aleo/foo into r0 r1;
output r0 as address.public;
output r1 as address.public;
output self.caller as address.public;
output self.signer as address.public;
In the parent.aleo
program, child.aleo
is imported, allowing the foo
function to be called from it.
However, since the assert.eq self.caller self.signer
check in child.aleo
ensures that the caller (self.caller
) must be the same as the signer (self.signer
), the call to child.aleo/foo
in the parent.aleo
program fails when executed.
When you execute this program using snarkvm execute foo
, you'll see that the assertion check fails, and the error message states that the caller's address is not equal to the signer's address, confirming that the function can only be called by the user who signs the transaction.
This example demonstrates how developers can restrict certain functions to be callable only by users, ensuring that they are not invoked by other programs or entities.
Program Callable Program
In this example, we’ll explore how to create a program that is callable only by other programs, and not by regular users. The assert.neq self.caller self.signer
condition is used to ensure that the function can only be called by another program.
restrict.aleo:
program restrict.aleo;
function foo:
assert.neq self.caller self.signer; // This check should fail if called by the user.
output self.caller as address.public;
output self.signer as address.public;
In the restrict.aleo
program, there's a function named foo
. Inside this function, an assert.neq
statement is used to compare self.caller
and self.signer
. The intention here is to ensure that the caller (self.caller
) is not the same as the signer (self.signer
). If they are equal, the assertion will fail, indicating that the function should only be callable by other programs, not by regular users.
When you execute this program using snarkvm execute foo
, you'll see that the assertion check fails, and the error message states that the caller's address is equal to the signer's address, confirming that the function can only be called by other programs, and not by regular users.
This example demonstrates how developers can create programs that are designed to interact with other programs and restrict access to regular users.