Module Structure
Move modules are the fundamental building blocks of smart contracts on Aptos. They encapsulate types, functions, and constants into reusable, composable units that can be published on-chain and invoked by transactions. Understanding proper module structure is essential for building maintainable, secure, and efficient smart contracts.
Overview#
Move modules define types (structs), functions (both public and private), and constants under a specific account address. On Aptos, modules are published to the blockchain under an account address and become immutable once deployed, though they can be upgraded following specific compatibility rules. Each module has a unique identifier consisting of the publishing account address and the module name (e.g., 0x1::coin).
Module Anatomy#
A well-structured Move module typically contains the following components organized in a logical order:
module 0x1::example_token {
// 1. Imports
use std::signer;
use std::string::String;
use aptos_framework::event;
use aptos_framework::timestamp;
// 2. Error codes
const ENOT_AUTHORIZED: u64 = 1;
const EINSUFFICIENT_BALANCE: u64 = 2;
const EALREADY_INITIALIZED: u64 = 3;
// 3. Constants
const MAX_SUPPLY: u64 = 1_000_000_000;
const DECIMALS: u8 = 8;
// 4. Structs (types)
struct TokenStore has key {
balance: u64,
frozen: bool
}
struct Capabilities has key {
mint_cap: MintCapability,
burn_cap: BurnCapability
}
struct MintCapability has store, drop {}
struct BurnCapability has store, drop {}
// 5. Events
struct TransferEvent has drop, store {
from: address,
to: address,
amount: u64,
timestamp: u64
}
// 6. Event handles
struct EventStore has key {
transfer_events: event::EventHandle<TransferEvent>
}
// 7. Public entry functions (transaction entry points)
public entry fun initialize(account: &signer) {
// Implementation
}
public entry fun transfer(from: &signer, to: address, amount: u64) acquires TokenStore, EventStore {
// Implementation
}
// 8. Public functions (callable by other modules)
public fun balance_of(addr: address): u64 acquires TokenStore {
// Implementation
0
}
// 9. Public(friend) functions
public(friend) fun mint(cap: &MintCapability, to: address, amount: u64) acquires TokenStore {
// Implementation
}
// 10. Internal functions
fun internal_transfer(from: address, to: address, amount: u64) acquires TokenStore {
// Implementation
}
// 11. Helper functions
fun validate_amount(amount: u64): bool {
amount > 0 && amount <= MAX_SUPPLY
}
}
Abilities and Type Safety#
Move's ability system provides fine-grained control over how types can be used. The four abilities are:
- key: Can be stored as a top-level resource under an account (required for global storage)
- store: Can be stored inside other structs with the
keyability - copy: Values can be copied (duplicated)
- drop: Values can be implicitly discarded
Use abilities judiciously to enforce proper resource semantics and prevent common vulnerabilities like resource duplication or accidental deletion.
Real-World Use Cases#
-
Token Contracts: Structure modules to manage fungible or non-fungible token supply, balances, and metadata with proper access controls and event emissions.
-
DeFi Protocols: Organize complex financial logic into separate modules for lending, borrowing, liquidity pools, and governance, each with clear interfaces.
-
NFT Marketplaces: Create modular systems with separate modules for listings, bids, royalties, and transfers to facilitate maintainability and upgrades.
-
Governance Systems: Structure voting, proposal, and execution logic into distinct modules with clear separation of concerns.
-
Access Control Systems: Build reusable authentication and authorization modules that other contracts can leverage through public interfaces.
-
Oracle Integration: Create structured modules that fetch and validate external data with proper error handling and event emission.
Best Practices#
Minimize Entry Function Logic: Keep entry functions thin by delegating complex logic to internal functions. This improves testability and code reuse.
Emit Comprehensive Events: Emit events for all state changes to enable off-chain indexing and user notifications. Include relevant context in event data.
Error Code Organization: Define all error codes as module constants at the top of the file with descriptive names prefixed with 'E'.
Function Ordering: Follow a consistent ordering pattern (entry functions first, then public, then internal) to improve code readability.
Use Friend Declarations: Leverage the friend visibility modifier to expose functions only to trusted modules while maintaining encapsulation.
Document Acquires: Always document which resources a function acquires to help developers understand global storage access patterns.
Separate Concerns: Split large modules into smaller, focused modules that each handle a specific concern or feature.
Module Dependencies#
// In Move.toml
[dependencies]
AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework", rev = "mainnet" }
[addresses]
my_project = "_"
Properly manage dependencies in your Move.toml file and use named addresses to make modules portable across different deployment environments.
Related Concepts#
- Upgradability - Learn how to safely upgrade modules
- Resource Management - Understand Move's resource model
- Testing - Best practices for testing module functionality
- View Functions - Implement gas-free read operations