The Making of the Aptos Gas Schedule

Aptos Labs
Aptos
Published in
8 min readOct 13, 2022

--

You can’t drive a car without gas, and you won’t go far unless you know how much gas you need. Gas metering is a concept fundamental to the Aptos blockchain — it defines an abstract measurement of the amount of computational and storage resources required to execute and store a transaction. The gas schedule codifies costs across all operations on the network and calculates the amount of gas used to execute a transaction.

To produce the first production-ready gas schedule in Move in Aptos, we

  • Defined our principles,
  • Prepared an evaluation framework to determine prices per operation,
  • Built a gas metering system and a safe gas algebra for Move,
  • Imported the upstream gas framework into Aptos, and
  • Made the gas framework storage-aware

Principles

There are five principles that drove our decision-making:

  1. Charge based upon actual cost: per operation cost directly relates to the resources on the network (e.g. CPU, memory, network, storage I/Os and space usage, etc.). This cost should reflect the evolution of resource cost changes over time due to new technology and process improvements.
  2. Seamlessly configurable: leverage on-chain governance for gas schedule updates.
  3. Enforce fair access: the network is a shared good, the policy should prevent denial of service attacks.
  4. Support growth and accessibility: at a healthy steady-state, the Aptos Foundation prefers lower gas fees to ensure better accessibility and to foster growth of the ecosystem.
  5. Prioritize good hygiene: charge a lower rate for operations that motivate safety, modularity, assertions, and event notifications.

Measuring gas

When a user submits a transaction, they must also specify two quantities in the transaction:

  • Max gas amount: Measured in gas units. This is the maximum number of gas units that the user (i.e., transaction sender) is willing to spend to execute the transaction.
  • Gas unit price: Measured in octa per gas unit, where 1 octa = 0.00000001 APT (=$10^{-8}$). This is the gas price the user is willing to pay.

The final transaction fee can be calculated by multiplying the total amount of gas consumed (measured in gas units) and the gas unit price.

For example, if a transaction consumes 670 gas units and the gas unit price specified by the user in the transaction is 100 Octa per unit, then the final transaction fee would be 670 * 100 = 67000 Octa = 0.00067 APT.

If a transaction runs out of gas during execution, the sender will be charged based on the max gas amount and all changes made by this transaction will be reverted.

Building a gas schedule

Basic configuration

There are several components of the gas schedule that do not relate to the specifics of an individual operation. These include transaction size and maximum gas units (different from the maximum gas amount that the user specifies in the transaction).

Transaction size

For most transactions, the transaction size will likely be on the order of a kilobyte. However, Move module publishing can easily be several kilobytes and the Aptos Framework is on the order of 100KB. Also, most user modules tend to be between 4KB and 40KB. Initially, we set the value of the transaction size to 32KB but the community responded quickly and asked for more space to make application development easy, so it was adjusted to 64KB.

Very large transactions induce bandwidth costs on the entire network and can have a negative impact on the performance. If abused, mempools are incentivized to ignore larger transactions, so our approach is to strike a balance between maximum transaction size and accessibility.

Maximum gas units

The maximum gas units in the gas schedule defines how many operations a transaction can perform. Note that this is different from the maximum gas amount that the user specifies in the transaction.

The gas schedule’s maximum gas units has direct implications on how long a transaction can execute. Setting it too high can result in transactions that can have negative performance implications on the blockchain. For example, a user may forget to increment the variable evaluated within a while loop resulting in an infinite loop, an unfortunately common bug. We found that even with our largest framework upgrade, we were still at less than 90% of gas schedule’s maximum gas units, which is set at 1,000,000.

Execution

In order to evaluate execution costs, we built a benchmark framework and used Valgrind to profile the Move VM while executing this framework. The output of this is a collection of annotated source code that tells us how many machine instructions each line of code has incurred.

With the help of profiling like the above, we came up with a rough estimation of the relative costs for all the Move instructions and native functions.

However, we noticed that this method had some problems with inline functions: They are not automatically included in the caller’s counts. We also saw that this only happened when we were profiling certain Move instructions, and we were able to work-around this by adding the numbers up.

Subsequently, by considering coding paradigms that enhance the robustness and the security of the system, the team arrived at a final number of the machine instructions executed. This number in turn was weighed against storage and maximum gas units to determine their current values in the gas schedule.

Storage

Whenever a ledger state item or data stored in persistent storage is accessed, the Aptos node issues a read from, or write to, the storage device. The total number of data accesses per second depends on the storage device’s bandwidth and IOPS capacities. Similar to CPU cycles for the computation part of the gas schedule, the data accesses are transient scarcities that the blockchain users compete for via a fee market while the system is under load. Furthermore, the occupied disk cost of data written is permanent on the chain. The Aptos team designed the storage gas schedule by factoring these costs.

Accessing and storing any state item imposes a cost associated with the data structure (the Jellyfish Merkle Tree) authenticating the entire blockchain state. This cost is related to the cardinality of distinct state items (out of $2^{256}$). There is also a cost proportional to the size of each item. To operate on one state item, the charge is (with exceptions described in the next section):

storage gas fee = item_fee + (byte_fee * bytes)

Read, create, and write

Any access to a state item falls into one of these three categories: read, create or write. Access is charged by the item fee and the byte fee, as shown in the above equation.

  • A read is the most common operation and is limited only by the transient resource scarcity. Hence the read fees are calibrated against the disk IOPS (item fee) and bandwidth capacities of the reference hardware specification.
  • A create adds a new item in the state storage. As a result, create grows the authentication data structure and makes everything more expensive, and costs the most as a result. The create fees are calibrated against the reference disk space the network has. As a consequence, it would take a non-trivial amount of gas to fill the disk with items (item_fee) and bytes (byte_fee).
  • A write updates an existing item in the state storage. Hence a write does not create additional overhead in the authentication data structure. However, by modifying the existing items to larger byte sizes one can still blow the disk. Hence, we charge the bytes in the updated item the same as the bytes when created.

It should be noted that the storage related costs are assessed on a per-transaction basis: you only get charged once even if you read/write the same resource multiple times.

With the above considerations, we defined six storage gas parameters that form the building blocks of the total storage gas fee. See the below:

  • per_item_read: Calibrated by IOPs
  • per_byte_read: Calibrated by real bandwidth
  • per_item_create: Calibrated by target total items
  • per_byte_create: Calibrated by target total size — first 1KB included in per item
  • per_item_write: Same with per_item_read
  • per_byte_write: Same with per_byte_create

For more details, refer to https://aptos.dev/concepts/base-gas/#storage-gas.

Stable gas unit cost

Irrespective of the cost of executing an operation in market-value terms of APT or fiat currency, each operation, and the transaction itself, needs a fixed unit cost relative to the storage and execution costs. A fixed gas unit cost helps keep the gas schedule static and be decoupled from the free-market value of APT. Moreover, a proper choice of the number of precision digits for the gas unit can help keep the gas schedule static. With this in mind, the team has landed on roughly 3-digit precision to express the gas units. Hence, the cost of a transfer transaction is roughly 700 gas units.

How to adjust the gas costs

The gas schedule is stored as an on-chain configuration. Through Aptos governance proposals, the gas fees can be changed and new instructions or native functions can be seamlessly added.

The on-chain gas schedule is designed to be extensible, allowing it to be upgraded via governance proposals. As Aptos and the Aptos community continue to improve the Move VM and incorporate user feedback, the gas parameters can be adjusted over time.

Sometimes the gas formulas may require complex changes that go beyond the on-chain configuration. These gas formulas are typically coded in Rust and are distinguished by the on-chain gas feature flags. To upgrade these formulas the node software must be updated with the new formula, distinguished by a distinct gas feature flag. The node software must then be published and be substantially adopted for the node operators. Finally, a governance proposal must be published and approved to use the new gas version.

Future work

This is the first viable gas framework for Move. It required substantial modifications to both the Move VM and the Aptos-Core. We are hopeful that this work paves the way for the following future work:

  • Reduce execution costs. A realistic gas model indicates where the compiler and VM have efficiencies. The team can improve much of this to drive down the execution costs. For example, function calls show an opportunity for improvement.
  • Multi-dimensional gas metering. This will allow users to specify separate budgets for execution and storage. Users would not have to pay exorbitant gas prices for poorly programmed applications that execute for too long. It would also allow for a finer grained definition of maximum gas price for a transaction on the blockchain side.
  • Bloated state mitigation. There is currently no easy way to shrink the state set except for the contracts (or users) to explicitly delete things. Paying the users to delete the data can result in arbitrage opportunities where users create storage when it is cheap and delete it when it is expensive. Aptos has delayed resolving this challenge, which can demotivate developers to delete on-chain data. The team is exploring the concept of a per item TTL that would remove unaccessed state items upon TTL expiry.

Acknowledgements

Special thanks to Alnoki from Econia Labs and Robert Chen from OtterSec for their reviews and feedback.

--

--

Aptos Labs
Aptos

Aptos Labs is a premier Web3 studio of engineers, researchers, strategists, designers, and dreamers building on Aptos, the Layer 1 blockchain.