Smart Contract Deployment in Aptos
Aptos — an L1 blockchain based on Move VM, originally developed by the Diem team (Meta). The key difference from EVM: the Move language with its resource-oriented model and linear type system. A resource (resource type) cannot be copied or destroyed arbitrarily — only explicitly transferred. This eliminates an entire class of vulnerabilities that plague Solidity: double-spend via reentrancy, tokens created from thin air. If you're used to EVM, your first days with Move will be uncomfortable — then you'll understand why this is correct.
Move Module Structure
Aptos has no concept of "contract" — instead there are modules (code) and resources (data stored in accounts). A module is published under an account's address, resources live in user accounts, not in the module account:
module my_addr::token {
use std::signer;
struct CoinStore has key {
balance: u64,
}
public fun initialize(account: &signer) {
move_to(account, CoinStore { balance: 0 });
}
public fun deposit(account: &signer, amount: u64) acquires CoinStore {
let store = borrow_global_mut<CoinStore>(signer::address_of(account));
store.balance = store.balance + amount;
}
}
has key — ability allowing the resource to be stored in global storage. acquires — explicit declaration that a function accesses a global resource of a specific type. The compiler checks this statically — no implicit side effects.
Tooling: Aptos CLI
The main tool is the aptos CLI. Account management, compilation, deployment, function calls:
# Account initialization (generates keypair, funds devnet)
aptos init --network devnet
# Compilation
aptos move compile --package-dir . --named-addresses my_addr=0xCAFE
# Module publication
aptos move publish \
--package-dir . \
--named-addresses my_addr=<YOUR_ADDRESS> \
--profile mainnet
Named addresses — Aptos mechanism: in Move.toml you use symbolic names, at deployment you substitute concrete addresses. This allows deploying the same code from different accounts without source modifications.
Move.toml and Dependency Management
[package]
name = "MyProtocol"
version = "1.0.0"
[addresses]
my_addr = "_" # substituted at deployment
[dependencies.AptosFramework]
git = "https://github.com/aptos-labs/aptos-core.git"
rev = "mainnet"
subdir = "aptos-move/framework/aptos-framework"
Dependencies are pulled from Git — this means you must pin rev to a specific commit for reproducible builds. _ in the address — placeholder, must be replaced on publication.
Upgradeability: Policy at Deployment
Unlike Ethereum proxy patterns, upgradeability in Aptos is managed at the VM level through upgrade policy, set in Move.toml:
[package]
upgrade_policy = "compatible" # or "immutable"
-
compatible— compatible updates allowed: adding functions and resources, but cannot change existing signatures or delete fields -
immutable— module frozen forever, no updates
compatible — right choice for production with live logic. After publishing with immutable, the contract cannot change — this must be intentional. No "proxy + implementation" patterns like in EVM; upgrade works through re-publication at the same address with compatibility checking.
Testing
Move has a built-in test framework — tests are written directly in modules with the #[test] attribute:
#[test_only]
module my_addr::token_tests {
use my_addr::token;
use aptos_framework::account;
#[test(alice = @0x1)]
fun test_deposit(alice: signer) acquires token::CoinStore {
token::initialize(&alice);
token::deposit(&alice, 100);
// assert state
}
}
aptos move test --package-dir . --named-addresses my_addr=0x1
For more complex scenarios — Aptos Move Prover: formal verification of invariants via SMT-solver (Z3). This is a level above normal testing:
spec deposit {
ensures global<CoinStore>(signer::address_of(account)).balance ==
old(global<CoinStore>(signer::address_of(account))).balance + amount;
}
Checklist Before Mainnet Deployment
- Tests cover all public functions, including edge values (
u64::MAX) - Prover specifications for critical financial logic
-
upgrade_policyintentionally chosen — immutable only if certain - Named addresses fixed in deployment script, not hardcoded manually
- Deployer account — multisig via Aptos native multisig (on-chain, not EOA)
- Aptos Framework version in dependencies matches target network (
mainnet/testnet) - After deployment — verification via Aptos Explorer (source published on-chain)







