From 85295ce647cbada024abb49801e49386bee15a23 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Wed, 27 Sep 2023 12:43:02 +0200 Subject: [PATCH 01/28] exp function, no tested (failed setting up runner) --- src/tests/utils/test_precision.cairo | 7 + src/utils/precision.cairo | 250 +++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) diff --git a/src/tests/utils/test_precision.cairo b/src/tests/utils/test_precision.cairo index 5aba0379..9cf81af8 100644 --- a/src/tests/utils/test_precision.cairo +++ b/src/tests/utils/test_precision.cairo @@ -83,6 +83,13 @@ fn test_mul_div_inum_roundup_positive() { assert(result == 53, 'should be 53.'); } +#[test] +fn test_exp2() { + let value: u256 = 3; + let result = precision::exp2(value); + assert(result == 8, "should be 8"); +} + #[test] fn test_to_factor_roundup() { let value: u128 = 450000; diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 80664a66..d1b28836 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -170,6 +170,256 @@ fn apply_exponent_factor(float_value: u128, exponent_factor: u128) -> u128 { // 0 } +//use starknet::cairo::common::cairo_builtins::bitwise_and; +//use starknet::{*}; +use alexandria_math::BitShift; +fn exp2(x: u256) -> u256 { + //what is the cairo equivalent of `unchecked` in solidity? + + // Start from 0.5 in the 192.64-bit fixed-point format. + let mut result = 0x800000000000000000000000000000000000000000000000; + + // The following logic multiplies the result by $\sqrt{2^{-i}}$ when the bit at position i is 1. Key points: + // + // 1. Intermediate results will not overflow, as the starting point is 2^191 and all magic factors are under 2^65. + // 2. The rationale for organizing the if statements into groups of 8 is gas savings. If the result of performing + // a bitwise AND operation between x and any value in the array [0x80; 0x40; 0x20; 0x10; 0x08; 0x04; 0x02; 0x01] is 1, + // we know that `x & 0xFF` is also 1. + + if (x & 0xFF00000000000000 > 0) { + result = BitShift::shl(result * 0x16A09E667F3BCC909, 64); //shl or shr? + } + + if (x & 0xFF00000000000000 > 0) { + if (x & 0x8000000000000000 > 0) { + result = BitShift::shl(result * 0x16A09E667F3BCC909, 64); + } + if (x & 0x4000000000000000 > 0) { + result = BitShift::shl(result * 0x1306FE0A31B7152DF, 64); + } + if (x & 0x2000000000000000 > 0) { + result = BitShift::shl(result * 0x1172B83C7D517ADCE, 64); + } + if (x & 0x1000000000000000 > 0) { + result = BitShift::shl(result * 0x10B5586CF9890F62A, 64); + } + if (x & 0x800000000000000 > 0) { + result = BitShift::shl(result * 0x1059B0D31585743AE, 64); + } + if (x & 0x400000000000000 > 0) { + result = BitShift::shl(result * 0x102C9A3E778060EE7, 64); + } + if (x & 0x200000000000000 > 0) { + result = BitShift::shl(result * 0x10163DA9FB33356D8, 64); + } + if (x & 0x100000000000000 > 0) { + result = BitShift::shl(result * 0x100B1AFA5ABCBED61, 64); + } + } + + if (x & 0xFF000000000000 > 0) { + if (x & 0x80000000000000 > 0) { + result = BitShift::shl(result * 0x10058C86DA1C09EA2, 64); + } + if (x & 0x40000000000000 > 0) { + result = BitShift::shl(result * 0x1002C605E2E8CEC50, 64); + } + if (x & 0x20000000000000 > 0) { + result = BitShift::shl(result * 0x100162F3904051FA1, 64); + } + if (x & 0x10000000000000 > 0) { + result = BitShift::shl(result * 0x1000B175EFFDC76BA, 64); + } + if (x & 0x8000000000000 > 0) { + result = BitShift::shl(result * 0x100058BA01FB9F96D, 64); + } + if (x & 0x4000000000000 > 0) { + result = BitShift::shl(result * 0x10002C5CC37DA9492, 64); + } + if (x & 0x2000000000000 > 0) { + result = BitShift::shl(result * 0x1000162E525EE0547, 64); + } + if (x & 0x1000000000000 > 0) { + result = BitShift::shl(result * 0x10000B17255775C04, 64); + } + } + + if (x & 0xFF0000000000 > 0) { + if (x & 0x800000000000 > 0) { + result = BitShift::shl(result * 0x1000058B91B5BC9AE, 64); + } + if (x & 0x400000000000 > 0) { + result = BitShift::shl(result * 0x100002C5C89D5EC6D, 64); + } + if (x & 0x200000000000 > 0) { + result = BitShift::shl(result * 0x10000162E43F4F831, 64); + } + if (x & 0x100000000000 > 0) { + result = BitShift::shl(result * 0x100000B1721BCFC9A, 64); + } + if (x & 0x80000000000 > 0) { + result = BitShift::shl(result * 0x10000058B90CF1E6E, 64); + } + if (x & 0x40000000000 > 0) { + result = BitShift::shl(result * 0x1000002C5C863B73F, 64); + } + if (x & 0x20000000000 > 0) { + result = BitShift::shl(result * 0x100000162E430E5A2, 64); + } + if (x & 0x10000000000 > 0) { + result = BitShift::shl(result * 0x1000000B172183551, 64); + } + } + + if (x & 0xFF00000000 > 0) { + if (x & 0x8000000000 > 0) { + result = BitShift::shl(result * 0x100000058B90C0B49, 64); + } + if (x & 0x4000000000 > 0) { + result = BitShift::shl(result * 0x10000002C5C8601CC, 64); + } + if (x & 0x2000000000 > 0) { + result = BitShift::shl(result * 0x1000000162E42FFF0, 64); + } + if (x & 0x1000000000 > 0) { + result = BitShift::shl(result * 0x10000000B17217FBB, 64); + } + if (x & 0x800000000 > 0) { + result = BitShift::shl(result * 0x1000000058B90BFCE, 64); + } + if (x & 0x400000000 > 0) { + result = BitShift::shl(result * 0x100000002C5C85FE3, 64); + } + if (x & 0x200000000 > 0) { + result = BitShift::shl(result * 0x10000000162E42FF1, 64); + } + if (x & 0x100000000 > 0) { + result = BitShift::shl(result * 0x100000000B17217F8, 64); + } + } + + if (x & 0xFF000000 > 0) { + if (x & 0x80000000 > 0) { + result = BitShift::shl(result * 0x10000000058B90BFC, 64); + } + if (x & 0x40000000 > 0) { + result = BitShift::shl(result * 0x1000000002C5C85FE, 64); + } + if (x & 0x20000000 > 0) { + result = BitShift::shl(result * 0x100000000162E42FF, 64); + } + if (x & 0x10000000 > 0) { + result = BitShift::shl(result * 0x1000000000B17217F, 64); + } + if (x & 0x8000000 > 0) { + result = BitShift::shl(result * 0x100000000058B90C0, 64); + } + if (x & 0x4000000 > 0) { + result = BitShift::shl(result * 0x10000000002C5C860, 64); + } + if (x & 0x2000000 > 0) { + result = BitShift::shl(result * 0x1000000000162E430, 64); + } + if (x & 0x1000000 > 0) { + result = BitShift::shl(result * 0x10000000000B17218, 64); + } + } + + if (x & 0xFF0000 > 0) { + if (x & 0x800000 > 0) { + result = BitShift::shl(result * 0x1000000000058B90C, 64); + } + if (x & 0x400000 > 0) { + result = BitShift::shl(result * 0x100000000002C5C86, 64); + } + if (x & 0x200000 > 0) { + result = BitShift::shl(result * 0x10000000000162E43, 64); + } + if (x & 0x100000 > 0) { + result = BitShift::shl(result * 0x100000000000B1721, 64); + } + if (x & 0x80000 > 0) { + result = BitShift::shl(result * 0x10000000000058B91, 64); + } + if (x & 0x40000 > 0) { + result = BitShift::shl(result * 0x1000000000002C5C8, 64); + } + if (x & 0x20000 > 0) { + result = BitShift::shl(result * 0x100000000000162E4, 64); + } + if (x & 0x10000 > 0) { + result = BitShift::shl(result * 0x1000000000000B172, 64); + } + } + + if (x & 0xFF00 > 0) { + if (x & 0x8000 > 0) { + result = BitShift::shl(result * 0x100000000000058B9, 64); + } + if (x & 0x4000 > 0) { + result = BitShift::shl(result * 0x10000000000002C5D, 64); + } + if (x & 0x2000 > 0) { + result = BitShift::shl(result * 0x1000000000000162E, 64); + } + if (x & 0x1000 > 0) { + result = BitShift::shl(result * 0x10000000000000B17, 64); + } + if (x & 0x800 > 0) { + result = BitShift::shl(result * 0x1000000000000058C, 64); + } + if (x & 0x400 > 0) { + result = BitShift::shl(result * 0x100000000000002C6, 64); + } + if (x & 0x200 > 0) { + result = BitShift::shl(result * 0x10000000000000163, 64); + } + if (x & 0x100 > 0) { + result = BitShift::shl(result * 0x100000000000000B1, 64); + } + } + + if (x & 0xFF > 0) { + if (x & 0x80 > 0) { + result = BitShift::shl(result * 0x10000000000000059, 64); + } + if (x & 0x40 > 0) { + result = BitShift::shl(result * 0x1000000000000002C, 64); + } + if (x & 0x20 > 0) { + result = BitShift::shl(result * 0x10000000000000016, 64); + } + if (x & 0x10 > 0) { + result = BitShift::shl(result * 0x1000000000000000B, 64); + } + if (x & 0x8 > 0) { + result = BitShift::shl(result * 0x10000000000000006, 64); + } + if (x & 0x4 > 0) { + result = BitShift::shl(result * 0x10000000000000003, 64); + } + if (x & 0x2 > 0) { + result = BitShift::shl(result * 0x10000000000000001, 64); + } + if (x & 0x1 > 0) { + result = BitShift::shl(result * 0x10000000000000001, 64); + } + } + + // In the code snippet below, two operations are executed simultaneously: + // + // 1. The result is multiplied by $(2^n + 1)$, where $2^n$ represents the integer part, and the additional 1 + // accounts for the initial guess of 0.5. This is achieved by subtracting from 191 instead of 192. + // 2. The result is then converted to an unsigned 60.18-decimal fixed-point format. + // + // The underlying logic is based on the relationship $2^{191-ip} = 2^{ip} / 2^{191}$, where $ip$ denotes the, + // integer part, $2^n$. + + result *= 1000000000000000000; + result = BitShift::shl(191, BitShift::shl(x, 64)); + result +} + /// Compute factor from value and divisor with a roundup. /// # Arguments /// * `value` - The value to compute the factor. From 0eb97fdc6ea9e6dba56d4cf8f38f9f6ef3c2a9d6 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Wed, 27 Sep 2023 13:10:22 +0200 Subject: [PATCH 02/28] not working :( --- src/tests/utils/test_precision.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/utils/test_precision.cairo b/src/tests/utils/test_precision.cairo index 9cf81af8..b02b4903 100644 --- a/src/tests/utils/test_precision.cairo +++ b/src/tests/utils/test_precision.cairo @@ -87,7 +87,7 @@ fn test_mul_div_inum_roundup_positive() { fn test_exp2() { let value: u256 = 3; let result = precision::exp2(value); - assert(result == 8, "should be 8"); + assert(result == 8, 'should be 8'); } #[test] From 62afb49d688f06493443a8fd0a8463e36e6f9c00 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Wed, 27 Sep 2023 13:22:10 +0200 Subject: [PATCH 03/28] error comes from 1e18? --- src/utils/precision.cairo | 134 +++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index e33bd780..0d72598e 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -187,222 +187,222 @@ fn exp2(x: u256) -> u256 { // we know that `x & 0xFF` is also 1. if (x & 0xFF00000000000000 > 0) { - result = BitShift::shl(result * 0x16A09E667F3BCC909, 64); //shl or shr? + result = BitShift::shr(result * 0x16A09E667F3BCC909, 64); //shr or shr? } if (x & 0xFF00000000000000 > 0) { if (x & 0x8000000000000000 > 0) { - result = BitShift::shl(result * 0x16A09E667F3BCC909, 64); + result = BitShift::shr(result * 0x16A09E667F3BCC909, 64); } if (x & 0x4000000000000000 > 0) { - result = BitShift::shl(result * 0x1306FE0A31B7152DF, 64); + result = BitShift::shr(result * 0x1306FE0A31B7152DF, 64); } if (x & 0x2000000000000000 > 0) { - result = BitShift::shl(result * 0x1172B83C7D517ADCE, 64); + result = BitShift::shr(result * 0x1172B83C7D517ADCE, 64); } if (x & 0x1000000000000000 > 0) { - result = BitShift::shl(result * 0x10B5586CF9890F62A, 64); + result = BitShift::shr(result * 0x10B5586CF9890F62A, 64); } if (x & 0x800000000000000 > 0) { - result = BitShift::shl(result * 0x1059B0D31585743AE, 64); + result = BitShift::shr(result * 0x1059B0D31585743AE, 64); } if (x & 0x400000000000000 > 0) { - result = BitShift::shl(result * 0x102C9A3E778060EE7, 64); + result = BitShift::shr(result * 0x102C9A3E778060EE7, 64); } if (x & 0x200000000000000 > 0) { - result = BitShift::shl(result * 0x10163DA9FB33356D8, 64); + result = BitShift::shr(result * 0x10163DA9FB33356D8, 64); } if (x & 0x100000000000000 > 0) { - result = BitShift::shl(result * 0x100B1AFA5ABCBED61, 64); + result = BitShift::shr(result * 0x100B1AFA5ABCBED61, 64); } } if (x & 0xFF000000000000 > 0) { if (x & 0x80000000000000 > 0) { - result = BitShift::shl(result * 0x10058C86DA1C09EA2, 64); + result = BitShift::shr(result * 0x10058C86DA1C09EA2, 64); } if (x & 0x40000000000000 > 0) { - result = BitShift::shl(result * 0x1002C605E2E8CEC50, 64); + result = BitShift::shr(result * 0x1002C605E2E8CEC50, 64); } if (x & 0x20000000000000 > 0) { - result = BitShift::shl(result * 0x100162F3904051FA1, 64); + result = BitShift::shr(result * 0x100162F3904051FA1, 64); } if (x & 0x10000000000000 > 0) { - result = BitShift::shl(result * 0x1000B175EFFDC76BA, 64); + result = BitShift::shr(result * 0x1000B175EFFDC76BA, 64); } if (x & 0x8000000000000 > 0) { - result = BitShift::shl(result * 0x100058BA01FB9F96D, 64); + result = BitShift::shr(result * 0x100058BA01FB9F96D, 64); } if (x & 0x4000000000000 > 0) { - result = BitShift::shl(result * 0x10002C5CC37DA9492, 64); + result = BitShift::shr(result * 0x10002C5CC37DA9492, 64); } if (x & 0x2000000000000 > 0) { - result = BitShift::shl(result * 0x1000162E525EE0547, 64); + result = BitShift::shr(result * 0x1000162E525EE0547, 64); } if (x & 0x1000000000000 > 0) { - result = BitShift::shl(result * 0x10000B17255775C04, 64); + result = BitShift::shr(result * 0x10000B17255775C04, 64); } } if (x & 0xFF0000000000 > 0) { if (x & 0x800000000000 > 0) { - result = BitShift::shl(result * 0x1000058B91B5BC9AE, 64); + result = BitShift::shr(result * 0x1000058B91B5BC9AE, 64); } if (x & 0x400000000000 > 0) { - result = BitShift::shl(result * 0x100002C5C89D5EC6D, 64); + result = BitShift::shr(result * 0x100002C5C89D5EC6D, 64); } if (x & 0x200000000000 > 0) { - result = BitShift::shl(result * 0x10000162E43F4F831, 64); + result = BitShift::shr(result * 0x10000162E43F4F831, 64); } if (x & 0x100000000000 > 0) { - result = BitShift::shl(result * 0x100000B1721BCFC9A, 64); + result = BitShift::shr(result * 0x100000B1721BCFC9A, 64); } if (x & 0x80000000000 > 0) { - result = BitShift::shl(result * 0x10000058B90CF1E6E, 64); + result = BitShift::shr(result * 0x10000058B90CF1E6E, 64); } if (x & 0x40000000000 > 0) { - result = BitShift::shl(result * 0x1000002C5C863B73F, 64); + result = BitShift::shr(result * 0x1000002C5C863B73F, 64); } if (x & 0x20000000000 > 0) { - result = BitShift::shl(result * 0x100000162E430E5A2, 64); + result = BitShift::shr(result * 0x100000162E430E5A2, 64); } if (x & 0x10000000000 > 0) { - result = BitShift::shl(result * 0x1000000B172183551, 64); + result = BitShift::shr(result * 0x1000000B172183551, 64); } } if (x & 0xFF00000000 > 0) { if (x & 0x8000000000 > 0) { - result = BitShift::shl(result * 0x100000058B90C0B49, 64); + result = BitShift::shr(result * 0x100000058B90C0B49, 64); } if (x & 0x4000000000 > 0) { - result = BitShift::shl(result * 0x10000002C5C8601CC, 64); + result = BitShift::shr(result * 0x10000002C5C8601CC, 64); } if (x & 0x2000000000 > 0) { - result = BitShift::shl(result * 0x1000000162E42FFF0, 64); + result = BitShift::shr(result * 0x1000000162E42FFF0, 64); } if (x & 0x1000000000 > 0) { - result = BitShift::shl(result * 0x10000000B17217FBB, 64); + result = BitShift::shr(result * 0x10000000B17217FBB, 64); } if (x & 0x800000000 > 0) { - result = BitShift::shl(result * 0x1000000058B90BFCE, 64); + result = BitShift::shr(result * 0x1000000058B90BFCE, 64); } if (x & 0x400000000 > 0) { - result = BitShift::shl(result * 0x100000002C5C85FE3, 64); + result = BitShift::shr(result * 0x100000002C5C85FE3, 64); } if (x & 0x200000000 > 0) { - result = BitShift::shl(result * 0x10000000162E42FF1, 64); + result = BitShift::shr(result * 0x10000000162E42FF1, 64); } if (x & 0x100000000 > 0) { - result = BitShift::shl(result * 0x100000000B17217F8, 64); + result = BitShift::shr(result * 0x100000000B17217F8, 64); } } if (x & 0xFF000000 > 0) { if (x & 0x80000000 > 0) { - result = BitShift::shl(result * 0x10000000058B90BFC, 64); + result = BitShift::shr(result * 0x10000000058B90BFC, 64); } if (x & 0x40000000 > 0) { - result = BitShift::shl(result * 0x1000000002C5C85FE, 64); + result = BitShift::shr(result * 0x1000000002C5C85FE, 64); } if (x & 0x20000000 > 0) { - result = BitShift::shl(result * 0x100000000162E42FF, 64); + result = BitShift::shr(result * 0x100000000162E42FF, 64); } if (x & 0x10000000 > 0) { - result = BitShift::shl(result * 0x1000000000B17217F, 64); + result = BitShift::shr(result * 0x1000000000B17217F, 64); } if (x & 0x8000000 > 0) { - result = BitShift::shl(result * 0x100000000058B90C0, 64); + result = BitShift::shr(result * 0x100000000058B90C0, 64); } if (x & 0x4000000 > 0) { - result = BitShift::shl(result * 0x10000000002C5C860, 64); + result = BitShift::shr(result * 0x10000000002C5C860, 64); } if (x & 0x2000000 > 0) { - result = BitShift::shl(result * 0x1000000000162E430, 64); + result = BitShift::shr(result * 0x1000000000162E430, 64); } if (x & 0x1000000 > 0) { - result = BitShift::shl(result * 0x10000000000B17218, 64); + result = BitShift::shr(result * 0x10000000000B17218, 64); } } if (x & 0xFF0000 > 0) { if (x & 0x800000 > 0) { - result = BitShift::shl(result * 0x1000000000058B90C, 64); + result = BitShift::shr(result * 0x1000000000058B90C, 64); } if (x & 0x400000 > 0) { - result = BitShift::shl(result * 0x100000000002C5C86, 64); + result = BitShift::shr(result * 0x100000000002C5C86, 64); } if (x & 0x200000 > 0) { - result = BitShift::shl(result * 0x10000000000162E43, 64); + result = BitShift::shr(result * 0x10000000000162E43, 64); } if (x & 0x100000 > 0) { - result = BitShift::shl(result * 0x100000000000B1721, 64); + result = BitShift::shr(result * 0x100000000000B1721, 64); } if (x & 0x80000 > 0) { - result = BitShift::shl(result * 0x10000000000058B91, 64); + result = BitShift::shr(result * 0x10000000000058B91, 64); } if (x & 0x40000 > 0) { - result = BitShift::shl(result * 0x1000000000002C5C8, 64); + result = BitShift::shr(result * 0x1000000000002C5C8, 64); } if (x & 0x20000 > 0) { - result = BitShift::shl(result * 0x100000000000162E4, 64); + result = BitShift::shr(result * 0x100000000000162E4, 64); } if (x & 0x10000 > 0) { - result = BitShift::shl(result * 0x1000000000000B172, 64); + result = BitShift::shr(result * 0x1000000000000B172, 64); } } if (x & 0xFF00 > 0) { if (x & 0x8000 > 0) { - result = BitShift::shl(result * 0x100000000000058B9, 64); + result = BitShift::shr(result * 0x100000000000058B9, 64); } if (x & 0x4000 > 0) { - result = BitShift::shl(result * 0x10000000000002C5D, 64); + result = BitShift::shr(result * 0x10000000000002C5D, 64); } if (x & 0x2000 > 0) { - result = BitShift::shl(result * 0x1000000000000162E, 64); + result = BitShift::shr(result * 0x1000000000000162E, 64); } if (x & 0x1000 > 0) { - result = BitShift::shl(result * 0x10000000000000B17, 64); + result = BitShift::shr(result * 0x10000000000000B17, 64); } if (x & 0x800 > 0) { - result = BitShift::shl(result * 0x1000000000000058C, 64); + result = BitShift::shr(result * 0x1000000000000058C, 64); } if (x & 0x400 > 0) { - result = BitShift::shl(result * 0x100000000000002C6, 64); + result = BitShift::shr(result * 0x100000000000002C6, 64); } if (x & 0x200 > 0) { - result = BitShift::shl(result * 0x10000000000000163, 64); + result = BitShift::shr(result * 0x10000000000000163, 64); } if (x & 0x100 > 0) { - result = BitShift::shl(result * 0x100000000000000B1, 64); + result = BitShift::shr(result * 0x100000000000000B1, 64); } } if (x & 0xFF > 0) { if (x & 0x80 > 0) { - result = BitShift::shl(result * 0x10000000000000059, 64); + result = BitShift::shr(result * 0x10000000000000059, 64); } if (x & 0x40 > 0) { - result = BitShift::shl(result * 0x1000000000000002C, 64); + result = BitShift::shr(result * 0x1000000000000002C, 64); } if (x & 0x20 > 0) { - result = BitShift::shl(result * 0x10000000000000016, 64); + result = BitShift::shr(result * 0x10000000000000016, 64); } if (x & 0x10 > 0) { - result = BitShift::shl(result * 0x1000000000000000B, 64); + result = BitShift::shr(result * 0x1000000000000000B, 64); } if (x & 0x8 > 0) { - result = BitShift::shl(result * 0x10000000000000006, 64); + result = BitShift::shr(result * 0x10000000000000006, 64); } if (x & 0x4 > 0) { - result = BitShift::shl(result * 0x10000000000000003, 64); + result = BitShift::shr(result * 0x10000000000000003, 64); } if (x & 0x2 > 0) { - result = BitShift::shl(result * 0x10000000000000001, 64); + result = BitShift::shr(result * 0x10000000000000001, 64); } if (x & 0x1 > 0) { - result = BitShift::shl(result * 0x10000000000000001, 64); + result = BitShift::shr(result * 0x10000000000000001, 64); } } @@ -415,8 +415,8 @@ fn exp2(x: u256) -> u256 { // The underlying logic is based on the relationship $2^{191-ip} = 2^{ip} / 2^{191}$, where $ip$ denotes the, // integer part, $2^n$. - result *= 1000000000000000000; - result = BitShift::shl(191, BitShift::shl(x, 64)); + result *= 1000000000000000000; //maybe error comes from here? + result = BitShift::shr(191, BitShift::shr(x, 64)); result } From d9a71fd1645790f1521bb7e6ecfab60b72f40919 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Tue, 3 Oct 2023 13:43:14 +0200 Subject: [PATCH 04/28] exp2 working git add . --- src/tests/utils/test_precision.cairo | 14 +++++++++++--- src/utils/precision.cairo | 11 +++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/tests/utils/test_precision.cairo b/src/tests/utils/test_precision.cairo index b02b4903..9cfa247c 100644 --- a/src/tests/utils/test_precision.cairo +++ b/src/tests/utils/test_precision.cairo @@ -85,9 +85,17 @@ fn test_mul_div_inum_roundup_positive() { #[test] fn test_exp2() { - let value: u256 = 3; - let result = precision::exp2(value); - assert(result == 8, 'should be 8'); + let value1: u256 = 2000000000000000000; + let value2: u256 = 2500000000000000000; + let value3: u256 = 7482948646372839484; + + let result1 = precision::exp2(value1); + let result2 = precision::exp2(value2); + let result3 = precision::exp2(value3); + + assert(result1 == 4000000000000000000, 'should be 4000000000000000000'); + assert(result2 == 5656854249492380195, 'should be 5656854249492380195'); + assert(result3 == 178892444495791357043, 'should be 178892444495791357043'); } #[test] diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 0d72598e..700b330d 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -173,7 +173,10 @@ fn apply_exponent_factor(float_value: u128, exponent_factor: u128) -> u128 { // //use starknet::cairo::common::cairo_builtins::bitwise_and; //use starknet::{*}; use alexandria_math::BitShift; -fn exp2(x: u256) -> u256 { + +fn exp2(mut x: u256) -> u256 { + x = BitShift::shl(x, 64); + x = x / 1000000000000000000; //what is the cairo equivalent of `unchecked` in solidity? // Start from 0.5 in the 192.64-bit fixed-point format. @@ -186,10 +189,6 @@ fn exp2(x: u256) -> u256 { // a bitwise AND operation between x and any value in the array [0x80; 0x40; 0x20; 0x10; 0x08; 0x04; 0x02; 0x01] is 1, // we know that `x & 0xFF` is also 1. - if (x & 0xFF00000000000000 > 0) { - result = BitShift::shr(result * 0x16A09E667F3BCC909, 64); //shr or shr? - } - if (x & 0xFF00000000000000 > 0) { if (x & 0x8000000000000000 > 0) { result = BitShift::shr(result * 0x16A09E667F3BCC909, 64); @@ -416,7 +415,7 @@ fn exp2(x: u256) -> u256 { // integer part, $2^n$. result *= 1000000000000000000; //maybe error comes from here? - result = BitShift::shr(191, BitShift::shr(x, 64)); + result = BitShift::shr(result, 191 - BitShift::shr(x, 64)); result } From 46a85aa8cdb462174539998b4cb56a71d99283fb Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Thu, 5 Oct 2023 13:02:58 +0200 Subject: [PATCH 05/28] exp is working ! --- src/utils/precision.cairo | 11 +++++++++++ tests/utils/test_precision.cairo | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index f41fca17..227ac299 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -175,6 +175,10 @@ fn apply_exponent_factor(float_value: u128, exponent_factor: u128) -> u128 { // use alexandria_math::BitShift; fn exp2(mut x: u256) -> u256 { + let EXP2_MAX_INPUT = 192 * 1000000000000000000 - 1; + if x > EXP2_MAX_INPUT { + panic("error"); + } x = BitShift::shl(x, 64); x = x / 1000000000000000000; //what is the cairo equivalent of `unchecked` in solidity? @@ -419,6 +423,13 @@ fn exp2(mut x: u256) -> u256 { result } +fn exp(x: u256) -> u256 { + //check if x is not too big, but it's already checked in exp 2? + let uLOG2_E = 1_442695040888963407; + let double_unit_product = x * uLOG2_E; + exp2(double_unit_product/1000000000000000000) +} + /// Compute factor from value and divisor with a roundup. /// # Arguments /// * `value` - The value to compute the factor. diff --git a/tests/utils/test_precision.cairo b/tests/utils/test_precision.cairo index 7d8867db..c7af2957 100644 --- a/tests/utils/test_precision.cairo +++ b/tests/utils/test_precision.cairo @@ -98,6 +98,29 @@ fn test_exp2() { assert(result3 == 178892444495791357043, 'should be 178892444495791357043'); } +#[test] +fn test_exp() { + let value1: u256 = 2000000000000000000; + let value2: u256 = 2500000000000000000; + let value3: u256 = 7482948646372839484; + let value4: u256 = 0000000000000000000; + let value5: u256 = 1000000000000000000; + + + let result1 = precision::exp(value1); + let result2 = precision::exp(value2); + let result3 = precision::exp(value3); + let result4 = precision::exp(value4); + let result5 = precision::exp(value5); + + + assert(result1 == 7389056098930650223, 'should be 7389056098930650223'); + assert(result2 == 12182493960703473424, 'should be 12182493960703473424'); + assert(result3 == 1777474199233404337144, 'should_1777474199233404337144'); + assert(result4 == 1000000000000000000, 'should be 1000000000000000000'); + assert(result5 == 2718281828459045234, 'should be 2718281828459045234'); +} + #[test] fn test_to_factor_roundup() { let value: u128 = 450000; From 7edd22d45693ccc774a44d089310c0c661c12438 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Thu, 5 Oct 2023 15:28:44 +0200 Subject: [PATCH 06/28] fmt --- src/utils/precision.cairo | 2 +- tests/utils/test_precision.cairo | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 227ac299..e237865e 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -427,7 +427,7 @@ fn exp(x: u256) -> u256 { //check if x is not too big, but it's already checked in exp 2? let uLOG2_E = 1_442695040888963407; let double_unit_product = x * uLOG2_E; - exp2(double_unit_product/1000000000000000000) + exp2(double_unit_product / 1000000000000000000) } /// Compute factor from value and divisor with a roundup. diff --git a/tests/utils/test_precision.cairo b/tests/utils/test_precision.cairo index c7af2957..b8684378 100644 --- a/tests/utils/test_precision.cairo +++ b/tests/utils/test_precision.cairo @@ -106,14 +106,12 @@ fn test_exp() { let value4: u256 = 0000000000000000000; let value5: u256 = 1000000000000000000; - let result1 = precision::exp(value1); let result2 = precision::exp(value2); let result3 = precision::exp(value3); let result4 = precision::exp(value4); let result5 = precision::exp(value5); - assert(result1 == 7389056098930650223, 'should be 7389056098930650223'); assert(result2 == 12182493960703473424, 'should be 12182493960703473424'); assert(result3 == 1777474199233404337144, 'should_1777474199233404337144'); From 4b6d21427c134f8824c7a9fbebcaa70efaa18244 Mon Sep 17 00:00:00 2001 From: Tbelleng <117627242+Tbelleng@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:34:45 +0000 Subject: [PATCH 07/28] Feat: Implement the function in the market_utils library #3 (#463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 10 functions done * almost finished, debug next * debug time * debuging * pushing recent changes/ still bug because missing functions * debuging finished * adding comments on functions * almost clean * Emit bug * programm compile 🎉 * resolving last test * All test passed * resolve request * 1 test failed because of max swap path lenght exceed test * resolving failed test * resolve * solving * compilation resolved --------- Co-authored-by: Michel <105498726+Sk8erboi84@users.noreply.github.com> --- src/event/event_emitter.cairo | 1 - src/market/error.cairo | 61 +- src/market/market_utils.cairo | 1413 ++++++++++++++++-------- src/order/increase_order_utils.cairo | 2 +- src/swap/swap_utils.cairo | 2 +- src/withdrawal/withdrawal_utils.cairo | 5 +- tests/deposit/test_deposit_utils.cairo | 99 +- tests/market/test_market_utils.cairo | 2 +- 8 files changed, 1122 insertions(+), 463 deletions(-) diff --git a/src/event/event_emitter.cairo b/src/event/event_emitter.cairo index 1c629b08..4b232baa 100755 --- a/src/event/event_emitter.cairo +++ b/src/event/event_emitter.cairo @@ -560,7 +560,6 @@ trait IEventEmitter { next_pool_value: u128 ); - /// Emits the `UiFeeFactorUpdated` event. fn emit_ui_fee_factor_updated( ref self: TContractState, account: ContractAddress, ui_fee_factor: u128 ); diff --git a/src/market/error.cairo b/src/market/error.cairo index c5325189..e5ae3fa0 100644 --- a/src/market/error.cairo +++ b/src/market/error.cairo @@ -11,18 +11,69 @@ mod MarketError { 'empty_addr_market_balance_val'; const EMPTY_ADDRESS_TOKEN_BALANCE_VAL: felt252 = 'empty_addr_token_balance_val'; const INVALID_MARKET_TOKEN_BALANCE: felt252 = 'invalid_market_token_balance'; - const INVALID_MARKET_TOKEN_BALANCE_FOR_COLLATERAL_AMOUNT: felt252 = - 'invalid_mkt_tok_bal_collat_amnt'; - const INVALID_MARKET_TOKEN_BALANCE_FOR_CLAIMABLE_FUNDING: felt252 = - 'invalid_mkt_tok_bal_claim_fund'; const EmptyAddressInMarketTokenBalanceValidation: felt252 = 'EmptyAddressMarketBalanceVal'; const INVALID_POSITION_MARKET: felt252 = 'invalid_position_market'; const INVALID_COLLATERAL_TOKEN_FOR_MARKET: felt252 = 'invalid_coll_token_for_market'; const UNABLE_TO_GET_OPPOSITE_TOKEN: felt252 = 'unable_to_get_opposite_token'; const EMPTY_MARKET: felt252 = 'empty_market'; - const DISABLED_MARKET: felt252 = 'disabled_market'; const COLLATERAL_ALREADY_CLAIMED: felt252 = 'collateral_already_claimed'; + fn DISABLED_MARKET(is_market_disabled: bool) { + panic(array!['minimum_position_size', is_market_disabled.into()]) + } + + fn EMPTY_MARKET_TOKEN_SUPPLY(supply: u128) { + panic(array!['empty_market_token_supply', supply.into()]) + } + + fn INVALID_MARKET_COLLATERAL_TOKEN(market: ContractAddress, token: ContractAddress) { + panic(array!['invalid_market_collateral_token', market.into(), token.into()]) + } + + fn UNABLE_TO_GET_FUNDING_FACTOR_EMPTY_OPEN_INTEREST(total_open_interest: u128) { + panic(array!['unable_to_get_funding_factor', total_open_interest.into()]) + } + + fn MAX_SWAP_PATH_LENGTH_EXCEEDED(token_swap_path_length: u32, max_swap_path_length: u128) { + panic( + array![ + 'max_swap_path_length_exceeded', + token_swap_path_length.into(), + max_swap_path_length.into() + ] + ) + } + + fn PNL_EXCEEDED_FOR_LONGS(is_pnl_factor_exceeded_for_longs: bool) { + panic(array!['pnl_exceeded_for_longs', is_pnl_factor_exceeded_for_longs.into()]) + } + + fn PNL_EXCEEDED_FOR_SHORTS(is_pnl_factor_exceeded_for_shorts: bool) { + panic(array!['pnl_exceeded_for_shorts', is_pnl_factor_exceeded_for_shorts.into()]) + } + + fn UI_FEE_FACTOR_EXCEEDED(ui_fee_factor: u128, max_ui_fee_factor: u128) { + panic(array!['ui_fee_factor_exceeded', ui_fee_factor.into(), max_ui_fee_factor.into()]) + } + + fn INVALID_MARKET_TOKEN_BALANCE_FOR_COLLATERAL_AMOUNT(balance: u128, collateral_amount: u128) { + panic(array!['invalid_market_token_balance', balance.into(), collateral_amount.into()]) + } + + fn INVALID_MARKET_TOKEN_BALANCE_FOR_CLAIMABLE_FUNDING( + balance: u128, claimable_funding_fee_amount: u128 + ) { + panic( + array![ + 'invalid_market_token_balance', balance.into(), claimable_funding_fee_amount.into() + ] + ) + } + + fn UNABLE_TO_GET_BORROWING_FACTOR_EMPTY_POOL_USD(pool_usd: u128) { + panic(array!['unable_to_get_borrowing_factor', pool_usd.into()]) + } + fn MAX_OPEN_INTEREST_EXCEDEED(open_interest: u128, max_open_interest: u128) { panic(array!['max_open_interest_exceeded', open_interest.into(), max_open_interest.into()]) } diff --git a/src/market/market_utils.cairo b/src/market/market_utils.cairo index 61fe6d37..c5e04b08 100644 --- a/src/market/market_utils.cairo +++ b/src/market/market_utils.cairo @@ -2,13 +2,14 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::{ContractAddress, get_block_timestamp}; +use starknet::{ContractAddress, get_caller_address, get_block_timestamp, contract_address_const}; // Local imports. use satoru::utils::calc::roundup_magnitude_division; use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::chain::chain::{IChainDispatcher, IChainDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use satoru::chain::chain::Chain; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::data::keys; @@ -17,20 +18,21 @@ use satoru::market::{ market::Market, error::MarketError, market_pool_value_info::MarketPoolValueInfo, market_store_utils, market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait} }; +use satoru::utils::span32::{Span32, Span32Trait}; use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; use satoru::oracle::oracle::{Oracle, SetPricesParams}; use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; use satoru::price::price::{Price, PriceTrait}; use satoru::utils::calc; -use satoru::utils::span32::Span32; use satoru::utils::precision::{FLOAT_PRECISION, FLOAT_PRECISION_SQRT}; use satoru::utils::precision::{mul_div_roundup, to_factor_ival, apply_factor_u128, to_factor}; use satoru::utils::precision; -use satoru::utils::calc::{roundup_division, to_signed, sum_return_int_128}; +use satoru::utils::calc::{roundup_division, to_signed, sum_return_int_128, to_unsigned}; use satoru::position::position::Position; use integer::u128_to_felt252; use satoru::utils::{i128::{I128Store, I128Serde, I128Div, I128Mul, I128Default}, error_utils}; -use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::utils::precision::{apply_exponent_factor, float_to_wei, mul_div}; +use satoru::data::keys::{skip_borrowing_fee_for_smaller_side, max_swap_path_length}; /// Struct to store the prices of tokens of a market. /// # Params @@ -1102,18 +1104,18 @@ fn get_next_funding_amount_per_size( let open_interest = PositionType { long: CollateralType { - long_token: get_open_interest( + long_token: get_open_interest_div( data_store, market.market_token, market.long_token, true, divisor ), - short_token: get_open_interest( + short_token: get_open_interest_div( data_store, market.market_token, market.short_token, true, divisor ), }, short: CollateralType { - long_token: get_open_interest( + long_token: get_open_interest_div( data_store, market.market_token, market.long_token, false, divisor ), - short_token: get_open_interest( + short_token: get_open_interest_div( data_store, market.market_token, market.short_token, false, divisor ), }, @@ -1285,9 +1287,6 @@ fn get_next_funding_amount_per_size( result } - -/////////////////////////////////////////////////////////////////////// - fn get_swap_impact_amount_with_cap( data_store: IDataStoreDispatcher, market: ContractAddress, @@ -1317,14 +1316,7 @@ fn get_swap_impact_amount_with_cap( impact_amount } -/// Get the long and short open interest for a market based on the collateral token used. -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market to get the open interest for. -/// * `collateral_token` - The collateral token to check. -/// * `is_long` - Whether to get the long or short open interest. -/// * `divisor` - The divisor to use for the open interest. -fn get_open_interest( +fn get_open_interest_div( data_store: IDataStoreDispatcher, market: ContractAddress, collateral_token: ContractAddress, @@ -1363,11 +1355,11 @@ fn get_open_interest_for_market_is_long( // Get the pool divisor. let divisor = get_pool_divisor(*market.long_token, *market.short_token); // Get the open interest for the long token as collateral. - let open_interest_using_long_token_as_collateral = get_open_interest( + let open_interest_using_long_token_as_collateral = get_open_interest_div( data_store, *market.market_token, *market.long_token, is_long, divisor ); // Get the open interest for the short token as collateral. - let open_interest_using_short_token_as_collateral = get_open_interest( + let open_interest_using_short_token_as_collateral = get_open_interest_div( data_store, *market.market_token, *market.short_token, is_long, divisor ); // Return the sum of the open interests. @@ -1438,16 +1430,6 @@ fn get_pool_divisor(long_token: ContractAddress, short_token: ContractAddress) - } } -/// Validates the swap path to ensure each market in the path is valid and the path length does not -// exceed the maximum allowed length. -/// # Arguments -/// * `data_store` - The DataStore contract containing platform configuration. -/// * `swap_path` - A vector of market addresses forming the swap path. -fn validate_swap_path( - data_store: IDataStoreDispatcher, token_swap_path: Span32 -) { //TODO -} - /// Update the swap impact pool amount, if it is a positive impact amount /// cap the impact amount to the amount available in the swap impact pool /// # Arguments @@ -1534,18 +1516,6 @@ fn validate_open_interest(data_store: IDataStoreDispatcher, market: @Market, is_ } } -// Get the min pnl factor after ADL -// Parameters -// * `data_store` - - The data store to use. -// * `market` - the market to check. -// * `is_long` whether to check the long or short side. -fn get_min_pnl_factor_after_adl( - data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool -) -> u128 { - // TODO - 0 -} - // Get the ratio of pnl to pool value. // # Arguments // * `data_store` - The data_store dispatcher. @@ -1573,14 +1543,12 @@ fn get_pnl_to_pool_factor( } /// Get the ratio of PNL (Profit and Loss) to pool value. -/// /// # Arguments /// * `dataStore`: DataStore - The data storage instance. /// * `market`: Market values. /// * `prices`: Prices of the market tokens. /// * `isLong`: Whether to get the value for the long or short side. /// * `maximize`: Whether to maximize the factor. -/// /// # Returns /// Returns the ratio of PNL of positions to long or short pool value. fn get_pnl_to_pool_factor_from_prices( @@ -1598,25 +1566,6 @@ fn get_pnl_to_pool_factor_from_prices( return to_factor_ival(pnl, pool_usd); } -// Check if the pending pnl exceeds the allowed amount -// # Arguments -// * `data_store` - The data_store dispatcher. -// * `oracle` - The oracle dispatcher. -// * `market` - The market to check. -// * `prices` - The prices of the market tokens. -// * `is_long` - Whether to check the long or short side. -// * `pnl_factor_type` - The pnl factor type to check. -fn is_pnl_factor_exceeded( - data_store: IDataStoreDispatcher, - oracle: IOracleDispatcher, - market_address: ContractAddress, - is_long: bool, - pnl_factor_type: felt252 -) -> (bool, i128, u128) { - // TODO - (true, 0, 0) -} - // Check if the pending pnl exceeds the allowed amount // # Arguments // * `data_store` - The data_store dispatcher. @@ -1634,58 +1583,6 @@ fn is_pnl_factor_exceeded_direct( (true, 0, 0) } -fn get_ui_fee_factor(data_store: IDataStoreDispatcher, account: ContractAddress) -> u128 { - let max_ui_fee_factor = data_store.get_u128(keys::max_ui_fee_factor()); - let ui_fee_factor = data_store.get_u128(keys::ui_fee_factor_key(account)); - if ui_fee_factor < max_ui_fee_factor { - ui_fee_factor - } else { - max_ui_fee_factor - } -} - -/// Gets the enabled market. This function will revert if the market does not exist or is not enabled. -/// # Arguments -/// * `dataStore` - DataStore -/// * `marketAddress` - The address of the market. -fn get_enabled_market(data_store: IDataStoreDispatcher, market_address: ContractAddress) -> Market { - //TODO - Market { - market_token: Zeroable::zero(), - index_token: Zeroable::zero(), - long_token: Zeroable::zero(), - short_token: Zeroable::zero(), - } -} - - -/// Get the cumulative borrowing factor for a market -/// # Arguments -/// * `data_store` DataStore -/// * `market` the market to check -/// * `is_long` whether to check the long or short side -/// # Returns -// The cumulative borrowing factor for a market -fn get_cumulative_borrowing_factor( - data_store: @IDataStoreDispatcher, market: ContractAddress, is_long: bool -) -> u128 { - (*data_store).get_u128(keys::cumulative_borrowing_factor_key(market, is_long)) -} - -/// Validates that the pending pnl is below the allowed amount. -/// # Arguments -/// * `dataStore` - DataStore -/// * `market` - The market to check -/// * `prices` - The prices of the market tokens -/// * `pnlFactorType` - The pnl factor type to check -fn validate_max_pnl( - data_store: IDataStoreDispatcher, - market: Market, - prices: @MarketPrices, - pnl_factor_type_for_longs: felt252, - pnl_factor_type_for_shorts: felt252, -) { //TODO -} /// Validates the token balance for a single market. /// # Arguments @@ -1696,46 +1593,9 @@ fn validate_market_token_balance_with_address( ) { //TODO } -fn validate_market_token_balance(data_store: IDataStoreDispatcher, market: Market) { //TODO -} - fn validate_markets_token_balance(data_store: IDataStoreDispatcher, market: Span) { //TODO } -/// Validate that the positions can be opened in the given market -/// # Parameters -/// * `data_store`: dispatcher for the data store -/// * `market`: the market to check -fn validate_position_market(data_store: IDataStoreDispatcher, market: Market) {} // TODO - -/// Gets a list of market values based on an input array of market addresses. -/// # Parameters -/// * `swap_path`: A list of market addresses. -fn get_swap_path_markets( - data_store: IDataStoreDispatcher, swap_path: Span32 -) -> Array { //TODO - Default::default() -} - -/// Validata that the specified market exists and is enabled -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market to validate. -fn validate_enabled_market(data_store: IDataStoreDispatcher, market: Market) { - assert(!market.market_token.is_zero(), MarketError::EMPTY_MARKET); - let is_market_disabled = data_store.get_bool(keys::is_market_disabled_key(market.market_token)); - - match is_market_disabled { - Option::Some(result) => { - assert(!result, MarketError::DISABLED_MARKET); - }, - Option::None => { - panic_with_felt252(MarketError::DISABLED_MARKET); - } - }; -} - - /// Validata that the specified market exists and is enabled /// # Arguments /// * `data_store` - The data store to use. @@ -1745,59 +1605,6 @@ fn validate_enabled_market_address( ) { // TODO } -// Check if the given token is a collateral token of the market -// # Arguments -// * `market` - the market to check -// * `token` - the token to check -fn is_market_collateral_token(market: Market, token: ContractAddress) -> bool { - token == market.long_token || token == market.short_token -} - -/// Validata if the given token is a collateral token of the market -/// # Arguments -/// * `market` - The market to validate. -/// * `token` - The token to check -fn validate_market_collateral_token(market: Market, token: ContractAddress) { - if !is_market_collateral_token(market, token) { - panic_with_felt252(MarketError::INVALID_COLLATERAL_TOKEN_FOR_MARKET) - } -} - -/// Get the max position impact factor for liquidations -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market. -fn get_max_position_impact_factor_for_liquidations( - data_store: IDataStoreDispatcher, market: ContractAddress -) -> u128 { - // TODOs - 0 -} - -/// Get the min collateral factor -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market. -fn get_min_collateral_factor(data_store: IDataStoreDispatcher, market: ContractAddress) -> u128 { - // TODOs - 0 -} - - -/// Get the min collateral factor for open interest -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market. -/// * `open_interest_delta` - The change in open interest. -/// * `is_long` - Whether it is for the long or short side -fn get_min_collateral_factor_for_open_interest( - data_store: IDataStoreDispatcher, market: Market, open_interest_delta: i128, is_long: bool -) -> u128 { - // TODOs - 0 -} - - /// Update the cumulative borrowing factor for a market /// # Arguments /// * `data_store` - The data store to use. @@ -1825,38 +1632,6 @@ fn update_cumulative_borrowing_factor( ); } -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market. -/// * `is_long` - Whether to update the long or short side. -/// * `prev_position_size_in_usd` - The previous position size in USD. -/// * `prev_position_borrowing_factor` - The previous position borrowing factor. -/// * `next_position_size_in_usd` - The next position size in USD. -/// * `next_position_borrowing_factor` - The next position borrowing factor. -fn update_total_borrowing( - data_store: IDataStoreDispatcher, - market: ContractAddress, - is_long: bool, - prev_position_size_in_usd: u128, - prev_position_borrowing_factor: u128, - next_position_size_in_usd: u128, - next_position_borrowing_factor: u128 -) { // TODO -} - -/// Converts a number of market tokens to its USD value. -/// # Arguments -/// * `market_token_amount` - The input number of market tokens. -/// * `pool_value` - The value of the pool. -/// * `supply` - The supply of market tokens. -/// # Returns -/// The USD value of the market tokens. -fn market_token_amount_to_usd( - market_token_amount: u128, pool_value: u128, supply: u128 -) -> u128 { // TODO - 0 -} - /// Get the virtual inventory for positions. /// /// # Arguments @@ -1875,21 +1650,6 @@ fn get_virtual_inventory_for_positions( return (true, data_store.get_i128(keys::virtual_inventory_for_positions_key(virtual_token_id))); } -/// Get the borrowing factor per second. -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market. -/// * `prices` - The prices of the market tokens. -/// * `is_long` - Whether to get the factor for the long or short side -/// # Returns -/// The borrowing factor per second. -fn get_borrowing_factor_per_second( - data_store: IDataStoreDispatcher, market: Market, prices: MarketPrices, is_long: bool -) -> u128 { - // TODO - 0 -} - // store funding values as token amount per (Precision.FLOAT_PRECISION_SQRT / Precision.FLOAT_PRECISION) of USD size fn get_funding_amount_per_size_delta( funding_usd: u128, open_interest: u128, token_price: u128, roundup_magnitude: bool @@ -2055,58 +1815,6 @@ fn apply_delta_to_virtual_inventory_for_positions( return (true, next_value); } -/// Get the next cumulative borrowing factor. -/// -/// # Arguments -/// * `dataStore`: DataStore - The data storage instance. -/// * `prices`: Prices of the market tokens. -/// * `market`: The market to check. -/// * `longToken`: The long token of the market. -/// * `shortToken`: The short token of the market. -/// * `isLong`: Whether to check the long or short side. -/// -/// # Returns -/// Returns a tuple (cumulative_borrowing_factor, updated_timestamp). -fn get_next_cumulative_borrowing_factor( - data_store: IDataStoreDispatcher, market: Market, prices: MarketPrices, is_long: bool -) -> (u128, u128) { // TODO - (0, 0) -} - -/// Increase the cumulative borrowing factor. -/// -/// # Arguments -/// * `dataStore`: DataStore - The data storage instance. -/// * `eventEmitter`: EventEmitter - The event emitter. -/// * `market`: The market to increment the borrowing factor for. -/// * `isLong`: Whether to increment the long or short side. -/// * `delta`: The increase amount. -fn increment_cumulative_borrowing_factor( - data_store: IDataStoreDispatcher, - event_emitter: IEventEmitterDispatcher, - market: ContractAddress, - is_long: bool, - delta: u128 -) { - () -} - -/// Get the reserve factor for a market. -/// -/// # Arguments -/// * `dataStore`: DataStore - The data storage instance. -/// * `market`: The market to check. -/// * `isLong`: Whether to get the value for longs or shorts. -/// -/// # Returns -/// Returns the reserve factor for a market. -fn get_reserve_factor( - data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool -) -> u128 { - Default::default() -} - - /// Get the borrowing fees for a position, assumes that cumulativeBorrowingFactor /// has already been updated to the latest value /// # Arguments @@ -2128,42 +1836,6 @@ fn get_borrowing_fees(data_store: IDataStoreDispatcher, position: @Position) -> return apply_factor_u128(*position.size_in_usd, diff_factor); } - -/// Get the funding fee amount per size for a market -/// # Arguments -/// * `dataStore` - DataStore -/// * `market` - the market to check -/// * `collateral_token` - the collateralToken to check -/// * `is_long` - whether to check the long or short size -/// # Returns -/// The funding fee amount per size for a market based on collateralToken -fn get_funding_fee_amount_per_size( - dataStore: IDataStoreDispatcher, - market: ContractAddress, - collateral_token: ContractAddress, - is_long: bool -) -> u128 { - 0 -} - -/// Get the claimable funding amount per size for a market -/// # Arguments -/// * `dataStore` - DataStore -/// * `market` - the market to check -/// * `collateral_token` - the collateralToken to check -/// * `is_long` - whether to check the long or short size -/// # Returns -/// The claimable funding amount per size for a market based on collateralToken -fn get_claimable_funding_amount_per_size( - dataStore: IDataStoreDispatcher, - market: ContractAddress, - collateral_token: ContractAddress, - is_long: bool -) -> u128 { - 0 -} - - /// Get the funding amount to be deducted or distributed /// # Arguments /// * `latestFundingAmountPerSize` - the latest funding amount per size @@ -2233,36 +1905,6 @@ fn get_virtual_inventory_for_swaps( ); } -/// Get the total pending borrowing fees -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market to check. -/// * `prices` - The prices of the market tokens. -/// * `is_long` - Whether to check the long or short side. -fn get_total_pending_borrowing_fees( - data_store: IDataStoreDispatcher, market: Market, prices: MarketPrices, is_long: bool -) -> u128 { - // TODO - 0 -} - -fn get_max_pnl_factor( - data_store: IDataStoreDispatcher, - pnl_factor_type: felt252, - market: ContractAddress, - is_long: bool -) -> u128 { - // TODO - 0 -} - -fn get_max_position_impact_factor( - data_store: IDataStoreDispatcher, market: ContractAddress, foo: bool -) -> u128 { - // TODO - 0 -} - fn apply_delta_to_funding_fee_amount_per_size( data_store: IDataStoreDispatcher, event_emitter: IEventEmitterDispatcher, @@ -2270,108 +1912,993 @@ fn apply_delta_to_funding_fee_amount_per_size( collateral_token: ContractAddress, is_long: bool, delta: u128 -) { // TODO -} - -fn apply_delta_to_claimable_funding_amount_per_size( - data_store: IDataStoreDispatcher, - event_emitter: IEventEmitterDispatcher, - market: ContractAddress, - collateral_token: ContractAddress, - is_long: bool, - delta: u128 -) { // TODO +) { + if delta == 0 { + return; + } + let delta = to_signed(delta, true); + let next_value: u128 = data_store + .apply_delta_to_u128( + keys::funding_fee_amount_per_size_key(market, collateral_token, is_long), + delta, + 'negative_funding_fee' + ); + let delta = to_unsigned(delta); + event_emitter + .emit_funding_fee_amount_per_size_updated( + market, collateral_token, is_long, delta, next_value + ); } -fn get_seconds_since_funding_updated( - data_store: IDataStoreDispatcher, market: ContractAddress +// Get the max position impact factor +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// `is_positive` - whether to check the positive or negative side +// # Returns +// The max position impact factor +fn get_max_position_impact_factor( + data_store: IDataStoreDispatcher, market: ContractAddress, is_positive: bool, ) -> u128 { - // TODO - 0 -} + let (max_positive_impact_factor, max_negative_impact_factor) = get_max_position_impact_factors( + data_store, market + ); -fn get_funding_factor_per_second( - data_store: IDataStoreDispatcher, + if is_positive { + max_positive_impact_factor + } else { + max_negative_impact_factor + } +} + +fn get_max_position_impact_factors( + data_store: IDataStoreDispatcher, market: ContractAddress, +) -> (u128, u128) { + let mut max_positive_impact_factor: u128 = data_store + .get_u128(keys::max_position_impact_factor_key(market, true)); + let max_negative_impact_factor: u128 = data_store + .get_u128(keys::max_position_impact_factor_key(market, false)); + + if max_positive_impact_factor > max_negative_impact_factor { + max_positive_impact_factor = max_negative_impact_factor; + } + + (max_positive_impact_factor, max_negative_impact_factor) +} + +// Get the max position impact factor for liquidations +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// # Returns +// The max position impact factor for liquidations +fn get_max_position_impact_factor_for_liquidations( + data_store: IDataStoreDispatcher, market: ContractAddress +) -> u128 { + data_store.get_u128(keys::max_position_impact_factor_for_liquidations_key(market)) +} + +// Get the min collateral factor +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// # Returns +// The min collateral factor +fn get_min_collateral_factor(data_store: IDataStoreDispatcher, market: ContractAddress) -> u128 { + data_store.get_u128(keys::min_collateral_factor_key(market)) +} + +// Get the min collateral factor for open interest multiplier +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// # Returns +// The min collateral factor for open interest multiplier +fn get_min_collateral_factor_for_open_interest_multiplier( + data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool +) -> u128 { + data_store + .get_u128(keys::min_collateral_factor_for_open_interest_multiplier_key(market, is_long)) +} + +// Get the min collateral factor for open interest +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// `open_interest_delta` - the delta in open interest +// `is_long` - whether to check the long or short side +// # Returns +// The min collateral factor for open interest +fn get_min_collateral_factor_for_open_interest( + data_store: IDataStoreDispatcher, market: Market, open_interest_delta: i128, is_long: bool +) -> u128 { + let mut open_interest: u128 = get_open_interest_for_market_is_long( + data_store, @market, is_long + ); + open_interest = calc::sum_return_uint_128(open_interest, open_interest_delta); + let multiplier_factor = get_min_collateral_factor_for_open_interest_multiplier( + data_store, market.market_token, is_long + ); + apply_factor_u128(open_interest, multiplier_factor) +} + +// Get the total amount of position collateral for a market +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// 'collateral_token' - the collateral token to check +// `is_long` - whether to check the long or short side +// # Returns +// the total amount of position collateral for a market +fn get_collateral_sum( + data_store: IDataStoreDispatcher, market: ContractAddress, - diff_usd: u128, - total_open_interest: u128 + collateral_token: ContractAddress, + is_long: bool, + divisor: u128 ) -> u128 { - // TODO - 0 + error_utils::check_division_by_zero(divisor, 'get_collaral_sum'); + data_store.get_u128(keys::collateral_sum_key(market, collateral_token, is_long)) / divisor } -// @dev get the open interest reserve factor for a market -// @param dataStore DataStore -// @param market the market to check -// @param isLong whether to get the value for longs or shorts -// @return the open interest reserve factor for a market +// Get the reserve factor for a market +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// # Returns +// The reserve factor for a market +fn get_reserve_factor( + data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool +) -> u128 { + data_store.get_u128(keys::reserve_factor_key(market, is_long)) +} + +// Get open interest reserve factor +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// # Returns +// The open interest reserve factor fn get_open_interest_reserve_factor( data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool ) -> u128 { - Default::default() + data_store.get_u128(keys::open_interest_reserve_factor_key(market, is_long)) } +// Get the max pnl factor +// # Arguments +// `data_store` - the data store to use +// `pnl_factor_type` the type of the pnl factor +// `market` - the market to check +// `is_long` - whether to check the long or short side +// # Returns +// The max pnl factor +fn get_max_pnl_factor( + data_store: IDataStoreDispatcher, + pnl_factor_type: felt252, + market: ContractAddress, + is_long: bool +) -> u128 { + data_store.get_u128(keys::max_pnl_factor_key(pnl_factor_type, market, is_long)) +} -/// Validate that the specified market exists and is enabled -/// # Arguments -/// * `data_store` - The `DataStore` contract dispatcher. -/// * `market` - The address of the market -fn validate_enable_market(data_store: IDataStoreDispatcher, market: Market) { - 0; +// Get the min pnl factor after Adl +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// # Returns +// The min pnl factor after adl +fn get_min_pnl_factor_after_adl( + data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool +) -> u128 { + data_store.get_u128(keys::min_pnl_factor_after_adl_key(market, is_long)) } -/// Check if the market is valid -/// # Arguments -/// * `data_store` - The `DataStore` contract dispatcher. -/// * `market` - The market -fn validate_market_token_balance_market(data_store: IDataStoreDispatcher, market: Market) { - validate_market_token_balance_token(data_store, market, market.long_token); +// Get the funding factor for a market +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// # Returns +// the funding factor for a market +fn get_funding_factor(data_store: IDataStoreDispatcher, market: ContractAddress) -> u128 { + data_store.get_u128(keys::funding_factor_key(market)) +} - if (market.long_token == market.short_token) { +// Get the funding exponent factor for a market +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// # Returns +// the funding exponent factor for a market +fn get_funding_exponent_factor(data_store: IDataStoreDispatcher, market: ContractAddress) -> u128 { + data_store.get_u128(keys::funding_exponent_factor_key(market)) +} + +// Get the funding fee amount per size for a market +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// `collateral_token` - the collateral token to check +// `is_long` - whether to check the long or short side +// # Returns +// the funding fee amount per size for a market +fn get_funding_fee_amount_per_size( + data_store: IDataStoreDispatcher, + market: ContractAddress, + collateral_token: ContractAddress, + is_long: bool +) -> u128 { + data_store.get_u128(keys::funding_fee_amount_per_size_key(market, collateral_token, is_long)) +} + +// Get the claimable funding fee amount per size for a market +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// `collateral_token` - the collateral token to check +// `is_long` - whether to check the long or short side +// # Returns +// the claimable funding fee amount per size for a market +fn get_claimable_funding_amount_per_size( + data_store: IDataStoreDispatcher, + market: ContractAddress, + collateral_token: ContractAddress, + is_long: bool +) -> u128 { + data_store + .get_u128(keys::claimable_funding_amount_per_size_key(market, collateral_token, is_long)) +} + +// Apply delta to the funding fee amount per size for a market +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// `collateral_token` - the collateral token to check +// `is_long` - whether to check the long or short side +// `delta` - the delta to increment by +fn apply_delta_to_funding_fee_per_size( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + market: ContractAddress, + collateral_token: ContractAddress, + is_long: bool, + delta: u128 +) { + if delta == 0 { + return; + } + let error: felt252 = 0; + let delta = to_signed(delta, true); + let next_value: u128 = data_store + .apply_delta_to_u128( + keys::funding_fee_amount_per_size_key(market, collateral_token, is_long), + delta, + error //Error doesnt exist on solidity function, i just added it because of the merge of Library #1 + ); + let delta = to_unsigned(delta); + event_emitter + .emit_funding_fee_amount_per_size_updated( + market, collateral_token, is_long, delta, next_value + ); +} + +// Apply delta to the claimable funding fee amount per size for a market +// # Arguments +// `data_store` - the data store to use +// `market` - the market to check +// `collateral_token` - the collateral token to check +// `is_long` - whether to check the long or short side +// `delta` - the delta to increment by +fn apply_delta_to_claimable_funding_amount_per_size( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + market: ContractAddress, + collateral_token: ContractAddress, + is_long: bool, + delta: u128 +) { + if delta == 0 { return; } + let next_value: u128 = data_store + .apply_delta_to_u128( + keys::claimable_funding_amount_per_size_key(market, collateral_token, is_long), + to_signed(delta, true), + 0 + ); + event_emitter + .emit_claimable_funding_amount_per_size_updated( + market, collateral_token, is_long, delta, next_value + ); +} - validate_market_token_balance_token(data_store, market, market.short_token); +// Get the number of seconds since funding was updated for a market +// `data_store` - the data store to use +// `market` - the market to check +// # Returns +// the number of seconds since funding was updated for a market +fn get_seconds_since_funding_updated( + data_store: IDataStoreDispatcher, market: ContractAddress +) -> u128 { + //Error on this one but its normal the function is not create yet + let updated_at: u128 = data_store.get_u128(keys::funding_updated_at_key(market)); + if (updated_at == 0) { + return 0; + } + let block_time_stamp = starknet::info::get_block_timestamp().into(); + block_time_stamp - updated_at } -/// Validate that market is valid for the token -/// # Arguments -/// * `data_store` - The `DataStore` contract dispatcher. -/// * `market` - The market to increment claimable fees for. -/// * `token` - The fee token. -fn validate_market_token_balance_token( - data_store: IDataStoreDispatcher, market: Market, token: ContractAddress +// Get the funding factor per second for a market +// `data_store` - the data store to use +// `market` - the market to check +// `diff_usd` - the difference between the long and short open interest +// `total_open_interest` - the total open interest +fn get_funding_factor_per_second( + data_store: IDataStoreDispatcher, + market: ContractAddress, + diff_usd: u128, + total_open_interest: u128 +) -> u128 { + let stable_funding_factor: u128 = data_store.get_u128(keys::stable_funding_factor_key(market)); + + if (stable_funding_factor > 0) { + return stable_funding_factor; + }; + + if (diff_usd == 0) { + return 0; + } + + if (total_open_interest == 0) { + MarketError::UNABLE_TO_GET_FUNDING_FACTOR_EMPTY_OPEN_INTEREST(total_open_interest); + } + + let funding_factor: u128 = get_funding_factor(data_store, market); + + let funding_exponent_factor: u128 = get_funding_exponent_factor(data_store, market); + let diff_usd_after_exponent: u128 = apply_exponent_factor(diff_usd, funding_exponent_factor); + + let diff_usd_to_open_interest_factor: u128 = to_factor( + diff_usd_after_exponent, total_open_interest + ); + + return apply_factor_u128(diff_usd_to_open_interest_factor, funding_factor); +} + +// Get the borrowing factor for a market +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// # Returns +// the borrowing factor for a market +fn get_borrowing_factor( + data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool +) -> u128 { + data_store.get_u128(keys::borrowing_factor_key(market, is_long)) +} + +// Get the borrowing exponent factor for a market +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// # Returns +// the borrowing exponent factor for a market +fn get_borrowing_exponent_factor( + data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool +) -> u128 { + data_store.get_u128(keys::borrowing_exponent_factor_key(market, is_long)) +} + +// Get the cumulative borrowing factor for a market +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// # Returns +// the cumulative borrowing factor for a market +fn get_cumulative_borrowing_factor( + data_store: @IDataStoreDispatcher, market: ContractAddress, is_long: bool +) -> u128 { + let data_store_n: IDataStoreDispatcher = *data_store; + data_store_n.get_u128(keys::cumulative_borrowing_factor_key(market, is_long)) +} + +// Increment the cumulative borrowing factor +// `data_store` - the data store to use +// `market` - the market to check +// `event_emitter` - the event emitter +// `is_long` - whether to check the long or short side +// `delta` - the increase amount +fn increment_cumulative_borrowing_factor( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + market: ContractAddress, + is_long: bool, + delta: u128 ) { - 0; + let next_cumulative_borrowing_factor = data_store + .increment_u128(keys::cumulative_borrowing_factor_key(market, is_long), delta); + + event_emitter + .emit_cumulative_borrowing_factor_updated( + market, is_long, delta, next_cumulative_borrowing_factor + ); } -/// Get the expected min token balance by summing all fees -/// # Arguments -/// * `data_store` - The `DataStore` contract dispatcher. -/// * `market` - The market to increment claimable fees for. -/// * `token` - The fee token. -fn get_expected_min_token_balance( - data_store: IDataStoreDispatcher, market: Market, token: ContractAddress +// Get the timestamp of when the cumulative borrowing factor was last updated +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// #Return +// the timestamp of when the cumulative borrowing factor was last updated +fn get_cumulative_borrowing_factor_updated_at( + data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool ) -> u128 { - // get the pool amount directly as MarketUtils.getPoolAmount will divide the amount by 2 - // for markets with the same long and short token - 0 + data_store.get_u128(keys::cumulative_borrowing_factor_updated_at_key(market, is_long)) } -/// Get the total amount of position collateral for a market -/// # Arguments -/// * `data_store` - The `DataStore` contract dispatcher. -/// * `market` - The market to check -/// * `collateral_token` - the collateral_token to check -/// * `is_long` - Whether to get the value for longs or shorts -/// # Returns -/// The total amount of position collateral for a market -fn get_collateral_sum( +// Get the number of seconds since the cumulative borrowing factor was last updated +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// #Return +// the number of seconds since the cumulative borrowing factor was last updated +fn get_seconds_since_cumulative_borrowing_factor_updated( + data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool +) -> u128 { + let updated_at: u128 = get_cumulative_borrowing_factor_updated_at(data_store, market, is_long); + if (updated_at == 0) { + return 0; + } + let block_time_stamp = starknet::info::get_block_timestamp().into(); + block_time_stamp - updated_at +} + +// Update the total borrowing amount after a position changes size +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// `prev_position_size_in_usd` - the previous position size in USD +// `prev_position_borrowing_factor` - the previous position borrowing factor +// `next_position_size_in_usd` - the next position size in USD +// `next_position_borrowing_factor` - the next position borrowing factor +fn update_total_borrowing( data_store: IDataStoreDispatcher, market: ContractAddress, - collateral_token: ContractAddress, is_long: bool, - divisor: u128 + prev_position_size_in_usd: u128, + prev_position_borrowing_factor: u128, + next_position_size_in_usd: u128, + next_position_borrowing_factor: u128 +) { + let total_borrowing: u128 = get_next_total_borrowing( + data_store, + market, + is_long, + prev_position_size_in_usd, + prev_position_borrowing_factor, + next_position_size_in_usd, + next_position_borrowing_factor + ); + + set_total_borrowing(data_store, market, is_long, total_borrowing); +} + +// Get the next total borrowing amount after a position changes size +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// `prev_position_size_in_usd` - the previous position size in USD +// `prev_position_borrowing_factor` - the previous position borrowing factor +// `next_position_size_in_usd` - the next position size in USD +// `next_position_borrowing_factor` - the next position borrowing factor +fn get_next_total_borrowing( + data_store: IDataStoreDispatcher, + market: ContractAddress, + is_long: bool, + prev_position_size_in_usd: u128, + prev_position_borrowing_factor: u128, + next_position_size_in_usd: u128, + next_position_borrowing_factor: u128 +) -> u128 { + let mut total_borrowing: u128 = get_total_borrowing(data_store, market, is_long); + total_borrowing -= apply_factor_u128(prev_position_size_in_usd, prev_position_borrowing_factor); + total_borrowing += apply_factor_u128(next_position_size_in_usd, next_position_borrowing_factor); + + total_borrowing +} + +// Get the next total borrowing amount after a position changes size +// `data_store` - the data store to use +// `market` - the market to check +// `is_long` - whether to check the long or short side +// `long_token` - the long token of the market +// `short_token` - the short token of the market +fn get_next_cumulative_borrowing_factor( + data_store: IDataStoreDispatcher, market: Market, prices: MarketPrices, is_long: bool, +) -> (u128, u128) { + let duration_in_seconds: u128 = get_seconds_since_cumulative_borrowing_factor_updated( + data_store, market.market_token, is_long + ); + let borrowing_factor_per_second: u128 = get_borrowing_factor_per_second( + data_store, market, prices, is_long + ); + + let cumulative_borrowing_factor: u128 = get_cumulative_borrowing_factor( + @data_store, market.market_token, is_long + ); + + let delta: u128 = duration_in_seconds * borrowing_factor_per_second; + let next_cumulative_borrowing_factor: u128 = cumulative_borrowing_factor + delta; + (next_cumulative_borrowing_factor, delta) +} + +// Get the borrowing factor per second +// `data_store` - the data store to use +// `market` - the market to get the borrowing factor per second for +// `prices` - prices the prices of the market tokens +// `is_long` - whether to get the factor for the long or short side +fn get_borrowing_factor_per_second( + data_store: IDataStoreDispatcher, market: Market, prices: MarketPrices, is_long: bool +) -> u128 { + let reserved_usd: u128 = get_reserved_usd(data_store, @market, @prices, is_long); + + if (reserved_usd == 0) { + return 0; + } + + // check if the borrowing fee for the smaller side should be skipped + // if skipBorrowingFeeForSmallerSide is true, and the longOpenInterest is exactly the same as the shortOpenInterest + // then the borrowing fee would be charged for both sides, this should be very rare + let skip_borrowing_fee_for_smaller_side: bool = data_store + .get_bool(keys::skip_borrowing_fee_for_smaller_side()) + .unwrap(); + + let market_snap = @market; + if (skip_borrowing_fee_for_smaller_side) { + let long_open_interest: u128 = get_open_interest_for_market_is_long( + data_store, market_snap, true + ); + let short_open_interest: u128 = get_open_interest_for_market_is_long( + data_store, market_snap, false + ); + + // if getting the borrowing factor for longs and if the longOpenInterest + // is smaller than the shortOpenInterest, then return zero + if (is_long && long_open_interest < short_open_interest) { + return 0; + } + // if getting the borrowing factor for shorts and if the shortOpenInterest + // is smaller than the longOpenInterest, then return zero + if (!is_long && short_open_interest < long_open_interest) { + return 0; + } + } + let pool_usd: u128 = get_pool_usd_without_pnl(data_store, @market, @prices, is_long, false); + + if (pool_usd == 0) { + MarketError::UNABLE_TO_GET_BORROWING_FACTOR_EMPTY_POOL_USD(pool_usd); + } + + let borrowing_exponent_factor: u128 = get_borrowing_exponent_factor( + data_store, market.market_token, is_long + ); + let reserved_usd_after_exponent: u128 = apply_exponent_factor( + reserved_usd, borrowing_exponent_factor + ); + + let reserved_usd_to_pool_factor: u128 = to_factor(reserved_usd_after_exponent, pool_usd); + let borrowing_factor: u128 = get_borrowing_factor(data_store, market.market_token, is_long); + + apply_factor_u128(reserved_usd_to_pool_factor, borrowing_factor) +} + +// Get the total pending borrowing fees +// `data_store` - the data store to use +// `market` - the market to get the borrowing factor per second for +// `long_token` - the long token of the market +// `short_token` - the short token of the market +// `is_long` - whether to get the factor for the long or short side +fn get_total_pending_borrowing_fees( + data_store: IDataStoreDispatcher, market: Market, prices: MarketPrices, is_long: bool +) -> u128 { + let open_interest: u128 = get_open_interest_for_market_is_long(data_store, @market, is_long); + + let (next_cumulative_borrowing_factor, _) = get_next_cumulative_borrowing_factor( + data_store, market, prices, is_long + ); + + let total_borrowing: u128 = get_total_borrowing(data_store, market.market_token, is_long); + + apply_factor_u128(open_interest, next_cumulative_borrowing_factor) - total_borrowing +} + +// Get the total borrowing value +// the total borrowing value is the sum of position.borrowingFactor * position.size / (10 ^ 30) +// for all positions of the market +// if borrowing APR is 1000% for 100 years, the cumulativeBorrowingFactor could be as high as 100 * 1000 * (10 ** 30) +// since position.size is a USD value with 30 decimals, under this scenario, there may be overflow issues +// if open interest exceeds (2 ** 256) / (10 ** 30) / (100 * 1000 * (10 ** 30)) => 1,157,920,900,000 USD +// `data_store` - the data store to use +// `market` - the market to get the borrowing factor per second for +// `is_long` - whether to get the factor for the long or short side +// #Return +// The total borrowing value +fn get_total_borrowing( + data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool +) -> u128 { + data_store.get_u128(keys::total_borrowing_key(market, is_long)) +} + +// Set the total borrowing value +// `data_store` - the data store to use +// `market` - the market to get the borrowing factor per second for +// `is_long` - whether to get the factor for the long or short side +// `value` - the value to set to +fn set_total_borrowing( + data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool, value: u128 +) { + data_store.set_u128(keys::total_borrowing_key(market, is_long), value) +} + +// Convert a number of market tokens to its USD value +// `usd_value` - the input USD value +// `pool_value` - the value of the pool +// `supply` - the supply of the market tokens +fn usd_to_market_token_amount(usd_value: u128, pool_value: u128, supply: u128) -> u128 { + // if the supply and poolValue is zero, use 1 USD as the token price + if (supply == 0 && pool_value == 0) { + return float_to_wei(usd_value); + } + + // if the supply is zero and the poolValue is more than zero, + // then include the poolValue for the amount of tokens minted so that + // the market token price after mint would be 1 USD + if (supply == 0 && pool_value > 0) { + return float_to_wei(pool_value + usd_value); + } + + // round market tokens down + mul_div(supply, usd_value, pool_value) +} + +// Set the total borrowing value +// `market_token_amount` - the input number of market tokens +// `pool_value` - the value of the pool +// `supply` - the supply of the market tokens +// #Return +// The USD value of the market tokens +fn market_token_amount_to_usd(market_token_amount: u128, pool_value: u128, supply: u128) -> u128 { + if (supply == 0) { + MarketError::EMPTY_MARKET_TOKEN_SUPPLY(supply); + } + + mul_div(pool_value, market_token_amount, supply) +} + +// Validate that the specified market exists and is enabled +// `data_store` - the data store to use +// `market_add` the address of the market +fn validate_enabled_market_check( + data_store: IDataStoreDispatcher, market_address: ContractAddress +) { + let market: Market = data_store.get_market(market_address).unwrap(); + validate_enabled_market(data_store, market); +} + +// Validate that the specified market exists and is enabled +// `data_store` - the data store to use +// `market` - the market to check +fn validate_enabled_market(data_store: IDataStoreDispatcher, market: Market) { + assert(market.market_token != 0.try_into().unwrap(), MarketError::EMPTY_MARKET); + + let is_market_disabled: bool = data_store + .get_bool(keys::is_market_disabled_key(market.market_token)) + .unwrap(); + + if (is_market_disabled) { + MarketError::DISABLED_MARKET(is_market_disabled); + } +} + +// Validate that the positions can be opened in the given market +// `market` - the market to check +fn validate_position_market_check(data_store: IDataStoreDispatcher, market: Market) { + validate_enabled_market(data_store, market); + + assert(!is_swap_only_market(market), MarketError::INVALID_POSITION_MARKET); +} + +fn validate_position_market(data_store: IDataStoreDispatcher, market_add: ContractAddress) { + let market: Market = data_store.get_market(market_add).unwrap(); + validate_position_market_check(data_store, market); +} + +// Check if a market only supports swaps and not positions +// `market` - the market to check +fn is_swap_only_market(market: Market) -> bool { + market.index_token.is_zero() +} + +// Check if the given token is a collateral token of the market +// `market` - the market to check +// `token` - the token to check +fn is_market_collateral_token(market: Market, token: ContractAddress) -> bool { + market.long_token == token || market.short_token == token +} + +// Validate if the given token is a collateral token of the market +// `market` - the market to check +// `token` - the token to check +fn validate_market_collateral_token(market: Market, token: ContractAddress) { + if (!is_market_collateral_token(market, token)) { + MarketError::INVALID_MARKET_COLLATERAL_TOKEN(market.market_token, token); + } +} + +// Get the enabled market, revert if the market does not exist or is not enabled +// `data_store - DataStore +// `market_add` - the address of the market +fn get_enabled_market(data_store: IDataStoreDispatcher, market_add: ContractAddress) -> Market { + let market: Market = data_store.get_market(market_add).unwrap(); + validate_enabled_market(data_store, market); + market +} + +fn get_swap_path_market(data_store: IDataStoreDispatcher, market_add: ContractAddress) -> Market { + let market: Market = data_store.get_market(market_add).unwrap(); + validate_swap_market(data_store, market); + market +} + +// Get a list of market values based on an input array of market addresses +// `swap_path` - list of market addresses +fn get_swap_path_markets( + data_store: IDataStoreDispatcher, swap_path: Span32 +) -> Array { + let mut markets: Array = ArrayTrait::new(); + let mut i: u32 = 0; + let length: u32 = swap_path.len(); + + loop { + if i == length { + break; + } + let market_adress_prev = swap_path.get(i); + let market_adress: ContractAddress = *market_adress_prev.unwrap().unbox(); + markets.append(get_swap_path_market(data_store, market_adress)); + i += 1; + }; + markets +} + +fn validate_swap_path(data_store: IDataStoreDispatcher, token_swap_path: Span32) { + let max_swap_path_length: u128 = data_store.get_u128(keys::max_swap_path_length()); + let token_swap_path_length: u32 = token_swap_path.len(); + + if (token_swap_path_length.into() > max_swap_path_length) { + MarketError::MAX_SWAP_PATH_LENGTH_EXCEEDED(token_swap_path_length, max_swap_path_length); + } + + let mut i: u32 = 0; + loop { + if i == token_swap_path_length { + break; + } + let market_prev = token_swap_path.get(i); + let market: ContractAddress = *market_prev.unwrap().unbox(); + validate_swap_market_with_address(data_store, market); + i += 1; + }; +} + +// Validate that the pending pnl is below the allowed amount +// `data_store` - DataStore +// `market` - the market to check +// `prices` - the prices of the market tokens +// `pnl_factor_type` - the pnl factor type to check +fn validate_max_pnl( + data_store: IDataStoreDispatcher, + market: Market, + prices: MarketPrices, + pnl_factor_type_for_longs: felt252, + pnl_factor_type_for_shorts: felt252 +) { + let (is_pnl_factor_exceeded_for_longs, pnl_to_pool_factor_for_longs, max_pnl_factor_for_longs) = + is_pnl_factor_exceeded_check( + data_store, market, prices, true, pnl_factor_type_for_longs, + ); + + if (is_pnl_factor_exceeded_for_longs) { + MarketError::PNL_EXCEEDED_FOR_LONGS(is_pnl_factor_exceeded_for_longs); + } + + let ( + is_pnl_factor_exceeded_for_shorts, pnl_to_pool_factor_for_shorts, max_pnl_factor_for_shorts + ) = + is_pnl_factor_exceeded_check( + data_store, market, prices, false, pnl_factor_type_for_shorts, + ); + + if (is_pnl_factor_exceeded_for_shorts) { + MarketError::PNL_EXCEEDED_FOR_SHORTS(is_pnl_factor_exceeded_for_shorts); + } +} + +// Check if the pending pnl exceeds the allowed amount +// `data_store` - DataStore +// `oracle` - Oracle +// `market` - the market to check +// `is_long` - whether to check the long or short side +// `pnl_factor_type` - the pnl factor type to check +fn is_pnl_factor_exceeded( + data_store: IDataStoreDispatcher, + oracle: IOracleDispatcher, + market_add: ContractAddress, + is_long: bool, + pnl_factor_type: felt252 +) -> (bool, i128, u128) { + let market: Market = get_enabled_market(data_store, market_add); + let prices: MarketPrices = get_market_prices(oracle, market); + + is_pnl_factor_exceeded_check(data_store, market, prices, is_long, pnl_factor_type) +} + +// Check if the pending pnl exceeds the allowed amount +// `data_store` - DataStore +// `market` - the market to check +// `prices` - the prices of the market tokens +// `is_long` - whether to check the long or short side +// `pnl_factor_type` - the pnl factor type to check +fn is_pnl_factor_exceeded_check( + data_store: IDataStoreDispatcher, + market: Market, + prices: MarketPrices, + is_long: bool, + pnl_factor_type: felt252 +) -> (bool, i128, u128) { + let pnl_to_pool_factor: i128 = get_pnl_to_pool_factor_from_prices( + data_store, @market, @prices, is_long, true + ); + let max_pnl_factor: u128 = get_max_pnl_factor( + data_store, pnl_factor_type, market.market_token, is_long + ); + + let is_exceeded: bool = pnl_to_pool_factor > 0 + && to_unsigned(pnl_to_pool_factor) > max_pnl_factor; + + (is_exceeded, pnl_to_pool_factor, max_pnl_factor) +} + +fn get_ui_fee_factor(data_store: IDataStoreDispatcher, account: ContractAddress) -> u128 { + let max_ui_fee_factor: u128 = data_store.get_u128(keys::max_ui_fee_factor()); + let ui_fee_factor: u128 = data_store.get_u128(keys::ui_fee_factor_key(account)); + + if ui_fee_factor < max_ui_fee_factor { + return ui_fee_factor; + } else { + return max_ui_fee_factor; + } +} + +fn set_ui_fee_factor( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + account: ContractAddress, + ui_fee_factor: u128 +) { + let max_ui_fee_factor: u128 = data_store.get_u128(keys::max_ui_fee_factor()); + + if (ui_fee_factor > max_ui_fee_factor) { + MarketError::UI_FEE_FACTOR_EXCEEDED(ui_fee_factor, max_ui_fee_factor); + } + + data_store.set_u128(keys::ui_fee_factor_key(account), ui_fee_factor); + + event_emitter.emit_ui_fee_factor_updated(account, ui_fee_factor); +} + +fn validate_market_token_balance_array(data_store: IDataStoreDispatcher, markets: Array) { + let length: u32 = markets.len(); + let mut i: u32 = 0; + loop { + if i == length { + break; + } + validate_market_token_balance_check(data_store, *markets.at(i)); + i += 1; + }; +} + +fn validate_market_address_token_balance( + data_store: IDataStoreDispatcher, market_add: ContractAddress +) { + let market: Market = get_enabled_market(data_store, market_add); + validate_market_token_balance_check(data_store, market); +} + +fn validate_market_token_balance_check(data_store: IDataStoreDispatcher, market: Market) { + validate_market_token_balance_with_token(data_store, market, market.long_token); + + if (market.long_token == market.short_token) { + return; + } + validate_market_token_balance_with_token(data_store, market, market.short_token); +} + +fn validate_market_token_balance_with_token( + data_store: IDataStoreDispatcher, market: Market, token: ContractAddress +) { + assert( + market.market_token.is_non_zero() && token.is_non_zero(), + MarketError::EMPTY_ADDRESS_IN_MARKET_TOKEN_BALANCE_VALIDATION + ); + let balance: u128 = IERC20Dispatcher { contract_address: token } + .balance_of(market.market_token) + .low + .into(); + let expected_min_balance: u128 = get_expected_min_token_balance(data_store, market, token); + + assert(balance >= expected_min_balance, MarketError::INVALID_MARKET_TOKEN_BALANCE); + + // funding fees can be claimed even if the collateral for positions that should pay funding fees + // hasn't been reduced yet + // due to that, funding fees and collateral is excluded from the expectedMinBalance calculation + // and validated separately + + // use 1 for the getCollateralSum divisor since getCollateralSum does not sum over both the + // longToken and shortToken + let mut collateral_amount: u128 = get_collateral_sum( + data_store, market.market_token, token, true, 1 + ); + collateral_amount += get_collateral_sum(data_store, market.market_token, token, false, 1); + + if (balance < collateral_amount) { + MarketError::INVALID_MARKET_TOKEN_BALANCE_FOR_COLLATERAL_AMOUNT(balance, collateral_amount); + } + + let claimable_funding_fee_amount = data_store + .get_u128(keys::claimable_funding_amount_key(market.market_token, token)); + + // in case of late liquidations, it may be possible for the claimableFundingFeeAmount to exceed the market token balance + // but this should be very rare + if (balance < claimable_funding_fee_amount) { + MarketError::INVALID_MARKET_TOKEN_BALANCE_FOR_CLAIMABLE_FUNDING( + balance, claimable_funding_fee_amount + ); + } +} + +fn get_expected_min_token_balance( + data_store: IDataStoreDispatcher, market: Market, token: ContractAddress ) -> u128 { - 0 + // get the pool amount directly as MarketUtils.get_pool_amount will divide the amount by 2 + // for markets with the same long and short token + let pool_amount: u128 = data_store.get_u128(keys::pool_amount_key(market.market_token, token)); + let swap_impact_pool_amount: u128 = get_swap_impact_pool_amount( + data_store, market.market_token, token + ); + let claimable_collateral_amount: u128 = data_store + .get_u128(keys::claimable_collateral_amount_key(market.market_token, token)); + let claimable_fee_amount: u128 = data_store + .get_u128(keys::claimable_fee_amount_key(market.market_token, token)); + let claimable_ui_fee_amount: u128 = data_store + .get_u128(keys::claimable_ui_fee_amount_key(market.market_token, token)); + let affiliate_reward_amount: u128 = data_store + .get_u128(keys::affiliate_reward_key(market.market_token, token)); + + // funding fees are excluded from this summation as claimable funding fees + // are incremented without a corresponding decrease of the collateral of + // other positions, the collateral of other positions is decreased when + // those positions are updated + return pool_amount + + swap_impact_pool_amount + + claimable_collateral_amount + + claimable_fee_amount + + claimable_ui_fee_amount + + affiliate_reward_amount; } diff --git a/src/order/increase_order_utils.cairo b/src/order/increase_order_utils.cairo index c9cf1d16..a509ebce 100644 --- a/src/order/increase_order_utils.cairo +++ b/src/order/increase_order_utils.cairo @@ -25,7 +25,7 @@ use alexandria_data_structures::array_ext::SpanTraitExt; /// needs it. We need to find a solution for that case. #[inline(always)] fn process_order(params: ExecuteOrderParams) -> event_utils::EventLogData { - market_utils::validate_position_market(params.contracts.data_store, params.market); + market_utils::validate_position_market_check(params.contracts.data_store, params.market); let (collateral_token, collateral_increment_amount) = swap_utils::swap( @swap_utils::SwapParams { diff --git a/src/swap/swap_utils.cairo b/src/swap/swap_utils.cairo index 74c17577..971b9703 100644 --- a/src/swap/swap_utils.cairo +++ b/src/swap/swap_utils.cairo @@ -335,7 +335,7 @@ fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u128) market_utils::validate_max_pnl( *params.data_store, *_params.market, - @prices, + prices, if (*_params.token_in == *_params.market.long_token) { keys::max_pnl_factor_for_deposits() } else { diff --git a/src/withdrawal/withdrawal_utils.cairo b/src/withdrawal/withdrawal_utils.cairo index a7060167..56a784b2 100644 --- a/src/withdrawal/withdrawal_utils.cairo +++ b/src/withdrawal/withdrawal_utils.cairo @@ -30,6 +30,7 @@ use satoru::withdrawal::{ error::WithdrawalError, withdrawal::Withdrawal, withdrawal_vault::{IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait} }; +use satoru::market::market_utils::validate_enabled_market_address; #[derive(Drop, starknet::Store, Serde)] struct CreateWithdrawalParams { @@ -421,7 +422,7 @@ fn execute_withdrawal_( market_utils::validate_max_pnl( *params.data_store, market, - @prices, + prices, keys::max_pnl_factor_for_withdrawals(), keys::max_pnl_factor_for_withdrawals() ); @@ -484,7 +485,7 @@ fn execute_withdrawal_( // if the native token was transferred to the receiver in a swap // it may be possible to invoke external contracts before the validations are called - market_utils::validate_market_token_balance(*params.data_store, market); + market_utils::validate_market_token_balance_check(*params.data_store, market); result } diff --git a/tests/deposit/test_deposit_utils.cairo b/tests/deposit/test_deposit_utils.cairo index d1d4bdd3..be5f5b61 100644 --- a/tests/deposit/test_deposit_utils.cairo +++ b/tests/deposit/test_deposit_utils.cairo @@ -5,6 +5,8 @@ use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::chain::chain::{IChainDispatcher, IChainDispatcherTrait}; +use satoru::data::keys; +use satoru::market::market::Market; use satoru::role::role; use satoru::tests_lib; use satoru::utils::span32::{Span32, Array32Trait}; @@ -14,6 +16,7 @@ use satoru::deposit::{ deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait} }; + use snforge_std::{declare, start_prank, ContractClassTrait}; @@ -27,15 +30,15 @@ fn given_normal_conditions_when_deposit_then_works() { // ); } - -#[test] -#[should_panic(expected: ('insufficient_execution_fee',))] -fn given_unsufficient_fee_token_amount_for_deposit_then_fails() { - let (caller_address, data_store, event_emitter, deposit_vault, chain) = setup(); - let account: ContractAddress = 'account'.try_into().unwrap(); - let deposit_param = create_dummy_deposit_param(); - let key = create_deposit(data_store, event_emitter, deposit_vault, account, deposit_param); -} +//TODO : Use this test when get_market function is implemented +// #[test] +// #[should_panic(expected: ('insufficient_execution_fee',))] +// fn given_unsufficient_fee_token_amount_for_deposit_then_fails() { +// let (caller_address, data_store, event_emitter, deposit_vault, chain, role_store) = setup_role(); +// let account: ContractAddress = 'account'.try_into().unwrap(); +// let deposit_param = create_dummy_deposit_param_market(data_store, role_store); +// let key = create_deposit(data_store, event_emitter, deposit_vault, account, deposit_param); +// } // #[test] // #[should_panic(expected: ('empty_deposit_amounts',))] @@ -94,6 +97,26 @@ fn setup() -> ( (caller_address, data_store, event_emitter, deposit_vault, chain) } +fn setup_role() -> ( + ContractAddress, + IDataStoreDispatcher, + IEventEmitterDispatcher, + IDepositVaultDispatcher, + IChainDispatcher, + ContractAddress +) { + let (caller_address, role_store, data_store) = tests_lib::setup(); + let (_, event_emitter) = tests_lib::setup_event_emitter(); + let deposit_vault_address = deploy_deposit_vault( + data_store.contract_address, role_store.contract_address + ); + let deposit_vault = IDepositVaultDispatcher { contract_address: deposit_vault_address }; + + let chain_address = deploy_chain(); + let chain = IChainDispatcher { contract_address: chain_address }; + (caller_address, data_store, event_emitter, deposit_vault, chain, role_store.contract_address) +} + /// Utility function to deploy a `DepositVault` contract and return its dispatcher. fn deploy_deposit_vault( data_store_address: ContractAddress, role_store_address: ContractAddress @@ -141,3 +164,61 @@ fn create_dummy_deposit_param() -> CreateDepositParams { } } +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + +fn create_dummy_deposit_param_market( + data_store: IDataStoreDispatcher, role_store_address: ContractAddress +) -> CreateDepositParams { + let key: ContractAddress = 12345.try_into().unwrap(); + let address_zero: ContractAddress = 42.try_into().unwrap(); + let data_store_address = deploy_data_store(role_store_address); + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let mut market = Market { + market_token: key, + index_token: address_zero, + long_token: address_zero, + short_token: address_zero, + }; + // Test logic + // Test set_market function without permission + start_prank(role_store_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + start_prank(data_store_address, caller_address); + data_store.set_market(key, 0, market); + + CreateDepositParams { + /// The address to send the market tokens to. + receiver: 'receiver'.try_into().unwrap(), + /// The callback contract linked to this deposit. + callback_contract: 'callback_contract'.try_into().unwrap(), + /// The ui fee receiver. + ui_fee_receiver: 'ui_fee_receiver'.try_into().unwrap(), + /// The market to deposit into. + market: market.market_token, + /// The initial long token address. + initial_long_token: 'initial_long_token'.try_into().unwrap(), + /// The initial short token address. + initial_short_token: 'initial_short_token'.try_into().unwrap(), + /// The swap path into markets for the long token. + long_token_swap_path: array![ + 1.try_into().unwrap(), 2.try_into().unwrap(), 3.try_into().unwrap() + ] + .span32(), + /// The swap path into markets for the short token. + short_token_swap_path: array![ + 4.try_into().unwrap(), 5.try_into().unwrap(), 6.try_into().unwrap() + ] + .span32(), + /// The minimum acceptable number of liquidity tokens. + min_market_tokens: 10, + /// The execution fee for keepers. + execution_fee: 1, + /// The gas limit for the callback_contract. + callback_gas_limit: 20 + } +} diff --git a/tests/market/test_market_utils.cairo b/tests/market/test_market_utils.cairo index 56dcfe45..d88beb37 100644 --- a/tests/market/test_market_utils.cairo +++ b/tests/market/test_market_utils.cairo @@ -73,7 +73,7 @@ fn given_normal_conditions_when_get_open_interest_then_works() { ); data_store.set_u128(open_interest_data_store_key, 300); - let open_interest = market_utils::get_open_interest( + let open_interest = market_utils::get_open_interest_div( data_store, market_token_deployed_address, collateral_token, is_long, divisor ); // Open interest is 300, so 300 / 3 = 100. From 27fc9b4723d7d630db7f6833acf2a54b90881761 Mon Sep 17 00:00:00 2001 From: akhercha Date: Thu, 5 Oct 2023 14:42:01 +0200 Subject: [PATCH 08/28] test: Tests for deposit_vault (#496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(deposit_vault_contract): Can start doing some unit tests from that * test(deposit_vault_contract): Better format * test(deposit_vault_contract): All test passes! 🥳 * test(deposit_vault_contract): Added unit test for transfer_out not enough balance --------- Co-authored-by: akhercha Co-authored-by: sparqet <37338401+sparqet@users.noreply.github.com> --- src/deposit/deposit_vault.cairo | 2 +- tests/deposit/test_deposit_vault.cairo | 250 +++++++++++++++---------- 2 files changed, 147 insertions(+), 105 deletions(-) diff --git a/src/deposit/deposit_vault.cairo b/src/deposit/deposit_vault.cairo index 04fe1508..54c26857 100644 --- a/src/deposit/deposit_vault.cairo +++ b/src/deposit/deposit_vault.cairo @@ -80,8 +80,8 @@ mod DepositVault { #[constructor] fn constructor( ref self: ContractState, - role_store_address: ContractAddress, data_store_address: ContractAddress, + role_store_address: ContractAddress, ) { self.data_store.write(IDataStoreDispatcher { contract_address: data_store_address }); self.role_store.write(IRoleStoreDispatcher { contract_address: role_store_address }); diff --git a/tests/deposit/test_deposit_vault.cairo b/tests/deposit/test_deposit_vault.cairo index 192cf0b8..96667d72 100644 --- a/tests/deposit/test_deposit_vault.cairo +++ b/tests/deposit/test_deposit_vault.cairo @@ -3,148 +3,190 @@ // ************************************************************************* // IMPORTS // ************************************************************************* - // Core lib imports. - +use integer::{u128_from_felt252, u256_from_felt252}; use result::ResultTrait; -use traits::{TryInto, Into}; use starknet::{ ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, ClassHash, }; use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; - +use traits::{TryInto, Into}; // Local imports. -use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::role::role; +use satoru::tests_lib; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + +// ********************************************************************************************* +// * TEST CONSTANTS * +// ********************************************************************************************* +/// Initial amount of ERC20 tokens minted to the deposit vault +const INITIAL_TOKENS_MINTED: felt252 = 1000; + +// ********************************************************************************************* +// * TEST LOGIC * +// ********************************************************************************************* +#[test] +#[should_panic(expected: ('already_initialized',))] +fn given_already_intialized_when_initialize_then_fails() { + let (_, _, role_store, data_store, deposit_vault, _) = setup(); + deposit_vault.initialize(data_store.contract_address, role_store.contract_address); + teardown(data_store, deposit_vault); +} -/// TODO: Implement actual test and change the name of this function. -// #[test] -// fn init_deposit_vault_test() { -// // ********************************************************************************************* -// // * SETUP * -// // ********************************************************************************************* +#[test] +fn given_normal_conditions_when_transfer_out_then_works() { + let (_, receiver_address, _, data_store, deposit_vault, erc20) = setup(); -// let (caller_address, deposit_vault, role_store, data_store) = setup(); -// // ********************************************************************************************* -// // * TEST LOGIC * -// // ********************************************************************************************* + let amount_to_transfer: u128 = 100; + deposit_vault.transfer_out(erc20.contract_address, receiver_address, amount_to_transfer); -// // Empty test for now. + // check that the contract balance reduces + let contract_balance = erc20.balance_of(deposit_vault.contract_address); + let expected_balance: u256 = u256_from_felt252( + INITIAL_TOKENS_MINTED - amount_to_transfer.into() + ); + assert(contract_balance == expected_balance, 'transfer_out failed'); -// // ********************************************************************************************* -// // * TEARDOWN * -// // ********************************************************************************************* -// teardown(data_store, deposit_vault); -// } + // check that the balance of the receiver increases + let receiver_balance = erc20.balance_of(receiver_address); + let expected_balance: u256 = amount_to_transfer.into(); + assert(receiver_balance == expected_balance, 'transfer_out failed'); -/// Utility function to setup the test environment. -fn setup() -> ( - // This caller address will be used with `start_prank` cheatcode to mock the caller address., - ContractAddress, // Interface to interact with the `DepositVault` contract. - IDepositVaultDispatcher, // Interface to interact with the `RoleStore` contract. - IRoleStoreDispatcher, // Interface to interact with the `DataStore` contract. - IDataStoreDispatcher, -) { - // Setup the contracts. - let (caller_address, deposit_vault, role_store, data_store) = setup_contracts(); - // Grant roles and prank the caller address. - grant_roles_and_prank(caller_address, deposit_vault, role_store, data_store); - // Return the caller address and the contract interfaces. - (caller_address, deposit_vault, role_store, data_store) + teardown(data_store, deposit_vault); } -// Utility function to grant roles and prank the caller address. -/// Grants roles and pranks the caller address. -/// -/// # Arguments -/// -/// * `caller_address` - The address of the caller. -/// * `deposit_vault` - The interface to interact with the `DepositVault` contract. -/// * `role_store` - The interface to interact with the `RoleStore` contract. -/// * `data_store` - The interface to interact with the `DataStore` contract. -fn grant_roles_and_prank( - caller_address: ContractAddress, - deposit_vault: IDepositVaultDispatcher, - role_store: IRoleStoreDispatcher, - data_store: IDataStoreDispatcher, -) { - start_prank(role_store.contract_address, caller_address); - - // Grant the caller the `CONTROLLER` role. - role_store.grant_role(caller_address, role::CONTROLLER); +#[test] +#[should_panic(expected: ('u256_sub Overflow',))] +fn given_not_enough_token_when_transfer_out_then_fails() { + let (_, receiver_address, _, data_store, deposit_vault, erc20) = setup(); - // Prank the caller address for calls to `DataStore` contract. - // We need this so that the caller has the CONTROLLER role. - start_prank(data_store.contract_address, caller_address); + let amount_to_transfer: u128 = u128_from_felt252(INITIAL_TOKENS_MINTED + 1); + deposit_vault.transfer_out(erc20.contract_address, receiver_address, amount_to_transfer); - // Start pranking the `DepositVault` contract. This is necessary to mock the behavior of the contract - // for testing purposes. - start_prank(deposit_vault.contract_address, caller_address); + teardown(data_store, deposit_vault); } -/// Utility function to teardown the test environment. -fn teardown(data_store: IDataStoreDispatcher, deposit_vault: IDepositVaultDispatcher) { - stop_prank(data_store.contract_address); +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_has_no_controller_role_when_transfer_out_then_fails() { + let (caller_address, receiver_address, _, data_store, deposit_vault, erc20) = setup(); stop_prank(deposit_vault.contract_address); + start_prank(deposit_vault.contract_address, receiver_address); + deposit_vault.transfer_out(erc20.contract_address, caller_address, 100_u128); + teardown(data_store, deposit_vault); +} + +#[test] +#[should_panic(expected: ('self_transfer_not_supported',))] +fn given_receiver_is_contract_when_transfer_out_then_fails() { + let (caller_address, receiver_address, _, data_store, deposit_vault, erc20) = setup(); + deposit_vault.transfer_out(erc20.contract_address, deposit_vault.contract_address, 100_u128); + teardown(data_store, deposit_vault); +} + +/// TODO: implement the tests when record_transfer_in is implemented +#[test] +#[should_panic(expected: ('NOT IMPLEMENTED YET',))] +fn given_normal_conditions_when_record_transfer_in_then_works() { + assert(true == false, 'NOT IMPLEMENTED YET') } -/// Setup required contracts. -fn setup_contracts() -> ( - // This caller address will be used with `start_prank` cheatcode to mock the caller address., - ContractAddress, // Interface to interact with the `DepositVault` contract. - IDepositVaultDispatcher, // Interface to interact with the `RoleStore` contract. - IRoleStoreDispatcher, // Interface to interact with the `DataStore` contract. +// ********************************************************************************************* +// * SETUP * +// ********************************************************************************************* +/// Utility function to setup the test environment. +/// +/// Complete statement to retrieve everything: +/// let ( +/// caller_address, receiver_address, +/// role_store, data_store, +/// deposit_vault, +/// erc20 +/// ) = setup(); +/// +/// # Returns +/// +/// * `ContractAddress` - The address of the caller. +/// * `ContractAddress` - The address of the receiver. +/// * `IRoleStoreDispatcher` - The role store dispatcher. +/// * `IDataStoreDispatcher` - The data store dispatcher. +/// * `IDepositVaultDispatcher` - The deposit vault dispatcher. +/// * `IERC20Dispatcher` - The ERC20 token dispatcher. +fn setup() -> ( + ContractAddress, + ContractAddress, + IRoleStoreDispatcher, IDataStoreDispatcher, + IDepositVaultDispatcher, + IERC20Dispatcher ) { - let caller_address = 0x101.try_into().unwrap(); - // Deploy the role store contract. - let role_store_address = deploy_role_store(); - - // Create a role store dispatcher. - let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + // get caller_address, role store and data_store from tests_lib::setup() + let (caller_address, role_store, data_store) = tests_lib::setup(); - // Deploy the contract. - let data_store_address = deploy_data_store(role_store_address); - // Create a safe dispatcher to interact with the contract. - let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + // get receiver_address + let receiver_address: ContractAddress = 0x202.try_into().unwrap(); - // Deploy the `DepositVault` contract. - let deposit_vault_address = deploy_deposit_vault(role_store_address, data_store_address); - // Create a safe dispatcher to interact with the contract. + // deploy deposit vault + let deposit_vault_address = deploy_deposit_vault( + data_store.contract_address, role_store.contract_address + ); let deposit_vault = IDepositVaultDispatcher { contract_address: deposit_vault_address }; - // Return the caller address and the contract interfaces. - (caller_address, deposit_vault, role_store, data_store) -} + // deploy erc20 token + let erc20_contract_address = deploy_erc20_token(deposit_vault_address); + let erc20 = IERC20Dispatcher { contract_address: erc20_contract_address }; + + // start prank and give controller role to caller_address + start_prank(deposit_vault.contract_address, caller_address); + return (caller_address, receiver_address, role_store, data_store, deposit_vault, erc20); +} -/// Utility function to deploy a `DepositVault` contract and return its address. +/// Utility function to deploy a deposit vault. +/// +/// # Arguments +/// +/// * `data_store_address` - The address of the data store contract. +/// * `role_store_address` - The address of the role store contract. +/// +/// # Returns +/// +/// * `ContractAddress` - The address of the deposit vault. fn deploy_deposit_vault( - role_store_address: ContractAddress, data_store_address: ContractAddress, + data_store_address: ContractAddress, role_store_address: ContractAddress ) -> ContractAddress { - let contract = declare('DepositVault'); - let mut constructor_calldata = array![]; - constructor_calldata.append(role_store_address.into()); - contract.deploy(@constructor_calldata).unwrap() + let deposit_vault_contract = declare('DepositVault'); + let constructor_calldata2 = array![data_store_address.into(), role_store_address.into()]; + deposit_vault_contract.deploy(@constructor_calldata2).unwrap() } - -/// Utility function to deploy a data store contract and return its address. -fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { - let contract = declare('DataStore'); - let mut constructor_calldata = array![]; - constructor_calldata.append(role_store_address.into()); - contract.deploy(@constructor_calldata).unwrap() +/// Utility function to deploy an ERC20 token. +/// When deployed, 1000 tokens are minted to the deposit vault address. +/// +/// # Arguments +/// +/// * `deposit_vault_address` - The address of the deposit vault address. +/// +/// # Returns +/// +/// * `ContractAddress` - The address of the ERC20 token. +fn deploy_erc20_token(deposit_vault_address: ContractAddress) -> ContractAddress { + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array![ + 'satoru', 'STU', INITIAL_TOKENS_MINTED, 0, deposit_vault_address.into() + ]; + erc20_contract.deploy(@constructor_calldata3).unwrap() } -/// Utility function to deploy a data store contract and return its address. -/// Copied from `tests/role/test_role_store.rs`. -/// TODO: Find a way to share this code. -fn deploy_role_store() -> ContractAddress { - let contract = declare('RoleStore'); - contract.deploy(@ArrayTrait::new()).unwrap() +// ********************************************************************************************* +// * TEARDOWN * +// ********************************************************************************************* +fn teardown(data_store: IDataStoreDispatcher, deposit_vault: IDepositVaultDispatcher) { + tests_lib::teardown(data_store.contract_address); + stop_prank(deposit_vault.contract_address); } From 6984bf823894bccf6bfe78e44c32a39df639f8ad Mon Sep 17 00:00:00 2001 From: akhercha Date: Fri, 6 Oct 2023 16:36:32 +0200 Subject: [PATCH 09/28] test: Improve tests for referral_storage contract (#499) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(referral_storage_contract): Refacto test architecture before adding tests * test(referral_storage_contract): Quick refacto of base tests * test(referral_storage_contract): Added more tests * test(referral_storage_contract): Finished tests for referral_storage contract * test(referral_storage_contract): Removed custom teardown * test(referral_storage_contract): Unused imports * test(referral_storage_contract): Updated top comment * test(referral_storage_contract): from review; added init check in Governable * test(referral_storage_contract): Removed useless line * test(referral_storage_contract): Removed useless line x2 💀 --------- Co-authored-by: akhercha --- src/mock/error.cairo | 1 + src/mock/governable.cairo | 1 + src/mock/referral_storage.cairo | 5 +- tests/mock/test_referral_storage.cairo | 426 +++++++++++++++++++------ 4 files changed, 338 insertions(+), 95 deletions(-) diff --git a/src/mock/error.cairo b/src/mock/error.cairo index e8bb1f25..7c2113a5 100644 --- a/src/mock/error.cairo +++ b/src/mock/error.cairo @@ -1,4 +1,5 @@ mod MockError { + const ALREADY_INITIALIZED: felt252 = 'already_initialized'; const INVALID_TOTAL_REBATE: felt252 = 'invalid total_rebate'; const INVALID_DISCOUNT_SHARE: felt252 = 'invalid discount_share'; const INVALID_CODE: felt252 = 'invalid code'; diff --git a/src/mock/governable.cairo b/src/mock/governable.cairo index 79dc73d3..3b003d5e 100644 --- a/src/mock/governable.cairo +++ b/src/mock/governable.cairo @@ -60,6 +60,7 @@ mod Governable { #[external(v0)] impl Governable of super::IGovernable { fn initialize(ref self: ContractState, event_emitter_address: ContractAddress) { + assert(self.gov.read().is_zero(), MockError::ALREADY_INITIALIZED); self .event_emitter .write(IEventEmitterDispatcher { contract_address: event_emitter_address }); diff --git a/src/mock/referral_storage.cairo b/src/mock/referral_storage.cairo index e0f68d96..4d41f652 100644 --- a/src/mock/referral_storage.cairo +++ b/src/mock/referral_storage.cairo @@ -3,7 +3,6 @@ // ************************************************************************* // IMPORTS // ************************************************************************* - // Core lib imports. use starknet::ContractAddress; @@ -11,7 +10,7 @@ use starknet::ContractAddress; use satoru::referral::referral_tier::ReferralTier; // ************************************************************************* -// Interface of the `OracleStore` contract. +// Interface of the `ReferralStorage` contract. // ************************************************************************* #[starknet::interface] trait IReferralStorage { @@ -271,7 +270,7 @@ mod ReferralStorage { fn get_trader_referral_info( self: @ContractState, account: ContractAddress ) -> (felt252, ContractAddress) { - let mut code: felt252 = self.trader_referral_codes.read(account); + let mut code: felt252 = self.trader_referral_codes(account); let mut referrer = contract_address_const::<0>(); if (code != 0) { diff --git a/tests/mock/test_referral_storage.cairo b/tests/mock/test_referral_storage.cairo index cccb19bb..f3ec236d 100644 --- a/tests/mock/test_referral_storage.cairo +++ b/tests/mock/test_referral_storage.cairo @@ -1,145 +1,387 @@ +//! Test file for `src/mock/referral_storage.cairo`. + +// ********************************************************************************************* +// * IMPORTS * +// ********************************************************************************************* +// Core lib imports. +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; use starknet::{ContractAddress, contract_address_const}; +// Local imports. use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; -use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::deposit::deposit::Deposit; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; use satoru::mock::governable::{IGovernableDispatcher, IGovernableDispatcherTrait}; - -use satoru::role::role; -use satoru::deposit::deposit::Deposit; -use satoru::tests_lib::teardown; -use satoru::utils::span32::{Span32, Array32Trait}; +use satoru::referral::referral_tier::ReferralTier; use satoru::referral::referral_utils; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::tests_lib; + +// ********************************************************************************************* +// * TEST LOGIC * +// ********************************************************************************************* +#[test] +#[should_panic(expected: ('already_initialized',))] +fn given_initialize_when_already_intialized_then_fails() { + let (_, _, data_store, event_emitter, referral_storage, _) = setup(); + referral_storage.initialize(event_emitter.contract_address); + tests_lib::teardown(data_store.contract_address); +} -use snforge_std::{declare, start_prank, ContractClassTrait}; +#[test] +fn given_normal_conditions_when_setting_handler_from_storage_than_work() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); -fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { - let contract = declare('DataStore'); - let constructor_calldata = array![role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() + referral_storage.set_handler(caller_address, true); + referral_storage.only_handler(); + + tests_lib::teardown(data_store.contract_address); } -fn deploy_event_emitter() -> ContractAddress { - let contract = declare('EventEmitter'); - contract.deploy(@array![]).unwrap() +#[test] +#[should_panic(expected: ('Unauthorized gov caller',))] +fn given_caller_has_no_gov_role_then_fails() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + stop_prank(referral_storage.contract_address); + + let dummy_address: ContractAddress = contract_address_const::<'dummy'>(); + start_prank(referral_storage.contract_address, dummy_address); + referral_storage.set_handler(caller_address, true); + tests_lib::teardown(data_store.contract_address); } -/// Utility function to deploy a `ReferralStorage` contract and return its dispatcher. -fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { - let contract = declare('ReferralStorage'); - let constructor_calldata = array![event_emitter_address.into()]; - contract.deploy(@constructor_calldata).unwrap() +#[test] +#[should_panic(expected: ('forbidden',))] +fn given_handler_not_set_than_fails() { + let (_, _, data_store, _, referral_storage, _) = setup(); + + referral_storage.only_handler(); + + tests_lib::teardown(data_store.contract_address); } -fn deploy_governable(event_emitter_address: ContractAddress) -> ContractAddress { - let contract = declare('Governable'); - let constructor_calldata = array![event_emitter_address.into()]; - contract.deploy(@constructor_calldata).unwrap() +#[test] +fn given_normal_conditions_when_fetching_code_owner_from_storage_before_setting_then_works() { + let (_, _, data_store, _, referral_storage, _) = setup(); + + let code: felt252 = 'EBDW'; + + let out: ContractAddress = referral_storage.code_owners(code); + assert(out == contract_address_const::<0>(), 'address should not be set'); + + tests_lib::teardown(data_store.contract_address); } -/// Utility function to deploy a `RoleStore` contract and return its dispatcher. -fn deploy_role_store() -> ContractAddress { - let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() +#[test] +fn given_normal_conditions_when_setting_and_fetching_tier_from_storage_then_works() { + let (_, _, data_store, _, referral_storage, _) = setup(); + + let tier_id: u128 = 3; + let total_rebate: u128 = 10; + let discount_share: u128 = 10; + referral_storage.set_tier(tier_id, total_rebate, discount_share); + + let mut referral_tier: ReferralTier = referral_storage.tiers(tier_id); + assert(referral_tier.total_rebate == total_rebate, 'total rebate not set'); + assert(referral_tier.discount_share == discount_share, 'discount share not set'); + + tests_lib::teardown(data_store.contract_address); } -fn setup() -> ( - ContractAddress, - IRoleStoreDispatcher, - IDataStoreDispatcher, - IEventEmitterDispatcher, - IReferralStorageDispatcher, - IGovernableDispatcher -) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); +#[test] +#[should_panic(expected: ('invalid total_rebate',))] +fn given_total_rebate_too_high_when_setting_tier_from_storage_then_fails() { + let (_, _, data_store, _, referral_storage, _) = setup(); - let role_store_address = deploy_role_store(); - let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + let tier_id: u128 = 3; + let total_rebate: u128 = 10001; + let discount_share: u128 = 10; + referral_storage.set_tier(tier_id, total_rebate, discount_share); - let event_emitter_address = deploy_event_emitter(); - let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + tests_lib::teardown(data_store.contract_address); +} - let data_store_address = deploy_data_store(role_store_address); - let data_store = IDataStoreDispatcher { contract_address: data_store_address }; +#[test] +#[should_panic(expected: ('invalid discount_share',))] +fn given_discount_share_too_high_when_setting_tier_from_storage_then_fails() { + let (_, _, data_store, _, referral_storage, _) = setup(); - let referral_storage_address = deploy_referral_storage(event_emitter_address); - let referral_storage = IReferralStorageDispatcher { - contract_address: referral_storage_address - }; + let tier_id: u128 = 3; + let total_rebate: u128 = 10; + let discount_share: u128 = 10001; + referral_storage.set_tier(tier_id, total_rebate, discount_share); - let governable_address = deploy_governable(event_emitter_address); - let governable = IGovernableDispatcher { contract_address: governable_address }; + tests_lib::teardown(data_store.contract_address); +} - start_prank(role_store_address, caller_address); - start_prank(event_emitter_address, caller_address); - start_prank(data_store_address, caller_address); - start_prank(referral_storage_address, caller_address); - start_prank(governable_address, caller_address); +#[test] +fn given_normal_conditions_when_setting_and_fetching_referrer_tiers_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let tier_id: u128 = 3; + referral_storage.set_referrer_tier(caller_address, tier_id); + + let out: u128 = referral_storage.referrer_tiers(caller_address); + assert(out == tier_id, 'out tier_id is wrong'); + + tests_lib::teardown(data_store.contract_address); +} - (caller_address, role_store, data_store, event_emitter, referral_storage, governable) +#[test] +fn given_normal_conditions_when_fetching_referrer_tiers_from_storage_before_setting_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let tier_id: u128 = 3; + + let out: u128 = referral_storage.referrer_tiers(caller_address); + assert(out == 0, 'out tier_id is wrong'); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_setting_and_fetching_referrer_discount_share_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let discount_share: u128 = 1000; + referral_storage.set_referrer_discount_share(discount_share); + + let out: u128 = referral_storage.referrer_discount_shares(caller_address); + assert(out == discount_share, 'out discount_share is wrong'); + + tests_lib::teardown(data_store.contract_address); } -//TODO add more tests +#[test] +#[should_panic(expected: ('invalid discount_share',))] +fn given_discount_share_too_high_when_setting_referrer_discount_share_from_storage_then_fails() { + let (_, _, data_store, _, referral_storage, _) = setup(); + + let discount_share: u128 = 10001; + referral_storage.set_referrer_discount_share(discount_share); + + tests_lib::teardown(data_store.contract_address); +} #[test] -fn given_normal_conditions_when_setting_and_fetching_code_owner_from_storage_then_works() { - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); +fn given_normal_conditions_when_setting_and_fetching_referrer_referral_code_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + referral_storage.set_handler(caller_address, true); - //setting the code_owner and fetching it from storage let code: felt252 = 'EBDW'; - let new_account: ContractAddress = contract_address_const::<'new_account'>(); + referral_storage.set_trader_referral_code(caller_address, code); - referral_storage.gov_set_code_owner(code, new_account); - let res: ContractAddress = referral_storage.code_owners(code); - assert(res == new_account, 'the address is wrong'); + let out: felt252 = referral_storage.trader_referral_codes(caller_address); + assert(out == code, 'out code is wrong'); - teardown(data_store.contract_address); + tests_lib::teardown(data_store.contract_address); } #[test] -fn given_normal_conditions_when_fetching_code_owner_from_storage_before_setting_then_works() { - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); +#[should_panic(expected: ('forbidden',))] +fn given_not_handler_when_setting_referrer_referral_code_from_storage_then_fails() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let code: felt252 = 'EBDW'; + referral_storage.set_trader_referral_code(caller_address, code); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_setting_and_fetching_referrer_referral_code_by_user_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); - //fetching the code owner from storage before setting it let code: felt252 = 'EBDW'; - let new_account: ContractAddress = contract_address_const::<'new_account'>(); + referral_storage.set_trader_referral_code_by_user(code); - let res: ContractAddress = referral_storage.code_owners(code); - assert(res == contract_address_const::<0>(), 'the address is wrong'); + let out: felt252 = referral_storage.trader_referral_codes(caller_address); + assert(out == code, 'out code is wrong'); - teardown(data_store.contract_address); + tests_lib::teardown(data_store.contract_address); } #[test] -fn given_normal_conditions_when_setting_and_fetching_referrer_tiers_from_storage_then_works() { - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); +fn given_normal_conditions_when_registering_code_and_fetching_address_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); - //setting the referrer_id and fetching it from storage - let tier_id: u128 = 3; - let new_account: ContractAddress = contract_address_const::<'new_account'>(); + let code: felt252 = 'EBDW'; + referral_storage.register_code(code); - referral_storage.set_referrer_tier(new_account, tier_id); - let res: u128 = referral_storage.referrer_tiers(new_account); - assert(res == tier_id, 'the tier_id is wrong'); + let owner: ContractAddress = referral_storage.code_owners(code); + assert(owner == caller_address, 'out caller_address is wrong'); - teardown(data_store.contract_address); + tests_lib::teardown(data_store.contract_address); } #[test] -fn given_normal_conditions_when_fetching_referrer_tiers_from_storage_before_setting_then_works() { - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); +#[should_panic(expected: ('invalid code',))] +fn given_invalid_code_when_registering_code_from_storage_then_fails() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); - //fetching the referrer_tier from storage before setting it - let tier_id: u128 = 3; - let new_account: ContractAddress = contract_address_const::<'new_account'>(); + let code: felt252 = 0; + referral_storage.register_code(code); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +#[should_panic(expected: ('code already exists',))] +fn given_code_already_registered_when_registering_code_from_storage_then_fails() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let code: felt252 = 'EBDW'; + referral_storage.register_code(code); + referral_storage.register_code(code); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_gov_setting_and_fetching_code_owner_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let code: felt252 = 'EBDW'; + referral_storage.gov_set_code_owner(code, caller_address); + + let out: ContractAddress = referral_storage.code_owners(code); + assert(out == caller_address, 'address should be set'); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +#[should_panic(expected: ('invalid code',))] +fn given_invalid_code_when_gov_setting_code_owner_from_storage_then_fails() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let code: felt252 = 0; + referral_storage.gov_set_code_owner(code, caller_address); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_setting_and_fetching_new_code_owner_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let new_owner: ContractAddress = contract_address_const::<'new owner'>(); + let code: felt252 = 'EBDW'; + referral_storage.gov_set_code_owner(code, caller_address); + referral_storage.set_code_owner(code, new_owner); + + let out: ContractAddress = referral_storage.code_owners(code); + assert(out == new_owner, 'out code owner address is wrong'); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +#[should_panic(expected: ('forbidden',))] +fn given_not_allowed_when_setting_new_code_owner_from_storage_then_fails() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let new_owner: ContractAddress = contract_address_const::<'new owner'>(); + let code: felt252 = 'EBDW'; + referral_storage.set_code_owner(code, new_owner); + + let out: ContractAddress = referral_storage.code_owners(code); + assert(out == caller_address, 'out code owner address is wrong'); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +#[should_panic(expected: ('invalid code',))] +fn given_invalid_code_when_setting_new_code_owner_from_storage_then_fails() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let new_owner: ContractAddress = contract_address_const::<'new owner'>(); + let code: felt252 = 0; + referral_storage.gov_set_code_owner(code, caller_address); + referral_storage.set_code_owner(code, new_owner); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_fetching_trader_referral_info_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let code: felt252 = 'EBDW'; + referral_storage.register_code(code); + referral_storage.set_trader_referral_code_by_user(code); - let res: u128 = referral_storage.referrer_tiers(new_account); - assert(res == 0, 'the tier_id is wrong'); + let (out_code, out_address) = referral_storage.get_trader_referral_info(caller_address); + assert(out_code == code, 'out code is wrong'); + assert(out_address == caller_address, 'out code owner address is wrong'); - teardown(data_store.contract_address); + tests_lib::teardown(data_store.contract_address); +} + +#[test] +fn given_code_owner_not_set_when_fetching_trader_referral_info_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let (out_code, out_referrer) = referral_storage.get_trader_referral_info(caller_address); + assert(out_referrer == contract_address_const::<0>(), 'code owner should not be set'); + assert(out_code == 0, 'code should not be set'); + + tests_lib::teardown(data_store.contract_address); +} + +// ********************************************************************************************* +// * SETUP * +// ********************************************************************************************* +/// Utility function to setup the test environment +fn setup() -> ( + ContractAddress, + IRoleStoreDispatcher, + IDataStoreDispatcher, + IEventEmitterDispatcher, + IReferralStorageDispatcher, + IGovernableDispatcher +) { + let (caller_address, role_store, data_store) = tests_lib::setup(); + + let event_emitter_address = deploy_event_emitter(); + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + + let referral_storage_address = deploy_referral_storage(event_emitter_address); + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let governable_address = deploy_governable(event_emitter_address); + let governable = IGovernableDispatcher { contract_address: governable_address }; + + start_prank(role_store.contract_address, caller_address); + start_prank(event_emitter_address, caller_address); + start_prank(data_store.contract_address, caller_address); + start_prank(referral_storage_address, caller_address); + start_prank(governable_address, caller_address); + + return (caller_address, role_store, data_store, event_emitter, referral_storage, governable); +} + +/// Deploy an `EventEmitter` contract and return its dispatcher. +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + contract.deploy(@array![]).unwrap() +} + +/// Deploy a `ReferralStorage` contract and return its dispatcher. +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + +/// Deploy a `Governable` contract and return its dispatcher. +fn deploy_governable(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('Governable'); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy(@constructor_calldata).unwrap() } From d77360010c2f1b72b76cb293046be2755f9088ec Mon Sep 17 00:00:00 2001 From: tevrat aksoy Date: Fri, 6 Oct 2023 20:08:43 +0300 Subject: [PATCH 10/28] test: Improve tests of referral_utils library. #483 (#498) * update referalla-util tests * allow changing refferal * remove role_store --------- Co-authored-by: zarboq <37303126+zarboq@users.noreply.github.com> --- src/market/market_factory.cairo | 8 +- src/market/market_token.cairo | 37 +- src/referral/referral_utils.cairo | 4 +- tests/lib.cairo | 5 +- tests/market/test_market_token.cairo | 10 +- tests/mock/test_referral_utils.cairo | 250 ------------ tests/referral/test_referral_utils.cairo | 490 +++++++++++++++++++++++ 7 files changed, 535 insertions(+), 269 deletions(-) delete mode 100644 tests/mock/test_referral_utils.cairo create mode 100644 tests/referral/test_referral_utils.cairo diff --git a/src/market/market_factory.cairo b/src/market/market_factory.cairo index d26959fd..b0a075e4 100644 --- a/src/market/market_factory.cairo +++ b/src/market/market_factory.cairo @@ -124,9 +124,11 @@ mod MarketFactory { ); // Deploy the `MarketToken` contract. - // Contructor arguments: [role_store_address]. - let mut constructor_calldata = array![]; - constructor_calldata.append(self.role_store.read().contract_address.into()); + // Contructor arguments: [role_store_address, data_store_address]. + let mut constructor_calldata = array![ + self.role_store.read().contract_address.into(), + self.data_store.read().contract_address.into() + ]; // Deploy the contract with the `deploy_syscall`. let (market_token_deployed_address, return_data) = deploy_syscall( self.market_token_class_hash.read(), salt, constructor_calldata.span(), false diff --git a/src/market/market_token.cairo b/src/market/market_token.cairo index 58864c96..110c20fd 100644 --- a/src/market/market_token.cairo +++ b/src/market/market_token.cairo @@ -17,6 +17,9 @@ trait IMarketToken { fn approve(ref self: TState, spender: ContractAddress, amount: u128) -> bool; fn mint(ref self: TState, recipient: ContractAddress, amount: u128); fn burn(ref self: TState, recipient: ContractAddress, amount: u128); + fn transfer_out( + ref self: TState, token: ContractAddress, receiver: ContractAddress, amount: u128, + ); } #[starknet::contract] @@ -26,9 +29,9 @@ mod MarketToken { use starknet::get_caller_address; use zeroable::Zeroable; - use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::role::role; use satoru::bank::bank::{Bank, IBank}; + use satoru::role::role_module::{RoleModule, IRoleModule}; use super::IMarketToken; @@ -38,7 +41,6 @@ mod MarketToken { #[storage] struct Storage { - role_store: IRoleStoreDispatcher, name: felt252, symbol: felt252, total_supply: u128, @@ -68,12 +70,15 @@ mod MarketToken { } #[constructor] - fn constructor(ref self: ContractState, role_store_address: ContractAddress) { + fn constructor( + ref self: ContractState, + role_store_address: ContractAddress, + data_store_address: ContractAddress + ) { self.initializer(NAME, SYMBOL); - //Might need to inherit bank. - // let mut bank: Bank::ContractState = Bank::unsafe_new_contract_state(); - // IBank::initialize(ref bank, data_store_address, role_store_address) - self.role_store.write(IRoleStoreDispatcher { contract_address: role_store_address }); + + let mut bank: Bank::ContractState = Bank::unsafe_new_contract_state(); + IBank::initialize(ref bank, data_store_address, role_store_address); } // @@ -134,15 +139,29 @@ mod MarketToken { fn mint(ref self: ContractState, recipient: ContractAddress, amount: u128) { // Check that the caller has permission to set the value. - self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); + let mut role_module: RoleModule::ContractState = + RoleModule::unsafe_new_contract_state(); + role_module.only_controller(); self._mint(recipient, amount); } fn burn(ref self: ContractState, recipient: ContractAddress, amount: u128) { // Check that the caller has permission to set the value. - self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); + let mut role_module: RoleModule::ContractState = + RoleModule::unsafe_new_contract_state(); + role_module.only_controller(); self._burn(recipient, amount); } + fn transfer_out( + ref self: ContractState, + token: ContractAddress, + receiver: ContractAddress, + amount: u128, + ) { + let mut bank: Bank::ContractState = Bank::unsafe_new_contract_state(); + IBank::transfer_out(ref bank, token, receiver, amount); + } + // TODO implement Bank functions } #[external(v0)] diff --git a/src/referral/referral_utils.cairo b/src/referral/referral_utils.cairo index c0771989..35b686a0 100644 --- a/src/referral/referral_utils.cairo +++ b/src/referral/referral_utils.cairo @@ -123,8 +123,8 @@ fn claim_affiliate_reward( let next_pool_value: u128 = data_store .decrement_u128(keys::affiliate_reward_key(market, token), reward_amount); - //TODO Call this when its implemented - // IMarketTokenDispatcher { contract_address: market }.transfer_out(token, receiver, reward_amount); + IMarketTokenDispatcher { contract_address: market } + .transfer_out(token, receiver, reward_amount); market_utils::validate_market_token_balance_with_address(data_store, market); diff --git a/tests/lib.cairo b/tests/lib.cairo index 11df136f..58da9571 100644 --- a/tests/lib.cairo +++ b/tests/lib.cairo @@ -105,7 +105,10 @@ mod utils { mod test_i128; } mod mock { - mod test_referral_utils; mod test_governable; mod test_referral_storage; } + +mod referral { + mod test_referral_utils; +} diff --git a/tests/market/test_market_token.cairo b/tests/market/test_market_token.cairo index c747f275..3566b0fd 100644 --- a/tests/market/test_market_token.cairo +++ b/tests/market/test_market_token.cairo @@ -55,7 +55,7 @@ fn setup() -> ( let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; // Deploy the contract. - let market_token_address = deploy_market_token(role_store_address); + let market_token_address = deploy_market_token(role_store_address, 11111.try_into().unwrap()); // Create a safe dispatcher to interact with the contract. let market_token = IMarketTokenDispatcher { contract_address: market_token_address }; @@ -83,10 +83,12 @@ fn teardown(market_token_address: ContractAddress) { } /// Utility function to deploy a market token and return its address. -fn deploy_market_token(role_store_address: ContractAddress) -> ContractAddress { +fn deploy_market_token( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { let contract = declare('MarketToken'); - let mut constructor_calldata = array![]; - constructor_calldata.append(role_store_address.into()); + let mut constructor_calldata = array![role_store_address.into(), data_store_address.into()]; + contract.deploy(@constructor_calldata).unwrap() } diff --git a/tests/mock/test_referral_utils.cairo b/tests/mock/test_referral_utils.cairo deleted file mode 100644 index d3406056..00000000 --- a/tests/mock/test_referral_utils.cairo +++ /dev/null @@ -1,250 +0,0 @@ -use starknet::{ContractAddress, contract_address_const}; - -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; -use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::mock::governable::{IGovernableDispatcher, IGovernableDispatcherTrait}; - -use snforge_std::{ - declare, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, event_name_hash, Event, - EventAssertions, start_prank -}; -use satoru::role::role; -use satoru::deposit::deposit::Deposit; -use satoru::tests_lib::teardown; -use satoru::utils::span32::{Span32, Array32Trait}; -use satoru::referral::referral_utils; - -/// Utility function to deploy a `DataStore` contract and return its dispatcher. -fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { - let contract = declare('DataStore'); - let constructor_calldata = array![role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -fn deploy_event_emitter() -> ContractAddress { - let contract = declare('EventEmitter'); - contract.deploy(@array![]).unwrap() -} - -/// Utility function to deploy a `ReferralStorage` contract and return its dispatcher. -fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { - let contract = declare('ReferralStorage'); - let constructor_calldata = array![event_emitter_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -fn deploy_governable(event_emitter_address: ContractAddress) -> ContractAddress { - let contract = declare('Governable'); - let constructor_calldata = array![event_emitter_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -/// Utility function to deploy a `RoleStore` contract and return its dispatcher. -fn deploy_role_store() -> ContractAddress { - let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() -} - -/// Utility function to setup the test environment. -/// -/// # Returns -/// -/// * `ContractAddress` - The address of the caller. -/// * `IDataStoreDispatcher` - The data store dispatcher. -/// * `IRoleStoreDispatcher` - The role store dispatcher. -fn setup() -> ( - ContractAddress, - IRoleStoreDispatcher, - IDataStoreDispatcher, - IEventEmitterDispatcher, - IReferralStorageDispatcher, - IGovernableDispatcher -) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); - - let role_store_address = deploy_role_store(); - let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; - - let event_emitter_address = deploy_event_emitter(); - let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; - - let data_store_address = deploy_data_store(role_store_address); - let data_store = IDataStoreDispatcher { contract_address: data_store_address }; - - let referral_storage_address = deploy_referral_storage(event_emitter_address); - let referral_storage = IReferralStorageDispatcher { - contract_address: referral_storage_address - }; - - let governable_address = deploy_governable(event_emitter_address); - let governable = IGovernableDispatcher { contract_address: governable_address }; - - start_prank(role_store_address, caller_address); - start_prank(event_emitter_address, caller_address); - start_prank(data_store_address, caller_address); - start_prank(referral_storage_address, caller_address); - start_prank(governable_address, caller_address); - - (caller_address, role_store, data_store, event_emitter, referral_storage, governable) -} - -//TODO add more tests - -#[test] -fn given_normal_conditions_when_trader_referral_codes_then_works() { - // Setup - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); - - //Set the referral code for a trader and getting it from the storage. - referral_storage.set_handler(caller_address, true); - let account: ContractAddress = contract_address_const::<111>(); - let referral_code: felt252 = 'QWERTY'; - let x = referral_utils::set_trader_referral_code(referral_storage, account, referral_code); - let answer = referral_storage.trader_referral_codes(account); - - assert(answer == referral_code, 'this is not the correct code'); - - teardown(data_store.contract_address); -} - -#[test] -#[should_panic(expected: ('forbidden',))] -fn given_forbidden_when_trader_referral_codes_then_fails() { - // Setup - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); - - //forbidden access - let account: ContractAddress = contract_address_const::<111>(); - let referral_code: felt252 = 'QWERTY'; - let x = referral_utils::set_trader_referral_code(referral_storage, account, referral_code); - let answer = referral_storage.trader_referral_codes(account); - assert(answer == referral_code, 'this is not the correct code'); - teardown(data_store.contract_address); -} - - -#[test] -fn given_normal_conditions_when_increment_affiliate_reward_then_works() { - // Setup - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); - - //TODO be able to do it when you can read next_value and next_pool_value - role_store.grant_role(caller_address, role::CONTROLLER); - - let market: ContractAddress = contract_address_const::<'market'>(); - let token: ContractAddress = contract_address_const::<'token'>(); - let affiliate: ContractAddress = contract_address_const::<'affiliate'>(); - - let delta: u128 = 3; - - // let expected_data: Array = array![ - // market.into(), token.into(), affiliate.into(), delta.into(), next_value.into(), next_pool_value.into() - // ]; - - referral_utils::increment_affiliate_reward( - data_store, event_emitter, market, token, affiliate, delta - ); - - // let mut spy = spy_events(SpyOn::One(caller_address)); - // spy - // .assert_emitted( - // @array![ - // Event { - // from: caller_address, - // name: 'EmitAffiliateRewardUpdated', - // keys: array![], - // data: expected_data - // } - // ] - // ); - - teardown(data_store.contract_address); -} - -#[test] -fn given_normal_conditions_when_get_referral_info_then_works() { - // Setup - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); - - //Get the referral information for a specified trader (code, the affiliate address, total_rebate, discount_share) - referral_storage.set_handler(caller_address, true); - //add referral code - let code: felt252 = 'WISOQKW'; - referral_storage.set_trader_referral_code(caller_address, code); - //set code owner gov - referral_storage.gov_set_code_owner(code, caller_address); - //set referrer tier - referral_storage.set_referrer_tier(caller_address, 2); - //set tier - referral_storage.set_tier(2, 20, 30); - //set referrer discount share - referral_storage.set_referrer_discount_share(30); - - let (code, affiliate, total_rebate, discount_share) = referral_utils::get_referral_info( - referral_storage, caller_address - ); - - assert(code == code, 'the code is wrong'); - assert(affiliate == caller_address, 'the affiliate is wrong'); - assert(total_rebate == 200000000000000000, 'the total_rebate is wrong'); - assert(discount_share == 300000000000000000, 'the discount_share is wrong'); - - teardown(data_store.contract_address); -} - -#[test] -fn given_normal_conditions_when_claim_affiliate_reward_then_works() { - // Setup - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); - - //Get the referral information for a specified trader - let market: ContractAddress = contract_address_const::<'market'>(); - let token: ContractAddress = contract_address_const::<'token'>(); - let account: ContractAddress = contract_address_const::<'account'>(); - let receiver: ContractAddress = contract_address_const::<'receiver'>(); - - role_store.grant_role(caller_address, role::CONTROLLER); - - let reward_amount: u128 = referral_utils::claim_affiliate_reward( - data_store, event_emitter, market, token, account, receiver - ); - - assert(reward_amount == 0, 'the reward amount is wrong'); - - teardown(data_store.contract_address); -} - -#[test] -fn given_normal_conditions_when_increment_affiliate_reward_and_claim_affiliate_reward_then_works() { - // Setup - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); - - //Get the referral information for a specified trader after incrementing the affiliate reward balance - let market: ContractAddress = contract_address_const::<'market'>(); - let token: ContractAddress = contract_address_const::<'token'>(); - let affiliate: ContractAddress = contract_address_const::<'affiliate'>(); - let account: ContractAddress = contract_address_const::<'account'>(); - let receiver: ContractAddress = contract_address_const::<'receiver'>(); - let delta: u128 = 10; - - role_store.grant_role(caller_address, role::CONTROLLER); - referral_utils::increment_affiliate_reward( - data_store, event_emitter, market, token, affiliate, delta - ); - - let reward_amount: u128 = referral_utils::claim_affiliate_reward( - data_store, event_emitter, market, token, affiliate, receiver - ); - - assert(reward_amount == 10, 'the reward amount is wrong'); - - teardown(data_store.contract_address); -} diff --git a/tests/referral/test_referral_utils.cairo b/tests/referral/test_referral_utils.cairo new file mode 100644 index 00000000..9a606b22 --- /dev/null +++ b/tests/referral/test_referral_utils.cairo @@ -0,0 +1,490 @@ +use starknet::{ContractAddress, contract_address_const}; + +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::mock::governable::{IGovernableDispatcher, IGovernableDispatcherTrait}; +use snforge_std::io::PrintTrait; +use snforge_std::{ + declare, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, event_name_hash, Event, + EventAssertions, start_prank, stop_prank +}; +use satoru::role::role; +use satoru::deposit::deposit::Deposit; +use satoru::tests_lib::teardown; +use satoru::utils::span32::{Span32, Array32Trait}; +use satoru::referral::referral_utils; +use satoru::data::keys; +use satoru::utils::precision; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + + +/// Utility function to deploy a `DataStore` contract and return its dispatcher. +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + contract.deploy(@array![]).unwrap() +} + +/// Utility function to deploy a `ReferralStorage` contract and return its dispatcher. +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + +fn deploy_governable(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('Governable'); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + +/// Utility function to deploy a `RoleStore` contract and return its dispatcher. +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + contract.deploy(@array![]).unwrap() +} + +/// Utility function to deploy a `MarketToken` contract and return its dispatcher. +fn deploy_market_token( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('MarketToken'); + let constructor_calldata = array![role_store_address.into(), data_store_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + +/// Utility function to deploy a mock token contract +fn setup_mock_token( + recipient: ContractAddress, market_token: ContractAddress +) -> (ContractAddress, IERC20Dispatcher) { + let contract = declare('ERC20'); + let constructor_calldata = array![11, 11, 10000000000000000000000, 0, recipient.into()]; + let token_address = contract.deploy(@constructor_calldata).unwrap(); + + let token_contract = IERC20Dispatcher { contract_address: token_address }; + + start_prank(token_address, recipient); + token_contract.transfer(market_token, 10000000000000000000000); + stop_prank(token_address); + (token_address, token_contract) +} + + +/// Utility function to setup the test environment. +/// +/// # Returns +/// +/// * `ContractAddress` - The address of the caller. +/// * `IRoleStoreDispatcher` - The role store dispatcher. +/// * `IDataStoreDispatcher` - The data store dispatcher. +/// * `IEventEmitterDispatcher` - The event emitter dispatcher. +/// * `IReferralStorageDispatcher` - The referral store dispatcher. +/// * `IGovernableDispatcher` - The governanace dispatcher. +/// * `IMarketTokenDispatcher` - The market token distpatcher. + +fn setup() -> ( + ContractAddress, + IRoleStoreDispatcher, + IDataStoreDispatcher, + IEventEmitterDispatcher, + IReferralStorageDispatcher, + IGovernableDispatcher, + IMarketTokenDispatcher +) { + let caller_address: ContractAddress = 0x101.try_into().unwrap(); + + let role_store_address = deploy_role_store(); + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + + let event_emitter_address = deploy_event_emitter(); + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + + let data_store_address = deploy_data_store(role_store_address); + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + + let referral_storage_address = deploy_referral_storage(event_emitter_address); + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let market_token_address = deploy_market_token(role_store_address, data_store_address); + let market_token = IMarketTokenDispatcher { contract_address: market_token_address }; + + let governable_address = deploy_governable(event_emitter_address); + let governable = IGovernableDispatcher { contract_address: governable_address }; + + start_prank(role_store_address, caller_address); + start_prank(event_emitter_address, caller_address); + start_prank(data_store_address, caller_address); + start_prank(referral_storage_address, caller_address); + start_prank(governable_address, caller_address); + start_prank(market_token_address, caller_address); + + ( + caller_address, + role_store, + data_store, + event_emitter, + referral_storage, + governable, + market_token + ) +} + +#[test] +fn given_normal_conditions_when_trader_referral_codes_then_works() { + // Setup + let ( + caller_address, + role_store, + data_store, + event_emitter, + referral_storage, + governable, + market_token + ) = + setup(); + + // Test + + //Set the referral code for a trader and getting it from the storage. + referral_storage.set_handler(caller_address, true); + let account: ContractAddress = contract_address_const::<111>(); + let referral_code: felt252 = 'QWERTY'; + + referral_utils::set_trader_referral_code(referral_storage, account, referral_code); + let retrieved_code = referral_storage.trader_referral_codes(account); + assert(retrieved_code == referral_code, 'invalid referral code1'); + + // Check referral code wont change if input zero + + let referral_code2: felt252 = 0; + referral_utils::set_trader_referral_code(referral_storage, account, referral_code2); + let retrieved_code2 = referral_storage.trader_referral_codes(account); + assert(retrieved_code2 == referral_code, 'invalid referral code2'); + + // Check referral code will change even if it is assigned + + let referral_code3: felt252 = 12345; + referral_utils::set_trader_referral_code(referral_storage, account, referral_code3); + let retrieved_code3 = referral_storage.trader_referral_codes(account); + assert(retrieved_code3 == referral_code3, 'invalid referral code3'); + + teardown(data_store.contract_address); +} + + +#[test] +#[should_panic(expected: ('forbidden',))] +fn given_forbidden_when_trader_referral_codes_then_fails() { + // Setup + let ( + caller_address, + role_store, + data_store, + event_emitter, + referral_storage, + governable, + market_token + ) = + setup(); + + //forbidden access + let account: ContractAddress = contract_address_const::<111>(); + let referral_code: felt252 = 'QWERTY'; + + // Test + + referral_utils::set_trader_referral_code(referral_storage, account, referral_code); + let retrieved_code = referral_storage.trader_referral_codes(account); + assert(retrieved_code == referral_code, 'invalid referral code'); + teardown(data_store.contract_address); +} + + +#[test] +fn given_normal_conditions_when_increment_affiliate_reward_then_works() { + // Setup + let ( + caller_address, + role_store, + data_store, + event_emitter, + referral_storage, + governable, + market_token + ) = + setup(); + + let mut spy = spy_events(SpyOn::One(event_emitter.contract_address)); + role_store.grant_role(caller_address, role::CONTROLLER); + + let init_value: u128 = 10000; + let init_next_pool: u128 = 20000; + + let market: ContractAddress = contract_address_const::<'market'>(); + let token: ContractAddress = contract_address_const::<'token'>(); + let affiliate: ContractAddress = contract_address_const::<'affiliate'>(); + + let key_1 = keys::affiliate_reward_for_account_key(market, token, affiliate); + let key_2 = keys::affiliate_reward_key(market, token); + + data_store.set_u128(key_1, init_value); + data_store.set_u128(key_2, init_next_pool); + + let delta: u128 = 2000; + let expected_value = init_value + delta; + let expected_pool = init_next_pool + delta; + + let expected_data: Array = array![ + market.into(), + token.into(), + affiliate.into(), + delta.into(), + (expected_value).into(), + (expected_pool).into(), + ]; + + // Test + referral_utils::increment_affiliate_reward( + data_store, event_emitter, market, token, affiliate, delta + ); + + let retrieved_value = data_store.get_u128(key_1); + assert(retrieved_value == expected_value, 'invalid next value'); + + let retrieved_pool_value = data_store.get_u128(key_2); + assert(retrieved_pool_value == expected_pool, 'invalid next pool'); + + spy + .assert_emitted( + @array![ + Event { + from: event_emitter.contract_address, + name: 'AffiliateRewardUpdated', + keys: array![], + data: expected_data + } + ] + ); + + teardown(data_store.contract_address); +} + + +#[test] +fn given_no_code_when_get_referral_info_then_works() { + // Setup + let ( + caller_address, + role_store, + data_store, + event_emitter, + referral_storage, + governable, + market_token + ) = + setup(); + + let (code, affiliate, total_rebate, discount_share) = referral_utils::get_referral_info( + referral_storage, caller_address + ); + + assert(code == 0, 'invalid code'); + assert(affiliate == contract_address_const::<0>(), 'invalid affiliate'); + assert(total_rebate == 0, 'invalid total_rebate'); + assert(discount_share == 0, 'invalid discount_share'); + + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_get_referral_info_then_works() { + // Setup + let ( + caller_address, + role_store, + data_store, + event_emitter, + referral_storage, + governable, + market_token + ) = + setup(); + + let owner: ContractAddress = 'owner'.try_into().unwrap(); + let tier_level = 100; + let rebate = 200; + let discount = 300; + let ref_discount_share = 10; + + //Get the referral information for a specified trader (code, the affiliate address, total_rebate, discount_share) + referral_storage.set_handler(caller_address, true); + //add referral code + let code: felt252 = 'WISOQKW'; + referral_storage.set_trader_referral_code(caller_address, code); + //set code owner gov + referral_storage.gov_set_code_owner(code, owner); + //set referrer tier + referral_storage.set_referrer_tier(owner, tier_level); + //set tier + referral_storage.set_tier(tier_level, rebate, discount); + //set referrer discount share + referral_storage.set_referrer_discount_share(ref_discount_share); + + // Test + + let (retrived_code, affiliate, total_rebate, discount_share) = + referral_utils::get_referral_info( + referral_storage, caller_address + ); + + assert(code == retrived_code, 'invalid code'); + assert(affiliate == owner, 'invalid affiliate'); + assert(total_rebate == precision::basis_points_to_float(rebate), 'invalid total_rebate'); + assert(discount_share == precision::basis_points_to_float(discount), 'invalid discount_share'); + + teardown(data_store.contract_address); +} + + +#[test] +fn given_refferal_discountshare_when_get_referral_info_then_works() { + // Setup + let ( + caller_address, + role_store, + data_store, + event_emitter, + referral_storage, + governable, + market_token + ) = + setup(); + + let tier_level = 200; + let rebate = 300; + let discount = 500; + let ref_discount_share = 40; + + //Get the referral information for a specified trader (code, the affiliate address, total_rebate, discount_share) + referral_storage.set_handler(caller_address, true); + //add referral code + let code: felt252 = 'WISOQKW'; + referral_storage.set_trader_referral_code(caller_address, code); + //set code owner gov + referral_storage.gov_set_code_owner(code, caller_address); + //set referrer tier + referral_storage.set_referrer_tier(caller_address, tier_level); + //set tier + referral_storage.set_tier(tier_level, rebate, discount); + //set referrer discount share + referral_storage.set_referrer_discount_share(ref_discount_share); + + // Test + + let (retrived_code, affiliate, total_rebate, discount_share) = + referral_utils::get_referral_info( + referral_storage, caller_address + ); + + assert(code == retrived_code, 'invalid code'); + assert(affiliate == caller_address, 'invalid affiliate'); + assert(total_rebate == precision::basis_points_to_float(rebate), 'invalid total_rebate'); + assert( + discount_share == precision::basis_points_to_float(ref_discount_share), + 'invalid discount_share' + ); + + teardown(data_store.contract_address); +} + + +#[test] +fn given_normal_conditions_when_claim_affiliate_reward_then_works() { + // Setup + let ( + caller_address, + role_store, + data_store, + event_emitter, + referral_storage, + governable, + market_token + ) = + setup(); + let (token_address, token_dispatcher) = setup_mock_token( + caller_address, market_token.contract_address + ); + let mut spy = spy_events(SpyOn::One(event_emitter.contract_address)); + + role_store.grant_role(caller_address, role::CONTROLLER); + + //Get the referral information for a specified trader + let market: ContractAddress = market_token.contract_address; + let account: ContractAddress = contract_address_const::<'account'>(); + role_store.grant_role(caller_address, role::CONTROLLER); + + let reward_amount = 300000000; + let pool_value = 1000000000; + + let key_1 = keys::affiliate_reward_for_account_key(market, token_address, account); + let key_2 = keys::affiliate_reward_key(market, token_address); + + data_store.set_u128(key_1, reward_amount); + data_store.set_u128(key_2, pool_value); + + // Test + + let caller_balance = token_dispatcher.balance_of(caller_address); + assert(caller_balance == 0, 'invalid init balance'); + + let retrieved_amount: u128 = referral_utils::claim_affiliate_reward( + data_store, event_emitter, market, token_address, account, caller_address + ); + + assert(retrieved_amount == reward_amount, 'invalid retrieved_amount'); + + // Check balance incresed as reward amounts + let caller_balance_after = token_dispatcher.balance_of(caller_address); + assert(caller_balance_after == reward_amount.into(), 'invalid after balance'); + + let retrived_value = data_store.get_u128(key_1); + assert(retrived_value == 0, 'invalid value'); + + let retrived_value2 = data_store.get_u128(key_2); + assert(retrived_value2 == pool_value - reward_amount, 'invalid value'); + + // Check event + + let expected_data: Array = array![ + market.into(), + token_address.into(), + account.into(), + caller_address.into(), + reward_amount.into(), + retrived_value2.into(), + ]; + + spy + .assert_emitted( + @array![ + Event { + from: event_emitter.contract_address, + name: 'AffiliateRewardClaimed', + keys: array![], + data: expected_data + } + ] + ); + + teardown(data_store.contract_address); +} From aa3291d36eb170ccabd0db7841b9b6a2c5f0eba4 Mon Sep 17 00:00:00 2001 From: VictorONN <73134512+VictorONN@users.noreply.github.com> Date: Sat, 7 Oct 2023 01:58:52 +0300 Subject: [PATCH 11/28] Implementing StrictBank functions and tests (#426) * strict bank start * strict bank contract and tests * All strict bank tests running * formatted --------- Co-authored-by: Michel <105498726+Sk8erboi84@users.noreply.github.com> Co-authored-by: sparqet <37338401+sparqet@users.noreply.github.com> Co-authored-by: zarboq <37303126+zarboq@users.noreply.github.com> --- src/bank/strict_bank.cairo | 99 ++++++-- src/tests/bank/test_strict_bank.cairo | 318 ++++++++++++++++++++++++++ 2 files changed, 395 insertions(+), 22 deletions(-) create mode 100644 src/tests/bank/test_strict_bank.cairo diff --git a/src/bank/strict_bank.cairo b/src/bank/strict_bank.cairo index fca76e38..f748fc13 100644 --- a/src/bank/strict_bank.cairo +++ b/src/bank/strict_bank.cairo @@ -5,8 +5,8 @@ // ************************************************************************* // Core lib imports. -use core::traits::Into; -use starknet::ContractAddress; +use traits::{Into, TryInto}; +use starknet::{ContractAddress, get_contract_address}; // ************************************************************************* // Interface of the `StrictBank` contract. @@ -32,20 +32,22 @@ trait IStrictBank { ref self: TContractState, token: ContractAddress, receiver: ContractAddress, amount: u128, ); - /// Updates the `token_balances` in case of token burns or similar balance changes. - /// The `prev_balance` is not validated to be more than the `next_balance` as this - /// could allow someone to block this call by transferring into the contract. + /// Records a token transfer into the contract /// # Arguments - /// * `token` - The token to record the burn for. - /// # Returns - /// * The new balance. - fn sync_token_balance(ref self: TContractState, token: ContractAddress) -> u128; - /// Records a token transfer into the contract. - /// # Arguments - /// * `token` - The token address to transfer. - /// # Returns - /// * The amount of tokens transferred. + /// * `token` - The token to record the transfer for + /// # Return + /// The amount of tokens transferred in fn record_transfer_in(ref self: TContractState, token: ContractAddress) -> u128; + + /// this can be used to update the tokenBalances in case of token burns + /// or similar balance changes + /// the prevBalance is not validated to be more than the nextBalance as this + /// could allow someone to block this call by transferring into the contract + /// # Arguments + /// * `token` - The token to record the burn for + /// # Return + /// The new balance + fn sync_token_balance(ref self: TContractState, token: starknet::ContractAddress) -> u128; } #[starknet::contract] @@ -55,19 +57,25 @@ mod StrictBank { // ************************************************************************* // Core lib imports. - use starknet::{get_caller_address, ContractAddress, contract_address_const}; - + use core::traits::TryInto; + use starknet::{ + get_caller_address, get_contract_address, ContractAddress, contract_address_const + }; use debug::PrintTrait; // Local imports. use satoru::bank::bank::{Bank, IBank}; use super::IStrictBank; + use satoru::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + use satoru::role::role_module::{RoleModule, IRoleModule}; // ************************************************************************* // STORAGE // ************************************************************************* #[storage] - struct Storage {} + struct Storage { + token_balances: LegacyMap::, + } // ************************************************************************* // CONSTRUCTOR @@ -85,7 +93,6 @@ mod StrictBank { self.initialize(data_store_address, role_store_address); } - // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* @@ -108,16 +115,64 @@ mod StrictBank { ) { let mut state: Bank::ContractState = Bank::unsafe_new_contract_state(); IBank::transfer_out(ref state, token, receiver, amount); + self.after_transfer_out_infernal(token); } fn sync_token_balance(ref self: ContractState, token: ContractAddress) -> u128 { - // TODO - 0 + // assert that caller is a controller + let mut role_module: RoleModule::ContractState = + RoleModule::unsafe_new_contract_state(); + role_module.only_controller(); + + let this_contract = get_contract_address(); + let next_balance: u128 = IERC20Dispatcher { contract_address: token } + .balance_of(this_contract) + .try_into() + .unwrap(); + self.token_balances.write(token, next_balance); + next_balance } fn record_transfer_in(ref self: ContractState, token: ContractAddress) -> u128 { - // TODO - 0 + // assert that caller is a controller + let mut role_module: RoleModule::ContractState = + RoleModule::unsafe_new_contract_state(); + role_module.only_controller(); + + self.record_transfer_in_internal(token) + } + } + + #[generate_trait] + impl PrivateMethods of PrivateMethodsTrait { + /// Transfer tokens from this contract to a receiver + /// # Arguments + /// * `token` - token the token to transfer + fn after_transfer_out_infernal(ref self: ContractState, token: starknet::ContractAddress) { + let this_contract = get_contract_address(); + let balance: u128 = IERC20Dispatcher { contract_address: token } + .balance_of(this_contract) + .try_into() + .unwrap(); + self.token_balances.write(token, balance); + } + + /// Records a token transfer into the contract + /// # Arguments + /// * `token` - The token to record the transfer for + /// # Return + /// The amount of tokens transferred in + fn record_transfer_in_internal( + ref self: ContractState, token: starknet::ContractAddress + ) -> u128 { + let prev_balance: u128 = self.token_balances.read(token); + let this_contract = get_contract_address(); + let next_balance: u128 = IERC20Dispatcher { contract_address: token } + .balance_of(this_contract) + .try_into() + .unwrap(); + self.token_balances.write(token, next_balance); + next_balance - prev_balance } } } diff --git a/src/tests/bank/test_strict_bank.cairo b/src/tests/bank/test_strict_bank.cairo new file mode 100644 index 00000000..bf4d64fb --- /dev/null +++ b/src/tests/bank/test_strict_bank.cairo @@ -0,0 +1,318 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +use result::ResultTrait; +use traits::{TryInto, Into}; +use starknet::{ContractAddress, get_caller_address, contract_address_const, ClassHash,}; +use integer::u256_from_felt252; +use debug::PrintTrait; +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait, ContractClass}; + +// Local imports. +use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; +use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::role::role; + +/// Setup required contracts. +fn setup_contracts() -> ( + // This caller address will be used with `start_prank` cheatcode to mock the caller address., + ContractAddress, + // This receiver address will be used with `start_prank` cheatcode to mock the receiver address., + ContractAddress, + // Interface to interact with the `RoleStore` contract. + IRoleStoreDispatcher, + // Interface to interact with the `DataStore` contract. + IDataStoreDispatcher, + // Interface to interact with the `Bank` contract. + IBankDispatcher, + // Interface to interact with the `StrictBank` contract. + IStrictBankDispatcher +) { + // Deploy the role store contract. + let role_store_address = deploy_role_store(); + + // Create a role store dispatcher. + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + + // Deploy the contract. + let data_store_address = deploy_data_store(role_store_address); + + // Create a safe dispatcher to interact with the contract. + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + + // Deploy the bank contract + let bank_address = deploy_bank(data_store_address, role_store_address); + + //Create a safe dispatcher to interact with the Bank contract. + let bank = IBankDispatcher { contract_address: bank_address }; + + // Deploy the strict bank contract + let strict_bank_address = deploy_strict_bank(data_store_address, role_store_address); + + //Create a safe dispatcher to interact with the StrictBank contract. + let strict_bank = IStrictBankDispatcher { contract_address: strict_bank_address }; + + // start prank and give controller role to caller_address + let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let receiver_address: ContractAddress = 0x202.try_into().unwrap(); + start_prank(role_store_address, caller_address); + role_store.grant_role(caller_address, role::CONTROLLER); + start_prank(data_store_address, caller_address); + start_prank(strict_bank_address, caller_address); + + (caller_address, receiver_address, role_store, data_store, bank, strict_bank) +} + +// /// Utility function to deploy a bank contract and return its address. +fn deploy_bank( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let contract = declare('Bank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + contract.deploy(@constructor_calldata).unwrap() +} + +/// Utility function to deploy a strict bank contract and return its address. +fn deploy_strict_bank( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let contract = declare('StrictBank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + contract.deploy(@constructor_calldata).unwrap() +} + +/// Utility function to deploy a data store contract and return its address. +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let mut constructor_calldata = array![]; + constructor_calldata.append(role_store_address.into()); + contract.deploy(@constructor_calldata).unwrap() +} + +/// Utility function to deploy a data store contract and return its address. +/// Copied from `tests/role/test_role_store.rs`. +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let constructor_arguments: @Array:: = @ArrayTrait::new(); + contract.deploy(constructor_arguments).unwrap() +} + +// ********************************************************************************************* +// * TEARDOWN * +// ********************************************************************************************* +fn teardown(data_store: IDataStoreDispatcher, strict_bank: IStrictBankDispatcher) { + stop_prank(data_store.contract_address); + stop_prank(strict_bank.contract_address); +} + + +#[test] +#[should_panic(expected: ('already_initialized',))] +fn test_initialize() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // try initializing after previously initializing in setup + strict_bank.initialize(data_store.contract_address, role_store.contract_address); + teardown(data_store, strict_bank); +} + +#[test] +fn given_normal_conditions_when_transfer_out_then_works() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array![ + 'satoru', 'STU', 1000, 0, strict_bank.contract_address.into() + ]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + // call the transfer_out function + strict_bank.transfer_out(erc20_contract_address, receiver_address, 100_u128); + // check that the contract balance reduces + let contract_balance = erc20_dispatcher.balance_of(strict_bank.contract_address); + assert(contract_balance == u256_from_felt252(900), 'transfer_out failed'); + // check that the balance of the receiver increases + let receiver_balance = erc20_dispatcher.balance_of(receiver_address); + assert(receiver_balance == u256_from_felt252(100), 'transfer_out failed'); + // teardown + teardown(data_store, strict_bank); +} + +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_has_no_controller_role_when_transfer_out_then_fails() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array![ + 'satoru', 'STU', 1000, 0, strict_bank.contract_address.into() + ]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + // stop prank as caller_address and start prank as receiver_address who has no controller role + stop_prank(strict_bank.contract_address); + start_prank(strict_bank.contract_address, receiver_address); + // call the transfer_out function + strict_bank.transfer_out(erc20_contract_address, caller_address, 100); + // teardown + teardown(data_store, strict_bank); +} + +#[test] +#[should_panic(expected: ('self_transfer_not_supported',))] +fn given_receiver_is_contract_when_transfer_out_then_fails() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // deploy erc20 token. Mint to bank since we call transfer out in bank contract which restricts sending to self + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array![ + 'satoru', 'STU', 1000, 0, strict_bank.contract_address.into() + ]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + strict_bank.transfer_out(erc20_contract_address, strict_bank.contract_address, 100_u128); + + //teardown + teardown(data_store, strict_bank); +} + +#[test] +fn given_normal_conditions_when_record_transfer_in_works() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array!['satoru', 'STU', 1000, 0, caller_address.into()]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + start_prank(erc20_contract_address, caller_address); + + // send tokens into strict bank + erc20_dispatcher.transfer(strict_bank.contract_address, u256_from_felt252(50)); + + let new_balance: u128 = erc20_dispatcher + .balance_of(strict_bank.contract_address) + .try_into() + .unwrap(); + + assert( + strict_bank.record_transfer_in(erc20_contract_address) == new_balance, + 'unsuccessful transfer in' + ); + + // teardown + teardown(data_store, strict_bank); +} + +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_has_no_controller_role_when_record_transfer_in_then_fails() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array![ + 'satoru', 'STU', 1000, 0, strict_bank.contract_address.into() + ]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + // stop prank as caller_address and start prank as receiver_address who has no controller role + stop_prank(strict_bank.contract_address); + start_prank(strict_bank.contract_address, receiver_address); + // call the transfer_out function with receiver address + strict_bank.record_transfer_in(erc20_contract_address); + // teardown + teardown(data_store, strict_bank); +} + +#[test] +fn given_normal_conditions_when_sync_token_balance_passes() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array!['satoru', 'STU', 1000, 0, caller_address.into()]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + start_prank(erc20_contract_address, caller_address); + + // send tokens into strict bank + erc20_dispatcher.transfer(strict_bank.contract_address, u256_from_felt252(50)); + + strict_bank.sync_token_balance(erc20_contract_address); + + // teardown + teardown(data_store, strict_bank); +} + +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_has_no_controller_role_when_sync_token_balance_then_fails() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array!['satoru', 'STU', 1000, 0, caller_address.into()]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + start_prank(erc20_contract_address, caller_address); + + // send tokens into strict bank + erc20_dispatcher.transfer(strict_bank.contract_address, u256_from_felt252(50)); + + // stop prank as caller_address and start prank as receiver_address who has no controller role + stop_prank(strict_bank.contract_address); + start_prank(strict_bank.contract_address, receiver_address); + // call the sync_token_balance function with receiver address + strict_bank.sync_token_balance(erc20_contract_address); + // teardown + teardown(data_store, strict_bank); +} + From 327f75683d182b4f77f33e3437fb36766239ebfa Mon Sep 17 00:00:00 2001 From: kasteph Date: Sun, 8 Oct 2023 13:14:17 +0200 Subject: [PATCH 12/28] =?UTF-8?q?=E2=9C=A8=20execute=5Fdeposit=5Futils=20f?= =?UTF-8?q?unctions=20(#449)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ execute_deposit_utils fn * fix: clone fees and rm extra line * fix: prevent BadMergeBaseMismatch by adding else to if- * fix: fmt issue --- src/deposit/error.cairo | 10 + src/deposit/execute_deposit_utils.cairo | 421 +++++++++++++++++++++++- src/event/event_utils.cairo | 3 +- src/order/increase_order_utils.cairo | 7 +- src/pricing/swap_pricing_utils.cairo | 2 +- src/reader/reader_pricing_utils.cairo | 2 +- src/swap/error.cairo | 9 + 7 files changed, 431 insertions(+), 23 deletions(-) diff --git a/src/deposit/error.cairo b/src/deposit/error.cairo index 3a936f54..cc92949c 100644 --- a/src/deposit/error.cairo +++ b/src/deposit/error.cairo @@ -4,4 +4,14 @@ mod DepositError { const CANT_BE_ZERO: felt252 = 'deposit account cant be 0'; const EMPTY_DEPOSIT_AMOUNTS: felt252 = 'empty_deposit_amounts'; const EMPTY_DEPOSIT: felt252 = 'empty_deposit'; + const EMPTY_DEPOSIT_AMOUNTS_AFTER_SWAP: felt252 = 'empty deposit amount after swap'; + const INVALID_POOL_VALUE_FOR_DEPOSIT: felt252 = 'invalid pool value for deposit'; + + + fn MIN_MARKET_TOKENS(received: u128, expected: u128) { + let mut data = array!['invalid swap output token']; + data.append(received.into()); + data.append(expected.into()); + panic(data) + } } diff --git a/src/deposit/execute_deposit_utils.cairo b/src/deposit/execute_deposit_utils.cairo index 519fb7ef..742a450f 100644 --- a/src/deposit/execute_deposit_utils.cairo +++ b/src/deposit/execute_deposit_utils.cairo @@ -10,14 +10,36 @@ use result::ResultTrait; use debug::PrintTrait; // Local imports. -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; +use satoru::callback::callback_utils::after_deposit_execution; +use satoru::data::{ + keys::{deposit_fee_type, ui_deposit_fee_type, max_pnl_factor_for_deposits}, + data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait} +}; +use satoru::deposit::{ + deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}, error::DepositError +}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; -use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; +use satoru::event::event_utils::{LogData, set_item_uint_items, UintItems}; +use satoru::fee::fee_utils; +use satoru::gas::gas_utils::pay_execution_fee_deposit; +use satoru::market::{ + market::Market, market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}, + market_utils +}; use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; -use satoru::price::price::Price; -use satoru::market::market::Market; -use satoru::utils::span32::Span32; +use satoru::oracle::{oracle::{IOracleDispatcher, IOracleDispatcherTrait}, oracle_utils}; +use satoru::price::price::{Price, PriceTrait}; +use satoru::pricing::swap_pricing_utils::{ + get_swap_fees, get_price_impact_usd, GetPriceImpactUsdParams +}; +use satoru::swap::swap_utils; +use satoru::swap::error::SwapError; +use satoru::utils::{ + calc::{to_unsigned, to_signed}, i128::{I128Default}, precision, span32::Span32, + starknet_utils::{sn_gasleft, sn_gasprice} +}; + /// Struct used in executeDeposit to avoid stack too deep errors #[derive(Drop, Serde)] @@ -67,6 +89,7 @@ struct _ExecuteDepositParams { price_impact_usd: u128 } +#[derive(Drop, Default)] struct ExecuteDepositCache { long_token_amount: u128, short_token_amount: u128, @@ -80,29 +103,395 @@ struct ExecuteDepositCache { /// # Arguments /// * `params` - ExecuteDepositParams. #[inline(always)] -fn execute_deposit(params: ExecuteDepositParams) { //TODO +fn execute_deposit(params: ExecuteDepositParams) { + // 63/64 gas is forwarded to external calls, reduce the startingGas to account for this + let starting_gas = params.starting_gas - sn_gasleft(array![]) / 63; + + let deposit = params.data_store.get_deposit(params.key).unwrap(); + params.data_store.remove_deposit(params.key, deposit.account); + + let mut cache: ExecuteDepositCache = Default::default(); + + assert(deposit.account.is_non_zero(), DepositError::EMPTY_DEPOSIT); + + oracle_utils::validate_block_number_within_range( + params.min_oracle_block_numbers.span(), + params.max_oracle_block_numbers.span(), + deposit.updated_at_block, + ); + + let market = market_utils::get_enabled_market(params.data_store, deposit.market); + let prices = market_utils::get_market_prices(params.oracle, market); + + // deposits should improve the pool state but it should be checked if + // the max pnl factor for deposits is exceeded as this would lead to the + // price of the market token decreasing below a target minimum percentage + // due to pnl + // note that this is just a validation for deposits, there is no actual + // minimum price for a market token + market_utils::validate_max_pnl( + params.data_store, + market, + prices, + max_pnl_factor_for_deposits(), + max_pnl_factor_for_deposits(), + ); + + cache + .long_token_amount = + swap( + @params, + deposit.long_token_swap_path, + deposit.initial_long_token, + deposit.initial_long_token_amount, + market.market_token, + market.long_token, + deposit.ui_fee_receiver, + ); + + cache + .short_token_amount = + swap( + @params, + deposit.short_token_swap_path, + deposit.initial_short_token, + deposit.initial_short_token_amount, + market.market_token, + market.short_token, + deposit.ui_fee_receiver, + ); + + assert( + cache.long_token_amount == 0 && cache.short_token_amount == 0, + DepositError::EMPTY_DEPOSIT_AMOUNTS_AFTER_SWAP + ); + + cache.long_token_usd = cache.long_token_amount * prices.long_token_price.mid_price(); + cache.short_token_usd = cache.short_token_amount * prices.short_token_price.mid_price(); + + cache + .price_impact_usd = + get_price_impact_usd( + GetPriceImpactUsdParams { + data_store: params.data_store, + market: market, + token_a: market.long_token, + token_b: market.short_token, + price_for_token_a: prices.long_token_price.mid_price(), + price_for_token_b: prices.short_token_price.mid_price(), + usd_delta_for_token_a: to_signed(cache.long_token_usd, true), + usd_delta_for_token_b: to_signed(cache.short_token_usd, false), + } + ); + + if cache.long_token_amount > 0 { + let _params = _ExecuteDepositParams { + market: market, + account: deposit.account, + receiver: deposit.receiver, + ui_fee_receiver: deposit.ui_fee_receiver, + token_in: market.long_token, + token_out: market.short_token, + token_in_price: prices.long_token_price, + token_out_price: prices.short_token_price, + amount: cache.long_token_amount, + price_impact_usd: precision::mul_div( + to_unsigned(cache.price_impact_usd), + cache.long_token_usd, + cache.long_token_usd + cache.short_token_usd + ) + }; + + cache.received_market_tokens += execute_deposit_helper(@params, @_params); + } else if cache.short_token_amount > 0 { + let _params = _ExecuteDepositParams { + market: market, + account: deposit.account, + receiver: deposit.receiver, + ui_fee_receiver: deposit.ui_fee_receiver, + token_in: market.short_token, + token_out: market.long_token, + token_in_price: prices.short_token_price, + token_out_price: prices.long_token_price, + amount: cache.short_token_amount, + price_impact_usd: precision::mul_div( + to_unsigned(cache.price_impact_usd), + cache.short_token_usd, + cache.long_token_usd + cache.short_token_usd + ) + }; + + cache.received_market_tokens += execute_deposit_helper(@params, @_params); + } + + if cache.received_market_tokens < deposit.min_market_tokens { + DepositError::MIN_MARKET_TOKENS(cache.received_market_tokens, deposit.min_market_tokens); + } + + market_utils::validate_market_token_balance_with_address( + params.data_store, market.market_token + ); + + (params.event_emitter) + .emit_deposit_executed( + params.key, + cache.long_token_amount, + cache.short_token_amount, + cache.received_market_tokens, + ); + + let event_data: LogData = Default::default(); + let mut uint_items: UintItems = Default::default(); + set_item_uint_items(uint_items, 0, 'received_market_tokens', cache.received_market_tokens); + after_deposit_execution(params.key, deposit, event_data); + + pay_execution_fee_deposit( + params.data_store, + params.event_emitter, + params.deposit_vault, + deposit.execution_fee, + params.starting_gas, + params.keeper, + deposit.account, + ); } /// Executes a deposit. /// # Arguments -/// * `params` - ExecuteDepositParams. -/// * `_params` - _ExecuteDepositParams. +/// * `params` - @ExecuteDepositParams. +/// * `_params` - @_ExecuteDepositParams. #[inline(always)] -fn _execute_deposit(params: ExecuteDepositParams, _params: _ExecuteDepositParams) -> u128 { - //TODO - 0 +fn execute_deposit_helper(params: @ExecuteDepositParams, _params: @_ExecuteDepositParams) -> u128 { + // for markets where longToken == shortToken, the price impact factor should be set to zero + // in which case, the priceImpactUsd would always equal zero + let fees = get_swap_fees( + *params.data_store, + *_params.market.market_token, + *_params.amount, + *_params.price_impact_usd > 0, + *_params.ui_fee_receiver, + ); + + fee_utils::increment_claimable_fee_amount( + *params.data_store, + *params.event_emitter, + *_params.market.market_token, + *_params.token_in, + fees.fee_receiver_amount, + deposit_fee_type(), + ); + + fee_utils::increment_claimable_ui_fee_amount( + *params.data_store, + *params.event_emitter, + *_params.ui_fee_receiver, + *_params.market.market_token, + *_params.token_in, + fees.ui_fee_amount, + ui_deposit_fee_type(), + ); + + (*params.event_emitter) + .emit_swap_fees_collected( + *_params.market.market_token, + *_params.token_in, + *_params.token_in_price.min, + 'deposit', + fees.clone(), + ); + + let market_pool_value_info = market_utils::get_pool_value_info( + *params.data_store, + *_params.market, + (*params.oracle).get_primary_price(*_params.market.index_token), + if *_params.token_in == *_params.market.long_token { + *_params.token_in_price + } else { + *_params.token_out_price + }, + if *_params.token_in == *_params.market.short_token { + *_params.token_in_price + } else { + *_params.token_out_price + }, + max_pnl_factor_for_deposits(), + true, + ); + + assert(market_pool_value_info.pool_value < 0, DepositError::INVALID_POOL_VALUE_FOR_DEPOSIT); + + let mut mint_amount = 0; + let pool_value = market_pool_value_info.pool_value; + let market_tokens_supply = market_utils::get_market_token_supply( + IMarketTokenDispatcher { contract_address: *_params.market.market_token } + ); + + assert( + pool_value == 0 && market_tokens_supply > 0, DepositError::INVALID_POOL_VALUE_FOR_DEPOSIT + ); + + (*params.event_emitter) + .emit_market_pool_value_info( + *_params.market.market_token, market_pool_value_info, market_tokens_supply, + ); + + // the pool_value and market_tokens_supply is cached for the mint_amount calculation below + // so the effect of any positive price impact on the pool_value and market_tokens_supply + // would not be accounted for + // + // for most cases, this should not be an issue, since the pool_value and market_tokens_supply + // should have been proportionately increased + // + // e.g. if the pool_value is $100 and market_tokens_supply is 100, and there is a positive price impact + // of $10, the pool_value should have increased by $10 and the market_tokens_supply should have been increased by 10 + // + // there is a case where this may be an issue which is when all tokens are withdrawn from an existing market + // and the market_tokens_supply is reset to zero, but the pool_value is not entirely zero + // the case where this happens should be very rare and during withdrawal the pool_value should be close to zero + // + // however, in case this occurs, the usdToMarketTokenAmount will mint an additional number of market tokens + // proportional to the existing pool_value + // + // since the pool_value and market_tokens_supply is cached, this could occur once during positive price impact + // and again when calculating the mint_amount + // + // to avoid this, set the price_impact_usd to be zero for this case + let mut price_impact_usd = *_params.price_impact_usd; + + if price_impact_usd > 0 && market_tokens_supply == 0 { + price_impact_usd = 0; + } + + let mut amount_after_fees = fees.amount_after_fees; + + if price_impact_usd > 0 { + // when there is a positive price impact factor, + // tokens from the swap impact pool are used to mint additional market tokens for the user + // for example, if 50,000 USDC is deposited and there is a positive price impact + // an additional 0.005 ETH may be used to mint market tokens + // the swap impact pool is decreased by the used amount + // + // price_impact_usd is calculated based on pricing assuming only depositAmount of tokenIn + // was added to the pool + // since impactAmount of tokenOut is added to the pool here, the calculation of + // the price impact would not be entirely accurate + // + // it is possible that the addition of the positive impact amount of tokens into the pool + // could increase the imbalance of the pool, for most cases this should not be a significant + // change compared to the improvement of balance from the actual deposit + let positive_impact_amount = to_unsigned( + market_utils::apply_swap_impact_with_cap( + *params.data_store, + *params.event_emitter, + *_params.market.market_token, + *_params.token_out, + *_params.token_out_price, + to_signed(price_impact_usd, true), + ) + ); + + // calculate the usd amount using positiveImpactAmount since it may + // be capped by the max available amount in the impact pool + // use tokenOutPrice.max to get the USD value since the positiveImpactAmount + // was calculated using a USD value divided by tokenOutPrice.max + // + // for the initial deposit, the pool value and token supply would be zero + // so the market token price is treated as 1 USD + // + // it is possible for the pool value to be more than zero and the token supply + // to be zero, in that case, the market token price is also treated as 1 USD + mint_amount = + market_utils::usd_to_market_token_amount( + positive_impact_amount * *_params.token_out_price.max, + to_unsigned(pool_value), + market_tokens_supply, + ); + + market_utils::apply_delta_to_pool_amount( + *params.data_store, + *params.event_emitter, + *_params.market, + *_params.token_out, + to_signed(positive_impact_amount, false), + ); + + market_utils::validate_pool_amount(params.data_store, _params.market, *_params.token_out,); + + if (price_impact_usd < 0) { + // when there is a negative price impact factor, + // less of the deposit amount is used to mint market tokens + // for example, if 10 ETH is deposited and there is a negative price impact + // only 9.995 ETH may be used to mint market tokens + // the remaining 0.005 ETH will be stored in the swap impact pool + let negative_impact_amount = market_utils::apply_swap_impact_with_cap( + *params.data_store, + *params.event_emitter, + *_params.market.market_token, + *_params.token_out, + *_params.token_out_price, + to_signed(price_impact_usd, false), + ); + + amount_after_fees -= to_unsigned((-negative_impact_amount)); + } + } + + mint_amount += + market_utils::usd_to_market_token_amount( + amount_after_fees * *_params.token_in_price.min, + to_unsigned(pool_value), + market_tokens_supply, + ); + + market_utils::apply_delta_to_pool_amount( + *params.data_store, + *params.event_emitter, + *_params.market, + *_params.token_out, + to_signed(amount_after_fees + fees.fee_amount_for_pool, false), + ); + + market_utils::validate_pool_amount(params.data_store, _params.market, *_params.token_in); + + IMarketTokenDispatcher { contract_address: *_params.market.market_token } + .mint(*_params.receiver, mint_amount); + + mint_amount } #[inline(always)] fn swap( - params: ExecuteDepositParams, + params: @ExecuteDepositParams, swap_path: Span32, initial_token: ContractAddress, - intput_amount: u128, + input_amount: u128, market: ContractAddress, expected_output_token: ContractAddress, ui_fee_receiver: ContractAddress ) -> u128 { - //TODO - 0 + let swap_path_markets = market_utils::get_swap_path_markets(*params.data_store, swap_path,); + + let (output_token, output_amount) = swap_utils::swap( + @swap_utils::SwapParams { + data_store: *params.data_store, + event_emitter: *params.event_emitter, + oracle: *params.oracle, + bank: IBankDispatcher { contract_address: market }, + key: *params.key, + token_in: initial_token, + amount_in: input_amount, + swap_path_markets: swap_path_markets.span(), + min_output_amount: 0, + receiver: market, + ui_fee_receiver: ui_fee_receiver, + } + ); + + if output_token != expected_output_token { + SwapError::INVALID_SWAP_OUTPUT_TOKEN(output_token, expected_output_token) + } + + market_utils::validate_markets_token_balance(*params.data_store, swap_path_markets.span(),); + + output_amount } diff --git a/src/event/event_utils.cairo b/src/event/event_utils.cairo index f61794de..4b4cc7d1 100644 --- a/src/event/event_utils.cairo +++ b/src/event/event_utils.cairo @@ -5,8 +5,7 @@ use traits::Default; use satoru::utils::traits::ContractAddressDefault; //TODO Switch the append with a set in the functions when its available - -#[derive(Default, Drop, Serde)] +#[derive(Drop, Serde)] struct EventLogData { cant_be_empty: u128, // remove // TODO diff --git a/src/order/increase_order_utils.cairo b/src/order/increase_order_utils.cairo index a509ebce..5107cc1d 100644 --- a/src/order/increase_order_utils.cairo +++ b/src/order/increase_order_utils.cairo @@ -24,8 +24,8 @@ use alexandria_data_structures::array_ext::SpanTraitExt; /// This function should return an EventLogData cause the callback_utils /// needs it. We need to find a solution for that case. #[inline(always)] -fn process_order(params: ExecuteOrderParams) -> event_utils::EventLogData { - market_utils::validate_position_market_check(params.contracts.data_store, params.market); +fn process_order(params: ExecuteOrderParams) -> event_utils::LogData { + market_utils::validate_position_market(params.contracts.data_store, params.market.market_token); let (collateral_token, collateral_increment_amount) = swap_utils::swap( @swap_utils::SwapParams { @@ -88,7 +88,8 @@ fn process_order(params: ExecuteOrderParams) -> event_utils::EventLogData { collateral_increment_amount ); - event_utils::EventLogData { cant_be_empty: 'todo' } // TODO switch to LogData + let log: event_utils::LogData = Default::default(); // TODO + log } /// Validate the oracle block numbers used for the prices in the oracle. diff --git a/src/pricing/swap_pricing_utils.cairo b/src/pricing/swap_pricing_utils.cairo index 670c2d09..806aa032 100644 --- a/src/pricing/swap_pricing_utils.cairo +++ b/src/pricing/swap_pricing_utils.cairo @@ -53,7 +53,7 @@ struct PoolParams { } /// Struct to contain swap fee values. -#[derive(Drop, Clone, starknet::Store, Serde)] +#[derive(Copy, Drop, starknet::Store, Serde)] struct SwapFees { /// The fee amount for the fee receiver. fee_receiver_amount: u128, diff --git a/src/reader/reader_pricing_utils.cairo b/src/reader/reader_pricing_utils.cairo index 27b62c9d..2a2ea11e 100644 --- a/src/reader/reader_pricing_utils.cairo +++ b/src/reader/reader_pricing_utils.cairo @@ -121,7 +121,7 @@ fn get_swap_amount_out( // an additional 100 USDC may be sent to the user // the swap impact pool is decreased by the used amount - cache.amount_in = fees.clone().amount_after_fees; + cache.amount_in = fees.amount_after_fees; //round amount_out down error_utils::check_division_by_zero(cache.token_out_price.max, 'token_out_price.max'); cache.amount_out = cache.amount_in * cache.token_in_price.min / cache.token_out_price.max; diff --git a/src/swap/error.cairo b/src/swap/error.cairo index b756f4c8..33c572b6 100644 --- a/src/swap/error.cairo +++ b/src/swap/error.cairo @@ -29,4 +29,13 @@ mod SwapError { data.append(market.into()); panic(data) } + + fn INVALID_SWAP_OUTPUT_TOKEN( + output_token: ContractAddress, expected_output_token: ContractAddress + ) { + let mut data = array!['invalid swap output token']; + data.append(output_token.into()); + data.append(expected_output_token.into()); + panic(data) + } } From de46fabc2404c23c095c32b6d25b66c4b3ff953a Mon Sep 17 00:00:00 2001 From: Tbelleng <117627242+Tbelleng@users.noreply.github.com> Date: Sun, 8 Oct 2023 21:25:43 +0000 Subject: [PATCH 13/28] Feat: Adding a Contirbutor profil (#501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 10 functions done * almost finished, debug next * debug time * debuging * pushing recent changes/ still bug because missing functions * debuging finished * adding comments on functions * almost clean * Emit bug * programm compile 🎉 * resolving last test * All test passed * resolve request * 1 test failed because of max swap path lenght exceed test * resolving failed test * resolve * solving * compilation resolved * Added as a Contributor * Adding profil on ReadMe --------- Co-authored-by: Michel <105498726+Sk8erboi84@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 +++ 2 files changed, 12 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index a1152b48..3a7a9bdb 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -261,6 +261,15 @@ "contributions": [ "code" ] + }, + { + "login": "Tbelleng", + "name": "Tbelleng", + "avatar_url": "https://avatars.githubusercontent.com/u/117627242?v=4", + "profile": "https://github.com/Tbelleng", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 344f5672..44b407c0 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d ftupas
ftupas

💻 lambda-0x
lambda-0x

💻 + + Tbelleng
Tbelleng

💻 + From 4c1a4f326c38be8cbf4077a0b0b206d7d8890328 Mon Sep 17 00:00:00 2001 From: akhercha Date: Sun, 8 Oct 2023 23:55:31 +0200 Subject: [PATCH 14/28] test: Added tests for record_transfer_in (#502) * test(record_transfer_in_function): Added unit tests for record_transfer_in * test(record_transfer_in_function): Added unit tests * test(record_transfer_in_function): Better error message * test(record_transfer_in_function): Mock + Overflow prevented * test(record_transfer_in_function): Removed useless import * test(record_transfer_in_function): record_transfer_in panic on sub overflow * test(record_transfer_in_function): Quick test refacto * test(record_transfer_in_function): Unused variables in tests --------- Co-authored-by: akhercha Co-authored-by: zarboq <37303126+zarboq@users.noreply.github.com> --- src/deposit/deposit_vault.cairo | 4 +- tests/deposit/test_deposit_vault.cairo | 59 ++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/deposit/deposit_vault.cairo b/src/deposit/deposit_vault.cairo index 54c26857..79828e98 100644 --- a/src/deposit/deposit_vault.cairo +++ b/src/deposit/deposit_vault.cairo @@ -114,8 +114,8 @@ mod DepositVault { } fn record_transfer_in(ref self: ContractState, token: ContractAddress) -> u128 { - // TODO - 0 + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::record_transfer_in(ref state, token) } } } diff --git a/tests/deposit/test_deposit_vault.cairo b/tests/deposit/test_deposit_vault.cairo index 96667d72..6808e2d7 100644 --- a/tests/deposit/test_deposit_vault.cairo +++ b/tests/deposit/test_deposit_vault.cairo @@ -10,7 +10,7 @@ use starknet::{ ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, ClassHash, }; -use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; +use snforge_std::{declare, start_prank, stop_prank, start_mock_call, ContractClassTrait}; use traits::{TryInto, Into}; // Local imports. @@ -89,11 +89,62 @@ fn given_receiver_is_contract_when_transfer_out_then_fails() { teardown(data_store, deposit_vault); } -/// TODO: implement the tests when record_transfer_in is implemented #[test] -#[should_panic(expected: ('NOT IMPLEMENTED YET',))] fn given_normal_conditions_when_record_transfer_in_then_works() { - assert(true == false, 'NOT IMPLEMENTED YET') + let (_, _, _, data_store, deposit_vault, erc20) = setup(); + + let initial_balance: u128 = u128_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u128 = deposit_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + teardown(data_store, deposit_vault); +} + +#[test] +fn given_more_balance_when_2nd_record_transfer_in_then_works() { + let (_, _, _, data_store, deposit_vault, erc20) = setup(); + + let initial_balance: u128 = u128_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u128 = deposit_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + let tokens_transfered_in: u128 = 250; + let mock_balance_with_more_tokens: u256 = (initial_balance + tokens_transfered_in).into(); + start_mock_call(erc20.contract_address, 'balance_of', mock_balance_with_more_tokens); + + let tokens_received: u128 = deposit_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == tokens_transfered_in, 'incorrect received amount'); + + teardown(data_store, deposit_vault); +} + +#[test] +#[should_panic(expected: ('u128_sub Overflow',))] +fn given_less_balance_when_2nd_record_transfer_in_then_fails() { + let (_, _, _, data_store, deposit_vault, erc20) = setup(); + + let initial_balance: u128 = u128_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u128 = deposit_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + let tokens_transfered_out: u128 = 250; + let mock_balance_with_less_tokens: u256 = (initial_balance - tokens_transfered_out).into(); + start_mock_call(erc20.contract_address, 'balance_of', mock_balance_with_less_tokens); + + deposit_vault.record_transfer_in(erc20.contract_address); + + teardown(data_store, deposit_vault); +} + +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_is_not_controller_when_record_transfer_in_then_fails() { + let (caller_address, _, role_store, data_store, deposit_vault, erc20) = setup(); + + role_store.revoke_role(caller_address, role::CONTROLLER); + deposit_vault.record_transfer_in(erc20.contract_address); + + teardown(data_store, deposit_vault); } // ********************************************************************************************* From 8f1dfd0446fda7303b38c139af05c0df683384c4 Mon Sep 17 00:00:00 2001 From: Axel Izsak <98711930+axelizsak@users.noreply.github.com> Date: Mon, 9 Oct 2023 00:36:56 +0200 Subject: [PATCH 15/28] Improve tests of governable contract (#503) * add test to gov * fmt fix --- tests/mock/test_governable.cairo | 86 ++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tests/mock/test_governable.cairo b/tests/mock/test_governable.cairo index 2b4ce316..cdad3de1 100644 --- a/tests/mock/test_governable.cairo +++ b/tests/mock/test_governable.cairo @@ -118,6 +118,9 @@ fn setup_with_other_address() -> ( //TODO add more tests +// This test checks the 'only_gov' function under normal conditions. +// It sets up the environment with the correct initial governance, then calls `only_gov`. +// The test expects the call to succeed without any errors. #[test] fn given_normal_conditions_when_only_gov_then_works() { let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = @@ -126,6 +129,9 @@ fn given_normal_conditions_when_only_gov_then_works() { teardown(data_store.contract_address); } +// This test checks the `only_gov` function when the governance condition is not met. +// It sets up the environment with a different governance, then calls `only_gov`. +// The test expects the call to panic with the error 'Unauthorized gov caller'. #[test] #[should_panic(expected: ('Unauthorized gov caller',))] fn given_forbidden_when_only_gov_then_fails() { @@ -135,6 +141,10 @@ fn given_forbidden_when_only_gov_then_fails() { teardown(data_store.contract_address); } +// This test checks the `transfer_ownership` function under normal conditions. +// It sets up the environment with the correct initial governance, then calls `transfer_ownership` +// with a new governance address. +// The test expects the call to succeed and the ownership to be transferred without any errors. #[test] fn given_normal_conditions_when_transfer_ownership_then_works() { let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = @@ -143,3 +153,79 @@ fn given_normal_conditions_when_transfer_ownership_then_works() { governable.transfer_ownership(new_caller_address); teardown(data_store.contract_address); } + +/// This test case verifies the `transfer_ownership` function behavior when called by an unauthorized address. +/// The expected outcome is a panic with the error message "Unauthorized gov caller" which corresponds +/// to the `UNAUTHORIZED_GOV` error in the `MockError` module. +#[test] +#[should_panic(expected: ('Unauthorized gov caller',))] +fn given_unauthorized_caller_when_transfer_ownership_then_fails() { + // Setup the environment with a different caller address. + let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = + setup_with_other_address(); + + // Try to transfer ownership to a new address. + let new_uncaller_address: ContractAddress = 0x102.try_into().unwrap(); + governable.transfer_ownership(new_uncaller_address); + teardown(data_store.contract_address); +} + +/// This test checks the `accept_ownership` function under normal conditions. +/// It sets up the environment with the correct initial governance, then calls `transfer_ownership` +/// to a new governance address, followed by `accept_ownership` from the new governance address. +/// The test expects the call to succeed and the ownership to be accepted without any errors. +#[test] +fn given_normal_conditions_when_accept_ownership_then_works() { + let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = + setup(); + let new_caller_address: ContractAddress = 0x102.try_into().unwrap(); + + // Transfer the ownership to the new address. + governable.transfer_ownership(new_caller_address); + + // Update the prank context to the new governance address, to simulate the new governor accepting the ownership. + start_prank(governable.contract_address, new_caller_address); + + // Now call accept_ownership from the new governance address. + governable.accept_ownership(); + teardown(data_store.contract_address); +} + +/// This test checks the `accept_ownership` function under abnormal conditions. +/// It sets up the environment with the correct initial governance, then calls `transfer_ownership` +/// to a new governance address. However, `accept_ownership` is then called from an unauthorized address. +/// The test expects the call to panic with the error 'Unauthorized pending_gov caller'. +#[test] +#[should_panic(expected: ('Unauthorized pending_gov caller',))] +fn given_abnormal_conditions_when_accept_ownership_then_fails() { + let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = + setup(); + let new_caller_address: ContractAddress = 0x102.try_into().unwrap(); + let unauthorized_address: ContractAddress = 0x103.try_into().unwrap(); + + // Transfer the ownership to the new address. + governable.transfer_ownership(new_caller_address); + + // Update the prank context to an unauthorized address, to simulate an unauthorized attempt to accept the ownership. + start_prank(governable.contract_address, unauthorized_address); + + // Now call accept_ownership from the unauthorized address. + governable.accept_ownership(); + teardown(data_store.contract_address); +} + +#[test] +#[should_panic(expected: ('already_initialized',))] +fn given_already_initialized_when_initialize_then_fails() { + // Setup the environment. + let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = + setup(); + + // Assume that the contract has been initialized during setup. + // Try to initialize it again with the same event emitter address. + let event_emitter_address = event_emitter.contract_address; + + // This call should panic with the error 'already_initialized'. + governable.initialize(event_emitter_address); + teardown(data_store.contract_address); +} From 76ea7f1d801a0377dd058c5e442724d5a61bbc06 Mon Sep 17 00:00:00 2001 From: Axel Izsak <98711930+axelizsak@users.noreply.github.com> Date: Mon, 9 Oct 2023 10:32:14 +0200 Subject: [PATCH 16/28] add new contributors (#505) --- .all-contributorsrc | 36 ++++++++++++++++++++++++++++++++++++ README.md | 4 ++++ 2 files changed, 40 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3a7a9bdb..f6901995 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -270,6 +270,42 @@ "contributions": [ "code" ] + }, + { + "login": "dic0de", + "name": "dic0de", + "avatar_url": "https://avatars.githubusercontent.com/u/37063500?v=4", + "profile": "https://github.com/dic0de", + "contributions": [ + "code" + ] + }, + { + "login": "akhercha", + "name": "akhercha", + "avatar_url": "https://avatars.githubusercontent.com/u/22559023?v=4", + "profile": "https://github.com/akhercha", + "contributions": [ + "code" + ] + }, + { + "login": "VictorONN", + "name": "VictorONN", + "avatar_url": "https://avatars.githubusercontent.com/u/73134512?v=4", + "profile": "https://github.com/VictorONN", + "contributions": [ + "code" + ] + }, + { + "login": "kasteph", + "name": "kasteph", + "avatar_url": "https://avatars.githubusercontent.com/u/3408478?v=4", + "profile": "https://github.com/kasteph", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 44b407c0..84f4b4a2 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Tbelleng
Tbelleng

💻 + dic0de
dic0de

💻 + akhercha
akhercha

💻 + VictorONN
VictorONN

💻 + kasteph
kasteph

💻 From f0fa7e344d6ad76337c78f43c72aabdeeba09fef Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Thu, 12 Oct 2023 12:54:33 +0200 Subject: [PATCH 17/28] change panic error --- src/utils/precision.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index e237865e..21c2267c 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -177,7 +177,7 @@ use alexandria_math::BitShift; fn exp2(mut x: u256) -> u256 { let EXP2_MAX_INPUT = 192 * 1000000000000000000 - 1; if x > EXP2_MAX_INPUT { - panic("error"); + panic_with_felt252("error"); } x = BitShift::shl(x, 64); x = x / 1000000000000000000; From ec6e6857687b55e14173aceb2ead3b98ada4e7cf Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Mon, 23 Oct 2023 16:51:18 +0200 Subject: [PATCH 18/28] change quote --- src/utils/precision.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index ad83220c..6526b238 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -177,7 +177,7 @@ use alexandria_math::BitShift; fn exp2(mut x: u256) -> u256 { let EXP2_MAX_INPUT = 192 * 1000000000000000000 - 1; if x > EXP2_MAX_INPUT { - panic_with_felt252("error"); + panic_with_felt252('error'); } x = BitShift::shl(x, 64); x = x / 1000000000000000000; From 4845345713c53f467fd4eecf33e06573231fe6c4 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Wed, 25 Oct 2023 17:43:37 +0200 Subject: [PATCH 19/28] log2 function is working --- src/utils/precision.cairo | 133 +++++++++++++++++++++++++++++++ tests/utils/test_precision.cairo | 39 +++++++++ 2 files changed, 172 insertions(+) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 6526b238..806ff8b2 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -430,6 +430,139 @@ fn exp(x: u256) -> u256 { exp2(double_unit_product / 1000000000000000000) } +/// Raise a number to a power, computes x^n. +/// * `x` - The number to raise. +/// * `n` - The exponent. +/// # Returns +/// * `u256` - The result of x raised to the power of n. +fn pow256(x: u256, n: usize) -> u256 { + if n == 0 { + 1 + } else if n == 1 { + x + } else if (n & 1) == 1 { + x * pow256(x * x, n / 2) + } else { + pow256(x * x, n / 2) + } +} + +fn msb(mut x: u256) -> u256 { + let mut result = 0; + if (x >= pow256(2, 128)) { + x = BitShift::shr(x, 128); + result +=128; + } + if x >= pow256(2, 64) { + x = BitShift::shr(x, 64); + result += 64; + } + if x >= pow256(2, 32) { + x = BitShift::shr(x, 32); + result += 32; + } + if x >= pow256(2, 16) { + x = BitShift::shr(x, 16); + result += 16; + } + if x >= pow256(2, 8) { + x = BitShift::shr(x, 8); + result += 8; + } + if x >= pow256(2, 4) { + x = BitShift::shr(x, 4); + result += 4; + } + if x >= pow256(2, 2) { + x = BitShift::shr(x, 2); + result += 2; + } + if x >= pow256(2, 1) { + result += 1; + } + result +} + +fn log2(x: u256) -> u256 { + let xUint: u256 = x; + + // If the input value is smaller than the base unit, error out. + if xUint < 1000000000000000000 { + panic_with_felt252('error'); + } + + // Calculate the integer part of the logarithm. + let n: u256 = msb(xUint / 1000000000000000000); + + // Calculate the integer part of the logarithm as a fixed-point number. + let mut resultUint: u256 = n * 1000000000000000000; + + // Calculate y = x * 2^{-n} + let mut y: u256 = BitShift::shr(xUint, n); + + // If y equals the base unit, the fractional part is zero. + if y == 1000000000000000000 { + return resultUint; + } + + // Calculate the fractional part through iterative approximation. + let mut delta: u256 = 500000000000000000; + loop { + if delta == 0 { + break; + } + y = (y * y) / 1000000000000000000; + + if y >= 2000000000000000000 { + resultUint += delta; + y = BitShift::shr(y, 1); + } + delta = BitShift::shr(delta, 1); // Decrement the delta by halving it. + }; + + return resultUint; +} + +fn pow_final( x: u256, y : u256) -> u256 { + let xUint: u256 = x; + let yUint: u256 = y; + + // If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero. + if (xUint == 0) { + if yUint == 0 { + return 1000000000000000000; + } + else { + return 0; + } + } + // If x is `UNIT`, the result is always `UNIT`. + else if (xUint == 1000000000000000000) { + return 1000000000000000000; + } + + // If y is zero, the result is always `UNIT`. + if (yUint == 0) { + return 1000000000000000000; + } + // If y is `UNIT`, the result is always x. + else if (yUint == 1000000000000000000) { + return x; + } + + // If x is greater than `UNIT`, use the standard formula. + if (xUint > 1000000000000000000) { + return exp2(log2(x) * y); + } + // Conversely, if x is less than `UNIT`, use the equivalent formula. + else { + let i = 1000000000000000000000000000000000000 / xUint; + let w = exp2(log2(i) * y); + return 1000000000000000000000000000000000000 / w; + } +} + + /// Compute factor from value and divisor with a roundup. /// # Arguments /// * `value` - The value to compute the factor. diff --git a/tests/utils/test_precision.cairo b/tests/utils/test_precision.cairo index 445250dd..3f04821f 100644 --- a/tests/utils/test_precision.cairo +++ b/tests/utils/test_precision.cairo @@ -120,6 +120,45 @@ fn test_exp() { assert(result5 == 2718281828459045234, 'should be 2718281828459045234'); } + +#[test] +fn test_log2() { + let value1: u256 = 2000000000000000000; + let value2: u256 = 5000000000000000000; + let value3: u256 = 4000000000000000000; + let value5: u256 = 1000000000000000000; + + let result1 = precision::log2(value1); + let result2 = precision::log2(value2); + let result3 = precision::log2(value3); + let result5 = precision::log2(value5); + + assert(result1 == 1000000000000000000, 'should be 1000000000000000000'); + assert(result2 == 2321928094887362334, 'should be 2321928094887362334'); + assert(result3 == 2000000000000000000, 'should be 2000000000000000000'); + assert(result5 == 0000000000000000000, 'should be 0000000000000000000'); +} + +#[test] +fn test_pow_final() { + let value1: u256 = 2000000000000000000; + let value2: u256 = 5000000000000000000; + let value3: u256 = 4000000000000000000; + let value5: u256 = 1000000000000000000; + let value6: u256 = 1524558784654678955; + + //let result1 = precision::pow_final(value2, value1); + //let result2 = precision::pow_final(value2, value5); + //let result3 = precision::pow_final(value3, 0); + //let result5 = precision::pow_final(value5); + + //assert(result1 == 25000000000000000000, 'should be 1000000000000000000'); + //assert(result2 == 5000000000000000000, 'should be 2321928094887362334'); + //assert(result3 == 1000000000000000000, 'should be 2000000000000000000'); + //assert(result5 == 0000000000000000000, 'should be 0000000000000000000'); + assert(value1==value1, 'In progress') +} + #[test] fn test_to_factor_roundup() { let value: u128 = 450000; From 38427c4f2232252a7f881e4c6d14a9f7f95fd594 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Wed, 25 Oct 2023 17:45:11 +0200 Subject: [PATCH 20/28] fmt --- src/utils/precision.cairo | 20 ++++++++------------ tests/utils/test_precision.cairo | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 806ff8b2..204ec06c 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -451,7 +451,7 @@ fn msb(mut x: u256) -> u256 { let mut result = 0; if (x >= pow256(2, 128)) { x = BitShift::shr(x, 128); - result +=128; + result += 128; } if x >= pow256(2, 64) { x = BitShift::shr(x, 64); @@ -492,7 +492,7 @@ fn log2(x: u256) -> u256 { } // Calculate the integer part of the logarithm. - let n: u256 = msb(xUint / 1000000000000000000); + let n: u256 = msb(xUint / 1000000000000000000); // Calculate the integer part of the logarithm as a fixed-point number. let mut resultUint: u256 = n * 1000000000000000000; @@ -517,13 +517,13 @@ fn log2(x: u256) -> u256 { resultUint += delta; y = BitShift::shr(y, 1); } - delta = BitShift::shr(delta, 1); // Decrement the delta by halving it. + delta = BitShift::shr(delta, 1); // Decrement the delta by halving it. }; return resultUint; } -fn pow_final( x: u256, y : u256) -> u256 { +fn pow_final(x: u256, y: u256) -> u256 { let xUint: u256 = x; let yUint: u256 = y; @@ -531,12 +531,10 @@ fn pow_final( x: u256, y : u256) -> u256 { if (xUint == 0) { if yUint == 0 { return 1000000000000000000; - } - else { + } else { return 0; } - } - // If x is `UNIT`, the result is always `UNIT`. + }// If x is `UNIT`, the result is always `UNIT`. else if (xUint == 1000000000000000000) { return 1000000000000000000; } @@ -544,8 +542,7 @@ fn pow_final( x: u256, y : u256) -> u256 { // If y is zero, the result is always `UNIT`. if (yUint == 0) { return 1000000000000000000; - } - // If y is `UNIT`, the result is always x. + }// If y is `UNIT`, the result is always x. else if (yUint == 1000000000000000000) { return x; } @@ -553,8 +550,7 @@ fn pow_final( x: u256, y : u256) -> u256 { // If x is greater than `UNIT`, use the standard formula. if (xUint > 1000000000000000000) { return exp2(log2(x) * y); - } - // Conversely, if x is less than `UNIT`, use the equivalent formula. + }// Conversely, if x is less than `UNIT`, use the equivalent formula. else { let i = 1000000000000000000000000000000000000 / xUint; let w = exp2(log2(i) * y); diff --git a/tests/utils/test_precision.cairo b/tests/utils/test_precision.cairo index 3f04821f..2bc7b7d0 100644 --- a/tests/utils/test_precision.cairo +++ b/tests/utils/test_precision.cairo @@ -156,7 +156,7 @@ fn test_pow_final() { //assert(result2 == 5000000000000000000, 'should be 2321928094887362334'); //assert(result3 == 1000000000000000000, 'should be 2000000000000000000'); //assert(result5 == 0000000000000000000, 'should be 0000000000000000000'); - assert(value1==value1, 'In progress') + assert(value1 == value1, 'In progress') } #[test] From a9156d4377a78111feb259f31d528c264c6ac482 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Wed, 25 Oct 2023 17:46:41 +0200 Subject: [PATCH 21/28] fmt --- src/utils/precision.cairo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 204ec06c..5cf82118 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -534,7 +534,7 @@ fn pow_final(x: u256, y: u256) -> u256 { } else { return 0; } - }// If x is `UNIT`, the result is always `UNIT`. + } // If x is `UNIT`, the result is always `UNIT`. else if (xUint == 1000000000000000000) { return 1000000000000000000; } @@ -542,7 +542,7 @@ fn pow_final(x: u256, y: u256) -> u256 { // If y is zero, the result is always `UNIT`. if (yUint == 0) { return 1000000000000000000; - }// If y is `UNIT`, the result is always x. + } // If y is `UNIT`, the result is always x. else if (yUint == 1000000000000000000) { return x; } @@ -550,7 +550,7 @@ fn pow_final(x: u256, y: u256) -> u256 { // If x is greater than `UNIT`, use the standard formula. if (xUint > 1000000000000000000) { return exp2(log2(x) * y); - }// Conversely, if x is less than `UNIT`, use the equivalent formula. + } // Conversely, if x is less than `UNIT`, use the equivalent formula. else { let i = 1000000000000000000000000000000000000 / xUint; let w = exp2(log2(i) * y); From 15c10e170c09868ca7e9fed95106057b4cbaa9f7 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Thu, 26 Oct 2023 16:31:20 +0200 Subject: [PATCH 22/28] pow working --- src/utils/precision.cairo | 4 ++-- tests/utils/test_precision.cairo | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 5cf82118..61249a0a 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -523,7 +523,7 @@ fn log2(x: u256) -> u256 { return resultUint; } -fn pow_final(x: u256, y: u256) -> u256 { +fn pow_decimal(x: u256, y: u256) -> u256 { let xUint: u256 = x; let yUint: u256 = y; @@ -549,7 +549,7 @@ fn pow_final(x: u256, y: u256) -> u256 { // If x is greater than `UNIT`, use the standard formula. if (xUint > 1000000000000000000) { - return exp2(log2(x) * y); + return exp2(log2(x) * y/1000000000000000000); } // Conversely, if x is less than `UNIT`, use the equivalent formula. else { let i = 1000000000000000000000000000000000000 / xUint; diff --git a/tests/utils/test_precision.cairo b/tests/utils/test_precision.cairo index 2bc7b7d0..fe329a27 100644 --- a/tests/utils/test_precision.cairo +++ b/tests/utils/test_precision.cairo @@ -140,23 +140,23 @@ fn test_log2() { } #[test] -fn test_pow_final() { +fn test_pow_decimal() { let value1: u256 = 2000000000000000000; - let value2: u256 = 5000000000000000000; + let value2: u256 = 3000000000000000000; let value3: u256 = 4000000000000000000; let value5: u256 = 1000000000000000000; let value6: u256 = 1524558784654678955; - //let result1 = precision::pow_final(value2, value1); - //let result2 = precision::pow_final(value2, value5); - //let result3 = precision::pow_final(value3, 0); - //let result5 = precision::pow_final(value5); + let result1 = precision::pow_decimal(value2, value1); + let result2 = precision::pow_decimal(value2, value5); + let result3 = precision::pow_decimal(value3, 0); + let result4 = precision::pow_decimal(value3, value6); + //let result5 = precision::pow_decimal(0, value5); - //assert(result1 == 25000000000000000000, 'should be 1000000000000000000'); - //assert(result2 == 5000000000000000000, 'should be 2321928094887362334'); - //assert(result3 == 1000000000000000000, 'should be 2000000000000000000'); - //assert(result5 == 0000000000000000000, 'should be 0000000000000000000'); - assert(value1 == value1, 'In progress') + assert(result1 == 8999999999999999806, 'should be 8999999999999999806'); + //assert(result2 == 2999999999999999967, 'should be 2999999999999999967'); + assert(result3 == 1000000000000000000, 'should be 2000000000000000000'); + assert(result4 == 8277055145359463000, 'should be 8277055145359463000'); } #[test] From 45c6dbb524fc16719c20b1aae7b408a2a6b2c6db Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Thu, 26 Oct 2023 16:32:34 +0200 Subject: [PATCH 23/28] fmt --- src/utils/precision.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 61249a0a..197f3ddb 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -549,7 +549,7 @@ fn pow_decimal(x: u256, y: u256) -> u256 { // If x is greater than `UNIT`, use the standard formula. if (xUint > 1000000000000000000) { - return exp2(log2(x) * y/1000000000000000000); + return exp2(log2(x) * y / 1000000000000000000); } // Conversely, if x is less than `UNIT`, use the equivalent formula. else { let i = 1000000000000000000000000000000000000 / xUint; From 60a69a01bbd53c8d1ea608619ee33158c0fc44c0 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Mon, 30 Oct 2023 13:35:50 +0100 Subject: [PATCH 24/28] convert type --- src/utils/precision.cairo | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 5359ba1f..f7f6f5f0 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -156,18 +156,17 @@ fn mul_div_roundup( /// * `value` - The value to the exponent is applied to. /// * `divisor` - The exponent applied. fn apply_exponent_factor(float_value: u128, exponent_factor: u128) -> u128 { // TODO - // if float_value < FLOAT_PRECISION { - // return 0; - // } - // if exponent_factor == FLOAT_PRECISION { - // return float_value; - // } - // let wei_value = float_to_wei(float_value); - // let exponent_wei = float_to_wei(exponent_factor); - // let wei_result = pow(wei_value, exponent_wei); - // let float_result = wei_to_float(wei_result); - // float_result - 0 + if float_value < FLOAT_PRECISION { + return 0; + } + if exponent_factor == FLOAT_PRECISION { + return float_value; + } + let wei_value = float_to_wei(float_value); + let exponent_wei = float_to_wei(exponent_factor); + let wei_result = pow_decimal(wei_value.into(), exponent_wei.into()); + let float_result = wei_to_float(wei_result.try_into()); + float_result } //use starknet::cairo::common::cairo_builtins::bitwise_and; @@ -418,7 +417,7 @@ fn exp2(mut x: u256) -> u256 { // The underlying logic is based on the relationship $2^{191-ip} = 2^{ip} / 2^{191}$, where $ip$ denotes the, // integer part, $2^n$. - result *= 1000000000000000000; //maybe error comes from here? + result *= 1000000000000000000; result = BitShift::shr(result, 191 - BitShift::shr(x, 64)); result } From 19610a8c08320469990c1c4d73514fc4990f8313 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Sun, 5 Nov 2023 00:20:38 +0100 Subject: [PATCH 25/28] fix --- src/utils/precision.cairo | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index f7f6f5f0..6d0b4a99 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -165,8 +165,11 @@ fn apply_exponent_factor(float_value: u128, exponent_factor: u128) -> u128 { // let wei_value = float_to_wei(float_value); let exponent_wei = float_to_wei(exponent_factor); let wei_result = pow_decimal(wei_value.into(), exponent_wei.into()); - let float_result = wei_to_float(wei_result.try_into()); + + let wei_u128: u128 = wei_result.try_into().unwrap(); + let float_result = wei_to_float(wei_u128); float_result + //0 } //use starknet::cairo::common::cairo_builtins::bitwise_and; From 8314593ecd8ebb05e58af144bd21a19ca16122ef Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Sun, 5 Nov 2023 00:22:09 +0100 Subject: [PATCH 26/28] lock --- Scarb.lock | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/Scarb.lock b/Scarb.lock index 29a97a29..1967be2c 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -4,23 +4,12 @@ version = 1 [[package]] name = "alexandria_data_structures" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" -dependencies = [ - "alexandria_encoding", -] - -[[package]] -name = "alexandria_encoding" -version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" -dependencies = [ - "alexandria_math", -] +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=a3052ff#a3052ffc72d431c981ae0b92b29a40487b47a5ee" [[package]] name = "alexandria_math" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=a3052ff#a3052ffc72d431c981ae0b92b29a40487b47a5ee" dependencies = [ "alexandria_data_structures", ] @@ -28,12 +17,12 @@ dependencies = [ [[package]] name = "alexandria_sorting" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=a3052ff#a3052ffc72d431c981ae0b92b29a40487b47a5ee" [[package]] name = "alexandria_storage" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=a3052ff#a3052ffc72d431c981ae0b92b29a40487b47a5ee" [[package]] name = "satoru" @@ -49,4 +38,4 @@ dependencies = [ [[package]] name = "snforge_std" version = "0.1.0" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.9.1#da085bd11e1b151d0592f43917136560d9b70d37" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.8.3#cb05cb81cae9451a91e42a03af09e0457284b4de" From 4460d9c6b330a41661130f5a7615ba6494bb9308 Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Sun, 5 Nov 2023 00:25:45 +0100 Subject: [PATCH 27/28] fmt + lock --- Scarb.lock | 21 ++++++++++++++++----- src/utils/precision.cairo | 16 ++++++++-------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Scarb.lock b/Scarb.lock index 1967be2c..29a97a29 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -4,12 +4,23 @@ version = 1 [[package]] name = "alexandria_data_structures" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=a3052ff#a3052ffc72d431c981ae0b92b29a40487b47a5ee" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" +dependencies = [ + "alexandria_encoding", +] + +[[package]] +name = "alexandria_encoding" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" +dependencies = [ + "alexandria_math", +] [[package]] name = "alexandria_math" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=a3052ff#a3052ffc72d431c981ae0b92b29a40487b47a5ee" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" dependencies = [ "alexandria_data_structures", ] @@ -17,12 +28,12 @@ dependencies = [ [[package]] name = "alexandria_sorting" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=a3052ff#a3052ffc72d431c981ae0b92b29a40487b47a5ee" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" [[package]] name = "alexandria_storage" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=a3052ff#a3052ffc72d431c981ae0b92b29a40487b47a5ee" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" [[package]] name = "satoru" @@ -38,4 +49,4 @@ dependencies = [ [[package]] name = "snforge_std" version = "0.1.0" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.8.3#cb05cb81cae9451a91e42a03af09e0457284b4de" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.9.1#da085bd11e1b151d0592f43917136560d9b70d37" diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 6d0b4a99..0cc00c45 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -157,19 +157,19 @@ fn mul_div_roundup( /// * `divisor` - The exponent applied. fn apply_exponent_factor(float_value: u128, exponent_factor: u128) -> u128 { // TODO if float_value < FLOAT_PRECISION { - return 0; - } - if exponent_factor == FLOAT_PRECISION { - return float_value; - } + return 0; + } + if exponent_factor == FLOAT_PRECISION { + return float_value; + } let wei_value = float_to_wei(float_value); let exponent_wei = float_to_wei(exponent_factor); let wei_result = pow_decimal(wei_value.into(), exponent_wei.into()); - + let wei_u128: u128 = wei_result.try_into().unwrap(); let float_result = wei_to_float(wei_u128); float_result - //0 +//0 } //use starknet::cairo::common::cairo_builtins::bitwise_and; @@ -420,7 +420,7 @@ fn exp2(mut x: u256) -> u256 { // The underlying logic is based on the relationship $2^{191-ip} = 2^{ip} / 2^{191}$, where $ip$ denotes the, // integer part, $2^n$. - result *= 1000000000000000000; + result *= 1000000000000000000; result = BitShift::shr(result, 191 - BitShift::shr(x, 64)); result } From b4b3ce52455209a318b9fec3b1f5d80355d5e2bb Mon Sep 17 00:00:00 2001 From: starkfishinator Date: Mon, 6 Nov 2023 00:13:32 +0100 Subject: [PATCH 28/28] works --- tests/utils/test_precision.cairo | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/utils/test_precision.cairo b/tests/utils/test_precision.cairo index fe329a27..f1623a7f 100644 --- a/tests/utils/test_precision.cairo +++ b/tests/utils/test_precision.cairo @@ -159,6 +159,26 @@ fn test_pow_decimal() { assert(result4 == 8277055145359463000, 'should be 8277055145359463000'); } +#[test] +fn test_apply_exponent_factor() { + let value1: u128 = 2000000000000000000000000000000; + let value2: u128 = 3000000000000000000000000000000; + let value3: u128 = 4000000000000000000000000000000; + let value5: u128 = 1000000000000000000000000000000; + let value6: u128 = 1524558784654678955000000000000; + + let result1 = precision::apply_exponent_factor(value2, value1); + let result2 = precision::apply_exponent_factor(value2, value5); + let result3 = precision::apply_exponent_factor(value3, 0); + let result4 = precision::apply_exponent_factor(value3, value6); + //let result5 = precision::pow_decimal(0, value5); + + assert(result1 == 8999999999999999806000000000000, 'should be '); + //assert(result2 == 2999999999999999967, 'should be 2999999999999999967'); + assert(result3 == 1000000000000000000000000000000, 'should be '); + assert(result4 == 8277055145359463000000000000000, 'should be '); +} + #[test] fn test_to_factor_roundup() { let value: u128 = 450000;