Post
Share your knowledge.
Can a smart contract own and mutate it's own object state in Sui?
I'm trying to understand this aspect of the Sui Network because I'm either building, debugging, or deploying something that touches this area. I want a detailed explanation of how this mechanism or feature works, along with relevant CLI usage, Move code structure, or architectural concepts. My goal is to gain enough clarity to apply this knowledge in a real project—whether that's a custom smart contract, an NFT system, a wallet integration, or a DeFi tool. The Sui Network has unique features compared to EVM chains, so I'm particularly interested in what sets it apart and how that affects development best practices. It would help to have sample code, command line examples, or typical errors to watch for, especially when using the Sui CLI, SDK, or deploying on localnet/testnet. Ultimately, I want to avoid common mistakes, follow the best security principles, and ensure that the functionality I’m working on behaves as expected under realistic conditions.
- Sui
- Architecture
- Move
Answers
15In Sui, a smart contract cannot directly own or mutate its own object state. Instead, the contract interacts with objects through the ownership model, where objects are owned by specific addresses (e.g., the user's wallet or an account). Smart contracts in Sui can define and modify the state of objects owned by other parties (e.g., users), but they cannot own objects themselves.
Key Concepts:
- Object Ownership: Objects are owned by addresses, not contracts.
- Interaction with Objects: A contract can manipulate the state of an object, but only if it has access to that object via ownership or mutable references.
Example Code:
module MyModule {
use sui::object::{Object, move_to};
public fun update_object_state(obj: &mut Object) {
// Modify the state of the object owned by someone else
obj.some_field = 42;
}
}
CLI Usage:
When deploying, the contract can only manipulate objects that are owned by an address, not itself.
Best Practices:
- Ensure contracts respect Sui's ownership model, passing objects between contracts and user accounts.
- Always manage permissions securely to avoid unauthorized access to object state.
Can a Smart Contract Own and Mutate Its Own Object State in Sui?
No, in Sui, smart contracts cannot directly own or mutate their own state. The ownership and mutation of resources (like coin objects or custom objects) are governed by user accounts or authorized addresses, not the contract itself.
Key Points:
- Ownership: Smart contracts in Sui are stateless; they define logic but don’t own or store state themselves.
- Resource Ownership: Resources (like coins or custom objects) must be owned by an address or an entity, not by the contract.
Typical Flow:
- Account Ownership: An address or user owns the object (e.g., a coin).
- Smart Contract Execution: The contract can modify the state of objects that are passed to it by the owner.
Example Flow:
- User creates an object (e.g., a coin).
- User passes the object to the contract for mutation.
- The contract modifies the object but doesn’t own it.
Sample Move Code:
public fun mutate_object(owner: &mut Address, coin: &mut Coin) {
Coin::transfer(coin, &mut owner);
}
CLI Example:
Deploy a contract and invoke a function to mutate an object’s state:
sui client publish --gas-budget 10000 --module <module-path>
Common Mistakes:
- Ownership Confusion: Trying to modify or access state that is not owned by the contract will result in errors.
- Incorrect Mutability: Ensure the object being passed to the contract is mutable (
&mut
).
Best Practices:
- Ensure Proper Ownership: Make sure the correct address owns the objects being passed to the contract.
- Test with Localnet/Testnet: Always verify contract logic and state changes before deploying to the mainnet.
In Sui, smart contracts (Move packages) cannot directly own or mutate objects themselves due to Sui's object-centric ownership model. Objects must be owned by an address, another object, or marked as shared/immutable – packages only contain code logic. For autonomous mutation, you typically use shared objects (with consensus) or dynamic fields where parent objects control child objects. This differs fundamentally from EVM's contract storage, requiring explicit ownership transfers via transactions rather than internal contract actions. Common patterns involve wrapping objects in administrator-controlled containers or using capability-based design to manage state mutations securely.
Yes, in Sui, a smart contract can mutate its own object state, but it cannot own objects independently. In Sui’s object-centric model, every object must be owned by either an address, another object, or marked as shared. Smart contracts don’t have an address like in EVM chains (e.g., Ethereum), so you must rely on object ownership patterns such as object-wrapped data structures or shared objects to persist and mutate state across transactions.
To update a contract's internal state, you usually design a struct
(with the key
ability) to hold state data, pass it to your entry
function, and perform updates through references. Ownership of this struct resides with a user or is marked as shared to allow broader access.
Example – Mutating Internal State via Owned Object
module example::counter {
use sui::object::{Self, UID};
use sui::tx_context::{Self, TxContext};
struct Counter has key {
id: UID,
value: u64,
}
public entry fun create(ctx: &mut TxContext): Counter {
Counter { id: object::new(ctx), value: 0 }
}
public entry fun increment(counter: &mut Counter) {
counter.value = counter.value + 1;
}
}
In this example, create
initializes a Counter
object. increment
can only be called by passing in a mutable reference to that object. Ownership lies with a user or another object, not the contract itself.
Shared Objects for Global Access
If your contract needs multiple users to interact with the same object, mark it as shared
. Example use cases: DAOs, global counters, on-chain games.
public entry fun increment_shared(counter: &mut Counter) {
counter.value = counter.value + 1;
}
On deployment or via an explicit function, you must share the object:
sui client call \
--function share_object \
--module counter \
--package <package_id> \
--args <counter_id>
CLI Tips:
- To view ownership:
sui client object <object_id>
- To mutate: use
--mut
flag or define&mut
in the entry function - For shared mutation: ensure the object is published as
shared
before mutation
SDK Integration (TS):
tx.moveCall({
target: `${packageId}::counter::increment`,
arguments: [tx.object(counterId)]
});
Best Practices:
- Avoid trying to store object references inside other objects unless needed.
- If you need global mutability, shared objects are the correct approach.
- Use
UID
andkey
ability to ensure objects can persist across transactions. - Always validate that the caller has the right to mutate, especially for shared objects.
Common Errors:
Cannot find mutable reference
– You're passing an immutable object where mutable is required.Object is not shared
– You're trying to mutate an object that hasn't been shared.Type mismatch
– Often caused by incorrect generics or misusing references.
You can read more about ownership and shared objects in Sui's developer docs.
In the Sui network, smart contracts cannot "own" objects in the traditional EVM sense because ownership is always tied to an address or another object. However, smart contracts written in Move can define and mutate their own custom state objects—these are typically shared objects or owned objects passed in by users.
Key Concepts
- Object Ownership in Sui: Every object is owned by:
A user address (address),
Another object (nested ownership),
Shared (accessible to all),
Immutable (cannot change after publishing).
-
Smart Contract State: To create contract state, developers define structs in Move modules and publish instances of these structs as objects. These objects are then passed to entry functions where their state can be read or modified.
-
Mutating State: To mutate a smart contract's object state, you must:
Pass the object into the function as &mut.
Ensure it's mutable and owned/shared as required.
Have the correct access rights.
- Shared Objects for Global State: If you want global, contract-wide state, you use shared objects:
struct Counter has key, store, copy { value: u64, }
public entry fun increment(counter: &mut Counter) { counter.value = counter.value + 1; }
- Publishing Shared Objects:
Shared objects must be explicitly created and shared at initialization.
Use move call or Sui CLI --gas-budget and --shared-object-inputs to interact with them.
- Example Deployment Flow:
Deploy module with sui move publish.
Call an entry function to create a shared object:
sui client call
--package <PACKAGE_ID>
--module counter
--function create_counter
--args 0
--gas-budget 10000
Then call increment with object ID of the shared object.
- Limitations:
Smart contracts don’t store internal state like in Solidity.
They operate on objects explicitly passed to them.
No persistent “contract storage” exists inside the module.
- Security and Ownership:
You must validate ownership manually or through capabilities.
It’s common to use access control patterns like AdminCap or Whitelist to gate mutations.
- Best Practices:
Avoid unnecessary sharing of objects to limit gas usage and contention.
Leverage capabilities or address checks to manage access securely.
Use has key and store properly to enable object persistence and mutability.
- CLI Commands to Watch:
sui client call for function calls.
sui client object
sui move test for unit testing state logic.
In Sui, a smart contract written in Move cannot truly own its own object in the same sense that a self-referencing EVM contract might hold and mutate internal state autonomously. Sui's object-centric architecture means that all objects must be explicitly passed into the transaction context, and their ownership is either account-based or shared. Smart contracts themselves are stateless—they only define logic. The actual state exists in Move objects, which must be passed to functions for reading or mutation.
To mutate a contract’s state object, you need to structure your module so that its data (typically stored in a has key
struct) is either:
- Owned by a user address (useful for account-scoped logic),
- Shared via
share_object()
(to support multiple actors like in DeFi, DAOs, games, etc.).
When using shared objects, you must mark your functions with entry
and pass the shared object as a &mut
reference.
Here’s an example of a shared state that can be mutated:
module my_app::vault {
use sui::object::{UID, ID};
use sui::tx_context::TxContext;
struct Vault has key {
id: UID,
balance: u64,
}
public fun init(ctx: &mut TxContext): Vault {
Vault { id: object::new(ctx), balance: 0 }
}
public entry fun deposit(vault: &mut Vault, amount: u64) {
vault.balance = vault.balance + amount;
}
}
In this example, the Vault
must be a shared object if you want it accessible by multiple users via entry
functions. When deploying, you share the object like this:
sui client call --function share_object --args <vault_id>
Once shared, any entry
function can mutate the object—provided it receives it as input. Sui ensures safety via ownership rules and versioning to prevent race conditions.
To learn more about how smart contracts in Sui interact with object state, shared access, and ownership, visit: https://docs.sui.io/build/programming-model/object-basics
This approach is what sets Sui apart from EVM chains. You don’t mutate internal contract storage directly. Instead, you mutate Move objects explicitly passed into your logic functions, which enhances concurrency and verifiability.
No, a smart contract (Move module) cannot directly own or mutate its own object state in Sui.
Objects are first-class entities owned by addresses, other objects, or shared—not by modules. State mutation happens via functions that take objects as mutable parameters, with ownership rules enforced at the transaction level.
Example:
struct Counter has key { id: UID, value: u64 }
public entry fun increment(counter: &mut Counter) { counter.value = counter.value + 1 }
The increment
function can mutate Counter
only when the transaction passes a mutable reference to an object owned by the sender. The module itself holds no state.
Use shared
objects (transfer::share_object
) for persistent, globally accessible state. Always validate ownership with object::is_owner()
and follow linear type rules.
Yes, a Sui smart contract (Move package) can own and mutate its own state through shared objects or dynamic object fields, unlike EVM's stateless design.
Key Mechanisms
-
Shared Objects:
- Globally mutable by anyone (with rules).
- Defined with
key + store
and owned by0x0
(package address).
struct ContractState has key, store { id: UID, value: u64, }
-
Dynamic Object Fields:
- Package can "own" objects by storing their IDs.
fun add_state(ctx: &mut TxContext) { let state = ContractState { id: object::new(ctx), value: 42 }; transfer::share_object(state); // Make it globally mutable }
Why Sui Is Unique
- No "msg.sender": Access control is via Move's type system, not EOAs.
- Parallel Mutations: Shared objects enable concurrent writes (unlike EVM's serialized TXs).
CLI Example
# Call a function that mutates shared state
sui client call \
--function update_state \
--module your_module \
--package 0xYOUR_PACKAGE \
--args 0xSHARED_OBJECT_ID 100 \ # New value
--gas-budget 10000000
Common Pitfalls
- Over-Sharing: Avoid
share_object
unless truly needed (usetransfer::transfer
for private ownership). - Race Conditions: Design for parallel access (e.g., use
sui::lock
).
Yes, a smart contract in Sui can mutate its own object state, but it must do so through objects explicitly passed into its functions. In Sui's object model, there is no internal contract storage like in Ethereum; instead, state is maintained through mutable objects. These objects can be owned by users or shared across the network. To mutate state, you define a struct with the has key ability, and then write entry functions that accept a &mut reference to the object. Shared objects allow global access and are required when multiple users need to interact with the same state. Mutating shared objects requires marking the function as public entry and passing the correct object ID during calls. Contracts do not “own” state directly; rather, they operate on data passed to them via arguments. You can enforce access control using capabilities or by checking sender addresses. This design supports high parallelism, better scalability, and fine-grained permissioning. Overall, smart contracts in Sui mutate state by modifying Move-defined objects under strict ownership and access rules.
Yes, a Sui smart contract (Move module) can own and mutate its own objects via:
- Owned Objects – Created during module init (stored under package ID).
- Shared Objects – Marked
shared
for multi-writer access.
Key Differences vs EVM:
✔ No External Owner – Objects can belong to the package itself.
✔ Direct Mutation – No approval system needed (unlike ERC-20).
Example Pattern:
struct MyState has key { id: UID, value: u64 }
// Module can mutate its own object
public fun update(self: &mut MyState) { self.value += 1 }
Watch For:
- Storage costs for owned objects.
- Use
shared
for global state.
(Sui’s object model enables autonomous contracts—no proxy wallets required.)
Yes, in the Sui Network, a smart contract can own and mutate its own object state using object ownership and the self parameter model in Move. Sui's object-centric architecture is designed specifically to allow smart contracts to manage their own state securely and efficiently, unlike Ethereum’s account-based model.
Key Concepts:
-
Object Ownership Model: Every object in Sui has an Owner (either an address, another object, or shared ownership). This enables composability and fine-grained access control.
-
Storing State in Objects: You encapsulate state within structs and publish them as objects. These can then be mutated if the calling function has the right permissions (ownership or shared with capabilities).
-
Smart Contract Ownership: A Move module can create an object with itself as the owner by storing the object inside another object or resource it controls.
-
Mutating State: To mutate the state, the function must take &mut access to the object, which enforces borrowing rules at compile time and access rules at runtime.
Example:
module my_module::MyContract { use sui::object::{Self, UID}; use sui::tx_context::{TxContext};
struct MyState has key {
id: UID,
counter: u64,
}
public fun new(ctx: &mut TxContext): MyState {
MyState {
id: UID::new(ctx),
counter: 0,
}
}
public entry fun increment(state: &mut MyState) {
state.counter = state.counter + 1;
}
}
In this example:
MyState is a mutable object owned by the smart contract caller.
The contract can increment the state through a public entry function.
You can store MyState within another object that is owned by another module or a central control object (DAO-like design) to simulate "self-ownership."
Deployment & CLI Tips:
Deploy: sui client publish --gas-budget 100000000
Call Method: sui client call --function increment --args <object_id>
View State: sui client object <object_id>
Common Mistakes:
Failing to pass &mut access to objects needing mutation.
Violating ownership rules (e.g., calling a function without owning the object).
Misconfiguring shared vs. owned access; only shared objects can be called by anyone.
Best Practices:
Encapsulate critical logic within objects and restrict access using capability-based permissions.
Use the has key modifier only when the object should be stored in Sui’s object store.
Simulate transactions locally with the SDK or sui client dry-run to validate state changes.
This design allows secure, modular, and composable state management, which is a major architectural advantage over traditional EVM chains.
Yes, in Sui, a smart contract can mutate its own state by using owned objects defined within the Move module. Sui uses an object-centric data model where smart contracts interact with objects that have clearly defined ownership and capabilities. To mutate an object, it must be passed into a function with &mut so the function has mutable access.
Here's a basic example:
module example::StateModule { use sui::object::{UID, Self}; use sui::tx_context::TxContext;
struct State has key {
id: UID,
value: u64,
}
public fun create(ctx: &mut TxContext): State {
State { id: UID::new(ctx), value: 0 }
}
public entry fun update(s: &mut State) {
s.value = s.value + 1;
}
}
The object State is stored on-chain and can be mutated through the update function. Only a transaction sender who owns the State object can call update with &mut. Shared objects must use entry functions and follow stricter access rules. The Sui CLI allows you to simulate or call these functions with sui client call or sui client dry-run. Always structure your logic around who owns the object and whether you need mutable or immutable access.
1. Core Concept: Object-Centric Ownership
Unlike EVM’s storage variables, Sui smart contracts operate through owned objects that can be mutated based on strict ownership rules.
Key Features
Feature | Sui | EVM |
---|---|---|
State Representation | Objects with UID | Storage variables |
Ownership | Explicit key ability | Implicit to contract |
Mutability | &mut references | Direct modification |
2. Move Implementation Patterns
Self-Owning Contract
module my_contract::state {
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context;
// Contract's state object
struct ContractState has key, store {
id: UID,
counter: u64
}
// Initialize and own the state
public fun init(ctx: &mut tx_context::TxContext) {
let state = ContractState {
id: object::new(ctx),
counter: 0
};
transfer::share_object(state); // Make shared for mutability
}
// Mutate owned state
public entry fun increment(state: &mut ContractState) {
state.counter = state.counter + 1;
}
}
Ownership Models
Model | Code Pattern | Use Case |
---|---|---|
Shared | transfer::share_object | Global mutable state |
Immutable | transfer::freeze_object | Configuration |
Owned | transfer::transfer | User assets |
3. CLI Interactions
Deploying Stateful Contracts
sui client publish --gas-budget 1000000000
# Output:
# - Package ID: 0x123...
# - Shared Object ID: 0x456...
Mutating State
sui client call \
--package 0x123 \
--module state \
--function increment \
--args 0x456 \ # Shared object ID
--gas-budget 100000000
4. Architectural Considerations
Concurrency Control
// Use `version` field for optimistic concurrency
struct ConcurrentState has key {
id: UID,
value: u64,
version: u64
}
public fun update(
state: &mut ConcurrentState,
new_value: u64,
expected_version: u64
) {
assert!(state.version == expected_version, EVERSION);
state.value = new_value;
state.version = state.version + 1;
}
State Migration (Upgrades)
module my_contract::v2 {
use my_contract::state::ContractState;
// Migrate V1 state to V2
public entry fun upgrade_state(
old_state: ContractState,
ctx: &mut tx_context::TxContext
) {
let new_state = V2State {
id: object::new(ctx),
counter: old_state.counter,
new_field: 0
};
transfer::share_object(new_state);
}
}
5. Security Patterns
Capability-Based Access
struct AdminCap has key, store {
id: UID
}
public entry fun secure_update(
state: &mut ContractState,
_cap: &AdminCap
) {
// Only callable with capability
state.counter += 1;
}
Reentrancy Protection
struct Lock has key {
id: UID,
locked: bool
}
public entry fun guarded_update(
state: &mut ContractState,
lock: &mut Lock
) {
assert!(!lock.locked, ELOCKED);
lock.locked = true;
state.counter += 1;
lock.locked = false;
}
6. Common Pitfalls & Solutions
Error | Cause | Fix |
---|---|---|
EInvalidSharedObjectUse | Incorrect mutability | Use &mut reference |
EMissingOwner | Object not owned by package | transfer::transfer to package address |
EImmutable | Attempting to modify frozen object | Initialize as share_object |
7. Performance Optimization
Batched Mutations
public entry fun batch_update(
states: vector<&mut ContractState>,
delta: u64
) {
let i = 0;
while (i < vector::length(&states)) {
let state = vector::borrow_mut(states, i);
state.counter = state.counter + delta;
i = i + 1;
}
}
Gas Cost Comparison
Operation | Gas (SUI) |
---|---|
Single update | 2,500 |
Batch update (10 items) | 3,800 |
Key Differentiators from EVM
- Explicit Ownership: Objects must be deliberately transferred
- Fine-Grained Mutability:
key + store
vscopy + drop
abilities - Parallel Processing: Independent objects mutate concurrently
For production use:
- Store critical state as shared objects
- Use capabilities for privileged operations
- Implement state migration paths upfront
Do you know the answer?
Please log in and share it.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.

- ... SUIMatthardy+2095
- ... SUIacher+1666
- ... SUIjakodelarin+1092
- ... SUIChubbycheeks +1081
- ... SUITucker+1047
- ... SUIKurosakisui+1034
- ... SUIzerus+890
- Why does BCS require exact field order for deserialization when Move structs have named fields?65
- Multiple Source Verification Errors" in Sui Move Module Publications - Automated Error Resolution55
- How to Maximize Profit Holding SUI: Sui Staking vs Liquid Staking414
- Sui Transaction Failing: Objects Reserved for Another Transaction49
- Sui Move Error - Unable to process transaction No valid gas coins found for the transaction316