Introduction
When building smart contracts on Aptos using Move, security and efficiency are paramount. One critical aspect is resource management and preventing unbounded execution.
Poorly designed contracts can allow attackers to clog the system, exhaust gas (the computational cost of transactions), and block functionality.
This tip explains how to manage resources securely and avoid loops that could spiral out of control, with practical examples to make it crystal clear.
Why It Matters
In Move, unbounded execution happens when a function loops over a data structure (like a list) that can grow infinitely, consuming excessive gas and potentially causing transactions to fail. Similarly, storing all user data in a single global structure can make your contract vulnerable to attacks and inefficient.
It’s like running a store where every customer’s order is piled into one massive, disorganized box—anyone could overwhelm it with junk orders, slowing everything down or crashing the system!
To manage resources effectively store user-specific data (like coins or NFTs) in individual user accounts, not a shared global space.Avoid iterating over unbounded structures that anyone can add to without limits.
Use efficient data structures (like SmartTable) to keep operations fast and secure.This ensures your dApp stays scalable, secure, and gas-efficient.
Real-World Example
Imagine running an online marketplace on Aptos, like eBay, where users place buy orders for items. All orders are stored in one global list accessible to everyone. An attacker could flood the list with thousands of fake orders, making it slow or impossible to search for a specific order because the system has to check every single one. This could crash the transaction or make the app unusable due to high gas costs.The solution? Store each user’s orders in their own account, like giving every customer their own order folder. This isolates data, prevents tampering, and keeps operations fast by avoiding huge loops.
Insecure Code Example
Here’s a problematic code snippet where orders are stored in a single global OrderStore. The get_order_by_id function loops through every order to find a match, which can become slow and costly if the list grows large due to unrestricted additions.
module 0x42::example {
struct Order has copy, drop, store { id: u64, /* other fields */ }
struct OrderStore has key { orders: vector<Order> }
public fun get_order_by_id(order_id: u64): Option<Order> acquires OrderStore {
let order_store = borrow_global_mut<OrderStore>(@admin);
let i = 0;
let len = vector::length(&order_store.orders);
while (i < len) {
let order = vector::borrow<Order>(&order_store.orders, i);
if (order.id == order_id) {
return option::some(*order)
};
i = i + 1;
};
return option::none<Order>()
}
public entry fun create_order(buyer: &signer) { /* ... adds to global order_store */ }
}
Why It’s Bad:
The orders vector is global and publicly accessible, so anyone can add unlimited orders via create_order.The while loop in get_order_by_id checks every order, making it an O(n) operation (slow for large lists).An attacker could spam the list with fake orders, causing high gas costs or transaction failures, blocking legitimate users.It’s like searching through a giant pile of orders at the post office—slow, costly, and easy to sabotage.
Secure Code Example
Instead, store orders in each user’s account using a SmartTable for efficient lookups. This isolates user data and eliminates unbounded loops, ensuring fast and secure operations.
module 0x42::example {
struct Order has copy, drop, store { id: u64, /* other fields */ }
struct OrderStore has key { orders: SmartTable<u64, Order> }
public fun get_order_by_id(user: &signer, order_id: u64): Option<Order> acquires OrderStore {
let order_store = borrow_global_mut<OrderStore>(signer::address_of(user));
if (smart_table::contains(&order_store.orders, order_id)) {
let order = smart_table::borrow(&order_store.orders, order_id);
option::some(*order)
} else {
option::none<Order>()
}
}
}
Why It’s Better:Orders are stored in a SmartTable under the user’s account (signer::address_of(user)), isolating data to prevent tampering.SmartTable enables O(1) lookups (constant time), avoiding costly loops.Only the user can access their own orders, enhancing security and scalability.It’s like giving each customer a personal, indexed filing cabinet—fast, secure, and tamper-proof.Key Takeaways
Avoid unbounded loops: Don’t iterate over structures (like vectors) that anyone can grow infinitely.Isolate user data: Store assets like orders, coins, or NFTs in individual user accounts, not a shared global space.
Pro Tips for Developers
Always check if a data structure can grow uncontrollably before looping over it.Use signer::address_of(user) to tie resources to specific accounts.Opt for SmartTable or similar structures for key-value lookups instead of vectors.Test your contract with large datasets to ensure gas efficiency and scalability.By designing with these principles, you’ll build secure, efficient, and scalable smart contracts on Aptos.
Top comments (0)