From 20219663658a3bb06797b85eafa33621866d7ab6 Mon Sep 17 00:00:00 2001 From: FabienC Date: Mon, 13 Nov 2023 19:14:47 +0200 Subject: [PATCH 1/4] feat: add wad_ray_math with tests --- src/math/src/lib.cairo | 1 + src/math/src/tests.cairo | 1 + src/math/src/tests/wad_ray_math_test.cairo | 160 +++++++++++++++++++++ src/math/src/wad_ray_math.cairo | 103 +++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 src/math/src/tests/wad_ray_math_test.cairo create mode 100644 src/math/src/wad_ray_math.cairo diff --git a/src/math/src/lib.cairo b/src/math/src/lib.cairo index 08a6e47c..d2f229e7 100644 --- a/src/math/src/lib.cairo +++ b/src/math/src/lib.cairo @@ -12,6 +12,7 @@ mod mod_arithmetics; mod perfect_number; mod sha256; mod sha512; +mod wad_ray_math; #[cfg(test)] mod tests; diff --git a/src/math/src/tests.cairo b/src/math/src/tests.cairo index a0032bc8..dfcfa481 100644 --- a/src/math/src/tests.cairo +++ b/src/math/src/tests.cairo @@ -14,3 +14,4 @@ mod sha256_test; mod sha512_test; mod test_keccak256; mod zellers_congruence_test; +mod wad_ray_math_test; diff --git a/src/math/src/tests/wad_ray_math_test.cairo b/src/math/src/tests/wad_ray_math_test.cairo new file mode 100644 index 00000000..7f3b2027 --- /dev/null +++ b/src/math/src/tests/wad_ray_math_test.cairo @@ -0,0 +1,160 @@ +use core::debug::PrintTrait; +use alexandria_math::{pow}; +use alexandria_math::wad_ray_math::{ + ray_div, ray_mul, wad_div, wad_mul, ray_to_wad, wad_to_ray, ray, wad, half_ray, half_wad +}; + +// conversion +#[test] +#[available_gas(2000000)] +fn test_wad_to_ray_conversion() { + let a = 5 * pow(10, 17); // 0.5e18 + let expected = 5 * pow(10, 26); // 0.5e27 + assert(wad_to_ray(a) == expected, 'Wrong wad_to_ray conversion'); +} + +#[test] +#[available_gas(2000000)] +fn test_ray_to_wad_conversion() { + let a = 5 * pow(10, 26); // 0.5e27 + let expected = 5 * pow(10, 17); // 0.5e18 + assert(ray_to_wad(a) == expected, 'Wrong ray_to_wad conversion'); +} + +// wad +#[test] +#[available_gas(2000000)] +#[should_panic()] +fn test_revertWhen_wad_mul_overflow() { + wad_mul(pow(2, 128), pow(2, 128)); +} + +#[test] +#[available_gas(2000000)] +fn test_wad_mul_trivial() { + assert(wad_mul(pow(2, 128) - 1, wad()) == pow(2, 128) - 1, 'Wrong result: 2**128 -1 * 1e18'); + assert(wad_mul(0, 0) == 0, 'Wrong result: 0 * 0'); + assert(wad_mul(0, wad()) == 0, 'Wrong result: 0 * 1e18'); + assert(wad_mul(wad(), 0) == 0, 'Wrong result: 1e18 * 0'); + assert(wad_mul(wad(), wad()) == wad(), 'Wrong result: 1e18 * 1e18 '); +} + +#[test] +#[available_gas(2000000)] +fn test_wad_mul_fractions() { + let val: u256 = 2 * pow(10, 17); // 0.2e18 + assert(wad_mul(wad(), val) == val, 'Wrong result: 1e18 * 0.2e18'); + assert(wad_mul(wad() * 2, val) == val * 2, 'Wrong result: 2e18 * 0.2e18'); +} + +#[test] +#[available_gas(2000000)] +#[should_panic()] +fn test_revertWhen_wad_div_zero() { + wad_div(wad(), 0); +} + +#[test] +#[available_gas(3000000)] +fn test_wad_div_trivial() { + assert(wad_div(pow(2, 128) - 1, wad()) == pow(2, 128) - 1, 'Wrong result: 2**128 -1 / 1e18'); + assert(wad_div(0, pow(2, 128) - 1) == 0, 'Wrong result: 0 / 2**128 -1'); + assert(wad_div(wad(), wad()) == wad(), 'Wrong result: 1e18 / 1e18'); +} + +#[test] +#[available_gas(2000000)] +fn test_wad_div_fractions() { + assert(wad_div(wad() * 2, wad() * 2) == wad(), 'Wrong result: 2e18 / 2e18'); + assert(wad_div(wad(), wad() * 2) == half_wad(), 'Wrong result: 1e18 / 2e18'); +} + +#[test] +#[available_gas(2000000)] +fn test_wad_mul_rounding() { + let a = 950000000000005647; + let b = 1000000000; + let expected = 950000000; + assert(wad_mul(a, b) == expected, 'Wrong rounding down: a * b'); + assert(wad_mul(b, a) == expected, 'Wrong rounding down: b * a'); +} + +#[test] +#[available_gas(2000000)] +fn test_wad_mul_rounding_up() { + let a = pow(10, 18) - 1; + let b = 2; + let expected = 2; + assert(wad_mul(a, b) == expected, 'Wrong rounding: a * b'); + assert(wad_mul(b, a) == expected, 'Wrong rounding: b * a'); +} + + +// wad +#[test] +#[available_gas(2000000)] +#[should_panic()] +fn test_revertWhen_ray_mul_overflow() { + ray_mul(pow(2, 128), pow(2, 128)); +} + +#[test] +#[available_gas(2000000)] +fn test_ray_mul_trivial() { + assert(ray_mul(pow(2, 128) - 1, ray()) == pow(2, 128) - 1, 'Wrong result: 2**128 -1 * 1e27'); + assert(ray_mul(0, 0) == 0, 'Wrong result: 0 * 0'); + assert(ray_mul(0, ray()) == 0, 'Wrong result: 0 * 1e27'); + assert(ray_mul(ray(), 0) == 0, 'Wrong result: 1e27 * 0'); + assert(ray_mul(ray(), ray()) == ray(), 'Wrong result: 1e27 * 1e27 '); +} + +#[test] +#[available_gas(2000000)] +fn test_ray_mul_fractions() { + let val: u256 = 2 * pow(10, 26); // 0.2e27 + assert(ray_mul(ray(), val) == val, 'Wrong result: 1e27 * 0.2e27'); + assert(ray_mul(ray() * 2, val) == val * 2, 'Wrong result: 2e27 * 0.2e27'); +} + +#[test] +#[available_gas(2000000)] +#[should_panic()] +fn test_revertWhen_ray_div_zero() { + ray_div(ray(), 0); +} + +#[test] +#[available_gas(3000000)] +fn test_ray_div_trivial() { + assert(ray_div(pow(2, 128) - 1, ray()) == pow(2, 128) - 1, 'Wrong result: 2**128 -1 / 1e27'); + assert(ray_div(0, pow(2, 128) - 1) == 0, 'Wrong result: 0 / 2**128 -1'); + assert(ray_div(ray(), ray()) == ray(), 'Wrong result: 1e27 / 1e27'); +} + +#[test] +#[available_gas(2000000)] +fn test_ray_div_fractions() { + assert(ray_div(ray() * 2, ray() * 2) == ray(), 'Wrong result: 2e27 / 2e27'); + assert(ray_div(ray(), ray() * 2) == half_ray(), 'Wrong result: 1e27 / 2e27'); +} + +#[test] +#[available_gas(2000000)] +fn test_ray_mul_rounding() { + let a = pow(10, 18); + let b = 95 * pow(10, 26) + 5647; + let expected = 95 * pow(10, 17); + assert(ray_mul(a, b) == expected, 'Wrong rounding down: a * b'); + assert(ray_mul(b, a) == expected, 'Wrong rounding down: b * a'); +} + + +#[test] +#[available_gas(2000000)] +fn test_ray_mul_rounding_up() { + let a = pow(10, 27) - 1; + let b = 2; + let expected = 2; + assert(ray_mul(a, b) == expected, 'Wrong rounding up: a * b'); + assert(ray_mul(b, a) == expected, 'Wrong rounding up: b * a'); +} diff --git a/src/math/src/wad_ray_math.cairo b/src/math/src/wad_ray_math.cairo new file mode 100644 index 00000000..71b0dc4e --- /dev/null +++ b/src/math/src/wad_ray_math.cairo @@ -0,0 +1,103 @@ +/// Provides functions to perform calculations with Wad and Ray units +/// @dev Provides mul and div function for wads (decimal numbers with 18 digits of precision) and rays (decimal numbers +/// with 27 digits of precision) +/// Operations are rounded. If a value is >=.5, will be rounded up, otherwise rounded down. +/// https://github.com/aave/aave-v3-core/blob/master/contracts/protocol/libraries/math/WadRayMath.sol + +const WAD: u256 = 1_000_000_000_000_000_000; // 1e18 +const HALF_WAD: u256 = 500_000_000_000_000_000; // 0.5e18 +const RAY: u256 = 1_000_000_000_000_000_000_000_000_000; // 1e27 +const HALF_RAY: u256 = 500_000_000_000_000_000_000_000_000; // 0.5e27 +const WAD_RAY_RATIO: u256 = 1_000_000_000; // 1e9 + + +/// Return the wad value +/// # Returns +/// * `u256` - The value +fn wad() -> u256 { + return WAD; +} + +/// Return the ray value +/// # Returns +/// * `u256` - The value +fn ray() -> u256 { + return RAY; +} + +/// Return the half wad value +/// # Returns +/// * `u256` - The value +fn half_wad() -> u256 { + return HALF_WAD; +} + +/// Return the half ray value +/// # Returns +/// * `u256` - The value +fn half_ray() -> u256 { + return HALF_RAY; +} + + +/// Multiplies two wad, rounding half up to the nearest wad +/// # Arguments +/// * a Wad +/// * b Wad +/// # Returns +/// * a*b, in wad +fn wad_mul(a: u256, b: u256) -> u256 { + return (a * b + HALF_WAD) / WAD; +} + +/// Divides two wad, rounding half up to the nearest wad +/// # Arguments +/// * a Wad +/// * b Wad +/// # Returns +/// * a/b, in wad +fn wad_div(a: u256, b: u256) -> u256 { + let half_b = b / 2; + return (a * WAD + half_b) / b; +} + +/// Multiplies two ray, rounding half up to the nearest ray +/// # Arguments +/// * a Ray +/// * b Ray +/// # Returns +/// * a raymul b +fn ray_mul(a: u256, b: u256) -> u256 { + return (a * b + HALF_RAY) / RAY; +} + +/// Divides two ray, rounding half up to the nearest ray +/// # Arguments +/// * a Ray +/// * b Ray +/// # Returns +/// * a raydiv b +fn ray_div(a: u256, b: u256) -> u256 { + let half_b = b / 2; + return (a * RAY + half_b) / b; +} + +/// Casts ray down to wad +/// # Arguments +/// * a Ray +/// # Returns +/// * a converted to wad, rounded half up to the nearest wad +fn ray_to_wad(a: u256) -> u256 { + let half_ratio = WAD_RAY_RATIO / 2; + return (half_ratio + a) / WAD_RAY_RATIO; +} + +/// Converts wad up to ray +/// # Arguments +/// * a Wad +/// # Returns +/// * a converted to ray +fn wad_to_ray(a: u256) -> u256 { + return a * WAD_RAY_RATIO; +} + From 3f0c2b6dfed9b657af5eef9d39ffda5c0337699f Mon Sep 17 00:00:00 2001 From: FabienC Date: Wed, 15 Nov 2023 09:10:05 +0200 Subject: [PATCH 2/4] fix: format --- src/math/src/lib.cairo | 2 +- src/math/src/tests.cairo | 2 +- src/math/src/tests/wad_ray_math_test.cairo | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/math/src/lib.cairo b/src/math/src/lib.cairo index d2f229e7..26f2dd87 100644 --- a/src/math/src/lib.cairo +++ b/src/math/src/lib.cairo @@ -12,10 +12,10 @@ mod mod_arithmetics; mod perfect_number; mod sha256; mod sha512; -mod wad_ray_math; #[cfg(test)] mod tests; +mod wad_ray_math; mod zellers_congruence; use integer::{ u8_wide_mul, u16_wide_mul, u32_wide_mul, u64_wide_mul, u128_wide_mul, u256_overflow_mul, diff --git a/src/math/src/tests.cairo b/src/math/src/tests.cairo index dfcfa481..0c2b2511 100644 --- a/src/math/src/tests.cairo +++ b/src/math/src/tests.cairo @@ -13,5 +13,5 @@ mod perfect_number_test; mod sha256_test; mod sha512_test; mod test_keccak256; -mod zellers_congruence_test; mod wad_ray_math_test; +mod zellers_congruence_test; diff --git a/src/math/src/tests/wad_ray_math_test.cairo b/src/math/src/tests/wad_ray_math_test.cairo index 7f3b2027..d96e3ab3 100644 --- a/src/math/src/tests/wad_ray_math_test.cairo +++ b/src/math/src/tests/wad_ray_math_test.cairo @@ -1,8 +1,7 @@ -use core::debug::PrintTrait; -use alexandria_math::{pow}; use alexandria_math::wad_ray_math::{ ray_div, ray_mul, wad_div, wad_mul, ray_to_wad, wad_to_ray, ray, wad, half_ray, half_wad }; +use alexandria_math::{pow}; // conversion #[test] From 934001dec820ea1f9f28444871df0b818227e2bd Mon Sep 17 00:00:00 2001 From: FabienC Date: Wed, 15 Nov 2023 09:10:31 +0200 Subject: [PATCH 3/4] refactor: inline half calculation --- src/math/src/wad_ray_math.cairo | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/math/src/wad_ray_math.cairo b/src/math/src/wad_ray_math.cairo index 71b0dc4e..2de12d19 100644 --- a/src/math/src/wad_ray_math.cairo +++ b/src/math/src/wad_ray_math.cairo @@ -57,8 +57,7 @@ fn wad_mul(a: u256, b: u256) -> u256 { /// # Returns /// * a/b, in wad fn wad_div(a: u256, b: u256) -> u256 { - let half_b = b / 2; - return (a * WAD + half_b) / b; + return (a * WAD + (b / 2)) / b; } /// Multiplies two ray, rounding half up to the nearest ray @@ -78,8 +77,7 @@ fn ray_mul(a: u256, b: u256) -> u256 { /// # Returns /// * a raydiv b fn ray_div(a: u256, b: u256) -> u256 { - let half_b = b / 2; - return (a * RAY + half_b) / b; + return (a * RAY + (b / 2)) / b; } /// Casts ray down to wad @@ -88,8 +86,7 @@ fn ray_div(a: u256, b: u256) -> u256 { /// # Returns /// * a converted to wad, rounded half up to the nearest wad fn ray_to_wad(a: u256) -> u256 { - let half_ratio = WAD_RAY_RATIO / 2; - return (half_ratio + a) / WAD_RAY_RATIO; + return ((WAD_RAY_RATIO / 2) + a) / WAD_RAY_RATIO; } /// Converts wad up to ray From d7da4ec961aa90e4958d43ed855179fe2a0e4faf Mon Sep 17 00:00:00 2001 From: FabienC Date: Wed, 15 Nov 2023 09:14:53 +0200 Subject: [PATCH 4/4] refactor: hardcode HALF_WAD_RAY_RATIO --- src/math/src/wad_ray_math.cairo | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/math/src/wad_ray_math.cairo b/src/math/src/wad_ray_math.cairo index 2de12d19..94a51565 100644 --- a/src/math/src/wad_ray_math.cairo +++ b/src/math/src/wad_ray_math.cairo @@ -9,6 +9,7 @@ const HALF_WAD: u256 = 500_000_000_000_000_000; // 0.5e18 const RAY: u256 = 1_000_000_000_000_000_000_000_000_000; // 1e27 const HALF_RAY: u256 = 500_000_000_000_000_000_000_000_000; // 0.5e27 const WAD_RAY_RATIO: u256 = 1_000_000_000; // 1e9 +const HALF_WAD_RAY_RATIO: u256 = 500_000_000; // 0.5e9 /// Return the wad value @@ -86,7 +87,7 @@ fn ray_div(a: u256, b: u256) -> u256 { /// # Returns /// * a converted to wad, rounded half up to the nearest wad fn ray_to_wad(a: u256) -> u256 { - return ((WAD_RAY_RATIO / 2) + a) / WAD_RAY_RATIO; + return (HALF_WAD_RAY_RATIO + a) / WAD_RAY_RATIO; } /// Converts wad up to ray