Skip to main content

Computing Transaction Gas

Aptos transactions by default charge a base gas fee, regardless of market conditions. For each transaction, this "base gas" amount is based on three conditions:

  1. Instructions.
  2. Storage.
  3. Payload.

The more function calls, branching conditional statements, etc. that a transaction requires, the more instruction gas it will cost. Likewise, the more reads from and writes into global storage that a transaction requires, the more storage gas it will cost. Finally, the more bytes in a transaction payload, the more it will cost.

As explained in the optimization principles section, storage gas has by far the largest effect on base gas. For background on the Aptos gas model, see The Making of the Aptos Gas Schedule.

Instruction gas

Basic instruction gas parameters are defined at instr.rs and include the following instruction types:

No-operation

ParameterMeaning
nopA no-operation

Control flow

ParameterMeaning
retReturn
abortAbort
br_trueExecute conditional true branch
br_falseExecute conditional false branch
branchBranch

Stack

ParameterMeaning
popPop from stack
ld_u8Load a u8
ld_u16Load a u16
ld_u32Load a u32
ld_u64Load a u64
ld_u128Load a u128
ld_256Load a u256
ld_trueLoad a true
ld_falseLoad a false
ld_const_baseBase cost to load a constant
ld_const_per_bytePer-byte cost to load a constant

Local scope

ParameterMeaning
imm_borrow_locImmutably borrow
mut_borrow_locMutably borrow
imm_borrow_fieldImmutably borrow a field
mut_borrow_fieldMutably borrow a field
imm_borrow_field_generic
mut_borrow_field_generic
copy_loc_baseBase cost to copy
copy_loc_per_abs_val_unit
move_loc_baseMove
st_loc_base

Calling

ParameterMeaning
call_baseBase cost for a function call
call_per_argCost per function argument
call_per_localCost per local argument
call_generic_base
call_generic_per_ty_argCost per type argument
call_generic_per_arg
call_generic_per_localCost generic per local argument

Structs

ParameterMeaning
pack_baseBase cost to pack a struct
pack_per_fieldCost to pack a struct, per field
pack_generic_base
pack_generic_per_field
unpack_baseBase cost to unpack a struct
unpack_per_fieldCost to unpack a struct, per field
unpack_generic_base
unpack_generic_per_field

References

ParameterMeaning
read_ref_baseBase cost to read from a reference
read_ref_per_abs_val_unit
write_ref_baseBase cost to write to a reference
freeze_refFreeze a reference

Casting

ParameterMeaning
cast_u8Cast to a u8
cast_u16Cast to a u16
cast_u32Cast to a u32
cast_u64Cast to a u64
cast_u128Cast to a u128
cast_u256Cast to a u256

Arithmetic

ParameterMeaning
addAdd
subSubtract
mulMultiply
mod_Modulo
divDivide

Bitwise

ParameterMeaning
bit_orOR: |
bit_andAND: &
xorXOR: ^
shlShift left: <<
shrShift right: >>

Boolean

ParameterMeaning
orOR: ||
andAND: &&
notNOT: !

Comparison

ParameterMeaning
ltLess than: <
gtGreater than: >
leLess than or equal to: <=
geGreater than or equal to: >=
eq_baseBase equality cost: ==
eq_per_abs_val_unit
neq_baseBase not equal cost: !=
neq_per_abs_val_unit

Global storage

ParameterMeaning
imm_borrow_global_baseBase cost to immutably borrow: borrow_global<T>()
imm_borrow_global_generic_base
mut_borrow_global_baseBase cost to mutably borrow: borrow_global_mut<T>()
mut_borrow_global_generic_base
exists_baseBase cost to check existence: exists<T>()
exists_generic_base
move_from_baseBase cost to move from: move_from<T>()
move_from_generic_base
move_to_baseBase cost to move to: move_to<T>()
move_to_generic_base

Vectors

ParameterMeaning
vec_len_baseLength of a vector
vec_imm_borrow_baseImmutably borrow an element
vec_mut_borrow_baseMutably borrow an element
vec_push_back_basePush back
vec_pop_back_basePop from the back
vec_swap_baseSwap elements
vec_pack_baseBase cost to pack a vector
vec_pack_per_elemCost to pack a vector per element
vec_unpack_baseBase cost to unpack a vector
vec_unpack_per_expected_elemBase cost to unpack a vector per element

Additional storage gas parameters are defined in table.rs, move_stdlib.rs, and other assorted source files in aptos-gas-schedule/src/.

IO and Storage charges

The following gas parameters are applied (i.e., charged) to represent the costs associated with transient storage device resources, including disk IOPS and bandwidth:

In Move, one can read from the global state:

ParameterMeaning
storage_io_per_state_slot_readcharged per item loaded from global state
storage_io_per_state_byte_readcharged per byte loaded from global state

For a committed transaction, various things will be written to the ledger history. Notice that the ledger hisotry is subject to pruning and doesn't occupy permanent space on the disk, hence no storage fee charged for it.

ParameterMeaning
storage_io_per_state_slot_writecharged per state write operation in the transaction output
storage_io_per_state_byte_writecharged per byte in all state write ops in the transaction output
storage_io_per_event_byte_writecharged per byte in all events in the transaction output
storage_io_per_transaction_byte_writecharged per byte in the transaction itself which is also part of the ledger history

The following storage fee parameters are applied (i.e., charged in absolute APT values) to represent the disk space and structural costs associated with using the Aptos authenticated data structure for storing items on the blockchain.

ParameterMeaning
storage_fee_per_state_slot_createallocating a state slot, by move_to(), table::add(), etc
storage_fee_per_state_byteNotice this is charged every time the slot grows in size, not only at allocation time. (However, for simplicity, refunding is only at deletion time. See AIP-65)

Vectors

Byte-wise fees are similarly assessed on vectors, which consume i=0n1ei+b(n)\sum_{i = 0}^{n - 1} e_i + b(n) bytes, where:

  • nn is the number of elements in the vector
  • eie_i is the size of element ii
  • b(n)b(n) is a "base size" which is a function of nn

See the BCS sequence specification for more information on vector base size (technically a ULEB128), which typically occupies just one byte in practice, such that a vector of 100 u8 elements accounts for 100+1=101100 + 1 = 101 bytes. Hence, per the item-wise read methodology described above, reading the last element of such a vector is treated as a 101-byte read.

Payload gas

Payload gas is defined in transaction.rs, which incorporates storage gas with several payload- and pricing-associated parameters:

ParameterMeaning
min_transaction_gas_unitsMinimum internal gas units for a transaction, charged at the start of execution
large_transaction_cutoffSize, in bytes, above which transactions will be charged an additional amount per byte
intrinsic_gas_per_byteInternal gas units charged per byte for payloads above large_transaction_cutoff
maximum_number_of_gas_unitsUpper limit on external gas units for a transaction
min_price_per_gas_unitMinimum gas price allowed for a transaction
max_price_per_gas_unitMaximum gas price allowed for a transaction
max_transaction_size_in_bytesMaximum transaction payload size in bytes
gas_unit_scaling_factorConversion factor between internal gas units and external gas units

Here, "internal gas units" are defined as constants in source files like instr.rs and storage_gas.move, which are more granular than "external gas units" by a factor of gas_unit_scaling_factor: to convert from internal gas units to external gas units, divide by gas_unit_scaling_factor. Then, to convert from external gas units to octas, multiply by the "gas price", which denotes the number of octas per unit of external gas.

Optimization principles

Unit and pricing constants

As of the time of this writing, min_price_per_gas_unit in transaction.rs is defined as aptos_global_constants::GAS_UNIT_PRICE (which is itself defined as 100), with other noteworthy transaction.rs constants as follows:

ConstantValue
min_price_per_gas_unit100
max_price_per_gas_unit10,000,000,000
gas_unit_scaling_factor1,000,000

See Payload gas for the meaning of these constants.

Storage Fee

When the network load is low, the gas unit price is expected to be low, making most aspects of the transaction cost more affordable. However, the storage fee is an exception, as it's priced in terms of absolute APT value. In most instances, the transaction fee is the predominant component of the overall transaction cost. This is especially true when a transaction allocates state slots, writes to sizable state items, emits numerous or large events, or when the transaction itself is a large one. All of these factors consume disk space on Aptos nodes and are charged accordingly.

On the other hand, the storage refund incentivizes releasing state slots by deleting state items. The state slot fee is fully refunded upon slot deallocation, while the excess state byte fee is non-refundable. This will soon change by differentiating between permanent bytes (those in the global state) and relative ephemeral bytes (those that traverse the ledger history).

Some cost optimization strategies concerning the storage fee:

  1. Minimize state item creation.
  2. Minimize event emissions.
  3. Avoid large state items, events, and transactions.
  4. Clean up state items that are no longer in use.
  5. If two fields are consistently updated together, group them into the same resource or resource group.
  6. If a struct is large and only a few fields are updated frequently, move those fields to a separate resource or resource group.

Instruction gas

As of the time of this writing, all instruction gas operations are multiplied by the EXECUTION_GAS_MULTIPLIER defined in meter.rs, which is set to 20. Hence, the following representative operations assume gas costs as follows (divide internal gas by scaling factor, then multiply by minimum gas price):

OperationMinimum Octas
Table add/borrow/remove box240
Function call200
Load constant130
Globally borrow100
Read/write reference40
Load u128 on stack16
Table box operation per byte2

(Note that per-byte table box operation instruction gas does not account for storage gas, which is assessed separately).

For comparison, reading a 100-byte item costs ri+100rb=3000+1003=3300r_i + 100 * r_b = 3000 + 100 * 3 = 3300 octas at minimum, some 16.5 times as much as a function call, and in general, instruction gas costs are largely dominated by storage gas costs.

Notably, however, there is still technically an incentive to reduce the number of function calls in a program, but engineering efforts are more effectively dedicated to writing modular, decomposed code that is geared toward reducing storage gas costs, rather than attempting to write repetitive code blocks with fewer nested functions (in nearly all cases).

In extreme cases it is possible for instruction gas to far outweigh storage gas, for example if a looping mathematical function takes 10,000 iterations to converge; but again this is an extreme case and for most applications storage gas has a larger impact on base gas than does instruction gas.

Payload gas

As of the time of this writing, transaction/mod.rs defines the minimum amount of internal gas per transaction as 1,500,000 internal units (15,000 octas at minimum), an amount that increases by 2,000 internal gas units (20 octas minimum) per byte for payloads larger than 600 bytes, with the maximum number of bytes permitted in a transaction set at 65536. Hence, in practice, payload gas is unlikely to be a concern.