RSS Amplifier

Stories by Aptos Labs on Medium · Jan 14, 2026

Lazy Loading: A New Era of Composable Move

0
Sign in to vote or save

This page did not load. You can still read it on the original site — the toolbar below keeps your place in the directory.

TL;DR: Eager Loading (previous) required gas metering for all transitive dependencies of a Move smart contract before deployment or execution. This is overly restrictive for the real-world DeFi protocols. Lazy Loading (new) meters and loads modules on first use during execution; it meters only the package and immediate package dependencies during deployment. Lazy Loading makes it unlikely to hit…

TL;DR:

  • Eager Loading (previous) required gas metering for all transitive dependencies of a Move smart contract before deployment or execution. This is overly restrictive for the real-world DeFi protocols.
  • Lazy Loading (new) meters and loads modules on first use during execution; it meters only the package and immediate package dependencies during deployment. Lazy Loading makes it unlikely to hit dependency-limit failures, reduces gas in the common case, and delivers on-par performance.

Module Loading in Move VM

The Move Virtual Machine (VM) uses a component called the loader to fetch modules that contain Move bytecode. Modules can have dependencies on other modules, enabling smart contract composability. Developers can upgrade modules by publishing new bytecode on-chain.

When a user executes a function in a smart contract, the VM loads the module that defines the function and then interprets the bytecode. Loading a module is more than just “reading bytes”: the loader fetches the module code from storage, deserializes, and verifies it.

Historically, the VM has used an Eager Loading approach, verifying that the module can link correctly to its declared dependencies by loading them and their dependencies, and so on. In the worst case, it was possible to load all transitive dependencies of a module.

To bound time spent on module loading and prevent denial-of-service attacks, VM charges gas for every module load and limits the number of transitive dependencies any module can have. With eager loading, the worst-case scenario was loading all transitive dependencies of a module, so the VM could not safely meter them “after the fact”. Instead, the VM had to pre-traverse all transitive dependencies to:

  • Charge gas proportional to the modules that may need to be loaded
  • Enforce a hard bound on the maximum number of transitive dependencies.

Unfortunately, this design hurts smart contract composability and restricts popular DeFi use cases.

The DEX Aggregator Example

On-chain DEX aggregators are common DeFi contracts that allow users to exchange assets within a single blockchain. Below is a minimal example of such an aggregator module written in Move: it allows the user to swap an asset of type X for an asset of type Y on a particular exchange.

module 0x99::dex_aggregator {
// All dependencies of this module.
use 0x1::fungible_asset;
use 0x123::dex_a;
use 0x456::dex_b;

// List of supported DEXes.
const DEX_A: u8 = 0;
const DEX_B: u8 = 1;

// Swaps some asset X into asset Y on selected DEX.
public entry fun swap<X, Y>(user: &signer, amount: u64, dex: u8) {
let asset_x = fungible_asset::withdraw<X>(user, amount);

let asset_y = if (dex == DEX_A) {
dex_a::swap<X, Y>(asset_x)
} else if (dex == DEX_B) {
dex_b::swap<X, Y>(asset_x)
} else {
// This DEX is not known - abort.
abort 1;
};

fungible_asset::deposit<Y>(user, asset_y);
}
}

Figure 1: An example DEX aggregator module written in Move. This aggregator module has 3 dependencies: 0x123::dex_a, 0x456::dex_b, 0x1::fungible_asset. All these modules have dependencies of their own (not shown here).

Once the code is complete, the developer publishes the dex_aggregator module. During publish, the VM:

  1. Verifies every module has well-formed bytecode.
  2. Traverses all transitive dependencies of the deployed contract: 0x123::dex_a, all transitive dependencies of 0x123::dex_a, 0x456::dex_b, all transitive dependencies of 0x456::dex_b, 0x1::fungible_asset and its transitive dependencies;
  3. Charges gas for each dependency*, also enforcing a hard limit on the number of traversed transitive dependencies;
  4. Verifies that the deployed module correctly links to its dependencies.

After the code is deployed, users interact with it, submitting transactions to execute swaps. For every transaction, before executing swap, the VM repeats steps (1) and (2) from the publishing flow: traversing transitive dependencies, charging gas, and enforcing a hard limit on the number of dependencies. Only after doing that does it execute the specified swap function.

The system seems to work, and users are happy, until….

The Problem

With eager loading, issues can arise surprisingly easily as the DEX aggregator module or its dependencies evolve.

Problem 1: The module cannot be re-published

A developer decides to integrate a new exchange, published as dex_c. They add dex_c dependency to dex_aggregator module and try to publish it. Publishing fails because the number of transitive dependencies exceeds the hard limit. This creates a composability ceiling: no new exchange can be added.

Problem 2: The module cannot be loaded

Developers of exchange A ship a new feature and upgrade their module dex_a. The upgrade succeeds; however, dex_aalso has more transitive dependencies and breaks dex_aggregator: its number of transitive dependencies exceeds the limit. As a result, any calls to the aggregator will fail, even if users want to swap via dex_b, which has not changed.

Problem 3: Excessive gas charging

Even if dex_aggregator remains callable, eager loading increases the execution cost. If a user selects to swap via exchange B, dex_b::swap will be called, never touching dex_a::swap. But eager loading still meters all transitive dependencies that include dex_a (and whatever new dependencies it gained). The user effectively pays a dependency tax for integrations they did not use.

Problem 4: Performance overhead

Loader V2 and module caching make the loading of frequently used modules very fast. But eager loading still requires traversing transitive dependencies to pay gas fees. Even if all modules are already loaded and in the cache, the VM still has to perform work proportional to the number of possible dependencies, not the number of executed dependencies, which becomes unnecessary overhead as the number of dependencies grows.

The Solution

The solution, enabled on the Aptos mainnet on Dec 16, 2025, as part of AIP-127, replaces eager loading with Lazy Loading. Lazy Loading changes the mental model to “load only modules that are used at execution”.

Concretely:

  • When an entry function or script is executed, the VM meters and loads modules as they are accessed. Using the example from Figure 1, a swap on DEX A will be charged only for 0x99::dex_aggregator, 0x123::dex_a, and any dependencies of 0x123::dex_a that will be used.
  • When a Move package is published, the VM meters all modules in the published package (including new and old versions being upgraded) and their immediate dependencies (not the whole transitive closure).

Using the example in Figure 1, the VM meters only0x99::dex_aggregator, 0x123::dex_a, and 0x456::dex_b when publishing the module with DEX aggregator code.

The lazy approach preserves all safety guarantees: at publish time, every module is verified and checked to ensure it links correctly to dependencies. At the same time, this approach removes the unnecessary requirement to load all transitive dependencies. For detailed specification of module metering, see AIP-127.

Impact on Gas Usage

With Lazy Loading, the gas usage per transaction decreases** because gas is charged only for modules that are actually used. It is also less likely to hit dependency limits. Figure 2 shows block gas usage when replaying mainnet blocks with Eager or Lazy Loading. This figure shows block gas usage for mainnet transactions for versions [2719042368, 2719042916), with 61 blocks in total.

Figure 2: Block gas usage when replaying 61 mainnet blocks [2719042368, 2719042916) with eager or lazy loading. The smaller the better.

Impact on Performance

The goal of lazy loading is not to improve performance. Still, experimental evaluation shows that lazy loading performs better or is on par with eager loading. Figure 3 shows the mean execution time of mainnet blocks of transactions for the same versions [2719042368, 2719042916) with and without lazy loading. Execution of the first blocks becomes faster because fewer module accesses (cache misses) occur. Once the cache contains most of the used contracts, lazy loading has on-par performance.

Figure 3: Mean block execution time in milliseconds when replaying 61 mainnet blocks [2719042368, 2719042916) with eager or lazy loading. The smaller the better.

Conclusion

Eager loading makes Move smart contract composability very fragile: contract upgrades can be blocked on dependency limits or cause transactions to fail after a downstream dependency change. Users are also charged gas for dependencies that never get touched when executing a transaction.

Lazy loading (AIP-127) changes that model: dependencies are metered and loaded on the first use. This is particularly important for DeFi, where smart contracts are inherently dependency-heavy: they compile against many other protocols, yet a typical transaction traverses only a few execution paths (e.g., a particular exchange). With Lazy Loading, adding a new dependency no longer forces every transaction to pay for all its transitive dependencies. Downstream dependency upgrades are unlikely to break unrelated executions that never touch the new code path.

*Dependencies at “special” addresses such as 0x1–0xf are not metered.

**There are cases where gas usage increases with lazy loading simply because it was never metered before. For example, view functions now have their loaded modules metered.


Lazy Loading: A New Era of Composable Move was originally published in Aptos Labs on Medium, where people are continuing the conversation by highlighting and responding to this story.

Read on medium.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.