From 3e9fb56c28d6332dcefa2239d83ceae34f79603a Mon Sep 17 00:00:00 2001 From: Igor Date: Fri, 8 Nov 2024 13:04:01 -0800 Subject: [PATCH] [move] Implement generic comparison method in move - move part --- .../pack_stdlib/sources/cmp.move | 1 + .../pack_stdlib_incompat/sources/cmp.move | 1 + aptos-move/framework/move-stdlib/doc/cmp.md | 235 ++++++++++++++++++ .../framework/move-stdlib/doc/overview.md | 1 + .../framework/move-stdlib/sources/cmp.move | 133 ++++++++++ .../move-stdlib/tests/bcs_tests.move | 31 ++- .../executor/tests/internal_indexer_test.rs | 1 + 7 files changed, 395 insertions(+), 8 deletions(-) create mode 120000 aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib/sources/cmp.move create mode 120000 aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib_incompat/sources/cmp.move create mode 100644 aptos-move/framework/move-stdlib/doc/cmp.md create mode 100644 aptos-move/framework/move-stdlib/sources/cmp.move diff --git a/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib/sources/cmp.move b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib/sources/cmp.move new file mode 120000 index 0000000000000..638cf924ea5a8 --- /dev/null +++ b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib/sources/cmp.move @@ -0,0 +1 @@ +../../../../../../framework/move-stdlib/sources/cmp.move \ No newline at end of file diff --git a/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib_incompat/sources/cmp.move b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib_incompat/sources/cmp.move new file mode 120000 index 0000000000000..638cf924ea5a8 --- /dev/null +++ b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib_incompat/sources/cmp.move @@ -0,0 +1 @@ +../../../../../../framework/move-stdlib/sources/cmp.move \ No newline at end of file diff --git a/aptos-move/framework/move-stdlib/doc/cmp.md b/aptos-move/framework/move-stdlib/doc/cmp.md new file mode 100644 index 0000000000000..266916e9115f2 --- /dev/null +++ b/aptos-move/framework/move-stdlib/doc/cmp.md @@ -0,0 +1,235 @@ + + + +# Module `0x1::cmp` + + + +- [Enum `Ordering`](#0x1_cmp_Ordering) +- [Function `compare`](#0x1_cmp_compare) +- [Function `is_eq`](#0x1_cmp_is_eq) +- [Function `is_lt`](#0x1_cmp_is_lt) +- [Function `is_le`](#0x1_cmp_is_le) +- [Function `is_gt`](#0x1_cmp_is_gt) +- [Function `is_ge`](#0x1_cmp_is_ge) + + +
+ + + + + +## Enum `Ordering` + + + +
enum Ordering has copy, drop
+
+ + + +
+Variants + + +
+Less + + +
+Fields + + +
+
+ + +
+ +
+ +
+Equal + + +
+Fields + + +
+
+ + +
+ +
+ +
+Greater + + +
+Fields + + +
+
+ + +
+ +
+ +
+ + + +## Function `compare` + +Compares two values with the natural ordering: +- native types are compared identically to < and other operators +- complex types +- Structs and vectors - are compared lexicographically - first field/element is compared first, +and if equal we proceed to the next. +- enum's are compared first by their variant, and if equal - they are compared as structs are. + + +
public(friend) fun compare<T>(first: &T, second: &T): cmp::Ordering
+
+ + + +
+Implementation + + +
native public(friend) fun compare<T>(first: &T, second: &T): Ordering;
+
+ + + +
+ + + +## Function `is_eq` + + + +
public fun is_eq(self: &cmp::Ordering): bool
+
+ + + +
+Implementation + + +
public fun is_eq(self: &Ordering): bool {
+    self is Ordering::Equal
+}
+
+ + + +
+ + + +## Function `is_lt` + + + +
public fun is_lt(self: &cmp::Ordering): bool
+
+ + + +
+Implementation + + +
public fun is_lt(self: &Ordering): bool {
+    self is Ordering::Less
+}
+
+ + + +
+ + + +## Function `is_le` + + + +
public fun is_le(self: &cmp::Ordering): bool
+
+ + + +
+Implementation + + +
public fun is_le(self: &Ordering): bool {
+    !(self is Ordering::Greater)
+}
+
+ + + +
+ + + +## Function `is_gt` + + + +
public fun is_gt(self: &cmp::Ordering): bool
+
+ + + +
+Implementation + + +
public fun is_gt(self: &Ordering): bool {
+    self is Ordering::Greater
+}
+
+ + + +
+ + + +## Function `is_ge` + + + +
public fun is_ge(self: &cmp::Ordering): bool
+
+ + + +
+Implementation + + +
public fun is_ge(self: &Ordering): bool {
+    !(self is Ordering::Less)
+}
+
+ + + +
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/move-stdlib/doc/overview.md b/aptos-move/framework/move-stdlib/doc/overview.md index 649873e8ab2f5..3c93f83875d10 100644 --- a/aptos-move/framework/move-stdlib/doc/overview.md +++ b/aptos-move/framework/move-stdlib/doc/overview.md @@ -16,6 +16,7 @@ For on overview of the Move language, see the [Move Book][move-book]. - [`0x1::acl`](acl.md#0x1_acl) - [`0x1::bcs`](bcs.md#0x1_bcs) - [`0x1::bit_vector`](bit_vector.md#0x1_bit_vector) +- [`0x1::cmp`](cmp.md#0x1_cmp) - [`0x1::error`](error.md#0x1_error) - [`0x1::features`](features.md#0x1_features) - [`0x1::fixed_point32`](fixed_point32.md#0x1_fixed_point32) diff --git a/aptos-move/framework/move-stdlib/sources/cmp.move b/aptos-move/framework/move-stdlib/sources/cmp.move new file mode 100644 index 0000000000000..fcb56573e0e51 --- /dev/null +++ b/aptos-move/framework/move-stdlib/sources/cmp.move @@ -0,0 +1,133 @@ +module std::cmp { + enum Ordering has copy, drop { + /// First value is less than the second value. + Less, + /// First value is equal to the second value. + Equal, + /// First value is greater than the second value. + Greater, + } + + // TODO - functions here are `public(friend)` here for one release, + // and to be changed to `public` one release later. + #[test_only] + friend std::bcs_tests; + + /// Compares two values with the natural ordering: + /// - native types are compared identically to `<` and other operators + /// - complex types + /// - Structs and vectors - are compared lexicographically - first field/element is compared first, + /// and if equal we proceed to the next. + /// - enum's are compared first by their variant, and if equal - they are compared as structs are. + native public(friend) fun compare(first: &T, second: &T): Ordering; + + public fun is_eq(self: &Ordering): bool { + self is Ordering::Equal + } + + public fun is_lt(self: &Ordering): bool { + self is Ordering::Less + } + + public fun is_le(self: &Ordering): bool { + !(self is Ordering::Greater) + } + + public fun is_gt(self: &Ordering): bool { + self is Ordering::Greater + } + + public fun is_ge(self: &Ordering): bool { + !(self is Ordering::Less) + } + + #[test_only] + struct SomeStruct has drop { + field_1: u64, + field_2: u64, + } + #[test_only] + enum SimpleEnum has drop { + V { field: u64 }, + } + + #[test_only] + enum SomeEnum has drop { + V1 { field_1: u64 }, + V2 { field_2: u64 }, + V3 { field_3: SomeStruct }, + V4 { field_4: vector }, + V5 { field_5: SimpleEnum }, + } + + #[test] + fun test_compare_numbers() { + assert!(!is_eq(&compare(&1, &5)), 0); + assert!(is_lt(&compare(&1, &5)), 1); + assert!(is_le(&compare(&1, &5)), 2); + assert!(is_eq(&compare(&5, &5)), 3); + assert!(!is_lt(&compare(&5, &5)), 4); + assert!(is_le(&compare(&5, &5)), 5); + assert!(!is_eq(&compare(&7, &5)), 6); + assert!(!is_lt(&compare(&7, &5)), 7); + assert!(!is_le(&compare(&7, &5)), 8); + + assert!(!compare(&1, &5).is_eq(), 0); + assert!(compare(&1, &5).is_lt(), 1); + assert!(compare(&1, &5).is_le(), 2); + assert!(compare(&5, &5).is_eq(), 3); + assert!(!compare(&5, &5).is_lt(), 4); + assert!(compare(&5, &5).is_le(), 5); + assert!(!compare(&7, &5).is_eq(), 6); + assert!(!compare(&7, &5).is_lt(), 7); + assert!(!compare(&7, &5).is_le(), 8); + } + + #[test] + fun test_compare_vectors() { + let empty = vector[]; // here for typing, for the second line + assert!(compare(&empty, &vector[1] ) is Ordering::Less, 0); + assert!(compare(&empty, &vector[] ) is Ordering::Equal, 1); + assert!(compare(&vector[1], &vector[] ) is Ordering::Greater, 2); + assert!(compare(&vector[1, 2], &vector[1, 2] ) is Ordering::Equal, 3); + assert!(compare(&vector[1, 2, 3], &vector[5] ) is Ordering::Less, 4); + assert!(compare(&vector[1, 2, 3], &vector[5, 6, 7]) is Ordering::Less, 5); + assert!(compare(&vector[1, 2, 3], &vector[1, 2, 7]) is Ordering::Less, 6); + } + + #[test] + fun test_compare_structs() { + assert!(compare(&SomeStruct { field_1: 1, field_2: 2}, &SomeStruct { field_1: 1, field_2: 2}) is Ordering::Equal, 0); + assert!(compare(&SomeStruct { field_1: 1, field_2: 2}, &SomeStruct { field_1: 1, field_2: 3}) is Ordering::Less, 1); + assert!(compare(&SomeStruct { field_1: 1, field_2: 2}, &SomeStruct { field_1: 1, field_2: 1}) is Ordering::Greater, 2); + assert!(compare(&SomeStruct { field_1: 1, field_2: 2}, &SomeStruct { field_1: 1, field_2: 1}) is Ordering::Greater, 3); + } + + #[test] + fun test_compare_vector_of_structs() { + assert!(compare(&vector[SomeStruct { field_1: 1, field_2: 2}, SomeStruct { field_1: 3, field_2: 4}], &vector[SomeStruct { field_1: 1, field_2: 3}]) is Ordering::Less, 0); + assert!(compare(&vector[SomeStruct { field_1: 1, field_2: 2}, SomeStruct { field_1: 3, field_2: 4}], &vector[SomeStruct { field_1: 1, field_2: 2}, SomeStruct { field_1: 1, field_2: 3}]) is Ordering::Greater, 1); + } + + #[test] + fun test_compare_enums() { + assert!(compare(&SomeEnum::V1 { field_1: 6}, &SomeEnum::V1 { field_1: 6}) is Ordering::Equal, 0); + assert!(compare(&SomeEnum::V1 { field_1: 6}, &SomeEnum::V2 { field_2: 1}) is Ordering::Less, 1); + assert!(compare(&SomeEnum::V1 { field_1: 6}, &SomeEnum::V2 { field_2: 8}) is Ordering::Less, 2); + assert!(compare(&SomeEnum::V1 { field_1: 6}, &SomeEnum::V1 { field_1: 5}) is Ordering::Greater, 3); + + assert!(compare(&SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 2}}, &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 2}}) is Ordering::Equal, 4); + assert!(compare(&SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 2}}, &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 3}}) is Ordering::Less, 5); + assert!(compare(&SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 2}}, &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 1}}) is Ordering::Greater, 6); + assert!(compare(&SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 2}}, &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 1}}) is Ordering::Greater, 7); + + assert!(compare(&SomeEnum::V4 { field_4: vector[1, 2]}, &SomeEnum::V4 { field_4: vector[1, 2]}) is Ordering::Equal, 8); + assert!(compare(&SomeEnum::V4 { field_4: vector[1, 2, 3]}, &SomeEnum::V4 { field_4: vector[5]}) is Ordering::Less, 9); + assert!(compare(&SomeEnum::V4 { field_4: vector[1, 2, 3]}, &SomeEnum::V4 { field_4: vector[5, 6, 7]}) is Ordering::Less, 10); + assert!(compare(&SomeEnum::V4 { field_4: vector[1, 2, 3]}, &SomeEnum::V4 { field_4: vector[1, 2, 7]}) is Ordering::Less, 11); + + assert!(compare(&SomeEnum::V5 { field_5: SimpleEnum::V { field: 3}}, &SomeEnum::V5 { field_5: SimpleEnum::V { field: 3}}) is Ordering::Equal, 12); + assert!(compare(&SomeEnum::V5 { field_5: SimpleEnum::V { field: 5}}, &SomeEnum::V5 { field_5: SimpleEnum::V { field: 3}}) is Ordering::Greater, 13); + assert!(compare(&SomeEnum::V5 { field_5: SimpleEnum::V { field: 3}}, &SomeEnum::V5 { field_5: SimpleEnum::V { field: 5}}) is Ordering::Less, 14); + } +} diff --git a/aptos-move/framework/move-stdlib/tests/bcs_tests.move b/aptos-move/framework/move-stdlib/tests/bcs_tests.move index 7fb6b0b57b0a9..c22cf8126342d 100644 --- a/aptos-move/framework/move-stdlib/tests/bcs_tests.move +++ b/aptos-move/framework/move-stdlib/tests/bcs_tests.move @@ -134,12 +134,27 @@ module std::bcs_tests { assert!(option::none() == bcs::constant_serialized_size>>(), 3); } - // enum Singleton { - // V1(u64), - // } - - // fun encode_enum() { - // assert!(option::none() == bcs::constant_serialized_size()); - // assert!(option::none() == bcs::constant_serialized_size>()); - // } + enum Singleton { + V1(u64), + } + + fun encode_enum() { + assert!(option::none() == bcs::constant_serialized_size()); + assert!(option::none() == bcs::constant_serialized_size>()); + } + + // test that serialization is little-endian, and so produces different + // ordering than "expected" natural ordering. + #[test] + fun bcs_comparison() { + let val = 256 * 4 + 2; + let other = 256 * 2 + 4; + + assert!(std::cmp::compare(&val, &other).is_gt()); + + let bytes_val = bcs::to_bytes(&val); + let bytes_other = bcs::to_bytes(&other); + + assert!(std::cmp::compare(&bytes_val, &bytes_other).is_lt()); + } } diff --git a/execution/executor/tests/internal_indexer_test.rs b/execution/executor/tests/internal_indexer_test.rs index 13d03f8092693..0a2872e98582a 100644 --- a/execution/executor/tests/internal_indexer_test.rs +++ b/execution/executor/tests/internal_indexer_test.rs @@ -204,6 +204,7 @@ fn test_db_indexer_data() { ident_str!("acl"), ident_str!("any"), ident_str!("bcs"), + ident_str!("cmp"), ident_str!("dkg"), ident_str!("mem"), ident_str!("code"),