See what Move’s ability rules can enforce—and what an application must still check. An optional Sui ticket example traces creation, transfer and destruction without requiring a wallet or a token purchase.
Move makes asset rules explicit
Move is a smart-contract language used in ecosystems including Aptos and Sui. Its type system helps developers control how values are copied, stored and consumed. Those controls are valuable, but language features alone do not guarantee that an application’s permissions, pricing or business logic are correct.
Abilities matter more than the label “resource”
The Move reference defines four abilities: copy, drop, store and key. A type with copy can be copied; one with drop may be discarded. Asset types commonly omit these abilities to restrict duplication and accidental disposal. It is therefore incorrect to say that every Move value is uncopyable or indestructible.
store and key govern storage-related capabilities, with details tied to the chain’s execution model. Even a non-droppable value can have an authorized destruction path defined by its module. The important question is which operations the type and module permit.
Ownership is chain-specific
Sui distinguishes address-owned, shared, immutable, wrapped and party object models. A shared object can be referenced by multiple users but still needs application authorization checks. An immutable object cannot be changed like an ordinary owned asset. Thus “every resource belongs to one user address” is too broad. See Sui’s ownership documentation.
Modules and transactions
Modules define types and functions. Visibility and authorization decide which calls are available; publishing a module does not make every function callable by everyone. Transaction formats also differ between Move-based chains. A generic description of “transaction scripts” should not be assumed to describe every Sui or Aptos workflow.
What formal verification can establish
The Move Prover checks specified properties using formal methods. Results depend on the specification, assumptions and supported model. A proof of an arithmetic invariant does not by itself prove an oracle honest, an administrator trustworthy or a financial design solvent. Testing, review and operational controls still matter.
A small design exercise
Imagine a ticket type that cannot be copied. That prevents duplicating a ticket value through an ordinary copy operation. It does not decide who may mint new tickets, whether a ticket can be redeemed twice through flawed application logic, or whether an administrator can change the rules. Review the mint authority, redemption state and upgrade mechanism separately.
This distinction helps a developer ask more precise questions: which invariant is enforced by the language, which by a contract, and which by an offchain operator? Comparing those answers is more useful than declaring one language universally safe.
Follow one ticket through its lifetime
The useful test is not whether a ticket is called a resource. It is whether you can explain who creates it, who can move it and what ends its life. The optional example uses Sui specifically; Aptos has a different storage model.
Open the Sui ticket reading lab — creation, transfer and destruction
Read without a wallet. These illustrative fragments are a code-reading exercise, not a complete package or a tested deployment tutorial. No compiler run is claimed. The deliberately missing mint restriction is part of the exercise.
// Sui Move fragments inside the same defining module.
// Imports: sui::object::{Self, UID}, sui::transfer,
// sui::tx_context::TxContext.
public struct Ticket has key, store {
id: UID,
event_id: u64,
}
// Deliberately unrestricted: who is allowed to mint?
public fun mint(event_id: u64, recipient: address,
ctx: &mut TxContext) {
let ticket = Ticket { id: object::new(ctx), event_id };
transfer::public_transfer(ticket, recipient);
}
public fun consume(ticket: Ticket) {
let Ticket { id, event_id: _ } = ticket;
object::delete(id);
}
| Operation | Expected outcome and reason |
|---|---|
| Create a ticket | The defining module constructs a value with a fresh UID. This example permits repeated minting: no copy ability does not impose a supply cap. |
| Copy an existing Ticket | Rejected by the type rules because Ticket lacks copy. Copying an object ID as ordinary data would not copy the object. |
| Transfer, then use the same local variable | The transfer consumes that value. Reusing the moved variable is rejected; a later transaction by the new owner is a different operation. |
| Leave a Ticket unused at the end of a function | It cannot simply be discarded because it lacks drop. The function needs a permitted way to return, store, transfer or consume it. |
| Call consume with an accessible owned ticket | The defining module unpacks it and deletes its UID. Explicit destruction is possible without granting drop to Ticket. |
Now find the missing application rule. Anyone able to call this mint function can request another ticket. The event_id field labels the ticket but is never checked. Consuming it proves neither admission at a venue nor payment. A real design needs mint authorization, event validation, an admission workflow and an upgrade policy. Adding store also permits public transfers; that choice matters if a project promises transfer restrictions.
Check your answer: removing copy protects the individual value from copying. It does not prevent the module from creating a second value with a new UID. That is why “uncopyable asset” and “limited supply” are different claims.
Compare the exercise with the ability rules, UID lifecycle and Sui transfer signatures. Sources checked 23 October 2026; the teaching example and questions are Blockchain808’s own.
Open the revision history
Revised 19 October 2026. Corrected universal ownership and uncopyability claims, qualified formal verification and removed unsupported Celo integration claims.
23 October 2026: Added a Sui-specific ticket reading exercise and separated language guarantees from application permissions. Moved the earlier correction note here.

