Aptos Move Gas Optimization: Proven Strategies for Peak Performance and Efficiency

Unlocking the Full Potential: The First Comprehensive Guide to Aptos Gas Optimization for Enhanced Gas Cost Efficiency in Every Operation based on latest Aptos Research

Moncayo Labs
Cryptocurrency Scripts
9 min readFeb 20, 2024

--

Photo by Carl Nenzen Loven on Unsplash

New to developing on Aptos using the Move language? Remember those Solidity days, where gas-efficient code meant everything to you?

Now, as you venture into Rust on Aptos, you might be scratching your head over gas costs for memory operations. — But worry not!

This article is your go-to guide for gas optimization on Aptos, backed by the latest research.

We’ve condensed everything into 11 straightforward coding patterns to boost efficiency. Ready to supercharge your code on Aptos? Let’s dive in! Happy reading!

Summary

This article summarises the latest peer-reviewed academic research on gas optimization for the Move language (a.k.a. Rust on Aptos/SUI 🦀), offering heightened security and efficiency across Aptos smart contracts.

The study performs gas optimization for programming in Move on the Aptos blockchain, the study proposes 11 gas optimization patterns. We have condensed everything for you here.

The mentioned patterns cause a notable 7% to 56% gas consumption reduction in typical smart contracts. A game-changer for efficient and secure blockchain development (💰 💰 💰).

The Aptos Gas Meter

What is Aptos gas?

The gas of a smart contract is the cost of storing its items and executing its logic on the blockchain. The native token on Aptos is APT (i.e. the currency that gas is paid in) and the unit of APT is Octa (the smallest possible unit is 1 Octa).

However, the Aptos gas meter operates at an even smaller unit, by using internal gas units where:

What do I need to do?

Users must include two key fields when submitting a transaction:

  1. such as max_gas_amount, representing the maximum gas units the sender is willing to spend, and
  2. gas_price, indicating the price per gas unit determined by the market.

The MoveVM tracks gas usage through the gas meter during execution, and the total gas charged for the transaction is the sum of the gas consumed:

If the gas used surpasses max_gas_amount, then the trans- action is aborted. Thus, the maximum amount a user can be charged is

The gas used by a transaction consists of summing the gas associated with the size of its payload (payload gas), the virtual machine instructions it executes (instruction gas), and the global storage it accesses (storage gas):

Payload gas = transaction size gas, the cost of publishing bytecode to the Aptos blockchain.

Thus, increases with the size of the actual bytecode and the size of the input parameters. This type of gas cost is usually negligibly small compared to instruction and storage gas. However, there’s a penalty if your bytecode exceeds 600 bytes. Note that the blockchain stores the bytecode, not the actual source code. Hence, the number of comments does not affect the overall byte size of your module.

Instruction gas = the cost of performing a transaction on the Aptos blockchain.

The instruction gas arises from the cost for the Move VM to execute a transaction. The Move VM is a 64-bit machine, and operates on exactly 64 bits. Thus, executing a transaction with u8 will cost as much as executing the same calculation with u64 (one register each). However, doing the same calculation with a u128 will cost you more transaction gas.

Storage gas = the cost for writing and accessing Aptos global storage resources.

These operations should be familiar to you as using borrow_global, borrow_global_mut, move_to, etc., continue reading about structs here to learn more about Aptos unique global storage system. Please note that deleting data from global storage does not cost you any gas.

Gas costs incrementally grow with the number of global storage access requests. Thus, keep them to an absolute minimum. Storage gas tends to dominate as the largest contributor to the overall gas cost, exceeding the contributions arising from payload or instruction gas.

The Top Aptos Gas Optimisation Patterns 🕵️‍♀️

What you need to remember in order to be a successful and highly efficient Aptos Mover.

The top 10 gas optimization hacks

Payload Gas Optimization

1. Minimize Lines of Code

Keep the module as small as possible. Do not import unnecessary libraries or write duplicated code. Do not worry about comments. They do not affect the size of the bytecode.

2. Minimize the number and size of the input parameters

Minimize the number and size of the input parameters to reduce the total gas cost. This means you should combine many small functions with many parameters into one larger function. Rust’s ownership rules are sometimes restrictive, but avoid passing resources as parameters into functions.

Instruction Gas Optimization

3. Limit Function Calls

This might seem counter-intuitive to what you learned in your Computer Science degree. However, the instruction gas arising from function calls to abstracted helper functions tends to always outweigh the increased module size of not using a helper function (potential code duplication). Thus, function calls should be avoided as much as possible.

For example, small under the hood getter helper function to retrieve certain values should be avoided. Removing these small getter functions is. a small change that saves you a large percentage of gas.

This pattern comes with the downside of favouring large and complicated functions that in turn render testing difficult. It is with everything in life, and a matter for the MOVEr to find the perfect balance.

    public fun helper_function() {

}

public entry fun function_call() {
let k:u64 = 0;
while (k < 1000) {
helper_function();
k = k + 1;
};
}
// 47 Octa


public entry fun no_function_call() {
let k:u64 = 0;
while (k < 1000) {
// no function call
k = k + 1;
};
}
// 21 Octa

4. Minimize Vector Element Operations

Vector operations are more expensive than local variable operations, as they are charged on a per-element basis.Accessing vectors is thus equally expensive as accessing global storage (see Storage Gas Optimization). Hence, the Storage Gas Optimization Patterns 8–10 presented in the following, equally apply to vectors.

5. Cheapest Checks Go First

Similar as to what you might know from Solidity, when using the logical connective AND (&&), if the first expression evaluates to false, then the second expression will not be evaluated. Equally, when using the logical connective OR (||), if the first expression evaluates to true, then the second expression will not be evaluated.

   // Cheapest Check Goes First
public entry fun short_circuit() {
if (cheap_function() && expensive_function()) {

};
}

Hence those continued conditional expressions when used in if-statements and while-loops should always be ordered in order of ascending cost. The cheapest condition check, that has the potential to “short circuit” the condition and render further checks redundant, is always the check that should be performed first.

6. Write Values Explicitly

Any constants should be written explicitly, hardcoded in the contract. They shall not be used as in an expression derived from other constants to avoid for the Move VM having to compute it within the smart contract.

7. Avoid Redundant Sanity Checks:

Familiarise yourself with the Aptos Move language superior under-the-hood security mechanisms to avoid performing unnecessary checks in your code. One example is checking integers for overflow/underflow, as this is automatically done by the bytecode verifier.

Storage Gas Optimization

As this tends to be the largest contributor to the overall gas cost, this section is most important for minimizing gas costs.

8. Operate on Local Variables

Global storage access consumes the most gas of any operation. Operating directly on resource variables and their fields consumes significantly more gas, than copying your variables to a local variable. Then, perform your operations on the local variable and transfer back to update the global storage at the end.

    public entry fun bad_resource_write(account: &signer) 
acquires MyResource{
let resource = borrow_global_mut<MyResource>(signer::address_of(account));
resource.value = 0;
while (resource.value < 100000) {
//
// operate on resource.field
//
resource.value = resource.value + 1;
};
}
// 62 Octa


public entry fun good_resource_write(account: &signer)
acquires MyResource{
let resource = borrow_global_mut<MyResource>(signer::address_of(account));
resource.value = 0;
let intermediate = resource.value;

while (intermediate < 100000) {
//
// operate on intermediate
//
intermediate = intermediate + 1;
};
resource.value = intermediate;
}
// 27 Octa

9. Variable Packing

Probably one of the most important takeaway points in this article is the following: If you access a field in a resource, a so-called global storage struct, the per-byte charge you will pay consists of all fields in the resource, not just the ones that were accessed!

This might seem a bit more advanced, butto save gas if you had three different variables to store in a struct, e.g. a u8, u32 and u24, the naive way would be to store them in separate fields. When it would be much more gas-efficient to actually pack those variables together in a single u64 integer field by using bitwise masking to pack and unpack the variables.

struct MyCostlyResource has key, store {
x8: u8,
x32: u64,
x24: u64
}

struct MyPackedResource has key, store {
x: u64
}

10. Overwrite, Don’t Delete

There is no gas incentive to deallocate global storage, thus to save gas, one should overwrite unused global storage, rather than deallocating it and creating new global storage resources.

11. Reading Is Better Than Writing.

Avoid write a.k.a. mutable accesses of global storage resources, if you can. Reading them, i.e. read-only access, is a lot cheaper and is always preferred, if possible.

// Cheaper Access
let resource = borrow_global<MyResource>(signer::address_of(account));
let x = resource.x;

// Expensive Access
let resource = borrow_global_mut<MyResource>(signer::address_of(account));
resource.x = 1;

Conclusion

To conclude, whilst starting out as an Aptos developer might seem daunting at first sight, many concepts that you will recall from EVM blockchains still hold true.

While Aptos is still in its infancy, it’s a ridiculously cheap blockchain to develop on. Despite the native token being worth more than 9 USD as of today (thank you for the bull run). Which is also much more than SUI’s current token value of 1.7 USD (last updated February 2024).

Further Reading on Aptos Gas

If you want to see the actual code that was written to compare gas costs depending on coding style, please check out the code written as part of the Aptos gas cost research or download the full research publication.

For further reading on the intrinsics of calculating gas costs, check out this Aptos medium article on the Aptos Gas Schedule.

For a generic introduction on gas by the Aptos Docs on base gas and transaction fees.

Don’t forget to clap 👏 👏 👏, if this article helped you in any way.

Follow for more of the lastest Aptos information. 🙌

--

--

Moncayo Labs
Cryptocurrency Scripts

Active Supporter of the Aptos Move Movement | Web3 Move Development Tutorials