diff --git a/aptos-move/aptos-release-builder/src/components/feature_flags.rs b/aptos-move/aptos-release-builder/src/components/feature_flags.rs index d8378144c7dec..ff8ec72e3dc79 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -103,6 +103,7 @@ pub enum FeatureFlag { ConcurrentFungibleAssets, RefundableBytes, ObjectCodeDeployment, + MaxObjectNestingCheck, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -264,6 +265,7 @@ impl From for AptosFeatureFlag { FeatureFlag::ConcurrentFungibleAssets => AptosFeatureFlag::CONCURRENT_FUNGIBLE_ASSETS, FeatureFlag::RefundableBytes => AptosFeatureFlag::REFUNDABLE_BYTES, FeatureFlag::ObjectCodeDeployment => AptosFeatureFlag::OBJECT_CODE_DEPLOYMENT, + FeatureFlag::MaxObjectNestingCheck => AptosFeatureFlag::MAX_OBJECT_NESTING_CHECK, } } } @@ -348,6 +350,7 @@ impl From for FeatureFlag { AptosFeatureFlag::CONCURRENT_FUNGIBLE_ASSETS => FeatureFlag::ConcurrentFungibleAssets, AptosFeatureFlag::REFUNDABLE_BYTES => FeatureFlag::RefundableBytes, AptosFeatureFlag::OBJECT_CODE_DEPLOYMENT => FeatureFlag::ObjectCodeDeployment, + AptosFeatureFlag::MAX_OBJECT_NESTING_CHECK => FeatureFlag::MaxObjectNestingCheck, } } } diff --git a/aptos-move/framework/aptos-framework/doc/object.md b/aptos-move/framework/aptos-framework/doc/object.md index 39a70aa12ce28..f6fedad987f5c 100644 --- a/aptos-move/framework/aptos-framework/doc/object.md +++ b/aptos-move/framework/aptos-framework/doc/object.md @@ -32,7 +32,6 @@ make it so that a reference to a global object can be returned from a function. - [Struct `LinearTransferRef`](#0x1_object_LinearTransferRef) - [Struct `DeriveRef`](#0x1_object_DeriveRef) - [Struct `TransferEvent`](#0x1_object_TransferEvent) -- [Resource `Ghost$g_roll`](#0x1_object_Ghost$g_roll) - [Constants](#@Constants_0) - [Function `is_burnt`](#0x1_object_is_burnt) - [Function `address_to_object`](#0x1_object_address_to_object) @@ -129,6 +128,7 @@ make it so that a reference to a global object can be returned from a function. use 0x1::create_signer; use 0x1::error; use 0x1::event; +use 0x1::features; use 0x1::from_bcs; use 0x1::guid; use 0x1::hash; @@ -496,33 +496,6 @@ Emitted whenever the object's owner field is changed. - - - - -## Resource `Ghost$g_roll` - - - -
struct Ghost$g_roll has copy, drop, store, key
-
- - - -
-Fields - - -
-
-v: u8 -
-
- -
-
- -
@@ -1913,18 +1886,11 @@ objects may have cyclic dependencies. let current_address = object.owner; let count = 0; - while ({ - spec { - invariant count < MAXIMUM_OBJECT_NESTING; - invariant forall i in 0..count: - exists<ObjectCore>(current_address) && global<ObjectCore>(current_address).allow_ungated_transfer; - // invariant forall i in 0..count: - // current_address == get_transfer_address(global<ObjectCore>(destination).owner, i); + while (owner != current_address) { + count = count + 1; + if (std::features::max_object_nesting_check_enabled()) { + assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING)) }; - owner != current_address - }) { - let count = count + 1; - assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING)); // At this point, the first object exists and so the more likely case is that the // object's owner is not an object. So we return a more sensible error. assert!( @@ -2124,16 +2090,11 @@ Return true if the provided address has indirect or direct ownership of the prov let current_address = object.owner; let count = 0; - while ({ - spec { - invariant count < MAXIMUM_OBJECT_NESTING; - invariant forall i in 0..count: - owner != current_address && exists<ObjectCore>(current_address); + while (owner != current_address) { + count = count + 1; + if (std::features::max_object_nesting_check_enabled()) { + assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING)) }; - owner != current_address - }) { - let count = count + 1; - assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING)); if (!exists<ObjectCore>(current_address)) { return false }; @@ -2224,8 +2185,6 @@ Return true if the provided address has indirect or direct ownership of the prov
pragma aborts_if_is_strict;
-
-global g_roll: u8;
 
@@ -3058,7 +3017,8 @@ Return true if the provided address has indirect or direct ownership of the prov -
let current_address_0 = object.inner;
+
pragma aborts_if_is_partial;
+let current_address_0 = object.inner;
 let object_0 = global<ObjectCore>(current_address_0);
 let current_address = object_0.owner;
 aborts_if object.inner != owner && !exists<ObjectCore>(object.inner);
diff --git a/aptos-move/framework/aptos-framework/sources/object.move b/aptos-move/framework/aptos-framework/sources/object.move
index 77c7ea3b59110..82400e72c63a4 100644
--- a/aptos-move/framework/aptos-framework/sources/object.move
+++ b/aptos-move/framework/aptos-framework/sources/object.move
@@ -532,18 +532,11 @@ module aptos_framework::object {
 
         let current_address = object.owner;
         let count = 0;
-        while ({
-            spec {
-                invariant count < MAXIMUM_OBJECT_NESTING;
-                invariant forall i in 0..count:
-                    exists(current_address) && global(current_address).allow_ungated_transfer;
-                // invariant forall i in 0..count:
-                //     current_address == get_transfer_address(global(destination).owner, i);
+        while (owner != current_address) {
+            count = count + 1;
+            if (std::features::max_object_nesting_check_enabled()) {
+                assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING))
             };
-            owner != current_address
-        }) {
-            let count = count + 1;
-            assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING));
             // At this point, the first object exists and so the more likely case is that the
             // object's owner is not an object. So we return a more sensible error.
             assert!(
@@ -623,16 +616,11 @@ module aptos_framework::object {
         let current_address = object.owner;
 
         let count = 0;
-        while ({
-            spec {
-                invariant count < MAXIMUM_OBJECT_NESTING;
-                invariant forall i in 0..count:
-                    owner != current_address && exists(current_address);
+        while (owner != current_address) {
+            count = count + 1;
+            if (std::features::max_object_nesting_check_enabled()) {
+                assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING))
             };
-            owner != current_address
-        }) {
-            let count = count + 1;
-            assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING));
             if (!exists(current_address)) {
                 return false
             };
@@ -816,4 +804,110 @@ module aptos_framework::object {
         let (_, hero) = create_hero(creator);
         unburn(creator, hero);
     }
+
+    #[test_only]
+    fun create_simple_object(creator: &signer, seed: vector): Object {
+        object_from_constructor_ref(&create_named_object(creator, seed))
+    }
+
+    #[test(creator = @0x123)]
+    #[expected_failure(abort_code = 131078, location = Self)]
+    fun test_exceeding_maximum_object_nesting_owns_should_fail(creator: &signer) acquires ObjectCore {
+        use std::features;
+        let feature = features::get_max_object_nesting_check_feature();
+        let fx = account::create_signer_for_test(@0x1);
+        features::change_feature_flags(&fx, vector[feature], vector[]);
+
+        let obj1 = create_simple_object(creator, b"1");
+        let obj2 = create_simple_object(creator, b"2");
+        let obj3 = create_simple_object(creator, b"3");
+        let obj4 = create_simple_object(creator, b"4");
+        let obj5 = create_simple_object(creator, b"5");
+        let obj6 = create_simple_object(creator, b"6");
+        let obj7 = create_simple_object(creator, b"7");
+        let obj8 = create_simple_object(creator, b"8");
+        let obj9 = create_simple_object(creator, b"9");
+
+        transfer(creator, obj1, object_address(&obj2));
+        transfer(creator, obj2, object_address(&obj3));
+        transfer(creator, obj3, object_address(&obj4));
+        transfer(creator, obj4, object_address(&obj5));
+        transfer(creator, obj5, object_address(&obj6));
+        transfer(creator, obj6, object_address(&obj7));
+        transfer(creator, obj7, object_address(&obj8));
+        transfer(creator, obj8, object_address(&obj9));
+
+        assert!(owns(obj9, signer::address_of(creator)), 1);
+        assert!(owns(obj8, signer::address_of(creator)), 1);
+        assert!(owns(obj7, signer::address_of(creator)), 1);
+        assert!(owns(obj6, signer::address_of(creator)), 1);
+        assert!(owns(obj5, signer::address_of(creator)), 1);
+        assert!(owns(obj4, signer::address_of(creator)), 1);
+        assert!(owns(obj3, signer::address_of(creator)), 1);
+        assert!(owns(obj2, signer::address_of(creator)), 1);
+
+        // Calling `owns` should fail as the nesting is too deep.
+        assert!(owns(obj1, signer::address_of(creator)), 1);
+    }
+
+    #[test(creator = @0x123)]
+    #[expected_failure(abort_code = 131078, location = Self)]
+    fun test_exceeding_maximum_object_nesting_transfer_should_fail(creator: &signer) acquires ObjectCore {
+        use std::features;
+        let feature = features::get_max_object_nesting_check_feature();
+        let fx = account::create_signer_for_test(@0x1);
+        features::change_feature_flags(&fx, vector[feature], vector[]);
+
+        let obj1 = create_simple_object(creator, b"1");
+        let obj2 = create_simple_object(creator, b"2");
+        let obj3 = create_simple_object(creator, b"3");
+        let obj4 = create_simple_object(creator, b"4");
+        let obj5 = create_simple_object(creator, b"5");
+        let obj6 = create_simple_object(creator, b"6");
+        let obj7 = create_simple_object(creator, b"7");
+        let obj8 = create_simple_object(creator, b"8");
+        let obj9 = create_simple_object(creator, b"9");
+
+        transfer(creator, obj1, object_address(&obj2));
+        transfer(creator, obj2, object_address(&obj3));
+        transfer(creator, obj3, object_address(&obj4));
+        transfer(creator, obj4, object_address(&obj5));
+        transfer(creator, obj5, object_address(&obj6));
+        transfer(creator, obj6, object_address(&obj7));
+        transfer(creator, obj7, object_address(&obj8));
+        transfer(creator, obj8, object_address(&obj9));
+
+        // This should fail as the nesting is too deep.
+        transfer(creator, obj1, @0x1);
+    }
+
+    #[test(creator = @0x123)]
+    #[expected_failure(abort_code = 131078, location = Self)]
+    fun test_cyclic_ownership_transfer_should_fail(creator: &signer) acquires ObjectCore {
+        use std::features;
+        let feature = features::get_max_object_nesting_check_feature();
+        let fx = account::create_signer_for_test(@0x1);
+        features::change_feature_flags(&fx, vector[feature], vector[]);
+
+        let obj1 = create_simple_object(creator, b"1");
+        // This creates a cycle (self-loop) in ownership.
+        transfer(creator, obj1, object_address(&obj1));
+        // This should fails as the ownership is cyclic.
+        transfer(creator, obj1, object_address(&obj1));
+    }
+
+    #[test(creator = @0x123)]
+    #[expected_failure(abort_code = 131078, location = Self)]
+    fun test_cyclic_ownership_owns_should_fail(creator: &signer) acquires ObjectCore {
+        use std::features;
+        let feature = features::get_max_object_nesting_check_feature();
+        let fx = account::create_signer_for_test(@0x1);
+        features::change_feature_flags(&fx, vector[feature], vector[]);
+
+        let obj1 = create_simple_object(creator, b"1");
+        // This creates a cycle (self-loop) in ownership.
+        transfer(creator, obj1, object_address(&obj1));
+        // This should fails as the ownership is cyclic.
+        let _ = owns(obj1, signer::address_of(creator));
+    }
 }
diff --git a/aptos-move/framework/aptos-framework/sources/object.spec.move b/aptos-move/framework/aptos-framework/sources/object.spec.move
index 7f7fbf10245f8..49ec75b3bdbe9 100644
--- a/aptos-move/framework/aptos-framework/sources/object.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/object.spec.move
@@ -47,8 +47,6 @@ spec aptos_framework::object {
     ///
     spec module {
         pragma aborts_if_is_strict;
-        //ghost variable
-        global g_roll: u8;
     }
 
     spec fun spec_exists_at(object: address): bool;
@@ -504,6 +502,7 @@ spec aptos_framework::object {
     }
 
     spec owns(object: Object, owner: address): bool {
+        pragma aborts_if_is_partial;
         let current_address_0 = object.inner;
         let object_0 = global(current_address_0);
         let current_address = object_0.owner;
diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md
index 357f55b2cd184..182e470ff503c 100644
--- a/aptos-move/framework/move-stdlib/doc/features.md
+++ b/aptos-move/framework/move-stdlib/doc/features.md
@@ -95,6 +95,8 @@ return true.
 -  [Function `get_concurrent_fungible_assets_feature`](#0x1_features_get_concurrent_fungible_assets_feature)
 -  [Function `concurrent_fungible_assets_enabled`](#0x1_features_concurrent_fungible_assets_enabled)
 -  [Function `is_object_code_deployment_enabled`](#0x1_features_is_object_code_deployment_enabled)
+-  [Function `get_max_object_nesting_check_feature`](#0x1_features_get_max_object_nesting_check_feature)
+-  [Function `max_object_nesting_check_enabled`](#0x1_features_max_object_nesting_check_enabled)
 -  [Function `change_feature_flags`](#0x1_features_change_feature_flags)
 -  [Function `is_enabled`](#0x1_features_is_enabled)
 -  [Function `set`](#0x1_features_set)
@@ -418,6 +420,16 @@ Lifetime: permanent
 
 
 
+
+
+Whether checking the maximum object nesting is enabled.
+
+
+
const MAX_OBJECT_NESTING_CHECK: u64 = 53;
+
+ + + Whether emit function in event.move are enabled for module events. @@ -2139,6 +2151,52 @@ Lifetime: transient + + + + +## Function `get_max_object_nesting_check_feature` + + + +
public fun get_max_object_nesting_check_feature(): u64
+
+ + + +
+Implementation + + +
public fun get_max_object_nesting_check_feature(): u64 { MAX_OBJECT_NESTING_CHECK }
+
+ + + +
+ + + +## Function `max_object_nesting_check_enabled` + + + +
public fun max_object_nesting_check_enabled(): bool
+
+ + + +
+Implementation + + +
public fun max_object_nesting_check_enabled(): bool acquires Features {
+    is_enabled(MAX_OBJECT_NESTING_CHECK)
+}
+
+ + +
diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index f7860686ce6b9..127ced208c7f4 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -394,10 +394,20 @@ module std::features { /// Whether deploying to objects is enabled. const OBJECT_CODE_DEPLOYMENT: u64 = 52; + public fun is_object_code_deployment_enabled(): bool acquires Features { is_enabled(OBJECT_CODE_DEPLOYMENT) } + /// Whether checking the maximum object nesting is enabled. + const MAX_OBJECT_NESTING_CHECK: u64 = 53; + + public fun get_max_object_nesting_check_feature(): u64 { MAX_OBJECT_NESTING_CHECK } + + public fun max_object_nesting_check_enabled(): bool acquires Features { + is_enabled(MAX_OBJECT_NESTING_CHECK) + } + // ============================================================================================ // Feature Flag Implementation diff --git a/aptos-move/vm-genesis/src/lib.rs b/aptos-move/vm-genesis/src/lib.rs index f93b908092986..9ef8a50a1c55a 100644 --- a/aptos-move/vm-genesis/src/lib.rs +++ b/aptos-move/vm-genesis/src/lib.rs @@ -460,6 +460,7 @@ pub fn default_features() -> Vec { FeatureFlag::JWK_CONSENSUS, FeatureFlag::REFUNDABLE_BYTES, FeatureFlag::OBJECT_CODE_DEPLOYMENT, + FeatureFlag::MAX_OBJECT_NESTING_CHECK, ] } diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index 51472a0aeeccd..d2a642d644cb9 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -60,6 +60,7 @@ pub enum FeatureFlag { CONCURRENT_FUNGIBLE_ASSETS = 50, REFUNDABLE_BYTES = 51, OBJECT_CODE_DEPLOYMENT = 52, + MAX_OBJECT_NESTING_CHECK = 53, } /// Representation of features on chain as a bitset.