Skip to content

code - [mainnet]

This module supports functionality related to code management.

use 0x1::copyable_any;
use 0x1::error;
use 0x1::event;
use 0x1::features;
use 0x1::object;
use 0x1::option;
use 0x1::permissioned_signer;
use 0x1::signer;
use 0x1::string;
use 0x1::system_addresses;
use 0x1::util;
use 0x1::vector;

Constants

code_object does not exist.

const ECODE_OBJECT_DOES_NOT_EXIST: u64 = 10;

A dependency to an arbitrary package must be on the same address.

const EDEP_ARBITRARY_NOT_SAME_ADDRESS: u64 = 7;

A dependency cannot have a weaker upgrade policy.

const EDEP_WEAKER_POLICY: u64 = 6;

Creating a package with incompatible upgrade policy is disabled.

const EINCOMPATIBLE_POLICY_DISABLED: u64 = 8;

Cannot delete a module that was published in the same package

const EMODULE_MISSING: u64 = 4;

Package contains duplicate module names with existing modules publised in other packages on this address

const EMODULE_NAME_CLASH: u64 = 1;

Not the owner of the package registry.

const ENOT_PACKAGE_OWNER: u64 = 9;

Current permissioned signer cannot publish codes.

const ENO_CODE_PERMISSION: u64 = 11;

Dependency could not be resolved to any published package.

const EPACKAGE_DEP_MISSING: u64 = 5;

Cannot upgrade an immutable package

const EUPGRADE_IMMUTABLE: u64 = 2;

Cannot downgrade a package’s upgradability policy

const EUPGRADE_WEAKER_POLICY: u64 = 3;

Structs

PackageMetadata

Metadata for a package. All byte blobs are represented as base64-of-gzipped-bytes

struct PackageMetadata has copy, drop, store
Fields
name: string::String
Name of this package.
upgrade_policy: code::UpgradePolicy
The upgrade policy of this package.
upgrade_number: u64
The numbers of times this module has been upgraded. Also serves as the on-chain version. This field will be automatically assigned on successful upgrade.
source_digest: string::String
The source digest of the sources in the package. This is constructed by first building the sha256 of each individual source, than sorting them alphabetically, and sha256 them again.
manifest: vector<u8>
The package manifest, in the Move.toml format. Gzipped text.
modules: vector<code::ModuleMetadata>
The list of modules installed by this package.
deps: vector<code::PackageDep>
Holds PackageDeps.
extension: option::Option<copyable_any::Any>
For future extension

PackageDep

A dependency to a package published at address

struct PackageDep has copy, drop, store
Fields
account: address
package_name: string::String

ModuleMetadata

Metadata about a module in a package.

struct ModuleMetadata has copy, drop, store
Fields
name: string::String
Name of the module.
source: vector<u8>
Source text, gzipped String. Empty if not provided.
source_map: vector<u8>
Source map, in compressed BCS. Empty if not provided.
extension: option::Option<copyable_any::Any>
For future extensions.

UpgradePolicy

Describes an upgrade policy

struct UpgradePolicy has copy, drop, store
Fields
policy: u8

PublishPackage

Event emitted when code is published to an address.

#[event]
struct PublishPackage has drop, store
Fields
code_address: address
is_upgrade: bool

CodePublishingPermission

struct CodePublishingPermission has copy, drop, store
Fields
dummy_field: bool

AllowedDep

A helper type for request_publish_with_allowed_deps

struct AllowedDep has drop
Fields
account: address
Address of the module.
module_name: string::String
Name of the module. If this is the empty string, then this serves as a wildcard for all modules from this address. This is used for speeding up dependency checking for packages from well-known framework addresses, where we can assume that there are no malicious packages.

Resources

PackageRegistry

The package registry at the given address.

struct PackageRegistry has drop, store, key
Fields
packages: vector<code::PackageMetadata>
Packages installed at this address.

Functions

check_code_publishing_permission

Permissions

public(friend) fun check_code_publishing_permission(s: &signer)
Implementation
public(friend) fun check_code_publishing_permission(s: &signer) {
assert!(
permissioned_signer::check_permission_exists(s, CodePublishingPermission {}),
error::permission_denied(ENO_CODE_PERMISSION),
);
}

grant_permission

Grant permission to publish code on behalf of the master signer.

public fun grant_permission(master: &signer, permissioned_signer: &signer)
Implementation
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
permissioned_signer::authorize_unlimited(master, permissioned_signer, CodePublishingPermission {})
}

upgrade_policy_arbitrary

Whether unconditional code upgrade with no compatibility check is allowed. This publication mode should only be used for modules which aren’t shared with user others. The developer is responsible for not breaking memory layout of any resources he already stored on chain.

public fun upgrade_policy_arbitrary(): code::UpgradePolicy
Implementation
public fun upgrade_policy_arbitrary(): UpgradePolicy {
UpgradePolicy { policy: 0 }
}

upgrade_policy_compat

Whether a compatibility check should be performed for upgrades. The check only passes if a new module has (a) the same public functions (b) for existing resources, no layout change.

public fun upgrade_policy_compat(): code::UpgradePolicy
Implementation
public fun upgrade_policy_compat(): UpgradePolicy {
UpgradePolicy { policy: 1 }
}

upgrade_policy_immutable

Whether the modules in the package are immutable and cannot be upgraded.

public fun upgrade_policy_immutable(): code::UpgradePolicy
Implementation
public fun upgrade_policy_immutable(): UpgradePolicy {
UpgradePolicy { policy: 2 }
}

can_change_upgrade_policy_to

Whether the upgrade policy can be changed. In general, the policy can be only strengthened but not weakened.

public fun can_change_upgrade_policy_to(from: code::UpgradePolicy, to: code::UpgradePolicy): bool
Implementation
public fun can_change_upgrade_policy_to(from: UpgradePolicy, to: UpgradePolicy): bool {
from.policy <= to.policy
}

initialize

Initialize package metadata for Genesis.

fun initialize(aptos_framework: &signer, package_owner: &signer, metadata: code::PackageMetadata)
Implementation
fun initialize(aptos_framework: &signer, package_owner: &signer, metadata: PackageMetadata)
acquires PackageRegistry {
system_addresses::assert_aptos_framework(aptos_framework);
let addr = signer::address_of(package_owner);
if (!exists<PackageRegistry>(addr)) {
move_to(package_owner, PackageRegistry { packages: vector[metadata] })
} else {
vector::push_back(&mut borrow_global_mut<PackageRegistry>(addr).packages, metadata)
}
}

publish_package

Publishes a package at the given signer’s address. The caller must provide package metadata describing the package.

public fun publish_package(owner: &signer, pack: code::PackageMetadata, code: vector<vector<u8>>)
Implementation
public fun publish_package(owner: &signer, pack: PackageMetadata, code: vector<vector<u8>>) acquires PackageRegistry {
check_code_publishing_permission(owner);
// Disallow incompatible upgrade mode. Governance can decide later if this should be reconsidered.
assert!(
pack.upgrade_policy.policy > upgrade_policy_arbitrary().policy,
error::invalid_argument(EINCOMPATIBLE_POLICY_DISABLED),
);
let addr = signer::address_of(owner);
if (!exists<PackageRegistry>(addr)) {
move_to(owner, PackageRegistry { packages: vector::empty() })
};
// Checks for valid dependencies to other packages
let allowed_deps = check_dependencies(addr, &pack);
// Check package against conflicts
// To avoid prover compiler error on spec
// the package need to be an immutable variable
let module_names = get_module_names(&pack);
let package_immutable = &borrow_global<PackageRegistry>(addr).packages;
let len = vector::length(package_immutable);
let index = len;
let upgrade_number = 0;
vector::enumerate_ref(package_immutable
, |i, old| {
let old: &PackageMetadata = old;
if (old.name == pack.name) {
upgrade_number = old.upgrade_number + 1;
check_upgradability(old, &pack, &module_names);
index = i;
} else {
check_coexistence(old, &module_names)
};
});
// Assign the upgrade counter.
pack.upgrade_number = upgrade_number;
let packages = &mut borrow_global_mut<PackageRegistry>(addr).packages;
// Update registry
let policy = pack.upgrade_policy;
if (index < len) {
*vector::borrow_mut(packages, index) = pack
} else {
vector::push_back(packages, pack)
};
event::emit(PublishPackage {
code_address: addr,
is_upgrade: upgrade_number > 0
});
// Request publish
if (features::code_dependency_check_enabled())
request_publish_with_allowed_deps(addr, module_names, allowed_deps, code, policy.policy)
else
// The new `request_publish_with_allowed_deps` has not yet rolled out, so call downwards
// compatible code.
request_publish(addr, module_names, code, policy.policy)
}

freeze_code_object

public fun freeze_code_object(publisher: &signer, code_object: object::Object<code::PackageRegistry>)
Implementation
public fun freeze_code_object(publisher: &signer, code_object: Object<PackageRegistry>) acquires PackageRegistry {
check_code_publishing_permission(publisher);
let code_object_addr = object::object_address(&code_object);
assert!(exists<PackageRegistry>(code_object_addr), error::not_found(ECODE_OBJECT_DOES_NOT_EXIST));
assert!(
object::is_owner(code_object, signer::address_of(publisher)),
error::permission_denied(ENOT_PACKAGE_OWNER)
);
let registry = borrow_global_mut<PackageRegistry>(code_object_addr);
vector::for_each_mut(&mut registry.packages, |pack| {
let package: &mut PackageMetadata = pack;
package.upgrade_policy = upgrade_policy_immutable();
});
// We unfortunately have to make a copy of each package to avoid borrow checker issues as check_dependencies
// needs to borrow PackageRegistry from the dependency packages.
// This would increase the amount of gas used, but this is a rare operation and it's rare to have many packages
// in a single code object.
vector::for_each(registry.packages, |pack| {
check_dependencies(code_object_addr, &pack);
});
}

publish_package_txn

Same as publish_package but as an entry function which can be called as a transaction. Because of current restrictions for txn parameters, the metadata needs to be passed in serialized form.

public entry fun publish_package_txn(owner: &signer, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
Implementation
public entry fun publish_package_txn(owner: &signer, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
acquires PackageRegistry {
publish_package(owner, util::from_bytes<PackageMetadata>(metadata_serialized), code)
}

check_upgradability

Checks whether the given package is upgradable, and returns true if a compatibility check is needed.

fun check_upgradability(old_pack: &code::PackageMetadata, new_pack: &code::PackageMetadata, new_modules: &vector<string::String>)
Implementation
fun check_upgradability(
old_pack: &PackageMetadata, new_pack: &PackageMetadata, new_modules: &vector<String>) {
assert!(old_pack.upgrade_policy.policy < upgrade_policy_immutable().policy,
error::invalid_argument(EUPGRADE_IMMUTABLE));
assert!(can_change_upgrade_policy_to(old_pack.upgrade_policy, new_pack.upgrade_policy),
error::invalid_argument(EUPGRADE_WEAKER_POLICY));
let old_modules = get_module_names(old_pack);
vector::for_each_ref(&old_modules, |old_module| {
assert!(
vector::contains(new_modules, old_module),
EMODULE_MISSING
);
});
}

check_coexistence

Checks whether a new package with given names can co-exist with old package.

fun check_coexistence(old_pack: &code::PackageMetadata, new_modules: &vector<string::String>)
Implementation
fun check_coexistence(old_pack: &PackageMetadata, new_modules: &vector<String>) {
// The modules introduced by each package must not overlap with `names`.
vector::for_each_ref(&old_pack.modules, |old_mod| {
let old_mod: &ModuleMetadata = old_mod;
let j = 0;
while (j < vector::length(new_modules)) {
let name = vector::borrow(new_modules, j);
assert!(&old_mod.name != name, error::already_exists(EMODULE_NAME_CLASH));
j = j + 1;
};
});
}

check_dependencies

Check that the upgrade policies of all packages are equal or higher quality than this package. Also compute the list of module dependencies which are allowed by the package metadata. The later is passed on to the native layer to verify that bytecode dependencies are actually what is pretended here.

fun check_dependencies(publish_address: address, pack: &code::PackageMetadata): vector<code::AllowedDep>
Implementation
fun check_dependencies(publish_address: address, pack: &PackageMetadata): vector<AllowedDep>
acquires PackageRegistry {
let allowed_module_deps = vector::empty();
let deps = &pack.deps;
vector::for_each_ref(deps, |dep| {
let dep: &PackageDep = dep;
assert!(exists<PackageRegistry>(dep.account), error::not_found(EPACKAGE_DEP_MISSING));
if (is_policy_exempted_address(dep.account)) {
// Allow all modules from this address, by using "" as a wildcard in the AllowedDep
let account: address = dep.account;
let module_name = string::utf8(b"");
vector::push_back(&mut allowed_module_deps, AllowedDep { account, module_name });
} else {
let registry = borrow_global<PackageRegistry>(dep.account);
let found = vector::any(&registry.packages, |dep_pack| {
let dep_pack: &PackageMetadata = dep_pack;
if (dep_pack.name == dep.package_name) {
// Check policy
assert!(
dep_pack.upgrade_policy.policy >= pack.upgrade_policy.policy,
error::invalid_argument(EDEP_WEAKER_POLICY)
);
if (dep_pack.upgrade_policy == upgrade_policy_arbitrary()) {
assert!(
dep.account == publish_address,
error::invalid_argument(EDEP_ARBITRARY_NOT_SAME_ADDRESS)
)
};
// Add allowed deps
let account = dep.account;
let k = 0;
let r = vector::length(&dep_pack.modules);
while (k < r) {
let module_name = vector::borrow(&dep_pack.modules, k).name;
vector::push_back(&mut allowed_module_deps, AllowedDep { account, module_name });
k = k + 1;
};
true
} else {
false
}
});
assert!(found, error::not_found(EPACKAGE_DEP_MISSING));
};
});
allowed_module_deps
}

is_policy_exempted_address

Core addresses which are exempted from the check that their policy matches the referring package. Without this exemption, it would not be possible to define an immutable package based on the core system, which requires to be upgradable for maintenance and evolution, and is configured to be compatible.

fun is_policy_exempted_address(addr: address): bool
Implementation
fun is_policy_exempted_address(addr: address): bool {
addr == @1 || addr == @2 || addr == @3 || addr == @4 || addr == @5 ||
addr == @6 || addr == @7 || addr == @8 || addr == @9 || addr == @10
}

get_module_names

Get the names of the modules in a package.

fun get_module_names(pack: &code::PackageMetadata): vector<string::String>
Implementation
fun get_module_names(pack: &PackageMetadata): vector<String> {
let module_names = vector::empty();
vector::for_each_ref(&pack.modules, |pack_module| {
let pack_module: &ModuleMetadata = pack_module;
vector::push_back(&mut module_names, pack_module.name);
});
module_names
}

request_publish

Native function to initiate module loading

fun request_publish(owner: address, expected_modules: vector<string::String>, bundle: vector<vector<u8>>, policy: u8)
Implementation
native fun request_publish(
owner: address,
expected_modules: vector<String>,
bundle: vector<vector<u8>>,
policy: u8
);

request_publish_with_allowed_deps

Native function to initiate module loading, including a list of allowed dependencies.

fun request_publish_with_allowed_deps(owner: address, expected_modules: vector<string::String>, allowed_deps: vector<code::AllowedDep>, bundle: vector<vector<u8>>, policy: u8)
Implementation
native fun request_publish_with_allowed_deps(
owner: address,
expected_modules: vector<String>,
allowed_deps: vector<AllowedDep>,
bundle: vector<vector<u8>>,
policy: u8
);

Specification

High-level Requirements

No.RequirementCriticalityImplementationEnforcement
1 Updating a package should fail if the user is not the owner of it. Critical The publish_package function may only be able to update the package if the signer is the actual owner of the package. The Aptos upgrade native functions have been manually audited.
2 The arbitrary upgrade policy should never be used. Critical There should never be a pass of an arbitrary upgrade policy to the request_publish native function. Manually audited that it aborts if package.upgrade_policy.policy == 0.
3 Should perform accurate compatibility checks when the policy indicates compatibility, ensuring it meets the required conditions. Critical Specifies if it should perform compatibility checks for upgrades. The check only passes if a new module has (a) the same public functions, and (b) for existing resources, no layout change. The Move upgradability patterns have been manually audited.
4 Package upgrades should abide by policy change rules. In particular, The new upgrade policy must be equal to or stricter when compared to the old one. The original upgrade policy must not be immutable. The new package must contain all modules contained in the old package. Medium A package may only be updated using the publish_package function when the check_upgradability function returns true. This is audited by a manual review of the check_upgradability patterns.
5 The upgrade policy of a package must not exceed the strictness level imposed by its dependencies. Medium The upgrade_policy of a package may only be less than its dependencies throughout the upgrades. In addition, the native code properly restricts the use of dependencies outside the passed-in metadata. This has been manually audited.
6 The extension for package metadata is currently unused. Medium The extension field in PackageMetadata should be unused. Data invariant on the extension field has been manually audited.
7 The upgrade number of a package increases incrementally in a monotonic manner with each subsequent upgrade. Low On each upgrade of a particular package, the publish_package function updates the upgrade_number for that package. Post condition on upgrade_number has been manually audited.

Module-level Specification

pragma verify = true;
pragma aborts_if_is_partial;

initialize

fun initialize(aptos_framework: &signer, package_owner: &signer, metadata: code::PackageMetadata)
let aptos_addr = signer::address_of(aptos_framework);
let owner_addr = signer::address_of(package_owner);
aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);
ensures exists<PackageRegistry>(owner_addr);

publish_package

public fun publish_package(owner: &signer, pack: code::PackageMetadata, code: vector<vector<u8>>)
pragma aborts_if_is_partial;
let addr = signer::address_of(owner);
modifies global<PackageRegistry>(addr);
aborts_if pack.upgrade_policy.policy <= upgrade_policy_arbitrary().policy;

freeze_code_object

public fun freeze_code_object(publisher: &signer, code_object: object::Object<code::PackageRegistry>)
pragma aborts_if_is_partial;
let code_object_addr = code_object.inner;
aborts_if !exists<object::ObjectCore>(code_object_addr);
aborts_if !exists<PackageRegistry>(code_object_addr);
aborts_if !object::is_owner(code_object, signer::address_of(publisher));
modifies global<PackageRegistry>(code_object_addr);

publish_package_txn

public entry fun publish_package_txn(owner: &signer, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
pragma verify = false;

check_upgradability

fun check_upgradability(old_pack: &code::PackageMetadata, new_pack: &code::PackageMetadata, new_modules: &vector<string::String>)
pragma aborts_if_is_partial;
aborts_if old_pack.upgrade_policy.policy >= upgrade_policy_immutable().policy;
aborts_if !can_change_upgrade_policy_to(old_pack.upgrade_policy, new_pack.upgrade_policy);

check_coexistence

fun check_coexistence(old_pack: &code::PackageMetadata, new_modules: &vector<string::String>)
pragma verify = false;

check_dependencies

fun check_dependencies(publish_address: address, pack: &code::PackageMetadata): vector<code::AllowedDep>
pragma verify = false;

get_module_names

fun get_module_names(pack: &code::PackageMetadata): vector<string::String>
pragma opaque;
aborts_if [abstract] false;
ensures [abstract] len(result) == len(pack.modules);
ensures [abstract] forall i in 0..len(result): result[i] == pack.modules[i].name;

request_publish

fun request_publish(owner: address, expected_modules: vector<string::String>, bundle: vector<vector<u8>>, policy: u8)
pragma opaque;

request_publish_with_allowed_deps

fun request_publish_with_allowed_deps(owner: address, expected_modules: vector<string::String>, allowed_deps: vector<code::AllowedDep>, bundle: vector<vector<u8>>, policy: u8)
pragma opaque;
schema AbortsIfPermissionedSigner {
s: signer;
let perm = CodePublishingPermission {};
aborts_if !permissioned_signer::spec_check_permission_exists(s, perm);
}