This article provides a detailed exploration of resource limitations encountered during Solana development. It specifically focuses on Compute Unit (CU) restrictions, using examples and program comparisons to illustrate how these limitations can affect Solana development.
Many developers face a common issue when building Solana programs (smart contracts)—the logic of their program appears correct, but when the program runs, unexpected errors occur. These errors often contain terms like "limit" or "exceed," indicating that the program has hit one of Solana's resource constraints. As a high-performance blockchain, Solana's core features, such as parallel processing, significantly boost transaction throughput. Behind this efficiency lies a strict resource management mechanism, requiring developers to understand these limitations in order to effectively develop and optimize Solana programs. This article introduces the various resource limitations in Solana development, with a particular focus on Compute Unit (CU) restrictions, analyzing real-world scenarios and optimization strategies.
Solana was launched in 2020 and quickly emerged as a popular blockchain network, becoming one of the leading ecosystems in the cryptocurrency industry. According to recent data, Solana accounted for 49.3% of global cryptocurrency investors' interest in specific chains in 2024, demonstrating its dominant position in the market.
Solana is a high-performance public blockchain platform that differs from traditional blockchain networks like Bitcoin and Ethereum. Its core characteristics include high throughput and low latency, thanks to its innovative Proof of History (PoH) consensus mechanism and efficient parallel processing architecture, enabling it to process thousands of transactions per second. To maintain network stability and fairness, Solana imposes various resource limitations on program execution to ensure the proper allocation and maximization of system resources.
Programs running on Solana are subject to several types of resource limitations. These limitations are designed to maintain the efficiency and stability of the network while providing developers with clear boundaries for development. These constraints cover a wide range of requirements, from computational power to data storage, ensuring that programs can use system resources fairly while maintaining performance.
In the Solana blockchain, CU is the smallest unit used to measure computational resources consumed during transaction execution. Each transaction on the chain consumes a certain number of CUs depending on the operations it performs (e.g., account writes, cross-program invocations, system calls). Every transaction has a CU limit, which can be set to the default or modified by the program. When a transaction exceeds the CU limit, processing is halted, resulting in a failed transaction. Common operations like executing instructions, transferring data between programs, and performing cryptographic calculations consume CUs. The CU system is designed to manage resource allocation, prevent network abuse, and improve overall efficiency. For more details, refer to the official documentation here.
The following CU limits apply in Solana:
For a transaction containing only one instruction, the CU limit would default to 200,000. However, the CU limit can be adjusted using the SetComputeUnitLimit
instruction, but cannot exceed the maximum transaction limit of 1.4 million CUs.
In Solana, each account's data structure is called AccountInfo, which includes the account's state, program code (if it's a program account), balance (in lamports, where 1 SOL = 1 billion lamports), and the associated owner program (program ID). In Solana's account model, each account is owned by a program, and only the owner program can modify the account data or reduce the balance. Adding balance, however, is not restricted. This model ensures the security of account data and the controllability of operations. For more information, check out the documentation here.
Here are the storage limitations in Solana:
Solana follows a maximum transmission unit (MTU) limit of 1280 bytes, in line with the IPv6 MTU standard, to ensure the efficient and reliable transmission of cluster information via UDP. After accounting for the 48-byte IPv6 and fragmentation headers, 1232 bytes remain for data, such as serialized transactions. This means that each Solana transaction, including its signature and message parts, cannot exceed 1232 bytes. Each signature occupies 64 bytes, with the number of signatures depending on the transaction requirements. The message part contains instructions, accounts, and other metadata, with each account taking 32 bytes. The total size of a transaction varies depending on the number of instructions it includes. This limit ensures efficient transmission of transaction data across the network. For more details, refer to the documentation here.
Here are the transaction size limitations in Solana:
To ensure efficient program execution, Solana imposes limits on the call stack depth of each program. If this limit is exceeded, a CallDepthExceeded
error is triggered. Solana also supports direct calls between programs (cross-program invocations), but the depth of such calls is also limited. Exceeding this depth triggers a CallDepth
error. These restrictions aim to enhance network performance and resource management efficiency. For more information, refer to the documentation here.
Here are the call depth limitations in Solana:
In Solana's virtual machine architecture, each stack frame has a size limit, using fixed stack frames instead of variable stack pointers. If the stack frame exceeds this limit, the compiler will issue a warning but will not block compilation. At runtime, if the stack size is exceeded, an AccessViolation
error occurs. Regarding heap management, Solana uses a simple heap with fast allocation via the bump model, which does not support memory deallocation or reallocation. Each Solana program has access to this memory area and can implement custom heap management as needed. These limitations help optimize performance and resource allocation. For more details, refer to the documentation here.
Here are the stack size limitations in Solana:
In Solana, Program Derived Addresses (PDA) offer developers a deterministic method for generating account addresses using predefined seeds (such as strings, numbers, or other account addresses) and the program ID. This mechanism mimics an on-chain hash map function. Additionally, Solana allows programs to sign transactions using their derived PDAs. The advantage of PDAs is that developers do not need to remember specific account addresses; instead, they only need to remember the input used to derive the address, simplifying account management and improving development efficiency. For more details, check out the documentation here.
Here are the PDA account limitations in Solana:
Having introduced the various resource limitations in Solana, let’s now focus on the CU limitations. As mentioned earlier, CU is the smallest unit used to measure the computational resources consumed during transaction execution. Each transaction's CU consumption cannot exceed 1.2 million CUs. For developers new to Solana, this concept might not be very intuitive, so let's break it down with some examples to better understand CU consumption.
Before analyzing CU consumption in programs, let’s first learn how to display CU consumption in Solana programs. In Solana, the log
function can be used to output logs, including CU consumption. Here's a simple example:
use solana_program::log::sol_log_compute_units;
sol_log_compute_units();
// other code
sol_log_compute_units();
Each call to sol_log_compute_units
outputs the current CU consumption. Below is a sample log after the program runs:
Program consumption: 149477 units remaining
# other logs
Program consumption: 137832 units remaining
By comparing the difference between two sol_log_compute_units
calls, we can calculate the CU consumption of the program. In this example, the difference between the two values represents the CU consumed by the operations performed between the two log calls.
We can also encapsulate this CU logging functionality into a Rust macro for easier usage in the program. Here’s how the code would look:
// build a macro to log compute units
#[macro_export]
macro_rules! compute_fn {
($msg:expr=> $($tt:tt)*) => {{
msg!(concat!($msg, " {"));
sol_log_compute_units();
let res = { $($tt)* };
sol_log_compute_units();
msg!(concat!(" } // ", $msg));
res
}};
}
// use the macro to log compute units
compute_fn!("create account" => {
// create account code
});
Encapsulating logging as a macro makes it much more convenient to call within the program while also providing additional information, making debugging easier.
To better understand CU consumption in Solana programs, we can explore example programs that demonstrate the CU usage of different operations.
Solana provides many learning resources for beginners, including a collection of simple example programs (program-examples
) designed to help developers understand the Solana development process. These examples can be found in this GitHub repository.
Each example program in this repository typically has two versions: one implemented as a native program and the other using the Anchor framework.
Anchor is a widely-used development framework for the Solana blockchain, designed to simplify the creation of programs (smart contracts) and decentralized applications (DApps). It provides developers with an efficient and intuitive set of tools and libraries, significantly lowering the barrier to entry for Solana application development. Solana officially recommends using Anchor for development.
In the following sections, we’ll reference examples from this repository to analyze CU consumption in Solana. We’ll break this down from the perspectives of operations and programs.
Let’s start by examining some common operations in Solana and analyzing their respective CU consumption.
Transfer SOL
Transferring SOL is one of the most common operations in Solana. Each transfer consumes a certain amount of CUs. You might wonder how many CUs are required for a single transfer. To find out, we can conduct a simple experiment on Solana's Devnet by performing a SOL transfer and then checking the transaction's CU consumption in the Solana Explorer. The result is as follows:
From the transaction details in the Explorer, we can see that the transfer consumed 150 CUs. This value is not fixed but generally doesn’t vary significantly. The amount of CU consumed is unrelated to the transfer amount but is instead determined by the number and complexity of the instructions in the transaction.
Create Account
Creating an account is another common operation in Solana. Each account creation consumes a certain amount of CUs. We can analyze CU consumption by running an account creation example.
In the program-examples
repository, you can find a sample program for account creation in the basic/create-account
directory. By adding CU logging statements to the code, we can verify CU consumption during execution. Below is the log output after running the program:
[2024-12-08T07:34:47.865105000Z DEBUG solana_runtime::message_processor::stable_log] Program consumption: 186679 units remaining
[2024-12-08T07:34:47.865181000Z DEBUG solana_runtime::message_processor::stable_log] Program 11111111111111111111111111111111 invoke [2]
[2024-12-08T07:34:47.865209000Z DEBUG solana_runtime::message_processor::stable_log] Program 11111111111111111111111111111111 success
[2024-12-08T07:34:47.865217000Z DEBUG solana_runtime::message_processor::stable_log] Program consumption: 183381 units remaining
[2024-12-08T07:34:47.865219000Z DEBUG solana_runtime::message_processor::stable_log] Program log: Account created succesfully.
From this test, we found that creating an account consumes approximately 3000 CUs. This value is not fixed but typically remains within a close range.
Create a Simple Data Structure
Next, let’s analyze the CU consumption of creating a simple data structure. An example of this can be found in the basic/account-data
directory of the program-examples
repository. The full example program can be found here. Below is the data structure defined in the program:
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)] // automatically calculate the space required for the struct
pub struct AddressInfo {
#[max_len(50)] // set a max length for the string
pub name: String, // 4 bytes + 50 bytes
pub house_number: u8, // 1 byte
#[max_len(50)]
pub street: String, // 4 bytes + 50 bytes
#[max_len(50)]
pub city: String, // 4 bytes + 50 bytes
}
This data structure contains three string fields and one u8
field. Each string field has a maximum length of 50. Testing reveals that creating this simple data structure consumes approximately 7000 CUs.
Counter
Solana’s official example programs include a simple counter program. This can be found in the basic/counter
directory of the program-examples
repository. The example defines a basic counter data structure, along with instructions for creating and incrementing the counter. The full example program is available here.
Testing indicates that initializing the counter consumes approximately 5000 CUs, while incrementing the counter consumes about 900 CUs.
Transfer Token
Another relatively common but more complex operation in Solana is transferring Tokens.
In Solana, Tokens are implemented through the SPL Token standard, which is an official Solana standard for issuing, transferring, and managing tokens. SPL Token provides a standard interface to simplify token-related operations on Solana.
The program-examples
repository includes an example program for transferring tokens, which can be found in the token/transfer-tokens
directory. This program also includes operations for creating tokens, minting tokens, and burning tokens. The full example code is available here.
Testing results reveal the following CU consumption for token-related operations:
We can also observe the CU consumption of a token transfer in a real transaction. For example, in this transaction, the CU consumption for the token transfer can be seen in the log output at the bottom of the transaction details:
Summary
Here’s a summary of CU consumption for common operations:
Action | CU Cost (approx.) |
---|---|
Transfer SOL | 150 |
Create Account | 3000 |
Create Simple data struct | 7000 |
Counter | 5000 (Init) 900 (Add count) |
Token | 3000 (Create) 4500 (Mint) 4000 (Burn) 4500 (Transfer) |
Having reviewed the CU consumption of common operations in Solana, let’s now look at the CU usage of frequently used program constructs and syntax.
Loop Statements
Loops are one of the common constructs in Solana programs. By analyzing loop statements, we can understand their CU consumption. Below is a comparison of CU usage for different loop sizes:
// simple msg print, cost 226 CU
msg!("i: {}", 1);
// simple print for loop 1 time, cost 527 CU
for i in 0..1 {
msg!("i: {}", i);
}
// simple print for loop 2 times, cost 934 CU
for i in 0..2 {
msg!("i: {}", i);
}
Tests reveal that a simple msg!
statement consumes 226 CU. Adding a loop that runs once increases the CU consumption to 527 CU, while running the loop twice raises it to 934 CU. From this, we can deduce the following:
To further verify the CU cost of loops, we can use a more computationally expensive operation, such as printing account addresses:
// print account address, cost 11809 CU
msg!("A string {0}", ctx.accounts.address_info.to_account_info().key());
// print account address in for loop 1 time, cost 12108 CU
for i in 0..1 {
msg!("A string {0}", ctx.accounts.address_info.to_account_info().key());
}
// print account address in for loop 2 times, cost 24096 CU
for i in 0..2 {
msg!("A string {0}", ctx.accounts.address_info.to_account_info().key());
}
As shown, the CU consumption for loops depends on the logic executed within them. However, the overhead of the loop itself is relatively small, roughly 200–300 CU.
If Statements
If
statements are another common construct. Let’s analyze their CU consumption:
// a base function consumed 221 CU
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
// after add if statement, the CU consumed is 339 CU
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
if true {
Ok(())
} else {
Ok(())
}
}
Tests show that an empty function consumes 221 CU. Adding an if
statement increases the consumption to 339 CU. Thus, a basic if
statement consumes approximately 100 CU.
Different Data Structure Sizes
The size of a data structure also affects CU consumption. Here’s a comparison of different-sized data structures:
// use a default vector and push 10 items, it will consume 628 CU
let mut a Vec<u32> = Vec::new();
for _ in 0..10 {
a.push(1);
}
// use a 64 bit vector and do the same things, it will consume 682 CU
let mut a Vec<u64> = Vec::new();
for _ in 0..10 {
a.push(1);
}
// use a 8 bit vector and do the same things, it will consume 462 CU
let mut a: Vec<u8> = Vec::new();
for _ in 0..10 {
a.push(1);
}
Vec<u32>
storing 10 items consumes approximately 628 CU.Vec<u64>
storing 10 items consumes approximately 682 CU.Vec<u8>
storing 10 items consumes approximately 462 CU.This shows that the size of the data structure impacts CU usage, with larger types consuming more.
Hash Functions
Hash functions are a commonly used feature in Solana programs. Let’s analyze their CU consumption using a simple example:
use solana_program::hash::hash;
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
let data = b"some data";
let _hash = hash(data);
Ok(())
}
Comparing a program with and without a hash function, we find that using Solana’s hash function to compute a hash value consumes approximately 200 CU.
Function Calls
Function calls are essential in programming. Below is an example of analyzing the CU consumption of calling a function:
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
let result = other_func(_ctx)?;
Ok(())
}
pub fn other_func(_ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
Tests indicate that the CU consumption of calling a function depends on the logic within the called function. Calling an empty function consumes approximately 100 CU.
Summary
The CU consumption of common program constructs is summarized below:
Program Construct | CU Cost (approx.) |
---|---|
For Loop | 301 (Init) 181 (Per Iteration) |
If Statement | 100 |
Different Data Sizes | 462 (Vec u8) 628 (Vec u32) 682 (Vec u64) |
Hash Function | 200 |
Function Call | 100 |
As previously mentioned, there are two main ways to develop Solana programs: one is by using native programs, and the other is by using the Anchor framework. Anchor is the official framework recommended by Solana, providing a set of efficient and intuitive tools and libraries that significantly reduce the barrier to entry for Solana application development. You might be curious about the difference in CU consumption between native programs and Anchor programs. In this article, we’ll compare the CU consumption of both types of programs, using the example of Token Transfer operations.
Let’s first examine the CU consumption of a native program for transferring tokens. The source code for Solana programs is available in this repository, and the core method for processing token transfers, process_transfer
, can be found here. In this method, we break down the steps involved and tally up the CU consumption for each step. The results of our analysis are as follows:
The total CU consumption for the token transfer operation is about 4555 CU, which aligns closely with our previous test result (4500 CU). The highest cost is seen in the transfer initialization step, which consumes 2641 CU. We can further break down the initialization phase into more detailed steps, with the following CU consumption:
The unpacking operations for both accounts consume the most CU, with each unpacking operation costing around 1361 CU, which is significant. Developers should be aware of this during the development process.
Now that we have seen the CU consumption of native programs, let’s look at the CU consumption of Anchor programs. An example of an Anchor program can be found in the program-examples
repository, under the tokens/transfer-tokens
directory. The source code for the token transfer operation can be found here.
Upon running this instruction for the first time, we were surprised to find that the CU consumption for an Anchor program performing a token transfer is around 80,000 to 90,000 CU—nearly 20 times that of the native program!
Why is the CU consumption of an Anchor program so much higher? We began analyzing the source code of this program. An Anchor program generally consists of two parts: one for account initialization and the other for instruction execution. Both parts contribute to the CU consumption. Our detailed analysis shows the following results:
During account initialization, various accounts such as sender_token_account
, recipient_token_account
, and programs like token_program
and associated_token_program
need to be initialized, which costs a total of 20,544 CU.
For the execution of the token transfer instruction, the total cost is 50,387 CU. Further breakdown of this process reveals:
anchor_spl::token::transfer
method is called. This method wraps the native transfer
method, adding some extra functionality in addition to calling the native transfer method.From this analysis, we found that the actual CU consumption for the token transfer portion of the program is 7,216 CU. However, due to the initialization of the Anchor framework, account initialization, and print statements, the total CU consumption for the program reaches 81,457 CU.
Although Anchor programs consume more CU, the framework provides more functionality and convenience, making this consumption understandable. Developers can choose the appropriate development method based on their needs.
In this article, we’ve summarized various resource limits in Solana development, focusing on the CU limit. We discussed the CU consumption for common operations and programs, and compared the CU consumption of native programs and Anchor programs. Whether you are a beginner or an experienced developer on Solana, we hope this article helps you better understand CU consumption in Solana, enabling you to plan program resources more effectively and optimize performance during development.
We’ve also compiled some optimization tips based on Solana’s official documentation to help developers avoid pitfalls related to CU limits. The tips are as follows:
msg!
macro) significantly increase CU consumption, especially when dealing with Base58 encoding and string concatenation. It’s recommended to log only essential information and use more efficient methods, such as .key().log()
, for logging public keys and other data.u64
) consume more CU than smaller ones (like u8
). Use smaller data types whenever possible to reduce CU usage.find_program_address
function depends on how many attempts are needed to find a valid address. Storing the bump value during initialization and reusing it in subsequent operations can reduce CU consumption.We hope that in future articles, we can continue discussing other aspects of Solana’s resource limits, providing more insights. If you have any questions or comments, feel free to leave them in the comment section.