diff --git a/crates/sui-framework-snapshot/bytecode_snapshot/70/0x0000000000000000000000000000000000000000000000000000000000000001 b/crates/sui-framework-snapshot/bytecode_snapshot/70/0x0000000000000000000000000000000000000000000000000000000000000001 deleted file mode 100644 index 39348f37acc90..0000000000000 Binary files a/crates/sui-framework-snapshot/bytecode_snapshot/70/0x0000000000000000000000000000000000000000000000000000000000000001 and /dev/null differ diff --git a/crates/sui-framework-snapshot/bytecode_snapshot/70/0x0000000000000000000000000000000000000000000000000000000000000002 b/crates/sui-framework-snapshot/bytecode_snapshot/70/0x0000000000000000000000000000000000000000000000000000000000000002 deleted file mode 100644 index ae6e7d936c827..0000000000000 Binary files a/crates/sui-framework-snapshot/bytecode_snapshot/70/0x0000000000000000000000000000000000000000000000000000000000000002 and /dev/null differ diff --git a/crates/sui-framework-snapshot/bytecode_snapshot/70/0x0000000000000000000000000000000000000000000000000000000000000003 b/crates/sui-framework-snapshot/bytecode_snapshot/70/0x0000000000000000000000000000000000000000000000000000000000000003 deleted file mode 100644 index bbddd71deebaa..0000000000000 Binary files a/crates/sui-framework-snapshot/bytecode_snapshot/70/0x0000000000000000000000000000000000000000000000000000000000000003 and /dev/null differ diff --git a/crates/sui-framework-snapshot/bytecode_snapshot/70/0x000000000000000000000000000000000000000000000000000000000000000b b/crates/sui-framework-snapshot/bytecode_snapshot/70/0x000000000000000000000000000000000000000000000000000000000000000b deleted file mode 100644 index 48d06a9c45a89..0000000000000 Binary files a/crates/sui-framework-snapshot/bytecode_snapshot/70/0x000000000000000000000000000000000000000000000000000000000000000b and /dev/null differ diff --git a/crates/sui-framework-snapshot/bytecode_snapshot/70/0x000000000000000000000000000000000000000000000000000000000000dee9 b/crates/sui-framework-snapshot/bytecode_snapshot/70/0x000000000000000000000000000000000000000000000000000000000000dee9 deleted file mode 100644 index d6568ff2626fa..0000000000000 Binary files a/crates/sui-framework-snapshot/bytecode_snapshot/70/0x000000000000000000000000000000000000000000000000000000000000dee9 and /dev/null differ diff --git a/crates/sui-framework-snapshot/manifest.json b/crates/sui-framework-snapshot/manifest.json index 3f56cd959f15b..01e805724b1ea 100644 --- a/crates/sui-framework-snapshot/manifest.json +++ b/crates/sui-framework-snapshot/manifest.json @@ -526,15 +526,5 @@ "0x000000000000000000000000000000000000000000000000000000000000dee9", "0x000000000000000000000000000000000000000000000000000000000000000b" ] - }, - "70": { - "git_revision": "1e7f26cc1baf", - "package_ids": [ - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x000000000000000000000000000000000000000000000000000000000000dee9", - "0x000000000000000000000000000000000000000000000000000000000000000b" - ] } -} \ No newline at end of file +} diff --git a/crates/sui-framework/packages/move-stdlib/sources/macros.move b/crates/sui-framework/packages/move-stdlib/sources/macros.move index 39083993f3896..48680c899d702 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/macros.move +++ b/crates/sui-framework/packages/move-stdlib/sources/macros.move @@ -145,3 +145,101 @@ public macro fun try_as_u128($x: _): Option { if (x > 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF) option::none() else option::some(x as u128) } + +/// Creates a fixed-point value from a quotient specified by its numerator and denominator. +/// `$T` is the underlying integer type for the fixed-point value, where `$T` has `$t_bits` bits. +/// `$U` is the type used for intermediate calculations, where `$U` is the next larger integer type. +/// `$max_t` is the maximum value that can be represented by `$T`. +/// `$t_bits` (as mentioned above) is the total number of bits in the fixed-point value (integer +/// plus fractional). +/// `$fractional_bits` is the number of fractional bits in the fixed-point value. +public macro fun uq_from_quotient<$T, $U>( + $numerator: $T, + $denominator: $T, + $max_t: $T, + $t_bits: u8, + $fractional_bits: u8, + $abort_denominator: _, + $abort_quotient_too_small: _, + $abort_quotient_too_large: _, +): $T { + let numerator = $numerator; + let denominator = $denominator; + if (denominator == 0) $abort_denominator; + + // Scale the numerator to have `$t_bits` fractional bits and the denominator to have + // `$t_bits - $fractional_bits` fractional bits, so that the quotient will have + // `$fractional_bits` fractional bits. + let scaled_numerator = numerator as $U << $t_bits; + let scaled_denominator = denominator as $U << ($t_bits - $fractional_bits); + let quotient = scaled_numerator / scaled_denominator; + + // The quotient can only be zero if the numerator is also zero. + if (quotient == 0 && numerator != 0) $abort_quotient_too_small; + + // Return the quotient as a fixed-point number. We first need to check whether the cast + // can succeed. + if (quotient > $max_t as $U) $abort_quotient_too_large; + quotient as $T +} + +public macro fun uq_from_int<$T, $U>($integer: $T, $fractional_bits: u8): $U { + ($integer as $U) << $fractional_bits +} + +public macro fun uq_add<$T, $U>($a: $T, $b: $T, $max_t: $T, $abort_overflow: _): $T { + let sum = $a as $U + ($b as $U); + if (sum > $max_t as $U) $abort_overflow; + sum as $T +} + +public macro fun uq_sub<$T>($a: $T, $b: $T, $abort_overflow: _): $T { + let a = $a; + let b = $b; + if (a < b) $abort_overflow; + a - b +} + +public macro fun uq_to_int<$T, $U>($a: $U, $fractional_bits: u8): $T { + ($a >> $fractional_bits) as $T +} + +public macro fun uq_int_mul<$T, $U>( + $val: $T, + $multiplier: $T, + $max_t: $T, + $fractional_bits: u8, + $abort_overflow: _, +): $T { + // The product of two `$T` bit values has the same number of bits as `$U`, so perform the + // multiplication with `$U` types and keep the full `$U` bit product + // to avoid losing accuracy. + let unscaled_product = $val as $U * ($multiplier as $U); + // The unscaled product has `$fractional_bits` fractional bits (from the multiplier) + // so rescale it by shifting away the low bits. + let product = unscaled_product >> $fractional_bits; + // Check whether the value is too large. + if (product > $max_t as $U) $abort_overflow; + product as $T +} + +public macro fun uq_int_div<$T, $U>( + $val: $T, + $divisor: $T, + $max_t: $T, + $fractional_bits: u8, + $abort_division_by_zero: _, + $abort_overflow: _, +): $T { + let val = $val; + let divisor = $divisor; + // Check for division by zero. + if (divisor == 0) $abort_division_by_zero; + // First convert to $U to increase the number of bits to the next integer size + // and then shift left to add `$fractional_bits` fractional zero bits to the dividend. + let scaled_value = val as $U << $fractional_bits; + let quotient = scaled_value / (divisor as $U); + // Check whether the value is too large. + if (quotient > $max_t as $U) $abort_overflow; + quotient as $T +} diff --git a/crates/sui-framework/packages/move-stdlib/sources/uq32_32.move b/crates/sui-framework/packages/move-stdlib/sources/uq32_32.move index cad6381324090..0af182ccc99e5 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/uq32_32.move +++ b/crates/sui-framework/packages/move-stdlib/sources/uq32_32.move @@ -26,6 +26,11 @@ const EOverflow: vector = b"Overflow from an arithmetic operation"; #[error] const EDivisionByZero: vector = b"Division by zero"; +/// The total number of bits in the fixed-point number. Used in `macro` invocations. +const TOTAL_BITS: u8 = 64; +/// The number of fractional bits in the fixed-point number. Used in `macro` invocations. +const FRACTIONAL_BITS: u8 = 32; + /// A fixed-point numeric type with 32 integer bits and 32 fractional bits, represented by an /// underlying 64 bit value. This is a binary representation, so decimal values may not be exactly /// representable, but it provides more than 9 decimal digits of precision both before and after the @@ -41,42 +46,39 @@ public struct UQ32_32(u64) has copy, drop, store; /// than 2^{-32}. /// Aborts if the input is too large, e.g. larger than or equal to 2^32. public fun from_quotient(numerator: u64, denominator: u64): UQ32_32 { - assert!(denominator != 0, EDenominator); - - // Scale the numerator to have 64 fractional bits and the denominator to have 32 fractional - // bits, so that the quotient will have 32 fractional bits. - let scaled_numerator = numerator as u128 << 64; - let scaled_denominator = denominator as u128 << 32; - let quotient = scaled_numerator / scaled_denominator; - - // The quotient can only be zero if the numerator is also zero. - assert!(quotient != 0 || numerator == 0, EQuotientTooSmall); - - // Return the quotient as a fixed-point number. We first need to check whether the cast - // can succeed. - assert!(quotient <= std::u64::max_value!() as u128, EQuotientTooLarge); - UQ32_32(quotient as u64) + UQ32_32(std::macros::uq_from_quotient!( + numerator, + denominator, + std::u64::max_value!(), + TOTAL_BITS, + FRACTIONAL_BITS, + abort EDenominator, + abort EQuotientTooSmall, + abort EQuotientTooLarge, + )) } /// Create a fixed-point value from an integer. /// `from_int` and `from_quotient` should be preferred over using `from_raw`. public fun from_int(integer: u32): UQ32_32 { - UQ32_32((integer as u64) << 32) + UQ32_32(std::macros::uq_from_int!(integer, FRACTIONAL_BITS)) } /// Add two fixed-point numbers, `a + b`. /// Aborts if the sum overflows. public fun add(a: UQ32_32, b: UQ32_32): UQ32_32 { - let sum = a.0 as u128 + (b.0 as u128); - assert!(sum <= std::u64::max_value!() as u128, EOverflow); - UQ32_32(sum as u64) + UQ32_32(std::macros::uq_add!( + a.0, + b.0, + std::u64::max_value!(), + abort EOverflow, + )) } /// Subtract two fixed-point numbers, `a - b`. /// Aborts if `a < b`. public fun sub(a: UQ32_32, b: UQ32_32): UQ32_32 { - assert!(a.0 >= b.0, EOverflow); - UQ32_32(a.0 - b.0) + UQ32_32(std::macros::uq_sub!(a.0, b.0, abort EOverflow)) } /// Multiply two fixed-point numbers, truncating any fractional part of the product. @@ -94,37 +96,33 @@ public fun div(a: UQ32_32, b: UQ32_32): UQ32_32 { /// Convert a fixed-point number to an integer, truncating any fractional part. public fun to_int(a: UQ32_32): u32 { - (a.0 >> 32) as u32 + std::macros::uq_to_int!(a.0, FRACTIONAL_BITS) } /// Multiply a `u64` integer by a fixed-point number, truncating any fractional part of the product. /// Aborts if the product overflows. public fun int_mul(val: u64, multiplier: UQ32_32): u64 { - // The product of two 64 bit values has 128 bits, so perform the - // multiplication with u128 types and keep the full 128 bit product - // to avoid losing accuracy. - let unscaled_product = val as u128 * (multiplier.0 as u128); - // The unscaled product has 32 fractional bits (from the multiplier) - // so rescale it by shifting away the low bits. - let product = unscaled_product >> 32; - // Check whether the value is too large. - assert!(product <= std::u64::max_value!() as u128, EOverflow); - product as u64 + std::macros::uq_int_mul!( + val, + multiplier.0, + std::u64::max_value!(), + FRACTIONAL_BITS, + abort EOverflow, + ) } /// Divide a `u64` integer by a fixed-point number, truncating any fractional part of the quotient. /// Aborts if the divisor is zero. /// Aborts if the quotient overflows. public fun int_div(val: u64, divisor: UQ32_32): u64 { - // Check for division by zero. - assert!(divisor.0 != 0, EDivisionByZero); - // First convert to 128 bits and then shift left to - // add 32 fractional zero bits to the dividend. - let scaled_value = val as u128 << 32; - let quotient = scaled_value / (divisor.0 as u128); - // Check whether the value is too large. - assert!(quotient <= std::u64::max_value!() as u128, EOverflow); - quotient as u64 + std::macros::uq_int_div!( + val, + divisor.0, + std::u64::max_value!(), + FRACTIONAL_BITS, + abort EDivisionByZero, + abort EOverflow, + ) } /// Less than or equal to. Returns `true` if and only if `a <= a`. diff --git a/crates/sui-framework/packages/move-stdlib/sources/uq64_64.move b/crates/sui-framework/packages/move-stdlib/sources/uq64_64.move new file mode 100644 index 0000000000000..862c19659c147 --- /dev/null +++ b/crates/sui-framework/packages/move-stdlib/sources/uq64_64.move @@ -0,0 +1,158 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Defines an unsigned, fixed-point numeric type with a 64-bit integer part and a 64-bit fractional +/// part. The notation `uq64_64` and `UQ64_64` is based on +/// [Q notation](https://en.wikipedia.org/wiki/Q_(number_format)). `q` indicates it a fixed-point +/// number. The `u` prefix indicates it is unsigned. The `64_64` suffix indicates the number of +/// bits, where the first number indicates the number of bits in the integer part, and the second +/// the number of bits in the fractional part--in this case 64 bits for each. +module std::uq64_64; + +#[error] +const EDenominator: vector = b"Quotient specified with a zero denominator"; + +#[error] +const EQuotientTooSmall: vector = + b"Quotient specified is too small, and is outside of the supported range"; + +#[error] +const EQuotientTooLarge: vector = + b"Quotient specified is too large, and is outside of the supported range"; + +#[error] +const EOverflow: vector = b"Overflow from an arithmetic operation"; + +#[error] +const EDivisionByZero: vector = b"Division by zero"; + +/// The total number of bits in the fixed-point number. Used in `macro` invocations. +const TOTAL_BITS: u8 = 128; +/// The number of fractional bits in the fixed-point number. Used in `macro` invocations. +const FRACTIONAL_BITS: u8 = 64; + +/// A fixed-point numeric type with 64 integer bits and 64 fractional bits, represented by an +/// underlying 128 bit value. This is a binary representation, so decimal values may not be exactly +/// representable, but it provides more than 19 decimal digits of precision both before and after +/// the decimal point (38 digits total). +public struct UQ64_64(u128) has copy, drop, store; + +/// Create a fixed-point value from a quotient specified by its numerator and denominator. +/// `from_quotient` and `from_int` should be preferred over using `from_raw`. +/// Unless the denominator is a power of two, fractions can not be represented accurately, +/// so be careful about rounding errors. +/// Aborts if the denominator is zero. +/// Aborts if the input is non-zero but so small that it will be represented as zero, e.g. smaller +/// than 2^{-64}. +/// Aborts if the input is too large, e.g. larger than or equal to 2^64. +public fun from_quotient(numerator: u128, denominator: u128): UQ64_64 { + UQ64_64(std::macros::uq_from_quotient!( + numerator, + denominator, + std::u128::max_value!(), + TOTAL_BITS, + FRACTIONAL_BITS, + abort EDenominator, + abort EQuotientTooSmall, + abort EQuotientTooLarge, + )) +} + +/// Create a fixed-point value from an integer. +/// `from_int` and `from_quotient` should be preferred over using `from_raw`. +public fun from_int(integer: u64): UQ64_64 { + UQ64_64(std::macros::uq_from_int!(integer, FRACTIONAL_BITS)) +} + +/// Add two fixed-point numbers, `a + b`. +/// Aborts if the sum overflows. +public fun add(a: UQ64_64, b: UQ64_64): UQ64_64 { + UQ64_64(std::macros::uq_add!( + a.0, + b.0, + std::u128::max_value!(), + abort EOverflow, + )) +} + +/// Subtract two fixed-point numbers, `a - b`. +/// Aborts if `a < b`. +public fun sub(a: UQ64_64, b: UQ64_64): UQ64_64 { + UQ64_64(std::macros::uq_sub!(a.0, b.0, abort EOverflow)) +} + +/// Multiply two fixed-point numbers, truncating any fractional part of the product. +/// Aborts if the product overflows. +public fun mul(a: UQ64_64, b: UQ64_64): UQ64_64 { + UQ64_64(int_mul(a.0, b)) +} + +/// Divide two fixed-point numbers, truncating any fractional part of the quotient. +/// Aborts if the divisor is zero. +/// Aborts if the quotient overflows. +public fun div(a: UQ64_64, b: UQ64_64): UQ64_64 { + UQ64_64(int_div(a.0, b)) +} + +/// Convert a fixed-point number to an integer, truncating any fractional part. +public fun to_int(a: UQ64_64): u64 { + std::macros::uq_to_int!(a.0, FRACTIONAL_BITS) +} + +/// Multiply a `u128` integer by a fixed-point number, truncating any fractional part of the product. +/// Aborts if the product overflows. +public fun int_mul(val: u128, multiplier: UQ64_64): u128 { + std::macros::uq_int_mul!( + val, + multiplier.0, + std::u128::max_value!(), + FRACTIONAL_BITS, + abort EOverflow, + ) +} + +/// Divide a `u128` integer by a fixed-point number, truncating any fractional part of the quotient. +/// Aborts if the divisor is zero. +/// Aborts if the quotient overflows. +public fun int_div(val: u128, divisor: UQ64_64): u128 { + std::macros::uq_int_div!( + val, + divisor.0, + std::u128::max_value!(), + FRACTIONAL_BITS, + abort EDivisionByZero, + abort EOverflow, + ) +} + +/// Less than or equal to. Returns `true` if and only if `a <= a`. +public fun le(a: UQ64_64, b: UQ64_64): bool { + a.0 <= b.0 +} + +/// Less than. Returns `true` if and only if `a < b`. +public fun lt(a: UQ64_64, b: UQ64_64): bool { + a.0 < b.0 +} + +/// Greater than or equal to. Returns `true` if and only if `a >= b`. +public fun ge(a: UQ64_64, b: UQ64_64): bool { + a.0 >= b.0 +} + +/// Greater than. Returns `true` if and only if `a > b`. +public fun gt(a: UQ64_64, b: UQ64_64): bool { + a.0 > b.0 +} + +/// Accessor for the raw u128 value. Can be paired with `from_raw` to perform less common operations +/// on the raw values directly. +public fun to_raw(a: UQ64_64): u128 { + a.0 +} + +/// Accessor for the raw u128 value. Can be paired with `to_raw` to perform less common operations +/// on the raw values directly. +public fun from_raw(raw_value: u128): UQ64_64 { + UQ64_64(raw_value) +} diff --git a/crates/sui-framework/packages/move-stdlib/tests/uq64_64_tests.move b/crates/sui-framework/packages/move-stdlib/tests/uq64_64_tests.move new file mode 100644 index 0000000000000..71389fdb9ba6f --- /dev/null +++ b/crates/sui-framework/packages/move-stdlib/tests/uq64_64_tests.move @@ -0,0 +1,257 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module std::uq64_64_tests; + +use std::unit_test::assert_eq; +use std::uq64_64::{ + Self, + add, + sub, + mul, + div, + int_div, + int_mul, + from_int, + from_quotient, + from_raw, + to_raw, +}; + +#[test] +fun from_quotient_zero() { + let x = from_quotient(0, 1); + assert_eq!(x.to_raw(), 0); +} + +#[test] +fun from_quotient_max_numerator_denominator() { + // Test creating a 1.0 fraction from the maximum u128 value. + let f = from_quotient(std::u128::max_value!(), std::u128::max_value!()); + let one = f.to_raw(); + assert_eq!(one, 1 << 64); // 0x1.00000000 +} + +#[test] +#[expected_failure(abort_code = uq64_64::EDenominator)] +fun from_quotient_div_zero() { + // A denominator of zero should cause an arithmetic error. + from_quotient(2, 0); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EQuotientTooLarge)] +fun from_quotient_ratio_too_large() { + // The maximum value is 2^64 - 1. Check that anything larger aborts + // with an overflow. + from_quotient(1 << 64, 1); // 2^64 +} + +#[test] +#[expected_failure(abort_code = uq64_64::EQuotientTooSmall)] +fun from_quotient_ratio_too_small() { + // The minimum non-zero value is 2^-64. Check that anything smaller + // aborts. + from_quotient(1, (1 << 64) + 1); // 1/(2^64 + 1) +} + +#[test] +fun test_from_int() { + assert_eq!(from_int(0).to_raw(), 0); + assert_eq!(from_int(1).to_raw(), 0x1_0000_0000_0000_0000); + assert_eq!(from_int(std::u64::max_value!()).to_raw(), std::u64::max_value!() as u128 << 64); +} + +#[test] +fun test_add() { + let a = from_quotient(3, 4); + assert!(a.add(from_int(0)) == a); + + let c = a.add(from_int(1)); + assert!(from_quotient(7, 4) == c); + + let b = from_quotient(1, 4); + let c = a.add(b); + assert!(from_int(1) == c); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EOverflow)] +fun test_add_overflow() { + let a = from_int(1 << 63); + let b = from_int(1 << 63); + let _ = a.add(b); +} + +#[test] +fun test_sub() { + let a = from_int(5); + assert_eq!(a.sub(from_int(0)), a); + + let b = from_int(4); + let c = a.sub(b); + assert_eq!(from_int(1), c); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EOverflow)] +fun test_sub_underflow() { + let a = from_int(3); + let b = from_int(5); + a.sub(b); +} + +#[test] +fun test_mul() { + let a = from_quotient(3, 4); + assert!(a.mul(from_int(0)) == from_int(0)); + assert!(a.mul(from_int(1)) == a); + + let b = from_quotient(3, 2); + let c = a.mul(b); + let expected = from_quotient(9, 8); + assert_eq!(c, expected); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EOverflow)] +fun test_mul_overflow() { + let a = from_int(1 << 32); + let b = from_int(1 << 32); + let _ = a.mul(b); +} + +#[test] +fun test_div() { + let a = from_quotient(3, 4); + assert!(a.div(from_int(1)) == a); + + let b = from_int(8); + let c = a.div(b); + let expected = from_quotient(3, 32); + assert_eq!(c, expected); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EDivisionByZero)] +fun test_div_by_zero() { + let a = from_int(7); + let b = from_int(0); + let _ = a.div(b); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EOverflow)] +fun test_div_overflow() { + let a = from_int(1 << 63); + let b = from_quotient(1, 2); + let _ = a.div(b); +} + +#[test] +fun exact_int_div() { + let f = from_quotient(3, 4); // 0.75 + let twelve = int_div(9, f); // 9 / 0.75 + assert_eq!(twelve, 12); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EDivisionByZero)] +fun int_div_by_zero() { + let f = from_raw(0); // 0 + // Dividing by zero should cause an arithmetic error. + int_div(1, f); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EOverflow)] +fun int_div_overflow_small_divisor() { + let f = from_raw(1); // 0x0.00000001 + // Divide 2^64 by the minimum fractional value. This should overflow. + int_div(1 << 64, f); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EOverflow)] +fun int_div_overflow_large_numerator() { + let f = from_quotient(1, 2); // 0.5 + // Divide the maximum u128 value by 0.5. This should overflow. + int_div(std::u128::max_value!(), f); +} + +#[test] +fun exact_int_mul() { + let f = from_quotient(3, 4); // 0.75 + let nine = int_mul(12, f); // 12 * 0.75 + assert_eq!(nine, 9); +} + +#[test] +fun int_mul_truncates() { + let f = from_quotient(1, 3); // 0.333... + let not_three = int_mul(9, copy f); // 9 * 0.333... + // multiply_u128 does NOT round -- it truncates -- so values that + // are not perfectly representable in binary may be off by one. + assert_eq!(not_three, 2); + + // Try again with a fraction slightly larger than 1/3. + let f = from_raw(f.to_raw() + 1); + let three = int_mul(9, f); + assert_eq!(three, 3); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EOverflow)] +fun int_mul_overflow_small_multiplier() { + let f = from_quotient(3, 2); // 1.5 + // Multiply the maximum u128 value by 1.5. This should overflow. + int_mul(std::u128::max_value!(), f); +} + +#[test] +#[expected_failure(abort_code = uq64_64::EOverflow)] +fun int_mul_overflow_large_multiplier() { + let f = from_raw(std::u128::max_value!()); + // Multiply 2^64 + 1 by the maximum fixed-point value. This should overflow. + int_mul((1 << 64) + 1, f); +} + +#[test] +fun test_comparison() { + let a = from_quotient(5, 2); + let b = from_quotient(5, 3); + let c = from_quotient(5, 2); + + assert!(b.le(a)); + assert!(b.lt(a)); + assert!(c.le(a)); + assert_eq!(c, a); + assert!(a.ge(b)); + assert!(a.gt(b)); + assert!(from_int(0).le(a)); +} + +#[random_test] +fun test_raw(raw: u128) { + assert_eq!(from_raw(raw).to_raw(), raw); +} + +#[random_test] +fun test_int_roundtrip(c: u64) { + assert_eq!(from_int(c).to_int(), c); +} + +#[random_test] +fun test_mul_rand(n: u32, d: u32, c: u32) { + if (d == 0) return; + let q = from_quotient(n as u128, d as u128); + assert_eq!(int_mul(c as u128, q), q.mul(from_int(c as u64)).to_int() as u128); +} + +#[random_test] +fun test_div_rand(n: u32, d: u32, c: u32) { + if (d == 0) return; + let q = from_quotient(n as u128, d as u128); + assert_eq!(int_div(c as u128, q), from_int(c as u64).div(q).to_int() as u128); +} diff --git a/crates/sui-framework/packages_compiled/move-stdlib b/crates/sui-framework/packages_compiled/move-stdlib index 48a182f7e21f3..084b953c118af 100644 Binary files a/crates/sui-framework/packages_compiled/move-stdlib and b/crates/sui-framework/packages_compiled/move-stdlib differ diff --git a/crates/sui-framework/published_api.txt b/crates/sui-framework/published_api.txt index 8a91b732b83ee..775880de57e60 100644 --- a/crates/sui-framework/published_api.txt +++ b/crates/sui-framework/published_api.txt @@ -4357,6 +4357,54 @@ to_raw from_raw public fun 0x1::uq32_32 +UQ64_64 + public struct + 0x1::uq64_64 +from_quotient + public fun + 0x1::uq64_64 +from_int + public fun + 0x1::uq64_64 +add + public fun + 0x1::uq64_64 +sub + public fun + 0x1::uq64_64 +mul + public fun + 0x1::uq64_64 +div + public fun + 0x1::uq64_64 +to_int + public fun + 0x1::uq64_64 +int_mul + public fun + 0x1::uq64_64 +int_div + public fun + 0x1::uq64_64 +le + public fun + 0x1::uq64_64 +lt + public fun + 0x1::uq64_64 +ge + public fun + 0x1::uq64_64 +gt + public fun + 0x1::uq64_64 +to_raw + public fun + 0x1::uq64_64 +from_raw + public fun + 0x1::uq64_64 empty public fun 0x1::vector diff --git a/crates/sui-protocol-config/src/lib.rs b/crates/sui-protocol-config/src/lib.rs index 3a0eb473c3d37..8f9d9168dc967 100644 --- a/crates/sui-protocol-config/src/lib.rs +++ b/crates/sui-protocol-config/src/lib.rs @@ -201,6 +201,7 @@ const MAX_PROTOCOL_VERSION: u64 = 70; // Version 70: Enable smart ancestor selection in testnet. // Enable probing for accepted rounds in round prober in testnet // Add new gas model version to update charging of native functions. +// Add std::uq64_64 module to Move stdlib. #[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ProtocolVersion(u64); diff --git a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap index 1730c9984aa4f..1489631919972 100644 --- a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap +++ b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap @@ -240,13 +240,13 @@ validators: next_epoch_worker_address: ~ extra_fields: id: - id: "0xd95aeec428abb407baf6185f83090de23f0fbe3ab5ec5ac455dfb09b01a86823" + id: "0xcebe975bb1b8e53e6af39513515419a9a7c689693f249740e43689f6aeda32ba" size: 0 voting_power: 10000 - operation_cap_id: "0x3a9f6bbd0b886a6e35dd50bdb54bb9a1dca35b24b6ae826add15a5e9ea45c1fb" + operation_cap_id: "0x2e4b656205b19fc58169a879f3ac919d5299a471693c23c70a93e36f9ae5d4a0" gas_price: 1000 staking_pool: - id: "0x5d5d4c125492ab2dcf2068aeb8f3c1361b4d30912023aac14313ddbbb77e7d71" + id: "0x9c8b960db18d273e85a80401ca97c09253b3fd82f413d9e355afe9eab0e82858" activation_epoch: 0 deactivation_epoch: ~ sui_balance: 20000000000000000 @@ -254,14 +254,14 @@ validators: value: 0 pool_token_balance: 20000000000000000 exchange_rates: - id: "0x0383c9a5226e6811ada38b22bf838c2617f89770394bd74560a9a0fdfd94c95d" + id: "0x705442e06a6805513bbde51a9030bd4725274e8843bd4ad603afcc48fedda000" size: 1 pending_stake: 0 pending_total_sui_withdraw: 0 pending_pool_token_withdraw: 0 extra_fields: id: - id: "0xc883911bbcf25665e4336f1e024c68535cfb34177b558d84a3e7870741bfb1b2" + id: "0x4ad2829c103ec2f25d4a2d8138fba5111ad6b5289a3ecddedf5ac4e030aae877" size: 0 commission_rate: 200 next_epoch_stake: 20000000000000000 @@ -269,27 +269,27 @@ validators: next_epoch_commission_rate: 200 extra_fields: id: - id: "0x24724d158dc6771c67b1dfce287faaac19570abbc4636bd0a9c3ca84cbc386f9" + id: "0xbfb962637c22ef853d4acb95ebfb811996d6ec8a27546e3d3f9e52c5c2713e82" size: 0 pending_active_validators: contents: - id: "0x07d40f289f98cf6f567ff9faaf07a266fba60765fa14389b4b956bba44c1a95a" + id: "0xb33ed5bbf74e91ab4d04031ec1ab518e780a7c3f3a366cd192d1cd853bfbc8be" size: 0 pending_removals: [] staking_pool_mappings: - id: "0xd4fe1d15e9895507c5018cf0319d87dc43802ca4b86c1506fd643b83809cf731" + id: "0xedbabcc635a1f34d5739154e11f258cea3e8bf15115f89508c5939b4ef577291" size: 1 inactive_validators: - id: "0xeedec924bd8bbb8b71a3533270c6c59d834268f82c55941140fd0b0a5473cbed" + id: "0xb8d534ef50875b43804861d8e5dbf4a06a5f4c963cc0ae8c659c87dfdda4c43f" size: 0 validator_candidates: - id: "0x2b0f871fb557f02fa03767da3ed958431a6d368924716f3f40e442a19f50d93a" + id: "0xa112fa6e91b7ce533851360d8368830407cdc9bf3599fa243d5204d97fe42711" size: 0 at_risk_validators: contents: [] extra_fields: id: - id: "0x1f240b3a58741356c667ae3f8afc9b555951caff229481a42ae7ba2d83e020c6" + id: "0x410bd3fc4e5000f94e8b70851d0aa23cb611154e3046cb06a4d8023c39fbb70b" size: 0 storage_fund: total_object_storage_rebates: @@ -306,7 +306,7 @@ parameters: validator_low_stake_grace_period: 7 extra_fields: id: - id: "0x210b2c39cb45252263779891213f744bc7e9f41bb36f909312981339e7cdc407" + id: "0x5e42d5798306b3e67b20ead164d54956558be121660350ca2447a08422bed768" size: 0 reference_gas_price: 1000 validator_report_records: @@ -320,7 +320,7 @@ stake_subsidy: stake_subsidy_decrease_rate: 1000 extra_fields: id: - id: "0x7e868a08d450d1b4efb59ae47d11b7653bfcfb4951aa8df560c2dc91cd96af81" + id: "0xb001553979b3d2613e00064f5c8a03c94879530f7faf9e3641c86ef5d02df396" size: 0 safe_mode: false safe_mode_storage_rewards: @@ -332,6 +332,5 @@ safe_mode_non_refundable_storage_fee: 0 epoch_start_timestamp_ms: 10 extra_fields: id: - id: "0x255200b4f3d4163d381d794c0915bdd635ff52661dfc9810d07285738fef908d" + id: "0x91168b5f93153d5b24b67b76d4e0dd4553a4bca86ce596e948c0bdefe7c56715" size: 0 -