Module 0x1::init

Module initialization support. This is internally used by the compiler.

use 0x1::create_signer;
use 0x1::error;
use 0x1::features;
use 0x1::from_bcs;
use 0x1::hash;
use 0x1::object;
use 0x1::option;
use 0x1::ordered_map;
use 0x1::vector;

Struct ModuleId

Opaque representation of module names.

struct ModuleId has copy, drop, store
Fields
hash: u128

Struct ModuleState

Per-module initialization metadata.

only_once is the flag first passed at initialization: none before init, some(false) re-inits after each upgrade (cleared by reset_initialized), some(true) inits only once. deploy_owner is the object's transitive root owner recorded at this module's last publish and gates object self-init (see assert_may_self_initialize); none for account addresses.

struct ModuleState has copy, drop, store
Fields
only_once: option::Option<bool>
deploy_owner: option::Option<address>

Enum Resource InitializationState

Per-address initialization metadata, keyed by module so each module is gated on the owner at its own last publish -- republishing one module does not re-arm a sibling.

enum InitializationState has key
Variants
V1
Fields
modules: ordered_map::OrderedMap<init::ModuleId, init::ModuleState>

Constants

Module initialization can only be requested by module code, not by scripts.

const EINVALID_INITIALIZE_CALLER: u64 = 1;

Lazy module initialization is not enabled (see the LAZY_MODULE_INITIALIZATION feature).

const ELAZY_MODULE_INITIALIZATION_NOT_ENABLED: u64 = 3;

A module on an object cannot self-initialize once the object changed owner, or was deleted, since that module's code was last published.

const EOWNER_CHANGED_SINCE_DEPLOY: u64 = 2;

Function internal_maybe_initialize

If the calling module needs initialization -- never initialized, or (with only_once false) upgraded since -- marks it initialized and returns a signer for its address; else returns none. Marking happens before the initializer runs, so a cyclic call terminates.

The caller module is derived by the VM from the call stack, so a module can only initialize itself. Aborts if the feature is disabled (ELAZY_MODULE_INITIALIZATION_NOT_ENABLED), the caller is not module code (EINVALID_INITIALIZE_CALLER), or an object-hosted module changed owner since publish (EOWNER_CHANGED_SINCE_DEPLOY).

public fun internal_maybe_initialize(only_once: bool): option::Option<signer>
Implementation
public fun internal_maybe_initialize(only_once: bool): Option<signer> {
    assert!(
        features::is_lazy_module_initialization_enabled(),
        error::invalid_state(ELAZY_MODULE_INITIALIZATION_NOT_ENABLED),
    );
    let (addr, module_id) = get_caller_address_and_module_id();
    if (check_and_set_initialized(addr, module_id, only_once)) {
        option::none()
    } else {
        // Guard only when actually minting: a legitimate transfer after initialization must not
        // brick ordinary calls. An abort here rolls back the mark set above.
        assert_may_self_initialize(addr, module_id);
        option::some(create_signer::create_signer(addr))
    }
}

Function assert_may_self_initialize

Aborts unless the module at addr may self-initialize now. Only object-hosted modules are gated: an object must still have the transitive root owner recorded for this module at publish, so a transfer of the object or an ancestor, or its deletion, blocks self-init; an object with no record is fail-closed. Account addresses authorize their own code by publishing.

fun assert_may_self_initialize(addr: address, module_id: init::ModuleId)
Implementation
fun assert_may_self_initialize(addr: address, module_id: ModuleId) {
    let recorded = recorded_deploy_owner(addr, module_id);
    let ok = if (recorded.is_some()) {
        object::is_object(addr)
            && recorded.destroy_some() == object::address_to_object<ObjectCore>(addr).root_owner()
    } else {
        !object::is_object(addr)
    };
    assert!(ok, error::permission_denied(EOWNER_CHANGED_SINCE_DEPLOY));
}

Function recorded_deploy_owner

The object root owner recorded for module_id at its last publish, or none if the module has no such record (an account module, or an object module never recorded).

fun recorded_deploy_owner(addr: address, module_id: init::ModuleId): option::Option<address>
Implementation
fun recorded_deploy_owner(addr: address, module_id: ModuleId): Option<address> {
    if (!exists<InitializationState>(addr)) return option::none();
    let modules = &InitializationState[addr].modules;
    if (modules.contains(&module_id)) modules.borrow(&module_id).deploy_owner else option::none()
}

Function record_deploy_owner

Records owner as the object root owner of the module named module_name at (re)publish, to gate its later self-init (see assert_may_self_initialize). Called per module by code::publish_package for object addresses only.

public(friend) fun record_deploy_owner(addr: address, module_name: vector<u8>, owner: address)
Implementation
package fun record_deploy_owner(addr: address, module_name: vector<u8>, owner: address) {
    let module_id = module_id_from_name(module_name);
    ensure_module_state(addr, module_id);
    InitializationState[addr].modules.borrow_mut(&module_id).deploy_owner = option::some(owner);
}

Function reset_initialized

Called on code upgrade to request re-run of initialization for the named module. Skipped for modules that used only_once = true when first initialized. Keeps the recorded deploy owner.

public(friend) fun reset_initialized(addr: address, module_name: vector<u8>)
Implementation
package fun reset_initialized(addr: address, module_name: vector<u8>) {
    if (exists<InitializationState>(addr)) {
        let modules = &mut InitializationState[addr].modules;
        let module_id = module_id_from_name(module_name);
        if (modules.contains(&module_id)) {
            let state = modules.borrow_mut(&module_id);
            if (state.only_once == option::some(false)) {
                state.only_once = option::none();
            }
        }
    }
}

Function module_id_from_name

Creates the id for a module name (see get_caller_address_and_module_id for the format).

fun module_id_from_name(name: vector<u8>): init::ModuleId
Implementation
fun module_id_from_name(name: vector<u8>): ModuleId {
    let hash = hash::sha3_256(name);
    hash.trim(16);
    ModuleId { hash: from_bcs::to_u128(hash) }
}

Function check_and_set_initialized

Returns true if the module is already initialized. Otherwise marks it initialized, recording only_once: if true the entry survives upgrades (initializer never re-runs); if false an upgrade resets it (initializer re-runs).

fun check_and_set_initialized(addr: address, module_id: init::ModuleId, only_once: bool): bool
Implementation
fun check_and_set_initialized(addr: address, module_id: ModuleId, only_once: bool): bool {
    ensure_module_state(addr, module_id);
    let state = InitializationState[addr].modules.borrow_mut(&module_id);
    if (state.only_once.is_some()) {
        true
    } else {
        state.only_once = option::some(only_once);
        false
    }
}

Function ensure_module_state

Ensures an InitializationState exists at addr holding an entry for module_id.

fun ensure_module_state(addr: address, module_id: init::ModuleId)
Implementation
fun ensure_module_state(addr: address, module_id: ModuleId) {
    if (!exists<InitializationState>(addr)) {
        move_to(
            &create_signer::create_signer(addr),
            InitializationState::V1 { modules: ordered_map::new() },
        );
    };
    let modules = &mut InitializationState[addr].modules;
    if (!modules.contains(&module_id)) {
        modules.add(module_id, ModuleState { only_once: option::none(), deploy_owner: option::none() });
    };
}

Function get_caller_address_and_module_id

Returns the address and module id of the module that directly called the function invoking this native. Aborts with EINVALID_INITIALIZE_CALLER if there is no such module (e.g. a script). The module id is the sha3-256 of the module name, trimmed to 16 bytes and read as the bcs encoding of a u128.

fun get_caller_address_and_module_id(): (address, init::ModuleId)
Implementation
native fun get_caller_address_and_module_id(): (address, ModuleId);

Specification

Function internal_maybe_initialize

public fun internal_maybe_initialize(only_once: bool): option::Option<signer>
pragma aborts_if_is_partial;

Function get_caller_address_and_module_id

fun get_caller_address_and_module_id(): (address, init::ModuleId)
pragma opaque;