diff --git a/.all-contributorsrc b/.all-contributorsrc index 8423ec78..bfd43361 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -234,6 +234,123 @@ "contributions": [ "code" ] + }, + { + "login": "piotmag769", + "name": "Piotr Magiera", + "avatar_url": "https://avatars.githubusercontent.com/u/56825108?v=4", + "profile": "https://github.com/piotmag769", + "contributions": [ + "code" + ] + }, + { + "login": "ftupas", + "name": "ftupas", + "avatar_url": "https://avatars.githubusercontent.com/u/35031356?v=4", + "profile": "https://github.com/ftupas", + "contributions": [ + "code" + ] + }, + { + "login": "lambda-0x", + "name": "lambda-0x", + "avatar_url": "https://avatars.githubusercontent.com/u/87354252?v=4", + "profile": "https://github.com/lambda-0x", + "contributions": [ + "code" + ] + }, + { + "login": "Tbelleng", + "name": "Tbelleng", + "avatar_url": "https://avatars.githubusercontent.com/u/117627242?v=4", + "profile": "https://github.com/Tbelleng", + "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" + ] + }, + { + "login": "khaeljy", + "name": "Khaeljy", + "avatar_url": "https://avatars.githubusercontent.com/u/1810456?v=4", + "profile": "https://github.com/khaeljy", + "contributions": [ + "code" + ] + }, + { + "login": "JeanWoked", + "name": "JeanWoked", + "avatar_url": "https://avatars.githubusercontent.com/u/149107619?v=4", + "profile": "https://github.com/JeanWoked", + "contributions": [ + "code" + ] + }, + { + "login": "vuittont60", + "name": "vuittont60", + "avatar_url": "https://avatars.githubusercontent.com/u/81072379?v=4", + "profile": "https://github.com/vuittont60", + "contributions": [ + "code" + ] + }, + { + "login": "MavericksFive", + "name": "Arnaud Berger", + "avatar_url": "https://avatars.githubusercontent.com/u/95299145?v=4", + "profile": "https://github.com/MavericksFive", + "contributions": [ + "code" + ] + }, + { + "login": "faytey", + "name": "faytey", + "avatar_url": "https://avatars.githubusercontent.com/u/40033608?v=4", + "profile": "https://github.com/faytey", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..a853ae67 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,10 @@ +{ + "name": "Satoru", + "image": "mcr.microsoft.com/devcontainers/base:jammy", + "customizations": { + "vscode": { + "extensions": ["starkware.cairo1"] + } + }, + "postCreateCommand": "curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh && curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh -s -- -v 0.8.3" +} diff --git a/.example.env b/.example.env new file mode 100644 index 00000000..1a96da21 --- /dev/null +++ b/.example.env @@ -0,0 +1,3 @@ +PROVIDER_URL= +ACCOUNT_PUBLIC= +ACCOUNT_PRIVATE= \ No newline at end of file diff --git a/.github/semgrep-cairo-rules.yaml b/.github/semgrep-cairo-rules.yaml new file mode 100644 index 00000000..c06f8bae --- /dev/null +++ b/.github/semgrep-cairo-rules.yaml @@ -0,0 +1,46 @@ +rules: +- id: unwrap + message: Use unwrap() method on $X, use expect() instead + languages: [cairo] + severity: WARNING + pattern: $X.unwrap() + +- id: division + message: Possible division by zero, use error_utils::check_division_by_zero to report a better error message + languages: [cairo] + severity: WARNING + patterns: + - pattern-regex: $Y / $X + - pattern-not-regex: error_utils::check_division_by_zero + +- id: reentrancy + message: | + Value mutated after call to external contract + severity: ERROR + mode: join + join: + rules: + - id: external-contract-declaration + languages: [cairo] + pattern: | + trait $SOME_CONTRACT {} + - id: external-call + languages: [cairo] + pattern: | + $SOME_CONTRACT::transfer(...); + ...; + $X::write(...); + on: + - 'external-contract-declaration.$SOME_CONTRACT == external-call.$SOME_CONTRACT' + +- id: unsafe-arithmetic + message: Call unsafe math operators on $X + languages: [cairo] + severity: ERROR + pattern-either: + - pattern: $X + $Y + - pattern: $X += ... + - pattern: $X - $Y + - pattern: $X -= ... + - pattern: $X * $Y + - pattern: $X *= ... \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cdbf30d0..ceffdb56 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build on: [push, pull_request] env: - SCARB_VERSION: 0.7.0 + SCARB_VERSION: 2.3.1 # For the moment we will use nightly versions of scarb to be able to use latest features of Cairo. # The installation process will be a bit different than when using non nightly versions. @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v3 - uses: software-mansion/setup-scarb@v1 with: - scarb-version: "0.7.0" + scarb-version: "2.3.1" # - name: Set up Scarb #ses: software-mansion/setup-scarb@v1 # Install Scarb from a nightly release diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 50d2b9b8..ad517588 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -9,13 +9,13 @@ jobs: - uses: actions/checkout@v3 - uses: software-mansion/setup-scarb@v1 with: - scarb-version: "0.7.0" + scarb-version: "2.3.1" - name: Install Semgrep run: | - pip install semgrep + pip install semgrep==1.45.0 - name: Run Semgrep - run: semgrep --config https://github.com/avnu-labs/semgrep-cairo-rules/releases/download/v0.0.1/cairo-rules.yaml ./src > semgrep-output.txt + run: semgrep --config .github/semgrep-cairo-rules.yaml ./src > semgrep-output.txt - name: Save Semgrep Output as an Artifact uses: actions/upload-artifact@v3 with: @@ -32,19 +32,19 @@ jobs: ~/.cargo key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Check if Caracal is installed - id: check-caracal - run: | - if ! command -v caracal &> /dev/null; then - echo "Caracal is not installed. Installing..." - cargo install --git https://github.com/crytic/caracal --profile release --force - else - echo "Caracal is already installed." - fi - - name: Run Caracal - run: caracal detect . > caracal-output.txt - - name: Save Caracal Output as an Artifact - uses: actions/upload-artifact@v3 - with: - name: caracal-cairo - path: caracal-output.txt + # - name: Check if Caracal is installed + # id: check-caracal + # run: | + # if ! command -v caracal &> /dev/null; then + # echo "Caracal is not installed. Installing..." + # cargo install --git https://github.com/crytic/caracal --profile release --force + # else + # echo "Caracal is already installed." + # fi + # - name: Run Caracal + # run: caracal detect . > caracal-output.txt + # - name: Save Caracal Output as an Artifact + # uses: actions/upload-artifact@v3 + # with: + # name: caracal-cairo + # path: caracal-output.txt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 63cb4080..2d601144 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,7 @@ name: Test on: [push, pull_request] env: - SCARB_VERSION: 0.7.0 - STARKNET_FOUNDRY_VERSION: 0.6.0 + SCARB_VERSION: 2.3.1 jobs: check: @@ -13,7 +12,12 @@ jobs: - uses: actions/checkout@v3 - uses: software-mansion/setup-scarb@v1 with: - scarb-version: "0.7.0" + scarb-version: ${{ env.SCARB_VERSION }} + - uses: foundry-rs/setup-snfoundry@v2 + with: + starknet-foundry-version: 0.10.1 + - name: Run cairo tests + run: snforge test # - name: Set up Scarb #ses: software-mansion/setup-scarb@v1 # Install Scarb from a nightly release @@ -22,7 +26,3 @@ jobs: # wget https://github.com/software-mansion/scarb-nightlies/releases/download/${NIGHTLY_DATE}/scarb-${NIGHTLY_DATE}-x86_64-unknown-linux-gnu.tar.gz # tar -xvf scarb-${NIGHTLY_DATE}-x86_64-unknown-linux-gnu.tar.gz # sudo mv scarb-v${SCARB_VERSION}-x86_64-unknown-linux-gnu/bin/scarb /usr/local/bin - - name: Install starknet foundry - run: curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh -s -- -v ${STARKNET_FOUNDRY_VERSION} - - name: Run cairo tests - run: snforge diff --git a/.gitignore b/.gitignore index f8951466..d0bddf58 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,6 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -**/.DS_Store \ No newline at end of file +**/.DS_Store +.env +node_modules \ No newline at end of file diff --git a/.tool-versions b/.tool-versions index ce84f339..697917e5 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -scarb 0.7.0 \ No newline at end of file +scarb 2.3.1 diff --git a/README.md b/README.md index 1730e492..382d85fe 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@

+
+ + [![Exploration_Team](https://img.shields.io/badge/Exploration_Team-29296E.svg?&style=for-the-badge&logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIGlkPSJhIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxODEgMTgxIj48ZGVmcz48c3R5bGU+LmJ7ZmlsbDojZmZmO308L3N0eWxlPjwvZGVmcz48cGF0aCBjbGFzcz0iYiIgZD0iTTE3Ni43Niw4OC4xOGwtMzYtMzcuNDNjLTEuMzMtMS40OC0zLjQxLTIuMDQtNS4zMS0xLjQybC0xMC42MiwyLjk4LTEyLjk1LDMuNjNoLjc4YzUuMTQtNC41Nyw5LjktOS41NSwxNC4yNS0xNC44OSwxLjY4LTEuNjgsMS44MS0yLjcyLDAtNC4yN0w5Mi40NSwuNzZxLTEuOTQtMS4wNC00LjAxLC4xM2MtMTIuMDQsMTIuNDMtMjMuODMsMjQuNzQtMzYsMzcuNjktMS4yLDEuNDUtMS41LDMuNDQtLjc4LDUuMThsNC4yNywxNi41OGMwLDIuNzIsMS40Miw1LjU3LDIuMDcsOC4yOS00LjczLTUuNjEtOS43NC0xMC45Ny0xNS4wMi0xNi4wNi0xLjY4LTEuODEtMi41OS0xLjgxLTQuNCwwTDQuMzksODguMDVjLTEuNjgsMi4zMy0xLjgxLDIuMzMsMCw0LjUzbDM1Ljg3LDM3LjNjMS4zNiwxLjUzLDMuNSwyLjEsNS40NCwxLjQybDExLjQtMy4xMSwxMi45NS0zLjYzdi45MWMtNS4yOSw0LjE3LTEwLjIyLDguNzYtMTQuNzYsMTMuNzNxLTMuNjMsMi45OC0uNzgsNS4zMWwzMy40MSwzNC44NGMyLjIsMi4yLDIuOTgsMi4yLDUuMTgsMGwzNS40OC0zNy4xN2MxLjU5LTEuMzgsMi4xNi0zLjYsMS40Mi01LjU3LTEuNjgtNi4wOS0zLjI0LTEyLjMtNC43OS0xOC4zOS0uNzQtMi4yNy0xLjIyLTQuNjItMS40Mi02Ljk5LDQuMyw1LjkzLDkuMDcsMTEuNTIsMTQuMjUsMTYuNzEsMS42OCwxLjY4LDIuNzIsMS42OCw0LjQsMGwzNC4zMi0zNS43NHExLjU1LTEuODEsMC00LjAxWm0tNzIuMjYsMTUuMTVjLTMuMTEtLjc4LTYuMDktMS41NS05LjE5LTIuNTktMS43OC0uMzQtMy42MSwuMy00Ljc5LDEuNjhsLTEyLjk1LDEzLjg2Yy0uNzYsLjg1LTEuNDUsMS43Ni0yLjA3LDIuNzJoLS42NWMxLjMtNS4zMSwyLjcyLTEwLjYyLDQuMDEtMTUuOGwxLjY4LTYuNzNjLjg0LTIuMTgsLjE1LTQuNjUtMS42OC02LjA5bC0xMi45NS0xNC4xMmMtLjY0LS40NS0xLjE0LTEuMDgtMS40Mi0xLjgxbDE5LjA0LDUuMTgsMi41OSwuNzhjMi4wNCwuNzYsNC4zMywuMTQsNS43LTEuNTVsMTIuOTUtMTQuMzhzLjc4LTEuMDQsMS42OC0xLjE3Yy0xLjgxLDYuNi0yLjk4LDE0LjEyLTUuNDQsMjAuNDYtMS4wOCwyLjk2LS4wOCw2LjI4LDIuNDYsOC4xNiw0LjI3LDQuMTQsOC4yOSw4LjU1LDEyLjk1LDEyLjk1LDAsMCwxLjMsLjkxLDEuNDIsMi4wN2wtMTMuMzQtMy42M1oiLz48L3N2Zz4=)](https://github.com/keep-starknet-strange) +
+ @@ -39,6 +44,18 @@ To build the project, run: scarb build ``` +## Satoru compatible frontends + +You can find the list of Satoru-compatible frontends, all of which have been built on top of the Satoru platform : + +- [Zohal](https://github.com/Zohal-Starknet/zohal-interface) + +## πŸ›οΈ Infrastructure + +

+ +

+ ## πŸ§ͺ Test To test the project, run: @@ -49,7 +66,7 @@ snforge ## πŸš€ Deploy -To deploy contracts of the saturo, you first need to setup a smart wallet : +To deploy the contracts of Satoru, you first need to setup a smart wallet : - Create a signer by following this tutorial : [Signers](https://book.starkli.rs/signers) @@ -63,6 +80,21 @@ cd scripts ./deploy_contract.sh ``` +## Deployed Contracts + +- RoleStore: 0x07eacab18c343f30edfa9336b8eacce9bc56303d43c92609a88e8da25177f5b3 +- DataStore: 0x0549539da18f4d574211365b6abd678ef940444b579900efedcb935210c41481 +- OrderVault: 0x01f1252d6d02feb14cfa88beff415e1524d1cebb31870056567aae257104b6fd +- Router: 0x00dd0912017ee7c8151555394380acd1012a814916d384b12ca64afa0eae2bc5 +- EventEmitter: 0x0284ae712869c0af4f538e9297e6965d3c9ba9110830944047de8d35da7ea447 +- MarketToken: 0x044391e9498f440cc41ace136ea317f6bfa2080311085d1846529e421974a1d3 +- MarketFactory: 0x05766918626a91ca83f52003eb03bbf1f13174aa22e340c8057d8d5d6affbfcf +- WithdrawalVault: 0x050c83c2bc74cc50676fdd5598b40f9d0d6d5ccf6ea3478a7999e29473da03f1 +- SwapHandler: 0x039aa67b479f4870878ec6d3002f9fa9b8e98d4d3d10c1f32b5d394a456aab28 +- ReferralStorage: 0x0189463034c24b2cb091dcb515287bea13a4767534f09e52692a4cdc30254001 +- DepositVault: 0x07d435e7ab3a5cd4b872e5725b02898833cb9a7c62e2d9a6a9db324d61e2925e + + ## πŸ“š Resources Here are some resources to help you get started: @@ -120,10 +152,27 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Jordy Romuald
Jordy Romuald

πŸ’» - StarkFishinator
StarkFishinator

πŸ’» + StarkFishinator
StarkFishinator

πŸ’» Axel Izsak
Axel Izsak

πŸ’» Luciefer
Luciefer

πŸ’» tevrat aksoy
tevrat aksoy

πŸ’» + Piotr Magiera
Piotr Magiera

πŸ’» + ftupas
ftupas

πŸ’» + lambda-0x
lambda-0x

πŸ’» + + + Tbelleng
Tbelleng

πŸ’» + dic0de
dic0de

πŸ’» + akhercha
akhercha

πŸ’» + VictorONN
VictorONN

πŸ’» + kasteph
kasteph

πŸ’» + Khaeljy
Khaeljy

πŸ’» + JeanWoked
JeanWoked

πŸ’» + + + vuittont60
vuittont60

πŸ’» + Arnaud Berger
Arnaud Berger

πŸ’» + faytey
faytey

πŸ’» @@ -133,4 +182,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file diff --git a/Scarb.lock b/Scarb.lock new file mode 100644 index 00000000..e0bb7f13 --- /dev/null +++ b/Scarb.lock @@ -0,0 +1,58 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "alexandria_data_structures" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?tag=cairo-v2.3.0-rc0#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" +dependencies = [ + "alexandria_encoding", +] + +[[package]] +name = "alexandria_encoding" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?tag=cairo-v2.3.0-rc0#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" +dependencies = [ + "alexandria_math", +] + +[[package]] +name = "alexandria_math" +version = "0.2.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?tag=cairo-v2.3.0-rc0#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" +dependencies = [ + "alexandria_data_structures", +] + +[[package]] +name = "alexandria_sorting" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?tag=cairo-v2.3.0-rc0#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" + +[[package]] +name = "alexandria_storage" +version = "0.2.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?tag=cairo-v2.3.0-rc0#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" + +[[package]] +name = "pragma_lib" +version = "1.0.0" +source = "git+https://github.com/astraly-labs/pragma-lib#fe9a46743254182ac331da488ccfc05e0185cdd0" + +[[package]] +name = "satoru" +version = "0.1.0" +dependencies = [ + "alexandria_data_structures", + "alexandria_math", + "alexandria_sorting", + "alexandria_storage", + "pragma_lib", + "snforge_std", +] + +[[package]] +name = "snforge_std" +version = "0.1.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.9.1#da085bd11e1b151d0592f43917136560d9b70d37" diff --git a/Scarb.toml b/Scarb.toml index 3c954730..d3b9a28e 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -18,12 +18,13 @@ allowed-libfuncs-list.name = "experimental" sierra-replace-ids = true [dependencies] -starknet = ">=2.1.0" -alexandria_data_structures = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "a3052ff" } -alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "a3052ff" } -alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "a3052ff" } -alexandria_sorting = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "a3052ff" } -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.5.0" } +starknet = ">=2.3.0" +alexandria_data_structures = { git = "https://github.com/keep-starknet-strange/alexandria.git", tag = "cairo-v2.3.0-rc0" } +alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git", tag = "cairo-v2.3.0-rc0" } +alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", tag = "cairo-v2.3.0-rc0" } +alexandria_sorting = { git = "https://github.com/keep-starknet-strange/alexandria.git", tag = "cairo-v2.3.0-rc0" } +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.9.1" } +pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } [tool.snforge] diff --git a/book/src/README.md b/book/src/README.md index bb8dfdfe..aed820de 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -4,6 +4,6 @@ Satoru is a cutting-edge synthetics platform for Starknet, taking inspiration fr ![Satoru Whats Up](./assets/satoru-whats-up.gif) -Like it's namesake, Satoru is **powerful** and **badass**. It's also a bit of a mystery. We don't know what it's capable of yet, but we're excited to find out. +Like its namesake, Satoru is **powerful** and **badass**. It's also a bit of a mystery. We don't know what it's capable of yet, but we're excited to find out. -Combine the power of **Starknet** with it's huge computation capacity to the great design of **GMX v2**, and you get Satoru, a synthetics platform that is fast, cheap and scalable. +Combine the power of **Starknet** with its huge computation capacity with the great design of **GMX v2**, and you get Satoru, a synthetics platform that is fast, cheap and scalable. diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index e528346e..a2645cda 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -9,23 +9,29 @@ - [Smart contracts architecture](smart-contracts-architecture/README.md) - [Overview](smart-contracts-architecture/overview.md) - [Adl Module](smart-contracts-architecture/adl-module.md) + - [Bank Module](smart-contracts-architecture/bank-module.md) - [Callback Module](smart-contracts-architecture/callback-module.md) + - [Chain Module](smart-contracts-architecture/chain-module.md) + - [Config Module](smart-contracts-architecture/config-module.md) - [Data Module](smart-contracts-architecture/data-module.md) + - [Deposit Module](smart-contracts-architecture/deposit-module.md) - [Exchange Module](smart-contracts-architecture/exchange-module.md) - [Feature Module](smart-contracts-architecture/feature-module.md) - [Fee Module](smart-contracts-architecture/fee-module.md) - [Gas Module](smart-contracts-architecture/gas-module.md) - - [Liquidation](smart-contracts-architecture/liquidation-module.md) - - [Mock](smart-contracts-architecture/mock-module.md) + - [Liquidation Module](smart-contracts-architecture/liquidation-module.md) + - [Market Module](smart-contracts-architecture/market-module.md) + - [Mock Module](smart-contracts-architecture/mock-module.md) - [Nonce Module](smart-contracts-architecture/nonce-module.md) - [Oracle Module](smart-contracts-architecture/oracle-module.md) - [Order Module](smart-contracts-architecture/order-module.md) - [Position Module](smart-contracts-architecture/position-module.md) + - [Price Module](smart-contracts-architecture/price-module.md) - [Pricing Module](smart-contracts-architecture/pricing-module.md) - [Reader Module](smart-contracts-architecture/reader-module.md) - [Referral Module](smart-contracts-architecture/referral-module.md) - - [Router Module](smart-contracts-architecture/router-module.md) - [Role Module](smart-contracts-architecture/role-module.md) + - [Router Module](smart-contracts-architecture/router-module.md) - [Swap Module](smart-contracts-architecture/swap-module.md) - [Utils Module](smart-contracts-architecture/utils-module.md) - [Withdrawal Module](smart-contracts-architecture/withdrawal-module.md) diff --git a/book/src/assets/satoru-infra.png b/book/src/assets/satoru-infra.png new file mode 100644 index 00000000..078ffb36 Binary files /dev/null and b/book/src/assets/satoru-infra.png differ diff --git a/book/src/continuous-integration/README.md b/book/src/continuous-integration/README.md index 26aaf1aa..0a120922 100644 --- a/book/src/continuous-integration/README.md +++ b/book/src/continuous-integration/README.md @@ -4,4 +4,4 @@ A workflow is a configurable automated process that will run one or more jobs. Workflows are defined by a YAML file checked in to your repository and will run when triggered by an event in your repository, or they can be triggered manually, or at a defined schedule. -In this section we will run through every workflows and give a detailed explanation of their functionalities. \ No newline at end of file +In this section we will run through every workflow and give a detailed explanation of their functionalities. diff --git a/book/src/continuous-integration/workflows.md b/book/src/continuous-integration/workflows.md index 92e81ce6..77fd8d64 100644 --- a/book/src/continuous-integration/workflows.md +++ b/book/src/continuous-integration/workflows.md @@ -175,7 +175,7 @@ The "Test" GitHub Actions workflow (`test.yml`) ensures the code's integrity by **Environment Variables:** - **SCARB_VERSION:** Specifies the Scarb version, currently set to `0.7.0`. -- **STARKNET_FOUNDRY_VERSION:** Defines the version of Starknet Foundry, currently set to `0.5.0`. +- **STARKNET_FOUNDRY_VERSION:** Defines the version of Starknet Foundry, currently set to `0.8.3`. **Jobs:** 1. **Test & Check Job**: @@ -198,18 +198,18 @@ on: - main env: SCARB_VERSION: 0.7.0 - STARKNET_FOUNDRY_VERSION: 0.5.0 jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: foundry-rs/setup-snfoundry@v1 + with: + starknet-foundry-version: 0.10.1 - uses: software-mansion/setup-scarb@v1 with: scarb-version: "0.7.0" - - name: Install starknet foundry - run: curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh -s -- -v ${STARKNET_FOUNDRY_VERSION} - name: Run cairo tests run: snforge ``` \ No newline at end of file diff --git a/book/src/getting-started/prerequisites.md b/book/src/getting-started/prerequisites.md index 8613d429..c569ce3b 100644 --- a/book/src/getting-started/prerequisites.md +++ b/book/src/getting-started/prerequisites.md @@ -1,3 +1,14 @@ # Prerequisites +There are several ways to run Satoru: +- Install prerequisites on the local system +- Run in a dev container + +## Installation on the local system +- [Scarb](https://docs.swmansion.com/scarb/download.html) - [Starknet Foundry](https://foundry-rs.github.io/starknet-foundry/) + +## Run in a dev container +Dev containers provide a dedicated environment for the project. Since the dev container configuration is stored in the `.devcontainer` directory, this ensures that the environment is strictly identical from one developer to the next. + +To run inside a dev container, please follow [Dev Containers tutorial](https://code.visualstudio.com/docs/devcontainers/tutorial). \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/adl-module.md b/book/src/smart-contracts-architecture/adl-module.md index 3755b579..f034442d 100644 --- a/book/src/smart-contracts-architecture/adl-module.md +++ b/book/src/smart-contracts-architecture/adl-module.md @@ -1,7 +1,79 @@ -# Adl module +# Auto-Deleveraging (ADL) Module -The adl module helps with the auto-deleveraging. +The ADL Module helps with automatic reduction of leverage in specific markets. This is particularly important for markets where the main token is different from the long token, like a STRK / USD perpetual market where ETH is the long token. It contains the following Cairo library files: -- [adl.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/adl/adl.cairo): Helps with the auto-deleveraging. This is particularly for markets with an index token that is different from the long token. \ No newline at end of file +- [adl.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/adl/adl_utils.cairo) + +## Structures and Types + +### `CreateAdlOrderParams` + +This struct is utilized within the `create_adl_order` function to encapsulate parameters needed for the order creation, aiding in avoiding stack overflow. + +- `data_store`: The `DataStore` contract dispatcher which provides access to a centralized data storage, used for storing and retrieving information about markets, positions, orders, etc. +- `event_emitter`: The `EventEmitter` contract dispatcher utilized for emitting events on the blockchain, allowing users and other contracts to track changes in the system. +- `account`: The address of the account whose position is to be reduced. In the ADL context, this typically means closing profitable positions to maintain system solvency. +- `market`: Address of the concerned market. Each market may have its own parameters and states, and this address helps identify the specific market to be dealt with. +- `collateral_token`: The address of the token used as collateral for the position. For instance, it's ETH in a STRK/USD market as per the given example. +- `is_long`: Indicates whether the position is long or short. A long position benefits from a price increase in the market, while a short position benefits from a price decrease. +- `size_delta_usd`: The size of the position to be reduced, expressed in US Dollars. This specifies how much of the position should be reduced to maintain system solvency. +- `updated_at_block`: The block number at which the order was updated. This tracks when the ADL order was last created or modified. + +## Functions + +### `update_adl_state` + +Checks the pending profit state and updates an `isAdlEnabled` flag to avoid repeatedly validating whether auto-deleveraging is required. It uses an oracle to fetch market prices and emits an `adl_state_updated` event to notify about the state change. The function also updates the latest ADL block to the current block number to ensure that the ADL status is associated with the most recent data. + +### `create_adl_order` + +Constructs an ADL order to reduce a profitable position. The function returns a `felt252` type representing the key of the created order, where `felt252` is a type representing a 252-bit field element. + +### `validate_adl` + +Validates if the requested ADL can be executed by checking the ADL enabled state and ensuring the oracle block numbers are recent enough. + +### `get_latest_adl_block`, `set_latest_adl_block` + +These functions interact with the `data_store` to retrieve and update the latest ADL block number respectively. `get_latest_adl_block` returns the latest block number at which the ADL flag was updated, and `set_latest_adl_block` sets the latest block number to a new value. + +### `get_is_adl_enabled`, `set_adl_enabled` + +Interact with the `data_store` to get and set the ADL enabled state for a specified market and position type (long/short). + +### `emit_adl_state_updated` + +Emits ADL state update events to notify about changes in the ADL state, including the market, position type, PnL to pool factor, max PnL factor, and whether ADL was enabled or disabled. + +## Errors + +The module defines an `AdlError` to handle ADL-specific errors. Each constant in the `AdlError` module represents a specific error case in the ADL module. Here are the defined errors: + +- `ORACLE_BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED`: This error is thrown when the block numbers from the oracle are smaller than required. It ensures that the data being used is recent enough to be reliable. + +- `INVALID_SIZE_DELTA_FOR_ADL`: Triggered when the size of the position to be reduced is invalid, for example, if it's larger than the current position size. It ensures that the ADL order size is valid and can be executed. + +- `ADL_NOT_ENABLED`: This error occurs if an ADL operation is attempted when ADL is not enabled for the specified market. It serves as a guard to prevent unwanted ADL operations. + +- `POSITION_NOT_VALID`: Thrown when a position is not valid, for instance, if it doesn't exist or has already been closed. This error ensures that the position associated with the ADL order is valid and open. + +### Others +- Additional utility modules are imported for array operations, error handling, and callback utilities to support various functionalities within the ADL module. + +## Usage Example + +```cairo +// Example of creating an ADL order +let params = adl_utils::CreateAdlOrderParams { + data_store: /* ... */, + event_emitter: /* ... */, + account: /* ... */, + market: /* ... */, + collateral_token: /* ... */, + is_long: /* ... */, + size_delta_usd: /* ... */, + updated_at_block: /* ... */, +}; +adl_utils::create_adl_order(params); \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/bank-module.md b/book/src/smart-contracts-architecture/bank-module.md new file mode 100644 index 00000000..e23de5d8 --- /dev/null +++ b/book/src/smart-contracts-architecture/bank-module.md @@ -0,0 +1,83 @@ +# Bank Module (Token Handling) + +This module helps to store and move tokens within a contract. It's crucial for a bigger project, letting you do basic bank tasks like starting the contract and sending tokens to a receiver. + +It contains the following Cairo library files: + +- [bank.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/bank/bank.cairo) + +## Structures + +### `Storage` +This struct houses the interface to interact with the `DataStore` contract, which is essential for the storage of data within the contract. + +- `data_store`: An instance of `IDataStoreDispatcher` providing the necessary methods to interact with the `DataStore` contract. + +## Interface + +### `IBank` +This interface defines the contract's structure and methods. The generic `TContractState` allows for a flexible contract state definition. + +- `initialize`: This method sets up the contract with the necessary addresses for the data store and role store contracts. +- `transfer_out`: A method to facilitate the transfer of tokens from this contract to a specified receiver. + +## Implementation + +### `Bank` Module +This module provides the implementation for the `IBank` interface and additional helper methods necessary for the contract's functionality. + +#### `constructor` +This is the constructor method for the contract, which calls the `initialize` method to set up the contract's state. + +#### `BankImpl` of `IBank` +This implementation provides the methods defined in the `IBank` interface. + +- `initialize`: Ensures the contract is not already initialized, sets up the role module, and writes the data store address to the contract's state. +- `transfer_out`: Ensures the caller is a controller before proceeding to call the internal method for token transfer. + +#### `BankHelperImpl` of `BankHelperTrait` +This implementation provides additional helper methods for the contract. + +- `transfer_out_internal`: Checks that the receiver is not this contract itself, then performs the token transfer using the `transfer` method from `token_utils`. + +## StrictBank Module +The StrictBank module extends the functionalities of the Bank module by implementing a sync function to update token balances, which can be particularly useful in scenarios of token burns or similar balance changes. + +### Interface + +#### `IStrictBank` +This interface extends the `IBank` interface and includes an additional method for syncing token balances. + +- `sync_token_balance`: Updates the `token_balances` in case of token burns or similar balance changes. This function returns the new balance of the specified token. + +### Implementation + +#### `StrictBank` of `IStrictBank` +This implementation provides the methods defined in the `IStrictBank` interface. It relies on the `Bank` module for `initialize` and `transfer_out` methods, while providing a custom implementation for `sync_token_balance` method which currently returns a placeholder value of `0`. + +## Errors + +### `BankError` +This enum encapsulates the error definitions for this contract, ensuring that the contract's methods are used correctly and safely. + +- `ALREADY_INITIALIZED`: Thrown if an attempt is made to initialize the contract when it's already initialized. Error code: `'already_initialized'`. +- `SELF_TRANSFER_NOT_SUPPORTED`: Thrown if an attempt is made to transfer tokens to the contract itself. Error code: `'self_transfer_not_supported'`. +- `TOKEN_TRANSFER_FAILED`: Thrown if a token transfer operation fails. Error code: `'token_transfer_failed'`. + +## Usage Example + +Here's a simplified example demonstrating how to initialize and interact with the `Bank` contract in Cairo: + +```cairo +use starknet::{ContractAddress, contract_address_const}; +use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; + +// Deploying the Bank contract +let bank_contract = declare('Bank'); +let constructor_calldata = array![data_store_contract_address.into(), role_store_contract_address.into()]; +let bank_contract_address = bank_contract.deploy(@constructor_calldata).unwrap(); +let bank_dispatcher = IBankDispatcher { contract_address: bank_contract_address }; + +// Transferring tokens using the Bank contract +let receiver_address: ContractAddress = 0x202.try_into().unwrap(); +bank_dispatcher.transfer_out(erc20_contract_address, receiver_address, 100_u128); \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/callback-module.md b/book/src/smart-contracts-architecture/callback-module.md index e42b3190..f9d7c368 100644 --- a/book/src/smart-contracts-architecture/callback-module.md +++ b/book/src/smart-contracts-architecture/callback-module.md @@ -1,7 +1,157 @@ -# Callback module +## Callback Module -Most features of satoru require a two step process to complete. The Callback module is used to facilitate usage of other contracts interacting with Satoru protocol, thus allowing better composability. +The Callback module is a part of the Satoru project and manages a two-step process. First, a user sends a request, then a keeper sends another transaction to carry out that request. This module makes it easier to work with other contracts by letting a special contract be specified, which gets called after requests are done or cancelled. -It contains the following smart contracts: +This module contains the following Cairo library files: -- [callback_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/callback/callback_utils.cairo): It is handling most functions linked to callback. \ No newline at end of file +- [callback](https://github.com/keep-starknet-strange/satoru/tree/main/src/callback) + +## Functions + +### `validate_callback_gas_limit` +Validates that the specified `callback_gas_limit` is below a maximum specified value to prevent callback gas limits exceeding the max gas limits per block. + +- **Arguments:** + - `data_store`: The data store to use. + - `callback_gas_limit`: The callback gas limit. + +### `set_saved_callback_contract` +Allows an external entity to associate a callback contract address with a specific account and market. + +- **Arguments:** + - `data_store`: The `DataStore` contract dispatcher. + - `account`: The account to set callback contract for. + - `market`: The market to set callback contract for. + - `callback_contract`: The callback contract address. + +### `get_saved_callback_contract` +Retrieves a previously stored callback contract address associated with a given account and market from the data store. + +- **Arguments:** + - `data_store`: The `DataStore` contract dispatcher. + - `account`: The account to get callback contract for. + - `market`: The market to get callback contract for. + +### function_after_deposit_execution, function_after_deposit_cancellation, function_after_withdrawal_execution, function_after_withdrawal_cancellation, function_after_order_execution, function_after_order_cancellation, and function_after_order_frozen +These functions are callback handlers called after specific actions such as deposit execution, deposit cancellation, withdrawal execution, withdrawal cancellation, order execution, order cancellation, and order frozen respectively. + +These functions are callback handlers called after specific actions such as deposit execution, deposit cancellation, withdrawal execution, withdrawal cancellation, order execution, order cancellation, and order frozen respectively. + +- **Common Arguments:** + - `key`: The key of the order/deposit/withdrawal. + - `order`/`deposit`/`withdrawal`: The order/deposit/withdrawal that was executed/cancelled/frozen. + - `event_data`: The event log data. + - `event_emitter`: The event emitter dispatcher. + +### `is_valid_callback_contract` +Validates that the given address is a contract. + +- **Arguments:** + - `callback_contract`: The callback contract. + +## Errors + +### `CallbackError` +This enum encapsulates the error definitions for this module, ensuring that the contract's methods are used correctly and safely. + +- `MAX_CALLBACK_GAS_LIMIT_EXCEEDED`: Thrown when the `callback_gas_limit` exceeds the maximum specified value. Error code: `'max_callback_gas_limit_exceeded'`. + +## Interface for `DepositCallbackReceiver` + +The `DepositCallbackReceiver` interface defines the callback handlers that are triggered after a deposit operation such as execution or cancellation. This interface is crucial for the callback mechanism within the Satoru project, allowing for additional logic to be executed after a deposit operation. + +### `IDepositCallbackReceiver` + +This interface specifies the methods for handling callbacks after deposit operations. + +#### `after_deposit_execution` + +Called after a deposit execution. + +- **Arguments:** + - `self`: The contract state. + - `key`: The key of the deposit. + - `deposit`: The deposit that was executed. + - `event_data`: The event log data. + +#### `after_deposit_cancellation` + +Called after a deposit cancellation. + +- **Arguments:** + - `self`: The contract state. + - `key`: The key of the deposit. + - `deposit`: The deposit that was cancelled. + - `event_data`: The event log data. + +## Interface for `OrderCallbackReceiver` + +The `OrderCallbackReceiver` interface defines the callback handlers that are triggered after an order operation such as execution, cancellation, or being frozen. This interface is vital for the callback mechanism within the Satoru project, allowing for additional logic to be executed after an order operation. + +### `IOrderCallbackReceiver` +This interface specifies the methods for handling callbacks after order operations. + +#### `after_order_execution` +Called after an order execution. + +- **Arguments:** + - `self`: The contract state. + - `key`: The key of the order. + - `order`: The order that was executed. + - `event_data`: The event log data. + +#### `after_order_cancellation` +Called after an order cancellation. + +- **Arguments:** + - `self`: The contract state. + - `key`: The key of the order. + - `order`: The order that was cancelled. + - `event_data`: The event log data. + +#### `after_order_frozen` +Called after an order is frozen. + +- **Arguments:** + - `self`: The contract state. + - `key`: The key of the order. + - `order`: The order that was frozen. + - `event_data`: The event log data. + +## Interface for `WithdrawalCallbackReceiver` + +The `WithdrawalCallbackReceiver` interface defines the callback handlers that are triggered after a withdrawal operation such as execution or cancellation. This interface is crucial for the callback mechanism within the Satoru project, allowing for additional logic to be executed after a withdrawal operation. + +### `IWithdrawalCallbackReceiver` +This interface specifies the methods for handling callbacks after withdrawal operations. + +#### `after_withdrawal_execution` +Called after a withdrawal execution. + +- **Arguments:** + - `self`: The contract state. + - `key`: The key of the withdrawal. + - `withdrawal`: The withdrawal that was executed. + - `event_data`: The event log data. + +#### `after_withdrawal_cancellation` +Called after a withdrawal cancellation. + +- **Arguments:** + - `self`: The contract state. + - `key`: The key of the withdrawal. + - `withdrawal`: The withdrawal that was cancelled. + - `event_data`: The event log data. + +## Usage Example + +```cairo +use starknet::ContractAddress; +use satoru::callback::callback; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; + +// Assuming data_store, account, market, and callback_contract are already initialized +callback::set_saved_callback_contract(data_store, account, market, callback_contract); + +// Retrieving the saved callback contract +let saved_callback_contract = callback::get_saved_callback_contract(data_store, account, market); \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/chain-module.md b/book/src/smart-contracts-architecture/chain-module.md new file mode 100644 index 00000000..3c448ae4 --- /dev/null +++ b/book/src/smart-contracts-architecture/chain-module.md @@ -0,0 +1,55 @@ +## Chain Module + +The Chain module provides functionalities to query chain-specific variables. It is designed as a library contract for retrieving the current block number and timestamp. + +This module contains the following Cairo library file: + +- [chain.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/chain/chain.cairo) + +## Functions + +### `get_block_number` +Returns the current block number on the Starknet network. + +- **Arguments:** None. + +- **Returns:** `u64` - The current block number. + +### `get_block_timestamp` +Returns the timestamp of the current block on the Starknet network. + +- **Arguments:** None. + +- **Returns:** `u64` - The timestamp of the current block. + +## Errors + +This module does not define any custom errors. + +## Interface for `Chain` + +The `Chain` interface defines the methods to query chain-specific variables like block number and block timestamp. These methods are crucial for contracts that need to interact with or check chain data. + +### `IChain` +This interface specifies the methods for querying chain-specific variables. + +#### `get_block_number` +Called to retrieve the current block number. + +- **Arguments:** + - `self`: The contract state. + +- **Returns:** `u64` - The current block number. + +#### `get_block_timestamp` +Called to retrieve the timestamp of the current block. + +- **Arguments:** + - `self`: The contract state. + +- **Returns:** `u64` - The timestamp of the current block. + +## Usage Example + +```cairo +TODO \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/config-module.md b/book/src/smart-contracts-architecture/config-module.md new file mode 100644 index 00000000..f2a4b225 --- /dev/null +++ b/book/src/smart-contracts-architecture/config-module.md @@ -0,0 +1,63 @@ +# Config Module + +The Configuration Module is really important because it lets you manage different settings in the project. You can change and view configurations related to contracts, roles, gas limits, markets, and more. It makes sure everything works within set rules and is crucial for the system to operate properly. + +Below is a detailed documentation of the Configuration Module, explaining its structures, types, functions, errors, imports, and a sample usage. + +It contains the following Cairo library files: + +- [adl.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/config/config.cairo) + +## Additional Module: Timelock + +Within the system, there's a module named `Timelock` designed to handle functionalities related to time-lock mechanisms. This is crucial for operations that require a predefined time delay for execution, enhancing the security and control over critical operations. + +## Structures and Types + +### `Storage` + +This struct encapsulates the storage fields necessary for the Configuration module, providing interfaces to interact with other contracts and a map to manage allowed base keys. + +- `role_store`: An interface to interact with the `RoleStore` contract. +- `data_store`: An interface to interact with the `DataStore` contract. +- `event_emitter`: An interface to interact with the `EventEmitter` contract. +- `allowed_base_keys`: A map to manage the allowed base keys for setting configurations. + +## Functions + +### `constructor` + +This function initializes the `Storage` struct with provided contract addresses and calls `init_allowed_base_keys` function to initialize allowed base keys. + +### `set_bool`, `set_address`, `set_felt252` + +These functions are implementations of the `IConfig` interface, allowing setting configurations of different data types. They ensure the caller has the `CONFIG_KEEPER` role, validate the base key, compute the full key from the base key and additional data, and set the value in the `DataStore`. + +### `init_allowed_base_keys` + +This function initializes the map of allowed base keys for setting configurations. It writes true to each base key that is allowed to be set. + +### `validate_key` + +This function checks that a provided base key is in the list of allowed base keys, throwing `ConfigError::INVALID_BASE_KEY` if it's not. + +### `get_full_key` + +This function computes the full key from the provided base key and additional data. If there's no additional data, it returns the base key. Otherwise, it computes a Poseidon hash of the concatenated base key and data. + +## Errors + +### `ConfigError` + +This module defines a `ConfigError` to handle configuration-specific errors. Here are the defined errors: + +- `INVALID_BASE_KEY`: This error is thrown when a base key provided is not in the list of allowed base keys. It is represented by the constant `'invalid_base_key'` in the `ConfigError` module. + +## Usage Example + +```cairo +// Example of setting a bool configuration +let base_key = /* ... */; +let data = array![]; +let value = true; +Config::set_bool(contract_state, base_key, data, value); \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/data-module.md b/book/src/smart-contracts-architecture/data-module.md index 209dddfd..5346f1e1 100644 --- a/book/src/smart-contracts-architecture/data-module.md +++ b/book/src/smart-contracts-architecture/data-module.md @@ -1,11 +1,31 @@ -# Data module +## Data Module -The data module is reponsible for storing and managing the data of the protocol. +The Data Module serves as the backbone for storing and managing the protocol's data. Below is a detailed outline of its constituents and their respective functions and responsibilities. -It contains the following smart contracts: +### Smart Contracts -- [DataStore](https://github.com/keep-starknet-strange/satoru/blob/main/src/data/data_store.cairo): The main smart contract of the module. It is responsible for storing the data of the protocol. +#### [data_store.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/data/data_store.cairo) +The `DataStore` is the central smart contract of the module, holding the responsibility of maintaining the protocol's data. It manages different entities, including orders, positions, withdrawals, and deposits. -It contains the following Cairo library files: +##### Key Features & Responsibilities: +- **Order Management:** Enables the creation, reading, updating, and deletion of orders, each linked to a specific user account. Orders can be retrieved using their unique keys or can be listed per user account. + +- **Position Management:** Manages financial positions associated with user accounts, offering functionalities to manipulate and view positions individually or by user account. + +- **Withdrawal Management:** Supports the creation, reading, updating, and deletion of withdrawal requests, and enables the listing of withdrawals by user account. + +- **Deposit Management:** Manages user deposits, allowing the creation, reading, updating, and deletion of deposits, viewable individually or listed by user account. -- [keys.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/data/keys.cairo): Contains functions to generate the keys (entries in the data store) of the protocol. +- **Market Management:** Facilitates the addition, deletion, and retrieval of markets, managing market indexes and ensuring that only authorized users can perform these operations. + +- **Oracle Functions:** Allows setting and getting token IDs, with stringent access controls, ensuring only authorized entities can access them. + +- **Access Control and Security:** Implements rigorous access control mechanisms, ensuring that only authorized addresses can perform certain operations. Specific role controls are applied, allowing only addresses with the `CONTROLLER` role to perform sensitive modifications to the contract’s state. + +##### Constructor +The constructor initializes the contract with a `role_store` address, establishing the access control and role management mechanism from the deployment of the contract. + +### Cairo Library Files + +#### [keys.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/data/keys.cairo) +This Cairo library file plays a crucial role in generating the keys for the protocol's entries in the data store. The keys serve as unique identifiers, enabling the protocol to accurately access and manage the stored data. diff --git a/book/src/smart-contracts-architecture/deposit-module.md b/book/src/smart-contracts-architecture/deposit-module.md index 206a6a5f..1da6bbd0 100644 --- a/book/src/smart-contracts-architecture/deposit-module.md +++ b/book/src/smart-contracts-architecture/deposit-module.md @@ -1,9 +1,91 @@ -# Deposit module +# Deposit Module -The deposit module contains main satoru functions for deposit, to manage the depositing of liquidity into a market. +The deposit module contains main Satoru functions for deposit, to manage the depositing of liquidity into a market. It contains the following Cairo library files: - [deposit_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/deposit/deposit_utils.cairo): Library for deposit functions, to help with the depositing of liquidity into a market in return for market tokens. - [deposit.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/deposit/deposit.cairo): Contains Deposit struct. - [execute_deposit_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/deposit/execute_deposit_utils.cairo): Library for deposit functions, to help with the depositing of liquidity into a market in return for market tokens. + +## Structures and Types + +### `CreateDepositParams` + +This struct is utilized within the `create_deposit` function to encapsulate parameters needed for the deposit creation. + +- `receiver`: The address to send the market tokens to. +- `callback_contract`: The callback contract linked to this deposit. +- `ui_fee_receiver`: The UI fee receiver. +- `market`: The market to deposit into. +- `initial_long_token`: The initial long token address. +- `initial_short_token`: The initial short token address. +- `long_token_swap_path`: The swap path into markets for the long token. +- `short_token_swap_path`: The swap path into markets for the short token. +- `min_market_tokens`: The minimum acceptable number of liquidity tokens. +- `execution_fee`: The execution fee for keepers. +- `callback_gas_limit`: The gas limit for the `callback_contract`. + +### `Deposit` + +A structure to represent a deposit in the system, containing information such as the addresses of the tokens, amounts deposited, and parameters of the concerned market. + +### `DepositError` + +Module for deposit-specific error operations. + +- `DEPOSIT_NOT_FOUND`: Deposit not found. +- `DEPOSIT_INDEX_NOT_FOUND`: Deposit index not found. +- `CANT_BE_ZERO`: Can't be zero. +- `EMPTY_DEPOSIT_AMOUNTS`: Empty deposit amounts. +- `EMPTY_DEPOSIT`: Empty deposit. + +## Functions + +### `create_deposit` + +Creates a deposit with the specified parameters, recording the transfer of initial tokens and validating the swap paths. + +### `cancel_deposit` + +Cancels a deposit, funds are sent back to the user. + +### `execute_deposit` + +Executes a deposit according to the provided parameters. (Function to be developed) + +### `swap` + +Performs a token swap according to the provided parameters. (Function to be developed) + +## Contracts + +### `DepositVault` + +The `DepositVault` contract provides functions to initialize the contract, transfer tokens out of the contract, and record a token transfer into the contract. + +## Usage Example + +```cairo +// Example of creating a deposit +let params = CreateDepositParams { + receiver: /* ... */, + callback_contract: /* ... */, + ui_fee_receiver: /* ... */, + market: /* ... */, + initial_long_token: /* ... */, + initial_short_token: /* ... */, + long_token_swap_path: /* ... */, + short_token_swap_path: /* ... */, + min_market_tokens: /* ... */, + execution_fee: /* ... */, + callback_gas_limit: /* ... */, +}; + +let key = create_deposit( + data_store, + event_emitter, + deposit_vault, + account, + params +); \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/exchange-module.md b/book/src/smart-contracts-architecture/exchange-module.md index 894ca114..5c40a4ff 100644 --- a/book/src/smart-contracts-architecture/exchange-module.md +++ b/book/src/smart-contracts-architecture/exchange-module.md @@ -1,17 +1,21 @@ -# Exchange module +# Exchange Module -The exchange module contains main satoru handlers to create and execute actions. +The Exchange module contains the core functionalities of the Satoru protocol, handling the creation, execution, and cancellation of various actions. -It contains the following smart contracts: +## Smart Contracts -- [AdlHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/adl_handler.cairo): Contract to handle adl. -- [BaseOrderHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/base_order_handler.cairo): Base contract for shared order handler functions. -- [DepositHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/deposit_handler.cairo): Contract to handle creation, execution and cancellation of deposits. -- [LiquidationHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/liquidation_handler.cairo): Contract to handle liquidation. -- [OrderHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/order_handler.cairo): Contract to handle creation, execution and cancellation of orders. -- [WithdrawalHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/withdrawal_handler.cairo): Contract to handle creation, execution and cancellation of withdrawals. +The module comprises the following smart contracts: -It contains the following Cairo library files: +- [AdlHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/adl_handler.cairo): This contract manages the ADL (Automatic Deleveraging) process. +- [BaseOrderHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/base_order_handler.cairo): A base contract encapsulating shared functionalities for order handling. +- [DepositHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/deposit_handler.cairo): Manages the creation, execution, and cancellation of deposit requests. +- [LiquidationHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/liquidation_handler.cairo): Handles the liquidation process. +- [OrderHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/order_handler.cairo): Manages the creation, execution, and cancellation of orders. +- [WithdrawalHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/withdrawal_handler.cairo): Handles the creation, execution, and cancellation of withdrawal requests. -- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/error.cairo): Contains the error codes of the module. -- [exchange_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/withdrawal_event_utils.cairo): Contains request validation utility function. +## Libraries + +The module also includes the following library files for utility and error handling: + +- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/error.cairo): Contains the module's error codes encapsulated as `ExchangeError`. +- [exchange_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/exchange/withdrawal_event_utils.cairo): Provides request validation utility functions. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/feature-module.md b/book/src/smart-contracts-architecture/feature-module.md index 342cecb6..e45bab08 100644 --- a/book/src/smart-contracts-architecture/feature-module.md +++ b/book/src/smart-contracts-architecture/feature-module.md @@ -1,7 +1,41 @@ -# Feature module +# Feature Module -The Feature is used to validate if a feature is enabled or disabled. +The Feature Module checks if different parts of the system are turned on or off. It’s really important for keeping the system stable and working correctly. -It contains the following smart contracts: +It encompasses the following smart contracts: +- [feature_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/feature/feature_utils.cairo): Central to the module, this contract is responsible for validating the operational status of a feature, determining whether it is enabled or disabled. -- [feature_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/feature/feature_utils.cairo): It is responsible for validating if a feature is enabled or disabled. +## ⚠️ Warning +Disabling a feature should be performed with extreme caution and only in absolutely necessary situations, as it can lead to unexpected and potentially harmful effects, such as operational discrepancies and system instability. + +## Functions + +### `is_feature_disabled` + +```cairo +fn is_feature_disabled(data_store: IDataStoreDispatcher, key: felt252) -> bool +``` + +- **Objective:** Determines the operational status of a specified feature. +- **Parameters:** + - `data_store`: The data storage contract dispatcher, facilitating interaction with stored data. + - `key`: The feature key representing the specific feature in question. +- **Returns:** A boolean indicating whether the feature is disabled. + +### `validate_feature` + +```cairo +fn validate_feature(data_store: IDataStoreDispatcher, key: felt252) +``` + +- **Objective:** Validates the operational status of a specified feature and reverts the operation if the feature is disabled. +- **Parameters:** + - `data_store`: The data storage contract dispatcher. + - `key`: The feature key representing the specific feature in question. +- **Implications:** Essential for maintaining system integrity by halting operations related to disabled features. + +## Errors + +### `FeatureError` + +- **DISABLED_FEATURE:** This error is triggered when an attempt is made to operate a disabled feature, indicating a breach in feature utilization protocols. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/fee-module.md b/book/src/smart-contracts-architecture/fee-module.md index 722f896d..c23582e0 100644 --- a/book/src/smart-contracts-architecture/fee-module.md +++ b/book/src/smart-contracts-architecture/fee-module.md @@ -1,13 +1,31 @@ -# Fee module +# Fee Module -The fee module is for the fees actions. +The Fee Module takes care of everything related to fees. It’s crucial for moving and claiming fees in specific markets. -It contains the following smart contracts: +The module incorporates the following components: +- [FeeHandler.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/fee/fee_handler.cairo): The nucleus of the module, entrusted with initializing the contract and claiming fees from identified markets. +- [fee_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/fee/fee_utils.cairo): A collection of utility functions vital for orchestrating fee actions and interactions such as claiming and incrementing fees. +- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/fee/error.cairo): A repository of error codes and messages related to fee operations, essential for accurate error handling and resolution. -- [FeeHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/fee/fee_handler.cairo): The main smart contract of the module. It is responsible for claiming the fees from the specified markets. +## Structures and Types -It contains the following Cairo library files: +### `FeeHandler` -- [fee_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/fee/error.cairo): Fee actions +The `FeeHandler` struct is pivotal within the module, managing the interactions and executions of fee-related functions, ensuring the integrity of fee transfers and claims. -- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/fee/error.cairo): Contains the error codes of the module. +- `data_store`: The `DataStore` contract dispatcher, a centralized repository for data storage, crucial for retrieving and storing information related to markets, positions, orders, etc. +- `role_store`: The `RoleStore` contract dispatcher, responsible for managing roles and permissions within the system. +- `event_emitter`: The `EventEmitter` contract dispatcher, vital for emitting events and notifications within the blockchain, allowing the tracking of system alterations. + +## Functions + +### `initialize` +- **Objective:** Initialize the FeeHandler contract with essential components like `DataStore`, `RoleStore`, and `EventEmitter`. + +### `claim_fees` +- **Objective:** Execute fee claims from specified markets for given tokens. + +## Error Handling +### `FeeError` +- **ALREADY_INITIALIZED:** Triggered when there is an attempt to initialize an already initialized contract. +- **INVALID_CLAIM_FEES_INPUT:** Occurs when the lengths of the market and tokens arrays do not match during a fee claim operation. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/gas-module.md b/book/src/smart-contracts-architecture/gas-module.md index 1accbac3..5b644517 100644 --- a/book/src/smart-contracts-architecture/gas-module.md +++ b/book/src/smart-contracts-architecture/gas-module.md @@ -1,7 +1,46 @@ # Gas Module -The purpose of the gas module is for the execution fee estimation and payments. +The Gas Module is developed to manage execution fee estimations and payments within the system. -It contains the following Cairo library files: +This module comprises the following Cairo library files: +- [GasUtils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/gas/gas_utils.cairo): Entrusted with the responsibility for execution fee estimation and payments. -- [GasUtils](https://github.com/keep-starknet-strange/satoru/blob/main/src/gas/gas_utils.cairo): It is responsible for the execution fee estimation and payments \ No newline at end of file +## Structures and Types + +### `ContractAddress` +- A specialized type representing the address of a contract within the Starknet network. + +## Functions + +### `get_min_handle_execution_error_gas` +- **Objective:** Retrieve the minimal gas required to handle execution errors from the data store. + +### `get_execution_gas` +- **Objective:** Validate that the starting gas is higher than the minimum handle execution gas and return the remaining gas after subtracting the minimum handle error gas. + +### `pay_execution_fee` +- **Objective:** Pays the execution fee to the keeper and refunds any excess amount to the refund receiver. + +### `validate_execution_fee` +- **Objective:** Validate that the provided execution fee is sufficient based on the estimated gas limit. + +### `adjust_gas_usage` +- **Objective:** Adjust the gas usage to ensure keepers are paid a nominal amount. + +### `adjust_gas_limit_for_estimate` +- **Objective:** Adjust the estimated gas limit to ensure the execution fee is sufficient during the actual execution. + +### `estimate_execute_deposit_gas_limit`, `estimate_execute_withdrawal_gas_limit`, `estimate_execute_order_gas_limit` +- **Objective:** Estimate the gas limits for deposits, withdrawals, and orders respectively based on different parameters. + +### `pay_execution_fee_deposit` +- **Objective:** Pay the deposit execution fee to the keeper and refund any excess amount to the refund receiver. It is specifically designed to handle deposit transactions. + +### `estimate_execute_increase_order_gas_limit`, `estimate_execute_decrease_order_gas_limit`, `estimate_execute_swap_order_gas_limit` +- **Objective:** Estimate the gas limits for increase orders, decrease orders, and swap orders respectively, based on different parameters. + +## Errors + +### `GasError` +- **INSUFF_EXEC_GAS (`'insufficient_gas_for_execute'`):** Triggered when the starting gas is less than the minimum required to handle execution errors. +- **INSUFF_EXEC_FEE (`'insufficient_execution_fee'`):** Occurs when the provided execution fee is less than the minimum execution fee calculated based on the estimated gas limit. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/liquidation-module.md b/book/src/smart-contracts-architecture/liquidation-module.md index 7d859a1a..3d7c89a1 100644 --- a/book/src/smart-contracts-architecture/liquidation-module.md +++ b/book/src/smart-contracts-architecture/liquidation-module.md @@ -1,7 +1,41 @@ -# Liquidation module +# Liquidation Module -The Liquidation is used to to help with liquidations. +The Liquidation Module is designed to facilitate and manage liquidations within the system, ensuring stability and solvency of the market. -It contains the following Cairo library files: +## Overview -- [liquidation_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/liquidation/liquidation_utils.cairo): It is responsible for liquidations. +This module contains the following Cairo library file: +- [liquidation_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/liquidation/liquidation_utils.cairo): Entrusted with managing liquidations in the network. + +## Structures and Types + +### `CreateLiquidationOrderParams` + +This struct is used within the `create_liquidation_order` function to encapsulate the necessary parameters for creating a liquidation order, thus preventing stack overflow. + +- `data_store`: The `DataStore` contract dispatcher providing access to centralized data storage, crucial for storing and retrieving market, position, and order-related information. +- `event_emitter`: The `EventEmitter` contract dispatcher, essential for emitting events on the blockchain and allowing users and other contracts to monitor system changes. +- `account`: Represents the address of the account associated with the position to be liquidated. +- `market`: Specifies the address of the concerned market, aiding in identifying the specific market parameters and states involved. +- `collateral_token`: Represents the address of the token used as collateral for the position, crucial for determining the liquidation impact. +- `is_long`: A boolean indicating whether the position is long or short, defining the nature of the liquidation. + +## Functions + +### `create_liquidation_order` + +This function creates a liquidation order for a specific position, ensuring market stability and solvency. The function returns a `felt252` type representing the key of the created order, where `felt252` is a type representing a 252-bit field element. + +## Usage Example + +```cairo +// Example of creating a liquidation order +let params = liquidation_utils::CreateLiquidationOrderParams { + data_store: /* ... */, + event_emitter: /* ... */, + account: /* ... */, + market: /* ... */, + collateral_token: /* ... */, + is_long: /* ... */, +}; +liquidation_utils::create_liquidation_order(params); \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/market-module.md b/book/src/smart-contracts-architecture/market-module.md new file mode 100644 index 00000000..d959e046 --- /dev/null +++ b/book/src/smart-contracts-architecture/market-module.md @@ -0,0 +1,91 @@ +# Market Module + +The Market Module helps with trading in different markets. It lets you create markets by choosing specific tokens. This module supports both regular and ongoing trading. + +Example markets include: + +- ETH/USD: Long collateral as ETH, short collateral as a stablecoin, index token as ETH. +- BTC/USD: Long collateral as WBTC, short collateral as a stablecoin, index token as BTC. +- STRK/USD: Long collateral as ETH, short collateral as a stablecoin, index token as STRK. + +In each market, liquidity providers can deposit either the long or the short collateral token, or both, to mint liquidity tokens. The module allows for risk isolation by exposing liquidity providers only to the markets they deposit into, enabling potentially permissionless listings. + +It contains the following Cairo library files: + +- [market.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/market/market.cairo) + +## Structures and Types + +### `Market` + +This struct represents a market with the following fields: + +- `market_token`: Address of the market token for the market. +- `index_token`: Address of the index token for the market. +- `long_token`: Address of the long token for the market. +- `short_token`: Address of the short token for the market. + +## Functions and Traits + +### `IntoMarketToken` + +This trait provides a method to get the `MarketToken` contract interface of a market. + +### `UniqueIdMarket` + +This trait provides a method to compute the unique id of a market based on its parameters. + +### `ValidateMarket` + +This trait provides methods to validate a market, either by returning a boolean value or by asserting the validity. + +## Implementations + +### `UniqueIdMarketImpl` + +This is the implementation of the `UniqueIdMarket` trait for the `Market` struct, providing a method to compute the unique id of a market. + +### `ValidateMarketImpl` + +This is the implementation of the `ValidateMarket` trait for the `Market` struct, offering methods to validate the market's state. + +### `MarketTokenImpl` + +This is the implementation of the `IntoMarketToken` trait for the `Market` struct, providing the `MarketToken` contract interface of a market. + +## Errors + +The module incorporates a `MarketError` enum to manage market-specific errors, primarily to handle cases involving invalid market parameters. Each constant in the `MarketError` module represents a specific error case in the market module. Here are the defined errors: + +- **`MARKET_NOT_FOUND`**: Triggered when the specified market cannot be located within the system. +- **`DIVISOR_CANNOT_BE_ZERO`**: Raised when an attempt is made to divide by zero. +- **`INVALID_MARKET_PARAMS`**: Occurs when the parameters provided for the market are invalid. +- **`OPEN_INTEREST_CANNOT_BE_UPDATED_FOR_SWAP_ONLY_MARKET`**: This error is triggered when there is an attempt to update open interest for a swap-only market. +- **`MAX_OPEN_INTEREST_EXCEEDED`**: Occurs when the maximum open interest for a market is surpassed. +- **`EMPTY_ADDRESS_IN_MARKET_TOKEN_BALANCE_VALIDATION`**: Raised when an empty address is found during market token balance validation. +- **`EMPTY_ADDRESS_TOKEN_BALANCE_VAL`**: Triggered when an empty address is discovered during token balance validation. +- **`INVALID_MARKET_TOKEN_BALANCE`**: Occurs when the market token balance is found to be invalid. +- **`INVALID_MARKET_TOKEN_BALANCE_FOR_COLLATERAL_AMOUNT`**: This error is raised when the market token balance for a collateral amount is invalid. +- **`INVALID_MARKET_TOKEN_BALANCE_FOR_CLAIMABLE_FUNDING`**: Triggered when the market token balance for claimable funding is invalid. +- **`EmptyAddressInMarketTokenBalanceValidation`**: Occurs when an empty address is encountered during market token balance validation. +- **`INVALID_POSITION_MARKET`**: Raised when the market for a position is invalid. +- **`INVALID_COLLATERAL_TOKEN_FOR_MARKET`**: Triggered when an invalid collateral token is provided for the market. +- **`EMPTY_MARKET`**: Occurs when the market is found to be empty. +- **`DISABLED_MARKET`**: Triggered when the market is disabled. + +Additionally, there is a function `UNABLE_TO_GET_CACHED_TOKEN_PRICE` which panics with a specific message when it is unable to get the cached token price for a given token. + +## Usage Example + +```cairo +let market = Market { + market_token: /* ContractAddress of the market token */, + index_token: /* ContractAddress of the index token */, + long_token: /* ContractAddress of the long token */, + short_token: /* ContractAddress of the short token */, +}; + +// Asserting that the market is valid +market.assert_valid(); +// Getting the MarketToken contract interface of the market +let market_token_dispatcher = market.market_token(); \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/mock.md b/book/src/smart-contracts-architecture/mock.md index 341e4583..0699d477 100644 --- a/book/src/smart-contracts-architecture/mock.md +++ b/book/src/smart-contracts-architecture/mock.md @@ -1,12 +1,61 @@ -# Mock module +# Mock Module -The Mock module is used to store mocked implementation of contracts to use them in tests. +The Mock Module is essential for testing environments and testnets. It holds mocked implementations of contracts. -It contains the following Cairo library files: +## Cairo Library Files +- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/mock/error.cairo): Contains error codes pertinent to the Mock Module. -- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/mock/error.cairo): Contains the error codes of the module. +## Smart Contracts -It contains the following smart contracts: +### [ReferralStorage.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/mock/referral_stoage.cairo) +- **Key Functions:** + - Manages Set and Get functions for handling referral-related data and operations. + - Allows the registration and management of referral codes, setting of trader and referrer tiers, and handling of referral-related data with robust error handling mechanisms. -- [ReferralStorage.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/mock/referral_stoage.cairo): Set and Get of functions for managing referral-related data and operations. -- [Governable.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/referral/governable.cairo): Referral storage for testing and testnets +- **Interface: IReferralStorage** + - **Functions:** + 1. `initialize`: Initializes the contract state with the given event_emitter_address. + 2. `only_handler`: Ensures that the caller is a handler. + 3. `set_handler`: Sets an address as a handler, controlling the active status of handlers. + 4. `set_referrer_discount_share`: Sets the trader discount share for an affiliate. + 5. `set_trader_referral_code_by_user`: Sets the referral code for a trader. + 6. `register_code`: Registers a referral code. + 7. `set_code_owner`: Sets the owner of a referral code. + 8. `code_owners`: Gets the owner of a referral code. + 9. `trader_referral_codes`: Gets the referral code of a trader. + 10. `referrer_discount_shares`: Gets the trader discount share for an affiliate. + 11. `referrer_tiers`: Gets the tier level of an affiliate. + 12. `get_trader_referral_info`: Gets the referral info for a trader. + 13. `set_trader_referral_code`: Sets the referral code for a trader. + 14. `set_tier`: Sets the values for a tier. + 15. `set_referrer_tier`: Sets the tier for an affiliate. + 16. `gov_set_code_owner`: Sets the owner for a referral code by the governor. + 17. `tiers`: Gets the tier values for a tier level. + +### [Governable.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/referral/governable.cairo) +- **Key Functions:** + - Provides functionalities to manage governance-related operations and states. + - Ensures that only authorized entities can perform certain operations, enhancing the security of the contract. + +- **Interface: IGovernable** + - **Functions:** + 1. `initialize`: Initializes the contract state with the given event_emitter_address. + 2. `only_gov`: Ensures that the caller has governance permissions; triggers panic if unauthorized. + 3. `transfer_ownership`: Initiates the transfer of contract governance to a new address; only the current governance address can call this. + 4. `accept_ownership`: Accepts the governance of the contract; only the pending governance address can call this. + +## Structures and Types +### `ReferralTier` + - Represents a referral tier, holding information such as total rebate and discount share for the tier. + +### `ContractState` + - Holds the contract state, facilitating the storage and retrieval of state information like event emitters, governance, and handler status. + +## Errors +- The module defines a `MockError` to handle mock-specific errors with constants representing specific error cases in the Mock module, such as `INVALID_TOTAL_REBATE`, `INVALID_DISCOUNT_SHARE`, and `FORBIDDEN`. + +## Usage Example +```cairo +// Example of registering a referral code +let code: felt252 = /* ... */; +referral_storage::register_code(code); \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/nonce-module.md b/book/src/smart-contracts-architecture/nonce-module.md index 7a1ce5d1..65b52411 100644 --- a/book/src/smart-contracts-architecture/nonce-module.md +++ b/book/src/smart-contracts-architecture/nonce-module.md @@ -1,7 +1,56 @@ # Nonce Module -The purpose of the nonce module is to maintain a progressively increasing nonce value. This value plays a crucial role in the generation of keys. +The Nonce Module keeps track of a number that goes up one at a time, which is crucial for creating unique keys. This is really important to make sure every operation in the system is unique. -It contains the following smart contracts: +It contains the following smart contract: -- [NonceUtils](https://github.com/keep-starknet-strange/satoru/blob/main/src/nonce/nonce_utils.cairo): The main smart contract of the module. It is used to maintain a progressively increasing nonce value. This value plays a crucial role in the generation of keys. \ No newline at end of file +- [NonceUtils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/nonce/nonce_utils.cairo): The principal smart contract in the module, responsible for sustaining an incrementing nonce value crucial for key generation. + +## Structures and Types + +### `IDataStoreDispatcher` +- The dispatcher for `DataStore` contract provides methods to interact with the centralized data storage, essential for storing and retrieving nonce-related information. + +## Functions + +### `get_current_nonce` +- Retrieves the current nonce value from the data store. +- **Arguments:** + - `data_store`: The data store to use. +- **Returns:** + - The current nonce value. + +### `increment_nonce` +- Increments the current nonce value in the data store. +- **Arguments:** + - `data_store`: The data store to use. +- **Returns:** + - The new nonce value. + +### `get_next_key` +- Computes a `felt252` hash using the next nonce and can also use the nonce directly as a key. +- **Arguments:** + - `data_store`: The data store to use. +- **Returns:** + - The `felt252` hash using the next nonce value. + +## Core Logic + +### `compute_key` +- Computes a key using the provided `data_store_address` and `nonce`. +- **Arguments:** + - `data_store_address`: The address of the data store. + - `nonce`: The nonce value. +- **Returns:** + - A `felt252` key. + +## Errors + +The module defines specific errors to handle nonce-specific anomalies and invalid operations, ensuring smooth and accurate operations within the module. + +## Usage Example + +```cairo +// Example of getting the next key +let data_store: IDataStoreDispatcher = /* ... */; +let next_key: felt252 = nonce_utils::get_next_key(data_store); \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/oracle-module.md b/book/src/smart-contracts-architecture/oracle-module.md index c75363bd..84fcb31e 100644 --- a/book/src/smart-contracts-architecture/oracle-module.md +++ b/book/src/smart-contracts-architecture/oracle-module.md @@ -7,7 +7,7 @@ The purpose of the oracle module is to validate and store signed values. Representing the prices in this way allows for conversions between token amounts and fiat values to be simplified, e.g. to calculate the fiat value of a given number of tokens the calculation would just be: `token amount * oracle price`, -to calculate the token amount for a fiat value it would be: `fiat value oracle price`. +to calculate the token amount for a fiat value it would be: `fiat value / oracle price`. The trade-off of this simplicity in calculation is that tokens with a small USD price and a lot of decimals may have precision issues it is also possible that @@ -110,4 +110,4 @@ It contains the following files: - [oracle_modules.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/oracle/oracle_modules.cairo): Modifiers for oracles. - [oracle_store.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/oracle/oracle_modules.cairo): Storage for oracles. - [oracle_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/oracle/oracle_utils.cairo): Contains utility structs and functions for Oracles. -- [oracle.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/oracle/oracle_modules.cairo): Main oracle smart contract. +- [oracle.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/oracle/oracle_modules.cairo): Main oracle smart contract. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/order-module.md b/book/src/smart-contracts-architecture/order-module.md index f35cbed5..0ef8d58d 100644 --- a/book/src/smart-contracts-architecture/order-module.md +++ b/book/src/smart-contracts-architecture/order-module.md @@ -1,19 +1,89 @@ -# Order module +# Order Module -The order module is reponsible for the vault order, functions related to orders. +The Order Module is key for handling orders in the system. It’s important for changing, processing, and looking after orders. -It contains the following smart contracts: +## Overview -- [OrderVault](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/order_vault.cairo): Vault for orders +This module centralizes the logic related to orders, managing various aspects including processing increasing and decreasing orders, structuring orders, and providing utilities for common order-related operations. Its design allows developers to interact with, modify, or extend the functionalities with ease and precision. -It contains the following Cairo library files: +## Smart Contracts -- [base_order_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/base_order_utils.cairo): This library comprises a collection of frequently used order-related functions, designed to facilitate common operations. +- [OrderVault.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/order_vault.cairo): Acts as a secure vault for orders, ensuring their safe storage and accessibility. -- [decrease_order_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/decrease_order_utils.cairo): Library for functions to help with processing a decreasing order. +## Cairo Library Files -- [increase_order_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/increase_order_utils.cairo): Library for functions to help with processing a increasing order. +### [base_order_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/base_order_utils.cairo) +A collection of essential functions facilitating common order-related operations, enhancing code reusability and organization. -- [order.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/order.cairo): Struct for orders +### [decrease_order_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/decrease_order_utils.cairo) +Contains functions aiding in the processing of decreasing orders, ensuring their accurate and efficient handling. -- [order_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/order.cairo): Library for order functions. +### [increase_order_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/increase_order_utils.cairo) +Comprises functions to assist in processing increasing orders, maintaining precision and efficiency in operations. + +### [order.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/order.cairo) +Defines the structure for orders, serving as a blueprint for order objects within the system. + +### [order_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/order/order_utils.cairo) +Encompasses various order-related functions, offering utilities to streamline order processing and management. + +## Detailed Structure and Types + +### `Order` +- Represents the blueprint for creating order objects, defining the properties and characteristics of an order within the system. + +#### Properties +- **key**: A unique identifier of the order of type `felt252`. +- **order_type**: Specifies the type of order from the enumerated `OrderType`. +- **decrease_position_swap_type**: Specifies the type of swap for decreasing position orders from the enumerated `DecreasePositionSwapType`. +- **account**: The account of the user creating the order, represented as a `ContractAddress`. +- **receiver**: The receiver for any token transfers, represented as a `ContractAddress`. +- **callback_contract**: The contract to call for callbacks, represented as a `ContractAddress`. +- **ui_fee_receiver**: The UI fee receiver, represented as a `ContractAddress`. +- **market**: The trading market's contract address. +- **initial_collateral_token**: The initial collateral token for increase orders, represented as a `ContractAddress`. +- **swap_path**: An array of market addresses to swap through. +- **size_delta_usd**: The requested change in position size, represented as `u128`. +- **initial_collateral_delta_amount**: Represents different amounts based on the order, either the amount of the initialCollateralToken sent in by the user for increase orders, the amount of the position's collateralToken to withdraw for decrease orders, or the amount of initialCollateralToken sent in for the swap, represented as `u128`. +- **trigger_price**: The trigger price for non-market orders, represented as `u128`. +- **acceptable_price**: The acceptable execution price for increase/decrease orders, represented as `u128`. +- **execution_fee**: The execution fee for keepers, represented as `u128`. +- **callback_gas_limit**: The gas limit for the callbackContract, represented as `u128`. +- **min_output_amount**: The minimum output amount for decrease orders and swaps, represented as `u128`. +- **updated_at_block**: The block at which the order was last updated, represented as `u64`. +- **is_long**: Boolean flag indicating whether the order is for a long or short. +- **is_frozen**: Boolean flag indicating whether the order is frozen. + +### Enumerations +#### `OrderType` +Enumerates the various types of orders that can be created in the system, including MarketSwap, LimitSwap, MarketIncrease, LimitIncrease, MarketDecrease, LimitDecrease, StopLossDecrease, and Liquidation. + +#### `DecreasePositionSwapType` +Indicates whether the decrease order should swap the pnl token to collateral token or vice versa, with possible values being NoSwap, SwapPnlTokenToCollateralToken, and SwapCollateralTokenToPnlToken. + +#### `SecondaryOrderType` +Further differentiates orders, with possible values being None and Adl. + +## Core Functionalities and Methods + +### `touch` +Updates the `updated_at_block` property of the order to the current block number. + +### `OrderTypeInto` +Converts the enumerated `OrderType` to a `felt252` type. + +### `OrderTypePrintImpl` +Prints the corresponding string representation of the `OrderType`. + +### `SecondaryOrderTypePrintImpl` +Prints the corresponding string representation of the `SecondaryOrderType`. + +### `DecreasePositionSwapTypePrintImpl` +Prints the corresponding string representation of the `DecreasePositionSwapType`. + +### `DefaultOrder` +Provides a default implementation for creating a new `Order` instance with default values. + +## Errors + +The module delineates specific error cases to manage anomalies and invalid operations related to orders, ensuring seamless execution of order operations and facilitating troubleshooting and debugging. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/overview.md b/book/src/smart-contracts-architecture/overview.md index 1a2f8873..b62de5e3 100644 --- a/book/src/smart-contracts-architecture/overview.md +++ b/book/src/smart-contracts-architecture/overview.md @@ -3,6 +3,11 @@ ![Schema](../assets/satoru-diagram.png) Satoru high-level modules overview. +# Protocol Infrastructure Overview + +![Schema](../assets/satoru-infra.png) +Satoru infrastucture overview. + ## Two steps actions in Satoru Satoru employs a two-step approach for critical actions like Deposit, Withdrawal, and Order execution. This method ensures enhanced security and guards against front-running risks. diff --git a/book/src/smart-contracts-architecture/position-module.md b/book/src/smart-contracts-architecture/position-module.md index 2cda6f0f..379c3e5a 100644 --- a/book/src/smart-contracts-architecture/position-module.md +++ b/book/src/smart-contracts-architecture/position-module.md @@ -4,7 +4,7 @@ The purpose of the position module is to help with management of positions. ## Fees in position -Borrowing fees for position require only a borrowing_factor to track. An example on how this works is if the global cumulative_borrowing_factor is 10020% a position would be opened with borrowingFactor as 10020%. After some time, if the cumulative\_\_borrowing_factor is updated to 10025% the position would owe 5% of the position size as borrowing fees. The total pending borrowing fees of all positions is factored into the calculation of the pool value for LPs. When a position is increased or decreased, the pending borrowing fees for the position is deducted from the position's +Borrowing fees for position require only a borrowing_factor to track. An example on how this works is if the global cumulative_borrowing_factor is 10020% a position would be opened with borrowingFactor as 10020%. After some time, if the cumulative_borrowing_factor is updated to 10025% the position would owe 5% of the position size as borrowing fees. The total pending borrowing fees of all positions is factored into the calculation of the pool value for LPs. When a position is increased or decreased, the pending borrowing fees for the position is deducted from the position's collateral and transferred into the LP pool. The same borrowing fee factor tracking cannot be applied for funding fees as those calculations consider pending funding fees based on the fiat value of the position sizes. diff --git a/book/src/smart-contracts-architecture/price-module.md b/book/src/smart-contracts-architecture/price-module.md new file mode 100644 index 00000000..144f2636 --- /dev/null +++ b/book/src/smart-contracts-architecture/price-module.md @@ -0,0 +1,72 @@ +# Price Module + +The Price Module helps manage everything related to prices. It organizes how to handle lowest and highest prices and makes working with these prices easier. + +## Cairo Library Files + +### [price.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/price/price.cairo) +Defines the `Price` struct and associated methods, serving as a utility to streamline price-related operations in contracts. + +## Structures and Types + +### `Price` + +This struct holds the minimum and maximum prices and provides a set of methods to perform various operations using these prices. + +- **min**: The minimum price, represented as `u128`. +- **max**: The maximum price, represented as `u128`. + +## Trait and Implementations + +### `PriceTrait` + +This trait defines a set of methods that can be performed on a `Price` struct. + +#### Methods + +- **mid_price**: + - Returns the average of the min and max values of the `Price` struct. + - Arguments: + - `self`: The `Price` struct. + - Returns: The average of the min and max values as `u128`. + +- **pick_price**: + - Picks either the min or max value based on the `maximize` parameter. + - Arguments: + - `self`: The `Price` struct. + - `maximize`: If true, picks the max value. Otherwise, picks the min value. + - Returns: The min or max value as `u128`. + +- **pick_price_for_pnl**: + - Picks the min or max price depending on whether it is for a long or short position, and whether the pending pnl should be maximized or not. + - Arguments: + - `self`: The `Price` struct. + - `is_long`: Whether it is for a long or a short position. + - `maximize`: Whether the pending pnl should be maximized or not. + - Returns: The min or max price as `u128`. + +### `PriceImpl` + +This implementation block provides concrete implementations for the methods defined in the `PriceTrait` for a `Price` struct. + +### `PriceZeroable` + +This implementation block provides methods to create a zero `Price` struct and check whether a `Price` struct is zero or non-zero. + +#### Methods + +- **zero**: + - Returns a `Price` struct with min and max values set to 0. + - Returns: A zero `Price` struct. + +- **is_zero**: + - Checks whether the `Price` struct is zero. + - Arguments: + - `self`: The `Price` struct. + - Returns: A boolean value indicating whether the `Price` struct is zero. + +- **is_non_zero**: + - Checks whether the `Price` struct is non-zero. + - Arguments: + - `self`: The `Price` struct. + - Returns: A boolean value indicating whether the `Price` struct is non-zero. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/pricing-module.md b/book/src/smart-contracts-architecture/pricing-module.md index e09d4b93..6ebca3e6 100644 --- a/book/src/smart-contracts-architecture/pricing-module.md +++ b/book/src/smart-contracts-architecture/pricing-module.md @@ -50,4 +50,4 @@ It contains the following Cairo library files: - [position_pricing_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/pricing/position_pricing_utils.cairo): Library for position pricing functions. - [pricing_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/pricing/pricing_utils.cairo): Library for pricing functions. -- [swap_pricing_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/pricing/swap_pricing_utils.cairo): Library for pricing functions linked to swaps. +- [swap_pricing_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/pricing/swap_pricing_utils.cairo): Library for pricing functions linked to swaps. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/reader-module.md b/book/src/smart-contracts-architecture/reader-module.md index 28ba3e7f..d63c9c51 100644 --- a/book/src/smart-contracts-architecture/reader-module.md +++ b/book/src/smart-contracts-architecture/reader-module.md @@ -1,9 +1,10 @@ # Reader Module -The purpose of this module is to get financial market data and trading utility library. +The Reader Module gets market data and is like a utility library for trading. It’s especially important for markets that need lots of calculations and data for operating and evaluating the market. -It contains the following files: +## Cairo Library Files -- [reader_pricing_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/reader/reader_pricing_utils.cairo): Utility functions for trading price, impact, and fee calculations. +The module contains the following Cairo files: +- [reader_pricing_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/reader/reader_pricing_utils.cairo): Utility functions for trading price calculations, impact, and fees. - [reader_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/reader/reader_utils.cairo): External utility functions for trading operations. -- [reader.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/reader/reader.cairo): Library for reading and calculating financial market data and trading operations. +- [reader.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/reader/reader.cairo): Library for reading and calculating financial market data and trading operations. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/referral-module.md b/book/src/smart-contracts-architecture/referral-module.md index af5840de..ed0ba953 100644 --- a/book/src/smart-contracts-architecture/referral-module.md +++ b/book/src/smart-contracts-architecture/referral-module.md @@ -1,8 +1,42 @@ -# Referral module +# Referral Module -The referral module is responsible for managing protocol users affiliations with discount and rebates. +The Referral Module handles user referrals, giving discounts and paybacks. It’s key for encouraging people to bring in others and rewarding them for helping the platform grow. It contains the following Cairo library files: -- [referral_tier.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/referral/referral_tier.cairo): Contains the referral tier struct. -- [referral_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/referral/referral_utils.cairo): Contains referral utility functions. +- [referral_tier.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/referral/referral_tier.cairo): Defines the `ReferralTier` struct and contains related functionalities. +- [referral_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/referral/referral_utils.cairo): Houses various referral utility functions essential for managing referrals within the platform. + +## Structures and Types + +### `ReferralTier` +This struct encapsulates the total rebate for the tier, which is the sum of the affiliate reward and trader discount, and the share of the total rebate designated for traders. + +## Functions + +### `set_trader_referral_code` +This function sets the referral code for a trader and is vital for linking traders to their referrers, ensuring that the correct users receive their due rewards. + +### `increment_affiliate_reward` +It increments the affiliate's reward balance by a specified delta, updating the reward balance and emitting an event signaling the update. + +### `get_referral_info` +Retrieves the referral information for a specified trader, returning the referral code, the affiliate's address, the total rebate, and the discount share. It plays a crucial role in fetching referral details needed for various operations, like calculating rebates and discounts. + +### `claim_affiliate_reward` +Allows claiming of the affiliate reward. It returns the reward amount and updates relevant balances and states to reflect the claimed reward. + +## Errors + +Specific error handling would be defined to manage any anomalies in referral operations, such as invalid referral codes, non-existent affiliates, etc., ensuring the robustness and reliability of the referral system. + +## Imports + +### Core Library Imports +- `starknet`: Used for core functionalities and structures in Starknet contracts. +- Several other local imports from the `satoru` project for various functionalities like data storage, event emission, and market utilities. + +### Local Imports from `satoru` project +- `referral_storage`: For managing referral-related data storage operations. +- `data_store`: Centralized data storage used for storing and retrieving information about referrals, rewards, etc. +- `event_emitter`: Utilized for emitting events on the blockchain, allowing users and other contracts to track changes in the system. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/role-module.md b/book/src/smart-contracts-architecture/role-module.md index 3853cabc..fc465abe 100644 --- a/book/src/smart-contracts-architecture/role-module.md +++ b/book/src/smart-contracts-architecture/role-module.md @@ -1,12 +1,74 @@ -# Role module +# Role Module -The role module is responsible for role-based access control. +The Role Module is crucial for managing who has access to what, controlling the assignment and removal of roles to different accounts in the system. -It contains the following smart contracts: +It consists of the following smart contracts and Cairo library files: -- [RoleStore](https://github.com/keep-starknet-strange/satoru/blob/main/src/role/role_store.cairo): The main smart contract of the module. It is responsible for storing the roles of the protocol and for managing the access control. +- [RoleStore](https://github.com/keep-starknet-strange/satoru/blob/main/src/role/role_store.cairo): The central contract of the module, focusing on storing roles and managing access control across the protocol. +- [role.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/role/role.cairo): Holds the definitions of different roles existing within the protocol. +- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/role/error.cairo): Encompasses the error codes specific to this module. +- [role_module.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/role/role_module.cairo): Implements the `RoleModule` contract interface, focusing on role validation and interaction with `RoleStore`. -It contains the following Cairo library files: +### Features of role_module.cairo -- [role.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/role/role.cairo): Contains the different roles of the protocol. -- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/role/error.cairo): Contains the error codes of the module. +- **Initialization**: It initializes the role store with the provided address. +- **Role Validation Functions**: Provides a set of functions like `only_timelock_admin`, `only_controller`, etc., each validating a specific role in the protocol. +- **Role Verification**: Utilizes `RoleStore` to verify if an account holds the specified role, ensuring secure and accurate role-based access control. +- **Access Restriction**: Employs role validation to restrict access to specific functions, maintaining the protocol's security and integrity. + +## Roles Defined + +The following roles are defined within the protocol: + +- `ADMIN` +- `TIMELOCK_ADMIN` +- `TIMELOCK_MULTISIG` +- `CONFIG_KEEPER` +- `CONTROLLER` +- `ROUTER_PLUGIN` +- `MARKET_KEEPER` +- `FEE_KEEPER` +- `ORDER_KEEPER` +- `FROZEN_ORDER_KEEPER` +- `PRICING_KEEPER` +- `LIQUIDATION_KEEPER` +- `ADL_KEEPER` + +These roles are represented by constants defined in `role.cairo`, and they are essential in maintaining the integrity and functionality of the system by granting specific permissions to different accounts. + +## Functions + +### `has_role` +Determines whether a given account holds a specified role. +### `grant_role` +Assigns a particular role to a specific account. +### `revoke_role` +Removes a designated role from a given account. +### `assert_only_role` +Ensures that a specified account holds only a particular role, reverting if the condition is not met. +### `get_role_count` +Returns the number of roles stored within the contract. +### `get_roles` +Retrieves the keys of roles stored within the contract, based on the provided range of indices. +### `get_role_member_count` +Returns the number of members assigned to a specified role. +### `get_role_members` +Retrieves the members of a specified role, based on the given range of indices. + +## Events + +### `RoleGranted` +Emitted when a role is assigned to an account. +### `RoleRevoked` +Emitted when a role is removed from an account. + +## Errors + +### `UNAUTHORIZED_ACCESS` +Indicates that an operation was attempted by an account lacking the necessary role. + +## Example + +```cairo +// Granting a role to an account +RoleStore.grant_role(account: ContractAddress, role_key: 'ADMIN') \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/router-module.md b/book/src/smart-contracts-architecture/router-module.md index 52049d9d..e2926564 100644 --- a/book/src/smart-contracts-architecture/router-module.md +++ b/book/src/smart-contracts-architecture/router-module.md @@ -1,6 +1,6 @@ # Router module -The exchange router is where users utilize the router to initiate token transactions, exchanges, and transfers. +The exchange router is the place where users go to start token trades, swaps, and moves. ## Front-running solution @@ -39,4 +39,4 @@ Prices are provided by an off-chain oracle system: It contains the following smart contracts: - [Router](https://github.com/keep-starknet-strange/satoru/blob/main/src/router/router.cairo): Users will approve this router for token expenditures. -- [ExchangeRouter](https://github.com/keep-starknet-strange/satoru/blob/main/src/router/exchange_router.cairo): Router for exchange functions, supports functions which require token transfers from the user. +- [ExchangeRouter](https://github.com/keep-starknet-strange/satoru/blob/main/src/router/exchange_router.cairo): Router for exchange functions, supports functions which require token transfers from the user. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/swap-module.md b/book/src/smart-contracts-architecture/swap-module.md index f0e9b22c..a07f1114 100644 --- a/book/src/smart-contracts-architecture/swap-module.md +++ b/book/src/smart-contracts-architecture/swap-module.md @@ -1,11 +1,65 @@ -# Swap module +# Swap Module -The swap module is reponsible for swaping. +The Swap Module is crucial for switching one token for another in the system. It makes sure the swap meets market conditions, adjusting for things like price changes and fees. -It contains the following smart contracts: +## Smart Contracts -- [SwapHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/swap/swap_handler): Smart contract to handle swaps. +- [SwapHandler](https://github.com/keep-starknet-strange/satoru/blob/main/src/swap/swap_handler): This contract is responsible for handling swaps, ensuring that only authorized entities can invoke the swap function. It validates the swap parameters and interacts with the `swap_utils` to perform the swap. -It contains the following Cairo library files: +## Cairo Library Files -- [swap_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/swap/swap_utils.cairo): It is responsible for swaping. +- [swap_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/swap/swap_utils.cairo): Implements the logic for performing swaps, including validating markets, calculating price impacts, applying fees, and transferring tokens. + +- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/swap/error.cairo): Defines errors specific to the Swap Module, handling cases like insufficient output amount, invalid input token, and duplicated market in swap path. + +## Structures and Types + +### `SwapParams` +This struct is used to pass parameters needed for executing a swap. It includes fields like: +- `data_store`: Provides access to on-chain data storage. +- `event_emitter`: Enables the emission of events. +- `oracle`: Provides access to price data from oracles. +- `bank`: Provides the funds for the swap. +- `token_in`: The address of the token being swapped. +- `amount_in`: The amount of the token being swapped. +- `swap_path_markets`: An array specifying the markets in which the swap should be executed. +- `min_output_amount`: The minimum amount of tokens that should be received as part of the swap. +- `receiver`: The address where the swapped tokens should be sent. + +### `SwapCache` +This struct caches data during a swap operation, including token addresses, prices, amounts, and price impacts. + +## Functions + +### `swap` +Executes a swap based on the given `SwapParams`, returning the address of the received token and the amount of the received token. It handles edge cases, such as zero amount in or empty swap path markets, and applies the swap to single or multiple markets as specified in the `swap_path_markets`. + +### `_swap` +Performs a swap on a single market, dealing with various conditions like token validity, price impact, and fees, and returns the token and amount that were swapped. + +## Errors + +### `SwapError` +Handles errors like: +- `INSUFFICIENT_OUTPUT_AMOUNT`: Triggered when the output amount is less than the minimum specified. +- `INVALID_TOKEN_IN`: Raised when the input token is not valid. +- `SWAP_PRICE_IMPACT_EXCEEDS_AMOUNT_IN`: Occurs when the price impact is more than the amount in. +- `DUPLICATED_MARKET_IN_SWAP_PATH`: Triggered when there is a duplicate market in the swap path. + +## Usage Example + +```cairo +let params = swap_utils::SwapParams { + data_store: /* ... */, + event_emitter: /* ... */, + oracle: /* ... */, + bank: /* ... */, + key: /* ... */, + token_in: /* ... */, + amount_in: /* ... */, + swap_path_markets: /* ... */, + min_output_amount: /* ... */, + receiver: /* ... */, + ui_fee_receiver: /* ... */, +}; +swap_utils::swap(params); \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/utils-module.md b/book/src/smart-contracts-architecture/utils-module.md index c8eaa9e6..98b2881f 100644 --- a/book/src/smart-contracts-architecture/utils-module.md +++ b/book/src/smart-contracts-architecture/utils-module.md @@ -1,27 +1,45 @@ # Utils module -The Utils module is a various collection of utility functions and tools designed to streamline various tasks within the protocol. This module serves as a repository for functions that do not fit into specific categories but are essential for enhancing the efficiency and readability of the codebase. +The Utils module is like a toolbox, filled with different helpful functions and tools that make tasks in the protocol easier. It’s a place for essential functions that don’t fit elsewhere but make the code clearer and more efficient. It contains the following files: -- [array.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/array.cairo): Helps with array manipulation. +- [account_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/account_utils.cairo): This file is like a helper in the project, it has functions to check accounts and receivers in the system. It uses methods like `validate_account` and `validate_receiver` to make sure accounts in operations are valid and real, making interactions within the system safer and more secure. This checking is important to avoid mistakes and weaknesses from invalid or empty account interactions. -- [basic_multicall.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/keys.cairo): Helps with multicall. +- [array.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/array.cairo): This file is a helper in the project, it has a bunch of functions for working with lists of items (arrays), which is important for managing data within the contract. It has functions like `get_felt252` and `get_u128` to safely get items from a list, and others like `are_eq`, `are_gt`, `are_gte`, `are_lt`, `are_lte` to compare items in the list to a certain value. This file is really important to make sure list operations are done safely and quickly, avoiding possible mistakes. -- [bits.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/keys.cairo): Bits constants. +- [basic_multicall.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/basic_multicall.cairo): This utility’s job is to handle and run groups of function calls on a contract, letting many actions happen in one go. It’s really important for saving on gas and making smart contract interactions more efficient. The `multicall` function in this file takes a list of function calls and runs them one after the other. -- [calc.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/keys.cairo): Various calculations. +- [bits.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/bits.cairo): This file has constants like `BITMASK_8`, `BITMASK_16`, `BITMASK_32`, and `BITMASK_64` that represent different sizes of binary words, used to do bit-level actions like shifting and changing bits on `u128` type values in the contract's code. They are really important for accurately changing binary data at a low level. -- [enumerable_values.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/keys.cairo): Extends EnumerableSet. +- [calc.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/calc.cairo): This file has many utility functions to do different math calculations and change types. It has functions for dividing, adding numbers with specific types, finding the absolute difference between two numbers, and adding and subtracting within limits to avoid going too high or too low. It also has functions to change between different types of numbers, making sure calculations in the contract's code are accurate and safe. -- [global_reentrancy_guard.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/keys.cairo): Reentrancy security on a global level. +- [enumerable_set.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/enumerable_set.cairo): This file creates a special `Set` structure for handling collections of unique items. It lets you make new sets, add and remove items, check if an item is in a set, access items in specific spots, and get all items as a list. It has special versions for `felt252`, `ContractAddress`, and `u128` types, making it adaptable and accurate for different kinds of data in the protocol. The set operations in this file are really important for different parts of the project, helping manage unique collections efficiently and neatly. -- [hash.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/keys.cairo): Hash utils. +- [enumerable_values.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/enumerable_values.cairo): This file is like an add-on to `EnumerableSet`, possibly offering more and specialized features to manage enumerable sets in the system. It has placeholder methods meant to give back lists of specific types of values (`felt252`, `ContractAddress`, `u128`) from a set, between certain start and end points. -- [precision.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/keys.cairo): Precisions utils. +- [error_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/error_utils.cairo): This file is dedicated to providing functionalities related to error handling within the system. -- [store_contract_address_array.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/keys.cairo): Implementation of store for Array of ContractAddress. +- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/error.cairo): This file defines constants for various types of errors, serving as standardized error messages or codes that can be referenced throughout the system to indicate specific error conditions. -- [u128_mask.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/keys.cairo): Mask function. +- [global_reentrancy_guard.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/global_reentrancy_guard.cairo): This file puts in place a high-level protection to keep smart contract functions safe from reentrancy attacks. It uses a global flag, `REENTRANCY_GUARD_STATUS`, to show if a secured function is running, and has `non_reentrant_before` and `non_reentrant_after` methods to control this flag, stopping harmful reentrant calls and making sure the smart contract runs consistently. -- [validate_account.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/keys.cairo): Helps validating accounts. +- [hash.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/hash.cairo): This file has utility functions for creating hashes, specifically with the Poseidon hash function. It has a function, `hash_poseidon_single`, that lets you hash a single `felt252` value using Poseidon, making sure data stays intact and meeting the cryptographic needs in the smart contract. + +- [i128_test_storage_contract.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/i128_test_storage_contract.cairo): This file creates a Starknet contract to test storing and getting `i128` values in the contract state. It’s a testing tool to make sure `i128` values are handled correctly in the smart contract environment. + +- [i128.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/i128.cairo): This file has ways to work with `i128` (signed 128-bit integers) in Cairo, including doing math operations, turning them into strings and back, and managing storage, allowing for precise and efficient use of `i128` values in Starknet smart contracts. + +- [precision.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/precision.cairo): This offers utility functions for detailed math and changing units, helping with accurate calculations and conversions between different measures, like from float to wei, applying factors, and managing rounding in the Satoru Starknet smart contract environment. + +- [serializable_dict.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/serializable_dict.cairo): This file defines the SerializableFelt252Dict structure that allows us to use a Felt252Dict and serialize/deserialize it. + +- [span32.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/span32.cairo): Provides utility functions for managing and manipulating fixed-size arrays (span32). A wrapper around Span type with a maximum size of 32. Used to prevent size overflow when storing Span. + +- [starknet_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/starknet_utils.cairo): This puts in place fake utilities to mimic Starknet environment features, like `gasleft` and `tx.gasprice`, in the Satoru Starknet smart contract environment. These functions give back set values based on the given parameters, allowing a way to mimic Starknet gas actions during testing and development. + +- [store_arrays.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/store_arrays.cairo): This gives ways to read and write different kinds of lists, including lists of `ContractAddress`, `Market`, `Price`, `u128`, `u64`, and `felt252`, to and from Starknet storage. This tool helps in storing different kinds of data in an organized way, allowing easy access and changes, which are key for the smart contracts in the Satoru project to work. + +- [traits.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/traits.cairo): This file has a way to make a default `ContractAddress` type from the Starknet library. It uses the `Default` trait to give a method, `default()`, that returns a `ContractAddress` set to `0`. This is useful when you need to make a `ContractAddress` with a default value when there is no specific address given. + +- [u128_mask.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/u128_mask.cairo): This file creates a `Mask` structure to check if a given index is unique within a range of 128 bits. The `Mask` has a `bits` field representing a 128-bit unsigned number. The `validate_unique_and_set_index` function checks if the bit at a specific index is unique and not set; if it’s already set or out of bounds, it will cause an error. If not, it sets the bit at that index, showing that this index is now taken. This tool is useful for managing and confirming the uniqueness of indices in a 128-bit range. \ No newline at end of file diff --git a/book/src/smart-contracts-architecture/withdrawal-module.md b/book/src/smart-contracts-architecture/withdrawal-module.md index 3c0efffb..1771968a 100644 --- a/book/src/smart-contracts-architecture/withdrawal-module.md +++ b/book/src/smart-contracts-architecture/withdrawal-module.md @@ -1,13 +1,92 @@ -# Withdrawal module +# Withdrawal Module -The withdrawal module is responsible for managing withdrawals. +The Withdrawal Module role is to manage the operations related to withdrawals. -It contains the following smart contracts: +## Smart Contracts -- [WithdrawalVault](https://github.com/keep-starknet-strange/satoru/blob/main/src/withdrawal/withdrawal_vault.cairo): Vault for withdrawals. +### [WithdrawalVault](https://github.com/keep-starknet-strange/satoru/blob/main/src/withdrawal/withdrawal_vault.cairo) +The WithdrawalVault is the vault specifically designed for withdrawals, ensuring the secure management of funds during the withdrawal processes. -It contains the following Cairo library files: +## Cairo Library Files -- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/withdrawal/error.cairo): Contains the error codes of the module. -- [withdrawal_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/withdrawal/withdrawal_utils.cairo): Contains withdrawal utility functions. -- [withdrawal.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/withdrawal/withdrawal.cairo): Contains withdrawal struct. +- [error.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/withdrawal/error.cairo): Holds the module-specific error codes. +- [withdrawal_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/withdrawal/withdrawal_utils.cairo): Encapsulates withdrawal-related utility functions. +- [withdrawal.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/withdrawal/withdrawal.cairo): Defines the structures related to withdrawal. + +### Withdrawal Creation and Execution +In this module, withdrawals can be created through the `create_withdrawal` function, which needs parameters such as the account initiating the withdrawal, the receiver of the tokens, and various other details related to the withdrawal. + +Execution of withdrawals is handled by the `execute_withdrawal` function, requiring parameters such as the unique identifier of the withdrawal and the event emitter used to emit events. + +### Swap Mechanism +The module includes a `swap` function, used to swap tokens within the context of executing withdrawals, ensuring the internal state changes are correct before calling external callbacks. + +## Structures and Types + +### `Storage` +This structure holds the interface to interact with the `DataStore` contract. +- `strict_bank`: Represents the interface to interact with the `IStrictBankDispatcher`. + +### `Withdrawal` +This structure represents a withdrawal within the system, holding essential information related to a specific withdrawal operation. The structure includes the following fields: +- `key`: A unique identifier of the withdrawal, represented as a `felt252` type. +- `account`: The account of the order, represented as a `ContractAddress`. +- `receiver`: The receiver for any token transfers, represented as a `ContractAddress`. +- `callback_contract`: The contract to call for callbacks, represented as a `ContractAddress`. +- `ui_fee_receiver`: The UI fee receiver, represented as a `ContractAddress`. +- `market`: The trading market, represented as a `ContractAddress`. +- `long_token_swap_path`: An array of market addresses to swap through for long tokens, represented as a `Span32`. +- `short_token_swap_path`: An array of market addresses to swap through for short tokens, represented as a `Span32`. +- `market_token_amount`: The amount of market tokens that will be withdrawn, represented as a `u128` type. +- `min_long_token_amount`: The minimum amount of long tokens that must be withdrawn, represented as a `u128` type. +- `min_short_token_amount`: The minimum amount of short tokens that must be withdrawn, represented as a `u128` type. +- `updated_at_block`: The block at which the withdrawal was last updated, represented as a `u64` type. +- `execution_fee`: The execution fee for the withdrawal, represented as a `u128` type. +- `callback_gas_limit`: The gas limit for calling the callback contract, represented as a `u128` type. + +### `Balance` +Represents the balance of an asset and includes the following fields: +- `amount`: The total amount of the asset, represented as a `u128` type. +- `locked`: The amount of the asset that is locked, represented as a `u128` type. + +### `Asset` +This structure represents an asset within the system and includes the following fields: +- `symbol`: The symbol of the asset, represented as a string. +- `decimals`: The number of decimals the asset uses, represented as a `u8` type. +- `total_supply`: The total supply of the asset, represented as a `u128` type. + +### Other Structures +- `CreateWithdrawalParams`: Holds parameters needed for creating a withdrawal, such as the receiver and the market on which the withdrawal will be executed. +- `ExecuteWithdrawalParams`: Holds parameters needed for executing a withdrawal, such as the data store where withdrawal data is stored and the unique identifier of the withdrawal to execute. +- `ExecuteWithdrawalCache`: Utilized to cache the results temporarily when executing a withdrawal. +- `ExecuteWithdrawalResult`: Represents the result of a withdrawal execution, holding details of the output token and its amount. +- `SwapCache`: Holds data related to token swap operations, such as the swap path markets and the output token and its amount. + +## Functions + +### `initialize` +This function is utilized to initialize the contract with the address of the strict bank contract. + +### `record_transfer_in` +Records the transfer in operation and returns a `u128` type representing the recorded value. + +### `transfer_out` +Executes the transfer out operation to the specified receiver with the defined amount. + +### `sync_token_balance` +Synchronizes the token balance and returns a `u128` type representing the synchronized value. + +## Errors + +The module employs `WithdrawalError` to address errors inherent to withdrawal operations. Here are the defined errors: +- `ALREADY_INITIALIZED`: Triggered if the contract has already been initialized, represented by the constant `'already_initialized'`. +- `NOT_FOUND`: Triggered when a specified withdrawal is not found in the system, represented by the constant `'withdrawal not found'`. +- `CANT_BE_ZERO`: Triggered when a withdrawal account is zero, represented by the constant `'withdrawal account cant be 0'`. +- `EMPTY_WITHDRAWAL_AMOUNT`: Occurs when an attempt is made to withdraw an empty amount, represented by the constant `'empty withdrawal amount'`. +- `EMPTY_WITHDRAWAL`: Occurs when a withdrawal is empty, represented by the constant `'empty withdrawal'`. + +Additionally, the module defines several panic functions to handle specific error scenarios with more context: +- `INSUFFICIENT_FEE_TOKEN_AMOUNT(data_1: u128, data_2: u128)`: Triggered when there is an insufficient amount of fee tokens, providing additional context with `data_1` and `data_2`. +- `INSUFFICIENT_MARKET_TOKENS(data_1: u128, data_2: u128)`: Triggered when there are insufficient market tokens available, providing additional context with `data_1` and `data_2`. +- `INVALID_POOL_VALUE_FOR_WITHDRAWAL(data: u128)`: Triggered when an invalid pool value is provided for withdrawal, providing additional context with `data`. +- `INVALID_WITHDRAWAL_KEY(data: felt252)`: Triggered when an invalid withdrawal key is provided, providing additional context with `data`. \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..b2607303 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "@types/node": "^20.11.16", + "dotenv": "^16.4.1", + "starknet": "^6.6.6", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + } +} diff --git a/scripts/actions/createLongOrder.ts b/scripts/actions/createLongOrder.ts new file mode 100644 index 00000000..5a8d0b17 --- /dev/null +++ b/scripts/actions/createLongOrder.ts @@ -0,0 +1,70 @@ +import { Account, Contract, json, Calldata, CallData, RpcProvider, shortString, uint256, CairoCustomEnum, ec } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function create_market() { + + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const marketTokenAddress = "0x69cfad927e7e4ef53261ad9a4630631ff8404746720ce3c73368de8291c4c4d" + const eth: string = "0x376bbceb1a044263cba28211fdcaee4e234ebf0c012521e1b258684bbc44949" + const usdc: string = "0x42a9a03ceb10ca07d3f598a627c414fe218b1138a78e3da6ce1675680cf95f2" + + const account0 = new Account(provider, account0Address!, privateKey0!) + console.log("Interacting with Account: " + account0Address) + + const compiledOrderHandlerSierra = json.parse(fs.readFileSync( "./target/dev/satoru_OrderHandler.contract_class.json").toString( "ascii")) + + const orderHandlerContract = new Contract(compiledOrderHandlerSierra.abi, process.env.ORDER_HANDLER as string, provider); + const compiledERC20Sierra = json.parse(fs.readFileSync( "./target/dev/satoru_ERC20.contract_class.json").toString( "ascii")) + + const ethContract = new Contract(compiledERC20Sierra.abi, eth as string, provider) + ethContract.connect(account0) + const transferCall = ethContract.populate("transfer", [process.env.ORDER_VAULT as string, uint256.bnToUint256(1000000000000000000n)]) + const transferTx = await ethContract.transfer(transferCall.calldata) + await provider.waitForTransaction(transferTx.transaction_hash) + + const compiledRoleStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_RoleStore.contract_class.json").toString( "ascii")) + const roleStoreContract = new Contract(compiledRoleStoreSierra.abi, process.env.ROLE_STORE as string, provider) + roleStoreContract.connect(account0); + + const roleCall4 = roleStoreContract.populate("grant_role", [process.env.ORDER_UTILS as string, shortString.encodeShortString("CONTROLLER")]) + const grant_role_tx4 = await roleStoreContract.grant_role(roleCall4.calldata) + await provider.waitForTransaction(grant_role_tx4.transaction_hash) + + orderHandlerContract.connect(account0) + const createOrderParams = { + receiver: account0.address, + callback_contract: 0, + ui_fee_receiver: 0, + market: marketTokenAddress, + initial_collateral_token: eth, + swap_path: [], + size_delta_usd: uint256.bnToUint256(10000000000000000000000n), + initial_collateral_delta_amount: uint256.bnToUint256(2000000000000000000n), + trigger_price: uint256.bnToUint256(5000), + acceptable_price: uint256.bnToUint256(5500), + execution_fee: uint256.bnToUint256(0), + callback_gas_limit: uint256.bnToUint256(0), + min_output_amount: uint256.bnToUint256(0), + order_type: new CairoCustomEnum({ MarketIncrease: {} }), + decrease_position_swap_type: new CairoCustomEnum({ NoSwap: {} }), + is_long: 1, + referral_code: 0 + }; + const createOrderCall = orderHandlerContract.populate("create_order", [ + account0.address, + createOrderParams + ]) + const createOrderTx = await orderHandlerContract.create_order(createOrderCall.calldata) + await provider.waitForTransaction(createOrderTx.transaction_hash) + console.log("Order created.") +} + +create_market() \ No newline at end of file diff --git a/scripts/actions/createMarket.ts b/scripts/actions/createMarket.ts new file mode 100644 index 00000000..869b6820 --- /dev/null +++ b/scripts/actions/createMarket.ts @@ -0,0 +1,153 @@ +import { Account, Contract, json, Calldata, CallData, RpcProvider, shortString, uint256, CairoCustomEnum, ec } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function create_market() { + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const account0 = new Account(provider, account0Address!, privateKey0!) + console.log("Interacting with Account: " + account0Address) + + let eth = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" + + const dataStoreAddress = process.env.DATA_STORE as string + const compiledDataStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_DataStore.contract_class.json").toString( "ascii")) + const dataStoreContract = new Contract(compiledDataStoreSierra.abi, dataStoreAddress, provider) + dataStoreContract.connect(account0); + const dataCall = dataStoreContract.populate( + "set_address", + [ec.starkCurve.poseidonHashMany([BigInt(shortString.encodeShortString("FEE_TOKEN"))]), process.env.FEE_TOKEN as string]) + const setAddressTx = await dataStoreContract.set_address(dataCall.calldata) + await provider.waitForTransaction(setAddressTx.transaction_hash) + const dataCall2 = dataStoreContract.populate( + "set_u256", + [ec.starkCurve.poseidonHashMany([BigInt(shortString.encodeShortString("MAX_SWAP_PATH_LENGTH"))]), 5n]) + const setAddressTx2 = await dataStoreContract.set_u256(dataCall2.calldata) + await provider.waitForTransaction(setAddressTx2.transaction_hash) + + const dataCall3 = dataStoreContract.populate( + "set_u256", + [ec.starkCurve.poseidonHashMany([BigInt(shortString.encodeShortString("MAX_ORACLE_PRICE_AGE"))]), 1000000000000n]) + const setAddressTx3 = await dataStoreContract.set_u256(dataCall3.calldata) + await provider.waitForTransaction(setAddressTx2.transaction_hash) + + + console.log("Deploying USDC...") + const compiledERC20Casm = json.parse(fs.readFileSync( "./target/dev/satoru_ERC20.compiled_contract_class.json").toString( "ascii")) + const compiledERC20Sierra = json.parse(fs.readFileSync( "./target/dev/satoru_ERC20.contract_class.json").toString( "ascii")) + const erc20CallData: CallData = new CallData(compiledERC20Sierra.abi) + const erc20Constructor: Calldata = erc20CallData.compile("constructor", { + name: "USDC", + symbol: "USDC", + initial_supply: "100000000000000000000000", + recipient: account0Address + }) + const deployERC20Response = await account0.declareAndDeploy({ + contract: compiledERC20Sierra, + casm: compiledERC20Casm, + constructorCalldata: erc20Constructor, + }) + console.log("USDC Deployed at: " + deployERC20Response.deploy.contract_address) + + const marketFactoryAddress = process.env.MARKET_FACTORY as string + const compiledMarketFactorySierra = json.parse(fs.readFileSync( "./target/dev/satoru_MarketFactory.contract_class.json").toString( "ascii")) + + const roleStoreAddress = process.env.ROLE_STORE as string + const compiledRoleStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_RoleStore.contract_class.json").toString( "ascii")) + const roleStoreContract = new Contract(compiledRoleStoreSierra.abi, roleStoreAddress, provider) + roleStoreContract.connect(account0) + const roleCall = roleStoreContract.populate("grant_role", [marketFactoryAddress, shortString.encodeShortString("CONTROLLER")]) + const grant_role_tx = await roleStoreContract.grant_role(roleCall.calldata) + await provider.waitForTransaction(grant_role_tx.transaction_hash) + + + const abi = compiledMarketFactorySierra.abi + const marketFactoryContract = new Contract(abi, marketFactoryAddress, provider); + console.log("Connected to MarketFactory: " + marketFactoryAddress) + marketFactoryContract.connect(account0) + + console.log("Granting roles...") + const roleCall2 = roleStoreContract.populate("grant_role", [process.env.MARKET_FACTORY as string, shortString.encodeShortString("MARKET_KEEPER")]) + + const grant_role_tx2 = await roleStoreContract.grant_role(roleCall2.calldata) + await provider.waitForTransaction(grant_role_tx2.transaction_hash) + console.log("Roles granted.") + + console.log("Creating Market...") + const myCall = marketFactoryContract.populate("create_market", [ + eth, + eth, + deployERC20Response.deploy.contract_address, + "market_type" + ]); + const res = await marketFactoryContract.create_market(myCall.calldata); + const marketTokenAddress = (await provider.waitForTransaction(res.transaction_hash) as any).events[0].data[1]; + console.log("Market created: " + marketTokenAddress) + + const orderVaultAddress = process.env.ORDER_VAULT as string + const ethContract = new Contract(compiledERC20Sierra.abi, eth as string, provider) + ethContract.connect(account0) + const transferCall = ethContract.populate("transfer", [orderVaultAddress, uint256.bnToUint256(1000n)]) + const transferTx = await ethContract.transfer(transferCall.calldata) + await provider.waitForTransaction(transferTx.transaction_hash) + const transferCall2 = ethContract.populate("transfer", [marketTokenAddress, uint256.bnToUint256(10000n)]) + const transferTx2 = await ethContract.transfer(transferCall2.calldata) + await provider.waitForTransaction(transferTx2.transaction_hash) + + const usdcContract = new Contract(compiledERC20Sierra.abi, deployERC20Response.deploy.contract_address, provider) + usdcContract.connect(account0) + const transferUSDCCall = usdcContract.populate("transfer", [marketTokenAddress, uint256.bnToUint256(10000n)]) + const transferUSDCTx = await usdcContract.transfer(transferUSDCCall.calldata) + await provider.waitForTransaction(transferUSDCTx.transaction_hash) + + const compiledOracleSierra = json.parse(fs.readFileSync( "./target/dev/satoru_Oracle.contract_class.json").toString( "ascii")) + + const abiOracle = compiledOracleSierra.abi + const oracleContract = new Contract(abiOracle, process.env.ORACLE as string, provider); + oracleContract.connect(account0); + const setPrimaryPriceCall1 = oracleContract.populate("set_primary_price", [ethContract.address, uint256.bnToUint256(5000n)]) + const setPrimaryPriceTx1 = await oracleContract.set_primary_price(setPrimaryPriceCall1.calldata); + await provider.waitForTransaction(setPrimaryPriceTx1.transaction_hash) + + const setPrimaryPriceCall2 = oracleContract.populate("set_primary_price", [usdcContract.address, uint256.bnToUint256(1n)]) + const setPrimaryPriceTx2 = await oracleContract.set_primary_price(setPrimaryPriceCall2.calldata); + await provider.waitForTransaction(setPrimaryPriceTx2.transaction_hash) + console.log("Primary prices set.") + // const orderHandlerContract = new Contract(compiledOrderHandlerSierra.abi, orderHandlerAddress, provider); + + // orderHandlerContract.connect(account0) + // const createOrderParams = { + // receiver: account0.address, + // callback_contract: 0, + // ui_fee_receiver: 0, + // market: 0, + // initial_collateral_token: eth, + // swap_path: [marketTokenAddress], + // size_delta_usd: uint256.bnToUint256(1000), + // initial_collateral_delta_amount: uint256.bnToUint256(10000), + // trigger_price: uint256.bnToUint256(0), + // acceptable_price: uint256.bnToUint256(0), + // execution_fee: uint256.bnToUint256(0), + // callback_gas_limit: uint256.bnToUint256(0), + // min_output_amount: uint256.bnToUint256(0), + // order_type: new CairoCustomEnum({ MarketSwap: {} }), + // decrease_position_swap_type: new CairoCustomEnum({ NoSwap: {} }), + // is_long: 0, + // referral_code: 0 + // }; + // const createOrderCall = orderHandlerContract.populate("create_order", [ + // account0.address, + // createOrderParams + // ]) + // const createOrderTx = await orderHandlerContract.create_order(createOrderCall.calldata) + // await provider.waitForTransaction(createOrderTx.transaction_hash) + // console.log("Order created.") +} + +create_market() \ No newline at end of file diff --git a/scripts/actions/createMarketAndDeposit.ts b/scripts/actions/createMarketAndDeposit.ts new file mode 100644 index 00000000..5147fe2f --- /dev/null +++ b/scripts/actions/createMarketAndDeposit.ts @@ -0,0 +1,371 @@ +import { Account, Contract, json, Calldata, CallData, RpcProvider, shortString, uint256, CairoCustomEnum, ec } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function create_market() { + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const account0 = new Account(provider, account0Address!, privateKey0!) + console.log("Interacting with Account: " + account0Address) + + // let eth = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" + + // const dataStoreAddress = process.env.DATA_STORE as string + // const compiledDataStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_DataStore.contract_class.json").toString( "ascii")) + // const dataStoreContract = new Contract(compiledDataStoreSierra.abi, dataStoreAddress, provider) + // dataStoreContract.connect(account0); + // const dataCall = dataStoreContract.populate( + // "set_address", + // [ec.starkCurve.poseidonHashMany([BigInt(shortString.encodeShortString("FEE_TOKEN"))]), process.env.FEE_TOKEN as string]) + // const setAddressTx = await dataStoreContract.set_address(dataCall.calldata) + // await provider.waitForTransaction(setAddressTx.transaction_hash) + // const dataCall2 = dataStoreContract.populate( + // "set_u256", + // [ec.starkCurve.poseidonHashMany([BigInt(shortString.encodeShortString("MAX_SWAP_PATH_LENGTH"))]), 5n]) + // const setAddressTx2 = await dataStoreContract.set_u256(dataCall2.calldata) + // await provider.waitForTransaction(setAddressTx2.transaction_hash) + + // const dataCall3 = dataStoreContract.populate( + // "set_u256", + // [ec.starkCurve.poseidonHashMany([BigInt(shortString.encodeShortString("MAX_ORACLE_PRICE_AGE"))]), 1000000000000n]) + // const setAddressTx3 = await dataStoreContract.set_u256(dataCall3.calldata) + // await provider.waitForTransaction(setAddressTx3.transaction_hash) + + // const compiledERC20Casm = json.parse(fs.readFileSync( "./target/dev/satoru_ERC20.compiled_contract_class.json").toString( "ascii")) + // const compiledERC20Sierra = json.parse(fs.readFileSync( "./target/dev/satoru_ERC20.contract_class.json").toString( "ascii")) + // const erc20CallData: CallData = new CallData(compiledERC20Sierra.abi) + // const erc20Constructor: Calldata = erc20CallData.compile("constructor", { + // name: "USDC", + // symbol: "USDC", + // initial_supply: "10000000000000000000", + // recipient: account0Address + // }) + // const deployERC20Response = await account0.declareAndDeploy({ + // contract: compiledERC20Sierra, + // casm: compiledERC20Casm, + // constructorCalldata: erc20Constructor, + // }) + // console.log("USDC Deployed at: " + deployERC20Response.deploy.contract_address) + + // const zETHCallData: CallData = new CallData(compiledERC20Sierra.abi) + // const zETHConstructor: Calldata = zETHCallData.compile("constructor", { + // name: "zEthereum", + // symbol: "zETH", + // initial_supply: "50000000000000000000000", + // recipient: account0Address + // }) + // const deployzETHResponse = await account0.declareAndDeploy({ + // contract: compiledERC20Sierra, + // casm: compiledERC20Casm, + // constructorCalldata: zETHConstructor, + // }) + // console.log("zETH Deployed at: " + deployzETHResponse.deploy.contract_address) + + // const marketFactoryAddress = process.env.MARKET_FACTORY as string + // const compiledMarketFactorySierra = json.parse(fs.readFileSync( "./target/dev/satoru_MarketFactory.contract_class.json").toString( "ascii")) + + const roleStoreAddress = process.env.ROLE_STORE as string + const compiledRoleStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_RoleStore.contract_class.json").toString( "ascii")) + const roleStoreContract = new Contract(compiledRoleStoreSierra.abi, roleStoreAddress, provider) + roleStoreContract.connect(account0) + const roleCall = roleStoreContract.populate("grant_role", ["0x04219D87E41d0eA40746f05DaB73659f5176cD328C5bE466027f93305089E166", shortString.encodeShortString("FROZEN_ORDER_KEEPER")]) + const grant_role_tx = await roleStoreContract.grant_role(roleCall.calldata) + await provider.waitForTransaction(grant_role_tx.transaction_hash) + + + // const abi = compiledMarketFactorySierra.abi + // const marketFactoryContract = new Contract(abi, marketFactoryAddress, provider); + // console.log("Connected to MarketFactory: " + marketFactoryAddress) + // marketFactoryContract.connect(account0) + + // console.log("Granting roles...") + // const roleCall2 = roleStoreContract.populate("grant_role", [process.env.MARKET_FACTORY as string, shortString.encodeShortString("MARKET_KEEPER")]) + // const grant_role_tx2 = await roleStoreContract.grant_role(roleCall2.calldata) + // await provider.waitForTransaction(grant_role_tx2.transaction_hash) + + // const roleCall3 = roleStoreContract.populate("grant_role", [process.env.DEPOSIT_HANDLER as string, shortString.encodeShortString("CONTROLLER")]) + // const grant_role_tx3 = await roleStoreContract.grant_role(roleCall3.calldata) + // await provider.waitForTransaction(grant_role_tx3.transaction_hash) + + // const roleCall4 = roleStoreContract.populate("grant_role", [process.env.ORDER_HANDLER as string, shortString.encodeShortString("CONTROLLER")]) + // const grant_role_tx4 = await roleStoreContract.grant_role(roleCall4.calldata) + // await provider.waitForTransaction(grant_role_tx4.transaction_hash) + // console.log("Roles granted.") + + // console.log("Creating Market...") + // const myCall = marketFactoryContract.populate("create_market", [ + // deployzETHResponse.deploy.contract_address, + // deployzETHResponse.deploy.contract_address, + // deployERC20Response.deploy.contract_address, + // "market_type" + // ]); + // const res = await marketFactoryContract.create_market(myCall.calldata); + // const marketTokenAddress = (await provider.waitForTransaction(res.transaction_hash) as any).events[0].data[1]; + // console.log("Market created: " + marketTokenAddress) + + // // Set constants for trade + // dataStoreContract.connect(account0); + // const dataCall5 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_max_pool_amount_key(marketTokenAddress, deployzETHResponse.deploy.contract_address), + // 2500000000000000000000000000000000000000000000n + // ] + // ) + // const setAddressTx5 = await dataStoreContract.set_u256(dataCall5.calldata) + // await provider.waitForTransaction(setAddressTx5.transaction_hash) + + // const dataCall6 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_max_pool_amount_key(marketTokenAddress, deployERC20Response.deploy.contract_address), + // 2500000000000000000000000000000000000000000000n + // ] + // ) + // const setAddressTx6 = await dataStoreContract.set_u256(dataCall6.calldata) + // await provider.waitForTransaction(setAddressTx6.transaction_hash) + + // // Set Constants for long + // const dataCall7 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_max_pnl_factor_key( + // "0x4896bc14d7c67b49131baf26724d3f29032ddd7539a3a8d88324140ea2de9b4", + // marketTokenAddress, + // true + // ), + // 50000000000000000000000000000000000000000000000n + // ] + // ) + // const setAddressTx7 = await dataStoreContract.set_u256(dataCall7.calldata) + // await provider.waitForTransaction(setAddressTx7.transaction_hash) + + // const dataCall9 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_max_pnl_factor_key( + // "0x425655404757d831905ce0c7aeb290f47c630d959038f3d087a009ba1236dbe", + // marketTokenAddress, + // true + // ), + // 50000000000000000000000000000000000000000000000n + // ] + // ) + // const setAddressTx9 = await dataStoreContract.set_u256(dataCall9.calldata) + // await provider.waitForTransaction(setAddressTx9.transaction_hash) + + // const dataCall10 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_reserve_factor_key( + // marketTokenAddress, + // true + // ), + // 1000000000000000000n + // ] + // ) + // const setAddressTx10 = await dataStoreContract.set_u256(dataCall10.calldata) + // await provider.waitForTransaction(setAddressTx10.transaction_hash) + + // const dataCall11 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_open_interest_reserve_factor_key( + // marketTokenAddress, + // true + // ), + // 1000000000000000000n + // ] + // ) + // const setAddressTx11 = await dataStoreContract.set_u256(dataCall11.calldata) + // await provider.waitForTransaction(setAddressTx11.transaction_hash) + + // const dataCall12 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_open_interest_key( + // marketTokenAddress, + // deployzETHResponse.deploy.contract_address, + // true + // ), + // 1n + // ] + // ) + // const setAddressTx12 = await dataStoreContract.set_u256(dataCall12.calldata) + // await provider.waitForTransaction(setAddressTx12.transaction_hash) + + // const dataCall8 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_max_open_interest_key( + // marketTokenAddress, + // true + // ), + // 1000000000000000000000000000000000000000000000000000n + // ] + // ) + // const setAddressTx8 = await dataStoreContract.set_u256(dataCall8.calldata) + // await provider.waitForTransaction(setAddressTx8.transaction_hash) + + // // Set constants for short + // const dataCall13 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_max_pnl_factor_key( + // "0x4896bc14d7c67b49131baf26724d3f29032ddd7539a3a8d88324140ea2de9b4", + // marketTokenAddress, + // false + // ), + // 50000000000000000000000000000000000000000000000n + // ] + // ) + // const setAddressTx13 = await dataStoreContract.set_u256(dataCall13.calldata) + // await provider.waitForTransaction(setAddressTx13.transaction_hash) + + // const dataCall14 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_max_pnl_factor_key( + // "0x425655404757d831905ce0c7aeb290f47c630d959038f3d087a009ba1236dbe", + // marketTokenAddress, + // false + // ), + // 50000000000000000000000000000000000000000000000n + // ] + // ) + // const setAddressTx14 = await dataStoreContract.set_u256(dataCall14.calldata) + // await provider.waitForTransaction(setAddressTx14.transaction_hash) + + // const dataCall15 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_reserve_factor_key( + // marketTokenAddress, + // false + // ), + // 1000000000000000000n + // ] + // ) + // const setAddressTx15 = await dataStoreContract.set_u256(dataCall15.calldata) + // await provider.waitForTransaction(setAddressTx15.transaction_hash) + + // const dataCall16 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_open_interest_reserve_factor_key( + // marketTokenAddress, + // false + // ), + // 1000000000000000000n + // ] + // ) + // const setAddressTx16 = await dataStoreContract.set_u256(dataCall16.calldata) + // await provider.waitForTransaction(setAddressTx16.transaction_hash) + + // const dataCall17 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_open_interest_key( + // marketTokenAddress, + // deployERC20Response.deploy.contract_address, + // false + // ), + // 1n + // ] + // ) + // const setAddressTx17 = await dataStoreContract.set_u256(dataCall17.calldata) + // await provider.waitForTransaction(setAddressTx17.transaction_hash) + + // const dataCall18 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_max_open_interest_key( + // marketTokenAddress, + // false + // ), + // 1000000000000000000000000000000000000000000000000000n + // ] + // ) + // const setAddressTx18 = await dataStoreContract.set_u256(dataCall18.calldata) + // await provider.waitForTransaction(setAddressTx18.transaction_hash) + + + // const usdcContract = new Contract(compiledERC20Sierra.abi, deployERC20Response.deploy.contract_address, provider) + // usdcContract.connect(account0) + + // const depositVaultAddress = process.env.DEPOSIT_VAULT as string + // const zEthContract = new Contract(compiledERC20Sierra.abi, deployzETHResponse.deploy.contract_address, provider) + // zEthContract.connect(account0) + + // const transferCall2 = zEthContract.populate("mint", [marketTokenAddress, uint256.bnToUint256(50000000000000000000000000000000000000n)]) + // const transferTx2 = await zEthContract.mint(transferCall2.calldata) + // await provider.waitForTransaction(transferTx2.transaction_hash) + // const transferUSDCCall = usdcContract.populate("mint", [marketTokenAddress, uint256.bnToUint256(25000000000000000000000000000000000000000n)]) + // const transferUSDCTx = await usdcContract.mint(transferUSDCCall.calldata) + // await provider.waitForTransaction(transferUSDCTx.transaction_hash) + + // console.log("All pre-settings done.") + + // NOT NEEDED NOW + + // const compiledERC20Casm = json.parse(fs.readFileSync( "./target/dev/satoru_ERC20.compiled_contract_class.json").toString( "ascii")) + // const compiledERC20Sierra = json.parse(fs.readFileSync( "./target/dev/satoru_ERC20.contract_class.json").toString( "ascii")) + // const erc20CallData: CallData = new CallData(compiledERC20Sierra.abi) + + // let USDCAddress = "0x6f82b80bfead3a249ee4352b27075dfa327de91e8e6df9755eb4f31de406d98"; + // let ETHaddress = "0x369c220f2a4699495bfe73ffe8a522f1bf1570c903c0d8fcf3767a252f7ae9a"; + // let MarketTokenAddress = "0x122cd6989d2429f580a0bff5e70cdb84b2bff4f8d19cee6b30a15d08c447e85"; + + // const usdcContract = new Contract(compiledERC20Sierra.abi, USDCAddress, provider) + // usdcContract.connect(account0) + + // const zEthContract = new Contract(compiledERC20Sierra.abi, ETHaddress, provider) + // zEthContract.connect(account0) + + // let depositVaultAddress = "0xad087c985ff7655d26eeaa496510a0590dd73b23d7e15beb53c79045ee4b6b"; + // let depositHandlerAddress = "0x7d82433606ef19a1f8a2d7e9be45c02677e214b83d2a079c930bc379ee246ef"; + + // const transferCall = zEthContract.populate("mint", [depositVaultAddress, uint256.bnToUint256(50000000000000000000000000000n)]) + // const transferTx = await zEthContract.mint(transferCall.calldata) + // await provider.waitForTransaction(transferTx.transaction_hash) + // const transferUSDCCall2 = usdcContract.populate("mint", [depositVaultAddress, uint256.bnToUint256(50000000000000000000000000000n)]) + // const transferUSDCTx2 = await usdcContract.mint(transferUSDCCall2.calldata) + // await provider.waitForTransaction(transferUSDCTx2.transaction_hash) + + // console.log("Sending tokens to the deposit vault...") + + // console.log("Creating Deposit...") + // const compiledDepositHandlerSierra = json.parse(fs.readFileSync( "./target/dev/satoru_DepositHandler.contract_class.json").toString( "ascii")) + + // const depositHandlerContract = new Contract(compiledDepositHandlerSierra.abi, depositHandlerAddress, provider); + + // depositHandlerContract.connect(account0) + // const createDepositParams = { + // receiver: account0.address, + // callback_contract: 0, + // ui_fee_receiver: 0, + // market: MarketTokenAddress, + // initial_long_token: ETHaddress, + // initial_short_token: USDCAddress, + // long_token_swap_path: [], + // short_token_swap_path: [], + // min_market_tokens: uint256.bnToUint256(0), + // execution_fee: uint256.bnToUint256(0), + // callback_gas_limit: uint256.bnToUint256(0), + // }; + // const createOrderCall = depositHandlerContract.populate("create_deposit", [ + // account0.address, + // createDepositParams + // ]) + // const createOrderTx = await depositHandlerContract.create_deposit(createOrderCall.calldata) + // await provider.waitForTransaction(createOrderTx.transaction_hash) + // console.log("Deposit created.") +} + +create_market() \ No newline at end of file diff --git a/scripts/actions/createSwapOrder.ts b/scripts/actions/createSwapOrder.ts new file mode 100644 index 00000000..3164f3b6 --- /dev/null +++ b/scripts/actions/createSwapOrder.ts @@ -0,0 +1,72 @@ +import { Account, Contract, json, Calldata, CallData, RpcProvider, shortString, uint256, CairoCustomEnum, ec } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function create_market() { + + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const marketTokenAddress = "0x4b3bd2fe7f3dd02a6a143a3040ede80048388e0cf1c20dc748d6a6d6fa93069" + const eth: string = "0x75acffcc1c3661fe1cfbb6d2c444355ef01e85a40e65962a4d9a2ac38903934" + const usdc: string = "0x70d22d4962de09d9ec0a590e9ff33a496425277235890575457f9582d837964" + + const account0 = new Account(provider, account0Address!, privateKey0!) + console.log("Interacting with Account: " + account0Address) + + const compiledOrderHandlerSierra = json.parse(fs.readFileSync( "./target/dev/satoru_OrderHandler.contract_class.json").toString( "ascii")) + + const orderHandlerContract = new Contract(compiledOrderHandlerSierra.abi, process.env.ORDER_HANDLER as string, provider); + const compiledERC20Sierra = json.parse(fs.readFileSync( "./target/dev/satoru_ERC20.contract_class.json").toString( "ascii")) + + const ethContract = new Contract(compiledERC20Sierra.abi, eth as string, provider) + ethContract.connect(account0) + const transferCall = ethContract.populate("transfer", [process.env.ORDER_VAULT as string, uint256.bnToUint256(1000000000000000000n)]) + const transferTx = await ethContract.transfer(transferCall.calldata) + await provider.waitForTransaction(transferTx.transaction_hash) + + const compiledRoleStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_RoleStore.contract_class.json").toString( "ascii")) + const roleStoreContract = new Contract(compiledRoleStoreSierra.abi, process.env.ROLE_STORE as string, provider) + roleStoreContract.connect(account0); + + console.log("Granting roles...") + const roleCall2 = roleStoreContract.populate("grant_role", ["0x05fc5a52d7141a90b79663eb22b80f7a13ec1fce7232bc8c4a03528f552cb02b" as string, shortString.encodeShortString("CONTROLLER")]) + const grant_role_tx2 = await roleStoreContract.grant_role(roleCall2.calldata) + await provider.waitForTransaction(grant_role_tx2.transaction_hash) + console.log("Roles granted.") + + orderHandlerContract.connect(account0) + const createOrderParams = { + receiver: account0.address, + callback_contract: 0, + ui_fee_receiver: 0, + market: 0, + initial_collateral_token: eth, + swap_path: [marketTokenAddress], + size_delta_usd: uint256.bnToUint256(5000000000000000000000n), + initial_collateral_delta_amount: uint256.bnToUint256(1000000000000000000n), + trigger_price: uint256.bnToUint256(0), + acceptable_price: uint256.bnToUint256(0), + execution_fee: uint256.bnToUint256(0), + callback_gas_limit: uint256.bnToUint256(0), + min_output_amount: uint256.bnToUint256(0), + order_type: new CairoCustomEnum({ MarketSwap: {} }), + decrease_position_swap_type: new CairoCustomEnum({ NoSwap: {} }), + is_long: 0, + referral_code: 0 + }; + const createOrderCall = orderHandlerContract.populate("create_order", [ + account0.address, + createOrderParams + ]) + const createOrderTx = await orderHandlerContract.create_order(createOrderCall.calldata) + await provider.waitForTransaction(createOrderTx.transaction_hash) + console.log("Order created.") +} + +create_market() \ No newline at end of file diff --git a/scripts/actions/executeDeposit.ts b/scripts/actions/executeDeposit.ts new file mode 100644 index 00000000..6c9bfe10 --- /dev/null +++ b/scripts/actions/executeDeposit.ts @@ -0,0 +1,79 @@ +import { Account, hash, Contract, json, Calldata, CallData, RpcProvider, shortString, ec } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function deploy() { + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const account0 = new Account(provider, account0Address!, privateKey0!) + // const marketToken = "0x122cd6989d2429f580a0bff5e70cdb84b2bff4f8d19cee6b30a15d08c447e85" + // const eth = "0x369c220f2a4699495bfe73ffe8a522f1bf1570c903c0d8fcf3767a252f7ae9a" + // const usdc = "0x6f82b80bfead3a249ee4352b27075dfa327de91e8e6df9755eb4f31de406d98" + console.log("Deploying with Account: " + account0Address) + console.log("RPC: " + providerUrl) + + const depositHandlerAddress = "0x7d82433606ef19a1f8a2d7e9be45c02677e214b83d2a079c930bc379ee246ef"; + // const dataStoreAddress = "0x12b79d662e668a585b978c8fa80c33c269297ee14eba2383829ef1890a6e201"; + const compiledDepositHandlerSierra = json.parse(fs.readFileSync("./target/dev/satoru_DepositHandler.contract_class.json").toString( "ascii")) + + // const compiledDataStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_DataStore.contract_class.json").toString( "ascii")) + // const dataStoreContract = new Contract(compiledDataStoreSierra.abi, dataStoreAddress, provider) + // dataStoreContract.connect(account0); + + // dataStoreContract.connect(account0); + // const dataCall5 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_pool_amount_key(marketToken, eth), + // 50000000000000000000000000000n + // ] + // ) + // const setAddressTx5 = await dataStoreContract.set_u256(dataCall5.calldata) + // await provider.waitForTransaction(setAddressTx5.transaction_hash) + + // const dataCall6 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_pool_amount_key(marketToken, usdc), + // 50000000000000000000000000000n + // ] + // ) + // const setAddressTx6 = await dataStoreContract.set_u256(dataCall6.calldata) + // await provider.waitForTransaction(setAddressTx6.transaction_hash) + + const depositHandlerContract = new Contract(compiledDepositHandlerSierra.abi, depositHandlerAddress, provider); + + const setPricesParams = { + signer_info: 1, + tokens: ["0x369c220f2a4699495bfe73ffe8a522f1bf1570c903c0d8fcf3767a252f7ae9a", "0x6f82b80bfead3a249ee4352b27075dfa327de91e8e6df9755eb4f31de406d98"], + compacted_min_oracle_block_numbers: [8189, 8189], + compacted_max_oracle_block_numbers: [81189, 81189], + compacted_oracle_timestamps: [171119803, 10], + compacted_decimals: [1, 1], + compacted_min_prices: [2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: [0], + compacted_max_prices: [3060, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: [0], + signatures: [ + ['signatures1', 'signatures2'], ['signatures1', 'signatures2'] + ], + price_feed_tokens: [] + }; + + depositHandlerContract.connect(account0) + let key = "0x4d65a6c15f989ebcccc12f7ad07d69e0d2e3caede2bd40de1f2eb5898c50c17"; + const executeOrderCall = depositHandlerContract.populate("execute_deposit", [ + key, + setPricesParams + ]) + let tx = await depositHandlerContract.execute_deposit(executeOrderCall.calldata) + +} + +deploy() \ No newline at end of file diff --git a/scripts/actions/executeLongOrder.ts b/scripts/actions/executeLongOrder.ts new file mode 100644 index 00000000..8e049b23 --- /dev/null +++ b/scripts/actions/executeLongOrder.ts @@ -0,0 +1,83 @@ +import { Account, Contract, json, Calldata, CallData, RpcProvider, shortString, uint256, CairoCustomEnum, ec } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function create_market() { + + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const marketTokenAddress = "0x69cfad927e7e4ef53261ad9a4630631ff8404746720ce3c73368de8291c4c4d" + const eth: string = "0x376bbceb1a044263cba28211fdcaee4e234ebf0c012521e1b258684bbc44949" + const usdc: string = "0x42a9a03ceb10ca07d3f598a627c414fe218b1138a78e3da6ce1675680cf95f2" + + const account0 = new Account(provider, account0Address!, privateKey0!) + console.log("Interacting with Account: " + account0Address) + + const compiledOrderHandlerSierra = json.parse(fs.readFileSync( "./target/dev/satoru_OrderHandler.contract_class.json").toString( "ascii")) + + const orderHandlerContract = new Contract(compiledOrderHandlerSierra.abi, process.env.ORDER_HANDLER as string, provider); + + + // const compiledRoleStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_RoleStore.contract_class.json").toString( "ascii")) + // const roleStoreContract = new Contract(compiledRoleStoreSierra.abi, process.env.ROLE_STORE as string, provider) + // roleStoreContract.connect(account0); + + // console.log("Granting roles...") + // const roleCall2 = roleStoreContract.populate("grant_role", [account0Address as string, shortString.encodeShortString("ORDER_KEEPER")]) + // const grant_role_tx2 = await roleStoreContract.grant_role(roleCall2.calldata) + // await provider.waitForTransaction(grant_role_tx2.transaction_hash) + // const roleCall3 = roleStoreContract.populate("grant_role", [process.env.INCREASE_ORDER_UTILS as string, shortString.encodeShortString("CONTROLLER")]) + // const grant_role_tx3 = await roleStoreContract.grant_role(roleCall3.calldata) + // await provider.waitForTransaction(grant_role_tx3.transaction_hash) + + // console.log("Roles granted.") + + const compiledDataStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_DataStore.contract_class.json").toString( "ascii")) + const dataStoreContract = new Contract(compiledDataStoreSierra.abi, process.env.DATA_STORE as string, provider) + dataStoreContract.connect(account0) + const dataCall8 = dataStoreContract.populate( + "remove_position", + [ + "0x5985ad845114a848d9cffdf9124a029e1d3fe1e704ed8230e42872f80f88cd1", + "0x4eaaccd6d2a2d9d1c0404cd2fea8485d62b437415948309736fdfd2542aee3" + ] + ) + const setAddressTx8 = await dataStoreContract.remove_position(dataCall8.calldata) + await provider.waitForTransaction(setAddressTx8.transaction_hash) + + + + // orderHandlerContract.connect(account0) + // const setPricesParams = { + // signer_info: 1, + // tokens: ["0x369c220f2a4699495bfe73ffe8a522f1bf1570c903c0d8fcf3767a252f7ae9a", "0x6f82b80bfead3a249ee4352b27075dfa327de91e8e6df9755eb4f31de406d98"], + // compacted_min_oracle_block_numbers: [63970, 63970], + // compacted_max_oracle_block_numbers: [64901, 64901], + // compacted_oracle_timestamps: [171119803, 10], + // compacted_decimals: [1, 1], + // compacted_min_prices: [2147483648010000], // 500000, 10000 compacted + // compacted_min_prices_indexes: [0], + // compacted_max_prices: [3389, 1], // 500000, 10000 compacted + // compacted_max_prices_indexes: [0], + // signatures: [ + // ['signatures1', 'signatures2'], ['signatures1', 'signatures2'] + // ], + // price_feed_tokens: [] + // }; + + // orderHandlerContract.connect(account0) + // let key = "0x1ecd2ae448fe9c2d0b632699a4c89f250f765d08dbba45a1a79c97ebd4dd155"; + // const executeOrderCall = orderHandlerContract.populate("execute_order", [ + // key, + // setPricesParams, + // ]) + // let tx = await orderHandlerContract.execute_order(executeOrderCall.calldata) +} + +create_market() \ No newline at end of file diff --git a/scripts/actions/executeSwapOrder.ts b/scripts/actions/executeSwapOrder.ts new file mode 100644 index 00000000..70caf1fd --- /dev/null +++ b/scripts/actions/executeSwapOrder.ts @@ -0,0 +1,70 @@ +import { Account, Contract, json, Calldata, CallData, RpcProvider, shortString, uint256, CairoCustomEnum, ec } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function create_market() { + + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const marketTokenAddress = "0x4b3bd2fe7f3dd02a6a143a3040ede80048388e0cf1c20dc748d6a6d6fa93069" + const eth: string = "0x75acffcc1c3661fe1cfbb6d2c444355ef01e85a40e65962a4d9a2ac38903934" + const usdc: string = "0x70d22d4962de09d9ec0a590e9ff33a496425277235890575457f9582d837964" + + const account0 = new Account(provider, account0Address!, privateKey0!) + console.log("Interacting with Account: " + account0Address) + + const compiledOrderHandlerSierra = json.parse(fs.readFileSync( "./target/dev/satoru_OrderHandler.contract_class.json").toString( "ascii")) + + const orderHandlerContract = new Contract(compiledOrderHandlerSierra.abi, process.env.ORDER_HANDLER as string, provider); + + + const compiledRoleStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_RoleStore.contract_class.json").toString( "ascii")) + const roleStoreContract = new Contract(compiledRoleStoreSierra.abi, process.env.ROLE_STORE as string, provider) + roleStoreContract.connect(account0); + + console.log("Granting roles...") + const roleCall2 = roleStoreContract.populate("grant_role", [account0Address as string, shortString.encodeShortString("ORDER_KEEPER")]) + const grant_role_tx2 = await roleStoreContract.grant_role(roleCall2.calldata) + await provider.waitForTransaction(grant_role_tx2.transaction_hash) + const roleCall3 = roleStoreContract.populate("grant_role", ["0x04b79329e9b295a50a27533d52484e0e3eb36a7f3303274745b6fe0e5dce7cc3" as string, shortString.encodeShortString("CONTROLLER")]) + const grant_role_tx3 = await roleStoreContract.grant_role(roleCall3.calldata) + await provider.waitForTransaction(grant_role_tx3.transaction_hash) + + console.log("Roles granted.") + + + orderHandlerContract.connect(account0) + const setPricesParams = { + signer_info: 1, + tokens: ["0x4b76dd1e0a8d0bc196aa75d7a85a6cc81cf7bc8e0cd2e5061237477eb2c109a", "0x6b6f734dca33adeb315c1ff399886b577bc3f2b51165af9277ca0096847d267"], + compacted_min_oracle_block_numbers: [63970, 63970], + compacted_max_oracle_block_numbers: [64901, 64901], + compacted_oracle_timestamps: [171119803, 10], + compacted_decimals: [1, 1], + compacted_min_prices: [2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: [0], + compacted_max_prices: [2147483648010000], // 500000, 10000 compacted + compacted_max_prices_indexes: [0], + signatures: [ + ['signatures1', 'signatures2'], ['signatures1', 'signatures2'] + ], + price_feed_tokens: [] + }; + + orderHandlerContract.connect(account0) + let key = "0x5dabb2c7c283c2b4759e3e8e38131a9f825decf26bd73a2e720c02222fa3c2f"; + const executeOrderCall = orderHandlerContract.populate("execute_order_keeper", [ + key, + setPricesParams, + account0Address + ]) + let tx = await orderHandlerContract.execute_order_keeper(executeOrderCall.calldata) +} + +create_market() \ No newline at end of file diff --git a/scripts/actions/openAndCloseLong.ts b/scripts/actions/openAndCloseLong.ts new file mode 100644 index 00000000..5a8d0b17 --- /dev/null +++ b/scripts/actions/openAndCloseLong.ts @@ -0,0 +1,70 @@ +import { Account, Contract, json, Calldata, CallData, RpcProvider, shortString, uint256, CairoCustomEnum, ec } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function create_market() { + + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const marketTokenAddress = "0x69cfad927e7e4ef53261ad9a4630631ff8404746720ce3c73368de8291c4c4d" + const eth: string = "0x376bbceb1a044263cba28211fdcaee4e234ebf0c012521e1b258684bbc44949" + const usdc: string = "0x42a9a03ceb10ca07d3f598a627c414fe218b1138a78e3da6ce1675680cf95f2" + + const account0 = new Account(provider, account0Address!, privateKey0!) + console.log("Interacting with Account: " + account0Address) + + const compiledOrderHandlerSierra = json.parse(fs.readFileSync( "./target/dev/satoru_OrderHandler.contract_class.json").toString( "ascii")) + + const orderHandlerContract = new Contract(compiledOrderHandlerSierra.abi, process.env.ORDER_HANDLER as string, provider); + const compiledERC20Sierra = json.parse(fs.readFileSync( "./target/dev/satoru_ERC20.contract_class.json").toString( "ascii")) + + const ethContract = new Contract(compiledERC20Sierra.abi, eth as string, provider) + ethContract.connect(account0) + const transferCall = ethContract.populate("transfer", [process.env.ORDER_VAULT as string, uint256.bnToUint256(1000000000000000000n)]) + const transferTx = await ethContract.transfer(transferCall.calldata) + await provider.waitForTransaction(transferTx.transaction_hash) + + const compiledRoleStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_RoleStore.contract_class.json").toString( "ascii")) + const roleStoreContract = new Contract(compiledRoleStoreSierra.abi, process.env.ROLE_STORE as string, provider) + roleStoreContract.connect(account0); + + const roleCall4 = roleStoreContract.populate("grant_role", [process.env.ORDER_UTILS as string, shortString.encodeShortString("CONTROLLER")]) + const grant_role_tx4 = await roleStoreContract.grant_role(roleCall4.calldata) + await provider.waitForTransaction(grant_role_tx4.transaction_hash) + + orderHandlerContract.connect(account0) + const createOrderParams = { + receiver: account0.address, + callback_contract: 0, + ui_fee_receiver: 0, + market: marketTokenAddress, + initial_collateral_token: eth, + swap_path: [], + size_delta_usd: uint256.bnToUint256(10000000000000000000000n), + initial_collateral_delta_amount: uint256.bnToUint256(2000000000000000000n), + trigger_price: uint256.bnToUint256(5000), + acceptable_price: uint256.bnToUint256(5500), + execution_fee: uint256.bnToUint256(0), + callback_gas_limit: uint256.bnToUint256(0), + min_output_amount: uint256.bnToUint256(0), + order_type: new CairoCustomEnum({ MarketIncrease: {} }), + decrease_position_swap_type: new CairoCustomEnum({ NoSwap: {} }), + is_long: 1, + referral_code: 0 + }; + const createOrderCall = orderHandlerContract.populate("create_order", [ + account0.address, + createOrderParams + ]) + const createOrderTx = await orderHandlerContract.create_order(createOrderCall.calldata) + await provider.waitForTransaction(createOrderTx.transaction_hash) + console.log("Order created.") +} + +create_market() \ No newline at end of file diff --git a/scripts/app/deployApp.ts b/scripts/app/deployApp.ts new file mode 100644 index 00000000..dc360cc5 --- /dev/null +++ b/scripts/app/deployApp.ts @@ -0,0 +1,409 @@ +import { Account, hash, Contract, json, Calldata, CallData, RpcProvider, shortString } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function deploy() { + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const account0 = new Account(provider, account0Address!, privateKey0!) + console.log("Deploying with Account: " + account0Address) + const resp = await provider.getSpecVersion(); + console.log('rpc version =', resp); + const compiledRoleStoreCasm = json.parse(fs.readFileSync( "./target/dev/satoru_RoleStore.compiled_contract_class.json").toString( "ascii")) + const compiledRoleStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_RoleStore.contract_class.json").toString( "ascii")) + const roleStoreCallData: CallData = new CallData(compiledRoleStoreSierra.abi) + const roleStoreConstructor: Calldata = roleStoreCallData.compile("constructor", { admin: account0.address }) + const deployRoleStoreResponse = await account0.declareAndDeploy({ + contract: compiledRoleStoreSierra, + casm: compiledRoleStoreCasm, + constructorCalldata: roleStoreConstructor + }) + console.log("RoleStore Deployed: " + deployRoleStoreResponse.deploy.contract_address) + + const compiledDataStoreCasm = json.parse(fs.readFileSync( "./target/dev/satoru_DataStore.compiled_contract_class.json").toString( "ascii")) + const compiledDataStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_DataStore.contract_class.json").toString( "ascii")) + const dataStoreCallData: CallData = new CallData(compiledDataStoreSierra.abi) + const dataStoreConstructor: Calldata = dataStoreCallData.compile("constructor", { + role_store_address: deployRoleStoreResponse.deploy.contract_address + }) + const deployDataStoreResponse = await account0.declareAndDeploy({ + contract: compiledDataStoreSierra, + casm: compiledDataStoreCasm , + constructorCalldata: dataStoreConstructor, + }) + console.log("DataStore Deployed: " + deployDataStoreResponse.deploy.contract_address) + + const roleStoreContract = new Contract(compiledRoleStoreSierra.abi, deployRoleStoreResponse.deploy.contract_address, provider) + roleStoreContract.connect(account0); + const roleCall = roleStoreContract.populate("grant_role", [account0.address, shortString.encodeShortString("CONTROLLER")]) + const grant_role_tx = await roleStoreContract.grant_role(roleCall.calldata) + await provider.waitForTransaction(grant_role_tx.transaction_hash) + console.log("Controller role granted.") + + console.log("Deploying EventEmitter...") + const compiledEventEmitterCasm = json.parse(fs.readFileSync( "./target/dev/satoru_EventEmitter.compiled_contract_class.json").toString( "ascii")) + const compiledEventEmitterSierra = json.parse(fs.readFileSync( "./target/dev/satoru_EventEmitter.contract_class.json").toString( "ascii")) + const eventEmitterCallData: CallData = new CallData(compiledEventEmitterSierra.abi) + const eventEmitterConstructor: Calldata = eventEmitterCallData.compile("constructor", {}) + const deployEventEmitterResponse = await account0.declareAndDeploy({ + contract: compiledEventEmitterSierra, + casm: compiledEventEmitterCasm , + constructorCalldata: eventEmitterConstructor, + }) + console.log("EventEmitter Deployed: " + deployEventEmitterResponse.deploy.contract_address) + + const compiledOracleStoreCasm = json.parse(fs.readFileSync( "./target/dev/satoru_OracleStore.compiled_contract_class.json").toString( "ascii")) + const compiledOracleStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_OracleStore.contract_class.json").toString( "ascii")) + const oracleStoreCallData: CallData = new CallData(compiledOracleStoreSierra.abi) + const oracleStoreConstructor: Calldata = oracleStoreCallData.compile("constructor", { + role_store_address: deployRoleStoreResponse.deploy.contract_address, + event_emitter_address: deployEventEmitterResponse.deploy.contract_address + }) + const deployOracleStoreResponse = await account0.declareAndDeploy({ + contract: compiledOracleStoreSierra, + casm: compiledOracleStoreCasm , + constructorCalldata: oracleStoreConstructor, + }) + console.log("OracleStore Deployed: " + deployOracleStoreResponse.deploy.contract_address) + + const compiledOracleCasm = json.parse(fs.readFileSync( "./target/dev/satoru_Oracle.compiled_contract_class.json").toString( "ascii")) + const compiledOracleSierra = json.parse(fs.readFileSync( "./target/dev/satoru_Oracle.contract_class.json").toString( "ascii")) + const oracleCallData: CallData = new CallData(compiledOracleSierra.abi) + const oracleConstructor: Calldata = oracleCallData.compile("constructor", { + role_store_address: deployRoleStoreResponse.deploy.contract_address, + oracle_store_address: deployOracleStoreResponse.deploy.contract_address, + pragma_address: account0.address + }) + const deployOracleResponse = await account0.declareAndDeploy({ + contract: compiledOracleSierra, + casm: compiledOracleCasm , + constructorCalldata: oracleConstructor, + }) + console.log("Oracle Deployed: " + deployOracleResponse.deploy.contract_address) + + const compiledOrderVaultCasm = json.parse(fs.readFileSync( "./target/dev/satoru_OrderVault.compiled_contract_class.json").toString( "ascii")) + const compiledOrderVaultSierra = json.parse(fs.readFileSync( "./target/dev/satoru_OrderVault.contract_class.json").toString( "ascii")) + const orderVaultCallData: CallData = new CallData(compiledOrderVaultSierra.abi) + const orderVaultConstructor: Calldata = orderVaultCallData.compile("constructor", { + data_store_address: deployDataStoreResponse.deploy.contract_address, + role_store_address: deployRoleStoreResponse.deploy.contract_address, + }) + const deployOrderVaultResponse = await account0.declareAndDeploy({ + contract: compiledOrderVaultSierra, + casm: compiledOrderVaultCasm , + constructorCalldata: orderVaultConstructor, + }) + console.log("OrderVault Deployed: " + deployOrderVaultResponse.deploy.contract_address) + + const compiledSwapHandlerCasm = json.parse(fs.readFileSync( "./target/dev/satoru_SwapHandler.compiled_contract_class.json").toString( "ascii")) + const compiledSwapHandlerSierra = json.parse(fs.readFileSync( "./target/dev/satoru_SwapHandler.contract_class.json").toString( "ascii")) + const swapHandlerCallData: CallData = new CallData(compiledSwapHandlerSierra.abi) + const swapHandlerConstructor: Calldata = swapHandlerCallData.compile("constructor", { + role_store_address: deployRoleStoreResponse.deploy.contract_address, + }) + const deploySwapHandlerResponse = await account0.declareAndDeploy({ + contract: compiledSwapHandlerSierra, + casm: compiledSwapHandlerCasm , + constructorCalldata: swapHandlerConstructor, + }) + console.log("SwapHandler Deployed: " + deploySwapHandlerResponse.deploy.contract_address) + + const compiledReferralStorageCasm = json.parse(fs.readFileSync( "./target/dev/satoru_ReferralStorage.compiled_contract_class.json").toString( "ascii")) + const compiledReferralStorageSierra = json.parse(fs.readFileSync( "./target/dev/satoru_ReferralStorage.contract_class.json").toString( "ascii")) + const referralStorageCallData: CallData = new CallData(compiledReferralStorageSierra.abi) + const referralStorageConstructor: Calldata = referralStorageCallData.compile("constructor", { + event_emitter_address: deployEventEmitterResponse.deploy.contract_address, + }) + const deployReferralStorageResponse = await account0.declareAndDeploy({ + contract: compiledReferralStorageSierra, + casm: compiledReferralStorageCasm , + constructorCalldata: referralStorageConstructor, + }) + console.log("ReferralStorage Deployed: " + deployReferralStorageResponse.deploy.contract_address) + + const compiledIncreaseOrderUtilsCasm = json.parse(fs.readFileSync( "./target/dev/satoru_IncreaseOrderUtils.compiled_contract_class.json").toString( "ascii")) + const compiledIncreaseOrderUtilsSierra = json.parse(fs.readFileSync( "./target/dev/satoru_IncreaseOrderUtils.contract_class.json").toString( "ascii")) + const increaseOrderUtilsCallData: CallData = new CallData(compiledIncreaseOrderUtilsSierra.abi) + const increaseOrderUtilsConstructor: Calldata = increaseOrderUtilsCallData.compile("constructor", {}) + const deployIncreaseOrderUtilsResponse = await account0.declareAndDeploy({ + contract: compiledIncreaseOrderUtilsSierra, + casm: compiledIncreaseOrderUtilsCasm, + }) + console.log("IncreaseOrderUtils Deployed: " + deployIncreaseOrderUtilsResponse.deploy.contract_address) + + + const compiledDecreaseOrderUtilsCasm = json.parse(fs.readFileSync( "./target/dev/satoru_DecreaseOrderUtils.compiled_contract_class.json").toString( "ascii")) + const compiledDecreaseOrderUtilsSierra = json.parse(fs.readFileSync( "./target/dev/satoru_DecreaseOrderUtils.contract_class.json").toString( "ascii")) + const decreaseOrderUtilsCallData: CallData = new CallData(compiledDecreaseOrderUtilsSierra.abi) + const decreaseOrderUtilsConstructor: Calldata = decreaseOrderUtilsCallData.compile("constructor", {}) + const deployDecreaseOrderUtilsResponse = await account0.declareAndDeploy({ + contract: compiledDecreaseOrderUtilsSierra, + casm: compiledDecreaseOrderUtilsCasm, + }) + console.log("DecreaseOrderUtils Deployed: " + deployDecreaseOrderUtilsResponse.deploy.contract_address) + + const compiledSwapOrderUtilsCasm = json.parse(fs.readFileSync( "./target/dev/satoru_SwapOrderUtils.compiled_contract_class.json").toString( "ascii")) + const compiledSwapOrderUtilsSierra = json.parse(fs.readFileSync( "./target/dev/satoru_SwapOrderUtils.contract_class.json").toString( "ascii")) + const swapOrderUtilsCallData: CallData = new CallData(compiledSwapOrderUtilsSierra.abi) + const swapOrderUtilsConstructor: Calldata = swapOrderUtilsCallData.compile("constructor", {}) + const deploySwapOrderUtilsResponse = await account0.declareAndDeploy({ + contract: compiledSwapOrderUtilsSierra, + casm: compiledSwapOrderUtilsCasm, + }) + console.log("SwapOrderUtils Deployed: " + deploySwapOrderUtilsResponse.deploy.contract_address) + + const compiledOrderUtilsCasm = json.parse(fs.readFileSync( "./target/dev/satoru_OrderUtils.compiled_contract_class.json").toString( "ascii")) + const compiledOrderUtilsSierra = json.parse(fs.readFileSync( "./target/dev/satoru_OrderUtils.contract_class.json").toString( "ascii")) + const orderUtilsCallData: CallData = new CallData(compiledOrderUtilsSierra.abi) + const orderUtilsConstructor: Calldata = orderUtilsCallData.compile("constructor", { + increase_order_class_hash: deployIncreaseOrderUtilsResponse.deploy.classHash, + decrease_order_class_hash: deployDecreaseOrderUtilsResponse.deploy.classHash, + swap_order_class_hash: deploySwapOrderUtilsResponse.deploy.classHash + }) + const deployOrderUtilsResponse = await account0.declareAndDeploy({ + contract: compiledOrderUtilsSierra, + casm: compiledOrderUtilsCasm, + constructorCalldata: orderUtilsConstructor + }) + console.log("OrderUtils Deployed: " + deployOrderUtilsResponse.deploy.contract_address) + + const compiledOrderHandlerCasm = json.parse(fs.readFileSync( "./target/dev/satoru_OrderHandler.compiled_contract_class.json").toString( "ascii")) + const compiledOrderHandlerSierra = json.parse(fs.readFileSync( "./target/dev/satoru_OrderHandler.contract_class.json").toString( "ascii")) + const orderHandlerCallData: CallData = new CallData(compiledOrderHandlerSierra.abi) + const orderHandlerConstructor: Calldata = orderHandlerCallData.compile("constructor", { + data_store_address: deployDataStoreResponse.deploy.contract_address, + role_store_address: deployRoleStoreResponse.deploy.contract_address, + event_emitter_address: deployEventEmitterResponse.deploy.contract_address, + order_vault_address: deployOrderVaultResponse.deploy.contract_address, + oracle_address: deployOracleResponse.deploy.contract_address, + swap_handler_address: deploySwapHandlerResponse.deploy.contract_address, + referral_storage_address: deployReferralStorageResponse.deploy.contract_address, + order_utils_class_hash: deployOrderUtilsResponse.deploy.classHash, + increase_order_utils_class_hash: deployIncreaseOrderUtilsResponse.deploy.classHash, + decrease_order_utils_class_hash: deployDecreaseOrderUtilsResponse.deploy.classHash, + swap_order_utils_class_hash: deploySwapOrderUtilsResponse.deploy.classHash, + }) + const deployOrderHandlerResponse = await account0.declareAndDeploy({ + contract: compiledOrderHandlerSierra, + casm: compiledOrderHandlerCasm , + constructorCalldata: orderHandlerConstructor, + }) + console.log("OrderHandler Deployed: " + deployOrderHandlerResponse.deploy.contract_address) + + const compiledDepositVaultCasm = json.parse(fs.readFileSync( "./target/dev/satoru_DepositVault.compiled_contract_class.json").toString( "ascii")) + const compiledDepositVaultSierra = json.parse(fs.readFileSync( "./target/dev/satoru_DepositVault.contract_class.json").toString( "ascii")) + const depositVaultCallData: CallData = new CallData(compiledDepositVaultSierra.abi) + const depositVaultConstructor: Calldata = depositVaultCallData.compile("constructor", { + data_store_address: deployDataStoreResponse.deploy.contract_address, + role_store_address: deployRoleStoreResponse.deploy.contract_address, + }) + const deployDepositVaultResponse = await account0.declareAndDeploy({ + contract: compiledDepositVaultSierra, + casm: compiledDepositVaultCasm , + constructorCalldata: depositVaultConstructor, + }) + console.log("DepositVault Deployed: " + deployDepositVaultResponse.deploy.contract_address) + + const compiledDepositHandlerCasm = json.parse(fs.readFileSync( "./target/dev/satoru_DepositHandler.compiled_contract_class.json").toString( "ascii")) + const compiledDepositHandlerSierra = json.parse(fs.readFileSync( "./target/dev/satoru_DepositHandler.contract_class.json").toString( "ascii")) + const depositHandlerCallData: CallData = new CallData(compiledDepositHandlerSierra.abi) + const depositHandlerConstructor: Calldata = depositHandlerCallData.compile("constructor", { + data_store_address: deployDataStoreResponse.deploy.contract_address, + role_store_address: deployRoleStoreResponse.deploy.contract_address, + event_emitter_address: deployEventEmitterResponse.deploy.contract_address, + deposit_vault_address: deployDepositVaultResponse.deploy.contract_address, + oracle_address: deployOracleResponse.deploy.contract_address, + }) + const deployDepositHandlerResponse = await account0.declareAndDeploy({ + contract: compiledDepositHandlerSierra, + casm: compiledDepositHandlerCasm , + constructorCalldata: depositHandlerConstructor, + }) + console.log("DepositHandler Deployed: " + deployDepositHandlerResponse.deploy.contract_address) + + const compiledWithdrawalVaultCasm = json.parse(fs.readFileSync( "./target/dev/satoru_WithdrawalVault.compiled_contract_class.json").toString( "ascii")) + const compiledWithdrawalVaultSierra = json.parse(fs.readFileSync( "./target/dev/satoru_WithdrawalVault.contract_class.json").toString( "ascii")) + const withdrawalVaultCallData: CallData = new CallData(compiledWithdrawalVaultSierra.abi) + const withdrawalVaultConstructor: Calldata = withdrawalVaultCallData.compile("constructor", { + data_store_address: deployDataStoreResponse.deploy.contract_address, + role_store_address: deployRoleStoreResponse.deploy.contract_address, + }) + const deployWithdrawalVaultResponse = await account0.declareAndDeploy({ + contract: compiledWithdrawalVaultSierra, + casm: compiledWithdrawalVaultCasm , + constructorCalldata: withdrawalVaultConstructor, + }) + console.log("WithdrawalVault Deployed: " + deployWithdrawalVaultResponse.deploy.contract_address) + + const compiledWithdrawalHandlerCasm = json.parse(fs.readFileSync( "./target/dev/satoru_WithdrawalHandler.compiled_contract_class.json").toString( "ascii")) + const compiledWithdrawalHandlerSierra = json.parse(fs.readFileSync( "./target/dev/satoru_WithdrawalHandler.contract_class.json").toString( "ascii")) + const withdrawalHandlerCallData: CallData = new CallData(compiledWithdrawalHandlerSierra.abi) + const withdrawalHandlerConstructor: Calldata = withdrawalHandlerCallData.compile("constructor", { + data_store_address: deployDataStoreResponse.deploy.contract_address, + role_store_address: deployRoleStoreResponse.deploy.contract_address, + event_emitter_address: deployEventEmitterResponse.deploy.contract_address, + withdrawal_vault_address: deployWithdrawalVaultResponse.deploy.contract_address, + oracle_address: deployOracleResponse.deploy.contract_address, + }) + const deployWithdrawalHandlerResponse = await account0.declareAndDeploy({ + contract: compiledWithdrawalHandlerSierra, + casm: compiledWithdrawalHandlerCasm , + constructorCalldata: withdrawalHandlerConstructor, + }) + console.log("WithdrawalHandler Deployed: " + deployWithdrawalHandlerResponse.deploy.contract_address) + + const compiledMarketTokenCasm = json.parse(fs.readFileSync( "./target/dev/satoru_MarketToken.compiled_contract_class.json").toString( "ascii")) + const compiledMarketTokenSierra = json.parse(fs.readFileSync( "./target/dev/satoru_MarketToken.contract_class.json").toString( "ascii")) + try { + await account0.declare({ + contract: compiledMarketTokenSierra, + casm: compiledMarketTokenCasm + }) + console.log("MarketToken Declared.") + } catch (error) { + console.log("Already Declared.") + } + + // const marketTokenClassHash = hash.computeCompiledClassHash(compiledMarketTokenCasm) + const compiledMarketFactoryCasm = json.parse(fs.readFileSync( "./target/dev/satoru_MarketFactory.compiled_contract_class.json").toString( "ascii")) + const compiledMarketFactorySierra = json.parse(fs.readFileSync( "./target/dev/satoru_MarketFactory.contract_class.json").toString( "ascii")) + const marketFactoryCallData: CallData = new CallData(compiledMarketFactorySierra.abi) + const marketFactoryConstructor: Calldata = marketFactoryCallData.compile("constructor", { + data_store_address: deployDataStoreResponse.deploy.contract_address, + role_store_address: deployRoleStoreResponse.deploy.contract_address, + event_emitter_address: deployEventEmitterResponse.deploy.contract_address, + market_token_class_hash: "0x0782830d85481434f237378795dc72d42a9295d11e5e8671bd3965dcd67a56ac" + }) + const deployMarketFactoryResponse = await account0.declareAndDeploy({ + contract: compiledMarketFactorySierra, + casm: compiledMarketFactoryCasm , + constructorCalldata: marketFactoryConstructor, + }) + console.log("MarketFactory Deployed: " + deployMarketFactoryResponse.deploy.contract_address) + + const compiledReaderCasm = json.parse(fs.readFileSync( "./target/dev/satoru_Reader.compiled_contract_class.json").toString( "ascii")) + const compiledReaderSierra = json.parse(fs.readFileSync( "./target/dev/satoru_Reader.contract_class.json").toString( "ascii")) + const readerCallData: CallData = new CallData(compiledReaderSierra.abi) + const readerConstructor: Calldata = readerCallData.compile("constructor", {}) + const deployReaderResponse = await account0.declareAndDeploy({ + contract: compiledReaderSierra, + casm: compiledReaderCasm , + constructorCalldata: readerConstructor, + }) + console.log("Reader Deployed: " + deployReaderResponse.deploy.contract_address) + + const compiledRouterCasm = json.parse(fs.readFileSync( "./target/dev/satoru_Router.compiled_contract_class.json").toString( "ascii")) + const compiledRouterSierra = json.parse(fs.readFileSync( "./target/dev/satoru_Router.contract_class.json").toString( "ascii")) + const routerCallData: CallData = new CallData(compiledRouterSierra.abi) + const routerConstructor: Calldata = routerCallData.compile("constructor", { + role_store_address: deployRoleStoreResponse.deploy.contract_address, + }) + const deployRouterResponse = await account0.declareAndDeploy({ + contract: compiledRouterSierra, + casm: compiledRouterCasm , + constructorCalldata: routerConstructor, + }) + console.log("Router Deployed: " + deployRouterResponse.deploy.contract_address) + + + const compiledExchangeRouterCasm = json.parse(fs.readFileSync( "./target/dev/satoru_ExchangeRouter.compiled_contract_class.json").toString( "ascii")) + const compiledExchangeRouterSierra = json.parse(fs.readFileSync( "./target/dev/satoru_ExchangeRouter.contract_class.json").toString( "ascii")) + const exchangeRouterCallData: CallData = new CallData(compiledExchangeRouterSierra.abi) + const exchangeRouterConstructor: Calldata = exchangeRouterCallData.compile("constructor", { + router_address: deployRouterResponse.deploy.contract_address, + data_store_address: deployDataStoreResponse.deploy.contract_address, + role_store_address: deployRoleStoreResponse.deploy.contract_address, + event_emitter_address: deployEventEmitterResponse.deploy.contract_address, + deposit_handler_address: deployDepositHandlerResponse.deploy.contract_address, + withdrawal_handler_address: deployWithdrawalHandlerResponse.deploy.contract_address, + order_handler_address: deployOrderHandlerResponse.deploy.contract_address + }) + const deployExchangeRouterResponse = await account0.declareAndDeploy({ + contract: compiledExchangeRouterSierra, + casm: compiledExchangeRouterCasm , + constructorCalldata: exchangeRouterConstructor, + }) + console.log("ExchangeRouter Deployed: " + deployExchangeRouterResponse.deploy.contract_address) + + + const roleCall2 = roleStoreContract.populate("grant_role", [account0.address, shortString.encodeShortString("MARKET_KEEPER")]) + const roleCall3 = roleStoreContract.populate("grant_role", [account0.address, shortString.encodeShortString("ORDER_KEEPER")]) + const roleCall4 = roleStoreContract.populate("grant_role", + [ + deployOrderHandlerResponse.deploy.contract_address, + shortString.encodeShortString("CONTROLLER") + ] + ) + const roleCall5 = roleStoreContract.populate("grant_role", + [ + deployIncreaseOrderUtilsResponse.deploy.contract_address, + shortString.encodeShortString("CONTROLLER") + ] + ) + const roleCall6 = roleStoreContract.populate("grant_role", + [ + deployDecreaseOrderUtilsResponse.deploy.contract_address, + shortString.encodeShortString("CONTROLLER") + ] + ) + const roleCall7 = roleStoreContract.populate("grant_role", + [ + deploySwapOrderUtilsResponse.deploy.contract_address, + shortString.encodeShortString("CONTROLLER") + ] + ) + const roleCall8 = roleStoreContract.populate("grant_role", + [ + deployDepositHandlerResponse.deploy.contract_address, + shortString.encodeShortString("CONTROLLER") + ] + ) + const roleCall9 = roleStoreContract.populate("grant_role", + [ + deployWithdrawalHandlerResponse.deploy.contract_address, + shortString.encodeShortString("CONTROLLER") + ] + ) + const roleCall10 = roleStoreContract.populate("grant_role", + [ + deploySwapHandlerResponse.deploy.contract_address, + shortString.encodeShortString("CONTROLLER") + ] + ) + const roleCall11 = roleStoreContract.populate("grant_role", + [ + deployExchangeRouterResponse.deploy.contract_address, + shortString.encodeShortString("CONTROLLER") + ] + ) + const grant_role_tx2 = await roleStoreContract.grant_role(roleCall2.calldata) + await provider.waitForTransaction(grant_role_tx2.transaction_hash) + const grant_role_tx3 = await roleStoreContract.grant_role(roleCall3.calldata) + await provider.waitForTransaction(grant_role_tx3.transaction_hash) + const grant_role_tx4 = await roleStoreContract.grant_role(roleCall4.calldata) + await provider.waitForTransaction(grant_role_tx4.transaction_hash) + const grant_role_tx5 = await roleStoreContract.grant_role(roleCall5.calldata) + await provider.waitForTransaction(grant_role_tx5.transaction_hash) + const grant_role_tx6 = await roleStoreContract.grant_role(roleCall6.calldata) + await provider.waitForTransaction(grant_role_tx6.transaction_hash) + const grant_role_tx7 = await roleStoreContract.grant_role(roleCall7.calldata) + await provider.waitForTransaction(grant_role_tx7.transaction_hash) + const grant_role_tx8 = await roleStoreContract.grant_role(roleCall8.calldata) + await provider.waitForTransaction(grant_role_tx8.transaction_hash) + const grant_role_tx9 = await roleStoreContract.grant_role(roleCall9.calldata) + await provider.waitForTransaction(grant_role_tx9.transaction_hash) + const grant_role_tx10 = await roleStoreContract.grant_role(roleCall10.calldata) + await provider.waitForTransaction(grant_role_tx10.transaction_hash) + const grant_role_tx11 = await roleStoreContract.grant_role(roleCall11.calldata) + await provider.waitForTransaction(grant_role_tx11.transaction_hash) + + console.log("Roles granted.") +} + +deploy() \ No newline at end of file diff --git a/scripts/app/test2.ts b/scripts/app/test2.ts new file mode 100644 index 00000000..d33d6eef --- /dev/null +++ b/scripts/app/test2.ts @@ -0,0 +1,74 @@ +import { Account, hash, Contract, json, Calldata, CallData, RpcProvider, shortString, ec, uint256 } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function deploy() { + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const eth: string = "0x3fa46510b749925fb3fa02e98195909683eaee8d4c982cc647cd98a7f160905" + const usdc: string = "0x636d15cd4dfe130c744282f86496077e089cb9dc96ccc37bf0d85ea358a5760" + const account0 = new Account(provider, account0Address!, privateKey0!) + const marketTokenAddress = "0x68ad9440759f0bd0367e407d53b5e5c32203590f12d54ed8968f48fee0cf636" + console.log("Deploying with Account: " + account0Address) + console.log("RPC: " + providerUrl) + + const dataStoreAddress = process.env.DATA_STORE as string + const compiledDataStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_DataStore.contract_class.json").toString( "ascii")) + const dataStoreContract = new Contract(compiledDataStoreSierra.abi, dataStoreAddress, provider) + dataStoreContract.connect(account0); + + // console.log(await dataStoreContract.get_u256(await dataStoreContract.get_pool_amount_key(marketTokenAddress, usdc))) + // console.log(await dataStoreContract.get_u256(await dataStoreContract.get_pool_amount_key(marketTokenAddress, eth))) + // const hashUSDC = await dataStoreContract.get_max_pool_amount_key(marketTokenAddress, usdc) + // const dataCall4 = dataStoreContract.populate( + // "set_u256", + // [await dataStoreContract.get_pool_amount_key(marketTokenAddress, usdc), 25000000000000000000000000n] + // ) + // const setAddressTx4 = await dataStoreContract.set_u256(dataCall4.calldata) + // await provider.waitForTransaction(setAddressTx4.transaction_hash) + + // const dataCall8 = dataStoreContract.populate( + // "set_u256", + // [ + // await dataStoreContract.get_max_open_interest_key( + // marketTokenAddress, + // true + // ), + // 1000000000000000000000000000000000000000000000000000n + // ] + // ) + // const setAddressTx8 = await dataStoreContract.set_u256(dataCall8.calldata) + // await provider.waitForTransaction(setAddressTx8.transaction_hash) + + // const dataCall5 = dataStoreContract.populate( + // "set_u256", + // [await dataStoreContract.get_pool_amount_key(marketTokenAddress, eth), 50000000000000000001000000n] + // ) + // const setAddressTx5 = await dataStoreContract.set_u256(dataCall5.calldata) + // await provider.waitForTransaction(setAddressTx5.transaction_hash) + + const compiledRoleStoreSierra = json.parse(fs.readFileSync( "./target/dev/satoru_RoleStore.contract_class.json").toString( "ascii")) + const roleStoreContract = new Contract(compiledRoleStoreSierra.abi, process.env.ROLE_STORE as string, provider) + roleStoreContract.connect(account0); + + const roleCall4 = roleStoreContract.populate("grant_role", ["0x04db27f09ae33b3f2720f730e03206050a62ca48c6d651d2853024cd21270ed3" as string, shortString.encodeShortString("CONTROLLER")]) + const grant_role_tx4 = await roleStoreContract.grant_role(roleCall4.calldata) + await provider.waitForTransaction(grant_role_tx4.transaction_hash) + + // const compiledOracleSierra = json.parse(fs.readFileSync( "./target/dev/satoru_Oracle.contract_class.json").toString( "ascii")) + + // const oracleContract = new Contract(compiledOracleSierra.abi, process.env.ORACLE as string, provider); + // oracleContract.connect(account0); + // const setPrimaryPriceCall1 = oracleContract.populate("set_primary_price", [eth, uint256.bnToUint256(6000)]) + // const setPrimaryPriceTx1 = await oracleContract.set_primary_price(setPrimaryPriceCall1.calldata); + // await provider.waitForTransaction(setPrimaryPriceTx1.transaction_hash) + +} + +deploy() \ No newline at end of file diff --git a/scripts/app/testDeploy.ts b/scripts/app/testDeploy.ts new file mode 100644 index 00000000..97d02316 --- /dev/null +++ b/scripts/app/testDeploy.ts @@ -0,0 +1,35 @@ +import { Account, hash, Contract, json, Calldata, CallData, RpcProvider, shortString, ec } from "starknet" +import fs from 'fs' +import dotenv from 'dotenv' + +dotenv.config() + +async function deploy() { + // connect provider + const providerUrl = process.env.PROVIDER_URL + const provider = new RpcProvider({ nodeUrl: providerUrl! }) + // connect your account. To adapt to your own account : + const privateKey0: string = process.env.ACCOUNT_PRIVATE as string + const account0Address: string = process.env.ACCOUNT_PUBLIC as string + const account0 = new Account(provider, account0Address!, privateKey0!) + console.log("Deploying with Account: " + account0Address) + console.log("RPC: " + providerUrl) + + + const compiledReaderCasm = json.parse(fs.readFileSync( "./target/dev/satoru_ReferralStorage.compiled_contract_class.json").toString( "ascii")) + const compiledReferralStorageSierra = json.parse(fs.readFileSync("./target/dev/satoru_ReferralStorage.contract_class.json").toString( "ascii")) + const referralStorageCallData: CallData = new CallData(compiledReferralStorageSierra.abi) + const referralStorageConstructor: Calldata = referralStorageCallData.compile("constructor", { + event_emitter_address: process.env.EVENT_EMITTER as string + }) + const deployReferralStorageResponse = await account0.declareAndDeploy({ + contract: compiledReferralStorageSierra, + casm: compiledReaderCasm , + constructorCalldata: referralStorageConstructor, + }) + console.log("Reader Deployed: " + deployReferralStorageResponse.deploy.contract_address) + + +} + +deploy() \ No newline at end of file diff --git a/scripts/bank/deploy_StrictBank_contract.sh b/scripts/bank/deploy_StrictBank_contract.sh new file mode 100644 index 00000000..3057d467 --- /dev/null +++ b/scripts/bank/deploy_StrictBank_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for strict_bank.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_StrictBank.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/bank/deploy_bank_contract.sh b/scripts/bank/deploy_bank_contract.sh new file mode 100644 index 00000000..21796eca --- /dev/null +++ b/scripts/bank/deploy_bank_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for bank.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_Bank.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/config/deploy_config_contract.sh b/scripts/config/deploy_config_contract.sh new file mode 100644 index 00000000..e8bcf550 --- /dev/null +++ b/scripts/config/deploy_config_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for config.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_Config.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 $5 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/config/deploy_timelock_contract.sh b/scripts/config/deploy_timelock_contract.sh new file mode 100644 index 00000000..936275ce --- /dev/null +++ b/scripts/config/deploy_timelock_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for timelock.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_Timelock.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/data/deploy_data_store_contract.sh b/scripts/data/deploy_data_store_contract.sh new file mode 100755 index 00000000..b730278e --- /dev/null +++ b/scripts/data/deploy_data_store_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for data_store.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_DataStore.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/deploy_role_store_contract.sh b/scripts/deploy_role_store_contract.sh deleted file mode 100644 index d9567d00..00000000 --- a/scripts/deploy_role_store_contract.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -# Deployment script for role_store.cairo - -# Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_RoleStore.sierra.json) - -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') - -# Deploy the contract using the extracted class hash -starkli deploy $class_hash \ No newline at end of file diff --git a/scripts/deposit/deploy_deposit_vault_contract.sh b/scripts/deposit/deploy_deposit_vault_contract.sh new file mode 100755 index 00000000..55fb5b41 --- /dev/null +++ b/scripts/deposit/deploy_deposit_vault_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for deposit_vault.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_DepositVault.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/event/deploy_event_emitter_contract.sh b/scripts/event/deploy_event_emitter_contract.sh new file mode 100755 index 00000000..c63f6396 --- /dev/null +++ b/scripts/event/deploy_event_emitter_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for event_emitter.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_EventEmitter.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_adl_handler_contract.sh b/scripts/exchange/deploy_adl_handler_contract.sh new file mode 100644 index 00000000..8747b6d2 --- /dev/null +++ b/scripts/exchange/deploy_adl_handler_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for adl_handler.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_AdlHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 $5 $6 $7 $8 $9 $10 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_base_order_handler_contract.sh b/scripts/exchange/deploy_base_order_handler_contract.sh new file mode 100644 index 00000000..0d046e06 --- /dev/null +++ b/scripts/exchange/deploy_base_order_handler_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for base_order_handler.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_BaseOrderHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 $5 $6 $7 $8 $9 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_deposit_handler_contract.sh b/scripts/exchange/deploy_deposit_handler_contract.sh new file mode 100644 index 00000000..442b31bd --- /dev/null +++ b/scripts/exchange/deploy_deposit_handler_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for deposit_handler.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_DepositHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 $5 $6 $7 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_liquidation_handler_contract.sh b/scripts/exchange/deploy_liquidation_handler_contract.sh new file mode 100644 index 00000000..cff1a5fe --- /dev/null +++ b/scripts/exchange/deploy_liquidation_handler_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for liquidation_handler.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_LiquidationHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 $5 $6 $7 $8 $9 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_order_handler_contract.sh b/scripts/exchange/deploy_order_handler_contract.sh new file mode 100644 index 00000000..72c5ab5d --- /dev/null +++ b/scripts/exchange/deploy_order_handler_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for order_handler.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_OrderHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 $5 $6 $7 $8 $9 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_withdrawal_handler_contract.sh b/scripts/exchange/deploy_withdrawal_handler_contract.sh new file mode 100644 index 00000000..5e1a9327 --- /dev/null +++ b/scripts/exchange/deploy_withdrawal_handler_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for withdrawal_handler.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_WithdrawalHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 $5 $6 $7 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/fee/deploy_fee_handler_contract.sh b/scripts/fee/deploy_fee_handler_contract.sh new file mode 100755 index 00000000..58958bdd --- /dev/null +++ b/scripts/fee/deploy_fee_handler_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for fee_handler.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_FeeHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 $5 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/market/deploy_market_factory_contract.sh b/scripts/market/deploy_market_factory_contract.sh new file mode 100644 index 00000000..d43db089 --- /dev/null +++ b/scripts/market/deploy_market_factory_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for market_factory.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_MarketFactory.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 $5 $6 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/market/deploy_market_token_contract.sh b/scripts/market/deploy_market_token_contract.sh new file mode 100644 index 00000000..08563d75 --- /dev/null +++ b/scripts/market/deploy_market_token_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for market_token.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_MarketToken.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/mock/deploy_governable_contract.sh b/scripts/mock/deploy_governable_contract.sh new file mode 100644 index 00000000..a7f802ee --- /dev/null +++ b/scripts/mock/deploy_governable_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for governable.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_Governable.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/mock/deploy_referral_storage.sh b/scripts/mock/deploy_referral_storage.sh new file mode 100644 index 00000000..89976652 --- /dev/null +++ b/scripts/mock/deploy_referral_storage.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for referral_storage.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_ReferralStorage.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/oracle/deploy_oracle_contract.sh b/scripts/oracle/deploy_oracle_contract.sh new file mode 100755 index 00000000..f156cde6 --- /dev/null +++ b/scripts/oracle/deploy_oracle_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for data_store.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_Oracle.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/oracle/deploy_oracle_store_contract.sh b/scripts/oracle/deploy_oracle_store_contract.sh new file mode 100755 index 00000000..947f165d --- /dev/null +++ b/scripts/oracle/deploy_oracle_store_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for oracle_store.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_OracleStore.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/oracle/deploy_price_feed_contract.sh b/scripts/oracle/deploy_price_feed_contract.sh new file mode 100644 index 00000000..0764fcb1 --- /dev/null +++ b/scripts/oracle/deploy_price_feed_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for price_feed.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_PriceFeed.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/order/deploy_order_vault_contract.sh b/scripts/order/deploy_order_vault_contract.sh new file mode 100755 index 00000000..84374869 --- /dev/null +++ b/scripts/order/deploy_order_vault_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for order_vault.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_OrderVault.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/reader/deploy_reader_contract.sh b/scripts/reader/deploy_reader_contract.sh new file mode 100644 index 00000000..36e7b56d --- /dev/null +++ b/scripts/reader/deploy_reader_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for reader.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_Reader.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/role/deploy_role_module_contract.sh b/scripts/role/deploy_role_module_contract.sh new file mode 100644 index 00000000..9f614190 --- /dev/null +++ b/scripts/role/deploy_role_module_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for role_module.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_RoleModule.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/role/deploy_role_store_contract.sh b/scripts/role/deploy_role_store_contract.sh new file mode 100755 index 00000000..8639bb29 --- /dev/null +++ b/scripts/role/deploy_role_store_contract.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Deployment script for role_store.cairo +command_output=$(starkli declare ../../target/dev/satoru_RoleStore.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/router/deploy_exchange_router_contract.sh b/scripts/router/deploy_exchange_router_contract.sh new file mode 100644 index 00000000..63109871 --- /dev/null +++ b/scripts/router/deploy_exchange_router_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for exchange_router.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_ExchangeRouter.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 $5 $6 $7 $8 $9 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/router/deploy_router_contract.sh b/scripts/router/deploy_router_contract.sh new file mode 100644 index 00000000..861f5a60 --- /dev/null +++ b/scripts/router/deploy_router_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for router.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_Router.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/swap/deploy_swap_handler_contract.sh b/scripts/swap/deploy_swap_handler_contract.sh new file mode 100644 index 00000000..e1c7df81 --- /dev/null +++ b/scripts/swap/deploy_swap_handler_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for swap_handler.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_SwapHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/withdrawal/deploy_withdrawal_vault_contract.sh b/scripts/withdrawal/deploy_withdrawal_vault_contract.sh new file mode 100644 index 00000000..6e33a342 --- /dev/null +++ b/scripts/withdrawal/deploy_withdrawal_vault_contract.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Deployment script for withdrawal_vault.cairo + +# Declare the contract and capture the command output +command_output=$(starkli declare ../../target/dev/satoru_WithdrawalVault.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) + +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" + +# Deploy the contract using the extracted class hash +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/src/adl/adl_utils.cairo b/src/adl/adl_utils.cairo index b7408bb8..60d3412a 100644 --- a/src/adl/adl_utils.cairo +++ b/src/adl/adl_utils.cairo @@ -2,8 +2,8 @@ //! This is particularly for markets with an index token that is different from //! the long token. //! -//! For example, if there is a DOGE / USD perp market with ETH as the long token -//! it would be possible for the price of DOGE to increase faster than the price of +//! For example, if there is a STRK / USD perp market with ETH as the long token +//! it would be possible for the price of STRK to increase faster than the price of //! ETH. //! //! In this scenario, profitable positions should be closed through ADL to ensure @@ -13,24 +13,25 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::{get_caller_address, ContractAddress}; +use starknet::{get_caller_address, ContractAddress, contract_address_const}; use integer::BoundedInt; // Local imports. use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::event::{event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait},}; use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; use satoru::market::market_utils::{ - MarketPrices, get_enabled_market, get_market_prices, is_pnl_factor_exceeded_direct + MarketPrices, get_enabled_market, get_market_prices, is_pnl_factor_exceeded_check }; use satoru::adl::error::AdlError; use satoru::data::keys; -use satoru::utils::arrays::u64_are_gte; +use satoru::utils::arrays::are_gte_u64; use satoru::position::position_utils; use satoru::position::position::Position; use satoru::order::order::{Order, OrderType, DecreasePositionSwapType}; use satoru::nonce::nonce_utils; use satoru::callback::callback_utils::get_saved_callback_contract; use satoru::utils::span32::{Span32, Array32Trait}; +use satoru::utils::i256::i256; /// CreateAdlOrderParams struct used in createAdlOrder to avoid stack #[derive(Drop, Copy, starknet::Store, Serde)] struct CreateAdlOrderParams { @@ -47,7 +48,7 @@ struct CreateAdlOrderParams { /// Whether the position is long or short. is_long: bool, /// The size to reduce the position by. - size_delta_usd: u128, + size_delta_usd: u256, /// The block to set the order's updated_at_block. updated_at_block: u64, } @@ -90,7 +91,7 @@ fn update_adl_state( ) { let latest_adl_block = get_latest_adl_block(data_store, market, is_long); assert( - u64_are_gte(max_oracle_block_numbers, latest_adl_block), + are_gte_u64(max_oracle_block_numbers, latest_adl_block), AdlError::ORACLE_BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED ); let _market = get_enabled_market(data_store, market); @@ -99,7 +100,7 @@ fn update_adl_state( // it is possible for a pool to be in a state where withdrawals and ADL is not allowed // this is similar to the case where there is a large amount of open positions relative // to the amount of tokens in the pool - let (should_enable_adl, pnl_to_pool_factor, max_pnl_factor) = is_pnl_factor_exceeded_direct( + let (should_enable_adl, pnl_to_pool_factor, max_pnl_factor) = is_pnl_factor_exceeded_check( data_store, _market, prices, is_long, keys::max_pnl_factor_for_adl() ); set_adl_enabled(data_store, market, is_long, should_enable_adl); @@ -126,19 +127,9 @@ fn create_adl_order(params: CreateAdlOrderParams) -> felt252 { let positon_key = position_utils::get_position_key( params.account, params.market, params.collateral_token, params.is_long ); - let position_result = params.data_store.get_position(positon_key); - let mut position: Position = Default::default(); + let position = params.data_store.get_position(positon_key); - // Check if the position is valid - match position_result { - Option::Some(pos) => { - assert(params.size_delta_usd <= pos.size_in_usd, AdlError::INVALID_SIZE_DELTA_FOR_ADL); - position = pos; - }, - Option::None => { - panic_with_felt252(AdlError::POSTION_NOT_VALID); - } - } + assert(params.size_delta_usd <= position.size_in_usd, AdlError::INVALID_SIZE_DELTA_FOR_ADL); // no slippage is set for this order, it may be preferrable for ADL orders // to be executed, in case of large price impact, the user could be refunded @@ -157,8 +148,8 @@ fn create_adl_order(params: CreateAdlOrderParams) -> felt252 { // swapping the pnl token to the collateral token helps to ensure fees can be paid // using the realized profit - let acceptable_price_: u128 = if position.is_long { - 0_u128 + let acceptable_price_: u256 = if position.is_long { + 0_u256 } else { BoundedInt::max() }; @@ -172,7 +163,7 @@ fn create_adl_order(params: CreateAdlOrderParams) -> felt252 { callback_contract: get_saved_callback_contract( params.data_store, params.account, params.market ), - ui_fee_receiver: 0.try_into().unwrap(), + ui_fee_receiver: contract_address_const::<0>(), market: params.market, initial_collateral_token: position.collateral_token, swap_path: Array32Trait::::span32(@ArrayTrait::new()), @@ -181,11 +172,7 @@ fn create_adl_order(params: CreateAdlOrderParams) -> felt252 { trigger_price: 0, acceptable_price: acceptable_price_, execution_fee: 0, - callback_gas_limit: params - .data_store - .get_felt252(keys::max_callback_gas_limit()) - .try_into() - .unwrap(), + callback_gas_limit: params.data_store.get_felt252(keys::max_callback_gas_limit()).into(), min_output_amount: 0, updated_at_block: params.updated_at_block, is_long: position.is_long, @@ -209,11 +196,11 @@ fn validate_adl( is_long: bool, max_oracle_block_numbers: Span ) { - let is_adl_enabled = get_adl_enabled(data_store, market, is_long); + let is_adl_enabled = get_is_adl_enabled(data_store, market, is_long); assert(is_adl_enabled, AdlError::ADL_NOT_ENABLED); let latest_block = get_latest_adl_block(data_store, market, is_long); assert( - u64_are_gte(max_oracle_block_numbers, latest_block), + are_gte_u64(max_oracle_block_numbers, latest_block), AdlError::ORACLE_BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED ); } @@ -228,7 +215,10 @@ fn validate_adl( fn get_latest_adl_block( data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool ) -> u64 { - data_store.get_u128(keys::latest_adl_block_key(market, is_long)).try_into().unwrap() + data_store + .get_u256(keys::latest_adl_block_key(market, is_long)) + .try_into() + .expect('get_u256 into u64 failed') } /// Set the latest block at which the ADL flag was updated. @@ -242,7 +232,7 @@ fn get_latest_adl_block( fn set_latest_adl_block( data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool, value: u64 ) -> u64 { - data_store.set_u128(keys::latest_adl_block_key(market, is_long), value.into()); + data_store.set_u256(keys::latest_adl_block_key(market, is_long), value.into()); value } @@ -253,18 +243,10 @@ fn set_latest_adl_block( /// * `is_long` - Indicates whether to check the long or short side of the market. /// # Returns /// Return whether ADL is enabled. -fn get_adl_enabled( +fn get_is_adl_enabled( data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool -) -> bool { // TODO - let result = data_store.get_bool(keys::is_adl_enabled_key(market, is_long)); - match result { - Option::Some(data) => { - return data; - }, - Option::None => { - return false; - } - } +) -> bool { + data_store.get_bool(keys::is_adl_enabled_key(market, is_long)) } /// Set whether ADL is enabled. @@ -294,8 +276,8 @@ fn emit_adl_state_updated( event_emitter: IEventEmitterDispatcher, market: ContractAddress, is_long: bool, - pnl_to_pool_factor: i128, - max_pnl_factor: u128, + pnl_to_pool_factor: i256, + max_pnl_factor: u256, should_enable_adl: bool ) { event_emitter diff --git a/src/adl/error.cairo b/src/adl/error.cairo index 78adb5a5..30ca0f29 100644 --- a/src/adl/error.cairo +++ b/src/adl/error.cairo @@ -3,5 +3,5 @@ mod AdlError { 'block_no_smaller_than_required'; const INVALID_SIZE_DELTA_FOR_ADL: felt252 = 'invalid_size_delta_for_adl'; const ADL_NOT_ENABLED: felt252 = 'adl_not_enabled'; - const POSTION_NOT_VALID: felt252 = 'position_not_valid'; + const POSITION_NOT_VALID: felt252 = 'position_not_valid'; } diff --git a/src/bank/bank.cairo b/src/bank/bank.cairo index 922089e0..800afad8 100644 --- a/src/bank/bank.cairo +++ b/src/bank/bank.cairo @@ -7,6 +7,7 @@ // Core lib imports. use core::traits::Into; use starknet::ContractAddress; +use satoru::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; // ************************************************************************* // Interface of the `Bank` contract. @@ -29,7 +30,11 @@ trait IBank { /// * `receiver` - The address of the receiver. /// * `amount` - The amount of tokens to transfer. fn transfer_out( - ref self: TContractState, token: ContractAddress, receiver: ContractAddress, amount: u128, + ref self: TContractState, + sender: ContractAddress, + token: ContractAddress, + receiver: ContractAddress, + amount: u256, ); } @@ -45,14 +50,14 @@ mod Bank { get_caller_address, get_contract_address, ContractAddress, contract_address_const }; - use debug::PrintTrait; - // Local imports. use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use super::IBank; use satoru::bank::error::BankError; use satoru::role::role_module::{RoleModule, IRoleModule}; - use satoru::token::token_utils::transfer; + // use satoru::token::token_utils::transfer; + use satoru::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + // ************************************************************************* // STORAGE @@ -60,7 +65,7 @@ mod Bank { #[storage] struct Storage { /// Interface to interact with the `DataStore` contract. - data_store: IDataStoreDispatcher, + data_store: IDataStoreDispatcher } // ************************************************************************* @@ -84,7 +89,7 @@ mod Bank { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl BankImpl of super::IBank { fn initialize( ref self: ContractState, @@ -103,15 +108,16 @@ mod Bank { fn transfer_out( ref self: ContractState, + sender: ContractAddress, token: ContractAddress, receiver: ContractAddress, - amount: u128, + amount: u256, ) { // assert that caller is a controller - let mut role_module: RoleModule::ContractState = - RoleModule::unsafe_new_contract_state(); - role_module.only_controller(); - self.transfer_out_internal(token, receiver, amount); + // let mut role_module: RoleModule::ContractState = + // RoleModule::unsafe_new_contract_state(); + // role_module.only_controller(); + self.transfer_out_internal(sender, token, receiver, amount); } } @@ -124,13 +130,15 @@ mod Bank { /// * `receiver` - receiver the address to transfer to fn transfer_out_internal( ref self: ContractState, + sender: ContractAddress, token: ContractAddress, receiver: ContractAddress, - amount: u128, + amount: u256, ) { // check that receiver is not this contract assert(receiver != get_contract_address(), BankError::SELF_TRANSFER_NOT_SUPPORTED); - transfer(self.data_store.read(), token, receiver, amount); + // transfer(self.data_store.read(), token, receiver, amount); // TODO check double send + IERC20Dispatcher { contract_address: token }.transfer_from(sender, receiver, amount); } } } diff --git a/src/bank/strict_bank.cairo b/src/bank/strict_bank.cairo index 18689369..c40b229b 100644 --- a/src/bank/strict_bank.cairo +++ b/src/bank/strict_bank.cairo @@ -5,8 +5,9 @@ // ************************************************************************* // Core lib imports. -use core::traits::Into; -use starknet::ContractAddress; +use traits::{Into, TryInto}; +use starknet::{ContractAddress, get_contract_address}; +use satoru::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; // ************************************************************************* // Interface of the `StrictBank` contract. @@ -29,17 +30,29 @@ trait IStrictBank { /// * `receiver` - The address of the receiver. /// * `amount` - The amount of tokens to transfer. fn transfer_out( - ref self: TContractState, token: ContractAddress, receiver: ContractAddress, amount: u128, + ref self: TContractState, + sender: ContractAddress, + token: ContractAddress, + receiver: ContractAddress, + amount: u256, ); - /// 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 transfer for + /// # Return + /// The amount of tokens transferred in + fn record_transfer_in(ref self: TContractState, token: ContractAddress) -> u256; + + /// 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. - /// # Returns - /// * The new balance. - fn sync_token_balance(ref self: TContractState, token: ContractAddress) -> u128; + /// * `token` - The token to record the burn for + /// # Return + /// The new balance + fn sync_token_balance(ref self: TContractState, token: starknet::ContractAddress) -> u256; } #[starknet::contract] @@ -49,19 +62,25 @@ mod StrictBank { // ************************************************************************* // Core lib imports. - use starknet::{get_caller_address, ContractAddress, contract_address_const}; - - use debug::PrintTrait; + use core::traits::TryInto; + use starknet::{ + get_caller_address, get_contract_address, ContractAddress, contract_address_const + }; // 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}; + use debug::PrintTrait; // ************************************************************************* // STORAGE // ************************************************************************* #[storage] - struct Storage {} + struct Storage { + token_balances: LegacyMap::, + } // ************************************************************************* // CONSTRUCTOR @@ -79,11 +98,10 @@ mod StrictBank { self.initialize(data_store_address, role_store_address); } - // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl StrictBank of super::IStrictBank { fn initialize( ref self: ContractState, @@ -96,16 +114,71 @@ mod StrictBank { fn transfer_out( ref self: ContractState, + sender: ContractAddress, token: ContractAddress, receiver: ContractAddress, - amount: u128, + amount: u256, ) { let mut state: Bank::ContractState = Bank::unsafe_new_contract_state(); - IBank::transfer_out(ref state, token, receiver, amount); + IBank::transfer_out(ref state, sender, token, receiver, amount); + self.after_transfer_out_infernal(token); + } + + fn sync_token_balance(ref self: ContractState, token: ContractAddress) -> u256 { + // 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: u256 = 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) -> u256 { + // 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: u256 = IERC20Dispatcher { contract_address: token } + .balance_of(this_contract) + .try_into() + .unwrap(); + self.token_balances.write(token, balance); } - fn sync_token_balance(ref self: ContractState, token: ContractAddress) -> u128 { - 0 + /// 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 + ) -> u256 { + let prev_balance: u256 = self.token_balances.read(token); + let this_contract = get_contract_address(); + let next_balance: u256 = 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/callback/callback_utils.cairo b/src/callback/callback_utils.cairo index 23d21d83..213b0f32 100644 --- a/src/callback/callback_utils.cairo +++ b/src/callback/callback_utils.cairo @@ -23,7 +23,7 @@ use starknet::ContractAddress; // Local imports. use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::data::keys; -use satoru::event::event_utils::EventLogData; +use satoru::event::event_utils::{LogData, LogDataTrait}; use satoru::order::order::Order; use satoru::deposit::deposit::Deposit; use satoru::withdrawal::withdrawal::Withdrawal; @@ -38,6 +38,7 @@ use satoru::callback::deposit_callback_receiver::interface::{ use satoru::callback::withdrawal_callback_receiver::interface::{ IWithdrawalCallbackReceiverDispatcher, IWithdrawalCallbackReceiverDispatcherTrait }; +use integer::U256TryIntoFelt252; /// Validate that the callback_gas_limit is less than the max specified value. /// This is to prevent callback gas limits which are larger than the max gas limits per block @@ -45,14 +46,14 @@ use satoru::callback::withdrawal_callback_receiver::interface::{ /// # Arguments /// * `data_store` - The data store to use. /// * `callback_gas_limit` - The callback gas limit. -fn validate_callback_gas_limit(data_store: IDataStoreDispatcher, callback_gas_limit: u128) { - let max_callback_gas_limit = data_store.get_u128(keys::max_callback_gas_limit()); +fn validate_callback_gas_limit(data_store: IDataStoreDispatcher, callback_gas_limit: u256) { + let max_callback_gas_limit = data_store.get_u256(keys::max_callback_gas_limit()); if callback_gas_limit > max_callback_gas_limit { panic( array![ CallbackError::MAX_CALLBACK_GAS_LIMIT_EXCEEDED, - callback_gas_limit.into(), - max_callback_gas_limit.into() + callback_gas_limit.try_into().expect('u256 into felt failed'), + max_callback_gas_limit.try_into().expect('u256 into felt failed') ] ); } @@ -91,119 +92,99 @@ fn get_saved_callback_contract( /// # Arguments /// * `key` - They key of the deposit. /// * `deposit` - The deposit that was executed. -/// * `event_data` - The event log data. -fn after_deposit_execution( - key: felt252, deposit: Deposit, event_data: EventLogData, event_emitter: IEventEmitterDispatcher -) { +/// * `log_data` - The log data. +fn after_deposit_execution(key: felt252, deposit: Deposit, mut log_data: LogData) { if !is_valid_callback_contract(deposit.callback_contract) { return; } let dispatcher = IDepositCallbackReceiverDispatcher { contract_address: deposit.callback_contract }; - dispatcher.after_deposit_execution(key, deposit, event_data) + dispatcher.after_deposit_execution(key, deposit, log_data.serialize_into()) } /// Called after a deposit cancellation. /// # Arguments /// * `key` - They key of the deposit. /// * `deposit` - The deposit that was cancelled. -/// * `event_data` - The event log data. -fn after_deposit_cancellation( - key: felt252, deposit: Deposit, event_data: EventLogData, event_emitter: IEventEmitterDispatcher -) { +/// * `log_data` - The log data. +fn after_deposit_cancellation(key: felt252, deposit: Deposit, mut log_data: LogData) { if !is_valid_callback_contract(deposit.callback_contract) { return; } let dispatcher = IDepositCallbackReceiverDispatcher { contract_address: deposit.callback_contract }; - dispatcher.after_deposit_cancellation(key, deposit, event_data) + dispatcher.after_deposit_cancellation(key, deposit, log_data.serialize_into()) } /// Called after a withdrawal execution. /// # Arguments /// * `key` - They key of the withdrawal. /// * `withdrawal` - The withdrawal that was executed. -/// * `event_data` - The event log data. -fn after_withdrawal_execution( - key: felt252, - withdrawal: Withdrawal, - event_data: EventLogData, - event_emitter: IEventEmitterDispatcher -) { +/// * `log_data` - The log data. +fn after_withdrawal_execution(key: felt252, withdrawal: Withdrawal, mut log_data: LogData) { if !is_valid_callback_contract(withdrawal.callback_contract) { return; } let dispatcher = IWithdrawalCallbackReceiverDispatcher { contract_address: withdrawal.callback_contract }; - dispatcher.after_withdrawal_execution(key, withdrawal, event_data) + dispatcher.after_withdrawal_execution(key, withdrawal, log_data.serialize_into()) } /// Called after an withdrawal cancellation. /// # Arguments /// * `key` - They key of the withdrawal. /// * `withdrawal` - The withdrawal that was cancelled. -/// * `event_data` - The event log data. -fn after_withdrawal_cancellation( - key: felt252, - withdrawal: Withdrawal, - event_data: EventLogData, - event_emitter: IEventEmitterDispatcher -) { +/// * `log_data` - The log data. +fn after_withdrawal_cancellation(key: felt252, withdrawal: Withdrawal, mut log_data: LogData) { if !is_valid_callback_contract(withdrawal.callback_contract) { return; } let dispatcher = IWithdrawalCallbackReceiverDispatcher { contract_address: withdrawal.callback_contract }; - dispatcher.after_withdrawal_cancellation(key, withdrawal, event_data) + dispatcher.after_withdrawal_cancellation(key, withdrawal, log_data.serialize_into()) } /// Called after an order execution. /// # Arguments /// * `key` - They key of the order. /// * `order` - The order that was executed. -/// * `event_data` - The event log data. -fn after_order_execution( - key: felt252, order: Order, event_data: EventLogData, event_emitter: IEventEmitterDispatcher -) { +/// * `log_data` - The log data. +fn after_order_execution(key: felt252, order: Order, mut log_data: LogData) { if !is_valid_callback_contract(order.callback_contract) { return; } let dispatcher = IOrderCallbackReceiverDispatcher { contract_address: order.callback_contract }; - dispatcher.after_order_execution(key, order, event_data) + dispatcher.after_order_execution(key, order, log_data.serialize_into()) } /// Called after an order cancellation. /// # Arguments /// * `key` - They key of the order. /// * `order` - The order that was cancelled. -/// * `event_data` - The event log data. -fn after_order_cancellation( - key: felt252, order: Order, event_data: EventLogData, event_emitter: IEventEmitterDispatcher -) { +/// * `log_data` - The log data. +fn after_order_cancellation(key: felt252, order: Order, mut log_data: LogData) { if !is_valid_callback_contract(order.callback_contract) { return; } let dispatcher = IOrderCallbackReceiverDispatcher { contract_address: order.callback_contract }; - dispatcher.after_order_cancellation(key, order, event_data) + dispatcher.after_order_cancellation(key, order, log_data.serialize_into()) } /// Called after an order cancellation. /// # Arguments /// * `key` - They key of the order. /// * `order` - The order that was frozen. -/// * `event_data` - The event log data. -fn after_order_frozen( - key: felt252, order: Order, event_data: EventLogData, event_emitter: IEventEmitterDispatcher -) { +/// * `log_data` - The log data. +fn after_order_frozen(key: felt252, order: Order, mut log_data: LogData) { if !is_valid_callback_contract(order.callback_contract) { return; } let dispatcher = IOrderCallbackReceiverDispatcher { contract_address: order.callback_contract }; - dispatcher.after_order_frozen(key, order, event_data) + dispatcher.after_order_frozen(key, order, log_data.serialize_into()) } /// Validates that the given address is a contract. diff --git a/src/callback/deposit_callback_receiver/interface.cairo b/src/callback/deposit_callback_receiver/interface.cairo index 19f933fa..e45b7b33 100644 --- a/src/callback/deposit_callback_receiver/interface.cairo +++ b/src/callback/deposit_callback_receiver/interface.cairo @@ -1,6 +1,6 @@ // Satoru imports use satoru::deposit::deposit::Deposit; -use satoru::event::event_utils::EventLogData; +use satoru::event::event_utils::LogData; // ************************************************************************* // Interface of the `DepositCallbackReceiver` contract. @@ -13,7 +13,7 @@ trait IDepositCallbackReceiver { /// * `event_data` - The event log data. /// * `deposit` - The deposit that was executed. fn after_deposit_execution( - ref self: TContractState, key: felt252, deposit: Deposit, event_data: EventLogData, + ref self: TContractState, key: felt252, deposit: Deposit, log_data: Array, ); /// Called after a deposit cancellation. @@ -22,6 +22,6 @@ trait IDepositCallbackReceiver { /// * `event_data` - The event log data. /// * `deposit` - The deposit that was cancelled. fn after_deposit_cancellation( - ref self: TContractState, key: felt252, deposit: Deposit, event_data: EventLogData, + ref self: TContractState, key: felt252, deposit: Deposit, log_data: Array, ); } diff --git a/src/callback/mocks.cairo b/src/callback/mocks.cairo index 6f01cbce..a8457361 100644 --- a/src/callback/mocks.cairo +++ b/src/callback/mocks.cairo @@ -1,7 +1,7 @@ use starknet::ContractAddress; use snforge_std::{declare, ContractClassTrait}; -use satoru::tests_lib::{setup, teardown, deploy_event_emitter}; +use satoru::test_utils::tests_lib::{setup, teardown, deploy_event_emitter}; #[starknet::interface] trait ICallbackMock { @@ -12,7 +12,7 @@ trait ICallbackMock { mod CallbackMock { use satoru::callback::deposit_callback_receiver::interface::IDepositCallbackReceiver; use satoru::deposit::deposit::Deposit; - use satoru::event::event_utils::EventLogData; + use satoru::event::event_utils::LogData; #[storage] struct Storage { @@ -25,23 +25,23 @@ mod CallbackMock { } - #[external(v0)] + #[abi(embed_v0)] impl ICallbackMockImpl of super::ICallbackMock { fn get_counter(self: @ContractState) -> u32 { self.counter.read() } } - #[external(v0)] + #[abi(embed_v0)] impl IDepositCallbackReceiverImpl of IDepositCallbackReceiver { fn after_deposit_execution( - ref self: ContractState, key: felt252, deposit: Deposit, event_data: EventLogData, + ref self: ContractState, key: felt252, deposit: Deposit, log_data: Array, ) { self.counter.write(self.get_counter() + 1); } fn after_deposit_cancellation( - ref self: ContractState, key: felt252, deposit: Deposit, event_data: EventLogData, + ref self: ContractState, key: felt252, deposit: Deposit, log_data: Array, ) { self.counter.write(self.get_counter() + 1); } diff --git a/src/callback/order_callback_receiver/interface.cairo b/src/callback/order_callback_receiver/interface.cairo index 29d5b255..8d1c8912 100644 --- a/src/callback/order_callback_receiver/interface.cairo +++ b/src/callback/order_callback_receiver/interface.cairo @@ -1,6 +1,6 @@ // Satoru imports use satoru::order::order::Order; -use satoru::event::event_utils::EventLogData; +use satoru::event::event_utils::LogData; // ************************************************************************* // Interface of the `OrderCallbackReceiver` contract. @@ -11,26 +11,26 @@ trait IOrderCallbackReceiver { /// # Arguments /// * `key` - They key of the order. /// * `order` - The order that was executed. - /// * `event_data` - The event log data. + /// * `log_data` - The log data. fn after_order_execution( - ref self: TContractState, key: felt252, order: Order, event_data: EventLogData + ref self: TContractState, key: felt252, order: Order, log_data: Array ); /// Called after an order cancellation. /// # Arguments /// * `key` - They key of the order. /// * `order` - The order that was cancelled. - /// * `event_data` - The event log data. + /// * `log_data` - The log data. fn after_order_cancellation( - ref self: TContractState, key: felt252, order: Order, event_data: EventLogData + ref self: TContractState, key: felt252, order: Order, log_data: Array ); /// Called after an order cancellation. /// # Arguments /// * `key` - They key of the order. /// * `order` - The order that was frozen. - /// * `event_data` - The event log data. + /// * `log_data` - The log data. fn after_order_frozen( - ref self: TContractState, key: felt252, order: Order, event_data: EventLogData + ref self: TContractState, key: felt252, order: Order, log_data: Array ); } diff --git a/src/callback/withdrawal_callback_receiver/interface.cairo b/src/callback/withdrawal_callback_receiver/interface.cairo index 146f88bb..1c43a31f 100644 --- a/src/callback/withdrawal_callback_receiver/interface.cairo +++ b/src/callback/withdrawal_callback_receiver/interface.cairo @@ -1,6 +1,6 @@ // Satoru imports use satoru::withdrawal::withdrawal::Withdrawal; -use satoru::event::event_utils::EventLogData; +use satoru::event::event_utils::LogData; // ************************************************************************* // Interface of the `WithdrawalCallbackReceiver` contract. @@ -11,18 +11,18 @@ trait IWithdrawalCallbackReceiver { /// # Arguments /// * `key` - They key of the withdrawal. /// * `withdrawal` - The withdrawal that was executed. - /// * `event_data` - The event log data. + /// * `log_data` - The log data. // TODO uncomment withdrawal when available fn after_withdrawal_execution( - ref self: TContractState, key: felt252, withdrawal: Withdrawal, event_data: EventLogData, + ref self: TContractState, key: felt252, withdrawal: Withdrawal, log_data: Array, ); /// Called after an withdrawal cancellation. /// # Arguments /// * `key` - They key of the withdrawal. /// * `withdrawal` - The withdrawal that was cancelled. - /// * `event_data` - The event log data. + /// * `log_data` - The log data. fn after_withdrawal_cancellation( - ref self: TContractState, key: felt252, withdrawal: Withdrawal, event_data: EventLogData, + ref self: TContractState, key: felt252, withdrawal: Withdrawal, log_data: Array, ); } diff --git a/src/chain/chain.cairo b/src/chain/chain.cairo index d860dbc9..5f5ed9d0 100644 --- a/src/chain/chain.cairo +++ b/src/chain/chain.cairo @@ -28,7 +28,7 @@ mod Chain { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl Chain of super::IChain { fn get_block_number(self: @ContractState) -> u64 { starknet::info::get_block_number() diff --git a/src/config/config.cairo b/src/config/config.cairo index 8ae72cfd..ca822651 100644 --- a/src/config/config.cairo +++ b/src/config/config.cairo @@ -48,7 +48,6 @@ mod Config { use starknet::{get_caller_address, ContractAddress, contract_address_const,}; use poseidon::poseidon_hash_span; - use debug::PrintTrait; // Local imports. use satoru::role::role; @@ -98,7 +97,7 @@ mod Config { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl ConfigImpl of super::IConfig { fn set_bool( ref self: ContractState, base_key: felt252, data: Array, value: bool, diff --git a/src/config/timelock.cairo b/src/config/timelock.cairo index 9016ab11..05117a19 100644 --- a/src/config/timelock.cairo +++ b/src/config/timelock.cairo @@ -24,7 +24,6 @@ mod Timelock { use core::zeroable::Zeroable; use starknet::{get_caller_address, ContractAddress, contract_address_const}; - use debug::PrintTrait; // Local imports. @@ -46,6 +45,6 @@ mod Timelock { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl TimelockImpl of super::ITimelock {} } diff --git a/src/data/data_store.cairo b/src/data/data_store.cairo index cd519245..995409a7 100644 --- a/src/data/data_store.cairo +++ b/src/data/data_store.cairo @@ -10,12 +10,40 @@ use satoru::order::order::Order; use satoru::position::position::Position; use satoru::withdrawal::withdrawal::Withdrawal; use satoru::deposit::deposit::Deposit; +use satoru::utils::i256::i256; // ************************************************************************* // Interface of the `DataStore` contract. // ************************************************************************* #[starknet::interface] trait IDataStore { + fn get_max_pool_amount_key( + self: @TContractState, market_token: ContractAddress, token: ContractAddress + ) -> felt252; + fn get_open_interest_key( + self: @TContractState, + market: ContractAddress, + collateral_token: ContractAddress, + is_long: bool + ) -> felt252; + fn get_max_open_interest_key( + self: @TContractState, market: ContractAddress, is_long: bool + ) -> felt252; + fn get_pool_amount_key( + self: @TContractState, market: ContractAddress, token: ContractAddress + ) -> felt252; + fn get_max_pnl_factor_key( + self: @TContractState, pnl_factor_type: felt252, market: ContractAddress, is_long: bool + ) -> felt252; + fn get_max_pnl_factor_for_deposit_key(self: @TContractState) -> felt252; + fn get_max_pnl_factor_for_withdrawals_key(self: @TContractState) -> felt252; + fn get_reserve_factor_key( + self: @TContractState, market: ContractAddress, is_long: bool + ) -> felt252; + fn get_open_interest_reserve_factor_key( + self: @TContractState, market: ContractAddress, is_long: bool + ) -> felt252; + // ************************************************************************* // Felt252 related functions. // ************************************************************************* @@ -82,39 +110,21 @@ trait IDataStore { /// * `value` - The value to subtract. fn decrement_u256(ref self: TContractState, key: felt252, value: u256) -> u256; - - // ************************************************************************* - // u128 related functions. - // ************************************************************************* - /// Get a u128 value for the given key. - /// # Arguments - /// * `key` - The key to get the value for. - /// # Returns - /// The value for the given key. - fn get_u128(self: @TContractState, key: felt252) -> u128; - - /// Set a u128 value for the given key. - /// # Arguments - /// * `key` - The key to set the value for. - /// * `value` - The value to set. - fn set_u128(ref self: TContractState, key: felt252, value: u128); - - /// Delete a u128 value for the given key. - /// # Arguments - /// * `key` - The key to delete the value for. - fn remove_u128(ref self: TContractState, key: felt252); - - /// Add input to existing value. + /// Add signed value to existing value if result positive. /// # Arguments /// * `key` - The key to add the value to. /// * `value` - The value to add. - fn increment_u128(ref self: TContractState, key: felt252, value: u128) -> u128; + /// * `error` - The error to throw if result is negative. + fn apply_delta_to_u256( + ref self: TContractState, key: felt252, value: i256, error: felt252 + ) -> u256; - /// Subtract input from existing value. + /// Add the input int value to the existing uint value, prevent the uint + /// value from becoming negative /// # Arguments - /// * `key` - The key to subtract the value from. - /// * `value` - The value to subtract. - fn decrement_u128(ref self: TContractState, key: felt252, value: u128) -> u128; + /// * `key` - the key of the value + /// * `value` - the input int value + fn apply_bounded_delta_to_u256(ref self: TContractState, key: felt252, value: i256) -> u256; // ************************************************************************* // Address related functions. @@ -144,7 +154,7 @@ trait IDataStore { /// * `key` - The key to get the value for. /// # Returns /// The value for the given key. - fn get_bool(self: @TContractState, key: felt252) -> Option; + fn get_bool(self: @TContractState, key: felt252) -> bool; /// Set a bool value for the given key. /// # Arguments @@ -165,7 +175,7 @@ trait IDataStore { /// * `key` - The key to get the value for. /// # Returns /// The value for the given key. - fn get_market(self: @TContractState, key: ContractAddress) -> Option; + fn get_market(self: @TContractState, key: ContractAddress) -> Market; /// Set a market value for the given key. /// # Arguments @@ -177,7 +187,7 @@ trait IDataStore { /// * `salt` - The salt to get the value for. /// # Returns /// The value for the given key. - fn get_by_salt_market(self: @TContractState, salt: felt252) -> Option; + fn get_by_salt_market(self: @TContractState, salt: felt252) -> Market; fn remove_market(ref self: TContractState, key: ContractAddress); /// Get a hash given salt. /// # Arguments @@ -222,7 +232,7 @@ trait IDataStore { /// * `key` - The key to get the value for. /// # Returns /// The value for the given key. - fn get_order(self: @TContractState, key: felt252) -> Option; + fn get_order(self: @TContractState, key: felt252) -> Order; /// Set a order value for the given key. /// # Arguments @@ -243,7 +253,6 @@ trait IDataStore { /// * `end` - Start index fn get_order_keys(self: @TContractState, start: usize, end: usize) -> Array; - // TODO checkk /// Return total order count fn get_order_count(self: @TContractState) -> u32; @@ -273,7 +282,7 @@ trait IDataStore { /// * `key` - The key to get the value for. /// # Returns /// The value for the given key. - fn get_position(self: @TContractState, key: felt252) -> Option; + fn get_position(self: @TContractState, key: felt252) -> Position; /// Set a position value for the given key. /// # Arguments @@ -294,7 +303,6 @@ trait IDataStore { /// * `end` - Start index fn get_position_keys(self: @TContractState, start: usize, end: usize) -> Array; - // TODO checkk /// Return total position count fn get_position_count(self: @TContractState) -> u32; @@ -323,7 +331,7 @@ trait IDataStore { /// * `key` - The key to get the value for. /// # Returns /// The value for the given key. - fn get_withdrawal(self: @TContractState, key: felt252) -> Option; + fn get_withdrawal(self: @TContractState, key: felt252) -> Withdrawal; /// Set a withdrawal value for the given key. /// # Arguments @@ -374,7 +382,7 @@ trait IDataStore { /// * `key` - The key to get the value for. /// # Returns /// The value for the given key. - fn get_deposit(self: @TContractState, key: felt252) -> Option; + fn get_deposit(self: @TContractState, key: felt252) -> Deposit; /// Set a deposit value for the given key. /// # Arguments @@ -411,16 +419,15 @@ trait IDataStore { ) -> Array; - //TODO: Update u128 to i128 when Serde and Store for i128 implementations are released. // ************************************************************************* - // int128 related functions. + // int256 related functions. // ************************************************************************* /// Get a int value for the given key. /// # Arguments /// * `key` - The key to get the value for. /// # Returns /// The value for the given key. - fn get_i128(self: @TContractState, key: felt252) -> u128; + fn get_i256(self: @TContractState, key: felt252) -> i256; /// Set the int value for the given key. /// # Arguments @@ -428,25 +435,31 @@ trait IDataStore { /// `value` - The value to set /// # Return /// The int value for the key. - fn set_i128(ref self: TContractState, key: felt252, value: u128); + fn set_i256(ref self: TContractState, key: felt252, value: i256); - /// Delete a i128 value for the given key. + /// Delete a i256 value for the given key. /// # Arguments /// * `key` - The key to delete the value for. - fn remove_i128(ref self: TContractState, key: felt252); + fn remove_i256(ref self: TContractState, key: felt252); + + // @dev add the input int value to the existing int value + // @param key the key of the value + // @param value the input int value + // @return the new int value + fn apply_delta_to_i256(ref self: TContractState, key: felt252, value: i256) -> i256; /// Add input to existing value. /// # Arguments /// * `key` - The key to add the value to. /// * `value` - The value to add. - fn increment_i128(ref self: TContractState, key: felt252, value: u128) -> u128; + fn increment_i256(ref self: TContractState, key: felt252, value: i256) -> i256; /// Subtract input from existing value. /// # Arguments /// * `key` - The key to subtract the value from. /// * `value` - The value to subtract. - fn decrement_i128(ref self: TContractState, key: felt252, value: u128) -> u128; + fn decrement_i256(ref self: TContractState, key: felt252, value: i256) -> i256; } #[starknet::contract] @@ -458,14 +471,14 @@ mod DataStore { // Core lib imports. use core::option::OptionTrait; use core::traits::TryInto; - use starknet::{get_caller_address, ContractAddress, contract_address_const,}; + use starknet::{get_caller_address, ContractAddress, contract_address_const}; use nullable::NullableTrait; use zeroable::Zeroable; use alexandria_storage::list::{ListTrait, List}; - use debug::PrintTrait; use poseidon::poseidon_hash_span; // Local imports. + use satoru::data::keys; use satoru::role::role; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::market::{market::{Market, ValidateMarket}, error::MarketError}; @@ -474,6 +487,9 @@ mod DataStore { use satoru::position::{position::Position, error::PositionError}; use satoru::withdrawal::{withdrawal::Withdrawal, error::WithdrawalError}; use satoru::deposit::{deposit::Deposit, error::DepositError}; + use satoru::utils::calc::{sum_return_uint_256, to_signed, to_unsigned}; + use satoru::utils::calc; + use satoru::utils::i256::{i256, i256_neg}; // ************************************************************************* // STORAGE @@ -483,10 +499,9 @@ mod DataStore { role_store: IRoleStoreDispatcher, felt252_values: LegacyMap::, u256_values: LegacyMap::, - u128_values: LegacyMap::, - i128_values: LegacyMap::, + i256_values: LegacyMap::, address_values: LegacyMap::, - bool_values: LegacyMap::>, + bool_values: LegacyMap::, /// Market storage market_values: LegacyMap::, markets: List, @@ -525,8 +540,61 @@ mod DataStore { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl DataStore of super::IDataStore { + fn get_max_pool_amount_key( + self: @ContractState, market_token: ContractAddress, token: ContractAddress + ) -> felt252 { + keys::max_pool_amount_key(market_token, token) + } + + fn get_open_interest_key( + self: @ContractState, + market: ContractAddress, + collateral_token: ContractAddress, + is_long: bool + ) -> felt252 { + keys::open_interest_key(market, collateral_token, is_long) + } + + fn get_max_open_interest_key( + self: @ContractState, market: ContractAddress, is_long: bool + ) -> felt252 { + keys::max_open_interest_key(market, is_long) + } + + fn get_pool_amount_key( + self: @ContractState, market: ContractAddress, token: ContractAddress + ) -> felt252 { + keys::pool_amount_key(market, token) + } + + fn get_max_pnl_factor_key( + self: @ContractState, pnl_factor_type: felt252, market: ContractAddress, is_long: bool + ) -> felt252 { + keys::max_pnl_factor_key(pnl_factor_type, market, is_long) + } + + fn get_max_pnl_factor_for_deposit_key(self: @ContractState) -> felt252 { + keys::max_pnl_factor_for_deposits() + } + + fn get_max_pnl_factor_for_withdrawals_key(self: @ContractState) -> felt252 { + keys::max_pnl_factor_for_withdrawals() + } + + fn get_reserve_factor_key( + self: @ContractState, market: ContractAddress, is_long: bool + ) -> felt252 { + keys::reserve_factor_key(market, is_long) + } + + fn get_open_interest_reserve_factor_key( + self: @ContractState, market: ContractAddress, is_long: bool + ) -> felt252 { + keys::open_interest_reserve_factor_key(market, is_long) + } + // ************************************************************************* // Felt252 related functions. // ************************************************************************* @@ -622,98 +690,82 @@ mod DataStore { new_value } - // ************************************************************************* - // u128 related functions. - // ************************************************************************* - fn get_u128(self: @ContractState, key: felt252) -> u128 { - self.u128_values.read(key) - } - - fn set_u128(ref self: ContractState, key: felt252, value: u128) { + fn apply_delta_to_u256( + ref self: ContractState, key: felt252, value: i256, error: felt252 + ) -> u256 { // Check that the caller has permission to set the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); - // Set the value. - self.u128_values.write(key, value); - } - fn remove_u128(ref self: ContractState, key: felt252) { - // Check that the caller has permission to delete the value. - self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); - // Delete the value. - self.u128_values.write(key, Default::default()); - } - - fn increment_u128(ref self: ContractState, key: felt252, value: u128) -> u128 { - // Check that the caller has permission to set the value. - self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); - // Get the current value. - let current_value = self.u128_values.read(key); - // Add the delta to the current value. - let new_value = current_value + value; - // Set the new value. - self.u128_values.write(key, new_value); - // Return the new value. - new_value + let current_value = self.u256_values.read(key); + if value < Zeroable::zero() && calc::to_unsigned(i256_neg(value)) > current_value { + panic(array![error]); + } + let next_value = calc::sum_return_uint_256(current_value, value); + self.u256_values.write(key, next_value); + next_value } - fn decrement_u128(ref self: ContractState, key: felt252, value: u128) -> u128 { - // Check that the caller has permission to set the value. - self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); - // Get the current value. - let current_value = self.u128_values.read(key); - // Subtract the delta from the current value. - let new_value = current_value - value; - // Set the new value. - self.u128_values.write(key, new_value); - // Return the new value. - new_value + fn apply_bounded_delta_to_u256(ref self: ContractState, key: felt252, value: i256) -> u256 { + let uint_value: u256 = self.u256_values.read(key); + if (value < Zeroable::zero() && to_unsigned(i256_neg(value)) > uint_value) { + self.u256_values.write(key, 0); + return 0; + } + let next_uint: u256 = sum_return_uint_256(uint_value, value); + self.u256_values.write(key, next_uint); + next_uint } - //TODO: Update u128 to i128 when Serde and Store for i128 implementations are released. // ************************************************************************* - // i128 related functions. + // i256 related functions. // ************************************************************************* - fn get_i128(self: @ContractState, key: felt252) -> u128 { - self.i128_values.read(key) + fn get_i256(self: @ContractState, key: felt252) -> i256 { + self.i256_values.read(key) } - fn set_i128(ref self: ContractState, key: felt252, value: u128) { + fn set_i256(ref self: ContractState, key: felt252, value: i256) { // Check that the caller has permission to set the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); // Set the value. - self.i128_values.write(key, value); + self.i256_values.write(key, value); } - fn remove_i128(ref self: ContractState, key: felt252) { + fn remove_i256(ref self: ContractState, key: felt252) { // Check that the caller has permission to delete the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); // Delete the value. - self.i128_values.write(key, Default::default()); + self.i256_values.write(key, Default::default()); + } + + fn apply_delta_to_i256(ref self: ContractState, key: felt252, value: i256) -> i256 { + let next_int: i256 = self.i256_values.read(key) + value; + self.i256_values.write(key, next_int); + next_int } - fn increment_i128(ref self: ContractState, key: felt252, value: u128) -> u128 { + fn increment_i256(ref self: ContractState, key: felt252, value: i256) -> i256 { // Check that the caller has permission to set the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); // Get the current value. - let current_value = self.i128_values.read(key); + let current_value = self.i256_values.read(key); // Add the delta to the current value. // TODO: Check for overflow. let new_value = current_value + value; // Set the new value. - self.i128_values.write(key, new_value); + self.i256_values.write(key, new_value); // Return the new value. new_value } - fn decrement_i128(ref self: ContractState, key: felt252, value: u128) -> u128 { + fn decrement_i256(ref self: ContractState, key: felt252, value: i256) -> i256 { // Check that the caller has permission to set the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); // Get the current value. - let current_value = self.i128_values.read(key); + let current_value = self.i256_values.read(key); // Subtract the delta from the current value. let new_value = current_value - value; // Set the new value. - self.i128_values.write(key, new_value); + self.i256_values.write(key, new_value); // Return the new value. new_value } @@ -742,7 +794,7 @@ mod DataStore { // ************************************************************************* // Bool related functions. // ************************************************************************* - fn get_bool(self: @ContractState, key: felt252) -> Option { + fn get_bool(self: @ContractState, key: felt252) -> bool { self.bool_values.read(key) } @@ -750,27 +802,31 @@ mod DataStore { // Check that the caller has permission to set the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); // Set the value. - self.bool_values.write(key, Option::Some(value)); + self.bool_values.write(key, value); } fn remove_bool(ref self: ContractState, key: felt252) { // Check that the caller has permission to delete the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); // Delete the value. - self.bool_values.write(key, Option::None); + self.bool_values.write(key, false); } // ************************************************************************* // Market related functions. // ************************************************************************* - fn get_market(self: @ContractState, key: ContractAddress) -> Option { + fn get_market(self: @ContractState, key: ContractAddress) -> Market { let offsetted_index: usize = self.market_indexes.read(key); if offsetted_index == 0 { - return Option::None; + return Default::default(); + } + let markets: List = self.markets.read(); + let market_maybe = markets.get(offsetted_index - 1); + match market_maybe { + Option::Some(market) => { market }, + Option::None => { Default::default() } } - let orders: List = self.markets.read(); - orders.get(offsetted_index - 1) } fn set_market( @@ -804,7 +860,10 @@ mod DataStore { self.role_store.read().assert_only_role(get_caller_address(), role::MARKET_KEEPER); let offsetted_index: usize = self.market_indexes.read(key); let mut markets = self.markets.read(); - assert(offsetted_index <= markets.len(), MarketError::MARKET_NOT_FOUND); + assert( + offsetted_index != 0 && offsetted_index <= markets.len(), + MarketError::MARKET_NOT_FOUND + ); let index = offsetted_index - 1; // Replace the value at `index` by the last market in the list. @@ -860,7 +919,7 @@ mod DataStore { self.markets.read().len() } - fn get_by_salt_market(self: @ContractState, salt: felt252) -> Option { + fn get_by_salt_market(self: @ContractState, salt: felt252) -> Market { let key = self.get_address(self.get_market_salt_hash(salt)); self.get_market(key) } @@ -885,19 +944,23 @@ mod DataStore { // Order related functions. // ************************************************************************* - fn get_order(self: @ContractState, key: felt252) -> Option { + fn get_order(self: @ContractState, key: felt252) -> Order { let offsetted_index: usize = self.order_indexes.read(key); if offsetted_index == 0 { - return Option::None; + return Default::default(); } let orders: List = self.orders.read(); - orders.get(offsetted_index - 1) + let order_maybe = orders.get(offsetted_index - 1); + match order_maybe { + Option::Some(order) => { order }, + Option::None => { Default::default() } + } } fn set_order(ref self: ContractState, key: felt252, order: Order) { // Check that the caller has permission to set the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); - assert(order.account != 0.try_into().unwrap(), OrderError::CANT_BE_ZERO); + assert(order.account != contract_address_const::<0>(), OrderError::CANT_BE_ZERO); let mut orders = self.orders.read(); let mut account_orders = self.account_orders.read(order.account); @@ -925,7 +988,9 @@ mod DataStore { self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); let offsetted_index: usize = self.order_indexes.read(key); let mut orders = self.orders.read(); - assert(offsetted_index <= orders.len(), OrderError::ORDER_NOT_FOUND); + assert( + offsetted_index != 0 && offsetted_index <= orders.len(), OrderError::ORDER_NOT_FOUND + ); let index = offsetted_index - 1; // Replace the value at `index` by the last order in the list. @@ -1016,19 +1081,23 @@ mod DataStore { // Position related functions. // ************************************************************************* - fn get_position(self: @ContractState, key: felt252) -> Option { + fn get_position(self: @ContractState, key: felt252) -> Position { let offsetted_index: usize = self.position_indexes.read(key); if offsetted_index == 0 { - return Option::None; + return Default::default(); } let positions: List = self.positions.read(); - positions.get(offsetted_index - 1) + let position_maybe = positions.get(offsetted_index - 1); + match position_maybe { + Option::Some(position) => { position }, + Option::None => { Default::default() } + } } fn set_position(ref self: ContractState, key: felt252, position: Position) { // Check that the caller has permission to set the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); - assert(position.account != 0.try_into().unwrap(), PositionError::CANT_BE_ZERO); + assert(position.account != contract_address_const::<0>(), PositionError::CANT_BE_ZERO); let mut positions = self.positions.read(); let mut account_positions = self.account_positions.read(position.account); @@ -1056,7 +1125,10 @@ mod DataStore { self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); let offsetted_index: usize = self.position_indexes.read(key); let mut positions = self.positions.read(); - assert(offsetted_index <= positions.len(), PositionError::POSITION_NOT_FOUND); + assert( + offsetted_index != 0 && offsetted_index <= positions.len(), + PositionError::POSITION_NOT_FOUND + ); let index = offsetted_index - 1; // Replace the value at `index` by the last position in the list. @@ -1147,19 +1219,25 @@ mod DataStore { // Withdrawal related functions. // ************************************************************************* - fn get_withdrawal(self: @ContractState, key: felt252) -> Option { + fn get_withdrawal(self: @ContractState, key: felt252) -> Withdrawal { let offsetted_index: usize = self.withdrawal_indexes.read(key); if offsetted_index == 0 { - return Option::None; + return Default::default(); } let withdrawals: List = self.withdrawals.read(); - withdrawals.get(offsetted_index - 1) + let withdrawal_maybe = withdrawals.get(offsetted_index - 1); + match withdrawal_maybe { + Option::Some(withdrawal) => { withdrawal }, + Option::None => { Default::default() } + } } fn set_withdrawal(ref self: ContractState, key: felt252, withdrawal: Withdrawal) { // Check that the caller has permission to set the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); - assert(withdrawal.account != 0.try_into().unwrap(), WithdrawalError::CANT_BE_ZERO); + assert( + withdrawal.account != contract_address_const::<0>(), WithdrawalError::CANT_BE_ZERO + ); let mut withdrawals = self.withdrawals.read(); let mut account_withdrawals = self.account_withdrawals.read(withdrawal.account); @@ -1187,7 +1265,10 @@ mod DataStore { self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); let offsetted_index: usize = self.withdrawal_indexes.read(key); let mut withdrawals = self.withdrawals.read(); - assert(offsetted_index <= withdrawals.len(), WithdrawalError::NOT_FOUND); + assert( + offsetted_index != 0 && offsetted_index <= withdrawals.len(), + WithdrawalError::NOT_FOUND + ); let index = offsetted_index - 1; // Replace the value at `index` by the last withdrawal in the list. @@ -1274,19 +1355,23 @@ mod DataStore { // Deposit related functions. // ************************************************************************* - fn get_deposit(self: @ContractState, key: felt252) -> Option { + fn get_deposit(self: @ContractState, key: felt252) -> Deposit { let offsetted_index: usize = self.deposit_indexes.read(key); if offsetted_index == 0 { - return Option::None; + return Default::default(); } let deposits: List = self.deposits.read(); - deposits.get(offsetted_index - 1) + let deposit_maybe = deposits.get(offsetted_index - 1); + match deposit_maybe { + Option::Some(deposit) => { deposit }, + Option::None => { Default::default() } + } } fn set_deposit(ref self: ContractState, key: felt252, deposit: Deposit) { // Check that the caller has permission to set the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); - assert(deposit.account != 0.try_into().unwrap(), DepositError::CANT_BE_ZERO); + assert(deposit.account != contract_address_const::<0>(), DepositError::CANT_BE_ZERO); let mut deposits = self.deposits.read(); let mut account_deposits = self.account_deposits.read(deposit.account); @@ -1313,7 +1398,10 @@ mod DataStore { self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); let offsetted_index: usize = self.deposit_indexes.read(key); let mut deposits = self.deposits.read(); - assert(offsetted_index <= deposits.len(), DepositError::DEPOSIT_NOT_FOUND); + assert( + offsetted_index != 0 && offsetted_index <= deposits.len(), + DepositError::DEPOSIT_NOT_FOUND + ); let index = offsetted_index - 1; // Replace the value at `index` by the last deposit in the list. @@ -1421,6 +1509,9 @@ mod DataStore { if account_withdrawals.len() == 0 { break; } + if last_key == withdrawal_key { + break; + } account_withdrawals.set(i, last_key); }, Option::None => { @@ -1450,6 +1541,9 @@ mod DataStore { if account_orders.len() == 0 { break; } + if last_key == order_key { + break; + } account_orders.set(i, last_key); }, Option::None => { @@ -1481,6 +1575,9 @@ mod DataStore { if account_deposits.len() == 0 { break; } + if last_key == deposit_key { + break; + } account_deposits.set(i, last_key); }, Option::None => { @@ -1512,6 +1609,9 @@ mod DataStore { if account_positions.len() == 0 { break; } + if last_key == position_key { + break; + } account_positions.set(i, last_key); }, Option::None => { diff --git a/src/data/error.cairo b/src/data/error.cairo index b611bc06..e6365f3a 100644 --- a/src/data/error.cairo +++ b/src/data/error.cairo @@ -1,4 +1,5 @@ mod DataError { const MARKET_NOT_FOUND: felt252 = 'market_not_found'; const MARKET_INDEX_NOT_FOUND: felt252 = 'market_index_not_found'; + const POSITION_NOT_FOUND: felt252 = 'position_not_found'; } diff --git a/src/data/keys.cairo b/src/data/keys.cairo index 6457a260..d47b81b3 100644 --- a/src/data/keys.cairo +++ b/src/data/keys.cairo @@ -694,7 +694,7 @@ fn account_order_list_key(account: ContractAddress) -> felt252 { /// * `token` - The token for the fee. /// # Returns /// * The key for the claimable fee amount. -fn claim_fee_amount_key(market: ContractAddress, token: ContractAddress) -> felt252 { +fn claimable_fee_amount_key(market: ContractAddress, token: ContractAddress) -> felt252 { let mut data = array![]; data.append(claimable_fee_amount()); data.append(market.into()); @@ -709,7 +709,7 @@ fn claim_fee_amount_key(market: ContractAddress, token: ContractAddress) -> felt /// * `account` - The account that can claim the ui fee. /// # Returns /// * The key for the claimable ui fee amount. -fn claim_ui_fee_amount_key(market: ContractAddress, token: ContractAddress) -> felt252 { +fn claimable_ui_fee_amount_key(market: ContractAddress, token: ContractAddress) -> felt252 { let mut data = array![]; data.append(claimable_ui_fee_amount()); data.append(market.into()); @@ -724,7 +724,7 @@ fn claim_ui_fee_amount_key(market: ContractAddress, token: ContractAddress) -> f /// * `account` - The account that can claim the ui fee. /// # Returns /// * The key for the claimable ui fee amount. -fn claim_ui_fee_amount_for_account_key( +fn claimable_ui_fee_amount_for_account_key( market: ContractAddress, token: ContractAddress, account: ContractAddress ) -> felt252 { let mut data = array![]; @@ -743,8 +743,7 @@ fn claim_ui_fee_amount_for_account_key( fn deposit_gas_limit_key(single_token: bool) -> felt252 { let mut data = array![]; data.append(deposit_gas_limit()); - // TODO: Replace by `single_token.into()` once upgrading to next version of Cairo. - data.append(bool_to_felt252(single_token)); + data.append(single_token.into()); poseidon_hash_span(data.span()) } @@ -1447,13 +1446,13 @@ fn claimable_collateral_amount_key(market: ContractAddress, token: ContractAddre /// * `time_key` - The time key for the claimable amount. /// * `account` - The account address. fn claimable_collateral_amount_for_account_key( - market: ContractAddress, token: ContractAddress, time_key: u128, account: ContractAddress + market: ContractAddress, token: ContractAddress, time_key: u256, account: ContractAddress ) -> felt252 { let mut data = array![]; data.append(claimable_collateral_amount()); data.append(market.into()); data.append(token.into()); - data.append(time_key.into()); + data.append(time_key.try_into().expect('u256 into felt failed')); data.append(account.into()); poseidon_hash_span(data.span()) } @@ -1464,13 +1463,13 @@ fn claimable_collateral_amount_for_account_key( /// * `token` - The token address. /// * `time_key` - The time key for the claimable amount. fn claimable_collateral_factor_key( - market: ContractAddress, token: ContractAddress, time_key: felt252 + market: ContractAddress, token: ContractAddress, time_key: u256 ) -> felt252 { let mut data = array![]; data.append(claimable_collateral_factor()); data.append(market.into()); data.append(token.into()); - data.append(time_key); + data.append(time_key.try_into().expect('u256 into felt failed')); poseidon_hash_span(data.span()) } @@ -1481,13 +1480,13 @@ fn claimable_collateral_factor_key( /// * `time_key` - The time key for the claimable amount. /// * `account` - The account address. fn claimable_collateral_factor_for_account_key( - market: ContractAddress, token: ContractAddress, time_key: felt252, account: ContractAddress + market: ContractAddress, token: ContractAddress, time_key: u256, account: ContractAddress ) -> felt252 { let mut data = array![]; data.append(claimable_collateral_factor()); data.append(market.into()); data.append(token.into()); - data.append(time_key); + data.append(time_key.try_into().expect('u256 into felt failed')); data.append(account.into()); poseidon_hash_span(data.span()) } @@ -1499,13 +1498,13 @@ fn claimable_collateral_factor_for_account_key( /// * `time_key` - The time key for the claimable amount. /// * `account` - The account address. fn claimed_collateral_amount_key( - market: ContractAddress, token: ContractAddress, time_key: felt252, account: ContractAddress + market: ContractAddress, token: ContractAddress, time_key: u256, account: ContractAddress ) -> felt252 { let mut data = array![]; data.append(claimed_collateral_amount()); data.append(market.into()); data.append(token.into()); - data.append(time_key); + data.append(time_key.try_into().expect('u256 into felt failed')); data.append(account.into()); poseidon_hash_span(data.span()) } diff --git a/src/deposit/deposit.cairo b/src/deposit/deposit.cairo index 114a3d16..bfb8b1fe 100644 --- a/src/deposit/deposit.cairo +++ b/src/deposit/deposit.cairo @@ -1,5 +1,5 @@ // Core Lib imports -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; // Satoru imports use satoru::utils::store_arrays::StoreContractAddressArray; @@ -29,31 +29,31 @@ struct Deposit { /// The short token swap path. short_token_swap_path: Span32, /// The amount of long tokens to deposit. - initial_long_token_amount: u128, + initial_long_token_amount: u256, /// The amount of short tokens to deposit. - initial_short_token_amount: u128, + initial_short_token_amount: u256, /// The minimum acceptable number of liquidity tokens. - min_market_tokens: u128, + min_market_tokens: u256, /// The block that the deposit was last updated at sending funds back to the user in case the deposit gets cancelled. updated_at_block: u64, /// The execution fee for keepers. - execution_fee: u128, + execution_fee: u256, /// The gas limit for the callback contract. /// TODO: investigate how we want to handle callback and gas limit for Starknet contracts. - callback_gas_limit: u128, + callback_gas_limit: u256, } impl DefaultDeposit of Default { fn default() -> Deposit { Deposit { key: 0, - account: 0.try_into().unwrap(), - receiver: 0.try_into().unwrap(), - callback_contract: 0.try_into().unwrap(), - ui_fee_receiver: 0.try_into().unwrap(), - market: 0.try_into().unwrap(), - initial_long_token: 0.try_into().unwrap(), - initial_short_token: 0.try_into().unwrap(), + account: contract_address_const::<0>(), + receiver: contract_address_const::<0>(), + callback_contract: contract_address_const::<0>(), + ui_fee_receiver: contract_address_const::<0>(), + market: contract_address_const::<0>(), + initial_long_token: contract_address_const::<0>(), + initial_short_token: contract_address_const::<0>(), long_token_swap_path: Array32Trait::::span32(@ArrayTrait::new()), short_token_swap_path: Array32Trait::::span32(@ArrayTrait::new()), initial_long_token_amount: 0, diff --git a/src/deposit/deposit_utils.cairo b/src/deposit/deposit_utils.cairo index 7e2b954c..8115fa03 100644 --- a/src/deposit/deposit_utils.cairo +++ b/src/deposit/deposit_utils.cairo @@ -8,6 +8,8 @@ use starknet::ContractAddress; use starknet::info::get_block_number; use result::ResultTrait; +use satoru::utils::traits::ContractAddressDefault; +use traits::Default; // Local imports. use satoru::utils::{ @@ -25,7 +27,7 @@ use satoru::callback::callback_utils::{validate_callback_gas_limit, after_deposi use satoru::nonce::nonce_utils; use satoru::token::token_utils; use starknet::contract_address::ContractAddressZeroable; -use satoru::event::event_utils::EventLogData; +use satoru::event::event_utils::LogData; /// Helps with deposit creation. #[derive(Drop, starknet::Store, Serde)] @@ -47,11 +49,11 @@ struct CreateDepositParams { /// The swap path into markets for the short token. short_token_swap_path: Span32, /// The minimum acceptable number of liquidity tokens. - min_market_tokens: u128, + min_market_tokens: u256, /// The execution fee for keepers. - execution_fee: u128, + execution_fee: u256, /// The gas limit for the callback_contract. - callback_gas_limit: u128, + callback_gas_limit: u256, } @@ -148,18 +150,14 @@ fn cancel_deposit( deposit_vault: IDepositVaultDispatcher, key: felt252, keeper: ContractAddress, - mut starting_gas: u128, + mut starting_gas: u256, reason: felt252, reason_bytes: Array ) { starting_gas -= (starknet_utils::sn_gasleft(array![]) / 63); // get deposit info from data_store - let mut deposit = Default::default(); - match data_store.get_deposit(key) { - Option::Some(stored_deposit) => deposit = stored_deposit, - Option::None => panic(array![DepositError::EMPTY_DEPOSIT, key]) - } + let deposit = data_store.get_deposit(key); assert(ContractAddressZeroable::is_non_zero(deposit.account), DepositError::EMPTY_DEPOSIT); assert( @@ -173,21 +171,27 @@ fn cancel_deposit( if deposit.initial_long_token_amount > 0 { deposit_vault .transfer_out( - deposit.initial_long_token, deposit.account, deposit.initial_long_token_amount + deposit_vault.contract_address, + deposit.initial_long_token, + deposit.account, + deposit.initial_long_token_amount ); } if deposit.initial_short_token_amount > 0 { deposit_vault .transfer_out( - deposit.initial_short_token, deposit.account, deposit.initial_short_token_amount + deposit_vault.contract_address, + deposit.initial_short_token, + deposit.account, + deposit.initial_short_token_amount ); } event_emitter.emit_deposit_cancelled(key, reason, reason_bytes.span()); - let event_log_data = EventLogData { cant_be_empty: 0 }; - after_deposit_cancellation(key, deposit, event_log_data, event_emitter); + let mut log_data: LogData = Default::default(); + after_deposit_cancellation(key, deposit, log_data); gas_utils::pay_execution_fee_deposit( data_store, diff --git a/src/deposit/deposit_vault.cairo b/src/deposit/deposit_vault.cairo index 04fe1508..6d31914e 100644 --- a/src/deposit/deposit_vault.cairo +++ b/src/deposit/deposit_vault.cairo @@ -29,7 +29,11 @@ trait IDepositVault { /// * `receiver` - The address of the receiver. /// * `amount` - The amount of tokens to transfer. fn transfer_out( - ref self: TContractState, token: ContractAddress, receiver: ContractAddress, amount: u128, + ref self: TContractState, + sender: ContractAddress, + token: ContractAddress, + receiver: ContractAddress, + amount: u256, ); /// Records a token transfer into the contract. @@ -37,7 +41,17 @@ trait IDepositVault { /// * `token` - The token address to transfer. /// # Returns /// * The amount of tokens transferred. - fn record_transfer_in(ref self: TContractState, token: ContractAddress) -> u128; + fn record_transfer_in(ref self: TContractState, token: ContractAddress) -> u256; + + /// 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: ContractAddress) -> u256; } #[starknet::contract] @@ -50,7 +64,6 @@ mod DepositVault { use core::zeroable::Zeroable; use starknet::{get_caller_address, ContractAddress, contract_address_const}; - use debug::PrintTrait; // Local imports. use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; @@ -80,8 +93,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 }); @@ -91,7 +104,7 @@ mod DepositVault { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl DepositVaultImpl of super::IDepositVault { fn initialize( ref self: ContractState, @@ -105,17 +118,23 @@ mod DepositVault { fn transfer_out( ref self: ContractState, + sender: ContractAddress, token: ContractAddress, receiver: ContractAddress, - amount: u128, + amount: u256, ) { let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); - IStrictBank::transfer_out(ref state, token, receiver, amount); + IStrictBank::transfer_out(ref state, sender, token, receiver, amount); } - fn record_transfer_in(ref self: ContractState, token: ContractAddress) -> u128 { - // TODO - 0 + fn record_transfer_in(ref self: ContractState, token: ContractAddress) -> u256 { + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::record_transfer_in(ref state, token) + } + + fn sync_token_balance(ref self: ContractState, token: ContractAddress) -> u256 { + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::sync_token_balance(ref state, token) } } } diff --git a/src/deposit/error.cairo b/src/deposit/error.cairo index 3a936f54..8d4fe9c8 100644 --- a/src/deposit/error.cairo +++ b/src/deposit/error.cairo @@ -4,4 +4,19 @@ 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'; + + + fn MIN_MARKET_TOKENS(received: u256, expected: u256) { + let mut data = array!['invalid swap output token']; + data.append(received.try_into().expect('u256 into felt failed')); + data.append(expected.try_into().expect('u256 into felt failed')); + panic(data) + } + + fn INVALID_POOL_VALUE_FOR_DEPOSIT(pool_value: u256) { + let mut data = array!['invalid pool value for deposit']; + data.append(pool_value.try_into().expect('u256 into felt failed')); + panic(data) + } } diff --git a/src/deposit/execute_deposit_utils.cairo b/src/deposit/execute_deposit_utils.cairo index 519fb7ef..05f3b80e 100644 --- a/src/deposit/execute_deposit_utils.cairo +++ b/src/deposit/execute_deposit_utils.cairo @@ -8,16 +8,38 @@ use starknet::ContractAddress; 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, LogDataTrait, ContractAddressDictValue, I256252DictValue}; +use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; +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}, i256::{i256, i256_new, i256_neg}, precision, span32::Span32, + starknet_utils::{sn_gasleft, sn_gasprice} +}; +use satoru::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; /// Struct used in executeDeposit to avoid stack too deep errors #[derive(Drop, Serde)] @@ -39,7 +61,7 @@ struct ExecuteDepositParams { /// `keeper` the address of the keeper executing the deposit. keeper: ContractAddress, /// `starting_gas` the starting amount of gas. - starting_gas: u128 + starting_gas: u256 } /// Struct used in executeDeposit to avoid stack too deep errors @@ -62,47 +84,406 @@ struct _ExecuteDepositParams { /// `token_out_price` Price of token_out. token_out_price: Price, /// `amount` Amount of token_in. - amount: u128, + amount: u256, /// `price_impact_usd` Price impact in USD. - price_impact_usd: u128 + price_impact_usd: i256 } +#[derive(Drop, Default)] struct ExecuteDepositCache { - long_token_amount: u128, - short_token_amount: u128, - long_token_usd: u128, - short_token_usd: u128, - received_market_tokens: u128, - price_impact_usd: i128 + long_token_amount: u256, + short_token_amount: u256, + long_token_usd: u256, + short_token_usd: u256, + received_market_tokens: u256, + price_impact_usd: i256 } /// Executes a deposit. /// # 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); + 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, + ); + + if cache.long_token_amount == 0 && cache.short_token_amount == 0 { + panic_with_felt252(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, true), + } + ); + + if cache.long_token_amount > 0 { + let mut _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_ival( + cache.price_impact_usd, + cache.long_token_usd, + cache.long_token_usd + cache.short_token_usd + ) + }; + + cache.received_market_tokens += execute_deposit_helper(@params, ref _params); + } + + if cache.short_token_amount > 0 { + let mut _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_ival( + cache.price_impact_usd, + cache.short_token_usd, + cache.long_token_usd + cache.short_token_usd + ) + }; + + cache.received_market_tokens += execute_deposit_helper(@params, ref _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_check(params.data_store, market); + + (params.event_emitter) + .emit_deposit_executed( + params.key, + cache.long_token_amount, + cache.short_token_amount, + cache.received_market_tokens, + ); + // let mut event_data: LogData = Default::default(); + // event_data.uint_dict.insert_single('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. -#[inline(always)] -fn _execute_deposit(params: ExecuteDepositParams, _params: _ExecuteDepositParams) -> u128 { - //TODO - 0 +/// * `params` - @ExecuteDepositParams. +/// * `_params` - @_ExecuteDepositParams. +fn execute_deposit_helper( + params: @ExecuteDepositParams, ref _params: _ExecuteDepositParams +) -> u256 { + // for markets where longToken == shortToken, the price impact factor should be set to zero + // in which case, the priceImpactUsd would always equal zero + let mut fees = get_swap_fees( + *params.data_store, + _params.market.market_token, + _params.amount, + _params.price_impact_usd > Zeroable::zero(), + _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 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, + ); + + if pool_value_info.pool_value < Zeroable::zero() { + DepositError::INVALID_POOL_VALUE_FOR_DEPOSIT(pool_value_info.pool_value.mag); + } + + let mut mint_amount = 0; + let pool_value = to_unsigned(pool_value_info.pool_value); + let market_tokens_supply = market_utils::get_market_token_supply( + IMarketTokenDispatcher { contract_address: _params.market.market_token } + ); + + if pool_value == Zeroable::zero() && market_tokens_supply > 0 { + DepositError::INVALID_POOL_VALUE_FOR_DEPOSIT(pool_value_info.pool_value.mag); + } + + (*params.event_emitter) + .emit_market_pool_value_info( + _params.market.market_token, 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 + + if _params.price_impact_usd > Zeroable::zero() && market_tokens_supply == Zeroable::zero() { + _params.price_impact_usd = i256_new(0, false); + } + + if _params.price_impact_usd > Zeroable::zero() { + // 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 = market_utils::apply_swap_impact_with_cap( + *params.data_store, + *params.event_emitter, + _params.market.market_token, + _params.token_out, + _params.token_out_price, + _params.price_impact_usd, + ); + + // 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( + to_unsigned(positive_impact_amount) * _params.token_out_price.max, + pool_value, + market_tokens_supply, + ); + + market_utils::apply_delta_to_pool_amount( + *params.data_store, + *params.event_emitter, + _params.market, + _params.token_out, + positive_impact_amount + ); + + market_utils::validate_pool_amount(params.data_store, @_params.market, _params.token_out); + } + + if (_params.price_impact_usd < Zeroable::zero()) { + // 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_in, + _params.token_in_price, + _params.price_impact_usd, + ); + + fees.amount_after_fees -= to_unsigned(i256_neg(negative_impact_amount)); + } + + mint_amount += + market_utils::usd_to_market_token_amount( + fees.amount_after_fees * _params.token_in_price.min, pool_value, market_tokens_supply, + ); + + market_utils::apply_delta_to_pool_amount( + *params.data_store, + *params.event_emitter, + _params.market, + _params.token_in, + to_signed(fees.amount_after_fees + fees.fee_amount_for_pool, true), + ); + + 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: u256, market: ContractAddress, expected_output_token: ContractAddress, ui_fee_receiver: ContractAddress -) -> u128 { - //TODO - 0 +) -> u256 { + 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_market_token_balance_array(*params.data_store, swap_path_markets); + + output_amount } diff --git a/src/event/event_emitter.cairo b/src/event/event_emitter.cairo old mode 100644 new mode 100755 index fbfe2281..1dd825f0 --- a/src/event/event_emitter.cairo +++ b/src/event/event_emitter.cairo @@ -9,23 +9,16 @@ use starknet::{ContractAddress, ClassHash}; // Local imports. use satoru::deposit::deposit::Deposit; use satoru::withdrawal::withdrawal::Withdrawal; -use satoru::position::position::Position; use satoru::market::market_pool_value_info::MarketPoolValueInfo; use satoru::pricing::swap_pricing_utils::SwapFees; -use satoru::position::position_event_utils::PositionIncreaseParams; -use satoru::position::position_utils::DecreasePositionCollateralValues; -use satoru::order::order::OrderType; +use satoru::position::{ + position::Position, position_event_utils::PositionIncreaseParams, + position_utils::DecreasePositionCollateralValues +}; use satoru::price::price::Price; use satoru::pricing::position_pricing_utils::PositionFees; -use satoru::order::order::{Order, SecondaryOrderType}; -use satoru::utils::span32::{Span32, DefaultSpan32}; -use satoru::utils::i128::{I128Div, I128Mul, I128Serde}; - - -//TODO: OrderCollatDeltaAmountAutoUpdtd must be renamed back to OrderCollateralDeltaAmountAutoUpdated when string will be allowed as event argument -//TODO: AfterWithdrawalCancelError must be renamed back to AfterWithdrawalCancellationError when string will be allowed as event argument -//TODO: CumulativeBorrowingFactorUpdatd must be renamed back to CumulativeBorrowingFactorUpdated when string will be allowed as event argument -//TODO: ClaimableFundingPerSizeUpdatd must be renamed back to ClaimableFundingAmountPerSizeUpdated when string will be allowed as event argument +use satoru::order::order::{Order, SecondaryOrderType, OrderType}; +use satoru::utils::{i256::i256, span32::{Span32, DefaultSpan32}}; // ************************************************************************* // Interface of the `EventEmitter` contract. @@ -38,10 +31,10 @@ trait IEventEmitter { market: ContractAddress, token: ContractAddress, account: ContractAddress, - time_key: u128, - delta: u128, - next_value: u128, - next_pool_value: u128, + time_key: u256, + delta: u256, + next_value: u256, + next_pool_value: u256, ); /// Emits the `ClaimableFundingUpdated` event. @@ -50,14 +43,14 @@ trait IEventEmitter { market: ContractAddress, token: ContractAddress, account: ContractAddress, - delta: u128, - next_value: u128, - next_pool_value: u128, + delta: u256, + next_value: u256, + next_pool_value: u256, ); /// Emits the `PositionImpactPoolAmountUpdated` event. fn emit_position_impact_pool_amount_updated( - ref self: TContractState, market: ContractAddress, delta: u128, next_value: u128, + ref self: TContractState, market: ContractAddress, delta: i256, next_value: u256, ); /// Emits the `SwapImpactPoolAmountUpdated` event. @@ -65,8 +58,8 @@ trait IEventEmitter { ref self: TContractState, market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128, + delta: i256, + next_value: u256, ); /// Emits the `MarketCreated` event. @@ -93,8 +86,8 @@ trait IEventEmitter { ref self: TContractState, market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128, + delta: u256, + next_value: u256, fee_type: felt252 ); @@ -104,9 +97,9 @@ trait IEventEmitter { ui_fee_receiver: ContractAddress, market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128, - next_pool_value: u128, + delta: u256, + next_value: u256, + next_pool_value: u256, fee_type: felt252 ); @@ -115,7 +108,7 @@ trait IEventEmitter { ref self: TContractState, market: ContractAddress, receiver: ContractAddress, - fee_amount: u128 + fee_amount: u256 ); /// Emits the `UiFeesClaimed` event. @@ -124,21 +117,20 @@ trait IEventEmitter { ui_fee_receiver: ContractAddress, market: ContractAddress, receiver: ContractAddress, - fee_amount: u128, - next_pool_value: u128 + fee_amount: u256, + next_pool_value: u256 ); /// Emits the `DepositCreated` event. - #[inline(always)] fn emit_deposit_created(ref self: TContractState, key: felt252, deposit: Deposit); /// Emits the `DepositExecuted` event. fn emit_deposit_executed( ref self: TContractState, key: felt252, - long_token_amount: u128, - short_token_amount: u128, - received_market_tokens: u128, + long_token_amount: u256, + short_token_amount: u256, + received_market_tokens: u256, ); /// Emits the `DepositCancelled` event. @@ -147,7 +139,6 @@ trait IEventEmitter { ); /// Emits the `WithdrawalCreated` event. - #[inline(always)] fn emit_withdrawal_created(ref self: TContractState, key: felt252, withdrawal: Withdrawal); /// Emits the `WithdrawalExecuted` event. @@ -159,18 +150,16 @@ trait IEventEmitter { ); /// Emits the `PositionIncrease` event. - #[inline(always)] fn emit_position_increase(ref self: TContractState, params: PositionIncreaseParams); /// Emits the `PositionDecrease` event. - #[inline(always)] fn emit_position_decrease( ref self: TContractState, order_key: felt252, position_key: felt252, position: Position, - size_delta_usd: u128, - collateral_delta_amount: u128, + size_delta_usd: u256, + collateral_delta_amount: u256, order_type: OrderType, values: DecreasePositionCollateralValues, index_token_price: Price, @@ -181,9 +170,9 @@ trait IEventEmitter { fn emit_insolvent_close_info( ref self: TContractState, order_key: felt252, - position_collateral_amount: u128, - base_pnl_usd: u128, - remaining_cost_usd: u128 + position_collateral_amount: u256, + base_pnl_usd: i256, + remaining_cost_usd: u256 ); /// Emits the `InsufficientFundingFeePayment` event. @@ -191,39 +180,36 @@ trait IEventEmitter { ref self: TContractState, market: ContractAddress, token: ContractAddress, - expected_amount: u128, - amount_paid_in_collateral_token: u128, - amount_paid_in_secondary_output_token: u128 + expected_amount: u256, + amount_paid_in_collateral_token: u256, + amount_paid_in_secondary_output_token: u256 ); /// Emits the `PositionFeesCollected` event. - #[inline(always)] fn emit_position_fees_collected( ref self: TContractState, order_key: felt252, position_key: felt252, market: ContractAddress, collateral_token: ContractAddress, - trade_size_usd: u128, + trade_size_usd: u256, is_increase: bool, fees: PositionFees ); /// Emits the `PositionFeesInfo` event. - #[inline(always)] fn emit_position_fees_info( ref self: TContractState, order_key: felt252, position_key: felt252, market: ContractAddress, collateral_token: ContractAddress, - trade_size_usd: u128, + trade_size_usd: u256, is_increase: bool, fees: PositionFees ); /// Emits the `OrderCreated` event. - #[inline(always)] fn emit_order_created(ref self: TContractState, key: felt252, order: Order); /// Emits the `OrderExecuted` event. @@ -235,23 +221,23 @@ trait IEventEmitter { fn emit_order_updated( ref self: TContractState, key: felt252, - size_delta_usd: u128, - acceptable_price: u128, - trigger_price: u128, - min_output_amount: u128 + size_delta_usd: u256, + acceptable_price: u256, + trigger_price: u256, + min_output_amount: u256 ); /// Emits the `OrderSizeDeltaAutoUpdated` event. fn emit_order_size_delta_auto_updated( - ref self: TContractState, key: felt252, size_delta_usd: u128, next_size_delta_usd: u128 + ref self: TContractState, key: felt252, size_delta_usd: u256, next_size_delta_usd: u256 ); - /// Emits the `OrderCollatDeltaAmountAutoUpdtd` event. + /// Emits the `OrderCollateralDeltaAmountAutoUpdated` event. fn emit_order_collateral_delta_amount_auto_updated( ref self: TContractState, key: felt252, - collateral_delta_amount: u128, - next_collateral_delta_amount: u128 + collateral_delta_amount: u256, + next_collateral_delta_amount: u256 ); /// Emits the `OrderCancelled` event. @@ -270,9 +256,9 @@ trait IEventEmitter { market: ContractAddress, token: ContractAddress, affiliate: ContractAddress, - delta: u128, - next_value: u128, - next_pool_value: u128 + delta: u256, + next_value: u256, + next_pool_value: u256 ); /// Emits the `AffiliateRewardClaimed` event. @@ -282,8 +268,8 @@ trait IEventEmitter { token: ContractAddress, affiliate: ContractAddress, receiver: ContractAddress, - amount: u128, - next_pool_value: u128 + amount: u256, + next_pool_value: u256 ); /// Emits the `AfterDepositExecutionError` event. @@ -299,7 +285,7 @@ trait IEventEmitter { ref self: TContractState, key: felt252, withdrawal: Withdrawal ); - /// Emits the `AfterWithdrawalCancelError` event. + /// Emits the `AfterWithdrawalCancellationError` event. fn emit_after_withdrawal_cancellation_error( ref self: TContractState, key: felt252, withdrawal: Withdrawal ); @@ -319,7 +305,7 @@ trait IEventEmitter { market: ContractAddress, is_long: bool, pnl_to_pool_factor: felt252, - max_pnl_factor: u128, + max_pnl_factor: u256, should_enable_adl: bool, ); @@ -340,7 +326,7 @@ trait IEventEmitter { /// Emits the `SetUint` event. fn emit_set_uint( - ref self: TContractState, key: felt252, data_bytes: Span, value: u128 + ref self: TContractState, key: felt252, data_bytes: Span, value: u256 ); /// Emits the `SetInt` event. @@ -404,9 +390,9 @@ trait IEventEmitter { action_key: felt252, token: ContractAddress, price_feed: ContractAddress, - price_feed_multiplier: u128, - price_feed_heartbeat_duration: u128, - stable_price: u128 + price_feed_multiplier: u256, + price_feed_heartbeat_duration: u256, + stable_price: u256 ); /// Emits the `SetPriceFeed` event. @@ -415,9 +401,9 @@ trait IEventEmitter { action_key: felt252, token: ContractAddress, price_feed: ContractAddress, - price_feed_multiplier: u128, - price_feed_heartbeat_duration: u128, - stable_price: u128 + price_feed_multiplier: u256, + price_feed_heartbeat_duration: u256, + stable_price: u256 ); /// Emits the `SignalPendingAction` event. @@ -432,21 +418,20 @@ trait IEventEmitter { /// Emits the `KeeperExecutionFee` event. fn emit_keeper_execution_fee( - ref self: TContractState, keeper: ContractAddress, execution_fee_amount: u128 + ref self: TContractState, keeper: ContractAddress, execution_fee_amount: u256 ); /// Emits the `ExecutionFeeRefund` event. fn emit_execution_fee_refund( - ref self: TContractState, receiver: ContractAddress, refund_fee_amount: u128 + ref self: TContractState, receiver: ContractAddress, refund_fee_amount: u256 ); /// Emits the `MarketPoolValueInfo` event. - #[inline(always)] fn emit_market_pool_value_info( ref self: TContractState, market: ContractAddress, market_pool_value_info: MarketPoolValueInfo, - market_tokens_supply: u128 + market_tokens_supply: u256 ); /// Emits the `PoolAmountUpdated` event. @@ -454,8 +439,8 @@ trait IEventEmitter { ref self: TContractState, market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 ); /// Emits the `OpenInterestInTokensUpdated` event. @@ -464,8 +449,8 @@ trait IEventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 ); /// Emits the `OpenInterestUpdated` event. @@ -474,8 +459,8 @@ trait IEventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 ); /// Emits the `VirtualSwapInventoryUpdated` event. @@ -484,8 +469,8 @@ trait IEventEmitter { market: ContractAddress, is_long_token: bool, virtual_market_id: felt252, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 ); /// Emits the `VirtualPositionInventoryUpdated` event. @@ -493,8 +478,8 @@ trait IEventEmitter { ref self: TContractState, token: ContractAddress, virtual_token_id: felt252, - delta: u128, - next_value: u128 + delta: i256, + next_value: i256 ); /// Emits the `CollateralSumUpdated` event. @@ -503,17 +488,17 @@ trait IEventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 ); - /// Emits the `CumulativeBorrowingFactorUpdatd` event. + /// Emits the `CumulativeBorrowingFactorUpdated` event. fn emit_cumulative_borrowing_factor_updated( ref self: TContractState, market: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: u256, + next_value: u256 ); /// Emits the `FundingFeeAmountPerSizeUpdated` event. @@ -522,29 +507,29 @@ trait IEventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: u256, + next_value: u256 ); - /// Emits the `ClaimableFundingPerSizeUpdatd` event. + /// Emits the `ClaimableFundingAmountPerSizeUpdated` event. fn emit_claimable_funding_amount_per_size_updated( ref self: TContractState, market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: u256, + next_value: u256 ); /// Emits the `FundingFeesClaimed` event. - fn emit_founding_fees_claimed( + fn emit_funding_fees_claimed( ref self: TContractState, market: ContractAddress, token: ContractAddress, account: ContractAddress, receiver: ContractAddress, - amount: u128, - next_pool_value: u128 + amount: u256, + next_pool_value: u256 ); /// Emits the `CollateralClaimed` event. @@ -554,22 +539,21 @@ trait IEventEmitter { token: ContractAddress, account: ContractAddress, receiver: ContractAddress, - time_key: u128, - amount: u128, - next_pool_value: u128 + time_key: u256, + amount: u256, + next_pool_value: u256 ); - /// Emits the `UiFeeFactorUpdated` event. fn emit_ui_fee_factor_updated( - ref self: TContractState, account: ContractAddress, ui_fee_factor: u128 + ref self: TContractState, account: ContractAddress, ui_fee_factor: u256 ); /// Emits the `OraclePriceUpdate` event. fn emit_oracle_price_update( ref self: TContractState, token: ContractAddress, - min_price: u128, - max_price: u128, + min_price: u256, + max_price: u256, is_price_feed: bool ); @@ -590,22 +574,21 @@ trait IEventEmitter { receiver: ContractAddress, token_in: ContractAddress, token_out: ContractAddress, - token_in_price: u128, - token_out_price: u128, - amount_in: u128, - amount_in_after_fees: u128, - amount_out: u128, - price_impact_usd: i128, - price_impact_amount: i128 + token_in_price: u256, + token_out_price: u256, + amount_in: u256, + amount_in_after_fees: u256, + amount_out: u256, + price_impact_usd: i256, + price_impact_amount: i256 ); /// Emits the `SwapFeesCollected` event. - #[inline(always)] fn emit_swap_fees_collected( ref self: TContractState, market: ContractAddress, token: ContractAddress, - token_price: u128, + token_price: u256, action: felt252, fees: SwapFees ); @@ -613,8 +596,8 @@ trait IEventEmitter { fn emit_oracle_price_updated( ref self: TContractState, token: ContractAddress, - min_price: u128, - max_price: u128, + min_price: u256, + max_price: u256, is_price_feed: bool ); @@ -625,13 +608,13 @@ trait IEventEmitter { ); fn emit_set_tier( - ref self: TContractState, tier_id: u128, total_rebate: u128, discount_share: u128 + ref self: TContractState, tier_id: u256, total_rebate: u256, discount_share: u256 ); - fn emit_set_referrer_tier(ref self: TContractState, referrer: ContractAddress, tier_id: u128); + fn emit_set_referrer_tier(ref self: TContractState, referrer: ContractAddress, tier_id: u256); fn emit_set_referrer_discount_share( - ref self: TContractState, referrer: ContractAddress, discount_share: u128 + ref self: TContractState, referrer: ContractAddress, discount_share: u256 ); fn emit_register_code(ref self: TContractState, account: ContractAddress, code: felt252); @@ -672,7 +655,7 @@ mod EventEmitter { use satoru::pricing::position_pricing_utils::PositionFees; use satoru::order::order::{Order, SecondaryOrderType}; use satoru::utils::span32::{Span32, DefaultSpan32}; - use satoru::utils::i128::{I128Div, I128Mul, I128Serde}; + use satoru::utils::i256::i256; // ************************************************************************* // STORAGE @@ -706,7 +689,7 @@ mod EventEmitter { OrderExecuted: OrderExecuted, OrderUpdated: OrderUpdated, OrderSizeDeltaAutoUpdated: OrderSizeDeltaAutoUpdated, - OrderCollatDeltaAmountAutoUpdtd: OrderCollatDeltaAmountAutoUpdtd, + OrderCollateralDeltaAmountAutoUpdated: OrderCollateralDeltaAmountAutoUpdated, OrderCancelled: OrderCancelled, OrderFrozen: OrderFrozen, PositionIncrease: PositionIncrease, @@ -720,7 +703,7 @@ mod EventEmitter { AfterDepositExecutionError: AfterDepositExecutionError, AfterDepositCancellationError: AfterDepositCancellationError, AfterWithdrawalExecutionError: AfterWithdrawalExecutionError, - AfterWithdrawalCancelError: AfterWithdrawalCancelError, + AfterWithdrawalCancellationError: AfterWithdrawalCancellationError, AfterOrderExecutionError: AfterOrderExecutionError, AfterOrderCancellationError: AfterOrderCancellationError, AfterOrderFrozenError: AfterOrderFrozenError, @@ -753,9 +736,9 @@ mod EventEmitter { VirtualSwapInventoryUpdated: VirtualSwapInventoryUpdated, VirtualPositionInventoryUpdated: VirtualPositionInventoryUpdated, CollateralSumUpdated: CollateralSumUpdated, - CumulativeBorrowingFactorUpdatd: CumulativeBorrowingFactorUpdatd, + CumulativeBorrowingFactorUpdated: CumulativeBorrowingFactorUpdated, FundingFeeAmountPerSizeUpdated: FundingFeeAmountPerSizeUpdated, - ClaimableFundingPerSizeUpdatd: ClaimableFundingPerSizeUpdatd, + ClaimableFundingAmountPerSizeUpdated: ClaimableFundingAmountPerSizeUpdated, FundingFeesClaimed: FundingFeesClaimed, CollateralClaimed: CollateralClaimed, UiFeeFactorUpdated: UiFeeFactorUpdated, @@ -781,10 +764,10 @@ mod EventEmitter { market: ContractAddress, token: ContractAddress, account: ContractAddress, - time_key: u128, - delta: u128, - next_value: u128, - next_pool_value: u128, + time_key: u256, + delta: u256, + next_value: u256, + next_pool_value: u256, } #[derive(Drop, starknet::Event)] @@ -792,24 +775,24 @@ mod EventEmitter { market: ContractAddress, token: ContractAddress, account: ContractAddress, - delta: u128, - next_value: u128, - next_pool_value: u128, + delta: u256, + next_value: u256, + next_pool_value: u256, } #[derive(Drop, starknet::Event)] struct PositionImpactPoolAmountUpdated { market: ContractAddress, - delta: u128, - next_value: u128, + delta: i256, + next_value: u256, } #[derive(Drop, starknet::Event)] struct SwapImpactPoolAmountUpdated { market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128, + delta: i256, + next_value: u256, } #[derive(Drop, starknet::Event)] @@ -833,8 +816,8 @@ mod EventEmitter { struct ClaimableFeeAmountUpdated { market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128, + delta: u256, + next_value: u256, fee_type: felt252, } @@ -843,9 +826,9 @@ mod EventEmitter { ui_fee_receiver: ContractAddress, market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128, - next_pool_value: u128, + delta: u256, + next_value: u256, + next_pool_value: u256, fee_type: felt252, } @@ -853,7 +836,7 @@ mod EventEmitter { struct FeesClaimed { market: ContractAddress, receiver: ContractAddress, - fee_amount: u128, + fee_amount: u256, } #[derive(Drop, starknet::Event)] @@ -861,8 +844,8 @@ mod EventEmitter { ui_fee_receiver: ContractAddress, market: ContractAddress, receiver: ContractAddress, - fee_amount: u128, - next_pool_value: u128, + fee_amount: u256, + next_pool_value: u256, } #[derive(Drop, starknet::Event)] @@ -876,20 +859,20 @@ mod EventEmitter { initial_short_token: ContractAddress, long_token_swap_path: Span32, short_token_swap_path: Span32, - initial_long_token_amount: u128, - initial_short_token_amount: u128, - min_market_tokens: u128, + initial_long_token_amount: u256, + initial_short_token_amount: u256, + min_market_tokens: u256, updated_at_block: u64, - execution_fee: u128, - callback_gas_limit: u128, + execution_fee: u256, + callback_gas_limit: u256, } #[derive(Drop, starknet::Event)] struct DepositExecuted { key: felt252, - long_token_amount: u128, - short_token_amount: u128, - received_market_tokens: u128, + long_token_amount: u256, + short_token_amount: u256, + received_market_tokens: u256, } #[derive(Drop, starknet::Event)] @@ -906,12 +889,14 @@ mod EventEmitter { receiver: ContractAddress, callback_contract: ContractAddress, market: ContractAddress, - market_token_amount: u128, - min_long_token_amount: u128, - min_short_token_amount: u128, + long_token_swap_path: Span32, + short_token_swap_path: Span32, + market_token_amount: u256, + min_long_token_amount: u256, + min_short_token_amount: u256, updated_at_block: u64, - execution_fee: u128, - callback_gas_limit: u128, + execution_fee: u256, + callback_gas_limit: u256, } #[derive(Drop, starknet::Event)] @@ -931,24 +916,24 @@ mod EventEmitter { account: ContractAddress, market: ContractAddress, collateral_token: ContractAddress, - size_in_usd: u128, - size_in_tokens: u128, - collateral_amount: u128, - borrowing_factor: u128, - funding_fee_amount_per_pize: u128, - long_token_claimable_funding_amount_per_size: u128, - short_token_claimable_funding_amount_per_size: u128, - execution_price: u128, - index_token_price_max: u128, - index_token_price_min: u128, - collateral_token_price_max: u128, - collateral_token_price_min: u128, - size_delta_usd: u128, - size_delta_in_tokens: u128, + size_in_usd: u256, + size_in_tokens: u256, + collateral_amount: u256, + borrowing_factor: u256, + funding_fee_amount_per_size: u256, + long_token_claimable_funding_amount_per_size: u256, + short_token_claimable_funding_amount_per_size: u256, + execution_price: u256, + index_token_price_max: u256, + index_token_price_min: u256, + collateral_token_price_max: u256, + collateral_token_price_min: u256, + size_delta_usd: u256, + size_delta_in_tokens: u256, order_type: OrderType, - collateral_delta_amount: u128, - price_impact_usd: u128, - price_impact_amount: u128, + collateral_delta_amount: i256, + price_impact_usd: i256, + price_impact_amount: i256, is_long: bool, order_key: felt252, position_key: felt252 @@ -959,26 +944,26 @@ mod EventEmitter { account: ContractAddress, market: ContractAddress, collateral_token: ContractAddress, - size_in_usd: u128, - size_in_tokens: u128, - collateral_amount: u128, - borrowing_factor: u128, - funding_fee_amount_per_pize: u128, - long_token_claimable_funding_amount_per_size: u128, - short_token_claimable_funding_amount_per_size: u128, - execution_price: u128, - index_token_price_max: u128, - index_token_price_min: u128, - collateral_token_price_max: u128, - collateral_token_price_min: u128, - size_delta_usd: u128, - size_delta_in_tokens: u128, - collateral_delta_amount: u128, - price_impact_diff_usd: u128, + size_in_usd: u256, + size_in_tokens: u256, + collateral_amount: u256, + borrowing_factor: u256, + funding_fee_amount_per_size: u256, + long_token_claimable_funding_amount_per_size: u256, + short_token_claimable_funding_amount_per_size: u256, + execution_price: u256, + index_token_price_max: u256, + index_token_price_min: u256, + collateral_token_price_max: u256, + collateral_token_price_min: u256, + size_delta_usd: u256, + size_delta_in_tokens: u256, + collateral_delta_amount: u256, + price_impact_diff_usd: u256, order_type: OrderType, - price_impact_usd: i128, - base_pnl_usd: i128, - uncapped_base_pnl_usd: i128, + price_impact_usd: i256, + base_pnl_usd: i256, + uncapped_base_pnl_usd: i256, is_long: bool, order_key: felt252, position_key: felt252 @@ -987,18 +972,18 @@ mod EventEmitter { #[derive(Drop, starknet::Event)] struct InsolventClose { order_key: felt252, - position_collateral_amount: u128, - base_pnl_usd: u128, - remaining_cost_usd: u128 + position_collateral_amount: u256, + base_pnl_usd: i256, + remaining_cost_usd: u256 } #[derive(Drop, starknet::Event)] struct InsufficientFundingFeePayment { market: ContractAddress, token: ContractAddress, - expected_amount: u128, - amount_paid_in_collateral_token: u128, - amount_paid_in_secondary_output_token: u128 + expected_amount: u256, + amount_paid_in_collateral_token: u256, + amount_paid_in_secondary_output_token: u256 } #[derive(Drop, starknet::Event)] @@ -1011,34 +996,34 @@ mod EventEmitter { affiliate: ContractAddress, trader: ContractAddress, ui_fee_receiver: ContractAddress, - collateral_token_price_min: u128, - collateral_token_price_max: u128, - trade_size_usd: u128, - total_rebate_factor: u128, - trader_discount_factor: u128, - total_rebate_amount: u128, - trader_discount_amount: u128, - affiliate_reward_amount: u128, - funding_fee_amount: u128, - claimable_long_token_amount: u128, - claimable_short_token_amount: u128, - latest_funding_fee_amount_per_size: u128, - latest_long_token_claimable_funding_amount_per_size: u128, - latest_short_token_claimable_funding_amount_per_size: u128, - borrowing_fee_usd: u128, - borrowing_fee_amount: u128, - borrowing_fee_receiver_factor: u128, - borrowing_fee_amount_for_fee_receiver: u128, - position_fee_factor: u128, - protocol_fee_amount: u128, - position_fee_receiver_factor: u128, - fee_receiver_amount: u128, - fee_amount_for_pool: u128, - position_fee_amount_for_pool: u128, - position_fee_amount: u128, - total_cost_amount: u128, - ui_fee_receiver_factor: u128, - ui_fee_amount: u128, + collateral_token_price_min: u256, + collateral_token_price_max: u256, + trade_size_usd: u256, + total_rebate_factor: u256, + trader_discount_factor: u256, + total_rebate_amount: u256, + trader_discount_amount: u256, + affiliate_reward_amount: u256, + funding_fee_amount: u256, + claimable_long_token_amount: u256, + claimable_short_token_amount: u256, + latest_funding_fee_amount_per_size: u256, + latest_long_token_claimable_funding_amount_per_size: u256, + latest_short_token_claimable_funding_amount_per_size: u256, + borrowing_fee_usd: u256, + borrowing_fee_amount: u256, + borrowing_fee_receiver_factor: u256, + borrowing_fee_amount_for_fee_receiver: u256, + position_fee_factor: u256, + protocol_fee_amount: u256, + position_fee_receiver_factor: u256, + fee_receiver_amount: u256, + fee_amount_for_pool: u256, + position_fee_amount_for_pool: u256, + position_fee_amount: u256, + total_cost_amount: u256, + ui_fee_receiver_factor: u256, + ui_fee_amount: u256, is_increase: bool } @@ -1052,34 +1037,34 @@ mod EventEmitter { affiliate: ContractAddress, trader: ContractAddress, ui_fee_receiver: ContractAddress, - collateral_token_price_min: u128, - collateral_token_price_max: u128, - trade_size_usd: u128, - total_rebate_factor: u128, - trader_discount_factor: u128, - total_rebate_amount: u128, - trader_discount_amount: u128, - affiliate_reward_amount: u128, - funding_fee_amount: u128, - claimable_long_token_amount: u128, - claimable_short_token_amount: u128, - latest_funding_fee_amount_per_size: u128, - latest_long_token_claimable_funding_amount_per_size: u128, - latest_short_token_claimable_funding_amount_per_size: u128, - borrowing_fee_usd: u128, - borrowing_fee_amount: u128, - borrowing_fee_receiver_factor: u128, - borrowing_fee_amount_for_fee_receiver: u128, - position_fee_factor: u128, - protocol_fee_amount: u128, - position_fee_receiver_factor: u128, - fee_receiver_amount: u128, - fee_amount_for_pool: u128, - position_fee_amount_for_pool: u128, - position_fee_amount: u128, - total_cost_amount: u128, - ui_fee_receiver_factor: u128, - ui_fee_amount: u128, + collateral_token_price_min: u256, + collateral_token_price_max: u256, + trade_size_usd: u256, + total_rebate_factor: u256, + trader_discount_factor: u256, + total_rebate_amount: u256, + trader_discount_amount: u256, + affiliate_reward_amount: u256, + funding_fee_amount: u256, + claimable_long_token_amount: u256, + claimable_short_token_amount: u256, + latest_funding_fee_amount_per_size: u256, + latest_long_token_claimable_funding_amount_per_size: u256, + latest_short_token_claimable_funding_amount_per_size: u256, + borrowing_fee_usd: u256, + borrowing_fee_amount: u256, + borrowing_fee_receiver_factor: u256, + borrowing_fee_amount_for_fee_receiver: u256, + position_fee_factor: u256, + protocol_fee_amount: u256, + position_fee_receiver_factor: u256, + fee_receiver_amount: u256, + fee_amount_for_pool: u256, + position_fee_amount_for_pool: u256, + position_fee_amount: u256, + total_cost_amount: u256, + ui_fee_receiver_factor: u256, + ui_fee_amount: u256, is_increase: bool } @@ -1098,24 +1083,24 @@ mod EventEmitter { #[derive(Drop, starknet::Event)] struct OrderUpdated { key: felt252, - size_delta_usd: u128, - acceptable_price: u128, - trigger_price: u128, - min_output_amount: u128 + size_delta_usd: u256, + acceptable_price: u256, + trigger_price: u256, + min_output_amount: u256 } #[derive(Drop, starknet::Event)] struct OrderSizeDeltaAutoUpdated { key: felt252, - size_delta_usd: u128, - next_size_delta_usd: u128 + size_delta_usd: u256, + next_size_delta_usd: u256 } #[derive(Drop, starknet::Event)] - struct OrderCollatDeltaAmountAutoUpdtd { + struct OrderCollateralDeltaAmountAutoUpdated { key: felt252, - collateral_delta_amount: u128, - next_collateral_delta_amount: u128 + collateral_delta_amount: u256, + next_collateral_delta_amount: u256 } #[derive(Drop, starknet::Event)] @@ -1137,9 +1122,9 @@ mod EventEmitter { market: ContractAddress, token: ContractAddress, affiliate: ContractAddress, - delta: u128, - next_value: u128, - next_pool_value: u128 + delta: u256, + next_value: u256, + next_pool_value: u256 } #[derive(Drop, starknet::Event)] @@ -1148,8 +1133,8 @@ mod EventEmitter { token: ContractAddress, affiliate: ContractAddress, receiver: ContractAddress, - amount: u128, - next_pool_value: u128 + amount: u256, + next_pool_value: u256 } #[derive(Drop, starknet::Event)] @@ -1171,7 +1156,7 @@ mod EventEmitter { } #[derive(Drop, starknet::Event)] - struct AfterWithdrawalCancelError { + struct AfterWithdrawalCancellationError { key: felt252, withdrawal: Withdrawal, } @@ -1199,7 +1184,7 @@ mod EventEmitter { market: ContractAddress, is_long: bool, pnl_to_pool_factor: felt252, - max_pnl_factor: u128, + max_pnl_factor: u256, should_enable_adl: bool, } @@ -1228,7 +1213,7 @@ mod EventEmitter { struct SetUint { key: felt252, data_bytes: Span, - value: u128, + value: u256, } #[derive(Drop, starknet::Event)] @@ -1307,9 +1292,9 @@ mod EventEmitter { action_key: felt252, token: ContractAddress, price_feed: ContractAddress, - price_feed_multiplier: u128, - price_feed_heartbeat_duration: u128, - stable_price: u128 + price_feed_multiplier: u256, + price_feed_heartbeat_duration: u256, + stable_price: u256 } #[derive(Drop, starknet::Event)] @@ -1317,9 +1302,9 @@ mod EventEmitter { action_key: felt252, token: ContractAddress, price_feed: ContractAddress, - price_feed_multiplier: u128, - price_feed_heartbeat_duration: u128, - stable_price: u128 + price_feed_multiplier: u256, + price_feed_heartbeat_duration: u256, + stable_price: u256 } #[derive(Drop, starknet::Event)] @@ -1337,28 +1322,28 @@ mod EventEmitter { #[derive(Drop, starknet::Event)] struct KeeperExecutionFee { keeper: ContractAddress, - execution_fee_amount: u128 + execution_fee_amount: u256 } #[derive(Drop, starknet::Event)] struct ExecutionFeeRefund { receiver: ContractAddress, - refund_fee_amount: u128 + refund_fee_amount: u256 } #[derive(Drop, starknet::Event)] struct MarketPoolValueInfoEvent { market: ContractAddress, market_pool_value_info: MarketPoolValueInfo, - market_tokens_supply: u128 + market_tokens_supply: u256 } #[derive(Drop, starknet::Event)] struct PoolAmountUpdated { market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 } #[derive(Drop, starknet::Event)] @@ -1366,8 +1351,8 @@ mod EventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 } #[derive(Drop, starknet::Event)] @@ -1375,8 +1360,8 @@ mod EventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 } #[derive(Drop, starknet::Event)] @@ -1384,16 +1369,16 @@ mod EventEmitter { market: ContractAddress, is_long_token: bool, virtual_market_id: felt252, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 } #[derive(Drop, starknet::Event)] struct VirtualPositionInventoryUpdated { token: ContractAddress, virtual_token_id: felt252, - delta: u128, - next_value: u128 + delta: i256, + next_value: i256 } #[derive(Drop, starknet::Event)] @@ -1401,16 +1386,16 @@ mod EventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 } #[derive(Drop, starknet::Event)] - struct CumulativeBorrowingFactorUpdatd { + struct CumulativeBorrowingFactorUpdated { market: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: u256, + next_value: u256 } #[derive(Drop, starknet::Event)] @@ -1418,17 +1403,17 @@ mod EventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: u256, + next_value: u256 } #[derive(Drop, starknet::Event)] - struct ClaimableFundingPerSizeUpdatd { + struct ClaimableFundingAmountPerSizeUpdated { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: u256, + next_value: u256 } #[derive(Drop, starknet::Event)] @@ -1437,8 +1422,8 @@ mod EventEmitter { token: ContractAddress, account: ContractAddress, receiver: ContractAddress, - amount: u128, - next_pool_value: u128 + amount: u256, + next_pool_value: u256 } #[derive(Drop, starknet::Event)] @@ -1447,22 +1432,22 @@ mod EventEmitter { token: ContractAddress, account: ContractAddress, receiver: ContractAddress, - time_key: u128, - amount: u128, - next_pool_value: u128 + time_key: u256, + amount: u256, + next_pool_value: u256 } #[derive(Drop, starknet::Event)] struct UiFeeFactorUpdated { account: ContractAddress, - ui_fee_factor: u128 + ui_fee_factor: u256 } #[derive(Drop, starknet::Event)] struct OraclePriceUpdate { token: ContractAddress, - min_price: u128, - max_price: u128, + min_price: u256, + max_price: u256, is_price_feed: bool } @@ -1489,20 +1474,20 @@ mod EventEmitter { receiver: ContractAddress, token_in: ContractAddress, token_out: ContractAddress, - token_in_price: u128, - token_out_price: u128, - amount_in: u128, - amount_in_after_fees: u128, - amount_out: u128, - price_impact_usd: i128, - price_impact_amount: i128 + token_in_price: u256, + token_out_price: u256, + amount_in: u256, + amount_in_after_fees: u256, + amount_out: u256, + price_impact_usd: i256, + price_impact_amount: i256 } #[derive(Drop, starknet::Event)] struct SwapFeesCollected { market: ContractAddress, token: ContractAddress, - token_price: u128, + token_price: u256, action: felt252, fees: SwapFees } @@ -1521,21 +1506,21 @@ mod EventEmitter { #[derive(Drop, starknet::Event)] struct SetTier { - tier_id: u128, - total_rebate: u128, - discount_share: u128 + tier_id: u256, + total_rebate: u256, + discount_share: u256 } #[derive(Drop, starknet::Event)] struct SetReferrerTier { referrer: ContractAddress, - tier_id: u128 + tier_id: u256 } #[derive(Drop, starknet::Event)] struct SetReferrerDiscountShare { referrer: ContractAddress, - discount_share: u128 + discount_share: u256 } #[derive(Drop, starknet::Event)] @@ -1566,7 +1551,7 @@ mod EventEmitter { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl EventEmitterImpl of super::IEventEmitter { /// Emits the `ClaimableCollateralUpdated` event. fn emit_claimable_collateral_updated( @@ -1574,10 +1559,10 @@ mod EventEmitter { market: ContractAddress, token: ContractAddress, account: ContractAddress, - time_key: u128, - delta: u128, - next_value: u128, - next_pool_value: u128, + time_key: u256, + delta: u256, + next_value: u256, + next_pool_value: u256, ) { self .emit( @@ -1593,9 +1578,9 @@ mod EventEmitter { market: ContractAddress, token: ContractAddress, account: ContractAddress, - delta: u128, - next_value: u128, - next_pool_value: u128, + delta: u256, + next_value: u256, + next_pool_value: u256, ) { self .emit( @@ -1607,7 +1592,7 @@ mod EventEmitter { /// Emits the `PositionImpactPoolAmountUpdated` event. fn emit_position_impact_pool_amount_updated( - ref self: ContractState, market: ContractAddress, delta: u128, next_value: u128, + ref self: ContractState, market: ContractAddress, delta: i256, next_value: u256, ) { self.emit(PositionImpactPoolAmountUpdated { market, delta, next_value, }); } @@ -1617,8 +1602,8 @@ mod EventEmitter { ref self: ContractState, market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128, + delta: i256, + next_value: u256, ) { self.emit(SwapImpactPoolAmountUpdated { market, token, delta, next_value, }); } @@ -1656,8 +1641,8 @@ mod EventEmitter { ref self: ContractState, market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128, + delta: u256, + next_value: u256, fee_type: felt252 ) { self.emit(ClaimableFeeAmountUpdated { market, token, delta, next_value, fee_type }); @@ -1669,9 +1654,9 @@ mod EventEmitter { ui_fee_receiver: ContractAddress, market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128, - next_pool_value: u128, + delta: u256, + next_value: u256, + next_pool_value: u256, fee_type: felt252 ) { self @@ -1687,7 +1672,7 @@ mod EventEmitter { ref self: ContractState, market: ContractAddress, receiver: ContractAddress, - fee_amount: u128 + fee_amount: u256 ) { self.emit(FeesClaimed { market, receiver, fee_amount }); } @@ -1698,8 +1683,8 @@ mod EventEmitter { ui_fee_receiver: ContractAddress, market: ContractAddress, receiver: ContractAddress, - fee_amount: u128, - next_pool_value: u128 + fee_amount: u256, + next_pool_value: u256 ) { self .emit( @@ -1708,7 +1693,6 @@ mod EventEmitter { } /// Emits the `DepositCreated` event. - #[inline(always)] fn emit_deposit_created(ref self: ContractState, key: felt252, deposit: Deposit) { self .emit( @@ -1736,9 +1720,9 @@ mod EventEmitter { fn emit_deposit_executed( ref self: ContractState, key: felt252, - long_token_amount: u128, - short_token_amount: u128, - received_market_tokens: u128 + long_token_amount: u256, + short_token_amount: u256, + received_market_tokens: u256 ) { self .emit( @@ -1765,6 +1749,8 @@ mod EventEmitter { receiver: withdrawal.receiver, callback_contract: withdrawal.callback_contract, market: withdrawal.market, + long_token_swap_path: withdrawal.long_token_swap_path, + short_token_swap_path: withdrawal.short_token_swap_path, market_token_amount: withdrawal.market_token_amount, min_long_token_amount: withdrawal.min_long_token_amount, min_short_token_amount: withdrawal.min_short_token_amount, @@ -1790,7 +1776,6 @@ mod EventEmitter { /// Emits the `PositionIncrease` event. /// # Arguments /// * `params` - The position increase parameters. - #[inline(always)] fn emit_position_increase(ref self: ContractState, params: PositionIncreaseParams) { self .emit( @@ -1802,7 +1787,7 @@ mod EventEmitter { size_in_tokens: params.position.size_in_tokens, collateral_amount: params.position.collateral_amount, borrowing_factor: params.position.borrowing_factor, - funding_fee_amount_per_pize: params.position.funding_fee_amount_per_size, + funding_fee_amount_per_size: params.position.funding_fee_amount_per_size, long_token_claimable_funding_amount_per_size: params .position .long_token_claimable_funding_amount_per_size, @@ -1838,14 +1823,13 @@ mod EventEmitter { /// * `values` - The parameters linked to the decrease of collateral. /// * `index_token_price` - The price of the index token. /// * `collateral_token_price` - The price of the collateral token. - #[inline(always)] fn emit_position_decrease( ref self: ContractState, order_key: felt252, position_key: felt252, position: Position, - size_delta_usd: u128, - collateral_delta_amount: u128, + size_delta_usd: u256, + collateral_delta_amount: u256, order_type: OrderType, values: DecreasePositionCollateralValues, index_token_price: Price, @@ -1861,7 +1845,7 @@ mod EventEmitter { size_in_tokens: position.size_in_tokens, collateral_amount: position.collateral_amount, borrowing_factor: position.borrowing_factor, - funding_fee_amount_per_pize: position.funding_fee_amount_per_size, + funding_fee_amount_per_size: position.funding_fee_amount_per_size, long_token_claimable_funding_amount_per_size: position .long_token_claimable_funding_amount_per_size, short_token_claimable_funding_amount_per_size: position @@ -1902,10 +1886,10 @@ mod EventEmitter { fn emit_order_updated( ref self: ContractState, key: felt252, - size_delta_usd: u128, - acceptable_price: u128, - trigger_price: u128, - min_output_amount: u128 + size_delta_usd: u256, + acceptable_price: u256, + trigger_price: u256, + min_output_amount: u256 ) { self .emit( @@ -1917,21 +1901,21 @@ mod EventEmitter { /// Emits the `OrderSizeDeltaAutoUpdated` event. fn emit_order_size_delta_auto_updated( - ref self: ContractState, key: felt252, size_delta_usd: u128, next_size_delta_usd: u128 + ref self: ContractState, key: felt252, size_delta_usd: u256, next_size_delta_usd: u256 ) { self.emit(OrderSizeDeltaAutoUpdated { key, size_delta_usd, next_size_delta_usd }); } - /// Emits the `OrderCollatDeltaAmountAutoUpdtd` event. + /// Emits the `OrderCollateralDeltaAmountAutoUpdated` event. fn emit_order_collateral_delta_amount_auto_updated( ref self: ContractState, key: felt252, - collateral_delta_amount: u128, - next_collateral_delta_amount: u128 + collateral_delta_amount: u256, + next_collateral_delta_amount: u256 ) { self .emit( - OrderCollatDeltaAmountAutoUpdtd { + OrderCollateralDeltaAmountAutoUpdated { key, collateral_delta_amount, next_collateral_delta_amount } ); @@ -1946,9 +1930,9 @@ mod EventEmitter { fn emit_insolvent_close_info( ref self: ContractState, order_key: felt252, - position_collateral_amount: u128, - base_pnl_usd: u128, - remaining_cost_usd: u128 + position_collateral_amount: u256, + base_pnl_usd: i256, + remaining_cost_usd: u256 ) { self .emit( @@ -1969,9 +1953,9 @@ mod EventEmitter { ref self: ContractState, market: ContractAddress, token: ContractAddress, - expected_amount: u128, - amount_paid_in_collateral_token: u128, - amount_paid_in_secondary_output_token: u128 + expected_amount: u256, + amount_paid_in_collateral_token: u256, + amount_paid_in_secondary_output_token: u256 ) { self .emit( @@ -1992,7 +1976,7 @@ mod EventEmitter { /// * `market` - The market where fees were collected. /// * `collateral_token` - The collateral token. /// * `trade_size_usd` - The trade size in usd. - /// * `is_increase` - Wether it is an increase. + /// * `is_increase` - Whether it is an increase. /// * `fees` - The struct storing position fees. fn emit_position_fees_collected( ref self: ContractState, @@ -2000,7 +1984,7 @@ mod EventEmitter { position_key: felt252, market: ContractAddress, collateral_token: ContractAddress, - trade_size_usd: u128, + trade_size_usd: u256, is_increase: bool, fees: PositionFees ) { @@ -2063,7 +2047,7 @@ mod EventEmitter { /// * `market` - The market where fees were collected. /// * `collateral_token` - The collateral token. /// * `trade_size_usd` - The trade size in usd. - /// * `is_increase` - Wether it is an increase. + /// * `is_increase` - Whether it is an increase. /// * `fees` - The struct storing position fees. fn emit_position_fees_info( ref self: ContractState, @@ -2071,7 +2055,7 @@ mod EventEmitter { position_key: felt252, market: ContractAddress, collateral_token: ContractAddress, - trade_size_usd: u128, + trade_size_usd: u256, is_increase: bool, fees: PositionFees ) { @@ -2147,9 +2131,9 @@ mod EventEmitter { market: ContractAddress, token: ContractAddress, affiliate: ContractAddress, - delta: u128, - next_value: u128, - next_pool_value: u128 + delta: u256, + next_value: u256, + next_pool_value: u256 ) { self .emit( @@ -2166,8 +2150,8 @@ mod EventEmitter { token: ContractAddress, affiliate: ContractAddress, receiver: ContractAddress, - amount: u128, - next_pool_value: u128 + amount: u256, + next_pool_value: u256 ) { self .emit( @@ -2202,7 +2186,7 @@ mod EventEmitter { fn emit_after_withdrawal_cancellation_error( ref self: ContractState, key: felt252, withdrawal: Withdrawal ) { - self.emit(AfterWithdrawalCancelError { key, withdrawal }); + self.emit(AfterWithdrawalCancellationError { key, withdrawal }); } /// Emits the `AfterOrderExecutionError` event. @@ -2225,7 +2209,7 @@ mod EventEmitter { /// # Arguments // * `market`- Address of the market for the ADL state update // * `is_long`- Indicates the ADL state update is for the long or short side of the market - // * `pnl_to_pool_factor`- The the ratio of PnL to pool value + // * `pnl_to_pool_factor`- The ratio of PnL to pool value // * `max_pnl_factor`- The max PnL factor // * `should_enable_adl`- Whether ADL was enabled or disabled fn emit_adl_state_updated( @@ -2233,7 +2217,7 @@ mod EventEmitter { market: ContractAddress, is_long: bool, pnl_to_pool_factor: felt252, - max_pnl_factor: u128, + max_pnl_factor: u256, should_enable_adl: bool, ) { self @@ -2266,7 +2250,7 @@ mod EventEmitter { /// Emits the `SetFelt252` event. fn emit_set_uint( - ref self: ContractState, key: felt252, data_bytes: Span, value: u128 + ref self: ContractState, key: felt252, data_bytes: Span, value: u256 ) { self.emit(SetUint { key, data_bytes, value }); } @@ -2366,9 +2350,9 @@ mod EventEmitter { action_key: felt252, token: ContractAddress, price_feed: ContractAddress, - price_feed_multiplier: u128, - price_feed_heartbeat_duration: u128, - stable_price: u128 + price_feed_multiplier: u256, + price_feed_heartbeat_duration: u256, + stable_price: u256 ) { self .emit( @@ -2389,9 +2373,9 @@ mod EventEmitter { action_key: felt252, token: ContractAddress, price_feed: ContractAddress, - price_feed_multiplier: u128, - price_feed_heartbeat_duration: u128, - stable_price: u128 + price_feed_multiplier: u256, + price_feed_heartbeat_duration: u256, + stable_price: u256 ) { self .emit( @@ -2422,25 +2406,24 @@ mod EventEmitter { /// Emits the `KeeperExecutionFee` event. fn emit_keeper_execution_fee( - ref self: ContractState, keeper: ContractAddress, execution_fee_amount: u128 + ref self: ContractState, keeper: ContractAddress, execution_fee_amount: u256 ) { self.emit(KeeperExecutionFee { keeper, execution_fee_amount }); } /// Emits the `ExecutionFeeRefund` event. fn emit_execution_fee_refund( - ref self: ContractState, receiver: ContractAddress, refund_fee_amount: u128 + ref self: ContractState, receiver: ContractAddress, refund_fee_amount: u256 ) { self.emit(ExecutionFeeRefund { receiver, refund_fee_amount }); } /// Emits the `MarketPoolValueInfo` event. - #[inline(always)] fn emit_market_pool_value_info( ref self: ContractState, market: ContractAddress, market_pool_value_info: MarketPoolValueInfo, - market_tokens_supply: u128 + market_tokens_supply: u256 ) { self .emit( @@ -2455,8 +2438,8 @@ mod EventEmitter { ref self: ContractState, market: ContractAddress, token: ContractAddress, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 ) { self.emit(PoolAmountUpdated { market, token, delta, next_value }); } @@ -2467,8 +2450,8 @@ mod EventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 ) { self .emit( @@ -2484,8 +2467,8 @@ mod EventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 ) { self.emit(OpenInterestUpdated { market, collateral_token, is_long, delta, next_value }); } @@ -2496,8 +2479,8 @@ mod EventEmitter { market: ContractAddress, is_long_token: bool, virtual_market_id: felt252, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 ) { self .emit( @@ -2512,8 +2495,8 @@ mod EventEmitter { ref self: ContractState, token: ContractAddress, virtual_token_id: felt252, - delta: u128, - next_value: u128 + delta: i256, + next_value: i256 ) { self .emit( @@ -2527,8 +2510,8 @@ mod EventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: i256, + next_value: u256 ) { self .emit( @@ -2536,15 +2519,15 @@ mod EventEmitter { ); } - /// Emits the `CumulativeBorrowingFactorUpdatd` event. + /// Emits the `CumulativeBorrowingFactorUpdated` event. fn emit_cumulative_borrowing_factor_updated( ref self: ContractState, market: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: u256, + next_value: u256 ) { - self.emit(CumulativeBorrowingFactorUpdatd { market, is_long, delta, next_value }); + self.emit(CumulativeBorrowingFactorUpdated { market, is_long, delta, next_value }); } /// Emits the `FundingFeeAmountPerSizeUpdated` event. @@ -2553,8 +2536,8 @@ mod EventEmitter { market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: u256, + next_value: u256 ) { self .emit( @@ -2564,32 +2547,32 @@ mod EventEmitter { ); } - /// Emits the `ClaimableFundingPerSizeUpdatd` event. + /// Emits the `ClaimableFundingAmountPerSizeUpdated` event. fn emit_claimable_funding_amount_per_size_updated( ref self: ContractState, market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - delta: u128, - next_value: u128 + delta: u256, + next_value: u256 ) { self .emit( - ClaimableFundingPerSizeUpdatd { + ClaimableFundingAmountPerSizeUpdated { market, collateral_token, is_long, delta, next_value } ); } /// Emits the `FundingFeesClaimed` event. - fn emit_founding_fees_claimed( + fn emit_funding_fees_claimed( ref self: ContractState, market: ContractAddress, token: ContractAddress, account: ContractAddress, receiver: ContractAddress, - amount: u128, - next_pool_value: u128 + amount: u256, + next_pool_value: u256 ) { self .emit( @@ -2604,9 +2587,9 @@ mod EventEmitter { token: ContractAddress, account: ContractAddress, receiver: ContractAddress, - time_key: u128, - amount: u128, - next_pool_value: u128 + time_key: u256, + amount: u256, + next_pool_value: u256 ) { self .emit( @@ -2618,7 +2601,7 @@ mod EventEmitter { /// Emits the `UiFeeFactorUpdated` event. fn emit_ui_fee_factor_updated( - ref self: ContractState, account: ContractAddress, ui_fee_factor: u128 + ref self: ContractState, account: ContractAddress, ui_fee_factor: u256 ) { self.emit(UiFeeFactorUpdated { account, ui_fee_factor }); } @@ -2627,8 +2610,8 @@ mod EventEmitter { fn emit_oracle_price_update( ref self: ContractState, token: ContractAddress, - min_price: u128, - max_price: u128, + min_price: u256, + max_price: u256, is_price_feed: bool ) { self.emit(OraclePriceUpdate { token, min_price, max_price, is_price_feed }); @@ -2660,13 +2643,13 @@ mod EventEmitter { receiver: ContractAddress, token_in: ContractAddress, token_out: ContractAddress, - token_in_price: u128, - token_out_price: u128, - amount_in: u128, - amount_in_after_fees: u128, - amount_out: u128, - price_impact_usd: i128, - price_impact_amount: i128 + token_in_price: u256, + token_out_price: u256, + amount_in: u256, + amount_in_after_fees: u256, + amount_out: u256, + price_impact_usd: i256, + price_impact_amount: i256 ) { self .emit( @@ -2688,12 +2671,11 @@ mod EventEmitter { } /// Emits the `SwapFeesCollected` event. - #[inline(always)] fn emit_swap_fees_collected( ref self: ContractState, market: ContractAddress, token: ContractAddress, - token_price: u128, + token_price: u256, action: felt252, fees: SwapFees ) { @@ -2703,8 +2685,8 @@ mod EventEmitter { fn emit_oracle_price_updated( ref self: ContractState, token: ContractAddress, - min_price: u128, - max_price: u128, + min_price: u256, + max_price: u256, is_price_feed: bool ) { self.emit(OraclePriceUpdate { token, min_price, max_price, is_price_feed }); @@ -2715,19 +2697,19 @@ mod EventEmitter { } fn emit_set_tier( - ref self: ContractState, tier_id: u128, total_rebate: u128, discount_share: u128 + ref self: ContractState, tier_id: u256, total_rebate: u256, discount_share: u256 ) { self.emit(SetTier { tier_id, total_rebate, discount_share }); } fn emit_set_referrer_tier( - ref self: ContractState, referrer: ContractAddress, tier_id: u128 + ref self: ContractState, referrer: ContractAddress, tier_id: u256 ) { self.emit(SetReferrerTier { referrer, tier_id }); } fn emit_set_referrer_discount_share( - ref self: ContractState, referrer: ContractAddress, discount_share: u128 + ref self: ContractState, referrer: ContractAddress, discount_share: u256 ) { self.emit(SetReferrerDiscountShare { referrer, discount_share }); } diff --git a/src/event/event_utils.cairo b/src/event/event_utils.cairo index ee9c8ebe..702d419b 100644 --- a/src/event/event_utils.cairo +++ b/src/event/event_utils.cairo @@ -1,5 +1,185 @@ -#[derive(Drop, starknet::Store, Serde)] +use starknet::{ + get_caller_address, ContractAddress, Felt252TryIntoContractAddress, ContractAddressIntoFelt252, + contract_address_const +}; +use array::ArrayTrait; +use satoru::utils::i256::i256; +use traits::Default; +use satoru::utils::traits::ContractAddressDefault; + +use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; + +use alexandria_data_structures::array_ext::SpanTraitExt; + + +// +// NEEDED IMPLEMENTATIONS FOR LOGDATA TYPES +// + +impl Felt252IntoBool of Into { + fn into(self: felt252) -> bool { + let as_u256: u256 = self.into(); + as_u256 > 0 + } +} + +impl Felt252IntoContractAddress of Into { + fn into(self: felt252) -> ContractAddress { + Felt252TryIntoContractAddress::try_into(self).expect('contractaddress overflow') + } +} + +// workaround for serialization to work with u256 +impl U256IntoFelt252 of Into { + fn into(self: u256) -> felt252 { + self.high.into() * 0x100000000000000000000000000000000_felt252 + self.low.into() + } +} + +impl I256252DictValue of Felt252DictValue { + fn zero_default() -> i256 nopanic { + i256 { mag: 0, sign: false } + } +} + +impl U256252DictValue of Felt252DictValue { + fn zero_default() -> u256 nopanic { + u256 { high: 0, low: 0 } + } +} + +impl ContractAddressDictValue of Felt252DictValue { + fn zero_default() -> ContractAddress nopanic { + contract_address_const::<0>() + } +} + +// +// LOG DATA IMPLEMENTATION +// + +//TODO Switch the append with a set in the functions when its available +#[derive(Default, Serde, Destruct)] struct EventLogData { - cant_be_empty: u128, // remove + cant_be_empty: u256, // remove // TODO } + +#[derive(Default, Destruct)] +struct LogData { + address_dict: SerializableFelt252Dict, + uint_dict: SerializableFelt252Dict, + int_dict: SerializableFelt252Dict, + bool_dict: SerializableFelt252Dict, + felt252_dict: SerializableFelt252Dict, + string_dict: SerializableFelt252Dict +} + +/// Number of dicts presents in LogData +const DICTS_IN_LOGDATA: usize = 6; + +/// When serializing dicts into a unique Array, this is the value that will +/// be used to recognized a separation between two dicts. +const END_OF_DICT: felt252 = '______'; + +#[generate_trait] +impl LogDataImpl of LogDataTrait { + /// Serializes all the sub-dicts of LogData & append all of them into a new felt252 array + fn serialize(ref self: LogData, ref output: Array) { + let mut serialized_dicts: Array> = array![ + self.address_dict.serialize_into(), + self.uint_dict.serialize_into(), + self.int_dict.serialize_into(), + self.bool_dict.serialize_into(), + self.felt252_dict.serialize_into(), + self.string_dict.serialize_into() + ]; + let mut span_arrays = serialized_dicts.span(); + loop { + match span_arrays.pop_front() { + Option::Some(arr) => { + let mut sub_array_span = arr.span(); + loop { + match sub_array_span.pop_front() { + Option::Some(v) => { output.append(*v); }, + Option::None => { break; } + }; + }; + output.append(END_OF_DICT); + }, + Option::None => { break; } + }; + }; + } + + /// Serializes all the sub-dicts of LogData & return the serialized data + fn serialize_into(ref self: LogData) -> Array { + let mut serialized_data: Array = array![]; + self.serialize(ref serialized_data); + serialized_data + } + + /// Deserialize all the sub-dicts serialized into a LogData + fn deserialize(ref serialized: Span) -> Option { + // There should be the right amount of dictionaries serialized + if serialized.occurrences_of(END_OF_DICT) != DICTS_IN_LOGDATA { + panic_with_felt252('serialized format error'); + } + + // Deserialize all dicts one by one + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let address_dict = SerializableFelt252DictTrait::< + ContractAddress + >::deserialize(ref serialized_dict) + .expect('deserialize err address'); + + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let uint_dict = SerializableFelt252DictTrait::::deserialize(ref serialized_dict) + .expect('deserialize err uint'); + + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let int_dict = SerializableFelt252DictTrait::::deserialize(ref serialized_dict) + .expect('deserialize err int'); + + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let bool_dict = SerializableFelt252DictTrait::::deserialize(ref serialized_dict) + .expect('deserialize err bool'); + + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let felt252_dict = SerializableFelt252DictTrait::::deserialize(ref serialized_dict) + .expect('deserialize err felt252'); + + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let string_dict = SerializableFelt252DictTrait::::deserialize(ref serialized_dict) + .expect('deserialize err string'); + + // Create the LogData struct with every dicts + let log_data: LogData = LogData { + address_dict, uint_dict, int_dict, bool_dict, felt252_dict, string_dict + }; + + Option::Some(log_data) + } +} + + +// +// UTILITY FUNCTION +// + +/// Pop every elements from the span until the next occurrences of END_OF_DICT or +/// the end of the Span and return those values in a Span. +fn get_next_dict_serialized(ref serialized: Span) -> Span { + let mut dict_data: Array = array![]; + loop { + match serialized.pop_front() { + Option::Some(v) => { if *v == END_OF_DICT { + break; + } else { + dict_data.append(*v); + } }, + Option::None => { break; } + }; + }; + dict_data.span() +} diff --git a/src/exchange/adl_handler.cairo b/src/exchange/adl_handler.cairo index 500d0761..a8589f1a 100644 --- a/src/exchange/adl_handler.cairo +++ b/src/exchange/adl_handler.cairo @@ -10,6 +10,8 @@ use starknet::ContractAddress; // Local imports. use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::utils::i256::i256; + // ************************************************************************* // Interface of the `AdlHandler` contract. @@ -19,7 +21,7 @@ trait IAdlHandler { /// Checks the ADL state to update the isAdlEnabled flag. /// # Arguments /// * `market` - The market to check. - /// * `is_long` - Wether to check long or short side. + /// * `is_long` - Whether to check long or short side. /// * `oracle_params` - The oracle set price parameters used to set price /// before performing checks fn update_adl_state( @@ -38,7 +40,7 @@ trait IAdlHandler { /// position profit, this is not implemented within the contracts at the moment. /// # Arguments /// * `market` - The market to check. - /// * `is_long` - Wether to check long or short side. + /// * `is_long` - Whether to check long or short side. /// * `oracle_params` - The oracle set price parameters used to set price /// before performing adl. fn execute_adl( @@ -47,7 +49,7 @@ trait IAdlHandler { market_address: ContractAddress, collateral_token: ContractAddress, is_long: bool, - size_delta_usd: u128, + size_delta_usd: u256, oracle_params: SetPricesParams ); } @@ -75,6 +77,7 @@ mod AdlHandler { use satoru::exchange::base_order_handler::BaseOrderHandler::{ data_store::InternalContractMemberStateTrait as DataStoreStateTrait, event_emitter::InternalContractMemberStateTrait as EventEmitterStateTrait, + order_utils::InternalContractMemberStateTrait as OrderUtilsTrait, oracle::InternalContractMemberStateTrait as OracleStateTrait, InternalTrait as BaseOrderHandleInternalTrait, }; @@ -89,33 +92,36 @@ mod AdlHandler { use satoru::order::{ order::{SecondaryOrderType, OrderType, Order}, order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}, - base_order_utils::{ExecuteOrderParams, ExecuteOrderParamsContracts}, order_utils + base_order_utils::{ExecuteOrderParams, ExecuteOrderParamsContracts}, + order_utils::{IOrderUtilsDispatcher} }; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; - use satoru::utils::store_arrays::StoreU64Array; + use satoru::utils::{store_arrays::StoreU64Array, calc::to_signed}; + use satoru::utils::i256::i256; + /// ExecuteAdlCache struct used in execute_adl. #[derive(Drop, Serde)] struct ExecuteAdlCache { /// The starting gas to execute adl. - starting_gas: u128, + starting_gas: u256, /// The min oracles block numbers. min_oracle_block_numbers: Array, /// The max oracles block numbers. max_oracle_block_numbers: Array, /// The key of the adl to execute. key: felt252, - /// Wether adl should be allowed, depending on pnl state. + /// Whether adl should be allowed, depending on pnl state. should_allow_adl: bool, /// The maximum pnl factor to allow adl. - max_pnl_factor_for_adl: u128, + max_pnl_factor_for_adl: u256, /// The factor between pnl and pool. - pnl_to_pool_factor: u128, // TODO i128 when it derive Store + pnl_to_pool_factor: i256, /// The new factor between pnl and pool. - next_pnl_to_pool_factor: u128, // TODO i128 when it derive Store + next_pnl_to_pool_factor: i256, /// The minimal pnl factor for adl. - min_pnl_factor_for_adl: u128 + min_pnl_factor_for_adl: u256 } // ************************************************************************* @@ -147,7 +153,7 @@ mod AdlHandler { oracle_address: ContractAddress, swap_handler_address: ContractAddress, referral_storage_address: ContractAddress, - order_handler_address: ContractAddress + order_utils_address: ContractAddress ) { let mut state: BaseOrderHandler::ContractState = BaseOrderHandler::unsafe_new_contract_state(); @@ -159,14 +165,16 @@ mod AdlHandler { order_vault_address, oracle_address, swap_handler_address, - referral_storage_address + referral_storage_address, + order_utils_address ); + self.order_utils.write(IOrderUtilsDispatcher { contract_address: order_utils_address }); } // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl AdlHandlerImpl of super::IAdlHandler { fn update_adl_state( ref self: ContractState, @@ -197,7 +205,7 @@ mod AdlHandler { market_address: ContractAddress, collateral_token: ContractAddress, is_long: bool, - size_delta_usd: u128, + size_delta_usd: u256, oracle_params: oracle_utils::SetPricesParams ) { let mut cache = ExecuteAdlCache { @@ -207,8 +215,8 @@ mod AdlHandler { key: 0, should_allow_adl: false, max_pnl_factor_for_adl: 0, - pnl_to_pool_factor: 0, - next_pnl_to_pool_factor: 0, + pnl_to_pool_factor: Zeroable::zero(), + next_pnl_to_pool_factor: Zeroable::zero(), min_pnl_factor_for_adl: 0 }; @@ -278,7 +286,7 @@ mod AdlHandler { ) ); - order_utils::execute_order(params); + base_order_handler_state.order_utils.read().execute_order_utils(params); // validate that the ratio of pending pnl to pool value was decreased cache @@ -292,7 +300,8 @@ mod AdlHandler { .min_pnl_factor_for_adl = market_utils::get_min_pnl_factor_after_adl(data_store, market_address, is_long); assert( - cache.next_pnl_to_pool_factor > cache.min_pnl_factor_for_adl, 'pnl overcorrected' + cache.next_pnl_to_pool_factor > to_signed(cache.min_pnl_factor_for_adl, true), + 'pnl overcorrected' ); } } diff --git a/src/exchange/base_order_handler.cairo b/src/exchange/base_order_handler.cairo index 88188982..6db1b2b9 100644 --- a/src/exchange/base_order_handler.cairo +++ b/src/exchange/base_order_handler.cairo @@ -6,7 +6,7 @@ // Core lib imports. use core::traits::Into; -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const, ClassHash}; use satoru::oracle::oracle_utils::SetPricesParams; use satoru::order::{order::SecondaryOrderType, base_order_utils::ExecuteOrderParams}; @@ -33,7 +33,11 @@ trait IBaseOrderHandler { order_vault_address: ContractAddress, oracle_address: ContractAddress, swap_handler_address: ContractAddress, - referral_storage_address: ContractAddress + referral_storage_address: ContractAddress, + order_utils_class_hash: ClassHash, + increase_order_utils_class_hash: ClassHash, + decrease_order_utils_class_hash: ClassHash, + swap_order_utils_class_hash: ClassHash, ); } @@ -44,16 +48,20 @@ mod BaseOrderHandler { // ************************************************************************* // Core lib imports. + use core::option::OptionTrait; use core::zeroable::Zeroable; use core::traits::Into; - use starknet::{get_caller_address, ContractAddress, contract_address_const}; + use starknet::{get_caller_address, ContractAddress, contract_address_const, ClassHash}; - use debug::PrintTrait; use result::ResultTrait; // Local imports. use super::IBaseOrderHandler; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; + use satoru::role::role_module::{ + IRoleModuleDispatcher, IRoleModuleDispatcherTrait, RoleModule, IRoleModule + }; + use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::oracle::{ @@ -62,9 +70,13 @@ mod BaseOrderHandler { oracle_utils::{SetPricesParams, get_uncompacted_oracle_block_numbers}, }; use satoru::order::{ - order::{SecondaryOrderType, OrderType, Order, DecreasePositionSwapType}, + error::OrderError, order::{SecondaryOrderType, OrderType, Order, DecreasePositionSwapType}, order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}, - base_order_utils::{ExecuteOrderParams, ExecuteOrderParamsContracts}, order_store_utils, + base_order_utils::{ExecuteOrderParams, ExecuteOrderParamsContracts}, + order_utils::IOrderUtilsLibraryDispatcher, + increase_order_utils::IIncreaseOrderUtilsLibraryDispatcher, + decrease_order_utils::IDecreaseOrderUtilsLibraryDispatcher, + swap_order_utils::ISwapOrderUtilsLibraryDispatcher }; use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; use satoru::exchange::error::ExchangeError; @@ -92,7 +104,15 @@ mod BaseOrderHandler { /// Interface to interact with the `Oracle` contract. oracle: IOracleDispatcher, /// Interface to interact with the `ReferralStorage` contract. - referral_storage: IReferralStorageDispatcher + referral_storage: IReferralStorageDispatcher, + /// Interface to interact with the `OrderUtils` lib. + order_utils_lib: IOrderUtilsLibraryDispatcher, + /// Interface to interact with the `IncreaseOrderUtils` lib. + increase_order_utils_lib: IIncreaseOrderUtilsLibraryDispatcher, + /// Interface to interact with the `DecreaseOrderUtils` lib. + decrease_order_utils_lib: IDecreaseOrderUtilsLibraryDispatcher, + /// Interface to interact with the `SwapOrderUtils` lib. + swap_order_utils_lib: ISwapOrderUtilsLibraryDispatcher } // ************************************************************************* @@ -117,7 +137,11 @@ mod BaseOrderHandler { order_vault_address: ContractAddress, oracle_address: ContractAddress, swap_handler_address: ContractAddress, - referral_storage_address: ContractAddress + referral_storage_address: ContractAddress, + order_utils_class_hash: ClassHash, + increase_order_utils_class_hash: ClassHash, + decrease_order_utils_class_hash: ClassHash, + swap_order_utils_class_hash: ClassHash, ) { self .initialize( @@ -127,7 +151,11 @@ mod BaseOrderHandler { order_vault_address, oracle_address, swap_handler_address, - referral_storage_address + referral_storage_address, + order_utils_class_hash, + increase_order_utils_class_hash, + decrease_order_utils_class_hash, + swap_order_utils_class_hash ); } @@ -135,7 +163,7 @@ mod BaseOrderHandler { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl BaseOrderHandlerImpl of super::IBaseOrderHandler { fn initialize( ref self: ContractState, @@ -145,7 +173,11 @@ mod BaseOrderHandler { order_vault_address: ContractAddress, oracle_address: ContractAddress, swap_handler_address: ContractAddress, - referral_storage_address: ContractAddress + referral_storage_address: ContractAddress, + order_utils_class_hash: ClassHash, + increase_order_utils_class_hash: ClassHash, + decrease_order_utils_class_hash: ClassHash, + swap_order_utils_class_hash: ClassHash, ) { // Make sure the contract is not already initialized. assert( @@ -154,6 +186,7 @@ mod BaseOrderHandler { ); self.data_store.write(IDataStoreDispatcher { contract_address: data_store_address }); self.role_store.write(IRoleStoreDispatcher { contract_address: role_store_address }); + self .event_emitter .write(IEventEmitterDispatcher { contract_address: event_emitter_address }); @@ -165,6 +198,28 @@ mod BaseOrderHandler { self .referral_storage .write(IReferralStorageDispatcher { contract_address: referral_storage_address }); + self + .order_utils_lib + .write(IOrderUtilsLibraryDispatcher { class_hash: order_utils_class_hash }); + self + .increase_order_utils_lib + .write( + IIncreaseOrderUtilsLibraryDispatcher { + class_hash: increase_order_utils_class_hash + } + ); + self + .decrease_order_utils_lib + .write( + IDecreaseOrderUtilsLibraryDispatcher { + class_hash: decrease_order_utils_class_hash + } + ); + self + .swap_order_utils_lib + .write( + ISwapOrderUtilsLibraryDispatcher { class_hash: swap_order_utils_class_hash } + ); } } @@ -175,7 +230,7 @@ mod BaseOrderHandler { impl InternalImpl of InternalTrait { /// Get the BaseOrderUtils.ExecuteOrderParams to execute an order. /// # Arguments - /// * `key` - The the key of the order to execute. + /// * `key` - The key of the order to execute. /// * `oracle_params` - The set price parameters for oracle. /// * `keeper` - The keeper executing the order. /// * `starting_gas` - The starting gas. @@ -184,12 +239,13 @@ mod BaseOrderHandler { key: felt252, oracle_params: SetPricesParams, keeper: ContractAddress, - starting_gas: u128, + starting_gas: u256, secondary_order_type: SecondaryOrderType ) -> ExecuteOrderParams { let data_store = self.data_store.read(); - let order = order_store_utils::get(data_store, key); + let order = data_store.get_order(key); + let swap_path_markets = market_utils::get_swap_path_markets( data_store, order.swap_path ); @@ -210,7 +266,7 @@ mod BaseOrderHandler { oracle_params.compacted_max_oracle_block_numbers.span(), oracle_params.tokens.len() ); - let address_zero = 0.try_into().unwrap(); + let address_zero = contract_address_const::<0>(); let mut market = Default::default(); diff --git a/src/exchange/deleted_funtions b/src/exchange/deleted_funtions new file mode 100644 index 00000000..3b5a8e19 --- /dev/null +++ b/src/exchange/deleted_funtions @@ -0,0 +1,165 @@ +// fn update_order( + // ref self: ContractState, + // key: felt252, + // size_delta_usd: u256, + // acceptable_price: u256, + // trigger_price: u256, + // min_output_amount: u256, + // order: Order + // ) -> Order { + // // Check only controller. + // let role_module_state = RoleModule::unsafe_new_contract_state(); + // role_module_state.only_controller(); + + // // Fetch data store. + // let base_order_handler_state = BaseOrderHandler::unsafe_new_contract_state(); + // let data_store = base_order_handler_state.data_store.read(); + // let event_emitter = base_order_handler_state.event_emitter.read(); + + // global_reentrancy_guard::non_reentrant_before(data_store); + + // // Validate feature. + // feature_utils::validate_feature( + // data_store, + // keys::update_order_feature_disabled_key(get_contract_address(), order.order_type) + // ); + + // assert(base_order_utils::is_market_order(order.order_type), 'OrderNotUpdatable'); + + // let mut updated_order = order.clone(); + // updated_order.size_delta_usd = size_delta_usd; + // updated_order.trigger_price = trigger_price; + // updated_order.acceptable_price = acceptable_price; + // updated_order.min_output_amount = min_output_amount; + // updated_order.is_frozen = false; + + // // Allow topping up of execution fee as frozen orders will have execution fee reduced. + // let fee_token = token_utils::fee_token(data_store); + // let order_vault = base_order_handler_state.order_vault.read(); + // let received_fee_token = order_vault.record_transfer_in(fee_token); + // updated_order.execution_fee = received_fee_token; + + // let estimated_gas_limit = gas_utils::estimate_execute_order_gas_limit( + // data_store, @updated_order + // ); + // gas_utils::validate_execution_fee( + // data_store, estimated_gas_limit, updated_order.execution_fee + // ); + + // updated_order.touch(); + + // base_order_utils::validate_non_empty_order(@updated_order); + + // data_store.set_order(key, updated_order); + // event_emitter + // .emit_order_updated( + // key, size_delta_usd, acceptable_price, trigger_price, min_output_amount + // ); + + // global_reentrancy_guard::non_reentrant_after(data_store); + + // updated_order + // } + + // fn cancel_order(ref self: ContractState, key: felt252) { + // let starting_gas: u256 = 0; // TODO: Get starting gas from Cairo. + + // // Check only controller. + // let role_module_state = RoleModule::unsafe_new_contract_state(); + // role_module_state.only_controller(); + + // // Fetch data store. + // let base_order_handler_state = BaseOrderHandler::unsafe_new_contract_state(); + // let data_store = base_order_handler_state.data_store.read(); + + // global_reentrancy_guard::non_reentrant_before(data_store); + + // let order = data_store.get_order(key); + + // // Validate feature. + // feature_utils::validate_feature( + // data_store, + // keys::cancel_order_feature_disabled_key(get_contract_address(), order.order_type) + // ); + + // if base_order_utils::is_market_order(order.order_type) { + // exchange_utils::validate_request_cancellation( + // data_store, order.updated_at_block, 'Order' + // ) + // } + + // order_utils::cancel_order( + // data_store, + // base_order_handler_state.event_emitter.read(), + // base_order_handler_state.order_vault.read(), + // key, + // order.account, + // starting_gas, + // keys::user_initiated_cancel(), + // ArrayTrait::::new(), + // ); + + // global_reentrancy_guard::non_reentrant_after(data_store); + // } + + + internal + +/// Handles error from order. + /// # Arguments + /// * `key` - The key of the deposit to handle error for. + /// * `starting_gas` - The starting gas of the transaction. + /// * `reason` - The reason of the error. + // fn handle_order_error( + // self: @ContractState, key: felt252, starting_gas: u256, reason_bytes: Array + // ) { + // let error_selector = error_utils::get_error_selector_from_data(reason_bytes.span()); + + // let mut base_order_handler_state = BaseOrderHandler::unsafe_new_contract_state(); + // let data_store = base_order_handler_state.data_store.read(); + + // let order = data_store.get_order(key); + // // let is_market_order = base_order_utils::is_market_order(order.order_type); + + // if (oracle_utils::is_oracle_error(error_selector) + // || order.is_frozen + // // || (!is_market_order && error_selector == PositionError::EMPTY_POSITION) + // || error_selector == OrderError::EMPTY_ORDER + // || error_selector == FeatureError::DISABLED_FEATURE + // || error_selector == OrderError::INVALID_KEEPER_FOR_FROZEN_ORDER + // || error_selector == OrderError::UNSUPPORTED_ORDER_TYPE + // || error_selector == OrderError::INVALID_ORDER_PRICES) { + // assert(false, error_utils::revert_with_custom_error(reason_bytes.span())) + // } + + // let reason = error_utils::get_revert_message(reason_bytes.span()); + + // if (is_market_order + // || error_selector == MarketError::INVALID_POSITION_MARKET + // || error_selector == MarketError::INVALID_COLLATERAL_TOKEN_FOR_MARKET + // || error_selector == PositionError::INVALID_POSITION_SIZE_VALUES) { + // order_utils::cancel_order( + // data_store, + // base_order_handler_state.event_emitter.read(), + // base_order_handler_state.order_vault.read(), + // key, + // order.account, + // starting_gas, + // reason, + // reason_bytes, + // ); + // return (); + // } + + // order_utils::freeze_order( + // data_store, + // base_order_handler_state.event_emitter.read(), + // base_order_handler_state.order_vault.read(), + // key, + // get_caller_address(), + // starting_gas, + // reason, + // reason_bytes + // ); + // } + diff --git a/src/exchange/deposit_handler.cairo b/src/exchange/deposit_handler.cairo index d0d2ca38..c128c4d5 100644 --- a/src/exchange/deposit_handler.cairo +++ b/src/exchange/deposit_handler.cairo @@ -66,16 +66,16 @@ mod DepositHandler { // ************************************************************************* // Core lib imports. - use starknet::ContractAddress; - + use starknet::{get_caller_address, get_contract_address, ContractAddress}; // Local imports. use super::IDepositHandler; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; + use satoru::role::role_module::{RoleModule, IRoleModule}; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::oracle::{ - oracle::{IOracleDispatcher, IOracleDispatcherTrait}, + oracle::{IOracleDispatcher, IOracleDispatcherTrait}, oracle_modules, oracle_modules::{with_oracle_prices_before, with_oracle_prices_after}, oracle_utils::{SetPricesParams, SimulatePricesParams} }; @@ -86,6 +86,14 @@ mod DepositHandler { deposit_utils::CreateDepositParams, deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait} }; + use satoru::deposit::deposit_utils; + use satoru::feature::feature_utils; + use satoru::gas::gas_utils; + use satoru::data::keys; + use satoru::exchange::exchange_utils; + use satoru::deposit::execute_deposit_utils; + use satoru::oracle::oracle_utils; + use satoru::utils::global_reentrancy_guard; // ************************************************************************* // STORAGE @@ -139,26 +147,105 @@ mod DepositHandler { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl DepositHandlerImpl of super::IDepositHandler { fn create_deposit( ref self: ContractState, account: ContractAddress, params: CreateDepositParams ) -> felt252 { - // TODO - 0 + let state: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); + IRoleModule::only_controller(@state); + + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + feature_utils::validate_feature( + self.data_store.read(), + keys::create_deposit_feature_disabled_key(get_contract_address()) + ); + + let key = deposit_utils::create_deposit( + self.data_store.read(), + self.event_emitter.read(), + self.deposit_vault.read(), + account, + params + ); + + global_reentrancy_guard::non_reentrant_after(data_store); + + key } - fn cancel_deposit(ref self: ContractState, key: felt252) { // TODO + fn cancel_deposit(ref self: ContractState, key: felt252) { + let state: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); + IRoleModule::only_controller(@state); + + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + // let starting_gas = gas_left(); + + let deposit = data_store.get_deposit(key); + + feature_utils::validate_feature( + data_store, keys::cancel_deposit_feature_disabled_key(get_contract_address()) + ); + exchange_utils::validate_request_cancellation( + data_store, deposit.updated_at_block, 'Deposit' + ); + + deposit_utils::cancel_deposit( + data_store, + self.event_emitter.read(), + self.deposit_vault.read(), + key, + deposit.account, + 0, //starting_gas + keys::user_initiated_cancel(), + array![''] + ); + + global_reentrancy_guard::non_reentrant_after(data_store); } - fn execute_deposit( - ref self: ContractState, key: felt252, oracle_params: SetPricesParams - ) { // TODO + fn execute_deposit(ref self: ContractState, key: felt252, oracle_params: SetPricesParams) { + let state: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); + IRoleModule::only_order_keeper(@state); + + let data_store = self.data_store.read(); + let oracle = self.oracle.read(); + let event_emitter = self.event_emitter.read(); + // global_reentrancy_guard::non_reentrant_before(data_store); + oracle_modules::with_oracle_prices_before( + oracle, data_store, event_emitter, @oracle_params + ); + + // let starting_gas = gas_left(); + let execution_gas = gas_utils::get_execution_gas(data_store, 0); + + self.execute_deposit_keeper(key, oracle_params, get_caller_address()); + + oracle_modules::with_oracle_prices_after(oracle); + // global_reentrancy_guard::non_reentrant_after(data_store); } fn simulate_execute_deposit( ref self: ContractState, key: felt252, params: SimulatePricesParams - ) { // TODO + ) { + let state: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); + IRoleModule::only_controller(@state); + + let data_store = self.data_store.read(); + let oracle = self.oracle.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + oracle_modules::with_simulated_oracle_prices_before(oracle, params); + + let oracleParams = Default::default(); + + self.execute_deposit_keeper(key, oracleParams, get_caller_address()); + + oracle_modules::with_simulated_oracle_prices_after(); + global_reentrancy_guard::non_reentrant_after(data_store); } fn execute_deposit_keeper( @@ -166,23 +253,49 @@ mod DepositHandler { key: felt252, oracle_params: SetPricesParams, keeper: ContractAddress - ) { // TODO - } - } + ) { + // let starting_gas = gas_left(); + let data_store = self.data_store.read(); + feature_utils::validate_feature( + data_store, keys::execute_deposit_feature_disabled_key(get_contract_address()) + ); + let min_oracle_block_numbers = oracle_utils::get_uncompacted_oracle_block_numbers( + oracle_params.compacted_min_oracle_block_numbers.span(), oracle_params.tokens.len() + ); - // ************************************************************************* - // INTERNAL FUNCTIONS - // ************************************************************************* - #[generate_trait] - impl InternalImpl of InternalTrait { - /// Handles error from deposit. - /// # Arguments - /// * `key` - The key of the deposit to handle error for. - /// * `starting_gas` - The starting gas of the transaction. - /// * `reason_bytes` - The reason of the error. - fn handle_deposit_error( - ref self: ContractState, key: felt252, starting_gas: u128, reason_bytes: Array - ) { // TODO + let max_oracle_block_numbers = oracle_utils::get_uncompacted_oracle_block_numbers( + oracle_params.compacted_max_oracle_block_numbers.span(), oracle_params.tokens.len() + ); + + let params = execute_deposit_utils::ExecuteDepositParams { + data_store, + event_emitter: self.event_emitter.read(), + deposit_vault: self.deposit_vault.read(), + oracle: self.oracle.read(), + key, + min_oracle_block_numbers, + max_oracle_block_numbers, + keeper, + starting_gas: 0 // TODO starting_gas + }; + + execute_deposit_utils::execute_deposit(params); } } +/// TODO no try catch, we need to find alternative +// // ************************************************************************* +// // INTERNAL FUNCTIONS +// // ************************************************************************* +// #[generate_trait] +// impl InternalImpl of InternalTrait { +// /// Handles error from deposit. +// /// # Arguments +// /// * `key` - The key of the deposit to handle error for. +// /// * `starting_gas` - The starting gas of the transaction. +// /// * `reason_bytes` - The reason of the error. +// fn handle_deposit_error( +// ref self: ContractState, key: felt252, starting_gas: u256, reason_bytes: Array +// ) { // TODO +// } +// } } diff --git a/src/exchange/exchange_utils.cairo b/src/exchange/exchange_utils.cairo index acb42cd5..8ff8f3d4 100644 --- a/src/exchange/exchange_utils.cairo +++ b/src/exchange/exchange_utils.cairo @@ -16,7 +16,7 @@ use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; fn validate_request_cancellation( data_store: IDataStoreDispatcher, created_at_block: u64, request_type: felt252 ) { - let request_expiration_age = data_store.get_u128(keys::request_expiration_block_age()); + let request_expiration_age = data_store.get_u256(keys::request_expiration_block_age()); let request_age = get_block_number() - created_at_block; if request_age.into() < request_expiration_age { diff --git a/src/exchange/liquidation_handler.cairo b/src/exchange/liquidation_handler.cairo index ed9ff523..68fabc1c 100644 --- a/src/exchange/liquidation_handler.cairo +++ b/src/exchange/liquidation_handler.cairo @@ -6,7 +6,7 @@ // Core lib imports. use core::traits::Into; -use starknet::ContractAddress; +use starknet::{ContractAddress, ClassHash}; // Local imports. use satoru::oracle::oracle_utils::SetPricesParams; @@ -40,15 +40,14 @@ mod LiquidationHandler { // ************************************************************************* // Core lib imports. - use satoru::exchange::base_order_handler::BaseOrderHandler::{ - event_emitter::InternalContractMemberStateTrait, data_store::InternalContractMemberStateImpl - }; - use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use starknet::{ContractAddress, get_caller_address, get_contract_address, ClassHash}; // Local imports. use super::ILiquidationHandler; use satoru::role::role_store::{IRoleStoreSafeDispatcher, IRoleStoreSafeDispatcherTrait}; + use satoru::role::role_module::{RoleModule, IRoleModule}; + use satoru::data::{ data_store::{IDataStoreSafeDispatcher, IDataStoreSafeDispatcherTrait, DataStore}, keys::execute_order_feature_disabled_key @@ -59,19 +58,28 @@ mod LiquidationHandler { oracle_utils::SetPricesParams }; use satoru::order::{ - order_utils, order::{SecondaryOrderType, OrderType, Order}, + order_utils::{IOrderUtilsDispatcher}, order::{SecondaryOrderType, OrderType, Order}, order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}, base_order_utils::{ExecuteOrderParams, ExecuteOrderParamsContracts} }; use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; use satoru::market::market::Market; - use satoru::exchange::base_order_handler::{IBaseOrderHandler, BaseOrderHandler}; + use satoru::exchange::{ + order_handler::{IOrderHandler, OrderHandler}, + base_order_handler::{IBaseOrderHandler, BaseOrderHandler} + }; + + use satoru::liquidation::liquidation_utils::create_liquidation_order; - use satoru::exchange::order_handler; - use debug::PrintTrait; use satoru::feature::feature_utils::validate_feature; - use satoru::exchange::order_handler::{IOrderHandler, OrderHandler}; - use satoru::utils::starknet_utils; + use satoru::utils::{starknet_utils, global_reentrancy_guard}; + use satoru::exchange::base_order_handler::BaseOrderHandler::{ + event_emitter::InternalContractMemberStateTrait, + data_store::InternalContractMemberStateImpl, + order_utils_lib::InternalContractMemberStateTrait as OrderUtilsTrait, + oracle::InternalContractMemberStateTrait as OracleStateTrait, + }; + use satoru::order::order_utils::IOrderUtilsDispatcherTrait; // ************************************************************************* // STORAGE @@ -100,7 +108,11 @@ mod LiquidationHandler { order_vault_address: ContractAddress, oracle_address: ContractAddress, swap_handler_address: ContractAddress, - referral_storage_address: ContractAddress + referral_storage_address: ContractAddress, + order_utils_class_hash: ClassHash, + increase_order_utils_class_hash: ClassHash, + decrease_order_utils_class_hash: ClassHash, + swap_order_utils_class_hash: ClassHash, ) { let mut state: BaseOrderHandler::ContractState = BaseOrderHandler::unsafe_new_contract_state(); @@ -112,16 +124,24 @@ mod LiquidationHandler { order_vault_address, oracle_address, swap_handler_address, - referral_storage_address + referral_storage_address, + order_utils_class_hash, + increase_order_utils_class_hash, + decrease_order_utils_class_hash, + swap_order_utils_class_hash, ); + let mut state: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); + IRoleModule::initialize(ref state, role_store_address,); } // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] - impl LiquidationHandlerImpl of super::ILiquidationHandler { // executes a position liquidation + #[abi(embed_v0)] + impl LiquidationHandlerImpl of super::ILiquidationHandler< + ContractState + > { // executes a position liquidation fn execute_liquidation( ref self: ContractState, account: ContractAddress, @@ -130,9 +150,23 @@ mod LiquidationHandler { is_long: bool, oracle_params: SetPricesParams ) { - let starting_gas: u128 = starknet_utils::sn_gasleft(array![100]); - let mut state_base: BaseOrderHandler::ContractState = + let mut state_base = BaseOrderHandler::unsafe_new_contract_state(); //retrieve BaseOrderHandler state + global_reentrancy_guard::non_reentrant_before(state_base.data_store.read()); + + // let mut role_state: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); TODO uncomment role + // IRoleModule::only_liquidation_keeper(@role_state); + + with_oracle_prices_before( + state_base.oracle.read(), + state_base.data_store.read(), + state_base.event_emitter.read(), + @oracle_params + ); + + // let starting_gas: u128 = starknet_utils::sn_gasleft(array![100]); TODO GAS + let starting_gas: u256 = 0; + let key: felt252 = create_liquidation_order( state_base.data_store.read(), state_base.event_emitter.read(), @@ -155,7 +189,10 @@ mod LiquidationHandler { params.contracts.data_store, execute_order_feature_disabled_key(get_contract_address(), params.order.order_type) ); - order_utils::execute_order(params); + state_base.order_utils_lib.read().execute_order_utils(params); + with_oracle_prices_after(state_base.oracle.read()); + + global_reentrancy_guard::non_reentrant_after(state_base.data_store.read()); } } } diff --git a/src/exchange/order_handler.cairo b/src/exchange/order_handler.cairo index b00d250d..da76b138 100644 --- a/src/exchange/order_handler.cairo +++ b/src/exchange/order_handler.cairo @@ -5,9 +5,7 @@ // ************************************************************************* // Core lib imports. -use core::traits::Into; use starknet::ContractAddress; - // Local imports. use satoru::oracle::oracle_utils::{SetPricesParams, SimulatePricesParams}; use satoru::order::{base_order_utils::CreateOrderParams, order::Order}; @@ -52,15 +50,15 @@ trait IOrderHandler { /// * `order` - The order to update that will be stored. /// # Returns /// The updated order. - fn update_order( - ref self: TContractState, - key: felt252, - size_delta_usd: u128, - acceptable_price: u128, - trigger_price: u128, - min_output_amount: u128, - order: Order - ) -> Order; + // fn update_order( + // ref self: TContractState, + // key: felt252, + // size_delta_usd: u256, + // acceptable_price: u256, + // trigger_price: u256, + // min_output_amount: u256, + // order: Order + // ) -> Order; /// Cancels the given order. The `cancelOrder()` feature must be enabled for the given order /// type. The caller must be the owner of the order. The order is cancelled by calling the `cancelOrder()` @@ -68,7 +66,7 @@ trait IOrderHandler { /// reason for cancellation, which is passed to the `cancelOrder()` function. /// # Arguments /// * `key` - The unique ID of the order to cancel. - fn cancel_order(ref self: TContractState, key: felt252); + // fn cancel_order(ref self: TContractState, key: felt252); /// Executes an order. /// # Arguments @@ -102,29 +100,30 @@ mod OrderHandler { // ************************************************************************* // Core lib imports. + use satoru::exchange::base_order_handler::BaseOrderHandler::order_utils_lib::InternalContractMemberStateTrait; + use satoru::order::order_utils::IOrderUtilsDispatcherTrait; use core::starknet::SyscallResultTrait; use core::traits::Into; use starknet::ContractAddress; - use starknet::{get_caller_address, get_contract_address}; + use starknet::{get_caller_address, get_contract_address, ClassHash}; use array::ArrayTrait; + use debug::PrintTrait; // Local imports. use super::IOrderHandler; - use satoru::oracle::{ - oracle_modules, oracle_utils, oracle_utils::{SetPricesParams, SimulatePricesParams} - }; - use satoru::order::{base_order_utils::CreateOrderParams, order_utils, order, base_order_utils}; + use satoru::oracle::oracle_modules; + + use satoru::oracle::oracle_utils::{SetPricesParams, SimulatePricesParams}; use satoru::order::{ + base_order_utils::CreateOrderParams, order_utils::{IOrderUtilsDispatcher}, order::{Order, OrderTrait, OrderType, SecondaryOrderType}, - order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}, order_store_utils, - order_event_utils + order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait} }; - use satoru::market::market::Market; - use satoru::market::error::MarketError; - use satoru::position::error::PositionError; - use satoru::feature::error::FeatureError; + // use satoru::market::error::MarketError; + // use satoru::position::error::PositionError; + // use satoru::feature::error::FeatureError; use satoru::order::error::OrderError; - use satoru::exchange::exchange_utils; + // use satoru::exchange::exchange_utils; use satoru::exchange::base_order_handler::{IBaseOrderHandler, BaseOrderHandler}; use satoru::exchange::base_order_handler::BaseOrderHandler::{ role_store::InternalContractMemberStateTrait as RoleStoreStateTrait, @@ -135,16 +134,19 @@ mod OrderHandler { oracle::InternalContractMemberStateTrait as OracleStateTrait, InternalTrait as BaseOrderHandleInternalTrait, }; - use satoru::feature::feature_utils; - use satoru::data::keys; - use satoru::role::role; + use satoru::feature::feature_utils::{validate_feature}; + use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; + use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; + use satoru::data::keys::{create_order_feature_disabled_key, execute_order_feature_disabled_key}; + use satoru::role::role::FROZEN_ORDER_KEEPER; use satoru::role::role_module::{RoleModule, IRoleModule}; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; - use satoru::token::token_utils; - use satoru::gas::gas_utils; - use satoru::chain::chain::Chain; - use satoru::utils::global_reentrancy_guard; - use satoru::utils::error_utils; + // use satoru::token::token_utils; + // use satoru::gas::gas_utils; + use satoru::utils::global_reentrancy_guard::{non_reentrant_before, non_reentrant_after}; + // use satoru::utils::error_utils; + use satoru::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + use starknet::contract_address_const; // ************************************************************************* // STORAGE @@ -173,7 +175,11 @@ mod OrderHandler { order_vault_address: ContractAddress, oracle_address: ContractAddress, swap_handler_address: ContractAddress, - referral_storage_address: ContractAddress + referral_storage_address: ContractAddress, + order_utils_class_hash: ClassHash, + increase_order_utils_class_hash: ClassHash, + decrease_order_utils_class_hash: ClassHash, + swap_order_utils_class_hash: ClassHash, ) { let mut state: BaseOrderHandler::ContractState = BaseOrderHandler::unsafe_new_contract_state(); @@ -185,7 +191,11 @@ mod OrderHandler { order_vault_address, oracle_address, swap_handler_address, - referral_storage_address + referral_storage_address, + order_utils_class_hash, + increase_order_utils_class_hash, + decrease_order_utils_class_hash, + swap_order_utils_class_hash ); } @@ -193,169 +203,61 @@ mod OrderHandler { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl OrderHandlerImpl of super::IOrderHandler { fn create_order( ref self: ContractState, account: ContractAddress, params: CreateOrderParams ) -> felt252 { // Check only controller. - let role_module_state = RoleModule::unsafe_new_contract_state(); - role_module_state.only_controller(); - + // let role_module_state = RoleModule::unsafe_new_contract_state(); // TODO uncomment role + // role_module_state.only_controller(); // Fetch data store. let base_order_handler_state = BaseOrderHandler::unsafe_new_contract_state(); let data_store = base_order_handler_state.data_store.read(); - global_reentrancy_guard::non_reentrant_before(data_store); + non_reentrant_before(data_store); // Validate feature and create order. - feature_utils::validate_feature( + validate_feature( data_store, - keys::create_order_feature_disabled_key(get_contract_address(), params.order_type) - ); - let key = order_utils::create_order( - data_store, - base_order_handler_state.event_emitter.read(), - base_order_handler_state.order_vault.read(), - base_order_handler_state.referral_storage.read(), - account, - params + create_order_feature_disabled_key(get_contract_address(), params.order_type) ); + let key = base_order_handler_state + .order_utils_lib + .read() + .create_order_utils( + data_store, + base_order_handler_state.event_emitter.read(), + base_order_handler_state.order_vault.read(), + base_order_handler_state.referral_storage.read(), + account, + params + ); - global_reentrancy_guard::non_reentrant_after(data_store); + non_reentrant_after(data_store); key } - fn update_order( - ref self: ContractState, - key: felt252, - size_delta_usd: u128, - acceptable_price: u128, - trigger_price: u128, - min_output_amount: u128, - order: Order - ) -> Order { - // Check only controller. - let role_module_state = RoleModule::unsafe_new_contract_state(); - role_module_state.only_controller(); - - // Fetch data store. - let base_order_handler_state = BaseOrderHandler::unsafe_new_contract_state(); - let data_store = base_order_handler_state.data_store.read(); - - global_reentrancy_guard::non_reentrant_before(data_store); - - // Validate feature. - feature_utils::validate_feature( - data_store, - keys::update_order_feature_disabled_key(get_contract_address(), order.order_type) - ); - - assert(base_order_utils::is_market_order(order.order_type), 'OrderNotUpdatable'); - - let mut updated_order = order.clone(); - updated_order.size_delta_usd = size_delta_usd; - updated_order.trigger_price = trigger_price; - updated_order.acceptable_price = acceptable_price; - updated_order.min_output_amount = min_output_amount; - updated_order.is_frozen = false; - - // Allow topping up of execution fee as frozen orders will have execution fee reduced. - let fee_token = token_utils::fee_token(data_store); - let order_vault = base_order_handler_state.order_vault.read(); - let received_fee_token = order_vault.record_transfer_in(fee_token); - updated_order.execution_fee = received_fee_token; - - let estimated_gas_limit = gas_utils::estimate_execute_order_gas_limit( - data_store, @updated_order - ); - gas_utils::validate_execution_fee( - data_store, estimated_gas_limit, updated_order.execution_fee - ); - - updated_order.touch(); - - base_order_utils::validate_non_empty_order(@updated_order); - - order_store_utils::set(data_store, key, @updated_order); - order_event_utils::emit_order_updated( - base_order_handler_state.event_emitter.read(), - key, - size_delta_usd, - acceptable_price, - trigger_price, - min_output_amount, - ); - - global_reentrancy_guard::non_reentrant_after(data_store); - - updated_order - } - - fn cancel_order(ref self: ContractState, key: felt252) { - let starting_gas: u128 = 0; // TODO: Get starting gas from Cairo. - - // Check only controller. - let role_module_state = RoleModule::unsafe_new_contract_state(); - role_module_state.only_controller(); - - // Fetch data store. - let base_order_handler_state = BaseOrderHandler::unsafe_new_contract_state(); - let data_store = base_order_handler_state.data_store.read(); - - global_reentrancy_guard::non_reentrant_before(data_store); - - let order = order_store_utils::get(data_store, key); - - // Validate feature. - feature_utils::validate_feature( - data_store, - keys::cancel_order_feature_disabled_key(get_contract_address(), order.order_type) - ); - - if base_order_utils::is_market_order(order.order_type) { - exchange_utils::validate_request_cancellation( - data_store, order.updated_at_block, 'Order' - ) - } - - order_utils::cancel_order( - data_store, - base_order_handler_state.event_emitter.read(), - base_order_handler_state.order_vault.read(), - key, - order.account, - starting_gas, - keys::user_initiated_cancel(), - ArrayTrait::::new(), - ); - - global_reentrancy_guard::non_reentrant_after(data_store); - } fn execute_order(ref self: ContractState, key: felt252, oracle_params: SetPricesParams) { // Check only order keeper. let role_module_state = RoleModule::unsafe_new_contract_state(); role_module_state.only_order_keeper(); - // Fetch data store. let base_order_handler_state = BaseOrderHandler::unsafe_new_contract_state(); let data_store = base_order_handler_state.data_store.read(); - - global_reentrancy_guard::non_reentrant_before(data_store); + // non_reentrant_before(data_store); oracle_modules::with_oracle_prices_before( base_order_handler_state.oracle.read(), data_store, base_order_handler_state.event_emitter.read(), @oracle_params ); - // TODO: Did not implement starting gas and try / catch logic as not available in Cairo self._execute_order(key, oracle_params, get_contract_address()); - oracle_modules::with_oracle_prices_after(base_order_handler_state.oracle.read()); - global_reentrancy_guard::non_reentrant_after(data_store); + // non_reentrant_after(data_store); } fn execute_order_keeper( @@ -378,16 +280,16 @@ mod OrderHandler { let base_order_handler_state = BaseOrderHandler::unsafe_new_contract_state(); let data_store = base_order_handler_state.data_store.read(); - global_reentrancy_guard::non_reentrant_before(data_store); - oracle_modules::with_simulated_oracle_prices_before( - base_order_handler_state.oracle.read(), params - ); + non_reentrant_before(data_store); + // oracle_modules::with_simulated_oracle_prices_before( + // base_order_handler_state.oracle.read(), params + // ); let oracle_params: SetPricesParams = Default::default(); self._execute_order(key, oracle_params, get_contract_address()); - oracle_modules::with_simulated_oracle_prices_after(); - global_reentrancy_guard::non_reentrant_after(data_store); + // oracle_modules::with_simulated_oracle_prices_after(); + non_reentrant_after(data_store); } } @@ -407,7 +309,7 @@ mod OrderHandler { oracle_params: SetPricesParams, keeper: ContractAddress ) { - let starting_gas: u128 = 0; // TODO: Get starting gas from Cairo. + let starting_gas: u256 = 100000; // TODO: Get starting gas from Cairo. // Check only self. let role_module_state = RoleModule::unsafe_new_contract_state(); @@ -424,73 +326,14 @@ mod OrderHandler { } // Validate feature. - feature_utils::validate_feature( + validate_feature( params.contracts.data_store, - keys::execute_order_feature_disabled_key( - get_contract_address(), params.order.order_type - ) + execute_order_feature_disabled_key(get_contract_address(), params.order.order_type) ); - order_utils::execute_order(params); + base_order_handler_state.order_utils_lib.read().execute_order_utils(params); } - /// Handles error from order. - /// # Arguments - /// * `key` - The key of the deposit to handle error for. - /// * `starting_gas` - The starting gas of the transaction. - /// * `reason` - The reason of the error. - fn handle_order_error( - self: @ContractState, key: felt252, starting_gas: u128, reason_bytes: Array - ) { - let error_selector = error_utils::get_error_selector_from_data(reason_bytes.span()); - - let mut base_order_handler_state = BaseOrderHandler::unsafe_new_contract_state(); - let data_store = base_order_handler_state.data_store.read(); - - let order = order_store_utils::get(data_store, key); - let is_market_order = base_order_utils::is_market_order(order.order_type); - - if (oracle_utils::is_oracle_error(error_selector) - || order.is_frozen - || (!is_market_order && error_selector == PositionError::EMPTY_POSITION) - || error_selector == OrderError::EMPTY_ORDER - || error_selector == FeatureError::DISABLED_FEATURE - || error_selector == OrderError::INVALID_KEEPER_FOR_FROZEN_ORDER - || error_selector == OrderError::UNSUPPORTED_ORDER_TYPE - || error_selector == OrderError::INVALID_ORDER_PRICES) { - assert(false, error_utils::revert_with_custom_error(reason_bytes.span())) - } - - let reason = error_utils::get_revert_message(reason_bytes.span()); - - if (is_market_order - || error_selector == MarketError::INVALID_POSITION_MARKET - || error_selector == MarketError::INVALID_COLLATERAL_TOKEN_FOR_MARKET - || error_selector == PositionError::INVALID_POSITION_SIZE_VALUES) { - order_utils::cancel_order( - data_store, - base_order_handler_state.event_emitter.read(), - base_order_handler_state.order_vault.read(), - key, - order.account, - starting_gas, - reason, - reason_bytes, - ); - return (); - } - - order_utils::freeze_order( - data_store, - base_order_handler_state.event_emitter.read(), - base_order_handler_state.order_vault.read(), - key, - get_caller_address(), - starting_gas, - reason, - reason_bytes - ); - } /// Validate that the keeper is a frozen order keeper. /// # Arguments @@ -500,7 +343,7 @@ mod OrderHandler { let role_store = base_order_handler_state.role_store.read(); assert( - role_store.has_role(keeper, role::FROZEN_ORDER_KEEPER), + role_store.has_role(keeper, FROZEN_ORDER_KEEPER), OrderError::INVALID_FROZEN_ORDER_KEEPER ); } diff --git a/src/exchange/withdrawal_handler.cairo b/src/exchange/withdrawal_handler.cairo index c5fbc24e..dac9aaba 100644 --- a/src/exchange/withdrawal_handler.cairo +++ b/src/exchange/withdrawal_handler.cairo @@ -57,7 +57,6 @@ mod WithdrawalHandler { use starknet::{ContractAddress, get_contract_address, get_caller_address}; use traits::Default; use clone::Clone; - // Local imports. use super::IWithdrawalHandler; use satoru::role::{role, role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}}; @@ -136,7 +135,7 @@ mod WithdrawalHandler { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl WithdrawalHandlerImpl of super::IWithdrawalHandler { fn create_withdrawal( ref self: ContractState, account: ContractAddress, params: CreateWithdrawalParams @@ -175,8 +174,8 @@ mod WithdrawalHandler { global_reentrancy_guard::non_reentrant_before(data_store); // Initiates re-entrancy - let starting_gas = starknet_utils::sn_gasleft(array![100]); // Returns 100 for now, - let withdrawal = data_store.get_withdrawal(key).unwrap(); // Panics if Option::None + let starting_gas = starknet_utils::sn_gasleft(array![200]); // Returns 200 for now, + let withdrawal = data_store.get_withdrawal(key); feature_utils::validate_feature( data_store, keys::cancel_withdrawal_feature_disabled_key(get_contract_address()) @@ -231,19 +230,19 @@ mod WithdrawalHandler { price_feed_tokens: oracle_params.price_feed_tokens.clone(), }; // withOraclePrices - oracle_modules::with_oracle_prices_before( - self.oracle.read(), - self.data_store.read(), - self.event_emitter.read(), - @oracle_params - ); + // oracle_modules::with_oracle_prices_before( + // self.oracle.read(), + // self.data_store.read(), + // self.event_emitter.read(), + // @oracle_params + // ); let starting_gas = starknet_utils::sn_gasleft(array![100]); let execution_gas = gas_utils::get_execution_gas(data_store, starting_gas); self.execute_withdrawal_keeper(key, oracle_params_copy, get_caller_address()); - oracle_modules::with_oracle_prices_after(self.oracle.read()); + // oracle_modules::with_oracle_prices_after(self.oracle.read()); global_reentrancy_guard::non_reentrant_after(data_store); // Finalizes re-entrancy } @@ -297,7 +296,7 @@ mod WithdrawalHandler { /// * `starting_gas` - The starting gas of the transaction. /// * `reason_bytes` - The reason of the error. fn handle_withdrawal_error( - ref self: ContractState, key: felt252, starting_gas: u128, reason_bytes: Array + ref self: ContractState, key: felt252, starting_gas: u256, reason_bytes: Array ) { // Just cancels withdrawal. There is no way to handle revert and revert reason right now. diff --git a/src/feature/feature_utils.cairo b/src/feature/feature_utils.cairo index f7b52440..c2a1e84f 100644 --- a/src/feature/feature_utils.cairo +++ b/src/feature/feature_utils.cairo @@ -16,10 +16,7 @@ use satoru::feature::error::FeatureError; /// # Returns /// whether the feature is disabled. fn is_feature_disabled(data_store: IDataStoreDispatcher, key: felt252) -> bool { - match data_store.get_bool(key) { - Option::Some(value) => value, - Option::None => false - } + data_store.get_bool(key) } /// Validate whether a feature is enabled, reverts if the feature is disabled. diff --git a/src/fee/fee_handler.cairo b/src/fee/fee_handler.cairo index eeda6679..ea7f8e76 100644 --- a/src/fee/fee_handler.cairo +++ b/src/fee/fee_handler.cairo @@ -44,7 +44,6 @@ mod FeeHandler { use core::zeroable::Zeroable; use starknet::{get_caller_address, ContractAddress, contract_address_const}; - use debug::PrintTrait; // Local imports. use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; @@ -92,7 +91,7 @@ mod FeeHandler { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl FeeHandlerImpl of super::IFeeHandler { fn initialize( ref self: ContractState, diff --git a/src/fee/fee_utils.cairo b/src/fee/fee_utils.cairo index 668c2b66..a196bdb0 100644 --- a/src/fee/fee_utils.cairo +++ b/src/fee/fee_utils.cairo @@ -5,11 +5,11 @@ use starknet::ContractAddress; // Local imports. +use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::data::keys::{ - claim_fee_amount_key, claim_ui_fee_amount_key, claim_ui_fee_amount_for_account_key -}; +use satoru::market::{market, market_utils::validate_market_token_balance_with_address}; +use satoru::data::keys; use satoru::utils::account_utils::validate_receiver; /// Increment the claimable fee amount for the specified market. @@ -25,16 +25,16 @@ fn increment_claimable_fee_amount( event_emitter: IEventEmitterDispatcher, market: ContractAddress, token: ContractAddress, - delta: u128, + delta: u256, fee_type: felt252, ) { if delta == 0 { return; } - let key = claim_fee_amount_key(market, token); + let key = keys::claimable_fee_amount_key(market, token); - let next_value = data_store.increment_u128(key, delta); + let next_value = data_store.increment_u256(key, delta); event_emitter.emit_claimable_fee_amount_updated(market, token, delta, next_value, fee_type); } @@ -54,7 +54,7 @@ fn increment_claimable_ui_fee_amount( ui_fee_receiver: ContractAddress, market: ContractAddress, token: ContractAddress, - delta: u128, + delta: u256, fee_type: felt252, ) { if delta == 0 { @@ -62,10 +62,12 @@ fn increment_claimable_ui_fee_amount( } let next_value = data_store - .increment_u128(claim_ui_fee_amount_for_account_key(market, token, ui_fee_receiver), delta); - - let next_pool_value = data_store.increment_u128(claim_ui_fee_amount_key(market, token), delta); + .increment_u256( + keys::claimable_ui_fee_amount_for_account_key(market, token, ui_fee_receiver), delta + ); + let next_pool_value = data_store + .increment_u256(keys::claimable_ui_fee_amount_key(market, token), delta); event_emitter .emit_claimable_ui_fee_amount_updated( ui_fee_receiver, market, token, delta, next_value, next_pool_value, fee_type @@ -85,7 +87,19 @@ fn claim_fees( market: ContractAddress, token: ContractAddress, receiver: ContractAddress, -) { // TODO +) { + validate_receiver(receiver); + + let key = keys::claimable_fee_amount_key(market, token); + + let fee_amount = data_store.get_u256(key); + data_store.set_u256(key, 0); + + IBankDispatcher { contract_address: market }.transfer_out(market, token, receiver, fee_amount); + + validate_market_token_balance_with_address(data_store, market); + + event_emitter.emit_fees_claimed(market, receiver, fee_amount); } /// Claim ui fees for the specified market. @@ -103,7 +117,22 @@ fn claim_ui_fees( market: ContractAddress, token: ContractAddress, receiver: ContractAddress, -) -> u128 { - // TODO - 0 +) -> u256 { + validate_receiver(receiver); + + let key = keys::claimable_ui_fee_amount_for_account_key(market, token, ui_fee_receiver); + let fee_amount = data_store.get_u256(key); + data_store.set_u256(key, 0); + + let next_pool_value = data_store + .decrement_u256(keys::claimable_ui_fee_amount_key(market, token), fee_amount); + + IBankDispatcher { contract_address: market }.transfer_out(market, token, receiver, fee_amount); + + validate_market_token_balance_with_address(data_store, market); + + event_emitter + .emit_ui_fees_claimed(ui_fee_receiver, market, receiver, fee_amount, next_pool_value); + + fee_amount } diff --git a/src/gas/gas_utils.cairo b/src/gas/gas_utils.cairo index ddfaf1d5..fba55c3f 100644 --- a/src/gas/gas_utils.cairo +++ b/src/gas/gas_utils.cairo @@ -8,8 +8,10 @@ use starknet::ContractAddress; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::data::keys; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; use satoru::order::{ + order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}, order::{Order, DecreasePositionSwapType}, base_order_utils::{is_increase_order, is_decrease_order, is_swap_order, OrderError} }; @@ -29,13 +31,13 @@ use satoru::gas::error::GasError; /// * `data_store` - The data storage dispatcher. /// # Returns /// The MIN_HANDLE_EXECUTION_ERROR_GAS. -fn get_min_handle_execution_error_gas(data_store: IDataStoreDispatcher) -> u128 { - data_store.get_u128(keys::min_handle_execution_error_gas()) +fn get_min_handle_execution_error_gas(data_store: IDataStoreDispatcher) -> u256 { + data_store.get_u256(keys::min_handle_execution_error_gas()) } /// Check that starting gas is higher than min handle execution gas and return starting. /// gas minus min_handle_error_gas. -fn get_execution_gas(data_store: IDataStoreDispatcher, starting_gas: u128) -> u128 { +fn get_execution_gas(data_store: IDataStoreDispatcher, starting_gas: u256) -> u256 { let min_handle_error_gas = get_min_handle_execution_error_gas(data_store); if starting_gas < min_handle_error_gas { panic(array![GasError::INSUFF_EXEC_GAS]); @@ -57,9 +59,9 @@ fn get_execution_gas(data_store: IDataStoreDispatcher, starting_gas: u128) -> u1 fn pay_execution_fee( data_store: IDataStoreDispatcher, event_emitter: IEventEmitterDispatcher, - bank: IWithdrawalVaultDispatcher, - execution_fee: u128, - starting_gas: u128, + bank: IBankDispatcher, + execution_fee: u256, + starting_gas: u256, keeper: ContractAddress, refund_receiver: ContractAddress ) { @@ -77,7 +79,7 @@ fn pay_execution_fee( execution_fee_for_keeper = execution_fee; } - bank.transfer_out(fee_token, keeper, execution_fee_for_keeper); + bank.transfer_out(bank.contract_address, fee_token, keeper, execution_fee_for_keeper); event_emitter.emit_keeper_execution_fee(keeper, execution_fee_for_keeper); @@ -88,7 +90,7 @@ fn pay_execution_fee( return; } - bank.transfer_out(fee_token, refund_receiver, refund_fee_amount); + bank.transfer_out(bank.contract_address, fee_token, refund_receiver, refund_fee_amount); event_emitter.emit_execution_fee_refund(refund_receiver, refund_fee_amount); } @@ -97,8 +99,87 @@ fn pay_execution_fee_deposit( data_store: IDataStoreDispatcher, event_emitter: IEventEmitterDispatcher, bank: IDepositVaultDispatcher, - execution_fee: u128, - starting_gas: u128, + execution_fee: u256, + starting_gas: u256, + keeper: ContractAddress, + refund_receiver: ContractAddress +) { + let fee_token: ContractAddress = token_utils::fee_token(data_store); + + // 63/64 gas is forwarded to external calls, reduce the startingGas to account for this + // let reduced_starting_gas = starting_gas - sn_gasleft(array![100]) / 63; + // let gas_used = reduced_starting_gas - sn_gasleft(array![100]); + + let gas_used = 0; + // each external call forwards 63/64 of the remaining gas + let mut execution_fee_for_keeper = adjust_gas_usage(data_store, gas_used) + * sn_gasprice(array![10]); + + if (execution_fee_for_keeper > execution_fee) { + execution_fee_for_keeper = execution_fee; + } + + bank.transfer_out(bank.contract_address, fee_token, keeper, execution_fee_for_keeper); + + event_emitter.emit_keeper_execution_fee(keeper, execution_fee_for_keeper); + + let refund_fee_amount = execution_fee - execution_fee_for_keeper; + + let refund_fee_amount = execution_fee - execution_fee_for_keeper; + if (refund_fee_amount == 0) { + return; + } + + bank.transfer_out(bank.contract_address, fee_token, refund_receiver, refund_fee_amount); + + event_emitter.emit_execution_fee_refund(refund_receiver, refund_fee_amount); +} + +fn pay_execution_fee_order( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + bank: IOrderVaultDispatcher, + execution_fee: u256, + starting_gas: u256, + keeper: ContractAddress, + refund_receiver: ContractAddress +) { + let fee_token: ContractAddress = token_utils::fee_token(data_store); + + // 63/64 gas is forwarded to external calls, reduce the startingGas to account for this + let reduced_starting_gas = starting_gas - sn_gasleft(array![100]) / 63; + let gas_used = reduced_starting_gas - sn_gasleft(array![0]); + + // each external call forwards 63/64 of the remaining gas + let mut execution_fee_for_keeper = adjust_gas_usage(data_store, gas_used) + * sn_gasprice(array![10]); + + if (execution_fee_for_keeper > execution_fee) { + execution_fee_for_keeper = execution_fee; + } + + bank.transfer_out(bank.contract_address, fee_token, keeper, execution_fee_for_keeper); + + event_emitter.emit_keeper_execution_fee(keeper, execution_fee_for_keeper); + + let refund_fee_amount = execution_fee - execution_fee_for_keeper; + + let refund_fee_amount = execution_fee - execution_fee_for_keeper; + if (refund_fee_amount == 0) { + return; + } + + bank.transfer_out(bank.contract_address, fee_token, refund_receiver, refund_fee_amount); + + event_emitter.emit_execution_fee_refund(refund_receiver, refund_fee_amount); +} + +fn pay_execution_fee_withdrawal( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + bank: IWithdrawalVaultDispatcher, + execution_fee: u256, + starting_gas: u256, keeper: ContractAddress, refund_receiver: ContractAddress ) { @@ -116,7 +197,7 @@ fn pay_execution_fee_deposit( execution_fee_for_keeper = execution_fee; } - bank.transfer_out(fee_token, keeper, execution_fee_for_keeper); + bank.transfer_out(bank.contract_address, fee_token, keeper, execution_fee_for_keeper); event_emitter.emit_keeper_execution_fee(keeper, execution_fee_for_keeper); @@ -127,7 +208,7 @@ fn pay_execution_fee_deposit( return; } - bank.transfer_out(fee_token, refund_receiver, refund_fee_amount); + bank.transfer_out(bank.contract_address, fee_token, refund_receiver, refund_fee_amount); event_emitter.emit_execution_fee_refund(refund_receiver, refund_fee_amount); } @@ -140,7 +221,7 @@ fn pay_execution_fee_deposit( /// # Returns /// * The key for the account order list. fn validate_execution_fee( - data_store: IDataStoreDispatcher, estimated_gas_limit: u128, execution_fee: u128 + data_store: IDataStoreDispatcher, estimated_gas_limit: u256, execution_fee: u256 ) { let gas_limit = adjust_gas_limit_for_estimate(data_store, estimated_gas_limit); let min_execution_fee = gas_limit * sn_gasprice(array![10]); @@ -153,7 +234,7 @@ fn validate_execution_fee( /// # Arguments /// * `data_store` - The data storage contract dispatcher. /// * `gas_used` - The amount of gas used. -fn adjust_gas_usage(data_store: IDataStoreDispatcher, gas_used: u128) -> u128 { +fn adjust_gas_usage(data_store: IDataStoreDispatcher, gas_used: u256) -> u256 { // gas measurements are done after the call to with_oracle_prices // with_oracle_prices may consume a significant amount of gas // the base_gas_limit used to calculate the execution cost @@ -161,12 +242,12 @@ fn adjust_gas_usage(data_store: IDataStoreDispatcher, gas_used: u128) -> u128 { // additionally, a transaction could fail midway through an execution transaction // before being cancelled, the possibility of this additional gas cost should // be considered when setting the base_gas_limit - let base_gas_limit = data_store.get_u128(keys::execution_gas_fee_base_amount()); + let base_gas_limit = data_store.get_u256(keys::execution_gas_fee_base_amount()); // the gas cost is estimated based on the gasprice of the request txn // the actual cost may be higher if the gasprice is higher in the execution txn // the multiplier_factor should be adjusted to account for this - let multiplier_factor = data_store.get_u128(keys::execution_gas_fee_multiplier_factor()); - base_gas_limit + precision::apply_factor_u128(gas_used, multiplier_factor) + let multiplier_factor = data_store.get_u256(keys::execution_gas_fee_multiplier_factor()); + base_gas_limit + precision::apply_factor_u256(gas_used, multiplier_factor) } /// Adjust the estimated gas limit to help ensure the execution fee is sufficient during the actual execution. @@ -176,29 +257,29 @@ fn adjust_gas_usage(data_store: IDataStoreDispatcher, gas_used: u128) -> u128 { /// # Returns /// The adjusted gas limit fn adjust_gas_limit_for_estimate( - data_store: IDataStoreDispatcher, estimated_gas_limit: u128 -) -> u128 { - let base_gas_limit = data_store.get_u128(keys::estimated_gas_fee_base_amount()); - let multiplier_factor = data_store.get_u128(keys::estimated_gas_fee_multiplier_factor()); - base_gas_limit + precision::apply_factor_u128(estimated_gas_limit, multiplier_factor) + data_store: IDataStoreDispatcher, estimated_gas_limit: u256 +) -> u256 { + let base_gas_limit = data_store.get_u256(keys::estimated_gas_fee_base_amount()); + let multiplier_factor = data_store.get_u256(keys::estimated_gas_fee_multiplier_factor()); + base_gas_limit + precision::apply_factor_u256(estimated_gas_limit, multiplier_factor) } /// The estimated gas limit for deposits. /// # Arguments /// * `data_store` - The data storage contract dispatcher. /// * `deposit` - The deposit to estimate the gas limit for. -fn estimate_execute_deposit_gas_limit(data_store: IDataStoreDispatcher, deposit: Deposit) -> u128 { - let gas_per_swap = data_store.get_u128(keys::single_swap_gas_limit()); +fn estimate_execute_deposit_gas_limit(data_store: IDataStoreDispatcher, deposit: Deposit) -> u256 { + let gas_per_swap = data_store.get_u256(keys::single_swap_gas_limit()); let swap_count = deposit.long_token_swap_path.len() + deposit.short_token_swap_path.len(); let gas_for_swaps = swap_count.into() * gas_per_swap; if (deposit.initial_long_token_amount == 0 || deposit.initial_short_token_amount == 0) { - return data_store.get_u128(keys::deposit_gas_limit_key(true)) + return data_store.get_u256(keys::deposit_gas_limit_key(true)) + deposit.callback_gas_limit + gas_for_swaps; } - return data_store.get_u128(keys::deposit_gas_limit_key(false)) + return data_store.get_u256(keys::deposit_gas_limit_key(false)) + deposit.callback_gas_limit + gas_for_swaps; } @@ -209,11 +290,11 @@ fn estimate_execute_deposit_gas_limit(data_store: IDataStoreDispatcher, deposit: /// * `withdrawal` - The withdrawal to estimate the gas limit for. fn estimate_execute_withdrawal_gas_limit( data_store: IDataStoreDispatcher, withdrawal: Withdrawal -) -> u128 { - let gas_per_swap = data_store.get_u128(keys::single_swap_gas_limit()); +) -> u256 { + let gas_per_swap = data_store.get_u256(keys::single_swap_gas_limit()); let swap_count = withdrawal.long_token_swap_path.len() + withdrawal.short_token_swap_path.len(); let gas_for_swaps = swap_count.into() * gas_per_swap; - return data_store.get_u128(keys::withdrawal_gas_limit_key()) + return data_store.get_u256(keys::withdrawal_gas_limit_key()) + withdrawal.callback_gas_limit + gas_for_swaps; } @@ -222,7 +303,7 @@ fn estimate_execute_withdrawal_gas_limit( /// # Arguments /// * `data_store` - The data storage contract dispatcher. /// * `order` - The order to estimate the gas limit for. -fn estimate_execute_order_gas_limit(data_store: IDataStoreDispatcher, order: @Order) -> u128 { +fn estimate_execute_order_gas_limit(data_store: IDataStoreDispatcher, order: @Order) -> u256 { if (is_increase_order(*order.order_type)) { return estimate_execute_increase_order_gas_limit(data_store, *order); } @@ -245,9 +326,9 @@ fn estimate_execute_order_gas_limit(data_store: IDataStoreDispatcher, order: @Or /// * `order` - The order to estimate the gas limit for. fn estimate_execute_increase_order_gas_limit( data_store: IDataStoreDispatcher, order: Order -) -> u128 { - let gas_per_swap = data_store.get_u128(keys::single_swap_gas_limit_key()); - return data_store.get_u128(keys::increase_order_gas_limit_key()) +) -> u256 { + let gas_per_swap = data_store.get_u256(keys::single_swap_gas_limit_key()); + return data_store.get_u256(keys::increase_order_gas_limit_key()) + gas_per_swap * order.swap_path.len().into() + order.callback_gas_limit; } @@ -258,12 +339,12 @@ fn estimate_execute_increase_order_gas_limit( /// * `order` - The order to estimate the gas limit for. fn estimate_execute_decrease_order_gas_limit( data_store: IDataStoreDispatcher, order: Order -) -> u128 { - let mut gas_per_swap = data_store.get_u128(keys::single_swap_gas_limit_key()); +) -> u256 { + let mut gas_per_swap = data_store.get_u256(keys::single_swap_gas_limit_key()); if (order.decrease_position_swap_type != DecreasePositionSwapType::NoSwap) { gas_per_swap += 1; } - return data_store.get_u128(keys::decrease_order_gas_limit_key()) + return data_store.get_u256(keys::decrease_order_gas_limit_key()) + gas_per_swap * order.swap_path.len().into() + order.callback_gas_limit; } @@ -272,9 +353,9 @@ fn estimate_execute_decrease_order_gas_limit( /// # Arguments /// * `data_store` - The data storage contract dispatcher. /// * `order` - The order to estimate the gas limit for. -fn estimate_execute_swap_order_gas_limit(data_store: IDataStoreDispatcher, order: Order) -> u128 { - let gas_per_swap = data_store.get_u128(keys::single_swap_gas_limit_key()); - return data_store.get_u128(keys::swap_order_gas_limit_key()) +fn estimate_execute_swap_order_gas_limit(data_store: IDataStoreDispatcher, order: Order) -> u256 { + let gas_per_swap = data_store.get_u256(keys::single_swap_gas_limit_key()); + return data_store.get_u256(keys::swap_order_gas_limit_key()) + gas_per_swap * order.swap_path.len().into() + order.callback_gas_limit; } diff --git a/src/lib.cairo b/src/lib.cairo index 7fdf19d3..aa1fb618 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -65,7 +65,7 @@ mod deposit { // `exchange` contains main satoru handlers to create and execute actions. mod exchange { - mod adl_handler; + // mod adl_handler; mod base_order_handler; mod deposit_handler; mod error; @@ -144,13 +144,17 @@ mod utils { mod global_reentrancy_guard; mod precision; mod span32; - mod u128_mask; + mod u256_mask; mod hash; - mod i128; + mod i256; + mod i256_test_storage_contract; mod store_arrays; mod error_utils; mod starknet_utils; mod traits; + mod default; + mod serializable_dict; + mod felt_math; } // `liquidation` function to help with liquidations. @@ -160,19 +164,20 @@ mod liquidation { // `market` contains market management functions. mod market { - mod market_utils; mod error; - mod market_token; - mod market_factory; mod market; + mod market_factory; mod market_pool_value_info; - mod market_event_utils; + mod market_store_utils; + mod market_token; + mod market_utils; } mod mock { mod error; mod governable; mod referral_storage; + mod mock_account; } // `oracle` contains functions related to oracles used by Satoru. @@ -183,6 +188,9 @@ mod oracle { mod oracle_utils; mod oracle; mod price_feed; + mod interfaces { + mod account; + } } // `order` contains order management functions. @@ -193,9 +201,8 @@ mod order { mod increase_order_utils; mod order_vault; mod order; - mod order_store_utils; - mod order_event_utils; mod error; + mod swap_order_utils; } // `position` contains positions management functions @@ -212,6 +219,7 @@ mod position { // `pricing` contains pricing utils mod pricing { + mod error; mod position_pricing_utils; mod pricing_utils; mod swap_pricing_utils; @@ -236,118 +244,11 @@ mod token { mod token_utils; } -// This is a temporary solution for tests until they resolve the issue (https://github.com/foundry-rs/starknet-foundry/issues/647) -mod tests { - mod adl { - mod test_adl_utils; - } - mod bank { - mod test_bank; - } - mod callback { - mod test_callback_utils; - } - mod config { - mod test_config; - } - mod data { - mod test_data_store; - mod test_deposit_store; - mod test_keys; - mod test_market; - mod test_order; - mod test_position; - mod test_withdrawal; - } - mod deposit { - mod test_deposit_utils; - mod test_deposit_vault; - mod test_execute_deposit_utils; - } - mod event { - mod test_adl_events_emitted; - mod test_callback_events_emitted; - mod test_config_events_emitted; - mod test_gas_events_emitted; - mod test_market_events_emitted; - mod test_oracle_events_emitted; - mod test_order_events_emitted; - mod test_position_events_emitted; - mod test_pricing_events_emitted; - mod test_referral_events_emitted; - mod test_swap_events_emitted; - mod test_timelock_events_emitted; - mod test_withdrawal_events_emitted; - } - mod exchange { - mod test_liquidation_handler; - mod test_withdrawal_handler; - } - mod feature { - mod test_feature_utils; - } - mod fee { - mod test_fee_handler; - mod test_fee_utils; - } - mod market { - mod test_market_factory; - mod test_market_token; - mod test_market_utils; - } - mod nonce { - mod test_nonce_utils; - } - mod oracle { - mod test_oracle; - } - mod order { - mod test_base_order_utils; - mod test_order; - } - mod position { - mod test_decrease_position_swap_utils; - mod test_position_utils; - } - mod price { - mod test_price; - } - - mod reader { - mod test_reader; - } - - mod role { - mod test_role_module; - mod test_role_store; - } - mod router { - mod test_router; - } - mod swap { - mod test_swap_handler; - } - mod utils { - mod test_account_utils; - mod test_arrays; - mod test_basic_multicall; - mod test_calc; - mod test_enumerable_set; - mod test_precision; - mod test_reentrancy_guard; - mod test_starknet_utils; - mod test_u128_mask; - mod test_i128; - } - mod mock { - mod test_referral_utils; - mod test_governable; - mod test_referral_storage; - } +mod test_utils { + mod deposit_setup; + mod tests_lib; } -mod tests_lib; - // `withdrawal` contains withdrawal management functions mod withdrawal { mod error; diff --git a/src/liquidation/liquidation_utils.cairo b/src/liquidation/liquidation_utils.cairo index d2c65d96..c7d60bfc 100644 --- a/src/liquidation/liquidation_utils.cairo +++ b/src/liquidation/liquidation_utils.cairo @@ -6,7 +6,14 @@ use starknet::ContractAddress; // Local imports. use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::data::keys; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::position::position_utils::get_position_key; +use satoru::order::order::{SecondaryOrderType, OrderType, Order, DecreasePositionSwapType}; +use satoru::callback::callback_utils::get_saved_callback_contract; +use satoru::utils::span32::{Span32, Array32Trait}; +use satoru::nonce::nonce_utils::get_next_key; +use integer::BoundedInt; /// Creates a liquidation order for a position. /// # Arguments @@ -24,6 +31,49 @@ fn create_liquidation_order( collateral_token: ContractAddress, is_long: bool ) -> felt252 { - // TODO - 0 + let key = get_position_key(account, market, collateral_token, is_long); + let position = data_store.get_position(key); + let callback_contract = get_saved_callback_contract(data_store, account, market); + let acceptable_price = if position.is_long { + 0 + } else { + BoundedInt::::max() + }; + let callback_gas_limit = data_store.get_u256(keys::max_callback_gas_limit()); + let swap_path = Array32Trait::::span32(@ArrayTrait::new()); + let updated_at_block = starknet::info::get_block_number(); + let size_delta_usd = position.size_in_usd; + let trigger_price = 0; + let min_output_amount = 0; + + let order = Order { + key, + order_type: OrderType::Liquidation, + decrease_position_swap_type: DecreasePositionSwapType::SwapPnlTokenToCollateralToken, + account, + receiver: account, + callback_contract, + ui_fee_receiver: 0.try_into().unwrap(), + market, + initial_collateral_token: position.collateral_token, + swap_path, + size_delta_usd, + initial_collateral_delta_amount: 0, + trigger_price, + acceptable_price, + execution_fee: 0, + callback_gas_limit, + min_output_amount: 0, + updated_at_block, + is_long: position.is_long, + is_frozen: false, + }; + let nonce_key = get_next_key(data_store); + data_store.set_order(nonce_key, order); + event_emitter + .emit_order_updated( + nonce_key, size_delta_usd, acceptable_price, trigger_price, min_output_amount + ); + + nonce_key } diff --git a/src/market/error.cairo b/src/market/error.cairo index 30d91b56..321d0988 100644 --- a/src/market/error.cairo +++ b/src/market/error.cairo @@ -1,22 +1,149 @@ mod MarketError { use starknet::ContractAddress; - const MARKET_NOT_FOUND: felt252 = 'market_not_found'; const DIVISOR_CANNOT_BE_ZERO: felt252 = 'zero_divisor'; const INVALID_MARKET_PARAMS: felt252 = 'invalid_market_params'; const OPEN_INTEREST_CANNOT_BE_UPDATED_FOR_SWAP_ONLY_MARKET: felt252 = 'oi_not_updated_swap_only_market'; - const MAX_OPEN_INTEREST_EXCEEDED: felt252 = 'max_open_interest_exceeded'; + const INVALID_SWAP_MARKET: felt252 = 'invalid_swap_market'; + const EMPTY_ADDRESS_IN_MARKET_TOKEN_BALANCE_VALIDATION: felt252 = + '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_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: u256) { + panic( + array!['empty_market_token_supply', supply.try_into().expect('u256 into felt failed')] + ) + } + + 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: u256) { + panic( + array![ + 'unable_to_get_funding_factor', + total_open_interest.try_into().expect('u256 into felt failed') + ] + ) + } + + fn MAX_SWAP_PATH_LENGTH_EXCEEDED(token_swap_path_length: u32, max_swap_path_length: u256) { + panic( + array![ + 'max_swap_path_length_exceeded', + token_swap_path_length.into(), + max_swap_path_length.try_into().expect('u256 into felt failed') + ] + ) + } + + 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: u256, max_ui_fee_factor: u256) { + panic( + array![ + 'ui_fee_factor_exceeded', + ui_fee_factor.try_into().expect('u256 into felt failed'), + max_ui_fee_factor.try_into().expect('u256 into felt failed') + ] + ) + } + + fn INVALID_MARKET_TOKEN_BALANCE_FOR_COLLATERAL_AMOUNT(balance: u256, collateral_amount: u256) { + panic( + array![ + 'invalid_mrkt_tkn_balance_col', + balance.try_into().expect('u256 into felt failed'), + collateral_amount.try_into().expect('u256 into felt failed') + ] + ) + } + + fn INVALID_MARKET_TOKEN_BALANCE_FOR_CLAIMABLE_FUNDING( + balance: u256, claimable_funding_fee_amount: u256 + ) { + panic( + array![ + 'invalid_mrkt_tkn_balance_clm', + balance.try_into().expect('u256 into felt failed'), + claimable_funding_fee_amount.try_into().expect('u256 into felt failed') + ] + ) + } + + fn UNABLE_TO_GET_BORROWING_FACTOR_EMPTY_POOL_USD(pool_usd: u256) { + panic( + array![ + 'unable_to_get_borrowing_factor', + pool_usd.try_into().expect('u256 into felt failed') + ] + ) + } + + fn MAX_OPEN_INTEREST_EXCEDEED(open_interest: u256, max_open_interest: u256) { + panic( + array![ + 'max_open_interest_exceeded', + open_interest.try_into().expect('u256 into felt failed'), + max_open_interest.try_into().expect('u256 into felt failed') + ] + ) + } + + fn UNABLE_TO_GET_CACHED_TOKEN_PRICE(token_in: ContractAddress, market_token: ContractAddress) { + panic(array!['unable_to_get_cached_token_pri', token_in.into(), market_token.into()]) + } + + fn MAX_POOL_AMOUNT_EXCEEDED(pool_amount: u256, max_pool_amount: u256) { + panic( + array![ + 'max_pool_amount_exceeded', + pool_amount.try_into().expect('u256 into felt failed'), + max_pool_amount.try_into().expect('u256 into felt failed') + ] + ) + } + + fn INSUFFICIENT_RESERVE(reserve: u256, amount: u256) { + panic( + array![ + 'insufficient_reserve', + reserve.try_into().expect('u256 into felt failed'), + amount.try_into().expect('u256 into felt failed') + ] + ) + } + + fn UNEXCEPTED_BORROWING_FACTOR(borrowing_factor: u256, next: u256) { + panic( + array![ + 'unexpected_borrowing_factor', + borrowing_factor.try_into().expect('u256 into felt failed'), + next.try_into().expect('u256 into felt failed') + ] + ) + } - fn UNABLE_TO_GET_CACHED_TOKEN_PRICE(token_in: ContractAddress) { - let mut data = array!['invalid token in']; - data.append(token_in.into()); - panic(data) + fn UNEXCEPTED_TOKEN(token: ContractAddress) { + panic(array!['unexpected_token', token.into()]) } } diff --git a/src/market/market.cairo b/src/market/market.cairo index 2a2c01f7..db93cae9 100644 --- a/src/market/market.cairo +++ b/src/market/market.cairo @@ -39,7 +39,7 @@ use zeroable::Zeroable; use satoru::market::error::MarketError; use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; -/// Deriving the `storage_access::StorageAccess` trait +/// Deriving the `storage_access::Store` trait /// allows us to store the `Market` struct in a contract's storage. /// We use `Copy` but this is inneficient. /// TODO: Optimize this. diff --git a/src/market/market_event_utils.cairo b/src/market/market_event_utils.cairo deleted file mode 100644 index a8be79cf..00000000 --- a/src/market/market_event_utils.cairo +++ /dev/null @@ -1,12 +0,0 @@ -use starknet::ContractAddress; - -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::market::market_pool_value_info::MarketPoolValueInfo; - -fn emit_market_pool_value_info( - event_emitter: IEventEmitterDispatcher, - market: ContractAddress, - props: MarketPoolValueInfo, - market_tokens_supply: u128 -) {} - diff --git a/src/market/market_factory.cairo b/src/market/market_factory.cairo index 25c88935..156ca797 100644 --- a/src/market/market_factory.cairo +++ b/src/market/market_factory.cairo @@ -48,7 +48,6 @@ mod MarketFactory { use starknet::syscalls::deploy_syscall; use poseidon::poseidon_hash_span; - use debug::PrintTrait; // Local imports. use satoru::role::role; @@ -103,7 +102,7 @@ mod MarketFactory { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl MarketFactory of super::IMarketFactory { fn create_market( ref self: ContractState, @@ -124,14 +123,16 @@ 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 ) - .unwrap(); + .expect('failed to deploy market'); // Create the market. let market = Market { diff --git a/src/market/market_pool_value_info.cairo b/src/market/market_pool_value_info.cairo index 3f39cb07..9cdb60e6 100644 --- a/src/market/market_pool_value_info.cairo +++ b/src/market/market_pool_value_info.cairo @@ -1,27 +1,29 @@ +use satoru::utils::i256::i256; + /// Struct to store MarketPoolValue infos. #[derive(Default, Drop, Copy, starknet::Store, Serde)] struct MarketPoolValueInfo { /// The pool value. - pool_value: u128, // TODO replace with i128 when it derives Store + pool_value: i256, /// The pending pnl of long positions. - long_pnl: u128, // TODO replace with i128 when it derives Store + long_pnl: i256, /// The pending pnl of short positions - short_pnl: u128, // TODO replace with i128 when it derives Store + short_pnl: i256, /// The net pnl of long and short positions. - net_pnl: u128, // TODO replace with i128 when it derives Store + net_pnl: i256, /// The amount of long token in the pool. - long_token_amount: u128, + long_token_amount: u256, /// The amount of short token in the pool. - short_token_amount: u128, + short_token_amount: u256, /// The USD value of the long tokens in the pool. - long_token_usd: u128, + long_token_usd: u256, /// The USD value of the short tokens in the pool. - short_token_usd: u128, + short_token_usd: u256, /// The total pending borrowing fees for the market. - total_borrowing_fees: u128, + total_borrowing_fees: u256, /// The pool factor for borrowing fees. - borrowing_fee_pool_factor: u128, + borrowing_fee_pool_factor: u256, /// The amount of tokens in the impact pool. - impact_pool_amount: u128, + impact_pool_amount: u256, } diff --git a/src/market/market_store_utils.cairo b/src/market/market_store_utils.cairo new file mode 100644 index 00000000..5cd25bfb --- /dev/null +++ b/src/market/market_store_utils.cairo @@ -0,0 +1,53 @@ +use poseidon::poseidon_hash_span; +use starknet::{ContractAddress, get_block_timestamp}; + +// Local imports. +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::data::keys; +use satoru::market::market::Market; +use satoru::utils::hash::hash_poseidon_single; + +fn market_salt() -> felt252 { + hash_poseidon_single('MARKET_SALT') +} + +fn market_key() -> felt252 { + hash_poseidon_single('MARKET_KEY') +} + +fn market_token() -> felt252 { + hash_poseidon_single('MARKET_TOKEN') +} + +fn index_token() -> felt252 { + hash_poseidon_single('INDEX_TOKEN') +} + +fn long_token() -> felt252 { + hash_poseidon_single('LONG_TOKEN') +} + +fn short_token() -> felt252 { + hash_poseidon_single('SHORT_TOKEN') +} + +fn get(data_store: IDataStoreDispatcher, key: ContractAddress) -> Market { + let market = data_store.get_market(key); + if market.market_token.is_zero() { + return market; + } + + let hash = poseidon_hash_span(array![key.into(), market_token()].span()); + let market_token = data_store.get_address(hash); + + let hash = poseidon_hash_span(array![key.into(), index_token()].span()); + let index_token = data_store.get_address(hash); + + let hash = poseidon_hash_span(array![key.into(), long_token()].span()); + let long_token = data_store.get_address(hash); + + let hash = poseidon_hash_span(array![key.into(), short_token()].span()); + let short_token = data_store.get_address(hash); + + Market { market_token, index_token, long_token, short_token } +} diff --git a/src/market/market_token.cairo b/src/market/market_token.cairo index 58864c96..90da10cf 100644 --- a/src/market/market_token.cairo +++ b/src/market/market_token.cairo @@ -7,16 +7,23 @@ trait IMarketToken { fn name(self: @TState) -> felt252; fn symbol(self: @TState) -> felt252; fn decimals(self: @TState) -> u8; - fn total_supply(self: @TState) -> u128; - fn balance_of(self: @TState, account: ContractAddress) -> u128; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u128; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u128) -> bool; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u128 + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 ) -> bool; - 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 approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + fn mint(ref self: TState, recipient: ContractAddress, amount: u256); + fn burn(ref self: TState, recipient: ContractAddress, amount: u256); + fn transfer_out( + ref self: TState, + sender: ContractAddress, + token: ContractAddress, + receiver: ContractAddress, + amount: u256, + ); } #[starknet::contract] @@ -26,9 +33,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,12 +45,11 @@ mod MarketToken { #[storage] struct Storage { - role_store: IRoleStoreDispatcher, name: felt252, symbol: felt252, - total_supply: u128, - balances: LegacyMap, - allowances: LegacyMap<(ContractAddress, ContractAddress), u128>, + total_supply: u256, + balances: LegacyMap, + allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, } #[event] @@ -57,30 +63,32 @@ mod MarketToken { struct Transfer { from: ContractAddress, to: ContractAddress, - value: u128 + value: u256 } #[derive(Drop, starknet::Event)] struct Approval { owner: ContractAddress, spender: ContractAddress, - value: u128 + value: u256 } #[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); } // // External // - - #[external(v0)] + #[abi(embed_v0)] impl MarketTokenImpl of IMarketToken { fn name(self: @ContractState) -> felt252 { self.name.read() @@ -94,21 +102,21 @@ mod MarketToken { DECIMALS } - fn total_supply(self: @ContractState) -> u128 { + fn total_supply(self: @ContractState) -> u256 { self.total_supply.read() } - fn balance_of(self: @ContractState, account: ContractAddress) -> u128 { + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { self.balances.read(account) } fn allowance( self: @ContractState, owner: ContractAddress, spender: ContractAddress - ) -> u128 { + ) -> u256 { self.allowances.read((owner, spender)) } - fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u128) -> bool { + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { let sender = get_caller_address(); self._transfer(sender, recipient, amount); true @@ -118,7 +126,7 @@ mod MarketToken { ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, - amount: u128 + amount: u256 ) -> bool { let caller = get_caller_address(); self._spend_allowance(sender, caller, amount); @@ -126,49 +134,63 @@ mod MarketToken { true } - fn approve(ref self: ContractState, spender: ContractAddress, amount: u128) -> bool { + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { let caller = get_caller_address(); self._approve(caller, spender, amount); true } - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u128) { + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { // 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) { + fn burn(ref self: ContractState, recipient: ContractAddress, amount: u256) { // 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, + sender: ContractAddress, + token: ContractAddress, + receiver: ContractAddress, + amount: u256, + ) { + let mut bank: Bank::ContractState = Bank::unsafe_new_contract_state(); + IBank::transfer_out(ref bank, sender, token, receiver, amount); + } } #[external(v0)] fn increase_allowance( - ref self: ContractState, spender: ContractAddress, added_value: u128 + ref self: ContractState, spender: ContractAddress, added_value: u256 ) -> bool { self._increase_allowance(spender, added_value) } #[external(v0)] fn increaseAllowance( - ref self: ContractState, spender: ContractAddress, addedValue: u128 + ref self: ContractState, spender: ContractAddress, addedValue: u256 ) -> bool { increase_allowance(ref self, spender, addedValue) } #[external(v0)] fn decrease_allowance( - ref self: ContractState, spender: ContractAddress, subtracted_value: u128 + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 ) -> bool { self._decrease_allowance(spender, subtracted_value) } #[external(v0)] fn decreaseAllowance( - ref self: ContractState, spender: ContractAddress, subtractedValue: u128 + ref self: ContractState, spender: ContractAddress, subtractedValue: u256 ) -> bool { decrease_allowance(ref self, spender, subtractedValue) } @@ -185,7 +207,7 @@ mod MarketToken { } fn _increase_allowance( - ref self: ContractState, spender: ContractAddress, added_value: u128 + ref self: ContractState, spender: ContractAddress, added_value: u256 ) -> bool { let caller = get_caller_address(); self._approve(caller, spender, self.allowances.read((caller, spender)) + added_value); @@ -193,7 +215,7 @@ mod MarketToken { } fn _decrease_allowance( - ref self: ContractState, spender: ContractAddress, subtracted_value: u128 + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 ) -> bool { let caller = get_caller_address(); self @@ -203,14 +225,14 @@ mod MarketToken { true } - fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u128) { + fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { assert(!recipient.is_zero(), 'ERC20: mint to 0'); self.total_supply.write(self.total_supply.read() + amount); self.balances.write(recipient, self.balances.read(recipient) + amount); self.emit(Transfer { from: Zeroable::zero(), to: recipient, value: amount }); } - fn _burn(ref self: ContractState, account: ContractAddress, amount: u128) { + fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { assert(!account.is_zero(), 'ERC20: burn from 0'); self.total_supply.write(self.total_supply.read() - amount); self.balances.write(account, self.balances.read(account) - amount); @@ -218,7 +240,7 @@ mod MarketToken { } fn _approve( - ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u128 + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 ) { assert(!owner.is_zero(), 'ERC20: approve from 0'); assert(!spender.is_zero(), 'ERC20: approve to 0'); @@ -230,7 +252,7 @@ mod MarketToken { ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, - amount: u128 + amount: u256 ) { assert(!sender.is_zero(), 'ERC20: transfer from 0'); assert(!recipient.is_zero(), 'ERC20: transfer to 0'); @@ -240,7 +262,7 @@ mod MarketToken { } fn _spend_allowance( - ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u128 + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 ) { let current_allowance = self.allowances.read((owner, spender)); if current_allowance != BoundedInt::max() { diff --git a/src/market/market_utils.cairo b/src/market/market_utils.cairo index 1174848f..267dba8a 100644 --- a/src/market/market_utils.cairo +++ b/src/market/market_utils.cairo @@ -2,25 +2,36 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::{ContractAddress, get_block_timestamp}; -use result::ResultTrait; - -use debug::PrintTrait; -use zeroable::Zeroable; - +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; +use satoru::event::event_emitter; use satoru::market::{ market::Market, error::MarketError, market_pool_value_info::MarketPoolValueInfo, - market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait} + 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::span32::Span32; -use satoru::utils::i128::{StoreI128, u128_to_i128, I128Serde, I128Div, I128Mul}; +use satoru::utils::calc; +use satoru::utils::precision::{FLOAT_PRECISION, FLOAT_PRECISION_SQRT}; +use satoru::utils::precision::{mul_div_roundup, to_factor_ival, apply_factor_u256, to_factor}; +use satoru::utils::precision; +use satoru::utils::calc::{roundup_division, to_signed, sum_return_int_256, to_unsigned}; +use satoru::position::position::Position; +use satoru::utils::{i256::{i256, i256_neg}, error_utils}; +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 /// * `indexTokenPrice` - Price of the market's index token. @@ -34,156 +45,402 @@ struct MarketPrices { short_token_price: Price, } -#[derive(Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde)] struct CollateralType { - long_token: u128, - short_token: u128, + long_token: u256, + short_token: u256, } -#[derive(Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde)] struct PositionType { long: CollateralType, short: CollateralType, } -#[derive(Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde)] struct GetNextFundingAmountPerSizeResult { longs_pay_shorts: bool, - funding_factor_per_second: u128, + funding_factor_per_second: u256, funding_fee_amount_per_size_delta: PositionType, claimable_funding_amount_per_size_delta: PositionType, } +struct GetExpectedMinTokenBalanceCache { + pool_amount: u256, + swap_impact_pool_amount: u256, + claimable_collateral_amount: u256, + claimable_fee_amount: u256, + claimable_ui_fee_amount: u256, + affiliate_reward_amount: u256, +} + +/// Get the market token price. +/// # Arguments +/// * `data_store` - The data store to use. +/// * `market` - The market to get the market token price for. +/// * `long_token_price` - The price of the long token. +/// * `short_token_price` - The price of the short token. +/// * `index_token_price` - The price of the index token. +/// * `maximize` - Whether to maximize or minimize the market token price. +/// # Returns +/// The market token price. +fn get_market_token_price( + data_store: IDataStoreDispatcher, + market: Market, + index_token_price: Price, + long_token_price: Price, + short_token_price: Price, + pnl_factor_type: felt252, + maximize: bool +) -> (i256, MarketPoolValueInfo) { + let supply = get_market_token_supply( + IMarketTokenDispatcher { contract_address: market.market_token } + ); + + let pool_value_info = get_pool_value_info( + data_store, + market, + index_token_price, + long_token_price, + short_token_price, + pnl_factor_type, + maximize + ); + + // if the supply is zero then treat the market token price as 1 USD + if supply == 0 { + return (calc::to_signed(precision::FLOAT_PRECISION, true), pool_value_info); + } + + if pool_value_info.pool_value == Zeroable::zero() { + return (Zeroable::zero(), pool_value_info); + } + + let market_token_price = precision::mul_div_inum( + precision::WEI_PRECISION, pool_value_info.pool_value, supply + ); + (market_token_price, pool_value_info) +} + +/// Gets the total supply of the marketToken. +/// # Arguments +/// * `market_token` - The market token whose total supply is to be retrieved. +/// # Returns +/// The total supply of the given marketToken. +fn get_market_token_supply(market_token: IMarketTokenDispatcher) -> u256 { + market_token.total_supply() +} + +/// Get the opposite token of the market +/// if the input_token is the token_long return the short_token and vice versa +/// # Arguments +/// * `market` - The market to validate the open interest for. +/// * `token` - The input_token. +/// # Returns +/// The opposite token. +fn get_opposite_token(input_token: ContractAddress, market: @Market) -> ContractAddress { + if input_token == *market.long_token { + *market.short_token + } else if input_token == *market.short_token { + *market.long_token + } else { + panic( + array![ + MarketError::UNABLE_TO_GET_OPPOSITE_TOKEN, + input_token.into(), + (*market.market_token).into() + ] + ) + } +} + +fn validate_swap_market_with_address( + data_store: IDataStoreDispatcher, market_address: ContractAddress +) { + let market = data_store.get_market(market_address); + validate_swap_market(data_store, market); +} + +/// Validata the swap market. +/// # Arguments +/// * `data_store` - The data store to use. +/// * `market` - The market to validate the open interest for. +fn validate_swap_market(data_store: IDataStoreDispatcher, market: Market) { + validate_enabled_market(data_store, market); + + if market.long_token == market.short_token { + panic(array![MarketError::INVALID_SWAP_MARKET, market.market_token.into()]) + } +} + // @dev get the token price from the stored MarketPrices // @param token the token to get the price for // @param the market values // @param the market token prices // @return the token price from the stored MarketPrices fn get_cached_token_price(token: ContractAddress, market: Market, prices: MarketPrices) -> Price { - if (token == market.long_token) { + if token == market.long_token { prices.long_token_price - } else if (token == market.short_token) { + } else if token == market.short_token { prices.short_token_price - } else if (token == market.index_token) { + } else if token == market.index_token { prices.index_token_price } else { - MarketError::UNABLE_TO_GET_CACHED_TOKEN_PRICE(token); - prices.index_token_price //todo : remove + MarketError::UNABLE_TO_GET_CACHED_TOKEN_PRICE(token, market.market_token); + Default::default() } } -fn get_swap_impact_amount_with_cap( - dataStore: IDataStoreDispatcher, - market: ContractAddress, - token: ContractAddress, - tokenPrice: Price, - priceImpactUsd: i128 //TODO : check u128 -) -> i128 { //Todo : check u128 - //TODO - return 0; +/// Returns the primary prices for the market tokens. +/// # Parameters +/// - `oracle`: The Oracle instance. +/// - `market`: The market values. +fn get_market_prices(oracle: IOracleDispatcher, market: Market) -> MarketPrices { + MarketPrices { + index_token_price: oracle.get_primary_price(market.index_token), + long_token_price: oracle.get_primary_price(market.long_token), + short_token_price: oracle.get_primary_price(market.short_token), + } } -/// Get the long and short open interest for a market based on the collateral token used. +/// Get the usd value of either the long or short tokens in the pool +/// without accounting for the pnl of open positions /// # 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( +/// * `market` - The market values. +/// * `prices` - The prices of the market tokens. +/// * `is_long` - Whether to return the value for the long or short token. +/// * `maximize` - Whether to maximize or minimize the pool value. +/// # Returns +/// The usd value of either the long or short tokens in the pool. +fn get_pool_usd_without_pnl( data_store: IDataStoreDispatcher, - market: ContractAddress, - collateral_token: ContractAddress, + market: @Market, + prices: @MarketPrices, is_long: bool, - divisor: u128 -) -> u128 { - assert(divisor != 0, MarketError::DIVISOR_CANNOT_BE_ZERO); - let key = keys::open_interest_key(market, collateral_token, is_long); - data_store.get_u128(key) / divisor + maximize: bool +) -> u256 { + let token = if is_long { + *market.long_token + } else { + *market.short_token + }; + // note that if it is a single token market, the poolAmount returned will be + // the amount of tokens in the pool divided by 2 + let pool_amount = get_pool_amount(data_store, market, token); + let token_price = if maximize { + if is_long { + prices.long_token_price.max + } else { + prices.short_token_price.max + } + } else { + if is_long { + prices.long_token_price.min + } else { + prices.short_token_price.min + } + }; + pool_amount * (*token_price) } -/// Get the long and short open interest for a market. +/// Get the USD value of a pool. +/// The value of a pool is the worth of the liquidity provider tokens in the pool - pending trader pnl. +/// We use the token index prices to calculate this and ignore price impact since if all positions were closed the +/// net price impact should be zero. /// # Arguments /// * `data_store` - The data store to use. -/// * `market` - The market to get the open interest for. +/// * `market` - The market values. +/// * `index_token_price` - The price of the index token. +/// * `long_token_price` - The price of the long token. +/// * `short_token_price` - The price of the short token. +/// * `maximize` - Whether to maximize or minimize the pool value. /// # Returns -/// The long and short open interest for a market. -fn get_open_interest_for_market(data_store: IDataStoreDispatcher, market: @Market) -> u128 { - // Get the open interest for the long token as collateral. - let long_open_interest = get_open_interest_for_market_is_long(data_store, market, true); - // Get the open interest for the short token as collateral. - let short_open_interest = get_open_interest_for_market_is_long(data_store, market, false); - long_open_interest + short_open_interest +/// The value information of a pool. +fn get_pool_value_info( + data_store: IDataStoreDispatcher, + market: Market, + index_token_price: Price, + long_token_price: Price, + short_token_price: Price, + pnl_factor_type: felt252, + maximize: bool +) -> MarketPoolValueInfo { + let mut result: MarketPoolValueInfo = Default::default(); + + result.long_token_amount = get_pool_amount(data_store, @market, market.long_token); + result.short_token_amount = get_pool_amount(data_store, @market, market.short_token); + + result.long_token_usd = result.long_token_amount * long_token_price.pick_price(maximize); + result.short_token_usd = result.short_token_amount * short_token_price.pick_price(maximize); + + result.pool_value = calc::to_signed(result.long_token_usd + result.short_token_usd, true); + + let prices = MarketPrices { index_token_price, long_token_price, short_token_price }; + + result + .total_borrowing_fees = get_total_pending_borrowing_fees(data_store, market, prices, true); + + result + .total_borrowing_fees += + get_total_pending_borrowing_fees(data_store, market, prices, false); + + result.borrowing_fee_pool_factor = precision::FLOAT_PRECISION + - data_store.get_u256(keys::borrowing_fee_receiver_factor()); + + let value = precision::apply_factor_u256( + result.total_borrowing_fees, result.borrowing_fee_pool_factor + ); + result.pool_value += calc::to_signed(value, true); + + // !maximize should be used for net pnl as a larger pnl leads to a smaller pool value + // and a smaller pnl leads to a larger pool value + // + // while positions will always be closed at the less favourable price + // using the inverse of maximize for the getPnl calls would help prevent + // gaming of market token values by increasing the spread + // + // liquidations could be triggerred by manipulating a large spread but + // that should be more difficult to execute + + result.long_pnl = get_pnl(data_store, @market, @index_token_price, true, !maximize); + + result + .long_pnl = + get_capped_pnl( + data_store, + market.market_token, + true, + result.long_pnl, + result.long_token_usd, + pnl_factor_type, + ); + + result.short_pnl = get_pnl(data_store, @market, @index_token_price, false, !maximize); + + result + .short_pnl = + get_capped_pnl( + data_store, + market.market_token, + false, + result.short_pnl, + result.short_token_usd, + pnl_factor_type, + ); + + result.net_pnl = result.long_pnl + result.short_pnl; + result.pool_value = result.pool_value - result.net_pnl; + + result.impact_pool_amount = get_position_impact_pool_amount(data_store, market.market_token); + // use !maximize for pick_price since the impact_pool_usd is deducted from the pool_value + let impact_pool_usd = result.impact_pool_amount * index_token_price.pick_price(!maximize); + + result.pool_value -= calc::to_signed(impact_pool_usd, true); + + result } -/// Get the long and short open interest for a market. +/// Get the net pending pnl for a market /// # Arguments /// * `data_store` - The data store to use. -/// * `market` - The market to get the open interest for. -/// * `is_long` - Whether to get the long or short open interest. +/// * `market` - The market to get the pending PNL for. +/// * `index_token_price` - The price of the index token. +/// * `maximize` - Whether to maximize or minimize the net PNL. /// # Returns -/// The long and short open interest for a market. -fn get_open_interest_for_market_is_long( - data_store: IDataStoreDispatcher, market: @Market, is_long: bool -) -> u128 { - // 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( - 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( - data_store, *market.market_token, *market.short_token, is_long, divisor - ); - // Return the sum of the open interests. - open_interest_using_long_token_as_collateral + open_interest_using_short_token_as_collateral +/// The net pending pnl for a market +fn get_net_pnl( + data_store: IDataStoreDispatcher, market: @Market, index_token_price: @Price, maximize: bool +) -> i256 { + let long_pnl = get_pnl(data_store, market, index_token_price, true, maximize); + let short_pnl = get_pnl(data_store, market, index_token_price, false, maximize); + long_pnl + short_pnl } - -/// Get the long and short open interest in tokens for a market. +/// Get the capped pending pnl for a market /// # Arguments /// * `data_store` - The data store to use. -/// * `market` - The market to get the open interest for. -/// * `is_long` - Whether to get the long or short open interest. +/// * `market` - The market to get the pending PNL for. +/// * `is_long` - Whether to get the long or short pending PNL. +/// * `pnl` - The uncapped pnl of the market. +/// * `pool_usd` - The USD value of the pool. +/// * `pnl_factor_type` - The pnl factor type to use. /// # Returns -/// The long and short open interest in tokens for a market based on the collateral token used. -fn get_open_interest_in_tokens_for_market( - data_store: IDataStoreDispatcher, market: @Market, is_long: bool, -) -> u128 { - // Get the pool divisor. - let divisor = get_pool_divisor(*market.long_token, *market.short_token); +/// The net pending pnl for a market +fn get_capped_pnl( + data_store: IDataStoreDispatcher, + market: ContractAddress, + is_long: bool, + pnl: i256, + pool_usd: u256, + pnl_factor_type: felt252 +) -> i256 { + if pnl < Zeroable::zero() { + return pnl; + } + let max_pnl_factor = get_max_pnl_factor(data_store, pnl_factor_type, market, is_long); + let max_pnl = calc::to_signed(precision::apply_factor_u256(pool_usd, max_pnl_factor), true); + if pnl > max_pnl { + max_pnl + } else { + pnl + } +} - // Get the open interest for the long token as collateral. - let open_interest_using_long_token_as_collateral = get_open_interest_in_tokens( - 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_in_tokens( - data_store, *market.market_token, *market.short_token, is_long, divisor - ); - // Return the sum of the open interests. - open_interest_using_long_token_as_collateral + open_interest_using_short_token_as_collateral +fn get_pnl_with_u256_price( + data_store: IDataStoreDispatcher, + market: @Market, + index_token_price: u256, + is_long: bool, + maximize: bool +) -> i256 { + let index_token_price_ = Price { min: index_token_price, max: index_token_price }; + get_pnl(data_store, market, @index_token_price_, is_long, maximize) } -/// Get the long and short open interest in tokens for a market based on the collateral token used. +/// Get the pending PNL for a market for either longs or shorts. /// # 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. +/// * `market` - The market to get the pending PNL for. +/// * `index_token_price` - The price of the index token. +/// * `is_long` - Whether to get the long or short pending PNL. +/// * `maximize` - Whether to maximize or minimize the net PNL. /// # Returns -/// The long and short open interest in tokens for a market based on the collateral token used. -fn get_open_interest_in_tokens( +/// The pending PNL for a market for either longs or shorts. +fn get_pnl( data_store: IDataStoreDispatcher, - market: ContractAddress, - collateral_token: ContractAddress, + market: @Market, + index_token_price: @Price, is_long: bool, - divisor: u128 -) -> u128 { - data_store.get_u128(keys::open_interest_in_tokens_key(market, collateral_token, is_long)) - / divisor + maximize: bool +) -> i256 { + // Get the open interest. + let open_interest = calc::to_signed( + get_open_interest_for_market_is_long(data_store, market, is_long), true + ); + // Get the open interest in tokens. + let open_interest_in_tokens = get_open_interest_in_tokens_for_market( + data_store, market, is_long + ); + // If either the open interest or the open interest in tokens is zero, return zero. + if open_interest == Zeroable::zero() || open_interest_in_tokens == 0 { + return Zeroable::zero(); + } + + // Pick the price for PNL. + let price = index_token_price.pick_price_for_pnl(is_long, maximize); + + // `open_interest` is the cost of all positions, `open_interest_valu`e is the current worth of all positions. + let open_interest_value = calc::to_signed(open_interest_in_tokens * price, true); + + // Return the PNL. + // If `is_long` is true, then the PNL is the difference between the current worth of all positions and the cost of all positions. + // If `is_long` is false, then the PNL is the difference between the cost of all positions and the current worth of all positions. + if is_long { + open_interest_value - open_interest + } else { + open_interest - open_interest_value + } } /// Get the amount of tokens in the pool @@ -195,9 +452,10 @@ fn get_open_interest_in_tokens( /// The amount of tokens in the pool. fn get_pool_amount( data_store: IDataStoreDispatcher, market: @Market, token_address: ContractAddress -) -> u128 { +) -> u256 { let divisor = get_pool_divisor(*market.long_token, *market.short_token); - data_store.get_u128(keys::pool_amount_key(*market.market_token, token_address)) / divisor + error_utils::check_division_by_zero(divisor, 'get_pool_amount'); + data_store.get_u256(keys::pool_amount_key(*market.market_token, token_address)) / divisor } /// Get the maximum amount of tokens allowed to be in the pool. @@ -211,8 +469,8 @@ fn get_max_pool_amount( data_store: IDataStoreDispatcher, market_address: ContractAddress, token_address: ContractAddress -) -> u128 { - data_store.get_u128(keys::max_pool_amount_key(market_address, token_address)) +) -> u256 { + data_store.get_u256(keys::max_pool_amount_key(market_address, token_address)) } /// Get the maximum open interest allowed for a market. @@ -224,8 +482,8 @@ fn get_max_pool_amount( /// The maximum open interest allowed for a market. fn get_max_open_interest( data_store: IDataStoreDispatcher, market_address: ContractAddress, is_long: bool -) -> u128 { - data_store.get_u128(keys::max_open_interest_key(market_address, is_long)) +) -> u256 { + data_store.get_u256(keys::max_open_interest_key(market_address, is_long)) } /// Increment the claimable collateral amount. @@ -239,30 +497,27 @@ fn get_max_open_interest( /// * `delta` - The amount to increment by. fn increment_claimable_collateral_amount( data_store: IDataStoreDispatcher, - chain: IChainDispatcher, event_emitter: IEventEmitterDispatcher, market_address: ContractAddress, token: ContractAddress, account: ContractAddress, - delta: u128 + delta: u256 ) { - let divisor = data_store.get_u128(keys::claimable_collateral_time_divisor()); + let divisor = data_store.get_u256(keys::claimable_collateral_time_divisor()); + error_utils::check_division_by_zero(divisor, 'increment_claimable_collateral'); // Get current timestamp. - let current_timestamp = chain.get_block_timestamp().into(); + let current_timestamp = get_block_timestamp().into(); let time_key = current_timestamp / divisor; // Increment the collateral amount for the account. - let next_value = data_store - .increment_u128( - keys::claimable_collateral_amount_for_account_key( - market_address, token, time_key, account - ), - delta - ); + let key = keys::claimable_collateral_amount_for_account_key( + market_address, token, time_key, account + ); + let next_value = data_store.increment_u256(key, delta); // Increment the total collateral amount for the market. let next_pool_value = data_store - .increment_u128(keys::claimable_collateral_amount_key(market_address, token), delta); + .increment_u256(keys::claimable_collateral_amount_key(market_address, token), delta); // Emit event. event_emitter @@ -285,17 +540,17 @@ fn increment_claimable_funding_amount( market_address: ContractAddress, token: ContractAddress, account: ContractAddress, - delta: u128 + delta: u256 ) { // Increment the funding amount for the account. let next_value = data_store - .increment_u128( + .increment_u256( keys::claimable_funding_amount_by_account_key(market_address, token, account), delta ); // Increment the total funding amount for the market. let next_pool_value = data_store - .increment_u128(keys::claimable_funding_amount_key(market_address, token), delta); + .increment_u256(keys::claimable_funding_amount_key(market_address, token), delta); // Emit event. event_emitter @@ -304,65 +559,247 @@ fn increment_claimable_funding_amount( ); } -/// Get the pool divisor. -/// This is used to divide the values of `get_pool_amount` and `get_open_interest` -/// if the longToken and shortToken are the same, then these values have to be divided by two -/// to avoid double counting +/// Claim funding fees /// # Arguments -/// * `long_token` - The long token. -/// * `short_token` - The short token. -/// # Returns -/// The pool divisor. -fn get_pool_divisor(long_token: ContractAddress, short_token: ContractAddress) -> u128 { - if long_token == short_token { - 2 - } else { - 1 - } +/// * `data_store` - The data store to use. +/// * `event_emitter` - The interface to interact with `EventEmitter` contract. +/// * `market_address` - The market to claim for. +/// * `token` - The token to claim. +/// * `account` - The account to claim for. +/// * `receiver` - The receiver to send the amount to. +fn claim_funding_fees( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + market_address: ContractAddress, + token: ContractAddress, + account: ContractAddress, + receiver: ContractAddress +) -> u256 { + let key = keys::claimable_funding_amount_by_account_key(market_address, token, account); + let claimable_amount = data_store.get_u256(key); + data_store.set_u256(key, 0); + + let next_pool_value = data_store + .decrement_u256( + keys::claimable_funding_amount_key(market_address, token), claimable_amount + ); + + // Transfer the amount to the receiver. + IBankDispatcher { contract_address: market_address } + .transfer_out(market_address, token, receiver, claimable_amount); + + // Validate the market token balance. + validate_market_token_balance_with_address(data_store, market_address); + + event_emitter + .emit_funding_fees_claimed( + market_address, token, account, receiver, claimable_amount, next_pool_value + ); + + claimable_amount } -/// Get the pending PNL for a market for either longs or shorts. +/// Claim collateral /// # Arguments /// * `data_store` - The data store to use. -/// * `market` - The market to get the pending PNL for. -/// * `index_token_price` - The price of the index token. -/// * `is_long` - Whether to get the long or short pending PNL. -/// * `maximize` - Whether to maximize or minimize the net PNL. -/// # Returns -/// The pending PNL for a market for either longs or shorts. -fn get_pnl( +/// * `event_emitter` - The interface to interact with `EventEmitter` contract. +/// * `market_address` - The market to claim for. +/// * `token` - The token to claim. +/// * `time_key` - The time key. +/// * `account` - The account to claim for. +/// * `receiver` - The receiver to send the amount to. +fn claim_collateral( data_store: IDataStoreDispatcher, - market: @Market, - index_token_price: @Price, - is_long: bool, - maximize: bool -) -> i128 { - // Get the open interest. - let open_interest = u128_to_i128( - get_open_interest_for_market_is_long(data_store, market, is_long) + event_emitter: IEventEmitterDispatcher, + market_address: ContractAddress, + token: ContractAddress, + time_key: u256, + account: ContractAddress, + receiver: ContractAddress +) -> u256 { + let key = keys::claimable_collateral_amount_for_account_key( + market_address, token, time_key, account ); - // Get the open interest in tokens. - let open_interest_in_tokens = get_open_interest_in_tokens_for_market( - data_store, market, is_long + let claimable_amount = data_store.get_u256(key); + data_store.set_u256(key, 0); + + let key = keys::claimable_collateral_factor_key(market_address, token, time_key); + let claimable_factor_for_time = data_store.get_u256(key); + + let key = keys::claimable_collateral_factor_for_account_key( + market_address, token, time_key, account ); - // If either the open interest or the open interest in tokens is zero, return zero. - if open_interest == 0 || open_interest_in_tokens == 0 { - return 0; + let claimable_factor_for_account = data_store.get_u256(key); + + let claimable_factor = if claimable_factor_for_time > claimable_factor_for_account { + claimable_factor_for_time + } else { + claimable_factor_for_account + }; + + let key = keys::claimed_collateral_amount_key(market_address, token, time_key, account); + let claimed_amount = data_store.get_u256(key); + + let adjusted_claimable_amount = precision::apply_factor_u256( + claimable_amount, claimable_factor + ); + if adjusted_claimable_amount <= claimed_amount { + panic( + array![ + MarketError::COLLATERAL_ALREADY_CLAIMED, + adjusted_claimable_amount.try_into().expect('u256 into felt failed'), + claimed_amount.try_into().expect('u256 into felt failed') + ] + ) } - // Pick the price for PNL. - let price = index_token_price.pick_price_for_pnl(is_long, maximize); + let amount_to_be_claimed = adjusted_claimable_amount - claimed_amount; - // `open_interest` is the cost of all positions, `open_interest_valu`e is the current worth of all positions. - let open_interest_value = u128_to_i128(open_interest_in_tokens * price); + let key = keys::claimed_collateral_amount_key(market_address, token, time_key, account); + data_store.set_u256(key, adjusted_claimable_amount); - // Return the PNL. - // If `is_long` is true, then the PNL is the difference between the current worth of all positions and the cost of all positions. - // If `is_long` is false, then the PNL is the difference between the cost of all positions and the current worth of all positions. - if is_long { - open_interest_value - open_interest + let key = keys::claimable_collateral_amount_key(market_address, token); + let next_pool_value = data_store.decrement_u256(key, amount_to_be_claimed); + + IBankDispatcher { contract_address: market_address } + .transfer_out(market_address, token, receiver, amount_to_be_claimed); + + validate_market_token_balance_with_address(data_store, market_address); + + event_emitter + .emit_collateral_claimed( + market_address, + token, + account, + receiver, + time_key, + amount_to_be_claimed, + next_pool_value + ); + + amount_to_be_claimed +} + + +/// Applies a delta to the pool amount for a given market and token. +/// `validatePoolAmount` is not called in this function since `apply_delta_to_pool_amount` +/// is typically called when receiving fees. +/// # Arguments +/// * `data_store` - Data store to manage internal states. +/// * `event_emitter` - Emits events for the system. +/// * `market` - The market to which the delta will be applied. +/// * `token` - The token to which the delta will be applied. +/// * `delta` - The delta amount to apply. +fn apply_delta_to_pool_amount( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + market: Market, + token: ContractAddress, + delta: i256 +) -> u256 { + let key = keys::pool_amount_key(market.market_token, token); + let next_value = data_store.apply_delta_to_u256(key, delta, 'negative poolAmount'); + + apply_delta_to_virtual_inventory_for_swaps(data_store, event_emitter, market, token, delta); + + event_emitter.emit_pool_amount_updated(market.market_token, token, delta, next_value); + + next_value +} + +fn get_adjusted_swap_impact_factor( + data_store: IDataStoreDispatcher, market: ContractAddress, is_positive: bool +) -> u256 { + let (positive_impact_factor, negative_impact_factor) = get_adjusted_swap_impact_factors( + data_store, market + ); + if is_positive { + positive_impact_factor } else { - open_interest - open_interest_value + negative_impact_factor + } +} + +fn get_adjusted_swap_impact_factors( + data_store: IDataStoreDispatcher, market: ContractAddress +) -> (u256, u256) { + let mut positive_impact_factor = data_store + .get_u256(keys::swap_impact_factor_key(market, true)); + let negative_impact_factor = data_store.get_u256(keys::swap_impact_factor_key(market, false)); + // if the positive impact factor is more than the negative impact factor, positions could be opened + // and closed immediately for a profit if the difference is sufficient to cover the position fees + if positive_impact_factor > negative_impact_factor { + positive_impact_factor = negative_impact_factor; + } + (positive_impact_factor, negative_impact_factor) +} + +fn get_adjusted_position_impact_factor( + data_store: IDataStoreDispatcher, market: ContractAddress, is_positive: bool +) -> u256 { + let (positive_impact_factor, negative_impact_factor) = get_adjusted_position_impact_factors( + data_store, market + ); + if is_positive { + positive_impact_factor + } else { + negative_impact_factor + } +} + +fn get_adjusted_position_impact_factors( + data_store: IDataStoreDispatcher, market: ContractAddress +) -> (u256, u256) { + let mut positive_impact_factor = data_store + .get_u256(keys::position_impact_factor_key(market, true)); + let negative_impact_factor = data_store + .get_u256(keys::position_impact_factor_key(market, false)); + // if the positive impact factor is more than the negative impact factor, positions could be opened + // and closed immediately for a profit if the difference is sufficient to cover the position fees + if positive_impact_factor > negative_impact_factor { + positive_impact_factor = negative_impact_factor; + } + (positive_impact_factor, negative_impact_factor) +} + +/// Cap the input priceImpactUsd by the available amount in the swap impact pool and the max positive swap impact factor. +/// # Arguments +/// * `data_store` - The data store to use. +/// * `market` - The trading market. +/// * `token_price` - The price of the token. +/// * `price_impact_usd` - The calculated USD price impact. +/// * `size_delta_usd` - The size delta in USD. +/// # Returns +/// The capped priceImpactUsd. +fn get_capped_position_impact_usd( + data_store: IDataStoreDispatcher, + market: ContractAddress, + token_price: Price, + mut price_impact_usd: i256, + size_delta_usd: u256 +) -> i256 { + if price_impact_usd < Zeroable::zero() { + return price_impact_usd; + } + + let impact_pool_amount = get_position_impact_pool_amount(data_store, market); + let max_price_impact_usd_based_on_impact_pool = calc::to_signed( + impact_pool_amount * token_price.min, true + ); + + if price_impact_usd > max_price_impact_usd_based_on_impact_pool { + price_impact_usd = max_price_impact_usd_based_on_impact_pool; + } + + let max_price_impact_factor = get_max_position_impact_factor(data_store, market, true); + let max_price_impact_usd_based_on_max_price_impact_factor = calc::to_signed( + precision::apply_factor_u256(size_delta_usd, max_price_impact_factor), true + ); + + if price_impact_usd > max_price_impact_usd_based_on_max_price_impact_factor { + max_price_impact_usd_based_on_max_price_impact_factor + } else { + price_impact_usd } } @@ -374,8 +811,8 @@ fn get_pnl( /// The position impact pool amount. fn get_position_impact_pool_amount( data_store: IDataStoreDispatcher, market_address: ContractAddress -) -> u128 { - data_store.get_u128(keys::position_impact_pool_amount_key(market_address)) +) -> u256 { + data_store.get_u256(keys::position_impact_pool_amount_key(market_address)) } /// Get the swap impact pool amount. @@ -387,79 +824,61 @@ fn get_position_impact_pool_amount( /// The swap impact pool amount. fn get_swap_impact_pool_amount( data_store: IDataStoreDispatcher, market_address: ContractAddress, token: ContractAddress -) -> u128 { - data_store.get_u128(keys::swap_impact_pool_amount_key(market_address, token)) +) -> u256 { + data_store.get_u256(keys::swap_impact_pool_amount_key(market_address, token)) } -/// Apply delta to the position impact pool. +/// Apply delta to the swap impact pool. /// # Arguments /// * `data_store` - The data store to use. /// * `event_emitter` - The interface to interact with `EventEmitter` contract. /// * `market_address` - The market to apply the delta to. +/// * `token` - The token to apply the delta to. /// * `delta` - The delta to apply. /// # Returns -/// The updated position impact pool amount. -fn apply_delta_to_position_impact_pool( +/// The updated swap impact pool amount. +fn apply_delta_to_swap_impact_pool( data_store: IDataStoreDispatcher, event_emitter: IEventEmitterDispatcher, market_address: ContractAddress, - delta: u128 -) -> u128 { - // Increment the position impact pool amount. + token: ContractAddress, + delta: i256 +) -> u256 { + // Increment the swap impact pool amount. let next_value = data_store - .increment_u128(keys::position_impact_pool_amount_key(market_address), delta); + .apply_bounded_delta_to_u256( + keys::swap_impact_pool_amount_key(market_address, token), delta + ); // Emit event. - event_emitter.emit_position_impact_pool_amount_updated(market_address, delta, next_value); + event_emitter.emit_swap_impact_pool_amount_updated(market_address, token, delta, next_value); - // Return the updated position impact pool amount. + // Return the updated swap impact pool amount. next_value } -/// Applies a delta to the pool amount for a given market and token. -/// `validatePoolAmount` is not called in this function since `apply_delta_to_pool_amount` -/// is typically called when receiving fees. -/// # Arguments -/// * `data_store` - Data store to manage internal states. -/// * `event_emitter` - Emits events for the system. -/// * `market` - The market to which the delta will be applied. -/// * `token` - The token to which the delta will be applied. -/// * `delta` - The delta amount to apply. -fn apply_delta_to_pool_amount( - data_store: IDataStoreDispatcher, - eventEmitter: IEventEmitterDispatcher, - market: Market, - token: ContractAddress, - delta: u128 // This is supposed to be i128 when it will be supported. -) -> u128 { - //TODO - 0 -} - -/// Apply delta to the swap impact pool. +/// Apply delta to the position impact pool. /// # Arguments /// * `data_store` - The data store to use. /// * `event_emitter` - The interface to interact with `EventEmitter` contract. /// * `market_address` - The market to apply the delta to. -/// * `token` - The token to apply the delta to. /// * `delta` - The delta to apply. /// # Returns -/// The updated swap impact pool amount. -fn apply_delta_to_swap_impact_pool( +/// The updated position impact pool amount. +fn apply_delta_to_position_impact_pool( data_store: IDataStoreDispatcher, event_emitter: IEventEmitterDispatcher, market_address: ContractAddress, - token: ContractAddress, - delta: u128 -) -> u128 { - // Increment the swap impact pool amount. + delta: i256 +) -> u256 { + // Increment the position impact pool amount. let next_value = data_store - .increment_u128(keys::swap_impact_pool_amount_key(market_address, token), delta); + .apply_bounded_delta_to_u256(keys::position_impact_pool_amount_key(market_address), delta); // Emit event. - event_emitter.emit_swap_impact_pool_amount_updated(market_address, token, delta, next_value); + event_emitter.emit_position_impact_pool_amount_updated(market_address, delta, next_value); - // Return the updated swap impact pool amount. + // Return the updated position impact pool amount. next_value } @@ -479,21 +898,16 @@ fn apply_delta_to_open_interest( market: @Market, collateral_token: ContractAddress, is_long: bool, - // TODO: Move to `i128` when `apply_delta_to_u128` is implemented and when supported in used Cairo version. - delta: i128 -) -> u128 { + delta: i256 +) -> u256 { // Check that the market is not a swap only market. assert( (*market.index_token).is_non_zero(), MarketError::OPEN_INTEREST_CANNOT_BE_UPDATED_FOR_SWAP_ONLY_MARKET ); - // Increment the open interest by the delta. - // TODO: create `apply_delta_to_u128` function in `DataStore` contract and pass `delta` as `i128`. - let next_value = data_store - .increment_u128( - keys::open_interest_key(*market.market_token, collateral_token, is_long), 0 - ); + let key = keys::open_interest_key(*market.market_token, collateral_token, is_long); + let next_value = data_store.apply_delta_to_u256(key, delta, 'negative open interest'); // If the open interest for longs is increased then tokens were virtually bought from the pool // so the virtual inventory should be decreased. @@ -504,566 +918,1968 @@ fn apply_delta_to_open_interest( // If the open interest for shorts is decreased then tokens were virtually bought from the pool // so the virtual inventory should be decreased. - // We need to validate the open interest if the delta is positive. - //if 0_i128 < delta { - //validate_open_interest(data_store, market, is_long); - //} + if is_long { + apply_delta_to_virtual_inventory_for_positions( + data_store, event_emitter, *market.index_token, i256_neg(delta) + ); + } else { + apply_delta_to_virtual_inventory_for_positions( + data_store, event_emitter, *market.index_token, delta + ); + } - 0 -} + if (delta > Zeroable::zero()) { + validate_open_interest(data_store, market, is_long); + } + event_emitter + .emit_open_interest_updated( + *market.market_token, collateral_token, is_long, delta, next_value + ); -/// 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 + next_value } - -/// @dev 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 +/// Apply a delta to the open interest in tokens. /// # Arguments -/// *`data_store` DataStore -/// *`event_emitter` EventEmitter -/// *`market` the market to apply to -/// *`token` the token to apply to -/// *`token_price` the price of the token -/// *`price_impact_usd` the USD price impact +/// * `data_store` - The data store to use. +/// * `event_emitter` - The interface to interact with `EventEmitter` contract. +/// * `market` - The market to apply the delta to. +/// * `collateral_token` - The collateral token to apply the delta to. +/// * `is_long` - Whether to apply the delta to the long or short side. +/// * `delta` - The delta to apply. /// # Returns -/// The impact amount as integer -fn apply_swap_impact_with_cap( +/// The updated open interest in tokens. +fn apply_delta_to_open_interest_in_tokens( data_store: IDataStoreDispatcher, event_emitter: IEventEmitterDispatcher, - market: ContractAddress, - token: ContractAddress, - token_price: Price, - price_impact_usd: i128 -) -> i128 { - // TODO: implement - return 0; -} + market: Market, + collateral_token: ContractAddress, + is_long: bool, + delta: i256 +) -> u256 { + let key = keys::open_interest_in_tokens_key(market.market_token, collateral_token, is_long); + let next_value = data_store.apply_delta_to_u256(key, delta, 'negative open interest tokens'); -/// @dev validate that the pool amount is within the max allowed amount -/// # Arguments -/// *`data_store` DataStore -/// *`market` the market to check -/// *`token` the token to check -fn validate_pool_amount( - data_store: @IDataStoreDispatcher, market: @Market, token: ContractAddress -) { // TODO + event_emitter + .emit_open_interest_in_tokens_updated( + market.market_token, collateral_token, is_long, delta, next_value + ); + + next_value } -/// @dev validate that the amount of tokens required to be reserved -/// is below the configured threshold +/// @dev apply a delta to the collateral sum /// # Arguments /// * `data_store` DataStore -/// * `market` the market values -/// * `prices` the prices of the market tokens -/// * `is_long` whether to check the long or short side -fn validata_reserve( - data_store: @IDataStoreDispatcher, market: @Market, prices: @MarketPrices, is_long: bool -) { // TODO +/// * `event_emitter` EventEmitter +/// * `market` the market to apply to +/// * `collateral_token` the collateralToken to apply to +/// * `is_long` whether to apply to the long or short side +/// * `delta` the delta amount +/// # Returns +/// The updated collateral sum amount +fn apply_delta_to_collateral_sum( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + market: ContractAddress, + collateral_token: ContractAddress, + is_long: bool, + delta: i256 +) -> u256 { + let key = keys::collateral_sum_key(market, collateral_token, is_long); + let next_value = data_store.apply_delta_to_u256(key, delta, 'negative collateralSum'); + + event_emitter.emit_collateral_sum_updated(market, collateral_token, is_long, delta, next_value); + + next_value } -/// Validata the open interest. +/// Update the funding state /// # Arguments /// * `data_store` - The data store to use. -/// * `market` - The market to validate the open interest for. -/// * `is_long` - Whether to validate the long or short side. -fn validate_open_interest(data_store: IDataStoreDispatcher, market: @Market, is_long: bool) { - // Get the open interest. - let open_interest = get_open_interest_for_market_is_long(data_store, market, is_long); +/// * `event_emitter` - The event emitter. +/// * `market` - The market. +/// * `prices` - The market prices. +fn update_funding_state( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + market: Market, + prices: MarketPrices +) { + let result = get_next_funding_amount_per_size(data_store, market, prices); + + apply_delta_to_funding_fee_amount_per_size( + data_store, + event_emitter, + market.market_token, + market.long_token, + true, + result.funding_fee_amount_per_size_delta.long.long_token + ); - // Get the maximum open interest. - let max_open_interest = get_max_open_interest(data_store, *market.market_token, is_long); + apply_delta_to_funding_fee_amount_per_size( + data_store, + event_emitter, + market.market_token, + market.long_token, + false, + result.funding_fee_amount_per_size_delta.short.long_token + ); - // Check that the open interest is not greater than the maximum open interest. - assert(open_interest <= max_open_interest, MarketError::MAX_OPEN_INTEREST_EXCEEDED); -} + apply_delta_to_funding_fee_amount_per_size( + data_store, + event_emitter, + market.market_token, + market.short_token, + true, + result.funding_fee_amount_per_size_delta.long.short_token + ); -/// Validata the swap market. -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market to validate the open interest for. -fn validate_swap_market(data_store: @IDataStoreDispatcher, market: @Market) { // TODO + apply_delta_to_funding_fee_amount_per_size( + data_store, + event_emitter, + market.market_token, + market.short_token, + false, + result.funding_fee_amount_per_size_delta.short.short_token + ); + + apply_delta_to_claimable_funding_amount_per_size( + data_store, + event_emitter, + market.market_token, + market.long_token, + true, + result.claimable_funding_amount_per_size_delta.long.long_token + ); + + apply_delta_to_claimable_funding_amount_per_size( + data_store, + event_emitter, + market.market_token, + market.long_token, + false, + result.claimable_funding_amount_per_size_delta.short.long_token + ); + + apply_delta_to_claimable_funding_amount_per_size( + data_store, + event_emitter, + market.market_token, + market.short_token, + true, + result.claimable_funding_amount_per_size_delta.long.short_token + ); + + apply_delta_to_claimable_funding_amount_per_size( + data_store, + event_emitter, + market.market_token, + market.short_token, + false, + result.claimable_funding_amount_per_size_delta.short.short_token + ); + + let key = keys::funding_updated_at_key(market.market_token); + data_store.set_u256(key, get_block_timestamp().into()); } -// @dev get the opposite token of the market -// if the input_token is the token_long return the short_token and vice versa +/// Get the next funding amount per size values. /// # Arguments -/// * `market` - The market to validate the open interest for. -/// * `token` - The input_token. +/// * `data_store` - The data store to use. +/// * `market` - The market to update. +/// * `prices` - The market prices. /// # Returns -/// The opposite token. -fn get_opposite_token(market: @Market, token: ContractAddress) -> ContractAddress { - // TODO - token -} +/// The next funding amount per size values. +fn get_next_funding_amount_per_size( + data_store: IDataStoreDispatcher, market: Market, prices: MarketPrices +) -> GetNextFundingAmountPerSizeResult { + let mut result: GetNextFundingAmountPerSizeResult = Default::default(); + let divisor = get_pool_divisor(market.long_token, market.short_token); + + // get the open interest values by long / short and by collateral used. + + let open_interest = PositionType { + long: CollateralType { + long_token: get_open_interest_div( + data_store, market.market_token, market.long_token, true, divisor + ), + short_token: get_open_interest_div( + data_store, market.market_token, market.short_token, true, divisor + ), + }, + short: CollateralType { + long_token: get_open_interest_div( + data_store, market.market_token, market.long_token, false, divisor + ), + short_token: get_open_interest_div( + data_store, market.market_token, market.short_token, false, divisor + ), + }, + }; -// 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 -} + // sum the open interest values to get the total long and short open interest values. + let long_open_interest = open_interest.long.long_token + open_interest.long.short_token; + let short_open_interest = open_interest.short.long_token + open_interest.short.short_token; -// Get the ratio of pnl to pool value. -// # Arguments -// * `data_store` - The data_store dispatcher. -// * `market` the market values. -// * `prices` the prices of the market tokens. -// * `is_long` whether to get the value for the long or short side. -// * `maximize` whether to maximize the factor. -// # Returns -// (pnl of positions) / (long or short pool value) -fn get_pnl_to_pool_factor( + // if either long or short open interest is zero, then funding should not be updated + // as there would not be any user to pay the funding to. + if long_open_interest == 0 || short_open_interest == 0 { + return result; + } + + // if the blockchain is not progressing / a market is disabled, funding fees + // will continue to accumulate + // this should be a rare occurrence so funding fees are not adjusted for this case. + let duration_in_seconds = get_seconds_since_funding_updated(data_store, market.market_token); + + let diff_usd = calc::diff(long_open_interest, short_open_interest); + let total_open_interest = long_open_interest + short_open_interest; + let size_of_larger_side = if long_open_interest > short_open_interest { + long_open_interest + } else { + short_open_interest + }; + + result + .funding_factor_per_second = + get_funding_factor_per_second( + data_store, market.market_token, diff_usd, total_open_interest + ); + + // for single token markets, if there is $200,000 long open interest + // and $100,000 short open interest and if the fundingUsd is $8: + // fundingUsdForLongCollateral: $4 + // fundingUsdForShortCollateral: $4 + // fundingFeeAmountPerSizeDelta.long.longToken: 4 / 100,000 + // fundingFeeAmountPerSizeDelta.long.shortToken: 4 / 100,000 + // claimableFundingAmountPerSizeDelta.short.longToken: 4 / 100,000 + // claimableFundingAmountPerSizeDelta.short.shortToken: 4 / 100,000 + // + // the divisor for fundingFeeAmountPerSizeDelta is 100,000 because the + // cache.openInterest.long.longOpenInterest and cache.openInterest.long.shortOpenInterest is divided by 2 + // + // when the fundingFeeAmountPerSize value is incremented, it would be incremented twice: + // 4 / 100,000 + 4 / 100,000 = 8 / 100,000 + // + // since the actual long open interest is $200,000, this would result in a total of 8 / 100,000 * 200,000 = $16 being charged + // + // when the claimableFundingAmountPerSize value is incremented, it would similarly be incremented twice: + // 4 / 100,000 + 4 / 100,000 = 8 / 100,000 + // + // when calculating the amount to be claimed, the longTokenClaimableFundingAmountPerSize and shortTokenClaimableFundingAmountPerSize + // are compared against the market's claimableFundingAmountPerSize for the longToken and claimableFundingAmountPerSize for the shortToken + // + // since both these values will be duplicated, the amount claimable would be: + // (8 / 100,000 + 8 / 100,000) * 100,000 = $16 + // + // due to these, the fundingUsd should be divided by the divisor + + let funding_usd = precision::apply_factor_u256( + size_of_larger_side, duration_in_seconds * result.funding_factor_per_second + ); + let funding_usd = funding_usd / divisor; + + result.longs_pay_shorts = long_open_interest > short_open_interest; + + // split the fundingUsd value by long and short collateral + // e.g. if the fundingUsd value is $500, and there is $1000 of long open interest using long collateral and $4000 of long open interest + // with short collateral, then $100 of funding fees should be paid from long positions using long collateral, $400 of funding fees + // should be paid from long positions using short collateral + // short positions should receive $100 of funding fees in long collateral and $400 of funding fees in short collateral + let funding_usd_for_long_collateral = if result.longs_pay_shorts { + precision::mul_div(funding_usd, open_interest.long.long_token, long_open_interest) + } else { + precision::mul_div(funding_usd, open_interest.short.long_token, short_open_interest) + }; + + let funding_usd_for_short_collateral = if result.longs_pay_shorts { + precision::mul_div(funding_usd, open_interest.long.short_token, long_open_interest) + } else { + precision::mul_div(funding_usd, open_interest.short.short_token, short_open_interest) + }; + + // calculate the change in funding amount per size values + // for example, if the fundingUsdForLongCollateral is $100, the longToken price is $2000, the longOpenInterest is $10,000, shortOpenInterest is $5000 + // if longs pay shorts then the fundingFeeAmountPerSize.long.longToken should be increased by 0.05 tokens per $10,000 or 0.000005 tokens per $1 + // the claimableFundingAmountPerSize.short.longToken should be increased by 0.05 tokens per $5000 or 0.00001 tokens per $1 + if result.longs_pay_shorts { + // use the same longTokenPrice.max and shortTokenPrice.max to calculate the amount to be paid and received + // positions only pay funding in the position's collateral token + // so the fundingUsdForLongCollateral is divided by the total long open interest for long positions using the longToken as collateral + // and the fundingUsdForShortCollateral is divided by the total long open interest for long positions using the shortToken as collateral + let amount = get_funding_amount_per_size_delta( + funding_usd_for_long_collateral, + open_interest.long.long_token, + prices.long_token_price.max, + true // roundUpMagnitude + ); + result.funding_fee_amount_per_size_delta.long.long_token = amount; + + let amount = get_funding_amount_per_size_delta( + funding_usd_for_short_collateral, + open_interest.long.short_token, + prices.short_token_price.max, + true // roundUpMagnitude + ); + result.funding_fee_amount_per_size_delta.long.short_token = amount; + + // positions receive funding in both the longToken and shortToken + // so the fundingUsdForLongCollateral and fundingUsdForShortCollateral is divided by the total short open interest + let amount = get_funding_amount_per_size_delta( + funding_usd_for_long_collateral, + short_open_interest, + prices.long_token_price.max, + false // roundUpMagnitude + ); + result.claimable_funding_amount_per_size_delta.short.long_token = amount; + + let amount = get_funding_amount_per_size_delta( + funding_usd_for_short_collateral, + short_open_interest, + prices.short_token_price.max, + false // roundUpMagnitude + ); + result.claimable_funding_amount_per_size_delta.short.short_token = amount; + } else { + // use the same longTokenPrice.max and shortTokenPrice.max to calculate the amount to be paid and received + // positions only pay funding in the position's collateral token + // so the fundingUsdForLongCollateral is divided by the total short open interest for short positions using the longToken as collateral + // and the fundingUsdForShortCollateral is divided by the total short open interest for short positions using the shortToken as collateral + let amount = get_funding_amount_per_size_delta( + funding_usd_for_long_collateral, + open_interest.short.long_token, + prices.long_token_price.max, + true // roundUpMagnitude + ); + result.funding_fee_amount_per_size_delta.short.long_token = amount; + + let amount = get_funding_amount_per_size_delta( + funding_usd_for_short_collateral, + open_interest.short.short_token, + prices.short_token_price.max, + true // roundUpMagnitude + ); + result.funding_fee_amount_per_size_delta.short.short_token = amount; + + // positions receive funding in both the longToken and shortToken + // so the fundingUsdForLongCollateral and fundingUsdForShortCollateral is divided by the total long open interest + let amount = get_funding_amount_per_size_delta( + funding_usd_for_long_collateral, + long_open_interest, + prices.long_token_price.max, + false // roundUpMagnitude + ); + result.claimable_funding_amount_per_size_delta.long.long_token = amount; + + let amount = get_funding_amount_per_size_delta( + funding_usd_for_short_collateral, + long_open_interest, + prices.short_token_price.max, + false // roundUpMagnitude + ); + result.claimable_funding_amount_per_size_delta.long.short_token = amount; + } + + result +} + +fn get_swap_impact_amount_with_cap( + data_store: IDataStoreDispatcher, + market: ContractAddress, + token: ContractAddress, + token_price: Price, + price_impact_usd: i256 +) -> i256 { + let mut impact_amount: i256 = Zeroable::zero(); + // positive impact: minimize impactAmount, use tokenPrice.max + // negative impact: maximize impactAmount, use tokenPrice.min + if price_impact_usd > Zeroable::zero() { + // round positive impactAmount down, this will be deducted from the swap impact pool for the user + let price = to_signed(token_price.max, true); + + let max_impact_amount = to_signed( + get_swap_impact_pool_amount(data_store, market, token), true + ); + + if (impact_amount > max_impact_amount) { + impact_amount = max_impact_amount; + } + } else { + let price = token_price.min; + // round negative impactAmount up, this will be deducted from the user + impact_amount = roundup_magnitude_division(price_impact_usd, price); + } + impact_amount +} + +fn get_open_interest_div( data_store: IDataStoreDispatcher, - oracle: IOracleDispatcher, market: ContractAddress, + collateral_token: ContractAddress, is_long: bool, - maximize: bool -) -> u128 { - // TODO - 0 + divisor: u256 +) -> u256 { + error_utils::check_division_by_zero(divisor, 'get_open_interest'); + let key = keys::open_interest_key(market, collateral_token, is_long); + data_store.get_u256(key) / divisor +} + +/// Get the long and short open interest for a market. +/// # Arguments +/// * `data_store` - The data store to use. +/// * `market` - The market to get the open interest for. +/// # Returns +/// The long and short open interest for a market. +fn get_open_interest_for_market(data_store: IDataStoreDispatcher, market: @Market) -> u256 { + // Get the open interest for the long token as collateral. + let long_open_interest = get_open_interest_for_market_is_long(data_store, market, true); + // Get the open interest for the short token as collateral. + let short_open_interest = get_open_interest_for_market_is_long(data_store, market, false); + long_open_interest + short_open_interest +} + +/// Get the long and short open interest for a market. +/// # Arguments +/// * `data_store` - The data store to use. +/// * `market` - The market to get the open interest for. +/// * `is_long` - Whether to get the long or short open interest. +/// # Returns +/// The long and short open interest for a market. +fn get_open_interest_for_market_is_long( + data_store: IDataStoreDispatcher, market: @Market, is_long: bool +) -> u256 { + // 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_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_div( + data_store, *market.market_token, *market.short_token, is_long, divisor + ); + // Return the sum of the open interests. + open_interest_using_long_token_as_collateral + open_interest_using_short_token_as_collateral +} + + +/// Get the long and short open interest in tokens for a market. +/// # Arguments +/// * `data_store` - The data store to use. +/// * `market` - The market to get the open interest for. +/// * `is_long` - Whether to get the long or short open interest. +/// # Returns +/// The long and short open interest in tokens for a market based on the collateral token used. +fn get_open_interest_in_tokens_for_market( + data_store: IDataStoreDispatcher, market: @Market, is_long: bool, +) -> u256 { + // 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_in_tokens( + 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_in_tokens( + data_store, *market.market_token, *market.short_token, is_long, divisor + ); + // Return the sum of the open interests. + open_interest_using_long_token_as_collateral + open_interest_using_short_token_as_collateral +} + +/// Get the long and short open interest in tokens 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. +/// # Returns +/// The long and short open interest in tokens for a market based on the collateral token used. +fn get_open_interest_in_tokens( + data_store: IDataStoreDispatcher, + market: ContractAddress, + collateral_token: ContractAddress, + is_long: bool, + divisor: u256 +) -> u256 { + error_utils::check_division_by_zero(divisor, 'get_open_interest_in_tokens'); + data_store.get_u256(keys::open_interest_in_tokens_key(market, collateral_token, is_long)) + / divisor +} + +/// Get the pool divisor. +/// This is used to divide the values of `get_pool_amount` and `get_open_interest` +/// if the longToken and shortToken are the same, then these values have to be divided by two +/// to avoid double counting +/// # Arguments +/// * `long_token` - The long token. +/// * `short_token` - The short token. +/// # Returns +/// The pool divisor. +fn get_pool_divisor(long_token: ContractAddress, short_token: ContractAddress) -> u256 { + if long_token == short_token { + 2 + } else { + 1 + } +} + +/// 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 +/// *`data_store` DataStore +/// *`event_emitter` EventEmitter +/// *`market` the market to apply to +/// *`token` the token to apply to +/// *`token_price` the price of the token +/// *`price_impact_usd` the USD price impact +/// # Returns +/// The impact amount as integer +fn apply_swap_impact_with_cap( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + market: ContractAddress, + token: ContractAddress, + token_price: Price, + price_impact_usd: i256 +) -> i256 { + let impact_amount: i256 = get_swap_impact_amount_with_cap( + data_store, market, token, token_price, price_impact_usd + ); + + // if there is a positive impact, the impact pool amount should be reduced + // if there is a negative impact, the impact pool amount should be increased + apply_delta_to_swap_impact_pool( + data_store, event_emitter, market, token, i256_neg(impact_amount) + ); + + return impact_amount; +} + +/// @dev validate that the pool amount is within the max allowed amount +/// # Arguments +/// *`data_store` DataStore +/// *`market` the market to check +/// *`token` the token to check +fn validate_pool_amount( + data_store: @IDataStoreDispatcher, market: @Market, token: ContractAddress +) { + let pool_amount: u256 = get_pool_amount(*data_store, market, token); + let max_pool_amount: u256 = get_max_pool_amount(*data_store, *market.market_token, token); + if (pool_amount > max_pool_amount) { + MarketError::MAX_POOL_AMOUNT_EXCEEDED(pool_amount, max_pool_amount); + } +} + +use debug::PrintTrait; + +/// Validates that the amount of tokens required to be reserved is below the configured threshold. +/// # Arguments +/// * `dataStore`: DataStore - The data storage instance. +/// * `market`: Market values to consider. +/// * `prices`: Prices of the market tokens. +/// * `is_long`: A boolean flag to indicate whether to check the long or short side. +fn validate_reserve( + data_store: IDataStoreDispatcher, market: @Market, prices: @MarketPrices, is_long: bool +) { + // poolUsd is used instead of pool amount as the indexToken may not match the longToken + // additionally, the shortToken may not be a stablecoin + let pool_usd = get_pool_usd_without_pnl(data_store, market, prices, is_long, false); + let reserve_factor = get_reserve_factor(data_store, *market.market_token, is_long); + let max_reserved_usd = apply_factor_u256(pool_usd, reserve_factor); + + let reserved_usd = get_reserved_usd(data_store, market, prices, is_long); + + if (reserved_usd > max_reserved_usd) { + MarketError::INSUFFICIENT_RESERVE(reserved_usd, max_reserved_usd); + } +} + + +/// Validate the open interest. +/// # Arguments +/// * `data_store` - The data store to use. +/// * `market` - The market to validate the open interest for. +/// * `is_long` - Whether to validate the long or short side. +fn validate_open_interest(data_store: IDataStoreDispatcher, market: @Market, is_long: bool) { + // Get the open interest. + let open_interest = get_open_interest_for_market_is_long(data_store, market, is_long); + // Get the maximum open interest. + let max_open_interest = get_max_open_interest(data_store, *market.market_token, is_long); + + // Check that the open interest is not greater than the maximum open interest. + if (open_interest > max_open_interest) { + MarketError::MAX_OPEN_INTEREST_EXCEDEED(open_interest, max_open_interest); + } } // Get the ratio of pnl to pool value. // # Arguments // * `data_store` - The data_store dispatcher. -// * `market` Rhe market. +// * `market` the market values. // * `prices` the prices of the market tokens. // * `is_long` whether to get the value for the long or short side. // * `maximize` whether to maximize the factor. // # Returns // (pnl of positions) / (long or short pool value) -// TODO same function names getPnlToPoolFactor -fn get_pnl_to_pool_factor_from_prices( +fn get_pnl_to_pool_factor( data_store: IDataStoreDispatcher, - market: Market, - prices: MarketPrices, + oracle: IOracleDispatcher, + market: ContractAddress, is_long: bool, maximize: bool -) -> i128 { - // TODO - 0 -} +) -> i256 { + let market: Market = get_enabled_market(data_store, market); + let prices: MarketPrices = MarketPrices { + index_token_price: oracle.get_primary_price(market.index_token), + long_token_price: oracle.get_primary_price(market.long_token), + short_token_price: oracle.get_primary_price(market.short_token) + }; + return get_pnl_to_pool_factor_from_prices(data_store, @market, @prices, is_long, maximize); +} -// 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( +/// 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( data_store: IDataStoreDispatcher, - oracle: IOracleDispatcher, - market_address: ContractAddress, + market: @Market, + prices: @MarketPrices, is_long: bool, - pnl_factor_type: felt252 -) -> (bool, u128, u128) { - // TODO - (true, 0, 0) + maximize: bool +) -> i256 { + let pool_usd: u256 = get_pool_usd_without_pnl(data_store, market, prices, is_long, !maximize); + if pool_usd == 0 { + return Zeroable::zero(); + } + let pnl: i256 = get_pnl(data_store, market, prices.index_token_price, is_long, maximize); + return to_factor_ival(pnl, pool_usd); } -// Check if the pending pnl exceeds the allowed amount -// # Arguments -// * `data_store` - The data_store 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_direct( +/// Validates the token balance for a single market. +/// # Arguments +/// * `data_store` - The data_store dispatcher +/// * `market` - Address of the market to check. +fn validate_market_token_balance_with_address( + data_store: IDataStoreDispatcher, market: ContractAddress +) { + let enabled_market: Market = get_enabled_market(data_store, market); + validate_market_token_balance_check(data_store, enabled_market); +} + +/// Update the cumulative borrowing factor for a market +/// # Arguments +/// * `data_store` - The data store to use. +/// * `event_emitter` - The event emitter. +/// * `market` - The market. +/// * `prices` - The market prices. +/// * `is_long` - Whether to update the long or short side. +fn update_cumulative_borrowing_factor( data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, market: Market, prices: MarketPrices, - is_long: bool, - pnl_factor_type: felt252 -) -> (bool, i128, u128) { - // TODO - (true, 0, 0) + is_long: bool +) { + let (_, delta) = get_next_cumulative_borrowing_factor(data_store, market, prices, is_long); + increment_cumulative_borrowing_factor( + data_store, event_emitter, market.market_token, is_long, delta + ); + let block_timestamp: u256 = starknet::info::get_block_timestamp().into(); + + data_store + .set_u256( + keys::cumulative_borrowing_factor_updated_at_key(market.market_token, is_long), + block_timestamp + ); +} + +/// Get the virtual inventory for positions. +/// +/// # Arguments +/// * `dataStore`: DataStore - The data storage instance. +/// * `token`: The token to check. +/// +/// # Returns +/// Returns a tuple (has_virtual_inventory, virtual_token_inventory). +fn get_virtual_inventory_for_positions( + data_store: IDataStoreDispatcher, token: ContractAddress +) -> (bool, i256) { + let virtual_token_id: felt252 = data_store.get_felt252(keys::virtual_token_id_key(token)); + if virtual_token_id == 0.into() { + return (false, Zeroable::zero()); + } + return (true, data_store.get_i256(keys::virtual_inventory_for_positions_key(virtual_token_id))); +} + +// 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: u256, open_interest: u256, token_price: u256, roundup_magnitude: bool +) -> u256 { + if funding_usd == 0 || open_interest == 0 { + return 0; + } + let funding_usd_per_size: u256 = mul_div_roundup( + funding_usd, FLOAT_PRECISION * FLOAT_PRECISION_SQRT, open_interest, roundup_magnitude + ); + if roundup_magnitude { + roundup_division(funding_usd_per_size, token_price) + } else { + funding_usd_per_size / token_price + } +} + +// @dev validate that the amount of tokens required to be reserved for open interest +// is below the configured threshold +// @param dataStore: DataStore - The data storage instance. +// @param market: Market values to consider. +// @param prices: Prices of the market tokens. +// @param is_long: A boolean flag to indicate whether to check the long or short side. +fn validate_open_interest_reserve( + data_store: IDataStoreDispatcher, market: @Market, prices: @MarketPrices, is_long: bool +) { + // poolUsd is used instead of pool amount as the indexToken may not match the longToken + // additionally, the shortToken may not be a stablecoin + let pool_usd: u256 = get_pool_usd_without_pnl(data_store, market, prices, is_long, false); + let reserve_factor: u256 = get_open_interest_reserve_factor( + data_store, *market.market_token, is_long + ); + let max_reserved_usd: u256 = apply_factor_u256(pool_usd, reserve_factor); + + let reserved_usd: u256 = get_reserved_usd(data_store, market, prices, is_long); + + if (reserved_usd > max_reserved_usd) { + MarketError::INSUFFICIENT_RESERVE(reserved_usd, max_reserved_usd); + } +} + +// @notice Get the next borrowing fees for a position. +// +// @param data_store IDataStoreDispatcher +// @param position Position +// @param market Market +// @param prices @MarketPrices +// +// @return The next borrowing fees for a position. +fn get_next_borrowing_fees( + data_store: IDataStoreDispatcher, position: @Position, market: @Market, prices: @MarketPrices +) -> u256 { + let (next_cumulative_borrowing_factor, _) = get_next_cumulative_borrowing_factor( + data_store, *market, *prices, *position.is_long + ); + if (next_cumulative_borrowing_factor < *position.borrowing_factor) { + MarketError::UNEXCEPTED_BORROWING_FACTOR( + *position.borrowing_factor, next_cumulative_borrowing_factor + ); + } + let diff_factor = next_cumulative_borrowing_factor - *position.borrowing_factor; + return apply_factor_u256(*position.size_in_usd, diff_factor); +} + +// @notice Get the total reserved USD required for positions. +// +// @param market The market to check. +// @param prices The prices of the market tokens. +// @param is_long Whether to get the value for the long or short side. +// +// @return The total reserved USD required for positions. +fn get_reserved_usd( + data_store: IDataStoreDispatcher, market: @Market, prices: @MarketPrices, is_long: bool +) -> u256 { + let mut reserved_usd: u256 = 0; + if (is_long) { + // for longs calculate the reserved USD based on the open interest and current indexTokenPrice + // this works well for e.g. an ETH / USD market with long collateral token as WETH + // the available amount to be reserved would scale with the price of ETH + // this also works for e.g. a SOL / USD market with long collateral token as WETH + // if the price of SOL increases more than the price of ETH, additional amounts would be + // automatically reserved + let open_interest_in_tokens = get_open_interest_in_tokens_for_market( + data_store, market, is_long + ); + reserved_usd = open_interest_in_tokens * *prices.index_token_price.max; + } else { + // for shorts use the open interest as the reserved USD value + // this works well for e.g. an ETH / USD market with short collateral token as USDC + // the available amount to be reserved would not change with the price of ETH + reserved_usd = get_open_interest_for_market_is_long(data_store, market, is_long); + } + reserved_usd +} + +fn get_is_long_token(market: Market, token: ContractAddress) -> bool { + if (token != market.long_token && token != market.short_token) { + MarketError::UNEXCEPTED_TOKEN(token); + } + return token == market.long_token; +} + +/// Update the virtual inventory for swaps. +/// +/// # Arguments +/// * `data_store`: The data storage instance. +/// * `market_address`: The address of the market to update. +/// * `token`: The token to update. +/// * `delta`: The update amount. +/// +/// # Returns +/// Returns a tuple (success, updated_amount). +fn apply_delta_to_virtual_inventory_for_swaps( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + market: Market, + token: ContractAddress, + delta: i256 +) -> (bool, u256) { + let virtual_market_id: felt252 = data_store + .get_felt252(keys::virtual_market_id_key(market.market_token)); + if (virtual_market_id == 0) { + return (false, 0); + } + let is_long_token: bool = get_is_long_token(market, token); + + let next_value: u256 = data_store + .apply_bounded_delta_to_u256( + keys::virtual_inventory_for_swaps_key(virtual_market_id, is_long_token), delta + ); + + event_emitter + .emit_virtual_swap_inventory_updated( + market.market_token, is_long_token, virtual_market_id, delta, next_value + ); + + return (true, next_value); +} + +/// Update the virtual inventory for positions. +/// +/// # Arguments +/// * `data_store`: The data storage instance. +/// * `event_emitter`: The event emitter instance. +/// * `token`: The token to update. +/// * `delta`: The update amount. +fn apply_delta_to_virtual_inventory_for_positions( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + token: ContractAddress, + delta: i256 +) -> (bool, i256) { + let virtual_token_id: felt252 = data_store.get_felt252(keys::virtual_token_id_key(token)); + if (virtual_token_id == 0) { + return (false, Zeroable::zero()); + } + + let next_value: i256 = data_store + .apply_delta_to_i256(keys::virtual_inventory_for_positions_key(virtual_token_id), delta); + event_emitter + .emit_virtual_position_inventory_updated(token, virtual_token_id, delta, next_value); + + return (true, next_value); +} + +/// Get the borrowing fees for a position, assumes that cumulativeBorrowingFactor +/// has already been updated to the latest value +/// # Arguments +/// * `dataStore` - DataStore +/// * `position` - Position +/// * `dataStore` - DataStore +/// # Returns +/// The borrowing fees for a position +fn get_borrowing_fees(data_store: IDataStoreDispatcher, position: @Position) -> u256 { + let cumulative_borrowing_factor: u256 = get_cumulative_borrowing_factor( + @data_store, *position.market, *position.is_long + ); + + if (cumulative_borrowing_factor < *position.borrowing_factor) { + MarketError::UNEXCEPTED_BORROWING_FACTOR( + *position.borrowing_factor, cumulative_borrowing_factor + ); + } + let diff_factor: u256 = cumulative_borrowing_factor - *position.borrowing_factor; + return apply_factor_u256(*position.size_in_usd, diff_factor); +} + +/// Get the funding amount to be deducted or distributed +/// # Arguments +/// * `latestFundingAmountPerSize` - the latest funding amount per size +/// * `dataSpositionFundingAmountPerSizetore` - the funding amount per size for the position +/// * `positionSizeInUsd` - the position size in USD +/// * `roundUpMagnitude` - whether the round up the result +/// # Returns +/// fundingAmount +fn get_funding_amount( + latest_funding_amount_per_size: u256, + position_funding_amount_per_size: u256, + position_size_in_usd: u256, + roundup_magnitude: bool +) -> u256 { + let funding_diff_factor: u256 = latest_funding_amount_per_size + - position_funding_amount_per_size; + return mul_div_roundup( + position_size_in_usd, + funding_diff_factor, + FLOAT_PRECISION * FLOAT_PRECISION_SQRT, + roundup_magnitude + ); +} + +/// The sum of open interest and pnl for a market +// get_open_interest_in_tokens * token_price would not reflect pending positive pnl +// for short positions, so get_open_interest_with_pnl should be used if that info is needed +/// # Arguments +/// * `data_store` - The data store to use. +/// * `market` - The market. +/// * `index_token_price` - The price of the index token. +/// * `is_long` - Whether to check the long or short side +/// * `maximize` - Whether to maximize or minimize the net PNL. +/// # Returns +/// The net pending pnl for a market +fn get_open_interest_with_pnl( + data_store: IDataStoreDispatcher, + market: @Market, + index_token_price: @Price, + is_long: bool, + maximize: bool +) -> i256 { + let open_interest: u256 = get_open_interest_for_market_is_long(data_store, market, is_long); + let pnl: i256 = get_pnl(data_store, market, index_token_price, is_long, maximize); + return sum_return_int_256(open_interest, pnl); +} + + +/// Get the virtual inventory for swaps +/// # Arguments +/// * `data_store` - The data store to use. +/// * `market` - The market. +/// # Returns +/// The tuple (has virtual inventory, virtual long token inventory, virtual short token inventory) +fn get_virtual_inventory_for_swaps( + data_store: IDataStoreDispatcher, market: ContractAddress +) -> (bool, u256, u256) { + let virtual_market_id = data_store.get_felt252(keys::virtual_market_id_key(market)); + if virtual_market_id.is_zero() { + return (false, 0, 0); + } + + return ( + true, + data_store.get_u256(keys::virtual_inventory_for_swaps_key(virtual_market_id, true)), + data_store.get_u256(keys::virtual_inventory_for_swaps_key(virtual_market_id, false)) + ); +} + +fn apply_delta_to_funding_fee_amount_per_size( + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + market: ContractAddress, + collateral_token: ContractAddress, + is_long: bool, + delta: u256 +) { + if delta == 0 { + return; + } + let delta = to_signed(delta, true); + let next_value: u256 = data_store + .apply_delta_to_u256( + 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 + ); +} + +// 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, +) -> u256 { + let (max_positive_impact_factor, max_negative_impact_factor) = get_max_position_impact_factors( + data_store, market + ); + + if is_positive { + max_positive_impact_factor + } else { + max_negative_impact_factor + } +} + +fn get_max_position_impact_factors( + data_store: IDataStoreDispatcher, market: ContractAddress, +) -> (u256, u256) { + let mut max_positive_impact_factor: u256 = data_store + .get_u256(keys::max_position_impact_factor_key(market, true)); + let max_negative_impact_factor: u256 = data_store + .get_u256(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 +) -> u256 { + data_store.get_u256(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) -> u256 { + data_store.get_u256(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 +) -> u256 { + data_store + .get_u256(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: i256, is_long: bool +) -> u256 { + let mut open_interest: u256 = get_open_interest_for_market_is_long( + data_store, @market, is_long + ); + open_interest = calc::sum_return_uint_256(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_u256(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, + collateral_token: ContractAddress, + is_long: bool, + divisor: u256 +) -> u256 { + error_utils::check_division_by_zero(divisor, 'get_collaral_sum'); + data_store.get_u256(keys::collateral_sum_key(market, collateral_token, is_long)) / divisor +} + +// 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 +) -> u256 { + data_store.get_u256(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 +) -> u256 { + data_store.get_u256(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 +) -> u256 { + data_store.get_u256(keys::max_pnl_factor_key(pnl_factor_type, market, is_long)) +} + +// 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 +) -> u256 { + data_store.get_u256(keys::min_pnl_factor_after_adl_key(market, is_long)) +} + +// 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) -> u256 { + data_store.get_u256(keys::funding_factor_key(market)) +} + +// 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) -> u256 { + data_store.get_u256(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 +) -> u256 { + data_store.get_u256(keys::funding_fee_amount_per_size_key(market, collateral_token, is_long)) } -/// 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 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 +) -> u256 { + data_store + .get_u256(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: u256 +) { + if delta == 0 { + return; + } + let error: felt252 = 0; + let delta = to_signed(delta, true); + let next_value: u256 = data_store + .apply_delta_to_u256( + 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: u256 +) { + if delta == 0 { + return; + } + let next_value: u256 = data_store + .apply_delta_to_u256( + 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 + ); +} + +// 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 +) -> u256 { + //Error on this one but its normal the function is not create yet + let updated_at: u256 = data_store.get_u256(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 +} + +// 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: u256, + total_open_interest: u256 +) -> u256 { + let stable_funding_factor: u256 = data_store.get_u256(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: u256 = get_funding_factor(data_store, market); + + let funding_exponent_factor: u256 = get_funding_exponent_factor(data_store, market); + let diff_usd_after_exponent: u256 = apply_exponent_factor(diff_usd, funding_exponent_factor); + + let diff_usd_to_open_interest_factor: u256 = to_factor( + diff_usd_after_exponent, total_open_interest + ); + + return apply_factor_u256(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 +) -> u256 { + data_store.get_u256(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 +) -> u256 { + data_store.get_u256(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 +) -> u256 { + let data_store_n: IDataStoreDispatcher = *data_store; + data_store_n.get_u256(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: u256 +) { + let next_cumulative_borrowing_factor = data_store + .increment_u256(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 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 +) -> u256 { + data_store.get_u256(keys::cumulative_borrowing_factor_updated_at_key(market, is_long)) +} + +// 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 +) -> u256 { + let updated_at: u256 = 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, + is_long: bool, + prev_position_size_in_usd: u256, + prev_position_borrowing_factor: u256, + next_position_size_in_usd: u256, + next_position_borrowing_factor: u256 +) { + let total_borrowing: u256 = 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: u256, + prev_position_borrowing_factor: u256, + next_position_size_in_usd: u256, + next_position_borrowing_factor: u256 +) -> u256 { + let mut total_borrowing: u256 = get_total_borrowing(data_store, market, is_long); + total_borrowing -= apply_factor_u256(prev_position_size_in_usd, prev_position_borrowing_factor); + total_borrowing += apply_factor_u256(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, +) -> (u256, u256) { + let duration_in_seconds: u256 = get_seconds_since_cumulative_borrowing_factor_updated( + data_store, market.market_token, is_long + ); + let borrowing_factor_per_second: u256 = get_borrowing_factor_per_second( + data_store, market, prices, is_long + ); + + let cumulative_borrowing_factor: u256 = get_cumulative_borrowing_factor( + @data_store, market.market_token, is_long + ); + + let delta: u256 = duration_in_seconds * borrowing_factor_per_second; + let next_cumulative_borrowing_factor: u256 = 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 +) -> u256 { + let reserved_usd: u256 = 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()); + + let market_snap = @market; + if (skip_borrowing_fee_for_smaller_side) { + let long_open_interest: u256 = get_open_interest_for_market_is_long( + data_store, market_snap, true + ); + let short_open_interest: u256 = 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: u256 = 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); } -} -/// Returns the primary prices for the market tokens. -/// # Parameters -/// - `oracle`: The Oracle instance. -/// - `market`: The market values. -fn get_market_prices(oracle: IOracleDispatcher, market: Market) -> MarketPrices { - //TODO - Default::default() -} + let borrowing_exponent_factor: u256 = get_borrowing_exponent_factor( + data_store, market.market_token, is_long + ); + let reserved_usd_after_exponent: u256 = apply_exponent_factor( + reserved_usd, borrowing_exponent_factor + ); -/// Validates that the amount of tokens required to be reserved is below the configured threshold. -/// # Arguments -/// * `dataStore`: DataStore - The data storage instance. -/// * `market`: Market values to consider. -/// * `prices`: Prices of the market tokens. -/// * `isLong`: A boolean flag to indicate whether to check the long or short side. -fn validate_reserve( - data_store: IDataStoreDispatcher, market: Market, prices: @MarketPrices, is_long: bool -) { //TODO -} + let reserved_usd_to_pool_factor: u256 = to_factor(reserved_usd_after_exponent, pool_usd); + let borrowing_factor: u256 = get_borrowing_factor(data_store, market.market_token, 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 + apply_factor_u256(reserved_usd_to_pool_factor, borrowing_factor) } -/// Validates the token balance for a single market. -/// # Arguments -/// * `data_store` - The data_store dispatcher -/// * `market` - Address of the market to check. -fn validate_market_token_balance_with_address( - data_store: IDataStoreDispatcher, market: ContractAddress -) { //TODO -} +// 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 +) -> u256 { + let open_interest: u256 = 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: u256 = get_total_borrowing(data_store, market.market_token, is_long); -fn validate_market_token_balance(data_store: IDataStoreDispatcher, market: Market) { //TODO + apply_factor_u256(open_interest, next_cumulative_borrowing_factor) - total_borrowing } -fn validate_markets_token_balance(data_store: IDataStoreDispatcher, market: Span) { //TODO +// 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 +) -> u256 { + data_store.get_u256(keys::total_borrowing_key(market, is_long)) } -/// 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() +// 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: u256 +) { + data_store.set_u256(keys::total_borrowing_key(market, is_long), value) } -/// Gets the USD value of a pool. -/// The value of a pool is determined by the worth of the liquidity provider tokens in the pool, -/// minus any pending trader profit and loss (PNL). -/// We use the token index prices for this calculation and ignore price impact. The reasoning is that -/// if all positions were closed, the net price impact should be zero. -/// # Arguments -/// * `data_store` - The DataStore structure. -/// * `market` - The market values. -/// * `long_token_price` - Price of the long token. -/// * `short_token_price` - Price of the short token. -/// * `index_token_price` - Price of the index token. -/// * `maximize` - Whether to maximize or minimize the pool value. -/// # Returns -/// Returns the value information of a pool. -fn get_pool_value_info( - data_store: IDataStoreDispatcher, - market: Market, - index_token_price: Price, - long_token_price: Price, - short_token_price: Price, - pnl_factor_type: felt252, - maximize: bool -) -> MarketPoolValueInfo { - // TODO - Default::default() +// 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: u256, pool_value: u256, supply: u256) -> u256 { + // 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) } -/// Get the capped pending pnl for a market -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market to get the pending PNL for. -/// * `is_long` - Whether to get the long or short pending PNL. -/// * `pnl` - The uncapped pnl of the market. -/// * `pool_usd` - The USD value of the pool. -/// * `pnl_factor_type` - The pnl factor type to use. -/// # Returns -/// The net pending pnl for a market -fn get_capped_pnl( - data_store: IDataStoreDispatcher, - market: ContractAddress, - is_long: bool, - pnl: i128, - pool_usd: u128, - pnl_factor_type: felt252 -) -> i128 { - // TODOs - 0 +// 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: u256, pool_value: u256, supply: u256) -> u256 { + 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); + validate_enabled_market(data_store, market); +} -/// Validata that the specified market exists and is enabled -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market to validate. +// 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.is_zero(), MarketError::EMPTY_MARKET); - let is_market_disabled = data_store.get_bool(keys::is_market_disabled_key(market.market_token)); + assert(market.market_token != 0.try_into().unwrap(), MarketError::EMPTY_MARKET); - match is_market_disabled { - Option::Some(result) => { - assert(!result, MarketError::DISABLED_MARKET); - }, - Option::None => { - panic_with_felt252(MarketError::DISABLED_MARKET); - } - }; + let is_market_disabled: bool = data_store + .get_bool(keys::is_market_disabled_key(market.market_token)); + + 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); -/// 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_address( - data_store: IDataStoreDispatcher, market: ContractAddress -) { // TODO + 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); + validate_position_market_check(data_store, market); +} -/// 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) { // TODO +// 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() } -/// 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 +// 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 } -/// 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 +// 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); + validate_enabled_market(data_store, market); + market +} -/// 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 +fn get_swap_path_market(data_store: IDataStoreDispatcher, market_add: ContractAddress) -> Market { + let market: Market = data_store.get_market(market_add); + 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 +} -/// Update the funding state -/// # Arguments -/// * `data_store` - The data store to use. -/// * `event_emitter` - The event emitter. -/// * `market` - The market. -/// * `prices` - The market prices. -fn update_funding_state( - data_store: IDataStoreDispatcher, - event_emitter: IEventEmitterDispatcher, - market: Market, - prices: MarketPrices -) { // TODO +fn validate_swap_path(data_store: IDataStoreDispatcher, token_swap_path: Span32) { + let max_swap_path_length: u256 = data_store.get_u256(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; + }; } -/// Update the cumulative borrowing factor for a market -/// # Arguments -/// * `data_store` - The data store to use. -/// * `event_emitter` - The event emitter. -/// * `market` - The market. -/// * `prices` - The market prices. -/// * `is_long` - Whether to update the long or short side. -fn update_cumulative_borrowing_factor( +// 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, - event_emitter: IEventEmitterDispatcher, market: Market, prices: MarketPrices, - is_long: bool -) { // TODO + 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); + } } -/// # 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( +// 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, - market: ContractAddress, + oracle: IOracleDispatcher, + market_add: 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 + pnl_factor_type: felt252 +) -> (bool, i256, u256) { + 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, i256, u256) { + let pnl_to_pool_factor: i256 = get_pnl_to_pool_factor_from_prices( + data_store, @market, @prices, is_long, true + ); + let max_pnl_factor: u256 = get_max_pnl_factor( + data_store, pnl_factor_type, market.market_token, is_long + ); -/// Gets the total supply of the marketToken. -/// # Arguments -/// * `market_token` - The market token whose total supply is to be retrieved. -/// # Returns -/// The total supply of the given marketToken. -fn get_market_token_supply(market_token: IMarketTokenDispatcher) -> u128 { - // TODO - market_token.total_supply() -} + let is_exceeded: bool = pnl_to_pool_factor > Zeroable::zero() + && to_unsigned(pnl_to_pool_factor) > max_pnl_factor; -/// 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 + (is_exceeded, pnl_to_pool_factor, max_pnl_factor) } +fn get_ui_fee_factor(data_store: IDataStoreDispatcher, account: ContractAddress) -> u256 { + let max_ui_fee_factor: u256 = data_store.get_u256(keys::max_ui_fee_factor()); + let ui_fee_factor: u256 = data_store.get_u256(keys::ui_fee_factor_key(account)); -/// 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 + if ui_fee_factor < max_ui_fee_factor { + return ui_fee_factor; + } else { + return max_ui_fee_factor; + } } -/// Get the borrowing factor per second. -/// # Arguments -/// * `data_store` - The `DataStore` contract dispatcher. -/// * `market` - Market to check. -/// * `index_token_price` - Price of the market's index token. -/// * `long_token_price` - Price of the market's long token. -/// * `short_token_price` - Price of the market's short token. -/// * `pnl_factor_type` - The pnl factor type. -/// * `maximize` - Whether to maximize or minimize the net PNL. -/// # Returns -/// Returns an integer representing the calculated market token price and MarketPoolValueInfo struct containing additional information related to market pool value. - -fn get_market_token_price( +fn set_ui_fee_factor( data_store: IDataStoreDispatcher, - market: Market, - index_token_price: Price, - long_token_price: Price, - short_token_price: Price, - pnl_factor_type: felt252, - maximize: bool -) -> (i128, MarketPoolValueInfo) { - // TODO - (0, Default::default()) + event_emitter: IEventEmitterDispatcher, + account: ContractAddress, + ui_fee_factor: u256 +) { + let max_ui_fee_factor: u256 = data_store.get_u256(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_u256(keys::ui_fee_factor_key(account), ui_fee_factor); + + event_emitter.emit_ui_fee_factor_updated(account, ui_fee_factor); } -/// Get the net pending pnl for a market -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market to get the pending PNL for. -/// * `index_token_price` - The price of the index token. -/// * `maximize` - Whether to maximize or minimize the net PNL. -/// # Returns -/// The net pending pnl for a market -fn get_net_pnl( - data_store: IDataStoreDispatcher, market: @Market, index_token_price: @Price, maximize: bool -) -> i128 { - // TODO - 0 +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; + }; } -/// The sum of open interest and pnl for a market -// get_open_interest_in_tokens * token_price would not reflect pending positive pnl -// for short positions, so get_open_interest_with_pnl should be used if that info is needed -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market. -/// * `index_token_price` - The price of the index token. -/// * `is_long` - Whether to check the long or short side -/// * `maximize` - Whether to maximize or minimize the net PNL. -/// # Returns -/// The net pending pnl for a market -fn get_open_interest_with_pnl( - data_store: IDataStoreDispatcher, - market: Market, - index_token_price: Price, - is_long: bool, - maximize: bool -) -> i128 { - // TODO - 0 +fn validate_market_token_balance_span(data_store: IDataStoreDispatcher, markets: Span) { + 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); -/// Get the virtual inventory for swaps -/// # Arguments -/// * `data_store` - The data store to use. -/// * `market` - The market address. -/// # Returns -/// has virtual inventory, virtual long token inventory, virtual short token inventory -fn get_virtual_inventory_for_swaps( - data_store: IDataStoreDispatcher, market: ContractAddress, -) -> (bool, u128, u128) { - // TODO - (false, 0, 0) + 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: u256 = IERC20Dispatcher { contract_address: token } + .balance_of(market.market_token) + .low + .into(); + let expected_min_balance: u256 = 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: u256 = get_collateral_sum( + data_store, market.market_token, token, true, 1 + ); + collateral_amount += get_collateral_sum(data_store, market.market_token, token, false, 1); -/// Get the virtual inventory for positions -/// # Arguments -/// * `data_store` - The data store to use. -/// * `token` - The token to check. -/// # Returns -/// has virtual inventory, virtual inventory -fn get_virtual_inventory_for_positions( - data_store: IDataStoreDispatcher, token: ContractAddress, -) -> (bool, i128) { - // TODO - (false, 0) + if (balance < collateral_amount) { + MarketError::INVALID_MARKET_TOKEN_BALANCE_FOR_COLLATERAL_AMOUNT(balance, collateral_amount); + } + + let claimable_funding_fee_amount = data_store + .get_u256(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 +) -> u256 { + // 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: u256 = data_store.get_u256(keys::pool_amount_key(market.market_token, token)); + let swap_impact_pool_amount: u256 = get_swap_impact_pool_amount( + data_store, market.market_token, token + ); + let claimable_collateral_amount: u256 = data_store + .get_u256(keys::claimable_collateral_amount_key(market.market_token, token)); + let claimable_fee_amount: u256 = data_store + .get_u256(keys::claimable_fee_amount_key(market.market_token, token)); + let claimable_ui_fee_amount: u256 = data_store + .get_u256(keys::claimable_ui_fee_amount_key(market.market_token, token)); + let affiliate_reward_amount: u256 = data_store + .get_u256(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/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..71b83646 100644 --- a/src/mock/governable.cairo +++ b/src/mock/governable.cairo @@ -57,9 +57,10 @@ mod Governable { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_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/mock_account.cairo b/src/mock/mock_account.cairo new file mode 100644 index 00000000..89f530a8 --- /dev/null +++ b/src/mock/mock_account.cairo @@ -0,0 +1,85 @@ +//! Mock Account for testing. + +#[starknet::contract] +mod MockAccount { + // ************************************************************************* + // IMPORTS + // ************************************************************************* + + // Core lib imports. + use core::zeroable::Zeroable; + use starknet::{get_caller_address, ContractAddress}; + use result::ResultTrait; + + // Local imports. + use satoru::oracle::{ + interfaces::account::{IAccount, IAccountDispatcher, IAccountDispatcherTrait} + }; + + + // ************************************************************************* + // STORAGE + // ************************************************************************* + #[storage] + struct Storage { + owner: felt252, + } + + // ************************************************************************* + // EXTERNAL FUNCTIONS + // ************************************************************************* + #[external(v0)] + impl MockAccount of IAccount { + fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 { + 1 + } + fn __validate_deploy__( + self: @ContractState, + class_hash: felt252, + contract_address_salt: felt252, + owner: felt252, + guardian: felt252 + ) -> felt252 { + 1 + } + + fn change_owner( + ref self: ContractState, new_owner: felt252, signature_r: felt252, signature_s: felt252 + ) { + self.owner.write(new_owner); + } + fn change_guardian(ref self: ContractState, new_guardian: felt252) {} + + + fn change_guardian_backup(ref self: ContractState, new_guardian_backup: felt252) {} + + + fn trigger_escape_owner(ref self: ContractState, new_owner: felt252) {} + + fn trigger_escape_guardian(ref self: ContractState, new_guardian: felt252) {} + + fn escape_owner(ref self: ContractState) {} + + fn escape_guardian(ref self: ContractState) {} + + fn cancel_escape(ref self: ContractState) {} + fn get_owner(self: @ContractState) -> felt252 { + self.owner.read() + } + fn get_guardian(self: @ContractState) -> felt252 { + 1 + } + fn get_guardian_backup(self: @ContractState) -> felt252 { + 1 + } + fn get_name(self: @ContractState) -> felt252 { + 1 + } + fn get_guardian_escape_attempts(self: @ContractState) -> u32 { + 1 + } + fn get_owner_escape_attempts(self: @ContractState) -> u32 { + 1 + } + } +} diff --git a/src/mock/referral_storage.cairo b/src/mock/referral_storage.cairo index 2fc2c0eb..9ee76a51 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 { @@ -28,7 +27,7 @@ trait IReferralStorage { /// Set the trader discount share for an affiliate. /// # Arguments /// * `account` - The address of the affiliate. - fn set_referrer_discount_share(ref self: TContractState, discount_share: u128); + fn set_referrer_discount_share(ref self: TContractState, discount_share: u256); /// Set the referral code for a trader. /// # Arguments @@ -64,14 +63,14 @@ trait IReferralStorage { /// * `account` - The address of the affiliate. /// # Returns /// The trader discount share. - fn referrer_discount_shares(self: @TContractState, account: ContractAddress) -> u128; + fn referrer_discount_shares(self: @TContractState, account: ContractAddress) -> u256; /// Get the tier level of an affiliate. /// # Arguments /// * `account` - The address of the affiliate. /// # Returns /// The tier level of the affiliate. - fn referrer_tiers(self: @TContractState, account: ContractAddress) -> u128; + fn referrer_tiers(self: @TContractState, account: ContractAddress) -> u256; /// Get the referral info for a trader. /// # Arguments @@ -93,13 +92,13 @@ trait IReferralStorage { /// * `tier_id` - The tier level. /// * `total_rebate` - The total rebate for the tier (affiliate reward + trader discount). /// * `discount_share` - The share of the total_rebate for traders. - fn set_tier(ref self: TContractState, tier_id: u128, total_rebate: u128, discount_share: u128); + fn set_tier(ref self: TContractState, tier_id: u256, total_rebate: u256, discount_share: u256); /// Set the tier for an affiliate. /// # Arguments /// * `referrer` - The referrer. /// * `tier_id` - The tier level. - fn set_referrer_tier(ref self: TContractState, referrer: ContractAddress, tier_id: u128); + fn set_referrer_tier(ref self: TContractState, referrer: ContractAddress, tier_id: u256); /// Set the owner for a referral code. /// # Arguments @@ -112,7 +111,7 @@ trait IReferralStorage { /// * `tier_level` - The tier level. /// # Returns /// (total_rebate, discount_share). - fn tiers(self: @TContractState, tier_level: u128) -> ReferralTier; + fn tiers(self: @TContractState, tier_level: u256) -> ReferralTier; } #[starknet::contract] @@ -122,7 +121,7 @@ mod ReferralStorage { // ************************************************************************* // Core lib imports. - use starknet::{get_caller_address, ContractAddress}; + use starknet::{get_caller_address, ContractAddress, contract_address_const}; use result::ResultTrait; // Local imports. @@ -137,9 +136,9 @@ mod ReferralStorage { // ************************************************************************* #[storage] struct Storage { - referrer_discount_shares: LegacyMap, - referrer_tiers: LegacyMap, - tiers: LegacyMap, + referrer_discount_shares: LegacyMap, + referrer_tiers: LegacyMap, + tiers: LegacyMap, is_handler: LegacyMap, code_owners: LegacyMap, trader_referral_codes: LegacyMap, @@ -151,12 +150,12 @@ mod ReferralStorage { self.initialize(event_emitter_address); } - const BASIS_POINTS: u128 = 10000; + const BASIS_POINTS: u256 = 10000; // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl ReferralStorageImpl of super::IReferralStorage { fn initialize(ref self: ContractState, event_emitter_address: ContractAddress) { let mut gov_state = Governable::unsafe_new_contract_state(); @@ -171,15 +170,15 @@ mod ReferralStorage { self.trader_referral_codes.read(account) } - fn referrer_discount_shares(self: @ContractState, account: ContractAddress) -> u128 { + fn referrer_discount_shares(self: @ContractState, account: ContractAddress) -> u256 { self.referrer_discount_shares.read(account) } - fn referrer_tiers(self: @ContractState, account: ContractAddress) -> u128 { + fn referrer_tiers(self: @ContractState, account: ContractAddress) -> u256 { self.referrer_tiers.read(account) } - fn tiers(self: @ContractState, tier_level: u128) -> ReferralTier { + fn tiers(self: @ContractState, tier_level: u256) -> ReferralTier { self.tiers.read(tier_level) } @@ -195,7 +194,7 @@ mod ReferralStorage { } fn set_tier( - ref self: ContractState, tier_id: u128, total_rebate: u128, discount_share: u128 + ref self: ContractState, tier_id: u256, total_rebate: u256, discount_share: u256 ) { let gov_state = Governable::unsafe_new_contract_state(); gov_state.only_gov(); @@ -209,14 +208,14 @@ mod ReferralStorage { self.event_emitter.read().emit_set_tier(tier_id, total_rebate, discount_share); } - fn set_referrer_tier(ref self: ContractState, referrer: ContractAddress, tier_id: u128) { + fn set_referrer_tier(ref self: ContractState, referrer: ContractAddress, tier_id: u256) { let gov_state = Governable::unsafe_new_contract_state(); gov_state.only_gov(); self.referrer_tiers.write(referrer, tier_id); self.event_emitter.read().emit_set_referrer_tier(referrer, tier_id); } - fn set_referrer_discount_share(ref self: ContractState, discount_share: u128) { + fn set_referrer_discount_share(ref self: ContractState, discount_share: u256) { assert(discount_share <= BASIS_POINTS, MockError::INVALID_DISCOUNT_SHARE); self.referrer_discount_shares.write(get_caller_address(), discount_share); @@ -240,7 +239,8 @@ mod ReferralStorage { fn register_code(ref self: ContractState, code: felt252) { assert(code != 0, MockError::INVALID_CODE); assert( - self.code_owners.read(code) == 0.try_into().unwrap(), MockError::CODE_ALREADY_EXISTS + self.code_owners.read(code) == contract_address_const::<0>(), + MockError::CODE_ALREADY_EXISTS ); self.code_owners.write(code, get_caller_address()); @@ -270,8 +270,8 @@ 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 referrer: ContractAddress = 0.try_into().unwrap(); + let mut code: felt252 = self.trader_referral_codes(account); + let mut referrer = contract_address_const::<0>(); if (code != 0) { referrer = self.code_owners.read(code); diff --git a/src/nonce/nonce_utils.cairo b/src/nonce/nonce_utils.cairo index 08323eb4..c79b2c7b 100644 --- a/src/nonce/nonce_utils.cairo +++ b/src/nonce/nonce_utils.cairo @@ -10,8 +10,8 @@ use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; /// * `data_store` - The data store to use. /// # Returns /// Return the current nonce value. -fn get_current_nonce(data_store: IDataStoreDispatcher) -> u128 { - data_store.get_u128(keys::nonce()) +fn get_current_nonce(data_store: IDataStoreDispatcher) -> u256 { + data_store.get_u256(keys::nonce()) } /// Increment the current nonce value. @@ -19,8 +19,8 @@ fn get_current_nonce(data_store: IDataStoreDispatcher) -> u128 { /// * `data_store` - The data store to use. /// # Returns /// Return the new nonce value. -fn increment_nonce(data_store: IDataStoreDispatcher) -> u128 { - data_store.increment_u128(keys::nonce(), 1) +fn increment_nonce(data_store: IDataStoreDispatcher) -> u256 { + data_store.increment_u256(keys::nonce(), 1) } /// Creates a felt252 hash using the next nonce. The nonce can also be used directly as a key, @@ -34,7 +34,7 @@ fn get_next_key(data_store: IDataStoreDispatcher) -> felt252 { compute_key(data_store.contract_address, nonce) } -fn compute_key(data_store_address: ContractAddress, nonce: u128) -> felt252 { - let data = array![data_store_address.into(), nonce.into()]; +fn compute_key(data_store_address: ContractAddress, nonce: u256) -> felt252 { + let data = array![data_store_address.into(), nonce.try_into().expect('u256 into felt failed')]; poseidon_hash_span(data.span()) } diff --git a/src/oracle/error.cairo b/src/oracle/error.cairo index 4e1487bb..a3577597 100644 --- a/src/oracle/error.cairo +++ b/src/oracle/error.cairo @@ -1,7 +1,9 @@ mod OracleError { use starknet::ContractAddress; + use serde::Serde; const ALREADY_INITIALIZED: felt252 = 'already_initialized'; + const EMPTY_ORACLE_BLOCK_NUMBERS: felt252 = 'empty_oracle_block_numbers'; fn NON_EMPTY_TOKENS_WITH_PRICES(data: u32) { panic(array!['non empty tokens prices', data.into()]) @@ -35,89 +37,137 @@ mod OracleError { panic(array!['block number not sorted', data_1.into(), data_2.into()]) } - fn ARRAY_OUT_OF_BOUNDS_FELT252(mut data_1: Span, data_2: u128, msg: felt252) { + fn ARRAY_OUT_OF_BOUNDS_FELT252(mut data_1: Span>, data_2: usize, msg: felt252) { let mut data: Array = array!['array out of bounds felt252']; let mut length = data_1.len(); - loop { - if length == 0 { - break; - } - data.append(*data_1.pop_front().unwrap()); - }; + // TODO add data_1 data to error data.append(data_2.into()); data.append(msg); panic(data) } - fn ARRAY_OUT_OF_BOUNDS_U128(mut data_1: Span, data_2: u128, msg: felt252) { - let mut data: Array = array!['array out of bounds u128']; + fn ARRAY_OUT_OF_BOUNDS_U256(mut data_1: Span, data_2: u256, msg: felt252) { + let mut data: Array = array!['array out of bounds u256']; let mut length = data_1.len(); loop { if length == 0 { break; } - data.append((*data_1.pop_front().unwrap()).into()); + data + .append( + (*data_1.pop_front().expect('array pop_front failed')) + .try_into() + .expect('u256 into felt failed') + ); }; - data.append(data_2.into()); + data.append(data_2.try_into().expect('u256 into felt failed')); data.append(msg); panic(data) } - fn INVALID_SIGNER_MIN_MAX_PRICE(data_1: u128, data_2: u128) { - panic(array!['invalid med min-max price', data_1.into(), data_2.into()]) + fn INVALID_SIGNER_MIN_MAX_PRICE(data_1: u256, data_2: u256) { + panic( + array![ + 'invalid med min-max price', + data_1.try_into().expect('u256 into felt failed'), + data_2.try_into().expect('u256 into felt failed') + ] + ) } - fn INVALID_MEDIAN_MIN_MAX_PRICE(data_1: u128, data_2: u128) { - panic(array!['invalid med min-max price', data_1.into(), data_2.into()]) + fn INVALID_MEDIAN_MIN_MAX_PRICE(data_1: u256, data_2: u256) { + panic( + array![ + 'invalid med min-max price', + data_1.try_into().expect('u256 into felt failed'), + data_2.try_into().expect('u256 into felt failed') + ] + ) } fn INVALID_ORACLE_PRICE(data_1: ContractAddress) { panic(array!['invalid oracle price', data_1.into()]) } - fn MIN_ORACLE_SIGNERS(data_1: u128, data_2: u128) { + fn MIN_ORACLE_SIGNERS(data_1: u256, data_2: u256) { let mut data: Array = array!['min oracle signers']; - data.append(data_1.into()); - data.append(data_2.into()); - panic(array!['min oracle signers', data_1.into(), data_2.into()]) + data.append(data_1.try_into().expect('u256 into felt failed')); + data.append(data_2.try_into().expect('u256 into felt failed')); + panic(data) } - fn MAX_ORACLE_SIGNERS(data_1: u128, data_2: u128) { - panic(array!['max oracle signers', data_1.into(), data_2.into()]) + fn MAX_ORACLE_SIGNERS(data_1: u256, data_2: u256) { + panic( + array![ + 'max oracle signers', + data_1.try_into().expect('u256 into felt failed'), + data_2.try_into().expect('u256 into felt failed') + ] + ) } - fn MAX_SIGNERS_INDEX(data_1: u128, data_2: u128) { - panic(array!['max signers index', data_1.into(), data_2.into()]) + fn MAX_SIGNERS_INDEX(data_1: u256, data_2: u256) { + panic( + array![ + 'max signers index', + data_1.try_into().expect('u256 into felt failed'), + data_2.try_into().expect('u256 into felt failed') + ] + ) } - fn EMPTY_SIGNER(data_1: u128) { - panic(array!['empty signers', data_1.into()]) + fn EMPTY_SIGNER(data_1: u256) { + panic(array!['empty signers', data_1.try_into().expect('u256 into felt failed')]) } fn MAX_REFPRICE_DEVIATION_EXCEEDED( - data_1: ContractAddress, data_2: u128, data_3: u128, data_4: u128 + data_1: ContractAddress, data_2: u256, data_3: u256, data_4: u256 ) { panic( array![ - 'max refprice deviation', data_1.into(), data_2.into(), data_3.into(), data_4.into() + 'max refprice deviation', + data_1.into(), + data_2.try_into().expect('u256 into felt failed'), + data_3.try_into().expect('u256 into felt failed'), + data_4.try_into().expect('u256 into felt failed') ] ) } - fn INVALID_PRICE_FEED(data_1: ContractAddress, data_2: u128) { - panic(array!['invalid price feed', data_1.into(), data_2.into()]) + fn INVALID_PRICE_FEED(data_1: ContractAddress, data_2: u256) { + panic( + array![ + 'invalid price feed', + data_1.into(), + data_2.try_into().expect('u256 into felt failed') + ] + ) } fn INVALID_PRIMARY_PRICES_FOR_SIMULATION(data_1: u32, data_2: u32) { panic(array!['Simulation:invalid prim_prices', data_1.into(), data_2.into()]) } - fn PRICE_FEED_NOT_UPDATED(data_1: ContractAddress, data_2: u64, data_3: u128) { - panic(array!['price feed not updated', data_1.into(), data_2.into(), data_3.into()]) + fn PRICE_FEED_NOT_UPDATED(data_1: ContractAddress, data_2: u64, data_3: u256) { + panic( + array![ + 'price feed not updated', + data_1.into(), + data_2.into(), + data_3.try_into().expect('u256 into felt failed') + ] + ) } - fn PRICE_ALREADY_SET(data_1: ContractAddress, data_2: u128, data_3: u128) { - panic(array!['price already set', data_1.into(), data_2.into(), data_3.into()]) + fn PRICE_ALREADY_SET(data_1: ContractAddress, data_2: u256, data_3: u256) { + panic( + array![ + 'price already set', + data_1.into(), + data_2.try_into().expect('u256 into felt failed'), + data_3.try_into().expect('u256 into felt failed') + ] + ) } fn EMPTY_PRICE_FEED(data_1: ContractAddress) { @@ -127,5 +177,90 @@ mod OracleError { fn END_OF_ORACLE_SIMULATION() { panic(array!['end of oracle simulation']) } -} + fn ORACLE_BLOCK_NUMBERS_NOT_WITHIN_RANGE( + min_oracle_block_numbers: Span, max_oracle_block_numbers: Span, block_number: u64 + ) { + let mut data: Array = array![]; + data.append('block number not in range'); + Serde::serialize(min_oracle_block_numbers.snapshot, ref data); + Serde::serialize(max_oracle_block_numbers.snapshot, ref data); + data.append(block_number.into()); + panic(data) + } + + fn ORACLE_BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED( + min_oracle_block_numbers: Span, block_number: u64 + ) { + let mut data: Array = array![]; + data.append('block numbers too small'); + Serde::serialize(min_oracle_block_numbers.snapshot, ref data); + data.append(block_number.into()); + panic(data) + } + + fn BLOCK_NUMBER_NOT_WITHIN_RANGE(mut data_1: Span, mut data_2: Span, data_3: u64) { + let mut data: Array = array!['block number not within range']; + let mut length = data_1.len(); + loop { + if length == 0 { + break; + } + let el = *data_1.pop_front().unwrap(); + data.append(el.into()); + }; + let mut length_2 = data_2.len(); + loop { + if length_2 == 0 { + break; + } + let el = *data_2.pop_front().unwrap(); + data.append(el.into()); + }; + data.append(data_3.into()); + panic(data) + } + + fn EMPTY_COMPACTED_PRICE(data_1: usize) { + panic(array!['empty compacted price', data_1.into()]) + } + + fn EMPTY_COMPACTED_TIMESTAMP(data_1: usize) { + panic(array!['empty compacted timestamp', data_1.into()]) + } + + fn INVALID_SIGNATURE(data_1: felt252, data_2: felt252) { + panic(array!['invalid signature', data_1.into(), data_2.into()]) + } + + fn BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED(mut data_1: Span, data_2: u64) { + let mut data: Array = array!['block numbers too small']; + let mut length = data_1.len(); + loop { + if length == 0 { + break; + } + let el = *data_1.pop_front().unwrap(); + data.append(el.into()); + }; + data.append(data_2.into()); + } + + fn MIN_PRICES_NOT_SORTED(token: ContractAddress, min_price: u256, min_price_prev: u256) { + let mut data: Array = array![]; + data.append('min prices not sorted'); + data.append(token.into()); + data.append(min_price.try_into().expect('u256 into felt failed')); + data.append(min_price_prev.try_into().expect('u256 into felt failed')); + panic(data) + } + + fn MAX_PRICES_NOT_SORTED(token: ContractAddress, max_price: u256, max_price_prev: u256) { + let mut data: Array = array![]; + data.append('max prices not sorted'); + data.append(token.into()); + data.append(max_price.try_into().expect('u256 into felt failed')); + data.append(max_price_prev.try_into().expect('u256 into felt failed')); + panic(data) + } +} diff --git a/src/oracle/interfaces/account.cairo b/src/oracle/interfaces/account.cairo new file mode 100644 index 00000000..651774e2 --- /dev/null +++ b/src/oracle/interfaces/account.cairo @@ -0,0 +1,76 @@ +#[starknet::interface] +trait IAccount { + fn __validate_declare__(self: @TContractState, class_hash: felt252) -> felt252; + fn __validate_deploy__( + self: @TContractState, + class_hash: felt252, + contract_address_salt: felt252, + owner: felt252, + guardian: felt252 + ) -> felt252; + // External + + /// @notice Changes the owner + /// Must be called by the account and authorised by the owner and a guardian (if guardian is set). + /// @param new_owner New owner address + /// @param signature_r Signature R from the new owner + /// @param signature_S Signature S from the new owner + /// Signature is required to prevent changing to an address which is not in control of the user + /// Signature is the Signed Message of this hash: + /// hash = pedersen(0, (change_owner selector, chainid, contract address, old_owner)) + fn change_owner( + ref self: TContractState, new_owner: felt252, signature_r: felt252, signature_s: felt252 + ); + + /// @notice Changes the guardian + /// Must be called by the account and authorised by the owner and a guardian (if guardian is set). + /// @param new_guardian The address of the new guardian, or 0 to disable the guardian + /// @dev can only be set to 0 if there is no guardian backup set + fn change_guardian(ref self: TContractState, new_guardian: felt252); + + /// @notice Changes the backup guardian + /// Must be called by the account and authorised by the owner and a guardian (if guardian is set). + /// @param new_guardian_backup The address of the new backup guardian, or 0 to disable the backup guardian + fn change_guardian_backup(ref self: TContractState, new_guardian_backup: felt252); + + /// @notice Triggers the escape of the owner when it is lost or compromised. + /// Must be called by the account and authorised by just a guardian. + /// Cannot override an ongoing escape of the guardian. + /// @param new_owner The new account owner if the escape completes + /// @dev This method assumes that there is a guardian, and that `_newOwner` is not 0. + /// This must be guaranteed before calling this method, usually when validating the transaction. + fn trigger_escape_owner(ref self: TContractState, new_owner: felt252); + + /// @notice Triggers the escape of the guardian when it is lost or compromised. + /// Must be called by the account and authorised by the owner alone. + /// Can override an ongoing escape of the owner. + /// @param new_guardian The new account guardian if the escape completes + /// @dev This method assumes that there is a guardian, and that `new_guardian` can only be 0 + /// if there is no guardian backup. + /// This must be guaranteed before calling this method, usually when validating the transaction + fn trigger_escape_guardian(ref self: TContractState, new_guardian: felt252); + + /// @notice Completes the escape and changes the owner after the security period + /// Must be called by the account and authorised by just a guardian + /// @dev This method assumes that there is a guardian, and that the there is an escape for the owner. + /// This must be guaranteed before calling this method, usually when validating the transaction. + fn escape_owner(ref self: TContractState); + + /// @notice Completes the escape and changes the guardian after the security period + /// Must be called by the account and authorised by just the owner + /// @dev This method assumes that there is a guardian, and that the there is an escape for the guardian. + /// This must be guaranteed before calling this method. Usually when validating the transaction. + fn escape_guardian(ref self: TContractState); + + /// @notice Cancels an ongoing escape if any. + /// Must be called by the account and authorised by the owner and a guardian (if guardian is set). + fn cancel_escape(ref self: TContractState); + + // Views + fn get_owner(self: @TContractState) -> felt252; + fn get_guardian(self: @TContractState) -> felt252; + fn get_guardian_backup(self: @TContractState) -> felt252; + fn get_name(self: @TContractState) -> felt252; + fn get_guardian_escape_attempts(self: @TContractState) -> u32; + fn get_owner_escape_attempts(self: @TContractState) -> u32; +} diff --git a/src/oracle/oracle.cairo b/src/oracle/oracle.cairo index 6b2c4c55..13330fbe 100644 --- a/src/oracle/oracle.cairo +++ b/src/oracle/oracle.cairo @@ -20,6 +20,8 @@ use satoru::oracle::{ oracle_utils::{SetPricesParams, ReportInfo}, error::OracleError, }; use satoru::price::price::Price; +use pragma_lib::types::{AggregationMode, DataType, PragmaPricesResponse}; + // ************************************************************************* // Interface of the `Oracle` contract. @@ -103,7 +105,7 @@ trait IOracle { /// The stable price of a token. fn get_stable_price( self: @TContractState, data_store: IDataStoreDispatcher, token: ContractAddress - ) -> u128; + ) -> u256; /// Get the multiplier value to convert the external price feed price to the price of 1 unit of the token /// represented with 30 decimals. @@ -120,15 +122,17 @@ trait IOracle { /// The price feed multiplier. fn get_price_feed_multiplier( self: @TContractState, data_store: IDataStoreDispatcher, token: ContractAddress, - ) -> u128; + ) -> u256; - /// Validate prices in `params` for oracles. + /// Validate prices in `params` for oracles. TODO implement price validations /// # Arguments /// * `data_store` - The `DataStore` contract dispatcher. /// * `params` - The parameters used to set prices in oracle. - fn validate_prices( - self: @TContractState, data_store: IDataStoreDispatcher, params: SetPricesParams, - ) -> Array; + // fn validate_prices( + // self: @TContractState, data_store: IDataStoreDispatcher, params: SetPricesParams, + // ) -> Array; + + fn get_asset_price_median(self: @TContractState, asset: DataType) -> PragmaPricesResponse; } /// A price that has been validated in validate_prices(). @@ -137,9 +141,9 @@ struct ValidatedPrice { /// The token to validate the price for. token: ContractAddress, /// The min price of the token. - min: u128, + min: u256, /// The max price of the token. - max: u128, + max: u256, /// The timestamp of the price validated. timestamp: u64, min_block_number: u64, @@ -155,7 +159,7 @@ struct SetPricesCache { /// The max allowed age of price values. max_price_age: u64, /// The max ref_price deviation factor allowed. - max_ref_price_deviation_factor: u128, + max_ref_price_deviation_factor: u256, /// The previous oracle block number of the loop. prev_min_oracle_block_number: u64, // The prices that have been validated to set. @@ -165,23 +169,23 @@ struct SetPricesCache { /// Struct used in validate_prices as an inner cache. #[derive(Default, Drop)] struct SetPricesInnerCache { - /// The current price index to retrieve from compactedMinPrices and compactedMaxPrices - /// to construct the minPrices and maxPrices array. - price_index: u128, + /// The current price index to retrieve from compacted_min_prices and compacted_max_prices + /// to construct the min_prices and max_prices array. + price_index: usize, /// The current signature index to retrieve from the signatures array. - signature_index: u128, + signature_index: usize, /// The index of the min price in min_prices for the current signer. - min_price_index: u128, + min_price_index: u256, /// The index of the max price in max_prices for the current signer. - max_price_index: u128, + max_price_index: u256, /// The min prices. - min_prices: Array, + min_prices: Array, /// The max prices. - max_prices: Array, - /// The min price index using U128Mask. - min_price_index_mask: u128, - /// The max price index using U128Mask. - max_price_index_mask: u128, + max_prices: Array, + /// The min price index using U256Mask. + min_price_index_mask: u256, + /// The max price index using U256Mask. + max_price_index_mask: u256, } #[starknet::contract] @@ -191,8 +195,11 @@ mod Oracle { // ************************************************************************* // Core lib imports. + use core::traits::Into; + use core::traits::TryInto; use core::zeroable::Zeroable; use starknet::ContractAddress; + use starknet::contract_address_const; use starknet::info::{get_block_timestamp, get_block_number}; use starknet::syscalls::get_block_hash_syscall; use starknet::SyscallResultTrait; @@ -202,7 +209,6 @@ mod Oracle { use alexandria_sorting::merge_sort; use alexandria_storage::list::{ListTrait, List}; use poseidon::poseidon_hash_span; - // Local imports. use satoru::data::{data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}, keys}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; @@ -210,16 +216,16 @@ mod Oracle { use satoru::oracle::{ oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}, oracle_utils, oracle_utils::{SetPricesParams, ReportInfo}, error::OracleError, - price_feed::{ - IPriceFeedDispatcher, IPriceFeedDispatcherTrait, DataType, PragmaPricesResponse, - } }; use satoru::role::role_module::{ IRoleModule, RoleModule }; //::role_store::IInternalContractMemberStateTrait as RoleModuleStateTrait; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::utils::{arrays, arrays::pow, bits, calc, precision}; - use satoru::utils::u128_mask::{Mask, MaskTrait, validate_unique_and_set_index}; + use satoru::utils::u256_mask::{Mask, MaskTrait, validate_unique_and_set_index}; + + use pragma_lib::abi::{IPragmaABIDispatcher, IPragmaABIDispatcherTrait}; + use pragma_lib::types::{AggregationMode, DataType, PragmaPricesResponse}; use super::{IOracle, SetPricesCache, SetPricesInnerCache, ValidatedPrice}; @@ -227,11 +233,11 @@ mod Oracle { // ************************************************************************* // CONSTANTS // ************************************************************************* - const SIGNER_INDEX_LENGTH: u128 = 16; + const SIGNER_INDEX_LENGTH: u256 = 16; // subtract 1 as the first slot is used to store number of signers - const MAX_SIGNERS: u128 = 15; //128 / SIGNER_INDEX_LENGTH - 1; - // signer indexes are recorded in a signerIndexFlags uint128 value to check for uniqueness - const MAX_SIGNER_INDEX: u128 = 128; + const MAX_SIGNERS: u256 = 15; //256 / SIGNER_INDEX_LENGTH - 1; + // signer indexes are recorded in a signerIndexFlags uint256 value to check for uniqueness + const MAX_SIGNER_INDEX: u256 = 256; // ************************************************************************* @@ -244,7 +250,7 @@ mod Oracle { /// Interface to interact with the `OracleStore` contract. oracle_store: IOracleStoreDispatcher, /// Interface to interact with the Pragma Oracle. - price_feed: IPriceFeedDispatcher, + price_feed: IPragmaABIDispatcher, /// List of Prices related to a token. tokens_with_prices: List, /// Mapping between tokens and prices. @@ -269,11 +275,10 @@ mod Oracle { self.initialize(role_store_address, oracle_store_address, pragma_address); } - // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl OracleImpl of super::IOracle { fn initialize( ref self: ContractState, @@ -289,7 +294,7 @@ mod Oracle { self .oracle_store .write(IOracleStoreDispatcher { contract_address: oracle_store_address }); - self.price_feed.write(IPriceFeedDispatcher { contract_address: pragma_address }); + self.price_feed.write(IPragmaABIDispatcher { contract_address: pragma_address }); } fn set_prices( @@ -300,20 +305,33 @@ mod Oracle { ) { let state: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); IRoleModule::only_controller(@state); - let tokens_with_prices_len = self.tokens_with_prices.read().len(); if !tokens_with_prices_len.is_zero() { OracleError::NON_EMPTY_TOKENS_WITH_PRICES(tokens_with_prices_len); }; - self.set_prices_from_price_feeds(data_store, event_emitter, @params.price_feed_tokens); + // self.set_prices_from_price_feeds(data_store, event_emitter, @params.price_feed_tokens); TODO uncomment // it is possible for transactions to be executed using just params.priceFeedTokens // in this case if params.tokens is empty, the function can return if params.tokens.len().is_zero() { return; } - - self.set_prices_(data_store, event_emitter, params); + // only for testing + // TODO Find how to handle decimals, example ETH price 3453.92399931123 + let mut i = 0; + loop { + if i == params.tokens.len() { + break; + } + let token = *params.tokens.at(i); + let price = Price { + min: *params.compacted_max_prices.at(i), max: *params.compacted_max_prices.at(i) + }; + self.set_primary_price_(token, price); + i += 1; + }; + // end for testing + // self.set_prices_(data_store, event_emitter, params); TODO uncomment } // Set the primary price @@ -329,17 +347,23 @@ mod Oracle { fn clear_all_prices(ref self: ContractState) { let state: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); IRoleModule::only_controller(@state); - let mut len = 0; loop { - if len == self.tokens_with_prices.read().len() { + if self.tokens_with_prices.read().len() == Zeroable::zero() { break; } - let token = self.tokens_with_prices.read().get(len).unwrap(); + let token = self.tokens_with_prices.read().get(0).expect('array get failed'); self.remove_primary_price(token); - len += 1; - } + }; } + fn get_asset_price_median(self: @ContractState, asset: DataType) -> PragmaPricesResponse { + self.price_feed.read().get_data(asset, AggregationMode::Median(())) + } + //USAGE/ + // let KEY :felt252 = 18669995996566340; // felt252 conversion of "BTC/USD", can also write const KEY : felt252 = 'BTC/USD'; + // Sepolia contract address : 0x36031daa264c24520b11d93af622c848b2499b66b41d611bac95e13cfca131a + // let oracle_address : ContractAddress = contract_address_const::<0x06df335982dddce41008e4c03f2546fa27276567b5274c7d0c1262f3c2b5d167>(); + // let price = get_asset_price_median(DataType::SpotEntry(KEY)); fn get_tokens_with_prices_count(self: @ContractState) -> u32 { let token_with_prices = self.tokens_with_prices.read(); @@ -350,7 +374,7 @@ mod Oracle { if i == tokens_with_prices_len { break; } - if !token_with_prices.get(i).unwrap().is_zero() { + if !token_with_prices.get(i).expect('array get failed').is_zero() { count += 1; } i += 1; @@ -396,26 +420,25 @@ mod Oracle { fn get_stable_price( self: @ContractState, data_store: IDataStoreDispatcher, token: ContractAddress - ) -> u128 { - data_store.get_u128(keys::stable_price_key(token)) + ) -> u256 { + data_store.get_u256(keys::stable_price_key(token)) } fn get_price_feed_multiplier( self: @ContractState, data_store: IDataStoreDispatcher, token: ContractAddress, - ) -> u128 { - let multiplier = data_store.get_u128(keys::price_feed_multiplier_key(token)); + ) -> u256 { + let multiplier = data_store.get_u256(keys::price_feed_multiplier_key(token)); if multiplier.is_zero() { OracleError::EMPTY_PRICE_FEED_MULTIPLIER(); } multiplier } - - fn validate_prices( - self: @ContractState, data_store: IDataStoreDispatcher, params: SetPricesParams, - ) -> Array { - self.validate_prices_(data_store, params) - } + // fn validate_prices( + // self: @ContractState, data_store: IDataStoreDispatcher, params: SetPricesParams, + // ) -> Array { + // self.validate_prices_(data_store, params) + // } } // ************************************************************************* @@ -441,309 +464,313 @@ mod Oracle { /// * `data_store` - The data store. /// * `event_emitter` - The event emitter. /// * `params` - The set price params. - fn set_prices_( - ref self: ContractState, - data_store: IDataStoreDispatcher, - event_emitter: IEventEmitterDispatcher, - params: SetPricesParams, - ) { - let validated_prices = self.validate_prices(data_store, params); - - let mut len = 0; - loop { - if len == validated_prices.len() { - break; - } - - let validated_price = *validated_prices.at(len); - if !validated_price.min.is_zero() || !validated_price.max.is_zero() { - OracleError::DUPLICATED_TOKEN_PRICE(); - } - self - .emit_oracle_price_updated( - event_emitter, - validated_price.token, - validated_price.min, - validated_price.max, - false - ); - self - .set_primary_price_( - validated_price.token, - Price { min: validated_price.min, max: validated_price.max } - ); - }; - len += 1; - } + // fn set_prices_( + // ref self: ContractState, + // data_store: IDataStoreDispatcher, + // event_emitter: IEventEmitterDispatcher, + // params: SetPricesParams, + // ) { + // let validated_prices = self.validate_prices(data_store, params); + + // let mut len = 0; + // loop { + // if len == validated_prices.len() { + // break; + // } + + // let validated_price = *validated_prices.at(len); + // if !self.primary_prices.read(validated_price.token).is_zero() { + // OracleError::DUPLICATED_TOKEN_PRICE(); + // } + // self + // .emit_oracle_price_updated( + // event_emitter, + // validated_price.token, + // validated_price.min, + // validated_price.max, + // false + // ); + // self + // .set_primary_price_( + // validated_price.token, + // Price { min: validated_price.min, max: validated_price.max } + // ); + // len += 1; + // }; + // } /// Validate prices in params. /// # Arguments /// * `data_store` - The data store. /// * `params` - The set price params. - fn validate_prices_( - self: @ContractState, data_store: IDataStoreDispatcher, params: SetPricesParams, - ) -> Array { - let signers = self.get_signers_(data_store, @params); - - let mut cache: SetPricesCache = Default::default(); - - cache - .min_block_confirmations = data_store - .get_u128(keys::min_oracle_block_confirmations()) - .try_into() - .unwrap(); - - cache - .max_price_age = data_store - .get_u128(keys::max_oracle_price_age()) - .try_into() - .unwrap(); - - cache - .max_ref_price_deviation_factor = data_store - .get_u128(keys::max_oracle_ref_price_deviation_factor()); - - let mut i = 0; - loop { - let mut report_info: ReportInfo = Default::default(); - let mut inner_cache: SetPricesInnerCache = Default::default(); - if i == params.tokens.len() { - break; - } - - report_info - .min_oracle_block_number = - oracle_utils::get_uncompacted_oracle_block_number( - params.compacted_min_oracle_block_numbers.span(), i.into() - ); - - report_info - .max_oracle_block_number = - oracle_utils::get_uncompacted_oracle_block_number( - params.compacted_max_oracle_block_numbers.span(), i.into() - ); - - if report_info.min_oracle_block_number > report_info.max_oracle_block_number { - OracleError::INVALID_MIN_MAX_BLOCK_NUMBER( - report_info.min_oracle_block_number, report_info.max_oracle_block_number - ); - } - - report_info - .oracle_timestamp = - oracle_utils::get_uncompacted_oracle_timestamp( - params.compacted_oracle_timestamps.span(), i - ); - - if report_info.min_oracle_block_number > get_block_number() { - OracleError::INVALID_BLOCK_NUMBER( - report_info.min_oracle_block_number, get_block_number() - ); - } - - if report_info.oracle_timestamp + cache.max_price_age < get_block_timestamp() { - OracleError::MAX_PRICE_EXCEEDED( - report_info.oracle_timestamp, get_block_timestamp() - ); - } - - if report_info.min_oracle_block_number < cache.prev_min_oracle_block_number { - OracleError::BLOCK_NUMBER_NOT_SORTED( - report_info.min_oracle_block_number, cache.prev_min_oracle_block_number - ); - } - - cache.prev_min_oracle_block_number = report_info.min_oracle_block_number; - - if get_block_number() - - report_info.max_oracle_block_number <= cache.min_block_confirmations { - report_info - .block_hash = get_block_hash_syscall(report_info.max_oracle_block_number) - .unwrap_syscall(); - } - - report_info.token = *params.tokens.at(i); - - report_info - .precision = - pow( - 10, - oracle_utils::get_uncompacted_decimal( - params.compacted_decimals.span(), i.into() - ) - .try_into() - .unwrap() - ); - - report_info - .token_oracle_type = data_store - .get_felt252(keys::oracle_type_key(report_info.token)); - - let mut j = 0; - let signers_len = signers.len(); - let compacted_min_prices_span = params.compacted_min_prices.span(); - let compacted_max_prices_span = params.compacted_max_prices.span(); - loop { - if j == signers_len { - break; - } - inner_cache.price_index = (i * signers_len + j).into(); - - inner_cache - .min_prices - .append( - oracle_utils::get_uncompacted_price( - compacted_min_prices_span, inner_cache.price_index - ) - ); - - inner_cache - .max_prices - .append( - oracle_utils::get_uncompacted_price( - compacted_max_prices_span, inner_cache.price_index - ) - ); - j += 1; - }; - - // Important: Arrays are built first, then sorted, due to inability to modify elements at arbitrary indices. Exercise caution in testing. - inner_cache.min_prices = merge_sort::merge(inner_cache.min_prices); - inner_cache.max_prices = merge_sort::merge(inner_cache.max_prices); - - let compacted_min_span = params.compacted_min_prices_indexes.span(); - let compacted_max_span = params.compacted_max_prices_indexes.span(); - let inner_cache_save = @inner_cache; - let signatures_span = params.signatures.span(); - let signers_span = signers.span(); - let signers_len = signers_span.len(); - let mut j = 0; - loop { - if j == signers_len { - break; - } - - inner_cache.signature_index = (i * signers_span.len() + j).into(); - - inner_cache - .min_price_index = - oracle_utils::get_uncompacted_price_index( - compacted_min_span, inner_cache.signature_index - ); - - inner_cache - .max_price_index = - oracle_utils::get_uncompacted_price_index( - compacted_max_span, inner_cache.signature_index - ); - - if inner_cache.signature_index >= signatures_span.len().into() { - OracleError::ARRAY_OUT_OF_BOUNDS_FELT252( - signatures_span, inner_cache.signature_index, 'signatures' - ); - } - - if inner_cache.min_price_index >= inner_cache.min_prices.len().into() { - OracleError::ARRAY_OUT_OF_BOUNDS_U128( - inner_cache.min_prices.span(), inner_cache.min_price_index, 'min_prices' - ); - } - - if inner_cache.max_price_index >= inner_cache.max_prices.len().into() { - OracleError::ARRAY_OUT_OF_BOUNDS_U128( - inner_cache.max_prices.span(), inner_cache.max_price_index, 'max_prices' - ); - } - - // since minPrices, maxPrices have the same length as the signers array - // and the signers array length is less than MAX_SIGNERS - // minPriceIndexMask and maxPriceIndexMask should be able to store the indexes - // using Uint256Mask - validate_unique_and_set_index( - ref inner_cache.min_price_index_mask, inner_cache.min_price_index - ); - - validate_unique_and_set_index( - ref inner_cache.max_price_index_mask, inner_cache.max_price_index - ); - - report_info - .min_price = *inner_cache - .min_prices - .at(inner_cache.min_price_index.try_into().unwrap()); - - report_info - .max_price = *inner_cache - .max_prices - .at(inner_cache.max_price_index.try_into().unwrap()); - - if report_info.min_price > report_info.max_price { - OracleError::INVALID_SIGNER_MIN_MAX_PRICE( - report_info.min_price, report_info.max_price - ); - } - - oracle_utils::validate_signer( - self.get_salt(), - report_info, - arrays::get_felt252( - signatures_span, inner_cache.signature_index.try_into().unwrap() - ), - signers_span.at(j) - ); - - j += 1; - }; - - let median_min_price = arrays::get_median(inner_cache_save.min_prices.span()) - * report_info.precision; - - let median_max_price = arrays::get_median(inner_cache_save.max_prices.span()) - * report_info.precision; - let (has_price_feed, ref_price) = self - .get_price_feed_price(data_store, report_info.token); - - if has_price_feed { - self - .validate_ref_price( - report_info.token, - median_min_price, - ref_price, - cache.max_ref_price_deviation_factor - ); - - self - .validate_ref_price( - report_info.token, - median_max_price, - ref_price, - cache.max_ref_price_deviation_factor - ); - } - - if median_min_price.is_zero() || median_max_price.is_zero() { - OracleError::INVALID_ORACLE_PRICE(report_info.token); - } - - if median_min_price > median_max_price { - OracleError::INVALID_MEDIAN_MIN_MAX_PRICE(median_min_price, median_max_price); - } - - let validated_price = ValidatedPrice { - token: report_info.token, - min: median_min_price, - max: median_max_price, - timestamp: report_info.oracle_timestamp, - min_block_number: report_info.min_oracle_block_number, - max_block_number: report_info.max_oracle_block_number - }; - - cache.validated_prices.append(validated_price); - - i += 1; - }; - cache.validated_prices - } + // fn validate_prices_( + // self: @ContractState, data_store: IDataStoreDispatcher, params: SetPricesParams, + // ) -> Array { + // let signers = self.get_signers_(data_store, @params); + + // let mut cache: SetPricesCache = Default::default(); + // cache + // .min_block_confirmations = data_store + // .get_u256(keys::min_oracle_block_confirmations()) + // .try_into() + // .expect('get_u256 into u64 failed'); + + // cache + // .max_price_age = data_store + // .get_u256(keys::max_oracle_price_age()) + // .try_into() + // .expect('get_u256 into u64 failed'); + + // cache + // .max_ref_price_deviation_factor = data_store + // .get_u256(keys::max_oracle_ref_price_deviation_factor()); + + // let mut i = 0; + // loop { + // let mut report_info: ReportInfo = Default::default(); + // let mut inner_cache: SetPricesInnerCache = Default::default(); + // if i == params.tokens.len() { + // break; + // } + + // report_info + // .min_oracle_block_number = + // oracle_utils::get_uncompacted_oracle_block_number( + // params.compacted_min_oracle_block_numbers.span(), i.into() + // ); + + // report_info + // .max_oracle_block_number = + // oracle_utils::get_uncompacted_oracle_block_number( + // params.compacted_max_oracle_block_numbers.span(), i.into() + // ); + + // if report_info.min_oracle_block_number > report_info.max_oracle_block_number { + // OracleError::INVALID_MIN_MAX_BLOCK_NUMBER( + // report_info.min_oracle_block_number, report_info.max_oracle_block_number + // ); + // } + + // report_info + // .oracle_timestamp = + // oracle_utils::get_uncompacted_oracle_timestamp( + // params.compacted_oracle_timestamps.span(), i + // ); + // if report_info.min_oracle_block_number > get_block_number() { + // OracleError::INVALID_BLOCK_NUMBER( + // report_info.min_oracle_block_number, get_block_number() + // ); + // } + + // if report_info.oracle_timestamp + cache.max_price_age < get_block_timestamp() { + // OracleError::MAX_PRICE_EXCEEDED( + // report_info.oracle_timestamp, get_block_timestamp() + // ); + // } + + // if report_info.min_oracle_block_number < cache.prev_min_oracle_block_number { + // OracleError::BLOCK_NUMBER_NOT_SORTED( + // report_info.min_oracle_block_number, cache.prev_min_oracle_block_number + // ); + // } + + // cache.prev_min_oracle_block_number = report_info.min_oracle_block_number; + + // if get_block_number() + // - report_info.max_oracle_block_number <= cache.min_block_confirmations { + // report_info + // .block_hash = get_block_hash_syscall(report_info.max_oracle_block_number) + // .unwrap_syscall(); + // } + + // report_info.token = *params.tokens.at(i); + + // report_info + // .precision = + // pow( + // 10, + // oracle_utils::get_uncompacted_decimal( + // params.compacted_decimals.span(), i.into() + // ) + // .try_into() + // .expect('u256 into u32 failed') + // ); + + // report_info + // .token_oracle_type = data_store + // .get_felt252(keys::oracle_type_key(report_info.token)); + + // let mut j = 0; + // let signers_len = signers.len(); + // let compacted_min_prices_span = params.compacted_min_prices.span(); + // let compacted_max_prices_span = params.compacted_max_prices.span(); + // loop { + // if j == signers_len { + // break; + // } + // inner_cache.price_index = (i * signers_len + j).into(); + // inner_cache + // .min_prices + // .append( + // oracle_utils::get_uncompacted_price( + // compacted_min_prices_span, inner_cache.price_index + // ) + // ); + + // inner_cache + // .max_prices + // .append( + // oracle_utils::get_uncompacted_price( + // compacted_max_prices_span, inner_cache.price_index + // ) + // ); + // if j != 0 { + // if *inner_cache.min_prices.at(j - 1) > *inner_cache.min_prices.at(j) { + // OracleError::MIN_PRICES_NOT_SORTED( + // report_info.token, + // *inner_cache.min_prices.at(j), + // *inner_cache.min_prices.at(j - 1) + // ); + // } + + // if *inner_cache.max_prices.at(j - 1) > *inner_cache.max_prices.at(j) { + // OracleError::MAX_PRICES_NOT_SORTED( + // report_info.token, + // *inner_cache.max_prices.at(j), + // *inner_cache.max_prices.at(j - 1) + // ); + // } + // } + // j += 1; + // }; + + // let compacted_min_indexes_span = params.compacted_min_prices_indexes.span(); + // let compacted_max_indexes_span = params.compacted_max_prices_indexes.span(); + // let inner_cache_save = @inner_cache; + // let signatures_span = params.signatures.span(); + // let signers_span = signers.span(); + // let mut j = 0; + // loop { + // if j == signers_len { + // break; + // } + + // inner_cache.signature_index = (i * signers_len + j).into(); + + // inner_cache + // .min_price_index = + // oracle_utils::get_uncompacted_price_index( + // compacted_min_indexes_span, inner_cache.signature_index + // ); + // inner_cache + // .max_price_index = + // oracle_utils::get_uncompacted_price_index( + // compacted_max_indexes_span, inner_cache.signature_index + // ); + // if inner_cache.signature_index >= signatures_span.len() { + // OracleError::ARRAY_OUT_OF_BOUNDS_FELT252( + // signatures_span, inner_cache.signature_index, 'signatures' + // ); + // } + // if inner_cache.min_price_index >= inner_cache.min_prices.len().into() { + // OracleError::ARRAY_OUT_OF_BOUNDS_U256( + // inner_cache.min_prices.span(), inner_cache.min_price_index, 'min_prices' + // ); + // } + + // if inner_cache.max_price_index >= inner_cache.max_prices.len().into() { + // OracleError::ARRAY_OUT_OF_BOUNDS_U256( + // inner_cache.max_prices.span(), inner_cache.max_price_index, 'max_prices' + // ); + // } + + // // since minPrices, maxPrices have the same length as the signers array + // // and the signers array length is less than MAX_SIGNERS + // // minPriceIndexMask and maxPriceIndexMask should be able to store the indexes + // // using Uint256Mask + // validate_unique_and_set_index( + // ref inner_cache.min_price_index_mask, inner_cache.min_price_index + // ); + + // validate_unique_and_set_index( + // ref inner_cache.max_price_index_mask, inner_cache.max_price_index + // ); + + // report_info + // .min_price = *inner_cache + // .min_prices + // .at(inner_cache.min_price_index.try_into().expect('array at failed')); + + // report_info + // .max_price = *inner_cache + // .max_prices + // .at(inner_cache.max_price_index.try_into().expect('array at failed')); + + // if report_info.min_price > report_info.max_price { + // OracleError::INVALID_SIGNER_MIN_MAX_PRICE( + // report_info.min_price, report_info.max_price + // ); + // } + // // oracle_utils::validate_signer( + // // self.get_salt(), + // // report_info, + // // *signatures_span.at(inner_cache.signature_index), + // // signers_span.at(j) + // // ); + + // j += 1; + // }; + + // let median_min_price = arrays::get_median(inner_cache_save.min_prices.span()) + // * report_info.precision; + + // let median_max_price = arrays::get_median(inner_cache_save.max_prices.span()) + // * report_info.precision; + + // let (has_price_feed, ref_price) = self + // .get_price_feed_price(data_store, report_info.token); + + // if has_price_feed { + // self + // .validate_ref_price( + // report_info.token, + // median_min_price, + // ref_price, + // cache.max_ref_price_deviation_factor + // ); + + // self + // .validate_ref_price( + // report_info.token, + // median_max_price, + // ref_price, + // cache.max_ref_price_deviation_factor + // ); + // } + + // if median_min_price.is_zero() || median_max_price.is_zero() { + // OracleError::INVALID_ORACLE_PRICE(report_info.token); + // } + + // if median_min_price > median_max_price { + // OracleError::INVALID_MEDIAN_MIN_MAX_PRICE(median_min_price, median_max_price); + // } + + // let validated_price = ValidatedPrice { + // token: report_info.token, + // min: median_min_price, + // max: median_max_price, + // timestamp: report_info.oracle_timestamp, + // min_block_number: report_info.min_oracle_block_number, + // max_block_number: report_info.max_oracle_block_number + // }; + + // cache.validated_prices.append(validated_price); + + // i += 1; + // }; + // cache.validated_prices + // } /// Get the signers /// # Arguments @@ -751,64 +778,67 @@ mod Oracle { /// * `token` - The token to get the price for. /// # Returns /// The signers - fn get_signers_( - self: @ContractState, data_store: IDataStoreDispatcher, params: @SetPricesParams, - ) -> Array { - let mut signers: Array = array![]; - - let signers_len = *params.signer_info & bits::BITMASK_16; - - if signers_len < data_store.get_u128(keys::min_oracle_signers()) { - OracleError::MIN_ORACLE_SIGNERS( - signers_len, data_store.get_u128(keys::min_oracle_signers()) - ); - } - - if signers_len > MAX_SIGNERS { - OracleError::MAX_ORACLE_SIGNERS(signers_len, MAX_SIGNERS); - } - - let mut signers_index_mask = Mask { bits: 0 }; - - let mut len = 0; - loop { - if len == signers_len { - break; - } - - let signer_index: u128 = BitShift::shr( - *params.signer_info, (8 + 8 * len) & bits::BITMASK_16 - ); - - if signer_index >= MAX_SIGNER_INDEX { - OracleError::MAX_SIGNERS_INDEX(signer_index, MAX_SIGNER_INDEX); - } - - signers_index_mask.validate_unique_and_set_index(signer_index); - - signers - .append(self.oracle_store.read().get_signer(signer_index.try_into().unwrap())); - - if (*signers.at(len.try_into().unwrap())).is_zero() { - OracleError::EMPTY_SIGNER(signer_index); - } - - len += 1; - }; - // } - - signers - } + // fn get_signers_( + // self: @ContractState, data_store: IDataStoreDispatcher, params: @SetPricesParams, + // ) -> Array { + // let mut signers: Array = array![]; + + // let signers_len = *params.signer_info & bits::BITMASK_16; + // if signers_len < data_store.get_u256(keys::min_oracle_signers()) { + // OracleError::MIN_ORACLE_SIGNERS( + // signers_len, data_store.get_u256(keys::min_oracle_signers()) + // ); + // } + + // if signers_len > MAX_SIGNERS { + // OracleError::MAX_ORACLE_SIGNERS(signers_len, MAX_SIGNERS); + // } + + // let mut signers_index_mask = Mask { bits: 0 }; + + // let mut len = 0; + // loop { + // if len == signers_len { + // break; + // } + + // let signer_index: u256 = BitShift::shr( + // *params.signer_info, (8 + 8 * len) & bits::BITMASK_16 + // ); + + // if signer_index >= MAX_SIGNER_INDEX { + // OracleError::MAX_SIGNERS_INDEX(signer_index, MAX_SIGNER_INDEX); + // } + + // signers_index_mask.validate_unique_and_set_index(signer_index); + + // signers + // .append( + // self + // .oracle_store + // .read() + // .get_signer(signer_index.try_into().expect('u256 into u32 failed')) + // ); + + // if (*signers.at(len.try_into().expect('u256 into u32 failed'))).is_zero() { + // OracleError::EMPTY_SIGNER(signer_index); + // } + + // len += 1; + // }; + + // signers + // } /// Compute a salt for validate_signer(). /// # Returns /// The computed salt. - fn get_salt(self: @ContractState,) -> felt252 { - let data: Array = array![ - starknet::info::get_tx_info().unbox().chain_id, 'xget-oracle-v1' - ]; - poseidon_hash_span(data.span()) - } + // fn get_salt(self: @ContractState,) -> felt252 { + // let data: Array = array![ + // starknet::info::get_tx_info().unbox().chain_id, 'xget-oracle-v1' + // ]; + // poseidon_hash_span(data.span()) + // } /// Validate that price does not deviate too much from ref_price. /// # Arguments @@ -816,22 +846,22 @@ mod Oracle { /// * `price` - The price to validate. /// * `ref_price` - The reference price. /// * `max_ref_price_deviation_from_factor` - The max ref_price deviation factor allowed. - fn validate_ref_price( - self: @ContractState, - token: ContractAddress, - price: u128, - ref_price: u128, - max_ref_price_deviation_factor: u128, - ) { - let diff = calc::diff(price, ref_price); - - let diff_factor = precision::to_factor(diff, ref_price); - if diff_factor > max_ref_price_deviation_factor { - OracleError::MAX_REFPRICE_DEVIATION_EXCEEDED( - token, price, ref_price, max_ref_price_deviation_factor - ); - } - } + // fn validate_ref_price( + // self: @ContractState, + // token: ContractAddress, + // price: u256, + // ref_price: u256, + // max_ref_price_deviation_factor: u256, + // ) { + // let diff = calc::diff(price, ref_price); + + // let diff_factor = precision::to_factor(diff, ref_price); + // if diff_factor > max_ref_price_deviation_factor { + // OracleError::MAX_REFPRICE_DEVIATION_EXCEEDED( + // token, price, ref_price, max_ref_price_deviation_factor + // ); + // } + // } /// Set the primary price. /// # Arguments @@ -849,12 +879,8 @@ mod Oracle { // otherwise the new token is appended to the list. This is to avoid the list // to grow indefinitely. match index_of_zero { - Option::Some(i) => { - tokens_with_prices.set(i, token); - }, - Option::None => { - tokens_with_prices.append(token); - } + Option::Some(i) => { tokens_with_prices.set(i, token); }, + Option::None => { tokens_with_prices.append(token); } } } } @@ -865,15 +891,9 @@ mod Oracle { /// * `token` - The token to set the price for. fn remove_primary_price(ref self: ContractState, token: ContractAddress) { self.primary_prices.write(token, Zeroable::zero()); - - let token_index = self.get_token_with_price_index(token); - match token_index { - Option::Some(i) => { - let mut tokens_with_prices = self.tokens_with_prices.read(); - tokens_with_prices.set(i, Zeroable::zero()); - }, - Option::None => (), - } + let mut tokens_prices = self.tokens_with_prices.read(); + tokens_prices.pop_front(); + self.tokens_with_prices.write(tokens_prices); } /// Get the price feed prices. @@ -886,28 +906,34 @@ mod Oracle { /// The price feed multiplier. fn get_price_feed_price( self: @ContractState, data_store: IDataStoreDispatcher, token: ContractAddress, - ) -> (bool, u128) { + ) -> (bool, u256) { let token_id = data_store.get_token_id(token); - let response = self.price_feed.read().get_data_median(DataType::SpotEntry(token_id)); + if token_id == 0 { + return (false, 0); + } + let response = self.get_asset_price_median(DataType::SpotEntry(token_id)); if response.price <= 0 { - OracleError::INVALID_PRICE_FEED(token, response.price); + OracleError::INVALID_PRICE_FEED(token, response.price.into()); } let heart_beat_duration = data_store - .get_u128(keys::price_feed_heartbeat_duration_key(token)); + .get_u256(keys::price_feed_heartbeat_duration_key(token)); let current_timestamp = get_block_timestamp(); if current_timestamp > response.last_updated_timestamp && current_timestamp - - response.last_updated_timestamp > heart_beat_duration.try_into().unwrap() { + - response + .last_updated_timestamp > heart_beat_duration + .try_into() + .expect('u256 into u32 failed') { OracleError::PRICE_FEED_NOT_UPDATED( token, response.last_updated_timestamp, heart_beat_duration ); } let precision_ = self.get_price_feed_multiplier(data_store, token); - let adjusted_price = precision::mul_div( - response.price, precision_, precision::FLOAT_PRECISION + let adjusted_price = precision::mul_div( // TODO check precision file + response.price.into(), precision_, precision::FLOAT_PRECISION ); (true, adjusted_price) @@ -939,7 +965,8 @@ mod Oracle { OracleError::PRICE_ALREADY_SET(token, stored_price.min, stored_price.max); } - let (has_price_feed, price) = self_copy.get_price_feed_price(data_store, token); + let (has_price_feed, price) = self_copy + .get_price_feed_price(data_store, token); // TODO here get pragma price if (!has_price_feed) { OracleError::EMPTY_PRICE_FEED(token); @@ -988,8 +1015,8 @@ mod Oracle { self: @ContractState, event_emitter: IEventEmitterDispatcher, token: ContractAddress, - min_price: u128, - max_price: u128, + min_price: u256, + max_price: u256, is_price_feed: bool, ) { event_emitter.emit_oracle_price_updated(token, min_price, max_price, is_price_feed); @@ -1026,4 +1053,3 @@ mod Oracle { } } } - diff --git a/src/oracle/oracle_modules.cairo b/src/oracle/oracle_modules.cairo index a79bea33..9cba664d 100644 --- a/src/oracle/oracle_modules.cairo +++ b/src/oracle/oracle_modules.cairo @@ -29,7 +29,6 @@ use satoru::oracle::error::OracleError; /// * `dataStore` - `DataStore` contract dispatcher /// * `eventEmitter` - `EventEmitter` contract dispatcher /// * `params` - parameters used to set oracle price -#[inline(always)] fn with_oracle_prices_before( oracle: IOracleDispatcher, data_store: IDataStoreDispatcher, @@ -39,7 +38,6 @@ fn with_oracle_prices_before( oracle.set_prices(data_store, event_emitter, params.clone()); } -#[inline(always)] fn with_oracle_prices_after(oracle: IOracleDispatcher) { oracle.clear_all_prices(); } @@ -62,18 +60,18 @@ fn with_simulated_oracle_prices_before(oracle: IOracleDispatcher, params: Simula params.primary_tokens.len(), params.primary_prices.len() ); } - let cur_idx = 0; + let mut cur_idx = 0; loop { if (cur_idx == params.primary_tokens.len()) { break (); } let token: ContractAddress = *params.primary_tokens.at(cur_idx); let price: Price = *params.primary_prices.at(cur_idx); - oracle.set_primary_price(token, price); + // oracle.set_primary_price(token, price); + cur_idx = cur_idx + 1; }; } -#[inline(always)] fn with_simulated_oracle_prices_after() { OracleError::END_OF_ORACLE_SIMULATION(); } diff --git a/src/oracle/oracle_store.cairo b/src/oracle/oracle_store.cairo index 4b0d5ccc..b4f225f0 100644 --- a/src/oracle/oracle_store.cairo +++ b/src/oracle/oracle_store.cairo @@ -35,7 +35,7 @@ trait IOracleStore { /// Get the total number of signers. /// # Returns /// Signer count. - fn get_signer_count(self: @TContractState) -> u128; + fn get_signer_count(self: @TContractState) -> u256; /// Get the total signer at index. /// # Arguments @@ -50,7 +50,7 @@ trait IOracleStore { /// * `end` - End index, not included. /// # Returns /// Signer for specified indexes. - fn get_signers(self: @TContractState, start: u128, end: u128) -> Array; + fn get_signers(self: @TContractState, start: u256, end: u256) -> Array; } #[starknet::contract] @@ -61,7 +61,7 @@ mod OracleStore { // Core lib imports. use core::zeroable::Zeroable; - use starknet::ContractAddress; + use starknet::{ContractAddress, contract_address_const}; use alexandria_storage::list::{ListTrait, List}; @@ -83,7 +83,8 @@ mod OracleStore { /// Interface to interact with the `EventEmitter` contract. event_emitter: IEventEmitterDispatcher, // NOTE: temporarily implemented to complete oracle tests. - signers: List + signers: List, + signers_indexes: LegacyMap } // ************************************************************************* @@ -107,7 +108,7 @@ mod OracleStore { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl OracleStoreImpl of super::IOracleStore { fn initialize( ref self: ContractState, @@ -125,29 +126,45 @@ mod OracleStore { self.role_store.write(IRoleStoreDispatcher { contract_address: role_store_address }); } - fn add_signer(ref self: ContractState, account: ContractAddress) { // TODO - // NOTE: temporarily implemented to complete oracle tests. + fn add_signer(ref self: ContractState, account: ContractAddress) { let mut signers = self.signers.read(); + let index = signers.len(); signers.append(account); + self.signers_indexes.write(account, index); } - fn remove_signer(ref self: ContractState, account: ContractAddress) { // TODO + fn remove_signer(ref self: ContractState, account: ContractAddress) { + let mut signers = self.signers.read(); + let last_signer_index = signers.len(); + let signer_to_remove_index = self.signers_indexes.read(account); + let last_signer = signers.get(last_signer_index).expect('failed to get last signer'); + signers.set(signer_to_remove_index, last_signer); + self.signers_indexes.write(last_signer, signer_to_remove_index); + signers.len = signers.len() - 1; } - fn get_signer_count(self: @ContractState) -> u128 { // TODO - 0 + fn get_signer_count(self: @ContractState) -> u256 { + self.signers.read().len().into() } - fn get_signer(self: @ContractState, index: usize) -> ContractAddress { // TODO - // NOTE: temporarily implemented to complete oracle tests. - let mut signers = self.signers.read(); - signers.get(index).unwrap() + fn get_signer(self: @ContractState, index: usize) -> ContractAddress { + // self.signers.read().get(index).expect('failed to get signer') + contract_address_const::<'signer'>() // TODO } - fn get_signers( - self: @ContractState, start: u128, end: u128 - ) -> Array { // TODO - ArrayTrait::new() + fn get_signers(self: @ContractState, start: u256, end: u256) -> Array { + let mut signers_subset: Array = ArrayTrait::new(); + let signers = self.signers.read(); + + let mut index: u32 = start.try_into().expect('failed convertion u32 to u256'); + loop { + if start == end { + break; + } + signers_subset.append(signers.get(index).expect('out of bound signer index')) + }; + + signers_subset } } } diff --git a/src/oracle/oracle_utils.cairo b/src/oracle/oracle_utils.cairo index af23dc3d..5f9e7594 100644 --- a/src/oracle/oracle_utils.cairo +++ b/src/oracle/oracle_utils.cairo @@ -5,18 +5,27 @@ use starknet::ContractAddress; use result::ResultTrait; use traits::Default; - +use hash::LegacyHash; +use ecdsa::recover_public_key; // Local imports. use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; use satoru::market::market::{Market}; -use satoru::oracle::oracle::{SetPricesCache, SetPricesInnerCache}; +use satoru::oracle::{ + oracle::{SetPricesCache, SetPricesInnerCache}, error::OracleError, + interfaces::account::{IAccountDispatcher, IAccountDispatcherTrait} +}; use satoru::price::price::{Price}; -use satoru::utils::store_arrays::{ - StoreContractAddressArray, StorePriceArray, StoreU128Array, StoreFelt252Array +use satoru::utils::{ + store_arrays::{StoreContractAddressArray, StorePriceArray, StoreU256Array, StoreFelt252Array}, + arrays::{are_lte_u64, are_gte_u64, get_uncompacted_value, get_uncompacted_value_u64}, + bits::{BITMASK_8, BITMASK_16, BITMASK_32, BITMASK_64} }; +// External imports. +use alexandria_data_structures::array_ext::SpanTraitExt; + /// SetPricesParams struct for values required in Oracle.set_prices. /// # Arguments @@ -34,17 +43,17 @@ use satoru::utils::store_arrays::{ /// * `price_feed_tokens` - tokens to set prices for based on an external price feed value. #[derive(Default, Drop, Clone, Serde)] struct SetPricesParams { - signer_info: u128, + signer_info: u256, tokens: Array, compacted_min_oracle_block_numbers: Array, compacted_max_oracle_block_numbers: Array, compacted_oracle_timestamps: Array, - compacted_decimals: Array, - compacted_min_prices: Array, - compacted_min_prices_indexes: Array, - compacted_max_prices: Array, - compacted_max_prices_indexes: Array, - signatures: Array, + compacted_decimals: Array, + compacted_min_prices: Array, + compacted_min_prices_indexes: Array, + compacted_max_prices: Array, + compacted_max_prices_indexes: Array, + signatures: Array>, price_feed_tokens: Array, } @@ -73,9 +82,39 @@ struct ReportInfo { block_hash: felt252, token: ContractAddress, token_oracle_type: felt252, - precision: u128, - min_price: u128, - max_price: u128, + precision: u256, + min_price: u256, + max_price: u256, +} + +// compacted prices have a length of 32 bits +const COMPACTED_PRICE_BIT_LENGTH: usize = 32; +fn COMPACTED_PRICE_BITMASK() -> u256 { + BITMASK_32 +} + +// compacted precisions have a length of 8 bits +const COMPACTED_PRECISION_BIT_LENGTH: usize = 8; +fn COMPACTED_PRECISION_BITMASK() -> u256 { + BITMASK_8 +} + +// compacted block numbers have a length of 64 bits +const COMPACTED_BLOCK_NUMBER_BIT_LENGTH: usize = 64; +fn COMPACTED_BLOCK_NUMBER_BITMASK() -> u64 { + BITMASK_64 +} + +// compacted timestamps have a length of 64 bits +const COMPACTED_TIMESTAMP_BIT_LENGTH: usize = 64; +fn COMPACTED_TIMESTAMP_BITMASK() -> u64 { + BITMASK_64 +} + +// compacted price indexes have a length of 8 bits +const COMPACTED_PRICE_INDEX_BIT_LENGTH: usize = 8; +fn COMPACTED_PRICE_INDEX_BITMASK() -> u256 { + BITMASK_8 } /// Validates wether a block number is in range. @@ -85,7 +124,14 @@ struct ReportInfo { /// * `block_number` - The block number to compare to. fn validate_block_number_within_range( min_oracle_block_numbers: Span, max_oracle_block_numbers: Span, block_number: u64 -) { // TODO +) { + if !is_block_number_within_range( + min_oracle_block_numbers, max_oracle_block_numbers, block_number + ) { + OracleError::ORACLE_BLOCK_NUMBERS_NOT_WITHIN_RANGE( + min_oracle_block_numbers, max_oracle_block_numbers, block_number + ); + } } /// Validates wether a block number is in range. @@ -96,9 +142,15 @@ fn validate_block_number_within_range( /// # Returns /// True if block_number is in range, false else. fn is_block_number_within_range( - min_oracle_block_numbers: Array, max_oracle_block_numbers: Array, block_number: u128 + min_oracle_block_numbers: Span, max_oracle_block_numbers: Span, block_number: u64 ) -> bool { - // TODO + if (!are_lte_u64(min_oracle_block_numbers, block_number)) { + return false; + } + if (!are_gte_u64(max_oracle_block_numbers, block_number)) { + return false; + } + true } @@ -108,9 +160,20 @@ fn is_block_number_within_range( /// * `index` - The index to get the decimal at. /// # Returns /// The price at the specified index. -fn get_uncompacted_price(compacted_prices: Span, index: u128) -> u128 { - // TODO - 10 +fn get_uncompacted_price(compacted_prices: Span, index: usize) -> u256 { + let price = get_uncompacted_value( + compacted_prices, + index, + COMPACTED_PRICE_BIT_LENGTH, + COMPACTED_PRICE_BITMASK(), + 'get_uncompacted_price' + ); + + if (price == 0) { + OracleError::EMPTY_COMPACTED_PRICE(index) + } + + price } /// Get the uncompacted decimal at the specified index. @@ -119,9 +182,16 @@ fn get_uncompacted_price(compacted_prices: Span, index: u128) -> u128 { /// * `index` - The index to get the decimal at. /// # Returns /// The decimal at the specified index. -fn get_uncompacted_decimal(compacted_decimals: Span, index: u128) -> u128 { - // TODO - 0 +fn get_uncompacted_decimal(compacted_decimals: Span, index: usize) -> u256 { + let decimal = get_uncompacted_value( + compacted_decimals, + index, + COMPACTED_PRECISION_BIT_LENGTH, + COMPACTED_PRECISION_BITMASK(), + 'get_uncompacted_decimal' + ); + + decimal } /// Get the uncompacted price index at the specified index. @@ -130,9 +200,16 @@ fn get_uncompacted_decimal(compacted_decimals: Span, index: u128) -> u128 /// * `index` - The index to get the price index at. /// # Returns /// The uncompacted price index at the specified index. -fn get_uncompacted_price_index(compacted_price_indexes: Span, index: u128) -> u128 { - // TODO - 0 +fn get_uncompacted_price_index(compacted_price_indexes: Span, index: usize) -> u256 { + let price_index = get_uncompacted_value( + compacted_price_indexes, + index, + COMPACTED_PRICE_INDEX_BIT_LENGTH, + COMPACTED_PRICE_INDEX_BITMASK(), + 'get_uncompacted_price_index' + ); + + price_index } /// Get the uncompacted oracle block numbers. @@ -144,8 +221,21 @@ fn get_uncompacted_price_index(compacted_price_indexes: Span, index: u128) fn get_uncompacted_oracle_block_numbers( compacted_oracle_block_numbers: Span, length: usize ) -> Array { - // TODO - ArrayTrait::new() + let mut block_numbers = ArrayTrait::new(); + + let mut i = 0; + loop { + if (i == length) { + break; + } + + block_numbers + .append(get_uncompacted_oracle_block_number(compacted_oracle_block_numbers, i)); + + i += 1; + }; + + block_numbers } /// Get the uncompacted oracle block number. @@ -157,8 +247,15 @@ fn get_uncompacted_oracle_block_numbers( fn get_uncompacted_oracle_block_number( compacted_oracle_block_numbers: Span, index: usize ) -> u64 { - // TODO - 0 + let block_number = get_uncompacted_value_u64( + compacted_oracle_block_numbers, + index, + COMPACTED_BLOCK_NUMBER_BIT_LENGTH, + COMPACTED_BLOCK_NUMBER_BITMASK(), + 'get_uncmpctd_oracle_block_numb' + ); + + block_number } /// Get the uncompacted oracle timestamp. @@ -168,8 +265,19 @@ fn get_uncompacted_oracle_block_number( /// # Returns /// The uncompacted oracle timestamp. fn get_uncompacted_oracle_timestamp(compacted_oracle_timestamps: Span, index: usize) -> u64 { - // TODO - 0 + let timestamp = get_uncompacted_value_u64( + compacted_oracle_timestamps, + index, + COMPACTED_TIMESTAMP_BIT_LENGTH, + COMPACTED_TIMESTAMP_BITMASK(), + 'get_uncmpctd_oracle_timestamp' + ); + + if (timestamp == 0) { + OracleError::EMPTY_COMPACTED_TIMESTAMP(index); + } + + timestamp } /// Validate the signer of a price. @@ -188,17 +296,43 @@ fn get_uncompacted_oracle_timestamp(compacted_oracle_timestamps: Span, inde /// * `signature` - The signer's signature. /// * `expected_signer` - The address of the expected signer. fn validate_signer( - salt: felt252, info: ReportInfo, signature: felt252, expected_signer: @ContractAddress -) { // TODO + salt: felt252, info: ReportInfo, signature: Span, expected_signer: @ContractAddress +) { + let signature_r = *signature[0]; + let signature_s = *signature[1]; + let mut digest = LegacyHash::::hash(salt, info.min_oracle_block_number); + digest = LegacyHash::::hash(digest, info.min_oracle_block_number); + digest = LegacyHash::::hash(digest, info.max_oracle_block_number); + digest = LegacyHash::::hash(digest, info.oracle_timestamp); + digest = LegacyHash::::hash(digest, info.block_hash); + digest = LegacyHash::::hash(digest, info.token); + digest = LegacyHash::::hash(digest, info.token_oracle_type); + digest = LegacyHash::::hash(digest, info.precision); + digest = LegacyHash::::hash(digest, info.min_price); + digest = LegacyHash::::hash(digest, info.max_price); + + // We now need to hash message_hash with the size of the array: (change_owner selector, chainid, contract address, old_owner) + // https://github.com/starkware-libs/cairo-lang/blob/b614d1867c64f3fb2cf4a4879348cfcf87c3a5a7/src/starkware/cairo/common/hash_state.py#L6 + digest = LegacyHash::::hash(digest, 10); + + // TODO: What should we have as y_parity (?) + let recovered_public_key = recover_public_key(digest, signature_r, signature_s, true).unwrap(); + + // Get expected public key + let account_dispatcher = IAccountDispatcher { contract_address: *expected_signer }; + let expected_public_key = account_dispatcher.get_owner(); + + if (recovered_public_key != expected_public_key) { + OracleError::INVALID_SIGNATURE(recovered_public_key, expected_public_key); + } } -/// Revert with OracleBlockNumberNotWithinRange error. -/// # Arguments -/// * `max_oracle_block_number` - The max block number used for the signed message hash. -/// * `block` - The current block number. fn revert_oracle_block_number_not_within_range( - min_oracle_block_numbers: Array, max_oracle_block_numbers: Array, block_number: u64 -) { // TODO + min_oracle_block_numbers: Span, max_oracle_block_numbers: Span, block_number: u64 +) { + OracleError::BLOCK_NUMBER_NOT_WITHIN_RANGE( + min_oracle_block_numbers, max_oracle_block_numbers, block_number + ) } /// Check wether `error` is an OracleError. @@ -207,8 +341,7 @@ fn revert_oracle_block_number_not_within_range( /// # Returns /// Wether it's the right error. fn is_oracle_error(error_selector: felt252) -> bool { - // TODO - true + is_oracle_block_number_error(error_selector) || is_empty_price_error(error_selector) } /// Check wether `error` is an EmptyPriceError. @@ -216,9 +349,9 @@ fn is_oracle_error(error_selector: felt252) -> bool { /// * `error` - The error to check. /// # Returns /// Wether it's the right error. +const EMPTY_PRIMARY_PRICE_SELECTOR: felt252 = selector!("EMPTY_PRIMARY_PRICE"); fn is_empty_price_error(error_selector: felt252) -> bool { - // TODO - true + error_selector == EMPTY_PRIMARY_PRICE_SELECTOR } /// Check wether `error` is an OracleBlockNumberError. @@ -226,9 +359,12 @@ fn is_empty_price_error(error_selector: felt252) -> bool { /// * `error` - The error to check. /// # Returns /// Wether it's the right error. +const BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED_SELECTOR: felt252 = + selector!("BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED"); +const BLOCK_NUMBER_NOT_WITHIN_RANGE_SELECTOR: felt252 = selector!("BLOCK_NUMBER_NOT_WITHIN_RANGE"); fn is_oracle_block_number_error(error_selector: felt252) -> bool { - // TODO - true + error_selector == BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED_SELECTOR + || error_selector == BLOCK_NUMBER_NOT_WITHIN_RANGE_SELECTOR } impl DefaultReportInfo of Default { @@ -246,4 +382,3 @@ impl DefaultReportInfo of Default { } } } - diff --git a/src/oracle/price_feed.cairo b/src/oracle/price_feed.cairo index 3809e1da..5cfb09c3 100644 --- a/src/oracle/price_feed.cairo +++ b/src/oracle/price_feed.cairo @@ -14,7 +14,7 @@ enum DataType { #[derive(Serde, Drop, Copy)] struct PragmaPricesResponse { - price: u128, + price: u256, decimals: u32, last_updated_timestamp: u64, num_sources_aggregated: u32, @@ -35,10 +35,7 @@ mod PriceFeed { #[storage] struct Storage {} - #[construct] - fn constructor() {} - - #[external(v0)] + #[abi(embed_v0)] impl PriceFeedImpl of super::IPriceFeed { fn get_data_median(self: @ContractState, data_type: DataType) -> PragmaPricesResponse { PragmaPricesResponse { diff --git a/src/order/base_order_utils.cairo b/src/order/base_order_utils.cairo index a7ffd65b..a83538db 100644 --- a/src/order/base_order_utils.cairo +++ b/src/order/base_order_utils.cairo @@ -4,9 +4,7 @@ // Core lib imports. use integer::BoundedInt; use starknet::ContractAddress; - // Local imports. -use satoru::utils::i128::{I128Div, u128_to_i128, i128_to_u128}; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; @@ -21,6 +19,10 @@ use satoru::utils::precision; use satoru::utils::store_arrays::{StoreMarketArray, StoreU64Array, StoreContractAddressArray}; use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; use satoru::utils::span32::Span32; +use satoru::utils::calc; + + +use satoru::utils::i256::{i256, i256_neg}; #[derive(Drop, starknet::Store, Serde)] struct ExecuteOrderParams { @@ -40,11 +42,12 @@ struct ExecuteOrderParams { /// The keeper sending the transaction. keeper: ContractAddress, /// The starting gas. - starting_gas: u128, + starting_gas: u256, /// The secondary order type. secondary_order_type: SecondaryOrderType } + #[derive(Drop, Copy, starknet::Store, Serde)] struct ExecuteOrderParamsContracts { /// The dispatcher to interact with the `DataStore` contract @@ -62,7 +65,7 @@ struct ExecuteOrderParamsContracts { } /// CreateOrderParams struct used in create_order. -#[derive(Drop, starknet::Store, Serde)] +#[derive(Drop, Copy, starknet::Store, Serde)] struct CreateOrderParams { /// Meant to allow the output of an order to be /// received by an address that is different from the position.account @@ -81,21 +84,21 @@ struct CreateOrderParams { /// An Span32 of market addresses to swap through. swap_path: Span32, /// The requested change in position size. - size_delta_usd: u128, + size_delta_usd: u256, /// For increase orders, this is the amount of the initialCollateralToken sent in by the user. /// For decrease orders, this is the amount of the position's collateralToken to withdraw. /// For swaps, this is the amount of initialCollateralToken sent in for the swap. - initial_collateral_delta_amount: u128, + initial_collateral_delta_amount: u256, /// The trigger price for non-market orders. - trigger_price: u128, + trigger_price: u256, /// The acceptable execution price for increase / decrease orders. - acceptable_price: u128, + acceptable_price: u256, /// The execution fee for keepers. execution_fee: u256, /// The gas limit for the callbackContract. - callback_gas_limit: u128, + callback_gas_limit: u256, /// The minimum output amount for decrease orders and swaps. - min_output_amount: u128, + min_output_amount: u256, /// The order type. order_type: OrderType, /// The swap type on decrease position. @@ -132,9 +135,9 @@ impl CreateOrderParamsClone of Clone { #[derive(Drop, starknet::Store, Serde)] struct GetExecutionPriceCache { - price: u128, - execution_price: u128, - adjusted_price_impact_usd: u128 + price: u256, + execution_price: u256, + adjusted_price_impact_usd: u256 } /// Check if an order_type is a market order. @@ -142,7 +145,6 @@ struct GetExecutionPriceCache { /// * `order_type` - The order type. /// # Return /// Return whether an order_type is a market order -#[inline(always)] fn is_market_order(order_type: OrderType) -> bool { // a liquidation order is not considered as a market order order_type == OrderType::MarketSwap @@ -155,7 +157,6 @@ fn is_market_order(order_type: OrderType) -> bool { /// * `order_type` - The order type. /// # Return /// Return whether an order_type is a limit order -#[inline(always)] fn is_limit_order(order_type: OrderType) -> bool { order_type == OrderType::LimitSwap || order_type == OrderType::LimitIncrease @@ -167,7 +168,6 @@ fn is_limit_order(order_type: OrderType) -> bool { /// * `order_type` - The order type. /// # Return /// Return whether an order_type is a swap order -#[inline(always)] fn is_swap_order(order_type: OrderType) -> bool { order_type == OrderType::MarketSwap || order_type == OrderType::LimitSwap } @@ -177,7 +177,6 @@ fn is_swap_order(order_type: OrderType) -> bool { /// * `order_type` - The order type. /// # Return /// Return whether an order_type is a position order -#[inline(always)] fn is_position_order(order_type: OrderType) -> bool { is_increase_order(order_type) || is_decrease_order(order_type) } @@ -187,7 +186,6 @@ fn is_position_order(order_type: OrderType) -> bool { /// * `order_type` - The order type. /// # Return /// Return whether an order_type is an increase order -#[inline(always)] fn is_increase_order(order_type: OrderType) -> bool { order_type == OrderType::MarketIncrease || order_type == OrderType::LimitIncrease } @@ -197,7 +195,6 @@ fn is_increase_order(order_type: OrderType) -> bool { /// * `order_type` - The order type. /// # Return /// Return whether an order_type is a decrease order -#[inline(always)] fn is_decrease_order(order_type: OrderType) -> bool { order_type == OrderType::MarketDecrease || order_type == OrderType::LimitDecrease @@ -210,7 +207,6 @@ fn is_decrease_order(order_type: OrderType) -> bool { /// * `order_type` - The order type. /// # Return /// Return whether an order_type is a liquidation order -#[inline(always)] fn is_liquidation_order(order_type: OrderType) -> bool { order_type == OrderType::Liquidation } @@ -225,12 +221,11 @@ fn is_liquidation_order(order_type: OrderType) -> bool { /// * `order_type` - The order type. /// * `trigger_price` - the order's trigger_price. /// * `is_long` - Whether the order is for a long or short. -#[inline(always)] fn validate_order_trigger_price( oracle: IOracleDispatcher, index_token: ContractAddress, order_type: OrderType, - trigger_price: u128, + trigger_price: u256, is_long: bool ) { if is_swap_order(order_type) @@ -255,7 +250,7 @@ fn validate_order_trigger_price( }; if !ok { - panic_invalid_prices(primary_price, trigger_price, order_type); + OrderError::INVALID_ORDER_PRICE(primary_price, trigger_price, order_type); } return; @@ -275,7 +270,7 @@ fn validate_order_trigger_price( }; if !ok { - panic_invalid_prices(primary_price, trigger_price, order_type); + OrderError::INVALID_ORDER_PRICE(primary_price, trigger_price, order_type); } return; @@ -295,7 +290,7 @@ fn validate_order_trigger_price( }; if !ok { - panic_invalid_prices(primary_price, trigger_price, order_type); + OrderError::INVALID_ORDER_PRICE(primary_price, trigger_price, order_type); } return; @@ -305,8 +300,8 @@ fn validate_order_trigger_price( } fn get_execution_price_for_increase( - size_delta_usd: u128, size_delta_in_tokens: u128, acceptable_price: u128, is_long: bool -) -> u128 { + size_delta_usd: u256, size_delta_in_tokens: u256, acceptable_price: u256, is_long: bool +) -> u256 { assert(size_delta_in_tokens != 0, OrderError::EMPTY_SIZE_DELTA_IN_TOKENS); let execution_price = size_delta_usd / size_delta_in_tokens; @@ -339,20 +334,19 @@ fn get_execution_price_for_increase( // it may also be possible for users to prevent the execution of orders from other users // by manipulating the price impact, though this should be costly - panic_unfulfillable(execution_price, acceptable_price); + OrderError::ORDER_NOT_FULFILLABLE_AT_ACCEPTABLE_PRICE(execution_price, acceptable_price); 0 // doesn't compile otherwise } -#[inline(always)] fn get_execution_price_for_decrease( index_token_price: Price, - position_size_in_usd: u128, - position_size_in_tokens: u128, - size_delta_usd: u128, - price_impact_usd: i128, - acceptable_price: u128, + position_size_in_usd: u256, + position_size_in_tokens: u256, + size_delta_usd: u256, + price_impact_usd: i256, + acceptable_price: u256, is_long: bool -) -> u128 { +) -> u256 { // decrease order: // - long: use the smaller price // - short: use the larger price @@ -410,44 +404,34 @@ fn get_execution_price_for_decrease( let adjusted_price_impact_usd = if is_long { price_impact_usd } else { - -price_impact_usd + i256_neg(price_impact_usd) }; - if adjusted_price_impact_usd < 0 - && i128_to_u128(-adjusted_price_impact_usd) > size_delta_usd { - panic( - array![ - OrderError::PRICE_IMPACT_LARGER_THAN_ORDER_SIZE, - adjusted_price_impact_usd.into(), - size_delta_usd.into() - ] + if adjusted_price_impact_usd < Zeroable::zero() + && calc::to_unsigned(i256_neg(adjusted_price_impact_usd)) > size_delta_usd { + OrderError::PRICE_IMPACT_LARGER_THAN_ORDER_SIZE( + adjusted_price_impact_usd, size_delta_usd ); } - // error: Trait has no implementation in context: core::traits::Div:: - // TODO: uncomment this when i128 division available - // let adjustment = precision::mul_div_inum(position_size_in_usd, adjusted_price_impact_usd, position_size_in_tokens) / size_delta_usd.try_into().unwrap(); let numerator = precision::mul_div_inum( position_size_in_usd, adjusted_price_impact_usd, position_size_in_tokens ); - let adjustment = numerator / u128_to_i128(size_delta_usd); - - let _execution_price: i128 = u128_to_i128(price) + adjustment; - - if _execution_price < 0 { - panic( - array![ - OrderError::NEGATIVE_EXECUTION_PRICE, - _execution_price.into(), - price.into(), - position_size_in_usd.into(), - adjusted_price_impact_usd.into(), - size_delta_usd.into() - ] + let adjustment = numerator / calc::to_signed(size_delta_usd, true); + + let _execution_price: i256 = calc::to_signed(price, true) + adjustment; + + if _execution_price < Zeroable::zero() { + OrderError::NEGATIVE_EXECUTION_PRICE( + _execution_price, + price, + position_size_in_usd, + adjusted_price_impact_usd, + size_delta_usd ); } - execution_price = i128_to_u128(_execution_price); + execution_price = calc::to_unsigned(_execution_price); } // decrease order: @@ -481,14 +465,13 @@ fn get_execution_price_for_decrease( // // it may also be possible for users to prevent the execution of orders from other users // by manipulating the price impact, though this should be costly - panic_unfulfillable(execution_price, acceptable_price); + OrderError::ORDER_NOT_FULFILLABLE_AT_ACCEPTABLE_PRICE(execution_price, acceptable_price); 0 } /// Validates that an order exists. /// # Arguments /// * `order` - The order to check. -#[inline(always)] fn validate_non_empty_order(order: @Order) { assert((*order.account).is_non_zero(), OrderError::EMPTY_ORDER); assert( @@ -496,27 +479,3 @@ fn validate_non_empty_order(order: @Order) { OrderError::EMPTY_ORDER ); } - -#[inline(always)] -fn panic_invalid_prices(primary_price: Price, trigger_price: u128, order_type: OrderType) { - panic( - array![ - OrderError::INVALID_ORDER_PRICES, - primary_price.min.into(), - primary_price.max.into(), - trigger_price.into(), - order_type.into(), - ] - ); -} - -#[inline(always)] -fn panic_unfulfillable(execution_price: u128, acceptable_price: u128) { - panic( - array![ - OrderError::ORDER_NOT_FULFILLABLE_AT_ACCEPTABLE_PRICE, - execution_price.into(), - acceptable_price.into() - ] - ); -} diff --git a/src/order/decrease_order_utils.cairo b/src/order/decrease_order_utils.cairo index b9c4eb01..04f918c2 100644 --- a/src/order/decrease_order_utils.cairo +++ b/src/order/decrease_order_utils.cairo @@ -1,75 +1,366 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + // Core lib imports. -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; // Local imports. use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::oracle::oracle_utils; use satoru::position::decrease_position_utils::DecreasePositionResult; -use satoru::order::{base_order_utils::ExecuteOrderParams, order::Order}; +use satoru::position::decrease_position_utils; +use satoru::order::{ + base_order_utils::ExecuteOrderParams, order::Order, order::OrderType, error::OrderError, order +}; +use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; -// 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) { //TODO -} +use satoru::utils::arrays; +use satoru::market::market_utils; +use satoru::position::position_utils; +use satoru::position::position::Position; +use satoru::swap::swap_utils::{SwapParams}; +use satoru::position::position_utils::UpdatePositionParams; +use satoru::event::event_utils::{ + Felt252IntoContractAddress, ContractAddressDictValue, I256252DictValue, U256252DictValue, + U256IntoFelt252 +}; +use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::utils::span32::{Span32, Array32Trait}; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +// ************************************************************************* +// Interface of the `OrderUtils` contract. +// ************************************************************************* +#[starknet::interface] +trait IDecreaseOrderUtils { + // This function should return an EventLogData cause the callback_utils + // needs it. We need to find a solution for that case. + fn process_order(ref self: TContractState, params: ExecuteOrderParams); -/// Validate the oracle block numbers used for the prices in the oracle. -/// # Arguments -/// * `min_oracle_block_numbers` - The min oracle block numbers. -/// * `max_oracle_block_numbers` - The max oracle block numbers. -/// * `order_type` - The order type. -/// * `order_updated_at_block` - The block at which the order was last updated. -/// * `position_increased_at_block` - The block at which the position was last increased. -/// * `position_decrease_at_block` - The block at which the position was last decreased. -#[inline(always)] -fn validate_oracle_block_numbers( - min_oracle_block_numbers: Array, - max_oracle_block_numbers: Array, - order_type: Order, - order_updated_at_block: u128, - position_increased_at_block: u128, - position_decrease_at_block: u128 -) { //TODO -} + /// Validate the oracle block numbers used for the prices in the oracle. + /// # Arguments + /// * `min_oracle_block_numbers` - The min oracle block numbers. + /// * `max_oracle_block_numbers` - The max oracle block numbers. + /// * `order_type` - The order type. + /// * `order_updated_at_block` - The block at which the order was last updated. + /// * `position_increased_at_block` - The block at which the position was last increased. + /// * `position_decrease_at_block` - The block at which the position was last decreased. + fn validate_oracle_block_numbers( + ref self: TContractState, + min_oracle_block_numbers: Span, + max_oracle_block_numbers: Span, + order_type: OrderType, + order_updated_at_block: u64, + position_increased_at_block: u64, + position_decreased_at_block: u64 + ); -// Note: that min_output_amount is treated as a USD value for this validation -fn validate_output_amount( - oracle: IOracleDispatcher, - output_token: ContractAddress, - output_amount: u128, - min_output_amount: u128, - max_output_amount: u128 -) { //TODO -} + fn validate_output_amount( + ref self: TContractState, + oracle: IOracleDispatcher, + output_token: ContractAddress, + output_amount: u256, + min_output_amount: u256 + ); -// Note: that min_output_amount is treated as a USD value for this validation -fn validate_output_amount_secondary( - oracle: IOracleDispatcher, - output_token: ContractAddress, - output_amount: u128, - min_output_amount: u128, - secondary_output_token: u128, - secondary_output_amount: u128, - max_output_amount: u128 -) { //TODO -} + // Note: that min_output_amount is treated as a USD value for this validation + fn validate_output_amount_secondary( + ref self: TContractState, + oracle: IOracleDispatcher, + output_token: ContractAddress, + output_amount: u256, + secondary_output_token: ContractAddress, + secondary_output_amount: u256, + min_output_amount: u256 + ); -#[inline(always)] -fn handle_swap_error( - oracle: IOracleDispatcher, - order: Order, - result: DecreasePositionResult, - reason: felt252, - reason_bytes: Array -) { //TOO + fn handle_swap_error( + ref self: TContractState, + oracle: IOracleDispatcher, + order: Order, + result: DecreasePositionResult, + reason: felt252, + reason_bytes: Span, + event_emitter: IEventEmitterDispatcher + ); } -// This function should return an EventLogData cause the callback_utils -// needs it. We need to find a solution for that case. -fn get_output_event_data( - output_token: ContractAddress, - output_amount: u128, - secondary_output_token: ContractAddress, - secondary_output_amount: u128 -) { //TODO +#[starknet::contract] +mod DecreaseOrderUtils { + // Core lib imports. + use starknet::{ContractAddress, contract_address_const}; + + // Local imports. + use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; + use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; + use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; + use satoru::oracle::oracle_utils; + use satoru::position::decrease_position_utils::DecreasePositionResult; + use satoru::position::decrease_position_utils; + use satoru::order::{ + base_order_utils::ExecuteOrderParams, order::Order, order::OrderType, error::OrderError, + order + }; + use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; + + use satoru::utils::arrays; + use satoru::market::market_utils; + use satoru::position::position_utils; + use satoru::position::position::Position; + use satoru::swap::swap_utils::{SwapParams}; + use satoru::position::position_utils::UpdatePositionParams; + use satoru::event::event_utils::{ + Felt252IntoContractAddress, ContractAddressDictValue, I256252DictValue, U256252DictValue, + U256IntoFelt252 + }; + use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; + use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; + use satoru::utils::span32::{Span32, Array32Trait}; + use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; + + #[storage] + struct Storage {} + + // ************************************************************************* + // EXTERNAL FUNCTIONS + // ************************************************************************* + #[abi(embed_v0)] + impl DecreaseOrderUtilsImpl of super::IDecreaseOrderUtils { + // This function should return an EventLogData cause the callback_utils + // needs it. We need to find a solution for that case. + fn process_order( + ref self: ContractState, params: ExecuteOrderParams + ) { //TODO check with refactor with callback_utils + let order: Order = params.order; + + market_utils::validate_position_market_check( + params.contracts.data_store, params.market + ); + + let position_key: felt252 = position_utils::get_position_key( + order.account, order.market, order.initial_collateral_token, order.is_long + ); + + let data_store: IDataStoreDispatcher = params.contracts.data_store; + let position = data_store.get_position(position_key); + position_utils::validate_non_empty_position(position); + + // validate_oracle_block_numbers( + // params.min_oracle_block_numbers.span(), + // params.max_oracle_block_numbers.span(), + // order.order_type, + // order.updated_at_block, + // position.increased_at_block, + // position.decreased_at_block + // ); + let mut update_position_params: UpdatePositionParams = UpdatePositionParams { + contracts: params.contracts, + market: params.market, + order: order, + order_key: params.key, + position: position, + position_key, + secondary_order_type: params.secondary_order_type + }; + let mut result: DecreasePositionResult = decrease_position_utils::decrease_position( + update_position_params + ); + // if the pnl_token and the collateral_token are different + // and if a swap fails or no swap was requested + // then it is possible to receive two separate tokens from decreasing + // the position + // transfer the two tokens to the user in this case and skip processing + // the swap_path + if (result.secondary_output_amount > 0) { + self + .validate_output_amount_secondary( + params.contracts.oracle, + result.output_token, + result.output_amount, + result.secondary_output_token, + result.secondary_output_amount, + order.min_output_amount + ); + IMarketTokenDispatcher { contract_address: order.market } + .transfer_out( + order.market, result.output_token, order.receiver, result.output_amount + ); + + IMarketTokenDispatcher { contract_address: order.market } + .transfer_out( + order.market, + result.secondary_output_token, + order.receiver, + result.secondary_output_amount + ); + // return get_output_event_data( + // result.output_token, + // result.output_amount, + // result.secondary_output_token, + // result.secondary_output_amount + // ); + } + + let swap_param: SwapParams = SwapParams { + data_store: params.contracts.data_store, + event_emitter: params.contracts.event_emitter, + oracle: params.contracts.oracle, + bank: IBankDispatcher { contract_address: order.market }, + key: params.key, + token_in: result.output_token, + amount_in: result.output_amount, + swap_path_markets: params.swap_path_markets.span(), + min_output_amount: 0, + receiver: order.receiver, + ui_fee_receiver: order.ui_fee_receiver, + }; + + //TODO handle the swap_error when its possible + let (token_out, swap_output_amount) = params.contracts.swap_handler.swap(swap_param); + + self + .validate_output_amount( + params.contracts.oracle, token_out, swap_output_amount, order.min_output_amount + ); + // return get_output_event_data(token_out, swap_output_amount, contract_address_const::<0>(), 0); + } + + + /// Validate the oracle block numbers used for the prices in the oracle. + /// # Arguments + /// * `min_oracle_block_numbers` - The min oracle block numbers. + /// * `max_oracle_block_numbers` - The max oracle block numbers. + /// * `order_type` - The order type. + /// * `order_updated_at_block` - The block at which the order was last updated. + /// * `position_increased_at_block` - The block at which the position was last increased. + /// * `position_decrease_at_block` - The block at which the position was last decreased. + fn validate_oracle_block_numbers( + ref self: ContractState, + min_oracle_block_numbers: Span, + max_oracle_block_numbers: Span, + order_type: OrderType, + order_updated_at_block: u64, + position_increased_at_block: u64, + position_decreased_at_block: u64 + ) { + if order_type == OrderType::MarketDecrease { + oracle_utils::validate_block_number_within_range( + min_oracle_block_numbers, max_oracle_block_numbers, order_updated_at_block + ); + return; + } + + if (order_type == OrderType::LimitDecrease + || order_type == OrderType::StopLossDecrease) { + let mut latest_updated_at_block: u64 = position_increased_at_block; + if (order_updated_at_block > position_increased_at_block) { + latest_updated_at_block = order_updated_at_block + } + if (!arrays::are_gte_u64(min_oracle_block_numbers, latest_updated_at_block)) { + OrderError::ORACLE_BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED( + min_oracle_block_numbers, latest_updated_at_block + ); + } + return; + } + if (order_type == OrderType::Liquidation) { + let mut latest_updated_at_block: u64 = position_decreased_at_block; + if (position_increased_at_block > position_decreased_at_block) { + latest_updated_at_block = position_increased_at_block + } + if (!arrays::are_gte_u64(min_oracle_block_numbers, latest_updated_at_block)) { + OrderError::ORACLE_BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED( + min_oracle_block_numbers, latest_updated_at_block + ); + } + return; + } + panic_with_felt252(OrderError::UNSUPPORTED_ORDER_TYPE); + } + + // Note: that min_output_amount is treated as a USD value for this validation + fn validate_output_amount( + ref self: ContractState, + oracle: IOracleDispatcher, + output_token: ContractAddress, + output_amount: u256, + min_output_amount: u256 + ) { + let output_token_price: u256 = oracle.get_primary_price(output_token).min; + let output_usd: u256 = output_amount * output_token_price; + + if (output_usd < min_output_amount) { + OrderError::INSUFFICIENT_OUTPUT_AMOUNT(output_usd, output_token_price); + } + } + + // Note: that min_output_amount is treated as a USD value for this validation + fn validate_output_amount_secondary( + ref self: ContractState, + oracle: IOracleDispatcher, + output_token: ContractAddress, + output_amount: u256, + secondary_output_token: ContractAddress, + secondary_output_amount: u256, + min_output_amount: u256 + ) { + let output_token_price: u256 = oracle.get_primary_price(output_token).min; + let output_usd: u256 = output_amount * output_token_price; + + let secondary_output_token_price: u256 = oracle + .get_primary_price(secondary_output_token) + .min; + let seconday_output_usd: u256 = secondary_output_amount * secondary_output_token_price; + + let total_output_usd: u256 = output_usd + seconday_output_usd; + + if (total_output_usd < min_output_amount) { + OrderError::INSUFFICIENT_OUTPUT_AMOUNT(output_usd, output_token_price); + } + } + + fn handle_swap_error( + ref self: ContractState, + oracle: IOracleDispatcher, + order: Order, + result: DecreasePositionResult, + reason: felt252, + reason_bytes: Span, + event_emitter: IEventEmitterDispatcher + ) { + event_emitter.emit_swap_reverted(reason, reason_bytes); + + self + .validate_output_amount( + oracle, result.output_token, result.output_amount, order.min_output_amount + ); + + IMarketTokenDispatcher { contract_address: order.market } + .transfer_out( + order.market, result.output_token, order.receiver, result.output_amount + ); + } + // This function should return an EventLogData cause the callback_utils + // needs it. We need to find a solution for that case. + // fn get_output_event_data( + // output_token: ContractAddress, + // output_amount: u256, + // secondary_output_token: ContractAddress, + // secondary_output_amount: u256 + // ) { //-> LogData { + // let mut log_data: LogData = Default::default(); + + // log_data.address_dict.insert_single('output_token', output_token); + // log_data.address_dict.insert_single('secondary_output_token', secondary_output_token); + + // log_data.uint_dict.insert_single('output_amount', output_amount); + // log_data.uint_dict.insert_single('secondary_output_amount', secondary_output_amount); + + // //log_data + // } + } } diff --git a/src/order/error.cairo b/src/order/error.cairo index 25dd29df..d9341423 100644 --- a/src/order/error.cairo +++ b/src/order/error.cairo @@ -1,16 +1,102 @@ mod OrderError { + use satoru::order::order::OrderType; + use satoru::price::price::Price; + use satoru::utils::i256::i256; + const EMPTY_ORDER: felt252 = 'empty_order'; + const INVALID_ORDER_PRICES: felt252 = 'invalid_order_prices'; const INVALID_KEEPER_FOR_FROZEN_ORDER: felt252 = 'invalid_keeper_for_frozen_order'; const UNSUPPORTED_ORDER_TYPE: felt252 = 'unsupported_order_type'; - const INVALID_ORDER_PRICES: felt252 = 'invalid_order_prices'; const INVALID_FROZEN_ORDER_KEEPER: felt252 = 'invalid_frozen_order_keeper'; const ORDER_NOT_FOUND: felt252 = 'order_not_found'; const ORDER_INDEX_NOT_FOUND: felt252 = 'order_index_not_found'; const CANT_BE_ZERO: felt252 = 'order account cant be 0'; - const ORDER_NOT_FULFILLABLE_AT_ACCEPTABLE_PRICE: felt252 = - 'order_unfulfillable_at_price'; // TODO: unshorten value - const NEGATIVE_EXECUTION_PRICE: felt252 = 'negative_execution_price'; - const PRICE_IMPACT_LARGER_THAN_ORDER_SIZE: felt252 = - 'price_impact_too_large'; // TODO: unshorten value const EMPTY_SIZE_DELTA_IN_TOKENS: felt252 = 'empty_size_delta_in_tokens'; + const UNEXPECTED_MARKET: felt252 = 'unexpected market'; + const INVALID_SIZE_DELTA_FOR_ADL: felt252 = 'invalid_size_delta_for_adl'; + const POSITION_NOT_VALID: felt252 = 'position_not_valid'; + const ORDER_ALREADY_FROZEN: felt252 = 'order_already_frozen'; + + + fn ORACLE_BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED( + min_oracle_block_numbers: Span, latest_updated_at_block: u64 + ) { + let mut data: Array = array!['Block nbs smaller than required']; + let len: u32 = min_oracle_block_numbers.len(); + let mut i: u32 = 0; + loop { + if (i == len) { + break; + } + let value: u64 = *min_oracle_block_numbers.at(i); + data.append(value.into()); + i += 1; + }; + data.append(latest_updated_at_block.into()); + panic(data) + } + + fn INSUFFICIENT_OUTPUT_AMOUNT(output_usd: u256, min_output_amount: u256) { + let mut data = array!['Insufficient output amount']; + data.append(output_usd.try_into().expect('u256 into felt failed')); + data.append(min_output_amount.try_into().expect('u256 into felt failed')); + panic(data); + } + + fn INVALID_ORDER_PRICE(primary_price: Price, trigger_price: u256, order_type: OrderType) { + let mut data: Array = array![]; + data.append('invalid_order_price'); + // data.append(primary_price.min.try_into().expect('u256 into felt failed')); // TODO Find a way to test them test_takeprofit_long_increase_fails + // data.append(primary_price.max.try_into().expect('u256 into felt failed')); + // data.append(trigger_price.try_into().expect('u256 into felt failed')); + data.append(order_type.into()); + panic(data); + } + + fn ORDER_NOT_FULFILLABLE_AT_ACCEPTABLE_PRICE(execution_price: u256, acceptable_price: u256) { + let mut data: Array = array![]; + data.append('order_unfulfillable_at_price'); + data.append(execution_price.try_into().expect('u256 into felt failed')); + data.append(acceptable_price.try_into().expect('u256 into felt failed')); + panic(data); + } + + fn PRICE_IMPACT_LARGER_THAN_ORDER_SIZE(price_impact_usd: i256, size_delta_usd: u256) { + let mut data: Array = array![]; + data.append('price_impact_too_large'); + data.append(price_impact_usd.try_into().expect('u256 into felt failed')); + data.append(size_delta_usd.try_into().expect('u256 into felt failed')); + panic(data); + } + + fn NEGATIVE_EXECUTION_PRICE( + execution_price: i256, + price: u256, + position_size_in_usd: u256, + adjusted_price_impact_usd: i256, + size_delta_usd: u256 + ) { + let mut data: Array = array![]; + data.append('negative_execution_price'); + data.append(execution_price.into()); + data.append(price.try_into().expect('u256 into felt failed')); + data.append(position_size_in_usd.try_into().expect('u256 into felt failed')); + data.append(adjusted_price_impact_usd.into()); + data.append(size_delta_usd.try_into().expect('u256 into felt failed')); + panic(data); + } + + fn ORDER_TYPE_CANNOT_BE_CREATED(order_type: OrderType,) { + let mut data: Array = array![]; + data.append('order_type_cannot_be_created'); + data.append(order_type.into()); + panic(data); + } + + fn INSUFFICIENT_WNT_AMOUNT_FOR_EXECUTION_FEE(first_amount: u256, secont_amount: u256) { + let mut data = array!['Insufficient wnt amount for fee']; + data.append(first_amount.try_into().expect('u256 into felt failed')); + data.append(secont_amount.try_into().expect('u256 into felt failed')); + panic(data); + } } diff --git a/src/order/increase_order_utils.cairo b/src/order/increase_order_utils.cairo index efe7ba6e..1577e4a7 100644 --- a/src/order/increase_order_utils.cairo +++ b/src/order/increase_order_utils.cairo @@ -1,26 +1,186 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + // Core lib imports. use starknet::ContractAddress; // Local imports. -use satoru::order::{base_order_utils::ExecuteOrderParams, order::Order}; +use satoru::order::{ + base_order_utils::ExecuteOrderParams, order::{Order, OrderType}, error::OrderError +}; +use satoru::data::{data_store::IDataStoreDispatcherTrait, error::DataError}; +use satoru::oracle::{oracle_utils, error::OracleError}; +use satoru::market::market_utils; +use satoru::swap::swap_utils; +use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; +use satoru::position::{position_utils, error::PositionError, increase_position_utils}; + +// External imports. +use alexandria_data_structures::array_ext::SpanTraitExt; + +// ************************************************************************* +// Interface of the `OrderUtils` contract. +// ************************************************************************* +#[starknet::interface] +trait IIncreaseOrderUtils { + /// Process an increase order. + /// # Arguments + /// * `params` - The execute order params. + /// # Returns + /// * `EventLogData` - The event log data. + /// This function should return an EventLogData cause the callback_utils + /// needs it. We need to find a solution for that case. + fn process_order(ref self: TContractState, params: ExecuteOrderParams); -// 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) { //TODO + /// Validate the oracle block numbers used for the prices in the oracle. + /// # Arguments + /// * `min_oracle_block_numbers` - The min oracle block numbers. + /// * `max_oracle_block_numbers` - The max oracle block numbers. + /// * `order_type` - The order type. + /// * `order_updated_at_block` - The block at which the order was last updated. + fn validate_oracle_block_numbers( + ref self: TContractState, + min_oracle_block_numbers: Span, + max_oracle_block_numbers: Span, + order_type: OrderType, + order_updated_at_block: u64 + ); } -/// Validate the oracle block numbers used for the prices in the oracle. -/// # Arguments -/// * `min_oracle_block_numbers` - The min oracle block numbers. -/// * `max_oracle_block_numbers` - The max oracle block numbers. -/// * `order_type` - The order type. -/// * `order_updated_at_block` - The block at which the order was last updated. -#[inline(always)] -fn validate_oracle_block_numbers( - min_oracle_block_numbers: Array, - max_oracle_block_numbers: Array, - order_type: Order, - order_updated_at_block: u128 -) { //TODO +#[starknet::contract] +mod IncreaseOrderUtils { + // Core lib imports. + use starknet::ContractAddress; + + // Local imports. + use satoru::order::{ + base_order_utils::ExecuteOrderParams, order::{Order, OrderType}, error::OrderError + }; + use satoru::data::{data_store::IDataStoreDispatcherTrait, error::DataError}; + use satoru::oracle::{oracle_utils, error::OracleError}; + use satoru::market::market_utils; + use satoru::swap::swap_utils; + use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; + use satoru::position::{position_utils, error::PositionError, increase_position_utils}; + + // External imports. + use alexandria_data_structures::array_ext::SpanTraitExt; + + #[storage] + struct Storage {} + + // ************************************************************************* + // EXTERNAL FUNCTIONS + // ************************************************************************* + #[abi(embed_v0)] + impl IncreaseOrderUtilsImpl of super::IIncreaseOrderUtils { + /// Process an increase order. + /// # Arguments + /// * `params` - The execute order params. + /// # Returns + /// * `EventLogData` - The event log data. + /// This function should return an EventLogData cause the callback_utils + /// needs it. We need to find a solution for that case. + fn process_order(ref self: ContractState, params: ExecuteOrderParams) { + 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 { + data_store: params.contracts.data_store, + event_emitter: params.contracts.event_emitter, + oracle: params.contracts.oracle, + bank: IBankDispatcher { + contract_address: params.contracts.order_vault.contract_address + }, + key: params.key, + token_in: params.order.initial_collateral_token, + amount_in: params.order.initial_collateral_delta_amount, + swap_path_markets: params.swap_path_markets.span(), + min_output_amount: params.order.min_output_amount, + receiver: params.order.market, + ui_fee_receiver: params.order.ui_fee_receiver, + } + ); + + market_utils::validate_market_collateral_token(params.market, collateral_token); + let position_key = position_utils::get_position_key( + params.order.account, params.order.market, collateral_token, params.order.is_long, + ); + let mut position = params.contracts.data_store.get_position(position_key); + // Initialize position + if position.account.is_zero() { + position.account = params.order.account; + if !position.market.is_zero() || !position.collateral_token.is_zero() { + panic_with_felt252(PositionError::UNEXPECTED_POSITION_STATE); + } + position.market = params.order.market; + position.collateral_token = collateral_token; + position.is_long = params.order.is_long; + }; + // validate_oracle_block_numbers( + // params.min_oracle_block_numbers.span(), + // params.max_oracle_block_numbers.span(), + // params.order.order_type, + // params.order.updated_at_block + // ); + increase_position_utils::increase_position( + position_utils::UpdatePositionParams { + contracts: params.contracts, + market: params.market, + order: params.order, + order_key: params.key, + position: position, + position_key: position_key, + secondary_order_type: params.secondary_order_type, + }, + collateral_increment_amount + ); + let position_updated = params.contracts.data_store.get_position(position_key); + } + + /// Validate the oracle block numbers used for the prices in the oracle. + /// # Arguments + /// * `min_oracle_block_numbers` - The min oracle block numbers. + /// * `max_oracle_block_numbers` - The max oracle block numbers. + /// * `order_type` - The order type. + /// * `order_updated_at_block` - The block at which the order was last updated. + fn validate_oracle_block_numbers( + ref self: ContractState, + min_oracle_block_numbers: Span, + max_oracle_block_numbers: Span, + order_type: OrderType, + order_updated_at_block: u64 + ) { + if order_type == OrderType::MarketIncrease { + oracle_utils::validate_block_number_within_range( + min_oracle_block_numbers, max_oracle_block_numbers, order_updated_at_block + ); + return; + }; + + if order_type == OrderType::LimitIncrease { + // since the oracle blocks are only validated against the orderUpdatedAtBlock + // it is possible to cause a limit increase order to become executable by + // having the order have an initial collateral amount of zero then opening + // a position and depositing collateral if the limit order is desired to be executed + // for this case, when the limit order price is reached, the order should be frozen + // the frozen order keepers should only execute frozen orders if the latest prices + // fulfill the limit price + let min_oracle_block_number = min_oracle_block_numbers + .min() + .expect(OracleError::EMPTY_ORACLE_BLOCK_NUMBERS); + if min_oracle_block_number < order_updated_at_block { + OracleError::ORACLE_BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED( + min_oracle_block_numbers, order_updated_at_block + ); + } + return; + } + + panic(array![OrderError::UNSUPPORTED_ORDER_TYPE]); + } + } } diff --git a/src/order/order.cairo b/src/order/order.cairo index 50c797d3..2e44b1bc 100644 --- a/src/order/order.cairo +++ b/src/order/order.cairo @@ -5,7 +5,6 @@ // Core lib imports. use starknet::{ContractAddress, contract_address_const}; use starknet::info::get_block_number; -use debug::PrintTrait; use array::ArrayTrait; // Local imports. @@ -37,21 +36,21 @@ struct Order { /// An array of market addresses to swap through. swap_path: Span32, /// The requested change in position size. - size_delta_usd: u128, + size_delta_usd: u256, /// For increase orders, this is the amount of the initialCollateralToken sent in by the user. /// For decrease orders, this is the amount of the position's collateralToken to withdraw. /// For swaps, this is the amount of initialCollateralToken sent in for the swap. - initial_collateral_delta_amount: u128, + initial_collateral_delta_amount: u256, /// The trigger price for non-market orders. - trigger_price: u128, + trigger_price: u256, /// The acceptable execution price for increase / decrease orders. - acceptable_price: u128, + acceptable_price: u256, /// The execution fee for keepers. - execution_fee: u128, + execution_fee: u256, /// The gas limit for the callbackContract. - callback_gas_limit: u128, + callback_gas_limit: u256, /// The minimum output amount for decrease orders and swaps. - min_output_amount: u128, + min_output_amount: u256, /// The block at which the order was last updated. updated_at_block: u64, /// Whether the order is for a long or short. @@ -66,12 +65,12 @@ impl DefaultOrder of Default { key: 0, decrease_position_swap_type: DecreasePositionSwapType::NoSwap, order_type: OrderType::MarketSwap, - account: 0.try_into().unwrap(), - receiver: 0.try_into().unwrap(), - callback_contract: 0.try_into().unwrap(), - ui_fee_receiver: 0.try_into().unwrap(), - market: 0.try_into().unwrap(), - initial_collateral_token: 0.try_into().unwrap(), + account: contract_address_const::<0>(), + receiver: contract_address_const::<0>(), + callback_contract: contract_address_const::<0>(), + ui_fee_receiver: contract_address_const::<0>(), + market: contract_address_const::<0>(), + initial_collateral_token: contract_address_const::<0>(), swap_path: Array32Trait::::span32(@ArrayTrait::new()), size_delta_usd: 0, initial_collateral_delta_amount: 0, @@ -119,20 +118,21 @@ enum OrderType { } /// To help further differentiate orders. -#[derive(Drop, Copy, starknet::Store, Serde)] +#[derive(Drop, starknet::Store, Serde, PartialEq, Copy, Default)] enum SecondaryOrderType { + #[default] None, Adl, } -impl SecondaryOrderTypePrintImpl of PrintTrait { - fn print(self: SecondaryOrderType) { - match self { - SecondaryOrderType::None => 'None'.print(), - SecondaryOrderType::Adl => 'Adl'.print(), - } - } -} +// impl SecondaryOrderTypePrintImpl of PrintTrait { +// fn print(self: SecondaryOrderType) { +// match self { +// SecondaryOrderType::None => 'None'.print(), +// SecondaryOrderType::Adl => 'Adl'.print(), +// } +// } +// } /// `DecreasePositionSwapType` is used to indicate whether the decrease order should swap /// the pnl token to collateral token or vice versa. @@ -143,17 +143,17 @@ enum DecreasePositionSwapType { SwapCollateralTokenToPnlToken, } -impl DecreasePositionSwapTypePrintImpl of PrintTrait { - fn print(self: DecreasePositionSwapType) { - match self { - DecreasePositionSwapType::NoSwap => 'NoSwap'.print(), - DecreasePositionSwapType::SwapPnlTokenToCollateralToken => 'SwapPnlTokenToCollateralToken' - .print(), - DecreasePositionSwapType::SwapCollateralTokenToPnlToken => 'SwapCollateralTokenToPnlToken' - .print(), - } - } -} +// impl DecreasePositionSwapTypePrintImpl of PrintTrait { +// fn print(self: DecreasePositionSwapType) { +// match self { +// DecreasePositionSwapType::NoSwap => 'NoSwap'.print(), +// DecreasePositionSwapType::SwapPnlTokenToCollateralToken => 'SwapPnlTokenToCollateralToken' +// .print(), +// DecreasePositionSwapType::SwapCollateralTokenToPnlToken => 'SwapCollateralTokenToPnlToken' +// .print(), +// } +// } +// } impl OrderTypeInto of Into { fn into(self: OrderType) -> felt252 { @@ -169,18 +169,19 @@ impl OrderTypeInto of Into { } } } +// impl OrderTypePrintImpl of PrintTrait { +// fn print(self: OrderType) { +// match self { +// OrderType::MarketSwap => 'MarketSwap'.print(), +// OrderType::LimitSwap => 'LimitSwap'.print(), +// OrderType::MarketIncrease => 'MarketIncrease'.print(), +// OrderType::LimitIncrease => 'LimitIncrease'.print(), +// OrderType::MarketDecrease => 'MarketDecrease'.print(), +// OrderType::LimitDecrease => 'LimitDecrease'.print(), +// OrderType::StopLossDecrease => 'StopLossDecrease'.print(), +// OrderType::Liquidation => 'Liquidation'.print(), +// } +// } +// } + -impl OrderTypePrintImpl of PrintTrait { - fn print(self: OrderType) { - match self { - OrderType::MarketSwap => 'MarketSwap'.print(), - OrderType::LimitSwap => 'LimitSwap'.print(), - OrderType::MarketIncrease => 'MarketIncrease'.print(), - OrderType::LimitIncrease => 'LimitIncrease'.print(), - OrderType::MarketDecrease => 'MarketDecrease'.print(), - OrderType::LimitDecrease => 'LimitDecrease'.print(), - OrderType::StopLossDecrease => 'StopLossDecrease'.print(), - OrderType::Liquidation => 'Liquidation'.print(), - } - } -} diff --git a/src/order/order_event_utils.cairo b/src/order/order_event_utils.cairo deleted file mode 100644 index c0372647..00000000 --- a/src/order/order_event_utils.cairo +++ /dev/null @@ -1,11 +0,0 @@ -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; - -fn emit_order_updated( - event_emitter: IEventEmitterDispatcher, - key: felt252, - size_delta_usd: u128, - acceptable_price: u128, - trigger_price: u128, - min_output_amount: u128 -) { // TODO -} diff --git a/src/order/order_store_utils.cairo b/src/order/order_store_utils.cairo deleted file mode 100644 index e42c8bb1..00000000 --- a/src/order/order_store_utils.cairo +++ /dev/null @@ -1,12 +0,0 @@ -use traits::Default; - -use satoru::data::data_store::IDataStoreDispatcher; -use satoru::order::order::Order; - -fn get(data_store: IDataStoreDispatcher, key: felt252) -> Order { - // TODO - Default::default() -} - -fn set(data_store: IDataStoreDispatcher, key: felt252, value: @Order) { // TODO -} diff --git a/src/order/order_utils.cairo b/src/order/order_utils.cairo index 4a5ec0f6..ddd85062 100644 --- a/src/order/order_utils.cairo +++ b/src/order/order_utils.cairo @@ -4,92 +4,479 @@ // Core lib imports. use starknet::ContractAddress; - // Local imports. -use satoru::order::base_order_utils::{ExecuteOrderParams, CreateOrderParams}; +use satoru::oracle::oracle_utils::{SetPricesParams, SimulatePricesParams}; +use satoru::order::{base_order_utils::{CreateOrderParams, ExecuteOrderParams}, order::Order}; +use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::event::event_utils::{ + Felt252IntoContractAddress, ContractAddressDictValue, I256252DictValue +}; +// ************************************************************************* +// Interface of the `OrderUtils` contract. +// ************************************************************************* +#[starknet::interface] +trait IOrderUtils { + /// Creates an order in the order store. + /// # Arguments + /// * `data_store` - The `DataStore` contract dispatcher. + /// * `event_emitter` - The `EventEmitter` contract dispatcher. + /// * `order_vault` - The `OrderVault` contract dispatcher. + /// * `referral_store` - The referral storage instance to use. + /// * `account` - The order account. + /// * `params` - The parameters used to create the order. + /// # Returns + /// Return the key of the created order. + fn create_order_utils( + ref self: TContractState, + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + order_vault: IOrderVaultDispatcher, + referral_storage: IReferralStorageDispatcher, + account: ContractAddress, + params: CreateOrderParams + ) -> felt252; -/// Creates an order in the order store. -/// # Arguments -/// * `data_store` - The `DataStore` contract dispatcher. -/// * `event_emitter` - The `EventEmitter` contract dispatcher. -/// * `order_vault` - The `OrderVault` contract dispatcher. -/// * `referral_store` - The referral storage instance to use. -/// * `account` - The order account. -/// * `params` - The parameters used to create the order. -/// # Returns -/// Return the key of the created order. -fn create_order( - data_store: IDataStoreDispatcher, - event_emitter: IEventEmitterDispatcher, - order_vault: IOrderVaultDispatcher, - referral_store: IReferralStorageDispatcher, - account: ContractAddress, - params: CreateOrderParams -) -> felt252 { - 0 -//TODO -} + fn execute_order_utils(ref self: TContractState, params: ExecuteOrderParams); -/// Executes an order. -/// # Arguments -/// * `params` - The parameters used to execute the order. -#[inline(always)] -fn execute_order(params: ExecuteOrderParams) { //TODO -} + /// Process an order execution. + /// # Arguments + /// * `params` - The parameters used to process the order. + fn process_order( + ref self: TContractState, params: ExecuteOrderParams + ); //TODO add LogData return value -/// Process an order execution. -/// # Arguments -/// * `params` - The parameters used to process the order. -#[inline(always)] -fn process_order(params: ExecuteOrderParams) { //TODO -} + /// Cancels an order. + /// # Arguments + /// * `data_store` - The `DataStore` contract dispatcher. + /// * `event_emitter` - The `EventEmitter` contract dispatcher. + /// * `order_vault` - The `OrderVault` contract dispatcher. + /// * `key` - The key of the order to cancel + /// * `keeper` - The keeper sending the transaction. + /// * `starting_gas` - The starting gas of the transaction. + /// * `reason` - The reason for cancellation. + /// # Returns + /// Return the key of the created order. + fn cancel_order( + ref self: TContractState, + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + order_vault: IOrderVaultDispatcher, + key: felt252, + keeper: ContractAddress, + starting_gas: u256, + reason: felt252, + reason_bytes: Array + ); -/// Cancels an order. -/// # Arguments -/// * `data_store` - The `DataStore` contract dispatcher. -/// * `event_emitter` - The `EventEmitter` contract dispatcher. -/// * `order_vault` - The `OrderVault` contract dispatcher. -/// * `key` - The key of the order to cancel -/// * `keeper` - The keeper sending the transaction. -/// * `starting_gas` - The starting gas of the transaction. -/// * `reason` - The reason for cancellation. -/// # Returns -/// Return the key of the created order. -fn cancel_order( - data_store: IDataStoreDispatcher, - event_emitter: IEventEmitterDispatcher, - order_vault: IOrderVaultDispatcher, - key: felt252, - keeper: ContractAddress, - starting_gas: u128, - reason: felt252, - reason_bytes: Array -) { //TODO + /// Freezes an order. + /// # Arguments + /// * `data_store` - The `DataStore` contract dispatcher. + /// * `event_emitter` - The `EventEmitter` contract dispatcher. + /// * `order_vault` - The `OrderVault` contract dispatcher. + /// * `key` - The key of the order to freeze + /// * `keeper` - The keeper sending the transaction. + /// * `starting_gas` - The starting gas of the transaction. + /// * `reason` - The reason the order was frozen. + /// # Returns + /// Return the key of the created order. + fn freeze_order( + ref self: TContractState, + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + order_vault: IOrderVaultDispatcher, + key: felt252, + keeper: ContractAddress, + starting_gas: u256, + reason: felt252, + reason_bytes: Array + ); } -/// Freezes an order. -/// # Arguments -/// * `data_store` - The `DataStore` contract dispatcher. -/// * `event_emitter` - The `EventEmitter` contract dispatcher. -/// * `order_vault` - The `OrderVault` contract dispatcher. -/// * `key` - The key of the order to freeze -/// * `keeper` - The keeper sending the transaction. -/// * `starting_gas` - The starting gas of the transaction. -/// * `reason` - The reason the order was frozen. -/// # Returns -/// Return the key of the created order. -fn freeze_order( - data_store: IDataStoreDispatcher, - event_emitter: IEventEmitterDispatcher, - order_vault: IOrderVaultDispatcher, - key: felt252, - keeper: ContractAddress, - starting_gas: u128, - reason: felt252, - reason_bytes: Array -) { //TODO + +#[starknet::contract] +mod OrderUtils { + // Core lib imports. + use satoru::order::swap_order_utils::ISwapOrderUtilsDispatcherTrait; + use satoru::order::decrease_order_utils::IDecreaseOrderUtilsDispatcherTrait; + use satoru::order::increase_order_utils::IIncreaseOrderUtilsDispatcherTrait; + use starknet::{ContractAddress, contract_address_const, ClassHash}; + use clone::Clone; + // Local imports. + use satoru::order::base_order_utils::{ExecuteOrderParams, CreateOrderParams}; + use satoru::order::base_order_utils; + use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; + use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; + use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; + use satoru::mock::referral_storage::{ + IReferralStorageDispatcher, IReferralStorageDispatcherTrait + }; + use satoru::market::market_utils; + use satoru::nonce::nonce_utils; + use satoru::utils::account_utils; + use satoru::referral::referral_utils; + use satoru::token::token_utils; + use satoru::callback::callback_utils; + use satoru::gas::gas_utils; + use satoru::order::order::{Order, OrderType, OrderTrait}; + use satoru::event::event_utils::{ + Felt252IntoContractAddress, ContractAddressDictValue, I256252DictValue + }; + use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; + use satoru::order::error::OrderError; + + use satoru::order::increase_order_utils::IIncreaseOrderUtilsLibraryDispatcher; + use satoru::order::decrease_order_utils::IDecreaseOrderUtilsLibraryDispatcher; + use satoru::order::swap_order_utils::ISwapOrderUtilsLibraryDispatcher; + + use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + + #[storage] + struct Storage { + increase_order_utils_lib: IIncreaseOrderUtilsLibraryDispatcher, + decrease_order_utils_lib: IDecreaseOrderUtilsLibraryDispatcher, + swap_order_utils_lib: ISwapOrderUtilsLibraryDispatcher, + } + + // ************************************************************************* + // CONSTRUCTOR + // ************************************************************************* + #[constructor] + fn constructor( + ref self: ContractState, + increase_order_class_hash: ClassHash, + decrease_order_class_hash: ClassHash, + swap_order_class_hash: ClassHash + ) { + self + .increase_order_utils_lib + .write(IIncreaseOrderUtilsLibraryDispatcher { class_hash: increase_order_class_hash }); + self + .decrease_order_utils_lib + .write(IDecreaseOrderUtilsLibraryDispatcher { class_hash: decrease_order_class_hash }); + self + .swap_order_utils_lib + .write(ISwapOrderUtilsLibraryDispatcher { class_hash: swap_order_class_hash }); + } + + // ************************************************************************* + // EXTERNAL FUNCTIONS + // ************************************************************************* + #[abi(embed_v0)] + impl OrderUtilsImpl of super::IOrderUtils { + /// Creates an order in the order store. + /// # Arguments + /// * `data_store` - The `DataStore` contract dispatcher. + /// * `event_emitter` - The `EventEmitter` contract dispatcher. + /// * `order_vault` - The `OrderVault` contract dispatcher. + /// * `referral_store` - The referral storage instance to use. + /// * `account` - The order account. + /// * `params` - The parameters used to create the order. + /// # Returns + /// Return the key of the created order. + fn create_order_utils( //TODO and fix when fee_token is implememted + ref self: ContractState, + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + order_vault: IOrderVaultDispatcher, + referral_storage: IReferralStorageDispatcher, + account: ContractAddress, + mut params: CreateOrderParams + ) -> felt252 { + account_utils::validate_account(account); + referral_utils::set_trader_referral_code( + referral_storage, account, params.referral_code + ); + + let mut initial_collateral_delta_amount = 0; + + let fee_token = token_utils::fee_token(data_store); + + let mut should_record_separate_execution_fee_transfer = true; + + if (params.order_type == OrderType::MarketSwap + || params.order_type == OrderType::LimitSwap + || params.order_type == OrderType::MarketIncrease + || params.order_type == OrderType::LimitIncrease) { + // for swaps and increase orders, the initialCollateralDeltaAmount is set based on the amount of tokens + // transferred to the orderVault + initial_collateral_delta_amount = order_vault + .record_transfer_in(params.initial_collateral_token); + if (params.initial_collateral_token == fee_token) { + if (initial_collateral_delta_amount < params.execution_fee) { + OrderError::INSUFFICIENT_WNT_AMOUNT_FOR_EXECUTION_FEE( + initial_collateral_delta_amount, params.execution_fee + ); + } + initial_collateral_delta_amount -= params.execution_fee; + should_record_separate_execution_fee_transfer = false; + } + } else if (params.order_type == OrderType::MarketDecrease + || params.order_type == OrderType::LimitDecrease + || params.order_type == OrderType::StopLossDecrease) { + // for decrease orders, the initialCollateralDeltaAmount is based on the passed in value + initial_collateral_delta_amount = params.initial_collateral_delta_amount; + } else { + OrderError::ORDER_TYPE_CANNOT_BE_CREATED(params.order_type); + } + + if (should_record_separate_execution_fee_transfer) { + let fee_token_amount = order_vault.record_transfer_in(fee_token); + if (fee_token_amount < params.execution_fee) { + OrderError::INSUFFICIENT_WNT_AMOUNT_FOR_EXECUTION_FEE( + fee_token_amount, params.execution_fee + ); + } + params.execution_fee = fee_token_amount; + } + + if (base_order_utils::is_position_order(params.order_type)) { + market_utils::validate_position_market(data_store, params.market); + } + + // validate swap path markets + market_utils::validate_swap_path(data_store, params.swap_path); + let key = nonce_utils::get_next_key(data_store); + + let mut order = Order { + key: key, + order_type: params.order_type, + decrease_position_swap_type: params.decrease_position_swap_type, + account, + receiver: params.receiver, + callback_contract: params.callback_contract, + ui_fee_receiver: params.ui_fee_receiver, + market: params.market, + initial_collateral_token: params.initial_collateral_token, + swap_path: params.swap_path, + size_delta_usd: params.size_delta_usd, + initial_collateral_delta_amount, + trigger_price: params.trigger_price, + acceptable_price: params.acceptable_price, + execution_fee: params.execution_fee, + callback_gas_limit: params.callback_gas_limit, + min_output_amount: params.min_output_amount, + /// The block at which the order was last updated. + updated_at_block: 0, + is_long: params.is_long, + /// Whether the order is frozen. + is_frozen: false, + }; + + account_utils::validate_receiver(order.receiver); + + callback_utils::validate_callback_gas_limit(data_store, order.callback_gas_limit); + + let estimated_gas_limit = gas_utils::estimate_execute_order_gas_limit( + data_store, @order + ); + gas_utils::validate_execution_fee(data_store, estimated_gas_limit, order.execution_fee); + + order.touch(); + + base_order_utils::validate_non_empty_order(@order); + data_store.set_order(key, order); + + event_emitter.emit_order_created(key, order); + + key + } + + /// Executes an order. + /// # Arguments + /// * `params` - The parameters used to execute the order. + fn execute_order_utils(ref self: ContractState, params: ExecuteOrderParams) { + // 63/64 gas is forwarded to external calls, reduce the startingGas to account for this + // TODO GAS NOT AVAILABLE params.startingGas -= gasleft() / 63; + params.contracts.data_store.remove_order(params.key, params.order.account); + + base_order_utils::validate_non_empty_order(@params.order); + + base_order_utils::validate_order_trigger_price( + params.contracts.oracle, + params.market.index_token, + params.order.order_type, + params.order.trigger_price, + params.order.is_long + ); + let params_process = ExecuteOrderParams { + contracts: params.contracts, + key: params.key, + order: params.order, + swap_path_markets: params.swap_path_markets.clone(), + min_oracle_block_numbers: params.min_oracle_block_numbers.clone(), + max_oracle_block_numbers: params.max_oracle_block_numbers.clone(), + market: params.market, + keeper: params.keeper, + starting_gas: params.starting_gas, + secondary_order_type: params.secondary_order_type + }; + + // let mut event_data: LogData = self.process_order(params_process); //TODO LogData return value + self.process_order(params_process); + // validate that internal state changes are correct before calling + // external callbacks + // 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 + + if (params.market.market_token != contract_address_const::<0>()) { + market_utils::validate_market_token_balance_check( + params.contracts.data_store, params.market + ); + } + market_utils::validate_market_token_balance_array( + params.contracts.data_store, params.swap_path_markets + ); + + params + .contracts + .event_emitter + .emit_order_executed(params.key, params.secondary_order_type); + // callback_utils::after_order_execution(params.key, params.order, event_data); + + // the order.executionFee for liquidation / adl orders is zero + // gas costs for liquidations / adl is subsidised by the treasury + // TODO crashing + // gas_utils::pay_execution_fee_order( + // params.contracts.data_store, + // params.contracts.event_emitter, + // params.contracts.order_vault, + // params.order.execution_fee, + // params.starting_gas, + // params.keeper, + // params.order.account + // ); + } + + /// Process an order execution. + /// # Arguments + /// * `params` - The parameters used to process the order. + fn process_order(ref self: ContractState, params: ExecuteOrderParams) { + if (base_order_utils::is_increase_order(params.order.order_type)) { + self.increase_order_utils_lib.read().process_order(params); + } else if (base_order_utils::is_decrease_order(params.order.order_type)) { + self.decrease_order_utils_lib.read().process_order(params); + } else if (base_order_utils::is_swap_order(params.order.order_type)) { + self.swap_order_utils_lib.read().process_order(params); + } else { + panic_with_felt252(OrderError::UNSUPPORTED_ORDER_TYPE) + } + } + + /// Cancels an order. + /// # Arguments + /// * `data_store` - The `DataStore` contract dispatcher. + /// * `event_emitter` - The `EventEmitter` contract dispatcher. + /// * `order_vault` - The `OrderVault` contract dispatcher. + /// * `key` - The key of the order to cancel + /// * `keeper` - The keeper sending the transaction. + /// * `starting_gas` - The starting gas of the transaction. + /// * `reason` - The reason for cancellation. + /// # Returns + /// Return the key of the created order. + fn cancel_order( + ref self: ContractState, + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + order_vault: IOrderVaultDispatcher, + key: felt252, + keeper: ContractAddress, + starting_gas: u256, + reason: felt252, + reason_bytes: Array + ) { + // 63/64 gas is forwarded to external calls, reduce the startingGas to account for this + // starting_gas -= gas_left() / 63; + + let order = data_store.get_order(key); + base_order_utils::validate_non_empty_order(@order); + + data_store.remove_order(key, order.account); + + if (base_order_utils::is_increase_order(order.order_type) + || base_order_utils::is_swap_order(order.order_type)) { + if (order.initial_collateral_delta_amount > 0) { + order_vault + .transfer_out( + order_vault.contract_address, + order.initial_collateral_token, + order.account, + order.initial_collateral_delta_amount, + ); + } + } + + event_emitter.emit_order_cancelled(key, reason, reason_bytes.span()); + + // let mut event_data: LogData = Default::default(); + // callback_utils::after_order_cancellation(key, order, event_data); + + gas_utils::pay_execution_fee_order( + data_store, + event_emitter, + order_vault, + order.execution_fee, + starting_gas, + keeper, + order.account + ); + } + + /// Freezes an order. + /// # Arguments + /// * `data_store` - The `DataStore` contract dispatcher. + /// * `event_emitter` - The `EventEmitter` contract dispatcher. + /// * `order_vault` - The `OrderVault` contract dispatcher. + /// * `key` - The key of the order to freeze + /// * `keeper` - The keeper sending the transaction. + /// * `starting_gas` - The starting gas of the transaction. + /// * `reason` - The reason the order was frozen. + /// # Returns + /// Return the key of the created order. + fn freeze_order( + ref self: ContractState, + data_store: IDataStoreDispatcher, + event_emitter: IEventEmitterDispatcher, + order_vault: IOrderVaultDispatcher, + key: felt252, + keeper: ContractAddress, + starting_gas: u256, + reason: felt252, + reason_bytes: Array + ) { + // 63/64 gas is forwarded to external calls, reduce the startingGas to account for this + // startingGas -= gas_left() / 63; + + let mut order = data_store.get_order(key); + base_order_utils::validate_non_empty_order(@order); + + if (order.is_frozen) { + panic_with_felt252(OrderError::ORDER_ALREADY_FROZEN) + } + + let execution_fee = order.execution_fee; + + order.execution_fee = 0; + order.is_frozen = true; + data_store.set_order(key, order); + + event_emitter.emit_order_frozen(key, reason, reason_bytes.span()); + + // let mut event_data: LogData = Default::default(); + // callback_utils::after_order_frozen(key, order, event_data); + + gas_utils::pay_execution_fee_order( + data_store, + event_emitter, + order_vault, + execution_fee, + starting_gas, + keeper, + order.account + ); + } + } } diff --git a/src/order/order_vault.cairo b/src/order/order_vault.cairo index 38a4fb2b..1d2395fe 100644 --- a/src/order/order_vault.cairo +++ b/src/order/order_vault.cairo @@ -19,15 +19,26 @@ trait IOrderVault { /// * `receiver` - The address of the receiver. /// * `amount` - The amount of tokens to transfer. fn transfer_out( - ref self: TContractState, token: ContractAddress, receiver: ContractAddress, amount: u128, + ref self: TContractState, + sender: ContractAddress, + token: ContractAddress, + receiver: ContractAddress, + amount: u256, ); - /// Records a token transfer into the contract. /// # Arguments /// * `token` - The token address to transfer. /// # Returns /// * The amount of tokens transferred. - fn record_transfer_in(ref self: TContractState, token: ContractAddress) -> u128; + fn record_transfer_in(ref self: TContractState, token: ContractAddress) -> u256; + /// 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. + /// # Arguments + /// * `token` - The token to record the burn for. + /// # Returns + /// * The new balance. + fn sync_token_balance(ref self: TContractState, token: ContractAddress) -> u256; } #[starknet::contract] @@ -39,10 +50,9 @@ mod OrderVault { // Core lib imports. use starknet::{get_caller_address, ContractAddress, contract_address_const}; - use debug::PrintTrait; - // Local imports. use satoru::bank::strict_bank::{StrictBank, IStrictBank}; + use debug::PrintTrait; // ************************************************************************* // STORAGE @@ -70,19 +80,27 @@ mod OrderVault { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl OrderVaultImpl of super::IOrderVault { fn transfer_out( ref self: ContractState, + sender: ContractAddress, token: ContractAddress, receiver: ContractAddress, - amount: u128, - ) { // TODO + amount: u256, + ) { + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::transfer_out(ref state, sender, token, receiver, amount); + } + + fn sync_token_balance(ref self: ContractState, token: ContractAddress) -> u256 { + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::sync_token_balance(ref state, token) } - fn record_transfer_in(ref self: ContractState, token: ContractAddress) -> u128 { - // TODO - 0 + fn record_transfer_in(ref self: ContractState, token: ContractAddress) -> u256 { + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::record_transfer_in(ref state, token) } } } diff --git a/src/order/swap_order_utils.cairo b/src/order/swap_order_utils.cairo index 702d1068..91abf4fa 100644 --- a/src/order/swap_order_utils.cairo +++ b/src/order/swap_order_utils.cairo @@ -1,28 +1,144 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + // Core lib imports. -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; // Local imports. use satoru::order::base_order_utils::ExecuteOrderParams; use satoru::order::order::OrderType; +use satoru::oracle::oracle_utils; +use satoru::utils::arrays::are_gte_u64; +use satoru::swap::swap_utils; +use satoru::event::event_utils::{ + Felt252IntoContractAddress, ContractAddressDictValue, I256252DictValue, U256252DictValue, + U256IntoFelt252 +}; +use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; +use satoru::order::error::OrderError; +use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; +use satoru::utils::span32::{Span32, DefaultSpan32}; +use satoru::oracle::error::OracleError; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + +// ************************************************************************* +// Interface of the `OrderUtils` contract. +// ************************************************************************* +#[starknet::interface] +trait ISwapOrderUtils { + fn process_order(ref self: TContractState, params: ExecuteOrderParams); -// 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) { //TODO + /// Validate the oracle block numbers used for the prices in the oracle. + /// # Arguments + /// * `min_oracle_block_numbers` - The min oracle block numbers. + /// * `max_oracle_block_numbers` - The max oracle block numbers. + /// * `order_type` - The order type. + /// * `order_updated_at_block` - the block at which the order was last updated. + fn validate_oracle_block_numbers( + ref self: TContractState, + min_oracle_block_numbers: Span, + max_oracle_block_numbers: Span, + order_type: OrderType, + order_updated_at_block: u64 + ); } +#[starknet::contract] +mod SwapOrderUtils { + // Core lib imports. + use starknet::{ContractAddress, contract_address_const}; + + use debug::PrintTrait; + + // Local imports. + use satoru::order::base_order_utils::ExecuteOrderParams; + use satoru::order::order::OrderType; + use satoru::oracle::oracle_utils; + use satoru::utils::arrays::are_gte_u64; + use satoru::swap::swap_utils; + use satoru::event::event_utils::{ + Felt252IntoContractAddress, ContractAddressDictValue, I256252DictValue, U256252DictValue, + U256IntoFelt252 + }; + use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; + use satoru::order::error::OrderError; + use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; + use satoru::utils::span32::{Span32, DefaultSpan32}; + use satoru::oracle::error::OracleError; + use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + + #[storage] + struct Storage {} + + // ************************************************************************* + // EXTERNAL FUNCTIONS + // ************************************************************************* + #[abi(embed_v0)] + impl SwapOrderUtilsImpl of super::ISwapOrderUtils { + fn process_order(ref self: ContractState, params: ExecuteOrderParams) { + if (params.order.market.is_non_zero()) { + panic(array![OrderError::UNEXPECTED_MARKET]); + } + // validate_oracle_block_numbers( + // params.min_oracle_block_numbers.span(), + // params.max_oracle_block_numbers.span(), + // params.order.order_type, + // params.order.updated_at_block + // ); + let (output_token, output_amount) = swap_utils::swap( + @swap_utils::SwapParams { + data_store: params.contracts.data_store, + event_emitter: params.contracts.event_emitter, + oracle: params.contracts.oracle, + bank: IBankDispatcher { + contract_address: params.contracts.order_vault.contract_address + }, + key: params.key, + token_in: params.order.initial_collateral_token, + amount_in: params.order.initial_collateral_delta_amount, + swap_path_markets: params.swap_path_markets.span(), + min_output_amount: params.order.min_output_amount, + receiver: params.order.receiver, + ui_fee_receiver: params.order.ui_fee_receiver, + } + ); + // let mut log_data: LogData = Default::default(); + + // log_data.address_dict.insert_single('output_token', output_token); + // log_data.uint_dict.insert_single('output_amount', output_amount); + + // log_data + } -/// Validate the oracle block numbers used for the prices in the oracle. -/// # Arguments -/// * `min_oracle_block_numbers` - The min oracle block numbers. -/// * `max_oracle_block_numbers` - The max oracle block numbers. -/// * `order_type` - The order type. -/// * `order_updated_at_block` - the block at which the order was last updated. -#[inline(always)] -fn validate_oracle_block_numbers( - min_oracle_block_numbers: Array, - max_oracle_block_numbers: Array, - order_type: OrderType, - order_updated_at_block: u128 -) { //TODO + /// Validate the oracle block numbers used for the prices in the oracle. + /// # Arguments + /// * `min_oracle_block_numbers` - The min oracle block numbers. + /// * `max_oracle_block_numbers` - The max oracle block numbers. + /// * `order_type` - The order type. + /// * `order_updated_at_block` - the block at which the order was last updated. + fn validate_oracle_block_numbers( + ref self: ContractState, + min_oracle_block_numbers: Span, + max_oracle_block_numbers: Span, + order_type: OrderType, + order_updated_at_block: u64 + ) { + if (order_type == OrderType::MarketSwap) { + oracle_utils::validate_block_number_within_range( + min_oracle_block_numbers, max_oracle_block_numbers, order_updated_at_block + ); + return; + } + if (order_type == OrderType::LimitSwap) { + if (!are_gte_u64(min_oracle_block_numbers, order_updated_at_block)) { + OracleError::ORACLE_BLOCK_NUMBERS_ARE_SMALLER_THAN_REQUIRED( + min_oracle_block_numbers, order_updated_at_block + ); + } + return; + } + panic(array![OrderError::UNSUPPORTED_ORDER_TYPE]); + } + } } diff --git a/src/position/decrease_position_collateral_utils.cairo b/src/position/decrease_position_collateral_utils.cairo index edceac54..35f2a1fa 100644 --- a/src/position/decrease_position_collateral_utils.cairo +++ b/src/position/decrease_position_collateral_utils.cairo @@ -4,125 +4,533 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; use result::ResultTrait; - // Local imports. -use satoru::position::position_utils::{ - DecreasePositionCollateralValues, UpdatePositionParams, DecreasePositionCache, - DecreasePositionCollateralValuesOutput -}; -use satoru::pricing::position_pricing_utils::{ - PositionFees, PositionBorrowingFees, PositionFundingFees, PositionReferralFees, PositionUiFees, -}; -use satoru::market::market_utils::MarketPrices; -use satoru::price::price::Price; +use satoru::position::{position_utils, decrease_position_swap_utils, error}; +use satoru::pricing::position_pricing_utils; +use satoru::market::market_utils; +use satoru::price::price::{Price, PriceTrait}; +use satoru::order::{base_order_utils, order}; +use satoru::utils::{i256::{i256, i256_neg, i256_new}, calc, precision}; +use satoru::data::{keys, data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::fee::fee_utils; +use debug::PrintTrait; /// Struct used in process_collateral function as cache. -#[derive(Drop, starknet::Store, Serde)] +#[derive(Drop, starknet::Store, Serde, Default, Copy)] struct ProcessCollateralCache { /// Wether an insolvent close is allowed or not. is_insolvent_close_allowed: bool, /// Wether profit is swapped to collateral token. was_swapped: bool, /// The amount swapped to collateral token. - swap_output_amount: u128, + swap_output_amount: u256, /// The output result after paying for costs. result: PayForCostResult, } /// Struct to store pay_for_cost function returned result. -#[derive(Drop, starknet::Store, Serde)] +#[derive(Drop, starknet::Store, Serde, Default, Copy)] struct PayForCostResult { /// The amount of collateral token paid as cost. - amount_paid_in_collateral_token: u128, + amount_paid_in_collateral_token: u256, /// The amount of secondary output token paid as cost. - amount_paid_in_secondary_output_token: u128, + amount_paid_in_secondary_output_token: u256, /// The amount of remaining cost in USD - remaining_cost_usd: u128, + remaining_cost_usd: u256, } /// Struct used in get_execution_price function as cache. -#[derive(Drop, starknet::Store, Serde)] +#[derive(Drop, starknet::Store, Serde, Default)] struct GetExecutionPriceCache { /// The price impact induced by execution. - price_impact_usd: u128, // TODO replace with i128 when it derives Store + price_impact_usd: i256, /// The difference between maximum price impact and originally calculated price impact. - priceImpactDiffUsd: u128, + price_impact_diff_usd: u256, /// The execution price. - execution_price: u128, + execution_price: u256, } /// Handle the collateral changes of the position. /// # Returns -/// (DecreasePositionCollateralValues, PositionFees) -#[inline(always)] +/// The values linked to the process of a decrease of collateral and position fees. fn process_collateral( - params: UpdatePositionParams, cache: DecreasePositionCache -) -> (DecreasePositionCollateralValues, PositionFees) { - // TODO - let address_zero: ContractAddress = 0.try_into().unwrap(); - let decrease_position_collateral_values_output = DecreasePositionCollateralValuesOutput { - output_token: address_zero, - output_amount: 0, - secondary_output_token: address_zero, - secondary_output_amount: 0, - }; - let decrease_position_collateral_values = DecreasePositionCollateralValues { - execution_price: 0, - remaining_collateral_amount: 0, - base_pnl_usd: 0, - uncapped_base_pnl_usd: 0, - size_delta_in_tokens: 0, - price_impact_usd: 0, - price_impact_diff_usd: 0, - output: decrease_position_collateral_values_output - }; - let position_referral_fees = PositionReferralFees { - referral_code: 0, - affiliate: address_zero, - trader: address_zero, - total_rebate_factor: 0, - trader_discount_factor: 0, - total_rebate_amount: 0, - trader_discount_amount: 0, - affiliate_reward_amount: 0, - }; - let position_funding_fees = PositionFundingFees { - funding_fee_amount: 0, - claimable_long_token_amount: 0, - claimable_short_token_amount: 0, - latest_funding_fee_amount_per_size: 0, - latest_long_token_claimable_funding_amount_per_size: 0, - latest_short_token_claimable_funding_amount_per_size: 0, - }; - let position_borrowing_fees = PositionBorrowingFees { - borrowing_fee_usd: 0, - borrowing_fee_amount: 0, - borrowing_fee_receiver_factor: 0, - borrowing_fee_amount_for_fee_receiver: 0, - }; - let position_ui_fees = PositionUiFees { - ui_fee_receiver: address_zero, ui_fee_receiver_factor: 0, ui_fee_amount: 0, + mut params: position_utils::UpdatePositionParams, cache: position_utils::DecreasePositionCache +) -> (position_utils::DecreasePositionCollateralValues, position_pricing_utils::PositionFees) { + let mut collateral_cache: ProcessCollateralCache = Default::default(); + let mut values: position_utils::DecreasePositionCollateralValues = Default::default(); + values.output.output_token = params.position.collateral_token; + values.output.secondary_output_token = cache.pnl_token; + + // only allow insolvent closing if it is a liquidation or ADL order + // is_insolvent_close_allowed is used in handleEarlyReturn to determine + // whether the txn should revert if the remainingCostUsd is below zero + // + // for is_insolvent_close_allowed to be true, the size_delta_usd must equal + // the position size, otherwise there may be pending positive pnl that + // could be used to pay for fees and the position would be undercharged + // if the position is not fully closed + // + // for ADLs it may be possible that a position needs to be closed by a larger + // size to fully pay for fees, but closing by that larger size could cause a PnlOvercorrected + // error to be thrown in AdlHandler, this case should be rare + collateral_cache + .is_insolvent_close_allowed = params + .order + .size_delta_usd == params + .position + .size_in_usd + && (base_order_utils::is_liquidation_order(params.order.order_type) + || params.secondary_order_type == order::SecondaryOrderType::Adl(())); + // in case price impact is too high it is capped and the difference is made to be claimable + // the execution price is based on the capped price impact so it may be a better price than what it should be + // price_impact_diff_usd is the difference between the maximum price impact and the originally calculated price impact + // e.g. if the originally calculated price impact is -$100, but the capped price impact is -$80 + // then priceImpactDiffUsd would be $20 + + //TODO uncomment this and should calculate price_impact_usd_ etc.. + // let (price_impact_usd_, price_impact_diff_usd_, execution_price_) = get_execution_price( + // params, cache.prices.index_token_price + // ); + let (price_impact_usd_, price_impact_diff_usd_, execution_price_) = (i256_new(0, false), 0, 0); + + values.price_impact_usd = price_impact_usd_; + values.price_impact_diff_usd = price_impact_diff_usd_; + values.execution_price = execution_price_; + // the total_position_pnl is calculated based on the current indexTokenPrice instead of the executionPrice + // since the executionPrice factors in price impact which should be accounted for separately + // the sizeDeltaInTokens is calculated as position.size_in_tokens() * size_delta_usd / position.size_in_usd() + // the basePnlUsd is the pnl to be realized, and is calculated as: + // total_position_pnl * size_delta_in_tokens / position.size_in_tokens() + let (base_pnl_usd_, uncapped_base_pnl_usd_, size_delta_in_tokens_) = + position_utils::get_position_pnl_usd( + params.contracts.data_store, + params.market, + cache.prices, + params.position, + params.order.size_delta_usd + ); + values.base_pnl_usd = base_pnl_usd_; + values.uncapped_base_pnl_usd = uncapped_base_pnl_usd_; + values.size_delta_in_tokens = size_delta_in_tokens_; + + let get_position_fees_params: position_pricing_utils::GetPositionFeesParams = + position_pricing_utils::GetPositionFeesParams { + data_store: params.contracts.data_store, + referral_storage: params.contracts.referral_storage, + position: params.position, + collateral_token_price: cache.collateral_token_price, + for_positive_impact: values.price_impact_usd > Zeroable::zero(), + long_token: params.market.long_token, + short_token: params.market.short_token, + size_delta_usd: params.order.size_delta_usd, + ui_fee_receiver: params.order.ui_fee_receiver, }; - let price = Price { min: 0, max: 0, }; - let position_fees = PositionFees { - referral: position_referral_fees, - funding: position_funding_fees, - borrowing: position_borrowing_fees, - ui: position_ui_fees, - collateral_token_price: price, - position_fee_factor: 0, - protocol_fee_amount: 0, - position_fee_receiver_factor: 0, - fee_receiver_amount: 0, - fee_amount_for_pool: 0, - position_fee_amount_for_pool: 0, - position_fee_amount: 0, - total_cost_amount_excluding_funding: 0, - total_cost_amount: 0, + let mut fees: position_pricing_utils::PositionFees = position_pricing_utils::get_position_fees( + get_position_fees_params + ); + + // if the pnl is positive, deduct the pnl amount from the pool + if values.base_pnl_usd > Zeroable::zero() { + // use pnl_token_price.max to minimize the tokens paid out + let deduction_amount_for_pool: u256 = calc::to_unsigned(values.base_pnl_usd) + / cache.pnl_token_price.max; + + market_utils::apply_delta_to_pool_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market, + cache.pnl_token, + calc::to_signed(deduction_amount_for_pool, false) + ); + + if values.output.output_token == cache.pnl_token { + values.output.output_amount += deduction_amount_for_pool; + } else { + values.output.secondary_output_amount += deduction_amount_for_pool; + } + } + + if values.price_impact_usd > Zeroable::zero() { + // use indexTokenPrice.min to maximize the position impact pool reduction + let deduction_amount_for_impact_pool = calc::roundup_division( + calc::to_unsigned(values.price_impact_usd), cache.prices.index_token_price.min + ); + + market_utils::apply_delta_to_position_impact_pool( + params.contracts.data_store, + params.contracts.event_emitter, + params.market.market_token, + calc::to_signed(deduction_amount_for_impact_pool, false) + ); + + // use pnlTokenPrice.max to minimize the payout from the pool + // some impact pool value may be transferred to the market token pool if there is a + // large spread between min and max prices + // since if there is a positive priceImpactUsd, the impact pool would be reduced using indexTokenPrice.min to + // maximize the deduction value, while the market token pool is reduced using the pnlTokenPrice.max to minimize + // the deduction value + // the pool value is calculated by subtracting the worth of the tokens in the position impact pool + // so this transfer of value would increase the price of the market token + let deduction_amount_for_pool: u256 = calc::to_unsigned(values.price_impact_usd) + / cache.pnl_token_price.max; + + market_utils::apply_delta_to_pool_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market, + cache.pnl_token, + calc::to_signed(deduction_amount_for_pool, false) + ); + + if values.output.output_token == cache.pnl_token { + values.output.output_amount += deduction_amount_for_pool; + } else { + values.output.secondary_output_amount += deduction_amount_for_pool; + } + } + + // swap profit to the collateral token + // if the decreasePositionSwapType was set to NoSwap or if the swap fails due + // to insufficient liquidity or other reasons then it is possible that + // the profit remains in a different token from the collateral token + let (was_swapped_, swap_output_amount_) = + decrease_position_swap_utils::swap_profit_to_collateral_token( + params, cache.pnl_token, values.output.secondary_output_amount + ); + collateral_cache.was_swapped = was_swapped_; + collateral_cache.swap_output_amount = swap_output_amount_; + + // if the swap was successful the profit should have been swapped + // to the collateral token + if collateral_cache.was_swapped { + values.output.output_amount += collateral_cache.swap_output_amount; + values.output.secondary_output_amount = 0; + } + + values.remaining_collateral_amount = params.position.collateral_amount; + + // pay for funding fees + let (values_, result_) = pay_for_cost( + params, + values, + cache.prices, + cache.collateral_token_price, + // use collateralTokenPrice.min because the payForCost + // will divide the USD value by the price.min as well + fees.funding.funding_fee_amount * cache.collateral_token_price.min + ); + values = values_; + collateral_cache.result = result_; + if collateral_cache.result.amount_paid_in_secondary_output_token > 0 { + let holding_address: ContractAddress = params + .contracts + .data_store + .get_address(keys::holding_address()); + + if holding_address.is_zero() { + panic_with_felt252(error::PositionError::EMPTY_HOLDING_ADDRESS); + } + + // send the funding fee amount to the holding address + // this funding fee amount should be swapped to the required token + // and the resulting tokens should be deposited back into the pool + market_utils::increment_claimable_collateral_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market.market_token, + values.output.secondary_output_token, + holding_address, + collateral_cache.result.amount_paid_in_secondary_output_token + ); + } + + if collateral_cache.result.amount_paid_in_collateral_token < fees.funding.funding_fee_amount { + // the case where this is insufficient collateral to pay funding fees + // should be rare, and the difference should be small + // in case it happens, the pool should be topped up with the required amount using + // the claimable amount sent to the holding address, an insurance fund, or similar mechanism + params + .contracts + .event_emitter + .emit_insufficient_funding_fee_payment( + params.market.market_token, + params.position.collateral_token, + fees.funding.funding_fee_amount, + collateral_cache.result.amount_paid_in_collateral_token, + collateral_cache.result.amount_paid_in_secondary_output_token + ); + } + + if collateral_cache.result.remaining_cost_usd > 0 { + return handle_early_return(params, @values, fees, collateral_cache, 'funding'); }; - (decrease_position_collateral_values, position_fees) + + // pay for negative pnl + if values.base_pnl_usd < Zeroable::zero() { + let (values_, result_) = pay_for_cost( + params, + values, + cache.prices, + cache.collateral_token_price, + calc::to_unsigned(i256_neg(values.base_pnl_usd)) + ); + values = values_; + collateral_cache.result = result_; + + if collateral_cache.result.amount_paid_in_collateral_token > 0 { + market_utils::apply_delta_to_pool_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market, + params.position.collateral_token, + calc::to_signed(collateral_cache.result.amount_paid_in_collateral_token, true) + ); + } + + if collateral_cache.result.amount_paid_in_secondary_output_token > 0 { + market_utils::apply_delta_to_pool_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market, + values.output.secondary_output_token, + calc::to_signed(collateral_cache.result.amount_paid_in_secondary_output_token, true) + ); + } + + if collateral_cache.result.remaining_cost_usd > 0 { + return handle_early_return(params, @values, fees, collateral_cache, 'pnl'); + } + } + + // pay for fees + let (values_, result_) = pay_for_cost( + params, + values, + cache.prices, + cache.collateral_token_price, + // use collateral_token_price.min because the pay_for_cost + // will divide the USD value by the price.min as well + fees.total_cost_amount_excluding_funding * cache.collateral_token_price.min + ); + values = values_; + collateral_cache.result = result_; + + // if fees were fully paid in the collateral token, update the pool and claimable fee amounts + if collateral_cache.result.remaining_cost_usd == 0 + && collateral_cache.result.amount_paid_in_secondary_output_token == 0 { + // there may be a large amount of borrowing fees that could have been accumulated + // these fees could cause the pool to become unbalanced, price impact is not paid for causing + // this imbalance + // the swap impact pool should be built up so that it can be used to pay for positive price impact + // for re-balancing to help handle this case + market_utils::apply_delta_to_pool_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market, + params.position.collateral_token, + calc::to_signed(fees.fee_amount_for_pool, true) + ); + + fee_utils::increment_claimable_fee_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market.market_token, + params.position.collateral_token, + fees.fee_receiver_amount, + keys::position_fee_type() + ); + + fee_utils::increment_claimable_ui_fee_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.order.ui_fee_receiver, + params.market.market_token, + params.position.collateral_token, + fees.ui.ui_fee_amount, + keys::ui_position_fee_type() + ); + } else { + // the fees are expected to be paid in the collateral token + // if there are insufficient funds to pay for fees entirely in the collateral token + // then credit the fee amount entirely to the pool + if collateral_cache.result.amount_paid_in_collateral_token > 0 { + market_utils::apply_delta_to_pool_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market, + params.position.collateral_token, + calc::to_signed(collateral_cache.result.amount_paid_in_collateral_token, true) + ); + } + + if collateral_cache.result.amount_paid_in_secondary_output_token > 0 { + market_utils::apply_delta_to_pool_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market, + values.output.secondary_output_token, + calc::to_signed(collateral_cache.result.amount_paid_in_secondary_output_token, true) + ); + } + + // empty the fees since the amount was entirely paid to the pool instead of for fees + // it is possible for the txn execution to still complete even in this case + // as long as the remainingCostUsd is still zero + fees = get_empty_fees(@fees); + } + + if collateral_cache.result.remaining_cost_usd > 0 { + return handle_early_return(params, @values, fees, collateral_cache, 'fees'); + } + + // pay for negative price impact + if values.price_impact_usd < Zeroable::zero() { + let (values_, result_) = pay_for_cost( + params, + values, + cache.prices, + cache.collateral_token_price, + calc::to_unsigned(i256_neg(values.price_impact_usd)) + ); + values = values_; + collateral_cache.result = result_; + + if collateral_cache.result.amount_paid_in_collateral_token > 0 { + market_utils::apply_delta_to_pool_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market, + params.position.collateral_token, + calc::to_signed(collateral_cache.result.amount_paid_in_collateral_token, true) + ); + + market_utils::apply_delta_to_position_impact_pool( + params.contracts.data_store, + params.contracts.event_emitter, + params.market.market_token, + calc::to_signed( + collateral_cache.result.amount_paid_in_collateral_token + * cache.collateral_token_price.min + / cache.prices.index_token_price.max, + true + ) + ); + } + + if collateral_cache.result.amount_paid_in_secondary_output_token > 0 { + market_utils::apply_delta_to_pool_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market, + values.output.secondary_output_token, + calc::to_signed(collateral_cache.result.amount_paid_in_secondary_output_token, true) + ); + + market_utils::apply_delta_to_position_impact_pool( + params.contracts.data_store, + params.contracts.event_emitter, + params.market.market_token, + calc::to_signed( + collateral_cache.result.amount_paid_in_secondary_output_token + * cache.pnl_token_price.min + / cache.prices.index_token_price.max, + true + ) + ); + } + + if collateral_cache.result.remaining_cost_usd > 0 { + return handle_early_return(params, @values, fees, collateral_cache, 'impact'); + } + } + + // pay for price impact diff + if values.price_impact_diff_usd > 0 { + let (values_, result_) = pay_for_cost( + params, values, cache.prices, cache.collateral_token_price, values.price_impact_diff_usd + ); + values = values_; + collateral_cache.result = result_; + + if collateral_cache.result.amount_paid_in_collateral_token > 0 { + market_utils::increment_claimable_collateral_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market.market_token, + params.position.collateral_token, + params.order.account, + collateral_cache.result.amount_paid_in_collateral_token + ); + } + + if collateral_cache.result.amount_paid_in_secondary_output_token > 0 { + market_utils::increment_claimable_collateral_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market.market_token, + values.output.secondary_output_token, + params.order.account, + collateral_cache.result.amount_paid_in_secondary_output_token + ); + } + + if collateral_cache.result.remaining_cost_usd > 0 { + return handle_early_return(params, @values, fees, collateral_cache, 'diff'); + } + } + + // the priceImpactDiffUsd has been deducted from the output amount or the position's collateral + // to reduce the chance that the position's collateral is reduced by an unexpected amount, adjust the + // initialCollateralDeltaAmount by the priceImpactDiffAmount + // this would also help to prevent the position's leverage from being unexpectedly increased + // + // note that this calculation may not be entirely accurate since it is possible that the priceImpactDiffUsd + // could have been paid with one of or a combination of collateral / outputAmount / secondaryOutputAmount + if params.order.initial_collateral_delta_amount > 0 && values.price_impact_diff_usd > 0 { + let initial_collateral_delta_amount: u256 = params.order.initial_collateral_delta_amount; + + let price_impact_diff_amount: u256 = values.price_impact_diff_usd + / cache.collateral_token_price.min; + if initial_collateral_delta_amount > price_impact_diff_amount { + params.order.initial_collateral_delta_amount = initial_collateral_delta_amount + - price_impact_diff_amount; + } else { + params.order.initial_collateral_delta_amount = 0; + } + + params + .contracts + .event_emitter + .emit_order_collateral_delta_amount_auto_updated( + params.order_key, + initial_collateral_delta_amount, // collateral_delta_amount + params.order.initial_collateral_delta_amount // next_collateral_delta_amount + ); + } + + // cap the withdrawable amount to the remainingCollateralAmount + if params.order.initial_collateral_delta_amount > values.remaining_collateral_amount { + params + .contracts + .event_emitter + .emit_order_collateral_delta_amount_auto_updated( + params.order_key, + params.order.initial_collateral_delta_amount, // collateral_delta_amount + values.remaining_collateral_amount // next_collateral_delta_amount + ); + + params.order.initial_collateral_delta_amount = values.remaining_collateral_amount; + } + + if params.order.initial_collateral_delta_amount > 0 { + values.remaining_collateral_amount -= params.order.initial_collateral_delta_amount; + values.output.output_amount += params.order.initial_collateral_delta_amount; + } + + (values, fees) } /// Compute execution price of the position update. @@ -131,10 +539,85 @@ fn process_collateral( /// * `index_token_price` - The price of the index token. /// (price_impact_usd, price_impact_diff_usd, execution_price) fn get_execution_price( - params: UpdatePositionParams, index_token_price: Price -) -> (i128, u128, u128) { - // TODO - (0, 0, 0) + params: position_utils::UpdatePositionParams, index_token_price: Price +) -> (i256, u256, u256) { + let size_delta_usd: u256 = params.order.size_delta_usd; + + // note that the executionPrice is not validated against the order.acceptable_price value + // if the size_delta_usd is zero + // for limit orders the order.triggerPrice should still have been validated + if size_delta_usd == 0 { + // decrease order: + // - long: use the smaller price + // - short: use the larger price + return (Zeroable::zero(), 0, index_token_price.pick_price(!params.position.is_long)); + } + + let mut cache: GetExecutionPriceCache = Default::default(); + + cache + .price_impact_usd = + position_pricing_utils::get_price_impact_usd( + position_pricing_utils::GetPriceImpactUsdParams { + data_store: params.contracts.data_store, + market: params.market, + usd_delta: calc::to_signed(size_delta_usd, false), + is_long: params.order.is_long, + } + ); + + // cap priceImpactUsd based on the amount available in the position impact pool + cache + .price_impact_usd = + market_utils::get_capped_position_impact_usd( + params.contracts.data_store, + params.market.market_token, + index_token_price, + cache.price_impact_usd, + size_delta_usd + ); + + if cache.price_impact_usd < Zeroable::zero() { + let max_price_impact_factor: u256 = market_utils::get_max_position_impact_factor( + params.contracts.data_store, params.market.market_token, false + ); + + // convert the max price impact to the min negative value + // e.g. if size_delta_usd is 10,000 and max_price_impact_factor is 2% + // then minPriceImpactUsd = -200 + let min_price_impact_usd: i256 = calc::to_signed( + precision::apply_factor_u256(size_delta_usd, max_price_impact_factor), false + ); + + // cap priceImpactUsd to the min negative value and store the difference in price_impact_diff_usd + // e.g. if price_impact_usd is -500 and min_price_impact_usd is -200 + // then set price_impact_diff_usd to -200 - -500 = 300 + // set priceImpactUsd to -200 + if cache.price_impact_usd < min_price_impact_usd { + cache + .price_impact_diff_usd = + calc::to_unsigned(min_price_impact_usd - cache.price_impact_usd); + cache.price_impact_usd = min_price_impact_usd; + } + } + + // the execution_price is calculated after the price impact is capped + // so the output amount directly received by the user may not match + // the execution_price, the difference would be in the stored as a + // claimable amount + cache + .execution_price = + base_order_utils::get_execution_price_for_decrease( + index_token_price, + params.position.size_in_usd, + params.position.size_in_tokens, + size_delta_usd, + cache.price_impact_usd, + params.order.acceptable_price, + params.position.is_long + ); + + (cache.price_impact_usd, cache.price_impact_diff_usd, cache.execution_price) } /// Pay costs of the position update. @@ -145,38 +628,81 @@ fn get_execution_price( /// * `collateral_token_price` - The prices of the collateral token. /// * `cost_usd` - The total cost in usd. /// # Returns -/// Updated DecreasePositionCollateralValues and output of pay for cost. +/// Updated position_utils::DecreasePositionCollateralValues and output of pay for cost. fn pay_for_cost( - params: UpdatePositionParams, - values: DecreasePositionCollateralValues, - prices: MarketPrices, + params: position_utils::UpdatePositionParams, + mut values: position_utils::DecreasePositionCollateralValues, + prices: market_utils::MarketPrices, collateral_token_price: Price, - cost_usd: u128, -) -> (DecreasePositionCollateralValues, PayForCostResult) { - // TODO - let address_zero: ContractAddress = 0.try_into().unwrap(); - let decrease_position_collateral_values_output = DecreasePositionCollateralValuesOutput { - output_token: address_zero, - output_amount: 0, - secondary_output_token: address_zero, - secondary_output_amount: 0, - }; - let decrease_position_collateral_values = DecreasePositionCollateralValues { - execution_price: 0, - remaining_collateral_amount: 0, - base_pnl_usd: 0, - uncapped_base_pnl_usd: 0, - size_delta_in_tokens: 0, - price_impact_usd: 0, - price_impact_diff_usd: 0, - output: decrease_position_collateral_values_output - }; - let pay_for_cost_result = PayForCostResult { - amount_paid_in_collateral_token: 0, - amount_paid_in_secondary_output_token: 0, - remaining_cost_usd: 0, - }; - (decrease_position_collateral_values, pay_for_cost_result) + cost_usd: u256, +) -> (position_utils::DecreasePositionCollateralValues, PayForCostResult) { + let mut result: PayForCostResult = Default::default(); + + if cost_usd == 0 { + return (values, result); + } + + let mut remaining_cost_in_output_token: u256 = calc::roundup_division( + cost_usd, collateral_token_price.min + ); + + if values.output.output_amount > 0 { + if values.output.output_amount > remaining_cost_in_output_token { + result.amount_paid_in_collateral_token += remaining_cost_in_output_token; + values.output.output_amount -= remaining_cost_in_output_token; + remaining_cost_in_output_token = 0; + } else { + result.amount_paid_in_collateral_token += values.output.output_amount; + remaining_cost_in_output_token -= values.output.output_amount; + values.output.output_amount = 0; + } + } + + if remaining_cost_in_output_token == 0 { + return (values, result); + } + + if (values.remaining_collateral_amount > 0) { + if (values.remaining_collateral_amount > remaining_cost_in_output_token) { + result.amount_paid_in_collateral_token += remaining_cost_in_output_token; + values.remaining_collateral_amount -= remaining_cost_in_output_token; + remaining_cost_in_output_token = 0; + } else { + result.amount_paid_in_collateral_token += values.remaining_collateral_amount; + remaining_cost_in_output_token -= values.remaining_collateral_amount; + values.remaining_collateral_amount = 0; + } + } + + if remaining_cost_in_output_token == 0 { + return (values, result); + } + + let secondary_output_token_price: Price = market_utils::get_cached_token_price( + values.output.secondary_output_token, params.market, prices + ); + + let mut remaining_cost_in_secondary_output_token: u256 = remaining_cost_in_output_token + * collateral_token_price.min + / secondary_output_token_price.min; + + if (values.output.secondary_output_amount > 0) { + if (values.output.secondary_output_amount > remaining_cost_in_secondary_output_token) { + result + .amount_paid_in_secondary_output_token += remaining_cost_in_secondary_output_token; + values.output.secondary_output_amount -= remaining_cost_in_secondary_output_token; + remaining_cost_in_secondary_output_token = 0; + } else { + result.amount_paid_in_secondary_output_token += values.output.secondary_output_amount; + remaining_cost_in_secondary_output_token -= values.output.secondary_output_amount; + values.output.secondary_output_amount = 0; + } + } + + result.remaining_cost_usd = remaining_cost_in_secondary_output_token + * secondary_output_token_price.min; + + (values, result) } /// Handle early return case where there is still remaining costs. @@ -186,121 +712,83 @@ fn pay_for_cost( /// * `fees` - The position fees. /// * `collateral_cache` - The struct used as cache in process_collateral. /// # Returns -/// Updated DecreasePositionCollateralValues and position fees. +/// Updated position_utils::DecreasePositionCollateralValues and position fees. fn handle_early_return( - params: UpdatePositionParams, - values: DecreasePositionCollateralValues, - fees: PositionFees, + params: position_utils::UpdatePositionParams, + values: @position_utils::DecreasePositionCollateralValues, + fees: position_pricing_utils::PositionFees, collateral_cache: ProcessCollateralCache, -) -> (DecreasePositionCollateralValues, PositionFees) { - // TODO - let address_zero: ContractAddress = 0.try_into().unwrap(); - let decrease_position_collateral_values_output = DecreasePositionCollateralValuesOutput { - output_token: address_zero, - output_amount: 0, - secondary_output_token: address_zero, - secondary_output_amount: 0, - }; - let decrease_position_collateral_values = DecreasePositionCollateralValues { - execution_price: 0, - remaining_collateral_amount: 0, - base_pnl_usd: 0, - uncapped_base_pnl_usd: 0, - size_delta_in_tokens: 0, - price_impact_usd: 0, - price_impact_diff_usd: 0, - output: decrease_position_collateral_values_output - }; - let position_referral_fees = PositionReferralFees { - referral_code: 0, - affiliate: address_zero, - trader: address_zero, - total_rebate_factor: 0, - trader_discount_factor: 0, - total_rebate_amount: 0, - trader_discount_amount: 0, - affiliate_reward_amount: 0, - }; - let position_funding_fees = PositionFundingFees { - funding_fee_amount: 0, - claimable_long_token_amount: 0, - claimable_short_token_amount: 0, - latest_funding_fee_amount_per_size: 0, - latest_long_token_claimable_funding_amount_per_size: 0, - latest_short_token_claimable_funding_amount_per_size: 0, - }; - let position_borrowing_fees = PositionBorrowingFees { - borrowing_fee_usd: 0, - borrowing_fee_amount: 0, - borrowing_fee_receiver_factor: 0, - borrowing_fee_amount_for_fee_receiver: 0, - }; - let position_ui_fees = PositionUiFees { - ui_fee_receiver: address_zero, ui_fee_receiver_factor: 0, ui_fee_amount: 0, - }; - let price = Price { min: 0, max: 0, }; - let position_fees = PositionFees { - referral: position_referral_fees, - funding: position_funding_fees, - borrowing: position_borrowing_fees, - ui: position_ui_fees, - collateral_token_price: price, - position_fee_factor: 0, - protocol_fee_amount: 0, - position_fee_receiver_factor: 0, - fee_receiver_amount: 0, - fee_amount_for_pool: 0, - position_fee_amount_for_pool: 0, - position_fee_amount: 0, - total_cost_amount_excluding_funding: 0, - total_cost_amount: 0, - }; - (decrease_position_collateral_values, position_fees) + step: felt252 +) -> (position_utils::DecreasePositionCollateralValues, position_pricing_utils::PositionFees) { + if (!collateral_cache.is_insolvent_close_allowed) { + error::PositionError::INSUFFICIENT_FUNDS_TO_PAY_FOR_COSTS( + collateral_cache.result.remaining_cost_usd, step + ); + } + + params + .contracts + .event_emitter + .emit_position_fees_info( + params.order_key, + params.position_key, + params.market.market_token, + params.position.collateral_token, + params.order.size_delta_usd, + false, // isIncrease + fees + ); + + params + .contracts + .event_emitter + .emit_insolvent_close_info( + params.order_key, + params.position.collateral_amount, + *values.base_pnl_usd, + collateral_cache.result.remaining_cost_usd + ); + + (*values, get_empty_fees(@fees)) } /// Return empty fees struct using fees struct given in parameter. /// Keep useful values such as accumulated funding fees. /// # Arguments -/// * `fees` - The PositionFees struct used to get the new empty struct. +/// * `fees` - The position_pricing_utils::PositionFees struct used to get the new empty struct. /// # Returns -/// An empty PositionFees struct. -fn get_empty_fees(fees: PositionFees) -> PositionFees { - // TODO - let address_zero: ContractAddress = 0.try_into().unwrap(); - let position_referral_fees = PositionReferralFees { - referral_code: 0, - affiliate: address_zero, - trader: address_zero, - total_rebate_factor: 0, - trader_discount_factor: 0, - total_rebate_amount: 0, - trader_discount_amount: 0, - affiliate_reward_amount: 0, - }; - let position_funding_fees = PositionFundingFees { +/// An empty position_pricing_utils::PositionFees struct. +fn get_empty_fees( + fees: @position_pricing_utils::PositionFees +) -> position_pricing_utils::PositionFees { + let referral: position_pricing_utils::PositionReferralFees = Default::default(); + + // allow the accumulated funding fees to still be claimable + // return the latestFundingFeeAmountPerSize, latest_long_token_claimable_funding_amount_per_size, + // latest_short_token_claimable_funding_amount_per_size values as these may be used to update the + // position's values if the position will be partially closed + let funding = position_pricing_utils::PositionFundingFees { funding_fee_amount: 0, - claimable_long_token_amount: 0, - claimable_short_token_amount: 0, - latest_funding_fee_amount_per_size: 0, - latest_long_token_claimable_funding_amount_per_size: 0, - latest_short_token_claimable_funding_amount_per_size: 0, - }; - let position_borrowing_fees = PositionBorrowingFees { - borrowing_fee_usd: 0, - borrowing_fee_amount: 0, - borrowing_fee_receiver_factor: 0, - borrowing_fee_amount_for_fee_receiver: 0, - }; - let position_ui_fees = PositionUiFees { - ui_fee_receiver: address_zero, ui_fee_receiver_factor: 0, ui_fee_amount: 0, + claimable_long_token_amount: *fees.funding.claimable_long_token_amount, + claimable_short_token_amount: *fees.funding.claimable_short_token_amount, + latest_funding_fee_amount_per_size: *fees.funding.latest_funding_fee_amount_per_size, + latest_long_token_claimable_funding_amount_per_size: *fees + .funding + .latest_long_token_claimable_funding_amount_per_size, + latest_short_token_claimable_funding_amount_per_size: *fees + .funding + .latest_short_token_claimable_funding_amount_per_size, }; - let price = Price { min: 0, max: 0, }; - PositionFees { - referral: position_referral_fees, - funding: position_funding_fees, - borrowing: position_borrowing_fees, - ui: position_ui_fees, - collateral_token_price: price, + let borrowing: position_pricing_utils::PositionBorrowingFees = Default::default(); + let ui: position_pricing_utils::PositionUiFees = Default::default(); + // all fees are zeroed even though funding may have been paid + // the funding fee amount value may not be accurate in the events due to this + position_pricing_utils::PositionFees { + referral, + funding, + borrowing, + ui, + collateral_token_price: *fees.collateral_token_price, position_fee_factor: 0, protocol_fee_amount: 0, position_fee_receiver_factor: 0, diff --git a/src/position/decrease_position_swap_utils.cairo b/src/position/decrease_position_swap_utils.cairo index 4c575ad6..df17a6b8 100644 --- a/src/position/decrease_position_swap_utils.cairo +++ b/src/position/decrease_position_swap_utils.cairo @@ -21,7 +21,6 @@ use satoru::swap::swap_utils::{SwapParams}; use satoru::market::market::Market; /// Swap the withdrawn collateral from collateral_token to pnl_token if needed. -#[inline(always)] fn swap_withdrawn_collateral_to_pnl_token( params: UpdatePositionParams, mut values: DecreasePositionCollateralValues ) -> DecreasePositionCollateralValues { @@ -66,10 +65,9 @@ fn swap_withdrawn_collateral_to_pnl_token( /// * `pnl_amount` - The amount of profit in usd. /// # Returns /// DecreasePositionCollateralValues -#[inline(always)] fn swap_profit_to_collateral_token( - params: UpdatePositionParams, pnl_token: ContractAddress, profit_amount: u128 -) -> (bool, u128) { + params: UpdatePositionParams, pnl_token: ContractAddress, profit_amount: u256 +) -> (bool, u256) { let mut swap_path_markets = ArrayTrait::::new(); if (profit_amount > 0 && params diff --git a/src/position/decrease_position_utils.cairo b/src/position/decrease_position_utils.cairo index 9aa4f056..c2538016 100644 --- a/src/position/decrease_position_utils.cairo +++ b/src/position/decrease_position_utils.cairo @@ -4,23 +4,35 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; use result::ResultTrait; // Local imports -use satoru::position::position_utils::UpdatePositionParams; +use satoru::utils::traits::ContractAddressDefault; +use satoru::position::{ + position_utils, decrease_position_collateral_utils, decrease_position_swap_utils, + position_utils::{UpdatePositionParams, DecreasePositionCache} +}; +use satoru::utils::calc::to_signed; +use satoru::data::{data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}, keys}; +use satoru::utils::precision; +use satoru::market::market_utils; +use satoru::order::order::{OrderType, DecreasePositionSwapType}; +use satoru::order::base_order_utils; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::position::error::PositionError; /// Struct used as result for decrease_position_function output. -#[derive(Drop, starknet::Store, Serde)] +#[derive(Drop, Default, Copy, starknet::Store, Serde)] struct DecreasePositionResult { /// The output token address. output_token: ContractAddress, /// The output token amount. - output_amount: u128, + output_amount: u256, /// The secondary output token address. secondary_output_token: ContractAddress, /// The secondary output token amount. - secondary_output_amount: u128, + secondary_output_amount: u256, } /// The decrease_position function decreases the size of an existing position @@ -40,13 +52,293 @@ struct DecreasePositionResult { /// Finally, the function returns a DecreasePositionResult object containing /// information about the outcome of the decrease operation, including the amount /// of collateral removed from the position and any fees that were paid. -fn decrease_position(params: UpdatePositionParams) -> DecreasePositionResult { - // TODO - let address_zero: ContractAddress = 0.try_into().unwrap(); +fn decrease_position(mut params: UpdatePositionParams) -> DecreasePositionResult { + let mut cache: DecreasePositionCache = Default::default(); + cache.prices = market_utils::get_market_prices(params.contracts.oracle, params.market); + cache + .collateral_token_price = + market_utils::get_cached_token_price( + params.order.initial_collateral_token, params.market, cache.prices + ); + + // cap the order size to the position size + if (params.order.size_delta_usd > params.position.size_in_usd) { + if (params.order.order_type == OrderType::LimitDecrease + || params.order.order_type == OrderType::StopLossDecrease) { + params + .contracts + .event_emitter + .emit_order_size_delta_auto_updated( + params.order_key, params.position.size_in_usd, params.position.size_in_usd + ); + params.order.size_delta_usd = params.position.size_in_usd; + } else { + PositionError::INVALID_DECREASE_ORDER_SIZE( + params.order.size_delta_usd, params.position.size_in_usd + ); + } + } + + // if the position will be partially decreased then do a check on the + // remaining collateral amount and update the order attributes if needed + if (params.order.size_delta_usd < params.position.size_in_usd) { + let (estimated_position_pnl_usd, uncapped_base_pnl_usd, size_delta_in_tokens) = + position_utils::get_position_pnl_usd( + params.contracts.data_store, + params.market, + cache.prices, + params.position, + params.position.size_in_usd + ); + cache.estimated_position_pnl_usd = estimated_position_pnl_usd; + cache + .estimated_realized_pnl_usd = + precision::mul_div_ival( + cache.estimated_position_pnl_usd, + params.order.size_delta_usd, + params.position.size_in_usd + ); + cache.estimated_remaining_pnl_usd = cache.estimated_position_pnl_usd + - cache.estimated_realized_pnl_usd; + let position_values = position_utils::WillPositionCollateralBeSufficientValues { + position_size_in_usd: params.position.size_in_usd - params.order.size_delta_usd, + position_collateral_amount: params.position.collateral_amount + - params.order.initial_collateral_delta_amount, + realized_pnl_usd: cache.estimated_realized_pnl_usd, + open_interest_delta: to_signed(params.order.size_delta_usd, false), + }; + + let (will_be_sufficient, mut estimated_remaining_collateral_usd) = + position_utils::will_position_collateral_be_sufficient( + params.contracts.data_store, + params.market, + cache.prices, + params.position.collateral_token, + params.position.is_long, + position_values + ); + + // do not allow withdrawal of collateral if it would lead to the position + // having an insufficient amount of collateral + // this helps to prevent gaming by opening a position then reducing collateral + // to increase the leverage of the position + if (!will_be_sufficient) { + if (params.order.size_delta_usd == 0) { + PositionError::UNABLE_TO_WITHDRAW_COLLATERAL(estimated_remaining_collateral_usd); + } + params + .contracts + .event_emitter + .emit_order_collateral_delta_amount_auto_updated( + params.order_key, params.order.initial_collateral_delta_amount, 0 + ); + + // the estimated_remaining_collateral_usd subtracts the initial_collateral_delta_amount + // since the initial_collateral_delta_amount will be set to zero, the initial_collateral_delta_amount + // should be added back to the estimated_remaining_collateral_usd + + estimated_remaining_collateral_usd += + to_signed( + params.order.initial_collateral_delta_amount * cache.collateral_token_price.min, + true + ); + + params.order.initial_collateral_delta_amount = 0; + } + + // if the remaining collateral including position pnl will be below + // the min collateral usd value, then close the position + // + // if the position has sufficient remaining collateral including pnl + // then allow the position to be partially closed and the updated + // position to remain open + + if ((estimated_remaining_collateral_usd + + cache + .estimated_remaining_pnl_usd) < to_signed( + params.contracts.data_store.get_u256(keys::min_collateral_usd()), true + )) { + params + .contracts + .event_emitter + .emit_order_size_delta_auto_updated( + params.order_key, params.order.size_delta_usd, params.position.size_in_usd + ); + params.order.size_delta_usd = params.position.size_in_usd; + } + + if (params.position.size_in_usd > params.order.size_delta_usd + && (params.position.size_in_usd - params.order.size_delta_usd) < params + .contracts + .data_store + .get_u256(keys::min_collateral_usd())) { + params + .contracts + .event_emitter + .emit_order_size_delta_auto_updated( + params.order_key, params.order.size_delta_usd, params.position.size_in_usd + ); + params.order.size_delta_usd = params.position.size_in_usd; + } + } + + // if the position will be closed, set the initial collateral delta amount + // to zero to help ensure that the order can be executed + if (params.order.size_delta_usd == params.position.size_in_usd + && params.order.initial_collateral_delta_amount > 0) { + params.order.initial_collateral_delta_amount = 0; + } + if (params.position.is_long) { + cache.pnl_token = params.market.long_token; + cache.pnl_token_price = cache.prices.long_token_price; + } else { + cache.pnl_token = params.market.short_token; + cache.pnl_token_price = cache.prices.short_token_price; + }; + + if (params.order.decrease_position_swap_type != DecreasePositionSwapType::NoSwap + && cache.pnl_token == params.position.collateral_token) { + params.order.decrease_position_swap_type = DecreasePositionSwapType::NoSwap; + } + + position_utils::update_funding_and_borrowing_state(params, cache.prices); + if (base_order_utils::is_liquidation_order(params.order.order_type)) { + let (is_liquidatable, liquidation_amount_usd) = position_utils::is_position_liquiditable( + params.contracts.data_store, + params.contracts.referral_storage, + params.position, + params.market, + cache.prices, + true + ); + if (!is_liquidatable) { + PositionError::POSITION_SHOULD_NOT_BE_LIQUIDATED(); + } + } + cache.initial_collateral_amount = params.position.collateral_amount; + let (mut values, fees) = decrease_position_collateral_utils::process_collateral(params, cache); + + cache.next_position_size_in_usd = params.position.size_in_usd - params.order.size_delta_usd; + cache + .next_position_borrowing_factor = + market_utils::get_cumulative_borrowing_factor( + @params.contracts.data_store, params.market.market_token, params.position.is_long + ); + + position_utils::update_total_borrowing( + params, cache.next_position_size_in_usd, cache.next_position_borrowing_factor + ); + params.position.size_in_usd = cache.next_position_size_in_usd; + params.position.size_in_tokens -= values.size_delta_in_tokens; + params.position.collateral_amount = values.remaining_collateral_amount; + params.position.decreased_at_block = starknet::info::get_block_number(); + + position_utils::increment_claimable_funding_amount(params, fees); + + if (params.position.size_in_usd == 0 || params.position.size_in_tokens == 0) { + // withdraw all collateral if the position will be closed + values.output.output_amount += params.position.collateral_amount; + + params.position.size_in_usd = 0; + params.position.size_in_tokens = 0; + params.position.collateral_amount = 0; + params.contracts.data_store.remove_position(params.position_key, params.order.account); + } else { + params.position.borrowing_factor = cache.next_position_borrowing_factor; + params + .position + .funding_fee_amount_per_size = fees + .funding + .latest_funding_fee_amount_per_size; + params + .position + .long_token_claimable_funding_amount_per_size = fees + .funding + .latest_long_token_claimable_funding_amount_per_size; + params + .position + .short_token_claimable_funding_amount_per_size = fees + .funding + .latest_short_token_claimable_funding_amount_per_size; + + params.contracts.data_store.set_position(params.position_key, params.position); + } + market_utils::apply_delta_to_collateral_sum( + params.contracts.data_store, + params.contracts.event_emitter, + params.position.market, + params.position.collateral_token, + params.position.is_long, + to_signed(cache.initial_collateral_amount - params.position.collateral_amount, false) + ); + + position_utils::update_open_interest( + params, + to_signed(params.order.size_delta_usd, false), + to_signed(values.size_delta_in_tokens, false) + ); + + // affiliate rewards are still distributed even if the order is a liquidation order + // this is expected as a partial liquidation is considered the same as an automatic + // closing of a position + position_utils::handle_referral(params, fees); + + // validatePosition should be called after open interest and all other market variables + // have been updated + if (params.position.size_in_usd != 0 || params.position.size_in_tokens != 0) { + // validate position which validates liquidation state is only called + // if the remaining position size is not zero + // due to this, a user can still manually close their position if + // it is in a partially liquidatable state + // this should not cause any issues as a liquidation is the same + // as automatically closing a position + // the only difference is that if the position has insufficient / negative + // collateral a liquidation transaction should still complete + // while a manual close transaction should revert + position_utils::validate_position( + params.contracts.data_store, + params.contracts.referral_storage, + params.position, + params.market, + cache.prices, + false, // should_validate_min_position_size + false // should_validate_min_collateral_usd + ); + } + + params + .contracts + .event_emitter + .emit_position_fees_collected( + params.order_key, + params.position_key, + params.market.market_token, + params.position.collateral_token, + params.order.size_delta_usd, + false, + fees + ); + + params + .contracts + .event_emitter + .emit_position_decrease( + params.order_key, + params.position_key, + params.position, + params.order.size_delta_usd, + cache.initial_collateral_amount - params.position.collateral_amount, + params.order.order_type, + values, + cache.prices.index_token_price, + cache.collateral_token_price + ); + + values = decrease_position_swap_utils::swap_withdrawn_collateral_to_pnl_token(params, values); DecreasePositionResult { - output_token: address_zero, - output_amount: 0, - secondary_output_token: address_zero, - secondary_output_amount: 0, + output_token: values.output.output_token, + output_amount: values.output.output_amount, + secondary_output_token: values.output.secondary_output_token, + secondary_output_amount: values.output.secondary_output_amount, } } diff --git a/src/position/error.cairo b/src/position/error.cairo index 444c227b..8ee7e753 100644 --- a/src/position/error.cairo +++ b/src/position/error.cairo @@ -1,10 +1,63 @@ mod PositionError { + use satoru::utils::i256::i256; + const EMPTY_POSITION: felt252 = 'empty_position'; const INVALID_POSITION_SIZE_VALUES: felt252 = 'invalid_position_size_values'; const POSITION_NOT_FOUND: felt252 = 'position_not_found'; const POSITION_INDEX_NOT_FOUND: felt252 = 'position_index_not_found'; - const CANT_BE_ZERO: felt252 = 'position account cant be 0'; - const INVALID_OUTPUT_TOKEN: felt252 = 'invalid output token'; - const MIN_POSITION_SIZE: felt252 = 'minumum position size'; - const LIQUIDATABLE_POSITION: felt252 = 'liquidatable position'; + const UNEXPECTED_POSITION_STATE: felt252 = 'unexpected_position_state'; + const CANT_BE_ZERO: felt252 = 'position_account_cant_be_0'; + const INVALID_OUTPUT_TOKEN: felt252 = 'invalid_output_token'; + const MIN_POSITION_SIZE: felt252 = 'minimum_position_size'; + const LIQUIDATABLE_POSITION: felt252 = 'liquidatable_position'; + const EMPTY_HOLDING_ADDRESS: felt252 = 'empty_holding_address'; + + fn INVALID_DECREASE_ORDER_SIZE(size_delta_usd: u256, size_in_usd: u256) { + let mut data = array!['invalid decrease order size']; + data.append(size_delta_usd.try_into().expect('u256 into felt failed')); + data.append(size_in_usd.try_into().expect('u256 into felt failed')); + panic(data) + } + + fn UNABLE_TO_WITHDRAW_COLLATERAL(estimated_remaining_collateral_usd: i256) { + let mut data = array!['unable to withdraw collateral']; + data.append(estimated_remaining_collateral_usd.into()); + panic(data) + } + + fn POSITION_SHOULD_NOT_BE_LIQUIDATED() { + let data = array!['position not be liquidated']; + panic(data) + } + + fn INSUFFICIENT_FUNDS_TO_PAY_FOR_COSTS(remaining_cost_usd: u256, step: felt252) { + let mut data = array![ + 'InsufficientFundsToPayForCosts', + remaining_cost_usd.try_into().expect('u256 into felt failed'), + step + ]; + panic(data); + } + + fn INSUFFICIENT_COLLATERAL_AMOUNT(collateral_amount: u256, collateral_delta_amount: i256) { + let mut data = array![ + 'Insufficient collateral amount', + collateral_amount.try_into().expect('u256 into felt failed'), + collateral_delta_amount.into() + ]; + panic(data); + } + + fn INSUFFICIENT_COLLATERAL_USD(remaining_collateral_usd: i256) { + let mut data = array!['Insufficient collateral usd', remaining_collateral_usd.into()]; + panic(data); + } + + fn PRICE_IMPACT_LARGER_THAN_ORDER_SIZE(price_impact_usd: i256, size_delta_usd: u256) { + let mut data = array![ + 'Price impact larger order size', + size_delta_usd.try_into().expect('u256 into felt failed') + ]; + panic(data); + } } diff --git a/src/position/increase_position_utils.cairo b/src/position/increase_position_utils.cairo index 75325274..59b316fe 100644 --- a/src/position/increase_position_utils.cairo +++ b/src/position/increase_position_utils.cairo @@ -4,32 +4,50 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; use result::ResultTrait; - // Local imports use satoru::position::position_utils::UpdatePositionParams; use satoru::pricing::position_pricing_utils::{ PositionFees, PositionBorrowingFees, PositionFundingFees, PositionReferralFees, PositionUiFees, + GetPositionFeesParams, get_position_fees, get_price_impact_usd, GetPriceImpactUsdParams }; -use satoru::price::price::Price; -#[derive(Drop, starknet::Store, Serde)] +use satoru::price::price::{Price, PriceTrait}; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::event::{event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait},}; +use satoru::market::market_utils; +use satoru::position::{ + position::Position, position_utils, position_utils::WillPositionCollateralBeSufficientValues, + position_event_utils +}; +use satoru::position::error::PositionError; +use satoru::utils::{ + calc::{ + to_unsigned, to_signed, sum_return_uint_256, roundup_magnitude_division, roundup_division + }, + i256::{i256, i256_neg} +}; +use satoru::fee::fee_utils; +use satoru::data::keys; +use satoru::order::base_order_utils; + +#[derive(Drop, starknet::Store, Serde, Default, Copy)] struct IncreasePositionCache { /// The change in collateral amount. - collateral_delta_amount: u128, // TODO replace with i128 when storeable - execution_price: u128, + collateral_delta_amount: i256, + execution_price: u256, collateral_token_price: Price, /// The price impact of the position increase in USD. - price_impact_usd: u128, // TODO replace with i128 when storeable + price_impact_usd: i256, /// The price impact of the position increase in tokens. - price_impact_amount: u128, // TODO replace with i128 when storeable + price_impact_amount: i256, /// The change in position size in tokens. - size_delta_in_tokens: u128, + size_delta_in_tokens: u256, /// The new position size in USD. - next_position_size_in_usd: u128, + next_position_size_in_usd: u256, /// The new position borrowing factor. - next_position_borrowing_factor: u128, + next_position_borrowing_factor: u256, } /// The increasePosition function is used to increase the size of a position @@ -37,7 +55,199 @@ struct IncreasePositionCache { /// calculating the price impact of the size increase, and updating the position's /// size and borrowing factor. This function also applies fees to the position /// and updates the market's liquidity pool based on the new position size. -fn increase_position(params: UpdatePositionParams, collateral_increment_amount: u128) { // TODO +fn increase_position(mut params: UpdatePositionParams, collateral_increment_amount: u256) { + // get the market prices for the given position + let prices = market_utils::get_market_prices(params.contracts.oracle, params.market); + + position_utils::update_funding_and_borrowing_state(params, prices); + + // create a new cache for holding intermediate results + let mut cache: IncreasePositionCache = Default::default(); + + cache + .collateral_token_price = + market_utils::get_cached_token_price( + params.position.collateral_token, params.market, prices + ); + if (params.position.size_in_usd == 0) { + params + .position + .funding_fee_amount_per_size = + market_utils::get_funding_fee_amount_per_size( + params.contracts.data_store, + params.market.market_token, + params.position.collateral_token, + params.position.is_long + ); + params + .position + .long_token_claimable_funding_amount_per_size = + market_utils::get_claimable_funding_amount_per_size( + params.contracts.data_store, + params.market.market_token, + params.market.long_token, + params.position.is_long + ); + + params + .position + .short_token_claimable_funding_amount_per_size = + market_utils::get_claimable_funding_amount_per_size( + params.contracts.data_store, + params.market.market_token, + params.market.short_token, + params.position.is_long + ); + } + let ( + get_price_impact_usd, get_price_impact_amount, get_size_delta_in_tokens, get_execution_price + ) = + get_execution_price( + params, prices.index_token_price + ); + cache.price_impact_usd = get_price_impact_usd; + cache.price_impact_amount = get_price_impact_amount; + cache.size_delta_in_tokens = get_size_delta_in_tokens; + cache.execution_price = get_execution_price; + // process the collateral for the given position and order + let mut fees: PositionFees = Default::default(); + let (processed_collateral_delta_amount, processed_fees) = process_collateral( + params, + cache.collateral_token_price, + to_signed(collateral_increment_amount, true), + cache.price_impact_usd + ); + cache.collateral_delta_amount = processed_collateral_delta_amount; + fees = processed_fees; + + // check if there is sufficient collateral for the position + if (cache.collateral_delta_amount < Zeroable::zero() + && params + .position + .collateral_amount < to_unsigned(i256_neg(cache.collateral_delta_amount))) { + PositionError::INSUFFICIENT_COLLATERAL_AMOUNT( + params.position.collateral_amount, cache.collateral_delta_amount + ) + } + params + .position + .collateral_amount = + sum_return_uint_256(params.position.collateral_amount, cache.collateral_delta_amount); + + // if there is a positive impact, the impact pool amount should be reduced + // if there is a negative impact, the impact pool amount should be increased + market_utils::apply_delta_to_position_impact_pool( + params.contracts.data_store, + params.contracts.event_emitter, + params.market.market_token, + i256_neg(cache.price_impact_amount) + ); + cache.next_position_size_in_usd = params.position.size_in_usd + params.order.size_delta_usd; + cache + .next_position_borrowing_factor = + market_utils::get_cumulative_borrowing_factor( + @params.contracts.data_store, params.market.market_token, params.position.is_long + ); + position_utils::update_total_borrowing( + params, cache.next_position_size_in_usd, cache.next_position_borrowing_factor + ); + position_utils::increment_claimable_funding_amount(params, fees); + params.position.size_in_usd = cache.next_position_size_in_usd; + params.position.size_in_tokens = params.position.size_in_tokens + cache.size_delta_in_tokens; + + params.position.funding_fee_amount_per_size = fees.funding.latest_funding_fee_amount_per_size; + params + .position + .long_token_claimable_funding_amount_per_size = fees + .funding + .latest_long_token_claimable_funding_amount_per_size; + params + .position + .short_token_claimable_funding_amount_per_size = fees + .funding + .latest_short_token_claimable_funding_amount_per_size; + + params.position.borrowing_factor = cache.next_position_borrowing_factor; + params.position.increased_at_block = starknet::info::get_block_number(); + + params.contracts.data_store.set_position(params.position_key, params.position); + position_utils::update_open_interest( + params, + to_signed(params.order.size_delta_usd, true), + to_signed(cache.size_delta_in_tokens, true) + ); + if (params.order.size_delta_usd > 0) { + // reserves are only validated if the sizeDeltaUsd is more than zero + // this helps to ensure that deposits of collateral into positions + // should still succeed even if pool tokens are fully reserved + market_utils::validate_reserve( + params.contracts.data_store, @params.market, @prices, params.order.is_long + ); + market_utils::validate_open_interest_reserve( + params.contracts.data_store, @params.market, @prices, params.order.is_long + ); + let position_values: WillPositionCollateralBeSufficientValues = + WillPositionCollateralBeSufficientValues { + position_size_in_usd: params.position.size_in_usd, + position_collateral_amount: params.position.collateral_amount, + realized_pnl_usd: Zeroable::zero(), + open_interest_delta: Zeroable::zero() + }; + let (will_be_sufficient, remaining_collateral_usd) = + position_utils::will_position_collateral_be_sufficient( + params.contracts.data_store, + params.market, + prices, + params.position.collateral_token, + params.position.is_long, + position_values + ); + if (!will_be_sufficient) { + PositionError::INSUFFICIENT_COLLATERAL_USD(remaining_collateral_usd); + } + } + position_utils::handle_referral(params, fees); + // validatePosition should be called after open interest and all other market variables + // have been updated + position_utils::validate_position( + params.contracts.data_store, + params.contracts.referral_storage, + params.position, + params.market, + prices, + true, + true + ); + params + .contracts + .event_emitter + .emit_position_fees_collected( + params.order_key, + params.position_key, + params.market.market_token, + params.position.collateral_token, + params.order.size_delta_usd, + true, + fees + ); + + let event_params = position_event_utils::PositionIncreaseParams { + event_emitter: params.contracts.event_emitter, + order_key: params.order_key, + position_key: params.position_key, + position: params.position, + index_token_price: prices.index_token_price, + collateral_token_price: cache.collateral_token_price, + execution_price: cache.execution_price, + size_delta_usd: params.order.size_delta_usd, + size_delta_in_tokens: cache.size_delta_in_tokens, + collateral_delta_amount: cache.collateral_delta_amount, + price_impact_usd: cache.price_impact_usd, + price_impact_amount: cache.price_impact_amount, + order_type: params.order.order_type + }; + + params.contracts.event_emitter.emit_position_increase(event_params); } /// Handle the collateral changes of the position. @@ -46,63 +256,159 @@ fn increase_position(params: UpdatePositionParams, collateral_increment_amount: fn process_collateral( params: UpdatePositionParams, collateral_token_price: Price, - collateral_delta_amount: i128, - price_impact_usd: i128, -) -> (i128, PositionFees) { - // TODO - let address_zero: ContractAddress = 0.try_into().unwrap(); - let position_referral_fees = PositionReferralFees { - referral_code: 0, - affiliate: address_zero, - trader: address_zero, - total_rebate_factor: 0, - trader_discount_factor: 0, - total_rebate_amount: 0, - trader_discount_amount: 0, - affiliate_reward_amount: 0, - }; - let position_funding_fees = PositionFundingFees { - funding_fee_amount: 0, - claimable_long_token_amount: 0, - claimable_short_token_amount: 0, - latest_funding_fee_amount_per_size: 0, - latest_long_token_claimable_funding_amount_per_size: 0, - latest_short_token_claimable_funding_amount_per_size: 0, + mut collateral_delta_amount: i256, + price_impact_usd: i256, +) -> (i256, PositionFees) { + let get_position_fees_params: GetPositionFeesParams = GetPositionFeesParams { + data_store: params.contracts.data_store, + referral_storage: params.contracts.referral_storage, + position: params.position, + collateral_token_price, + for_positive_impact: price_impact_usd > Zeroable::zero(), + long_token: params.market.long_token, + short_token: params.market.short_token, + size_delta_usd: params.order.size_delta_usd, + ui_fee_receiver: params.order.ui_fee_receiver }; - let position_borrowing_fees = PositionBorrowingFees { - borrowing_fee_usd: 0, - borrowing_fee_amount: 0, - borrowing_fee_receiver_factor: 0, - borrowing_fee_amount_for_fee_receiver: 0, - }; - let position_ui_fees = PositionUiFees { - ui_fee_receiver: address_zero, ui_fee_receiver_factor: 0, ui_fee_amount: 0, - }; - let price = Price { min: 0, max: 0, }; - let position_fees = PositionFees { - referral: position_referral_fees, - funding: position_funding_fees, - borrowing: position_borrowing_fees, - ui: position_ui_fees, - collateral_token_price: price, - position_fee_factor: 0, - protocol_fee_amount: 0, - position_fee_receiver_factor: 0, - fee_receiver_amount: 0, - fee_amount_for_pool: 0, - position_fee_amount_for_pool: 0, - position_fee_amount: 0, - total_cost_amount_excluding_funding: 0, - total_cost_amount: 0, - }; - (0, position_fees) + + let fees: PositionFees = get_position_fees(get_position_fees_params); + + fee_utils::increment_claimable_fee_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market.market_token, + params.position.collateral_token, + fees.fee_receiver_amount, + keys::position_fee_type() + ); + + fee_utils::increment_claimable_ui_fee_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.order.ui_fee_receiver, + params.market.market_token, + params.position.collateral_token, + fees.ui.ui_fee_amount, + keys::ui_position_fee_type() + ); + + collateral_delta_amount -= to_signed(fees.total_cost_amount, true); + + market_utils::apply_delta_to_collateral_sum( + params.contracts.data_store, + params.contracts.event_emitter, + params.order.market, + params.position.collateral_token, + params.order.is_long, + collateral_delta_amount + ); + + market_utils::apply_delta_to_pool_amount( + params.contracts.data_store, + params.contracts.event_emitter, + params.market, + params.position.collateral_token, + to_signed(fees.fee_amount_for_pool, true) + ); + + return (collateral_delta_amount, fees); } /// # Returns /// price_impact_usd, price_impact_amount, size_delta_in_tokens, execution_price fn get_execution_price( params: UpdatePositionParams, index_token_price: Price -) -> (i128, i128, u128, u128) { - // TODO - (0, 0, 0, 0) +) -> (i256, i256, u256, u256) { + // note that the executionPrice is not validated against the order.acceptablePrice value + // if the sizeDeltaUsd is zero + // for limit orders the order.triggerPrice should still have been validated + if (params.order.size_delta_usd == 0) { + // increase order: + // - long: use the larger price + // - short: use the smaller price + return ( + Zeroable::zero(), + Zeroable::zero(), + 0, + index_token_price.pick_price(params.position.is_long) + ); + } + let mut price_impact_usd = get_price_impact_usd( + GetPriceImpactUsdParams { + data_store: params.contracts.data_store, + market: params.market, + usd_delta: to_signed(params.order.size_delta_usd, true), + is_long: params.order.is_long + } + ); + // cap priceImpactUsd based on the amount available in the position impact pool + price_impact_usd = + market_utils::get_capped_position_impact_usd( + params.contracts.data_store, + params.market.market_token, + index_token_price, + price_impact_usd, + params.order.size_delta_usd + ); + // for long positions + // + // if price impact is positive, the sizeDeltaInTokens would be increased by the priceImpactAmount + // the priceImpactAmount should be minimized + // + // if price impact is negative, the sizeDeltaInTokens would be decreased by the priceImpactAmount + // the priceImpactAmount should be maximized + + // for short positions + // + // if price impact is positive, the sizeDeltaInTokens would be decreased by the priceImpactAmount + // the priceImpactAmount should be minimized + // + // if price impact is negative, the sizeDeltaInTokens would be increased by the priceImpactAmount + // the priceImpactAmount should be maximized + + let mut price_impact_amount: i256 = Zeroable::zero(); + + if (price_impact_usd > Zeroable::zero()) { + // use indexTokenPrice.max and round down to minimize the priceImpactAmount + price_impact_amount = price_impact_usd / to_signed(index_token_price.max, true); + } else { + // use indexTokenPrice.min and round up to maximize the priceImpactAmount + price_impact_amount = roundup_magnitude_division(price_impact_usd, index_token_price.min); + } + + let mut base_size_delta_in_tokens: u256 = 0; + + if (params.position.is_long) { + // round the number of tokens for long positions down + base_size_delta_in_tokens = params.order.size_delta_usd / index_token_price.max; + } else { + // round the number of tokens for short positions up + base_size_delta_in_tokens = + roundup_division(params.order.size_delta_usd, index_token_price.min); + } + let mut size_delta_in_tokens: i256 = Zeroable::zero(); + + if (params.position.is_long) { + size_delta_in_tokens = to_signed(base_size_delta_in_tokens, true) + price_impact_amount; + } else { + size_delta_in_tokens = to_signed(base_size_delta_in_tokens, true) - price_impact_amount; + } + if (size_delta_in_tokens < Zeroable::zero()) { + PositionError::PRICE_IMPACT_LARGER_THAN_ORDER_SIZE( + price_impact_usd, params.order.size_delta_usd + ) + } + // using increase of long positions as an example + // if price is $2000, sizeDeltaUsd is $5000, priceImpactUsd is -$1000 + // priceImpactAmount = -1000 / 2000 = -0.5 + // baseSizeDeltaInTokens = 5000 / 2000 = 2.5 + // sizeDeltaInTokens = 2.5 - 0.5 = 2 + // executionPrice = 5000 / 2 = $2500 + let execution_price = base_order_utils::get_execution_price_for_increase( + params.order.size_delta_usd, + to_unsigned(size_delta_in_tokens), + params.order.acceptable_price, + params.position.is_long + ); + (price_impact_usd, price_impact_amount, to_unsigned(size_delta_in_tokens), execution_price) } diff --git a/src/position/position.cairo b/src/position/position.cairo index b7e8bc66..a4bbadf9 100644 --- a/src/position/position.cairo +++ b/src/position/position.cairo @@ -4,7 +4,7 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; /// Main struct used to store positions. #[derive(Copy, Drop, starknet::Store, Serde, PartialEq)] @@ -18,19 +18,19 @@ struct Position { /// The collateral token of the position. collateral_token: ContractAddress, /// The size of the position in USD. - size_in_usd: u128, + size_in_usd: u256, /// The size of the position in tokens. - size_in_tokens: u128, + size_in_tokens: u256, /// The amount of collateralToken for collateral. - collateral_amount: u128, + collateral_amount: u256, /// The borrowing factor of the position. - borrowing_factor: u128, + borrowing_factor: u256, /// The position funding fee per size.. - funding_fee_amount_per_size: u128, + funding_fee_amount_per_size: u256, /// the position's claimable funding amount per size for the market.long_token - long_token_claimable_funding_amount_per_size: u128, + long_token_claimable_funding_amount_per_size: u256, /// the position's claimable funding amount per size for the market.short_token - short_token_claimable_funding_amount_per_size: u128, + short_token_claimable_funding_amount_per_size: u256, /// The block at which the position was last increased. increased_at_block: u64, /// The block at which the position was last decreased. @@ -43,9 +43,9 @@ impl DefaultPosition of Default { fn default() -> Position { Position { key: 0, - account: 0.try_into().unwrap(), - market: 0.try_into().unwrap(), - collateral_token: 0.try_into().unwrap(), + account: contract_address_const::<0>(), + market: contract_address_const::<0>(), + collateral_token: contract_address_const::<0>(), size_in_usd: 0, size_in_tokens: 0, collateral_amount: 0, diff --git a/src/position/position_event_utils.cairo b/src/position/position_event_utils.cairo index 5ad4e23f..f311ba08 100644 --- a/src/position/position_event_utils.cairo +++ b/src/position/position_event_utils.cairo @@ -11,6 +11,8 @@ use satoru::order::order::OrderType; use satoru::position::{position_utils::DecreasePositionCollateralValues, position::Position,}; use satoru::price::price::Price; use satoru::pricing::position_pricing_utils::PositionFees; +use satoru::utils::i256::i256; + /// Struct to store a position increase parameters. #[derive(Drop, starknet::Store, Serde)] @@ -28,17 +30,17 @@ struct PositionIncreaseParams { /// The position index token price. collateral_token_price: Price, /// The execution price. - execution_price: u128, + execution_price: u256, /// The position increase amount in usd. - size_delta_usd: u128, + size_delta_usd: u256, /// The position increase amount in tokens. - size_delta_in_tokens: u128, + size_delta_in_tokens: u256, /// The collateral variation amount in usd. - collateral_delta_amount: u128, // TODO i128 when storeable + collateral_delta_amount: i256, /// The position increase price impact in usd. - price_impact_usd: u128, // TODO i128 when storeable + price_impact_usd: i256, /// The position increase price impact in tokens. - price_impact_amount: u128, // TODO i128 when storeable + price_impact_amount: i256, /// The type of the order. order_type: OrderType } diff --git a/src/position/position_utils.cairo b/src/position/position_utils.cairo index 846cb637..45aaef25 100644 --- a/src/position/position_utils.cairo +++ b/src/position/position_utils.cairo @@ -4,30 +4,27 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; use poseidon::poseidon_hash_span; // Local imports. -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::data::{data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}, keys}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; use satoru::market::{market::Market, market_utils::MarketPrices, market_utils}; -use satoru::data::keys; use satoru::position::{position::Position, error::PositionError}; use satoru::pricing::{ position_pricing_utils, position_pricing_utils::PositionFees, position_pricing_utils::GetPriceImpactUsdParams, position_pricing_utils::GetPositionFeesParams }; -use satoru::order::order::{Order, SecondaryOrderType}; +use satoru::order::{ + order::{Order, SecondaryOrderType}, base_order_utils::ExecuteOrderParamsContracts, + order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait} +}; use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; -use satoru::order::base_order_utils::ExecuteOrderParamsContracts; use satoru::price::price::{Price, PriceTrait}; -use satoru::utils::{ - precision, i128::{StoreI128, u128_to_i128, i128_to_u128, I128Serde, I128Div, I128Mul} -}; -use satoru::utils::calc::{roundup_division}; +use satoru::utils::{calc, precision, i256::i256, default::DefaultContractAddress, error_utils}; use satoru::referral::referral_utils; -use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; /// Struct used in increasePosition and decreasePosition. #[derive(Drop, Copy, starknet::Store, Serde)] @@ -50,16 +47,15 @@ struct UpdatePositionParams { impl DefaultUpdatePositionParams of Default { fn default() -> UpdatePositionParams { + let contract_address = contract_address_const::<0>(); UpdatePositionParams { contracts: ExecuteOrderParamsContracts { - data_store: IDataStoreDispatcher { contract_address: 0.try_into().unwrap() }, - event_emitter: IEventEmitterDispatcher { contract_address: 0.try_into().unwrap() }, - order_vault: IOrderVaultDispatcher { contract_address: 0.try_into().unwrap() }, - oracle: IOracleDispatcher { contract_address: 0.try_into().unwrap() }, - swap_handler: ISwapHandlerDispatcher { contract_address: 0.try_into().unwrap() }, - referral_storage: IReferralStorageDispatcher { - contract_address: 0.try_into().unwrap() - } + data_store: IDataStoreDispatcher { contract_address }, + event_emitter: IEventEmitterDispatcher { contract_address }, + order_vault: IOrderVaultDispatcher { contract_address }, + oracle: IOracleDispatcher { contract_address }, + swap_handler: ISwapHandlerDispatcher { contract_address }, + referral_storage: IReferralStorageDispatcher { contract_address } }, market: Default::default(), order: Default::default(), @@ -74,56 +70,56 @@ impl DefaultUpdatePositionParams of Default { /// Struct to determine wether position collateral will be sufficient. #[derive(Drop, starknet::Store, Serde)] struct WillPositionCollateralBeSufficientValues { - position_size_in_usd: u128, - position_collateral_amount: u128, - realized_pnl_usd: i128, - open_interest_delta: i128, + position_size_in_usd: u256, + position_collateral_amount: u256, + realized_pnl_usd: i256, + open_interest_delta: i256, } /// Struct used as decrease_position_collateral output. -#[derive(Drop, starknet::Store, Serde)] +#[derive(Drop, starknet::Store, Serde, Default, Copy)] struct DecreasePositionCollateralValuesOutput { /// The output token address. output_token: ContractAddress, /// The output amount in tokens. - output_amount: u128, + output_amount: u256, /// The seconary output token address. secondary_output_token: ContractAddress, /// The secondary output amount in tokens. - secondary_output_amount: u128, + secondary_output_amount: u256, } /// Struct used to contain the values in process_collateral -#[derive(Drop, starknet::Store, Serde)] +#[derive(Drop, starknet::Store, Serde, Default, Copy)] struct DecreasePositionCollateralValues { /// The order execution price. - execution_price: u128, + execution_price: u256, /// The remaining collateral amount of the position. - remaining_collateral_amount: u128, + remaining_collateral_amount: u256, /// The pnl of the position in USD. - base_pnl_usd: i128, + base_pnl_usd: i256, /// The uncapped pnl of the position in USD. - uncapped_base_pnl_usd: i128, + uncapped_base_pnl_usd: i256, /// The change in position size in tokens. - size_delta_in_tokens: u128, + size_delta_in_tokens: u256, /// The price impact in usd. - price_impact_usd: i128, + price_impact_usd: i256, /// The price impact difference in USD. - price_impact_diff_usd: u128, + price_impact_diff_usd: u256, /// The output struct. output: DecreasePositionCollateralValuesOutput } -#[derive(Drop, starknet::Store, Serde)] +#[derive(Copy, Drop, starknet::Store, Serde)] struct DecreasePositionCache { /// The prices of the tokens in the market. prices: MarketPrices, /// The estimated position pnl in USD. - estimated_position_pnl_usd: i128, + estimated_position_pnl_usd: i256, /// The estimated realized position pnl in USD after decrease. - estimated_realized_pnl_usd: i128, + estimated_realized_pnl_usd: i256, /// The estimated remaining position pnl in USD. - estimated_remaining_pnl_usd: i128, + estimated_remaining_pnl_usd: i256, /// The token that the pnl for the user is in, for long positions. /// This is the market.longToken, for short positions this is the market.short_token. pnl_token: ContractAddress, @@ -132,80 +128,80 @@ struct DecreasePositionCache { /// The price of the collateral token. collateral_token_price: Price, /// The initial collateral amount. - initial_collateral_amount: u128, + initial_collateral_amount: u256, /// The new position size in USD. - next_position_size_in_usd: u128, + next_position_size_in_usd: u256, /// The new position borrowing factor. - next_position_borrowing_factor: u128, + next_position_borrowing_factor: u256, } /// Struct used as cache in get_position_pnl. #[derive(Drop, starknet::Store, Serde)] struct GetPositionPnlUsdCache { /// The position value. - position_value: i128, + position_value: i256, /// The total position pnl. - total_position_pnl: i128, + total_position_pnl: i256, /// The uncapped total position pnl. - uncapped_total_position_pnl: i128, + uncapped_total_position_pnl: i256, /// The pnl token address. pnl_token: ContractAddress, /// The amount of token in pool. - pool_token_amount: u128, + pool_token_amount: u256, /// The price of pool token. - pool_token_price: u128, + pool_token_price: u256, /// The pool token value in usd. - pool_token_usd: u128, + pool_token_usd: u256, /// The total pool pnl. - pool_pnl: i128, + pool_pnl: i256, /// The capped pool pnl. - capped_pool_pnl: i128, + capped_pool_pnl: i256, /// The size variation in tokens. - size_delta_in_tokens: u128, + size_delta_in_tokens: u256, /// The positions pnl in usd. - position_pnl_usd: i128, + position_pnl_usd: i256, /// The uncapped positions pnl in usd. - uncapped_position_pnl_usd: i128, + uncapped_position_pnl_usd: i256, } /// Struct used as cache in is_position_liquidatable. #[derive(Drop, starknet::Store, Serde)] struct IsPositionLiquidatableCache { /// The position's pnl in USD. - position_pnl_usd: i128, + position_pnl_usd: i256, /// The min collateral factor. - min_collateral_factor: u128, + min_collateral_factor: u256, /// The collateral token price. collateral_token_price: Price, /// The position's collateral in USD. - collateral_usd: u128, + collateral_usd: u256, /// The usd_delta value for the price impact calculation. - usd_delta_for_price_impact: i128, + usd_delta_for_price_impact: i256, /// The price impact of closing the position in USD. - price_impact_usd: i128, + price_impact_usd: i256, has_positive_impact: bool, /// The minimum allowed collateral in USD. - min_collateral_usd: i128, - min_collateral_usd_for_leverage: i128, + min_collateral_usd: i256, + min_collateral_usd_for_leverage: i256, /// The remaining position collateral in USD. - remaining_collateral_usd: i128, + remaining_collateral_usd: i256, } impl DefaultGetPositionPnlUsdCache of Default { fn default() -> GetPositionPnlUsdCache { GetPositionPnlUsdCache { - position_value: 0, - total_position_pnl: 0, - uncapped_total_position_pnl: 0.try_into().unwrap(), - pnl_token: 0.try_into().unwrap(), + position_value: Zeroable::zero(), + total_position_pnl: Zeroable::zero(), + uncapped_total_position_pnl: 0.into(), + pnl_token: contract_address_const::<0>(), pool_token_amount: 0, pool_token_price: 0, pool_token_usd: 0, - pool_pnl: 0, - capped_pool_pnl: 0, + pool_pnl: Zeroable::zero(), + capped_pool_pnl: Zeroable::zero(), size_delta_in_tokens: 0, - position_pnl_usd: 0, - uncapped_position_pnl_usd: 0, + position_pnl_usd: Zeroable::zero(), + uncapped_position_pnl_usd: Zeroable::zero(), } } } @@ -213,16 +209,33 @@ impl DefaultGetPositionPnlUsdCache of Default { impl DefaultIsPositionLiquidatableCache of Default { fn default() -> IsPositionLiquidatableCache { IsPositionLiquidatableCache { - position_pnl_usd: 0, + position_pnl_usd: Zeroable::zero(), min_collateral_factor: 0, collateral_token_price: Price { min: 0, max: 0 }, collateral_usd: 0, - usd_delta_for_price_impact: 0, - price_impact_usd: 0, + usd_delta_for_price_impact: Zeroable::zero(), + price_impact_usd: Zeroable::zero(), has_positive_impact: false, - min_collateral_usd: 0, - min_collateral_usd_for_leverage: 0, - remaining_collateral_usd: 0 + min_collateral_usd: Zeroable::zero(), + min_collateral_usd_for_leverage: Zeroable::zero(), + remaining_collateral_usd: Zeroable::zero() + } + } +} + +impl DefaultDecreasePositionCache of Default { + fn default() -> DecreasePositionCache { + DecreasePositionCache { + prices: Default::default(), + estimated_position_pnl_usd: Zeroable::zero(), + estimated_realized_pnl_usd: Zeroable::zero(), + estimated_remaining_pnl_usd: Zeroable::zero(), + pnl_token: Default::default(), + pnl_token_price: Default::default(), + collateral_token_price: Default::default(), + initial_collateral_amount: Default::default(), + next_position_size_in_usd: Default::default(), + next_position_borrowing_factor: Default::default(), } } } @@ -251,23 +264,22 @@ fn get_position_pnl_usd( market: Market, prices: MarketPrices, position: Position, - size_delta_usd: u128, -) -> (i128, i128, u128) { + size_delta_usd: u256, +) -> (i256, i256, u256) { let mut cache: GetPositionPnlUsdCache = Default::default(); let execution_price = prices.index_token_price.pick_price_for_pnl(position.is_long, false); - // position.sizeInUsd is the cost of the tokens, positionValue is the current worth of the tokens - cache.position_value = u128_to_i128(position.size_in_tokens * execution_price); + cache.position_value = calc::to_signed(position.size_in_tokens * execution_price, true); cache .total_position_pnl = if position.is_long { - cache.position_value - u128_to_i128(position.size_in_usd) + cache.position_value - calc::to_signed(position.size_in_usd, true) } else { - u128_to_i128(position.size_in_usd) - cache.position_value + calc::to_signed(position.size_in_usd, true) - cache.position_value }; cache.uncapped_total_position_pnl = cache.total_position_pnl; - if (cache.total_position_pnl > 0) { + if (cache.total_position_pnl > Zeroable::zero()) { cache.pnl_token = if position.is_long { market.long_token } else { @@ -300,27 +312,28 @@ fn get_position_pnl_usd( keys::max_pnl_factor_for_traders() ); if (cache.capped_pool_pnl != cache.pool_pnl - && cache.capped_pool_pnl > 0 - && cache.pool_pnl > 0) { + && cache.capped_pool_pnl > Zeroable::zero() + && cache.pool_pnl > Zeroable::zero()) { cache .total_position_pnl = precision::mul_div_inum( - i128_to_u128(cache.total_position_pnl), + calc::to_unsigned(cache.total_position_pnl), cache.capped_pool_pnl, - i128_to_u128(cache.pool_pnl) + calc::to_unsigned(cache.pool_pnl) ); } } if position.size_in_usd == size_delta_usd { - cache.size_delta_in_tokens = position.size_in_tokens + cache.size_delta_in_tokens = position.size_in_tokens; } else { if position.is_long { cache .size_delta_in_tokens = - roundup_division( + calc::roundup_division( position.size_in_tokens * size_delta_usd, position.size_in_usd ); } else { + error_utils::check_division_by_zero(position.size_in_usd, 'position.size_in_usd'); cache.size_delta_in_tokens = position.size_in_tokens * size_delta_usd / position.size_in_usd; @@ -409,7 +422,7 @@ fn validate_position( market_utils::validate_enabled_market(data_store, market); market_utils::validate_market_collateral_token(market, position.collateral_token); if should_validate_min_position_size { - let min_position_size_usd = data_store.get_u128(keys::min_position_size_usd()); + let min_position_size_usd = data_store.get_u256(keys::min_position_size_usd()); assert(position.size_in_usd >= min_position_size_usd, PositionError::MIN_POSITION_SIZE); } let (is_liquiditable, reason) = is_position_liquiditable( @@ -446,8 +459,9 @@ fn is_position_liquiditable( market_utils::get_cached_token_price(position.collateral_token, market, prices); cache.collateral_usd = position.collateral_amount * cache.collateral_token_price.min; + // calculate the usdDeltaForPriceImpact for fully closing the position - cache.usd_delta_for_price_impact = -u128_to_i128(position.size_in_usd); + cache.usd_delta_for_price_impact = calc::to_signed(position.size_in_usd, false); cache .price_impact_usd = position_pricing_utils::get_price_impact_usd( @@ -458,13 +472,13 @@ fn is_position_liquiditable( is_long: position.is_long } ); - cache.has_positive_impact = cache.price_impact_usd > 0; + cache.has_positive_impact = cache.price_impact_usd > Zeroable::zero(); // even if there is a large positive price impact, positions that would be liquidated // if the positive price impact is reduced should not be allowed to be created // as they would be easily liquidated if the price impact changes // cap the priceImpactUsd to zero to prevent these positions from being created - if cache.price_impact_usd >= 0 { - cache.price_impact_usd = 0; + if cache.price_impact_usd >= Zeroable::zero() { + cache.price_impact_usd = Zeroable::zero(); } else { let max_price_impact_factor = market_utils::get_max_position_impact_factor_for_liquidations( data_store, market.market_token @@ -474,8 +488,8 @@ fn is_position_liquiditable( // this could result in very large price impact temporarily // cap the max negative price impact to prevent cascading liquidations - let max_negatice_price_impact = u128_to_i128( - precision::apply_factor_u128(position.size_in_usd, max_price_impact_factor) + let max_negatice_price_impact = calc::to_signed( + precision::apply_factor_u256(position.size_in_usd, max_price_impact_factor), true ); if cache.price_impact_usd < max_negatice_price_impact { cache.price_impact_usd = max_negatice_price_impact; @@ -490,7 +504,7 @@ fn is_position_liquiditable( long_token: market.long_token, short_token: market.short_token, size_delta_usd: position.size_in_usd, - ui_fee_receiver: 0.try_into().unwrap(), + ui_fee_receiver: contract_address_const::<0>(), }; let fees = position_pricing_utils::get_position_fees(pos_fees_params); // the totalCostAmount is in tokens, use collateralTokenPrice.min to calculate the cost in USD @@ -501,18 +515,20 @@ fn is_position_liquiditable( // the position's pnl is counted as collateral for the liquidation check // as a position in profit should not be liquidated if the pnl is sufficient // to cover the position's fees - cache.remaining_collateral_usd = u128_to_i128(cache.collateral_usd) + cache.remaining_collateral_usd = calc::to_signed(cache.collateral_usd, true) + cache.position_pnl_usd + cache.price_impact_usd - - u128_to_i128(collateral_cost_usd); + - calc::to_signed(collateral_cost_usd, true); if should_validate_min_collateral_usd { - cache.min_collateral_usd = u128_to_i128(data_store.get_u128(keys::min_collateral_usd())); + cache + .min_collateral_usd = + calc::to_signed(data_store.get_u256(keys::min_collateral_usd()), true); if (cache.remaining_collateral_usd < cache.min_collateral_usd) { return (true, 'min collateral'); } } - if cache.remaining_collateral_usd <= 0 { + if cache.remaining_collateral_usd <= Zeroable::zero() { return (true, '0<'); } cache @@ -523,9 +539,11 @@ fn is_position_liquiditable( // i.e. if the position does not have sufficient collateral after closing fees it is considered a liquidatable position cache .min_collateral_usd_for_leverage = - u128_to_i128( - precision::apply_factor_u128(position.size_in_usd, cache.min_collateral_factor) + calc::to_signed( + precision::apply_factor_u256(position.size_in_usd, cache.min_collateral_factor), + true ); + if cache.remaining_collateral_usd <= cache.min_collateral_usd_for_leverage { return (true, 'min collateral for leverage'); } @@ -570,19 +588,19 @@ fn will_position_collateral_be_sufficient( collateral_token: ContractAddress, is_long: bool, values: WillPositionCollateralBeSufficientValues, -) -> (bool, i128) { +) -> (bool, i256) { let collateral_token_price = market_utils::get_cached_token_price( collateral_token, market, prices ); - let mut remaining_collateral_usd = u128_to_i128(values.position_collateral_amount) - * u128_to_i128(collateral_token_price.min); + let mut remaining_collateral_usd = calc::to_signed(values.position_collateral_amount, true) + * calc::to_signed(collateral_token_price.min, true); // deduct realized pnl if it is negative since this would be paid from // the position's collateral - if values.realized_pnl_usd < 0 { + if values.realized_pnl_usd < Zeroable::zero() { remaining_collateral_usd = remaining_collateral_usd + values.realized_pnl_usd; } - if (remaining_collateral_usd < 0) { + if (remaining_collateral_usd < Zeroable::zero()) { return (false, remaining_collateral_usd); } // the min collateral factor will increase as the open interest for a market increases @@ -602,8 +620,8 @@ fn will_position_collateral_be_sufficient( if (min_collateral_factor_for_market > min_collateral_factor) { min_collateral_factor = min_collateral_factor_for_market; } - let min_collateral_usd_for_leverage = u128_to_i128( - precision::apply_factor_u128(values.position_size_in_usd, min_collateral_factor) + let min_collateral_usd_for_leverage = calc::to_signed( + precision::apply_factor_u256(values.position_size_in_usd, min_collateral_factor), true ); let will_be_sufficient: bool = remaining_collateral_usd >= min_collateral_usd_for_leverage; @@ -647,8 +665,8 @@ fn update_funding_and_borrowing_state(params: UpdatePositionParams, prices: Mark /// *`next_position_borrowing_factor` - Thenext position borrowing factor fn update_total_borrowing( params: UpdatePositionParams, - next_position_size_in_usd: u128, - next_position_borrowing_factor: u128, + next_position_size_in_usd: u256, + next_position_borrowing_factor: u256, ) { market_utils::update_total_borrowing( params.contracts.data_store, // dataStore @@ -699,9 +717,9 @@ fn increment_claimable_funding_amount(params: UpdatePositionParams, fees: Positi /// *`size_delta_usd` - The USD change in position size. /// *`size_delta_in_tokens` - The change in position size. fn update_open_interest( - params: UpdatePositionParams, size_delta_usd: i128, size_delta_in_tokens: i128, + params: UpdatePositionParams, size_delta_usd: i256, size_delta_in_tokens: i256, ) { - if (size_delta_usd != 0) { + if (size_delta_usd != Zeroable::zero()) { market_utils::apply_delta_to_open_interest( params.contracts.data_store, params.contracts.event_emitter, @@ -710,11 +728,10 @@ fn update_open_interest( params.position.is_long, size_delta_usd ); - - market_utils::apply_delta_to_open_interest( + market_utils::apply_delta_to_open_interest_in_tokens( params.contracts.data_store, params.contracts.event_emitter, - @params.market, + params.market, params.position.collateral_token, params.position.is_long, size_delta_in_tokens diff --git a/src/price/price.cairo b/src/price/price.cairo index a8b38eea..e9daa206 100644 --- a/src/price/price.cairo +++ b/src/price/price.cairo @@ -4,9 +4,9 @@ use zeroable::Zeroable; #[derive(Copy, Default, starknet::Store, Drop, Serde)] struct Price { /// The minimum price. - min: u128, + min: u256, /// The maximum price. - max: u128, + max: u256, } /// The trait for `Price` struct. @@ -16,7 +16,7 @@ trait PriceTrait { /// * `self` - The `Price` struct. /// # Returns /// * The average of the min and max values. - fn mid_price(self: @Price) -> u128; + fn mid_price(self: @Price) -> u256; /// Pick either the min or max value. /// # Arguments @@ -24,7 +24,7 @@ trait PriceTrait { /// * `maximize` - If true, pick the max value. Otherwise, pick the min value. /// # Returns /// * The min or max value. - fn pick_price(self: @Price, maximize: bool) -> u128; + fn pick_price(self: @Price, maximize: bool) -> u256; /// Pick the min or max price depending on wheter it is for a long or a short position, /// and whether the pending pnl should be maximized or not. @@ -34,16 +34,16 @@ trait PriceTrait { /// * `maximize` - Whether the pending pnl should be maximized or not. /// # Returns /// * The min or max price. - fn pick_price_for_pnl(self: @Price, is_long: bool, maximize: bool) -> u128; + fn pick_price_for_pnl(self: @Price, is_long: bool, maximize: bool) -> u256; } impl PriceImpl of PriceTrait { - fn mid_price(self: @Price) -> u128 { + fn mid_price(self: @Price) -> u256 { (*self.min + *self.max) / 2 } - fn pick_price(self: @Price, maximize: bool) -> u128 { + fn pick_price(self: @Price, maximize: bool) -> u256 { if maximize { *self.max } else { @@ -51,7 +51,7 @@ impl PriceImpl of PriceTrait { } } - fn pick_price_for_pnl(self: @Price, is_long: bool, maximize: bool) -> u128 { + fn pick_price_for_pnl(self: @Price, is_long: bool, maximize: bool) -> u256 { if is_long { self.pick_price(maximize) } else { @@ -64,11 +64,9 @@ impl PriceZeroable of Zeroable { fn zero() -> Price { Price { min: 0, max: 0 } } - #[inline(always)] fn is_zero(self: Price) -> bool { self.min == 0 && self.max == 0 } - #[inline(always)] fn is_non_zero(self: Price) -> bool { !self.is_zero() } diff --git a/src/pricing/error.cairo b/src/pricing/error.cairo new file mode 100644 index 00000000..3c6a8e1d --- /dev/null +++ b/src/pricing/error.cairo @@ -0,0 +1,24 @@ +mod PricingError { + use satoru::utils::i256::i256; + + fn USD_DELTA_EXCEEDS_LONG_OPEN_INTEREST(usd_delta: i256, long_open_interest: u256) { + let mut data = array!['usd delta exceeds long interest']; + data.append(usd_delta.into()); + data.append(long_open_interest.try_into().expect('u256 into felt failed')); + panic(data) + } + fn USD_DELTA_EXCEEDS_SHORT_OPEN_INTEREST(usd_delta: i256, short_open_interest: u256) { + let mut data = array!['usd delta exceed short interest']; + data.append(usd_delta.into()); + data.append(short_open_interest.try_into().expect('u256 into felt failed')); + panic(data) + } + + fn USD_DELTA_EXCEEDS_POOL_VALUE(usd_delta: felt252, pool_usd_for_token: u256) { + let mut data = array!['usd_delta_exceeds_pool_value']; + // TODO adding this crash on swap test + // data.append(usd_delta.into()); + data.append(pool_usd_for_token.try_into().expect('u256 into felt failed')); + panic(data) + } +} diff --git a/src/pricing/position_pricing_utils.cairo b/src/pricing/position_pricing_utils.cairo index 22f9dae1..1eec99ed 100644 --- a/src/pricing/position_pricing_utils.cairo +++ b/src/pricing/position_pricing_utils.cairo @@ -4,7 +4,7 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; use result::ResultTrait; use zeroable::Zeroable; @@ -13,8 +13,19 @@ use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::market::market::Market; use satoru::price::price::Price; use satoru::position::position::Position; + + +use satoru::market::market_utils; +use satoru::pricing::pricing_utils; +use satoru::data::keys; use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; -use satoru::utils::i128::{StoreI128, I128Serde,}; +use satoru::utils::{calc, precision}; +use satoru::pricing::error::PricingError; +use satoru::referral::referral_utils; +use satoru::utils::{ + i256::{i256, i256_neg}, error_utils, calc::to_signed, default::DefaultContractAddress, +}; + /// Struct used in get_position_fees. #[derive(Drop, starknet::Store, Serde)] struct GetPositionFeesParams { @@ -33,20 +44,20 @@ struct GetPositionFeesParams { /// The short token contract address. short_token: ContractAddress, /// The size variation in USD. - size_delta_usd: u128, + size_delta_usd: u256, /// The ui fee receiver contract address. ui_fee_receiver: ContractAddress, } /// Struct used in get_price_impact_usd. -#[derive(Drop, starknet::Store, Serde)] +#[derive(Drop, Copy, starknet::Store, Serde)] struct GetPriceImpactUsdParams { /// The `DataStore` contract dispatcher. data_store: IDataStoreDispatcher, /// The market to check. market: Market, /// The change in position size in USD. - usd_delta: i128, + usd_delta: i256, /// Whether the position is long or short. is_long: bool, } @@ -55,22 +66,17 @@ struct GetPriceImpactUsdParams { #[derive(Drop, starknet::Store, Serde)] struct OpenInterestParams { /// The amount of long open interest. - long_open_interest: u128, + long_open_interest: u256, /// The amount of short open interest. - short_open_interest: u128, + short_open_interest: u256, /// The updated amount of long open interest. - next_long_open_interest: u128, + next_long_open_interest: u256, /// The updated amount of short open interest. - next_short_open_interest: u128, + next_short_open_interest: u256, } -impl DefaultContractAddress of Default { - fn default() -> ContractAddress { - Zeroable::zero() - } -} /// Struct to store position fees data. -#[derive(Default, Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde, Copy)] struct PositionFees { /// The referral fees. referral: PositionReferralFees, @@ -83,27 +89,27 @@ struct PositionFees { /// The collateral_token_price. collateral_token_price: Price, /// The position fee factor. - position_fee_factor: u128, + position_fee_factor: u256, /// The amount of fee to the protocol. - protocol_fee_amount: u128, + protocol_fee_amount: u256, /// The factor of fee due to receiver. - position_fee_receiver_factor: u128, + position_fee_receiver_factor: u256, /// The amount of fee due to receiver. - fee_receiver_amount: u128, + fee_receiver_amount: u256, /// The amount of fee due to the pool. - fee_amount_for_pool: u128, + fee_amount_for_pool: u256, /// The position fee amount for the pool - position_fee_amount_for_pool: u128, + position_fee_amount_for_pool: u256, /// The fee amount for increasing / decreasing the position. - position_fee_amount: u128, + position_fee_amount: u256, /// The total cost amount in tokens excluding funding. - total_cost_amount_excluding_funding: u128, + total_cost_amount_excluding_funding: u256, /// The total cost amount in tokens. - total_cost_amount: u128, + total_cost_amount: u256, } /// Struct used to store referral parameters useful for fees computation. -#[derive(Default, Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde, Copy)] struct PositionReferralFees { /// The referral code used. referral_code: felt252, @@ -112,72 +118,169 @@ struct PositionReferralFees { /// The trader address. trader: ContractAddress, /// The total rebate factor. - total_rebate_factor: u128, + total_rebate_factor: u256, /// The trader discount factor. - trader_discount_factor: u128, + trader_discount_factor: u256, /// The total rebate amount. - total_rebate_amount: u128, + total_rebate_amount: u256, /// The discount amount for the trader. - trader_discount_amount: u128, + trader_discount_amount: u256, /// The affiliate reward amount. - affiliate_reward_amount: u128, + affiliate_reward_amount: u256, } /// Struct used to store position borrowing fees. -#[derive(Default, Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde, Copy)] struct PositionBorrowingFees { /// The borrowing fees amount in USD. - borrowing_fee_usd: u128, + borrowing_fee_usd: u256, /// The borrowing fees amount in tokens. - borrowing_fee_amount: u128, + borrowing_fee_amount: u256, /// The borrowing fees factor for receiver. - borrowing_fee_receiver_factor: u128, + borrowing_fee_receiver_factor: u256, /// The borrowing fees amount in tokens for fee receiver. - borrowing_fee_amount_for_fee_receiver: u128, + borrowing_fee_amount_for_fee_receiver: u256, } /// Struct used to store position funding fees. -#[derive(Default, Drop, starknet::Store, Serde)] +#[derive(Default, Copy, Drop, starknet::Store, Serde)] struct PositionFundingFees { /// The amount of funding fees in tokens. - funding_fee_amount: u128, + funding_fee_amount: u256, /// The negative funding fee in long token that is claimable. - claimable_long_token_amount: u128, + claimable_long_token_amount: u256, /// The negative funding fee in short token that is claimable. - claimable_short_token_amount: u128, + claimable_short_token_amount: u256, /// The latest long token funding fee amount per size for the market. - latest_funding_fee_amount_per_size: u128, + latest_funding_fee_amount_per_size: u256, /// The latest long token funding amount per size for the market. - latest_long_token_claimable_funding_amount_per_size: u128, + latest_long_token_claimable_funding_amount_per_size: u256, /// The latest short token funding amount per size for the market. - latest_short_token_claimable_funding_amount_per_size: u128, + latest_short_token_claimable_funding_amount_per_size: u256, } /// Struct used to store position ui fees -#[derive(Default, Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde, Copy)] struct PositionUiFees { /// The ui fee receiver address ui_fee_receiver: ContractAddress, /// The factor for fee receiver. - ui_fee_receiver_factor: u128, + ui_fee_receiver_factor: u256, /// The ui fee amount in tokens. - ui_fee_amount: u128, + ui_fee_amount: u256, } /// Get the price impact in USD for a position increase / decrease. -fn get_price_impact_usd(params: GetPriceImpactUsdParams) -> i128 { - // TODO - 0 +/// # Arguments +/// * `params` - GetPriceImpactUsdParams +/// # Returns +/// Price impact usd +fn get_price_impact_usd(params: GetPriceImpactUsdParams) -> i256 { + let open_interest_params: OpenInterestParams = get_next_open_interest(params); + let price_impact_usd = get_price_impact_usd_internal( + params.data_store, params.market.market_token, open_interest_params + ); + + /// the virtual price impact calculation is skipped if the price impact + /// is positive since the action is helping to balance the pool + /// + /// in case two virtual pools are unbalanced in a different direction + /// e.g. pool0 has more longs than shorts while pool1 has less longs + /// than shorts + /// not skipping the virtual price impact calculation would lead to + /// a negative price impact for any trade on either pools and would + /// disincentivise the balancing of pools + + if (price_impact_usd >= Zeroable::zero()) { + return price_impact_usd; + } + + let (has_virtual_inventory, virtual_inventory) = + market_utils::get_virtual_inventory_for_positions( + params.data_store, params.market.index_token + ); + + if (!has_virtual_inventory) { + return price_impact_usd; + } + + let open_interest_params_for_virtual_inventory: OpenInterestParams = + get_next_open_interest_for_virtual_inventory( + params, virtual_inventory + ); + let price_impact_usd_for_virtual_inventory = get_price_impact_usd_internal( + params.data_store, params.market.market_token, open_interest_params_for_virtual_inventory + ); + + if (price_impact_usd_for_virtual_inventory < price_impact_usd) { + return price_impact_usd_for_virtual_inventory; + } + + price_impact_usd } /// Called internally by get_price_impact_params(). -fn get_price_impact_usd_( - params: GetPriceImpactUsdParams, +/// # Arguments +/// * `data_store` - DataStore +/// * `market` - the trading market +/// * `openInterestParams` - OpenInterestParams +/// # Returns +/// Price impact usd +fn get_price_impact_usd_internal( + data_store: IDataStoreDispatcher, market: ContractAddress, open_interest_params: OpenInterestParams, -) -> i128 { - // TODO - 0 +) -> i256 { + let initial_diff_usd = calc::diff( + open_interest_params.long_open_interest, open_interest_params.short_open_interest + ); + let next_diff_usd = calc::diff( + open_interest_params.next_long_open_interest, open_interest_params.next_short_open_interest + ); + + /// check whether an improvement in balance comes from causing the balance to switch sides + /// for example, if there is $2000 of ETH and $1000 of USDC in the pool + /// adding $1999 USDC into the pool will reduce absolute balance from $1000 to $999 but it does not + /// help rebalance the pool much, the isSameSideRebalance value helps avoid gaming using this case + let is_same_side_rebalance_first = open_interest_params + .long_open_interest <= open_interest_params + .short_open_interest; + let is_same_side_rebalance_second = open_interest_params + .short_open_interest <= open_interest_params + .next_long_open_interest; + let is_same_side_rebalance_third = open_interest_params + .next_long_open_interest <= open_interest_params + .next_short_open_interest; + let is_same_side_rebalance = is_same_side_rebalance_first + && is_same_side_rebalance_second + && is_same_side_rebalance_third; + + let impact_exponent_factor = data_store + .get_u256(keys::position_impact_exponent_factor_key(market)); + + if (is_same_side_rebalance) { + let has_positive_impact = next_diff_usd < initial_diff_usd; + let impact_factor = market_utils::get_adjusted_position_impact_factor( + data_store, market, has_positive_impact + ); + + return pricing_utils::get_price_impact_usd_for_same_side_rebalance( + initial_diff_usd, next_diff_usd, impact_factor, impact_exponent_factor + ); + } else { + let (positive_impact_factor, negative_impact_factor) = + market_utils::get_adjusted_position_impact_factors( + data_store, market + ); + + return pricing_utils::get_price_impact_usd_for_crossover_rebalance( + initial_diff_usd, + next_diff_usd, + positive_impact_factor, + negative_impact_factor, + impact_exponent_factor + ); + } } /// Compute new open interest. @@ -186,13 +289,15 @@ fn get_price_impact_usd_( /// # Returns /// New open interest. fn get_next_open_interest(params: GetPriceImpactUsdParams) -> OpenInterestParams { - // TODO - OpenInterestParams { - long_open_interest: 0, - short_open_interest: 0, - next_long_open_interest: 0, - next_short_open_interest: 0, - } + let long_open_interest = market_utils::get_open_interest_for_market_is_long( + params.data_store, @params.market, true + ); + + let short_open_interest = market_utils::get_open_interest_for_market_is_long( + params.data_store, @params.market, false + ); + + return get_next_open_interest_params(params, long_open_interest, short_open_interest); } /// Compute new open interest for virtual inventory. @@ -202,34 +307,73 @@ fn get_next_open_interest(params: GetPriceImpactUsdParams) -> OpenInterestParams /// # Returns /// New open interest for virtual inventory. fn get_next_open_interest_for_virtual_inventory( - params: GetPriceImpactUsdParams, virtual_inventory: i128, + params: GetPriceImpactUsdParams, virtual_inventory: i256, ) -> OpenInterestParams { - // TODO - OpenInterestParams { - long_open_interest: 0, - short_open_interest: 0, - next_long_open_interest: 0, - next_short_open_interest: 0, + let mut long_open_interest = 0; + let mut short_open_interest = 0; + + /// if virtualInventory is more than zero it means that + /// tokens were virtually sold to the pool, so set shortOpenInterest + /// to the virtualInventory value + /// if virtualInventory is less than zero it means that + /// tokens were virtually bought from the pool, so set longOpenInterest + /// to the virtualInventory value + + if (virtual_inventory > Zeroable::zero()) { + short_open_interest = calc::to_unsigned(virtual_inventory); + } else { + long_open_interest = calc::to_unsigned(i256_neg(virtual_inventory)); + } + + /// the virtual long and short open interest is adjusted by the usdDelta + /// to prevent an underflow in getNextOpenInterestParams + /// price impact depends on the change in USD balance, so offsetting both + /// values equally should not change the price impact calculation + if (params.usd_delta < Zeroable::zero()) { + let offset = calc::to_unsigned(i256_neg(params.usd_delta)); + long_open_interest += offset; + short_open_interest += offset; } + + return get_next_open_interest_params(params, long_open_interest, short_open_interest); } /// Compute new open interest. /// # Arguments /// * `params` - Price impact in usd. /// * `long_open_interest` - Long positions open interest. -/// * `long_open_interest` - Short positions open interest. +/// * `short _open_interest` - Short positions open interest. /// # Returns /// New open interest. fn get_next_open_interest_params( - params: GetPriceImpactUsdParams, long_open_interest: u128, short_open_interest: u128 + params: GetPriceImpactUsdParams, long_open_interest: u256, short_open_interest: u256 ) -> OpenInterestParams { - // TODO - OpenInterestParams { - long_open_interest: 0, - short_open_interest: 0, - next_long_open_interest: 0, - next_short_open_interest: 0, + let mut next_long_open_interest = long_open_interest; + let mut next_short_open_interest = short_open_interest; + + if (params.is_long) { + if (params.usd_delta < Zeroable::zero() + && calc::to_unsigned(i256_neg(params.usd_delta)) > long_open_interest) { + PricingError::USD_DELTA_EXCEEDS_LONG_OPEN_INTEREST(params.usd_delta, long_open_interest) + } + + next_long_open_interest = calc::sum_return_uint_256(long_open_interest, params.usd_delta); + } else { + if (params.usd_delta < Zeroable::zero() + && calc::to_unsigned(i256_neg(params.usd_delta)) > short_open_interest) { + PricingError::USD_DELTA_EXCEEDS_SHORT_OPEN_INTEREST( + params.usd_delta, short_open_interest + ) + } + + next_short_open_interest = calc::sum_return_uint_256(short_open_interest, params.usd_delta); } + + let open_interest_params = OpenInterestParams { + long_open_interest, short_open_interest, next_long_open_interest, next_short_open_interest + }; + + open_interest_params } /// Compute position fees. @@ -237,53 +381,78 @@ fn get_next_open_interest_params( /// * `params` - parameters to compute position fees. /// # Returns /// Position fees. -fn get_position_fees(params: GetPositionFeesParams,) -> PositionFees { - // TODO - let address_zero: ContractAddress = 0.try_into().unwrap(); - let position_referral_fees = PositionReferralFees { - referral_code: 0, - affiliate: address_zero, - trader: address_zero, - total_rebate_factor: 0, - trader_discount_factor: 0, - total_rebate_amount: 0, - trader_discount_amount: 0, - affiliate_reward_amount: 0, - }; - let position_funding_fees = PositionFundingFees { - funding_fee_amount: 0, - claimable_long_token_amount: 0, - claimable_short_token_amount: 0, - latest_funding_fee_amount_per_size: 0, - latest_long_token_claimable_funding_amount_per_size: 0, - latest_short_token_claimable_funding_amount_per_size: 0, - }; - let position_borrowing_fees = PositionBorrowingFees { - borrowing_fee_usd: 0, - borrowing_fee_amount: 0, - borrowing_fee_receiver_factor: 0, - borrowing_fee_amount_for_fee_receiver: 0, - }; - let position_ui_fees = PositionUiFees { - ui_fee_receiver: address_zero, ui_fee_receiver_factor: 0, ui_fee_amount: 0, - }; - let price = Price { min: 0, max: 0, }; - PositionFees { - referral: position_referral_fees, - funding: position_funding_fees, - borrowing: position_borrowing_fees, - ui: position_ui_fees, - collateral_token_price: price, - position_fee_factor: 0, - protocol_fee_amount: 0, - position_fee_receiver_factor: 0, - fee_receiver_amount: 0, - fee_amount_for_pool: 0, - position_fee_amount_for_pool: 0, - position_fee_amount: 0, - total_cost_amount_excluding_funding: 0, - total_cost_amount: 0, - } +fn get_position_fees(params: GetPositionFeesParams) -> PositionFees { + let mut fees = get_position_fees_after_referral( + params.data_store, + params.referral_storage, + params.collateral_token_price, + params.for_positive_impact, + params.position.account, + params.position.market, + params.size_delta_usd + ); + + let borrowing_fee_usd = market_utils::get_borrowing_fees(params.data_store, @params.position); + + fees + .borrowing = + get_borrowing_fees(params.data_store, params.collateral_token_price, borrowing_fee_usd); + + fees.fee_amount_for_pool = fees.position_fee_amount_for_pool + + fees.borrowing.borrowing_fee_amount + - fees.borrowing.borrowing_fee_amount_for_fee_receiver; + fees.fee_receiver_amount += fees.borrowing.borrowing_fee_amount_for_fee_receiver; + + fees + .funding + .latest_funding_fee_amount_per_size = + market_utils::get_funding_fee_amount_per_size( + params.data_store, + params.position.market, + params.position.collateral_token, + params.position.is_long + ); + + fees + .funding + .latest_long_token_claimable_funding_amount_per_size = + market_utils::get_claimable_funding_amount_per_size( + params.data_store, + params.position.market, + params.long_token, + params.position.is_long + ); + + fees + .funding + .latest_short_token_claimable_funding_amount_per_size = + market_utils::get_claimable_funding_amount_per_size( + params.data_store, + params.position.market, + params.short_token, + params.position.is_long + ); + + fees.funding = get_funding_fees(fees.funding, params.position); + + fees + .ui = + get_ui_fees( + params.data_store, + params.collateral_token_price, + params.size_delta_usd, + params.ui_fee_receiver + ); + + fees.total_cost_amount_excluding_funding = fees.position_fee_amount + + fees.borrowing.borrowing_fee_amount + + fees.ui.ui_fee_amount + - fees.referral.trader_discount_amount; + + fees.total_cost_amount = fees.total_cost_amount_excluding_funding + + fees.funding.funding_fee_amount; + + fees } /// Compute borrowing fees data. @@ -294,14 +463,18 @@ fn get_position_fees(params: GetPositionFeesParams,) -> PositionFees { /// # Returns /// Borrowing fees. fn get_borrowing_fees( - data_store: IDataStoreDispatcher, collateral_token_price: Price, borrowing_fee_usd: u128, + data_store: IDataStoreDispatcher, collateral_token_price: Price, borrowing_fee_usd: u256, ) -> PositionBorrowingFees { - // TODO + error_utils::check_division_by_zero(collateral_token_price.min, 'collateral_token_price.min'); + let borrowing_fee_amount = borrowing_fee_usd / collateral_token_price.min; + let borrowing_fee_receiver_factor = data_store.get_u256(keys::borrowing_fee_receiver_factor()); PositionBorrowingFees { - borrowing_fee_usd: 0, - borrowing_fee_amount: 0, - borrowing_fee_receiver_factor: 0, - borrowing_fee_amount_for_fee_receiver: 0, + borrowing_fee_usd, + borrowing_fee_amount, + borrowing_fee_receiver_factor, + borrowing_fee_amount_for_fee_receiver: precision::apply_factor_u256( + borrowing_fee_amount, borrowing_fee_receiver_factor + ) } } @@ -310,35 +483,69 @@ fn get_borrowing_fees( /// * `funding_fees` - The position funding fees struct to store fees. /// * `position` - The position to compute funding fees for. /// # Returns -/// Borrowing fees. -fn get_funding_fees(funding_fees: PositionFundingFees, position: Position,) -> PositionFundingFees { - // TODO - PositionFundingFees { - funding_fee_amount: 0, - claimable_long_token_amount: 0, - claimable_short_token_amount: 0, - latest_funding_fee_amount_per_size: 0, - latest_long_token_claimable_funding_amount_per_size: 0, - latest_short_token_claimable_funding_amount_per_size: 0, - } +/// Funding fees. +fn get_funding_fees( + mut funding_fees: PositionFundingFees, position: Position +) -> PositionFundingFees { + funding_fees + .funding_fee_amount = + market_utils::get_funding_amount( + funding_fees.latest_funding_fee_amount_per_size, + position.funding_fee_amount_per_size, + position.size_in_usd, + true // roundUpMagnitude + ); + + funding_fees + .claimable_long_token_amount = + market_utils::get_funding_amount( + funding_fees.latest_long_token_claimable_funding_amount_per_size, + position.long_token_claimable_funding_amount_per_size, + position.size_in_usd, + false // roundUpMagnitude + ); + + funding_fees + .claimable_short_token_amount = + market_utils::get_funding_amount( + funding_fees.latest_short_token_claimable_funding_amount_per_size, + position.short_token_claimable_funding_amount_per_size, + position.size_in_usd, + false // roundUpMagnitude + ); + + funding_fees } /// Compute ui fees. /// # Arguments /// * `data_store` - The `DataStore` contract dispatcher. /// * `collateral_token_price` - The price of the collateral token. +/// * `size_delta_usd` - Size delta usd /// * `ui_fee receiver` - The ui fee receiver address. /// # Returns -/// Borrowing fees. +/// Ui fees. fn get_ui_fees( data_store: IDataStoreDispatcher, collateral_token_price: Price, - size_delta_usd: u128, + size_delta_usd: u256, ui_fee_receiver: ContractAddress ) -> PositionUiFees { - // TODO - let address_zero: ContractAddress = 0.try_into().unwrap(); - PositionUiFees { ui_fee_receiver: address_zero, ui_fee_receiver_factor: 0, ui_fee_amount: 0, } + let mut ui_fees: PositionUiFees = Default::default(); + + if (ui_fee_receiver == contract_address_const::<0>()) { + return ui_fees; + } + + ui_fees.ui_fee_receiver = ui_fee_receiver; + ui_fees.ui_fee_receiver_factor = market_utils::get_ui_fee_factor(data_store, ui_fee_receiver); + error_utils::check_division_by_zero(collateral_token_price.min, 'collateral_token_price.min'); + ui_fees + .ui_fee_amount = + precision::apply_factor_u256(size_delta_usd, ui_fees.ui_fee_receiver_factor) + / collateral_token_price.min; + + ui_fees } /// Get position fees after applying referral rebates / discounts. @@ -358,52 +565,62 @@ fn get_position_fees_after_referral( for_positive_impact: bool, account: ContractAddress, market: ContractAddress, - size_delta_usd: u128, + size_delta_usd: u256, ) -> PositionFees { - // TODO - let address_zero: ContractAddress = 0.try_into().unwrap(); - let position_referral_fees = PositionReferralFees { - referral_code: 0, - affiliate: address_zero, - trader: address_zero, - total_rebate_factor: 0, - trader_discount_factor: 0, - total_rebate_amount: 0, - trader_discount_amount: 0, - affiliate_reward_amount: 0, - }; - let position_funding_fees = PositionFundingFees { - funding_fee_amount: 0, - claimable_long_token_amount: 0, - claimable_short_token_amount: 0, - latest_funding_fee_amount_per_size: 0, - latest_long_token_claimable_funding_amount_per_size: 0, - latest_short_token_claimable_funding_amount_per_size: 0, - }; - let position_borrowing_fees = PositionBorrowingFees { - borrowing_fee_usd: 0, - borrowing_fee_amount: 0, - borrowing_fee_receiver_factor: 0, - borrowing_fee_amount_for_fee_receiver: 0, - }; - let position_ui_fees = PositionUiFees { - ui_fee_receiver: address_zero, ui_fee_receiver_factor: 0, ui_fee_amount: 0, - }; - let price = Price { min: 0, max: 0, }; - PositionFees { - referral: position_referral_fees, - funding: position_funding_fees, - borrowing: position_borrowing_fees, - ui: position_ui_fees, - collateral_token_price: price, - position_fee_factor: 0, - protocol_fee_amount: 0, - position_fee_receiver_factor: 0, - fee_receiver_amount: 0, - fee_amount_for_pool: 0, - position_fee_amount_for_pool: 0, - position_fee_amount: 0, - total_cost_amount_excluding_funding: 0, - total_cost_amount: 0, - } + let mut fees: PositionFees = Default::default(); + + fees.collateral_token_price = collateral_token_price; + + fees.referral.trader = account; + + let (referral_code, affiliate, total_rebate_factor, trader_discount_factor) = + referral_utils::get_referral_info( + referral_storage, account + ); + + fees.referral.referral_code = referral_code; + fees.referral.affiliate = affiliate; + fees.referral.total_rebate_factor = total_rebate_factor; + fees.referral.trader_discount_factor = trader_discount_factor; + + /// note that since it is possible to incur both positive and negative price impact values + /// and the negative price impact factor may be larger than the positive impact factor + /// it is possible for the balance to be improved overall but for the price impact to still be negative + /// in this case the fee factor for the negative price impact would be charged + /// a user could split the order into two, to incur a smaller fee, reducing the fee through this should not be a large issue + fees + .position_fee_factor = data_store + .get_u256(keys::position_fee_factor_key(market, for_positive_impact)); + error_utils::check_division_by_zero(collateral_token_price.min, 'collateral_token_price.min'); + fees + .position_fee_amount = + precision::apply_factor_u256(size_delta_usd, fees.position_fee_factor) + / collateral_token_price.min; + + fees + .referral + .total_rebate_amount = + precision::apply_factor_u256( + fees.position_fee_amount, fees.referral.total_rebate_factor + ); + fees + .referral + .trader_discount_amount = + precision::apply_factor_u256( + fees.referral.total_rebate_amount, fees.referral.trader_discount_factor + ); + fees.referral.affiliate_reward_amount = fees.referral.total_rebate_amount + - fees.referral.trader_discount_amount; + + fees.protocol_fee_amount = fees.position_fee_amount - fees.referral.total_rebate_amount; + + fees.position_fee_receiver_factor = data_store.get_u256(keys::position_fee_receiver_factor()); + fees + .fee_receiver_amount = + precision::apply_factor_u256( + fees.protocol_fee_amount, fees.position_fee_receiver_factor + ); + fees.position_fee_amount_for_pool = fees.protocol_fee_amount - fees.fee_receiver_amount; + + fees } diff --git a/src/pricing/pricing_utils.cairo b/src/pricing/pricing_utils.cairo index a81d132c..063cb85e 100644 --- a/src/pricing/pricing_utils.cairo +++ b/src/pricing/pricing_utils.cairo @@ -4,14 +4,15 @@ // IMPORTS // ************************************************************************* use satoru::utils::{precision, calc}; +use satoru::utils::i256::i256; /// Get the price impact USD if there is no crossover in balance /// a crossover in balance is for example if the long open interest is larger /// than the short open interest, and a short position is opened such that the /// short open interest becomes larger than the long open interest. fn get_price_impact_usd_for_same_side_rebalance( - initial_diff_usd: u128, next_diff_usd: u128, impact_factor: u128, impact_exponent_factor: u128, -) -> i128 { + initial_diff_usd: u256, next_diff_usd: u256, impact_factor: u256, impact_exponent_factor: u256, +) -> i256 { let has_positive_impact: bool = next_diff_usd < initial_diff_usd; let delta_diff_usd = calc::diff( @@ -26,13 +27,13 @@ fn get_price_impact_usd_for_same_side_rebalance( /// a crossover in balance is for example if the long open interest is larger /// than the short open interest, and a short position is opened such that the /// short open interest becomes larger than the long open interest. -fn get_price_impact_usd_for_crossover_side_rebalance( - initial_diff_usd: u128, - next_diff_usd: u128, - positive_impact_factor: u128, - negative_impact_factor: u128, - impact_exponent_factor: u128, -) -> i128 { +fn get_price_impact_usd_for_crossover_rebalance( + initial_diff_usd: u256, + next_diff_usd: u256, + positive_impact_factor: u256, + negative_impact_factor: u256, + impact_exponent_factor: u256, +) -> i256 { let positive_impact_usd = apply_impact_factor( initial_diff_usd, positive_impact_factor, impact_exponent_factor ); @@ -45,7 +46,7 @@ fn get_price_impact_usd_for_crossover_side_rebalance( } /// Apply the impact factor calculation to a USD diff value. -fn apply_impact_factor(diff_usd: u128, impact_factor: u128, impact_exponent_factor: u128,) -> u128 { +fn apply_impact_factor(diff_usd: u256, impact_factor: u256, impact_exponent_factor: u256,) -> u256 { let exponent_value = precision::apply_exponent_factor(diff_usd, impact_exponent_factor); - precision::apply_factor_u128(exponent_value, impact_factor) + precision::apply_factor_u256(exponent_value, impact_factor) } diff --git a/src/pricing/swap_pricing_utils.cairo b/src/pricing/swap_pricing_utils.cairo index cc1504c3..05e9dce8 100644 --- a/src/pricing/swap_pricing_utils.cairo +++ b/src/pricing/swap_pricing_utils.cairo @@ -10,57 +10,63 @@ use result::ResultTrait; // Local imports. use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::data::keys; use satoru::market::market::Market; -use satoru::utils::i128::{StoreI128, I128Serde, I128Div, I128Mul, i128_to_u128, u128_to_i128}; +use satoru::market::market_utils; +use satoru::pricing::error::PricingError; +use satoru::pricing::pricing_utils; +use satoru::utils::calc; +use satoru::utils::precision; +use satoru::utils::i256::{i256, i256_neg}; /// Struct used in get_price_impact_usd. -#[derive(Drop, starknet::Store, Serde)] +#[derive(Copy, Drop, starknet::Store, Serde)] struct GetPriceImpactUsdParams { /// The `DataStore` contract dispatcher. - dataStore: IDataStoreDispatcher, + data_store: IDataStoreDispatcher, /// The market to check. market: Market, /// The token to check balance for. token_a: ContractAddress, /// The token to check balance for. token_b: ContractAddress, - price_for_token_a: u128, - price_for_token_b: u128, + price_for_token_a: u256, + price_for_token_b: u256, // The USD change in amount of token_a. - usd_delta_for_token_a: i128, + usd_delta_for_token_a: i256, // The USD change in amount of token_b. - usd_delta_for_token_b: i128, + usd_delta_for_token_b: i256, } /// Struct to contain pool values. #[derive(Drop, starknet::Store, Serde)] struct PoolParams { /// The USD value of token_a in the pool. - pool_usd_for_token_a: u128, + pool_usd_for_token_a: u256, /// The USD value of token_b in the pool. - pool_usd_for_token_b: u128, + pool_usd_for_token_b: u256, /// The next USD value of token_a in the pool. - next_pool_usd_for_token_a: u128, + next_pool_usd_for_token_a: u256, /// The next USD value of token_b in the pool. - next_pool_usd_for_token_b: u128, + next_pool_usd_for_token_b: u256, } /// 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, + fee_receiver_amount: u256, /// The fee amount for the pool. - fee_amount_for_pool: u128, + fee_amount_for_pool: u256, /// The output amount after fees. - amount_after_fees: u128, + amount_after_fees: u256, /// The ui fee receiver. ui_fee_receiver: ContractAddress, /// The factor for receiver. - ui_fee_receiver_factor: u128, + ui_fee_receiver_factor: u256, /// The ui fee amount. - ui_fee_amount: u128, + ui_fee_amount: u256, } impl DefaultSwapFees of Default { @@ -76,14 +82,6 @@ impl DefaultSwapFees of Default { } } -/// Called by get_price_impact_usd(). -/// # Returns -/// The price impact in USD. -fn get_price_impact_usd(params: GetPriceImpactUsdParams) -> i128 { - // TODO - 0 -} - /// Get the price impact in USD /// /// Note that there will be some difference between the pool amounts used for @@ -99,16 +97,138 @@ fn get_price_impact_usd(params: GetPriceImpactUsdParams) -> i128 { /// * `params` - The necessary params to compute next pool amount in USD. /// # Returns /// New pool amount. -fn get_next_pool_amount_usd(params: GetPriceImpactUsdParams) -> PoolParams { - // TODO - PoolParams { - pool_usd_for_token_a: 0, - pool_usd_for_token_b: 0, - next_pool_usd_for_token_a: 0, - next_pool_usd_for_token_b: 0, +fn get_price_impact_usd(params: GetPriceImpactUsdParams) -> i256 { + let pool_params = get_next_pool_amount_usd(params); + let price_impact_usd = get_price_impact_usd_(params.data_store, params.market, pool_params); + + // the virtual price impact calculation is skipped if the price impact + // is positive since the action is helping to balance the pool + // + // in case two virtual pools are unbalanced in a different direction + // e.g. pool0 has more WNT than USDC while pool1 has less WNT + // than USDT + // not skipping the virtual price impact calculation would lead to + // a negative price impact for any trade on either pools and would + // disincentivise the balancing of pools + if price_impact_usd >= Zeroable::zero() { + return price_impact_usd; + } + + // note that the virtual pool for the long token / short token may be different across pools + // e.g. ETH/USDC, ETH/USDT would have USDC and USDT as the short tokens + // the short token amount is multiplied by the price of the token in the current pool, e.g. if the swap + // is for the ETH/USDC pool, the combined USDC and USDT short token amounts is multiplied by the price of + // USDC to calculate the price impact, this should be reasonable most of the time unless there is a + // large depeg of one of the tokens, in which case it may be necessary to remove that market from being a virtual + // market, removal of virtual markets may lead to incorrect virtual token accounting, the feature to correct for + // this can be added if needed + let ( + has_virtual_inventory, + virtual_pool_amount_for_long_token, + virtual_pool_amount_for_short_token + ) = + market_utils::get_virtual_inventory_for_swaps( + params.data_store, params.market.market_token + ); + + if !has_virtual_inventory { + return price_impact_usd; + } + + let token_a_is_long = params.token_a == params.market.long_token; + let (virtual_pool_amount_for_token_a, virtual_pool_amount_for_token_b) = if token_a_is_long { + (virtual_pool_amount_for_long_token, virtual_pool_amount_for_short_token) + } else { + (virtual_pool_amount_for_short_token, virtual_pool_amount_for_long_token) + }; + + let pool_params_for_virtual_inventory = get_next_pool_amount_params( + params, virtual_pool_amount_for_token_a, virtual_pool_amount_for_token_b + ); + + let price_impact_usd_for_virtual_inventory = get_price_impact_usd_( + params.data_store, params.market, pool_params_for_virtual_inventory + ); + + if price_impact_usd_for_virtual_inventory < price_impact_usd { + price_impact_usd_for_virtual_inventory + } else { + price_impact_usd } } +/// Called by get_price_impact_usd(). +/// # Arguments +/// * `data_store` - DataStore +/// * `market` - the trading market +/// * `pool_params` - PoolParams +/// # Returns +/// The price impact in USD. +fn get_price_impact_usd_( + data_store: IDataStoreDispatcher, market: Market, pool_params: PoolParams, +) -> i256 { + let initial_diff_usd = calc::diff( + pool_params.pool_usd_for_token_a, pool_params.pool_usd_for_token_b + ); + let next_diff_usd = calc::diff( + pool_params.next_pool_usd_for_token_a, pool_params.next_pool_usd_for_token_b + ); + + // check whether an improvement in balance comes from causing the balance to switch sides + // for example, if there is $2000 of ETH and $1000 of USDC in the pool + // adding $1999 USDC into the pool will reduce absolute balance from $1000 to $999 but it does not + // help rebalance the pool much, the isSameSideRebalance value helps avoid gaming using this case + + let a_lte_b = pool_params.pool_usd_for_token_a <= pool_params.pool_usd_for_token_b; + let next_a_lte_b = pool_params + .next_pool_usd_for_token_a <= pool_params + .next_pool_usd_for_token_b; + let is_same_side_rebalance = a_lte_b == next_a_lte_b; + let impact_exponent_factor = data_store + .get_u256(keys::swap_impact_exponent_factor_key(market.market_token)); + + if is_same_side_rebalance { + let has_positive_impact = next_diff_usd < initial_diff_usd; + let impact_factor = market_utils::get_adjusted_swap_impact_factor( + data_store, market.market_token, has_positive_impact + ); + + pricing_utils::get_price_impact_usd_for_same_side_rebalance( + initial_diff_usd, next_diff_usd, impact_factor, impact_exponent_factor + ) + } else { + let (positive_impact_factor, negative_impact_factor) = + market_utils::get_adjusted_swap_impact_factors( + data_store, market.market_token + ); + + pricing_utils::get_price_impact_usd_for_crossover_rebalance( + initial_diff_usd, + next_diff_usd, + positive_impact_factor, + negative_impact_factor, + impact_exponent_factor + ) + } +} + +/// Get the next pool amounts in USD +/// # Arguments +/// `params` - GetPriceImpactUsdParams +/// # Returns +/// PoolParams +fn get_next_pool_amount_usd(params: GetPriceImpactUsdParams) -> PoolParams { + let pool_amount_for_token_a = market_utils::get_pool_amount( + params.data_store, @params.market, params.token_a + ); + + let pool_amount_for_token_b = market_utils::get_pool_amount( + params.data_store, @params.market, params.token_b + ); + + get_next_pool_amount_params(params, pool_amount_for_token_a, pool_amount_for_token_b) +} + /// Get the new pool values. /// # Arguments /// * `params` - The necessary params to compute price impact. @@ -117,14 +237,35 @@ fn get_next_pool_amount_usd(params: GetPriceImpactUsdParams) -> PoolParams { /// # Returns /// New pool values. fn get_next_pool_amount_params( - params: GetPriceImpactUsdParams, pool_amount_for_token_a: u128, pool_amount_for_token_b: u128 + params: GetPriceImpactUsdParams, pool_amount_for_token_a: u256, pool_amount_for_token_b: u256 ) -> PoolParams { - // TODO + let pool_usd_for_token_a = pool_amount_for_token_a * params.price_for_token_a; + let pool_usd_for_token_b = pool_amount_for_token_b * params.price_for_token_b; + if params.usd_delta_for_token_a < Zeroable::zero() + && calc::to_unsigned(i256_neg(params.usd_delta_for_token_a)) > pool_usd_for_token_a { + PricingError::USD_DELTA_EXCEEDS_POOL_VALUE( + params.usd_delta_for_token_a.into(), pool_usd_for_token_a.into() + ); + } + if params.usd_delta_for_token_b < Zeroable::zero() + && calc::to_unsigned(i256_neg(params.usd_delta_for_token_b)) > pool_usd_for_token_b { + PricingError::USD_DELTA_EXCEEDS_POOL_VALUE( + params.usd_delta_for_token_b.into(), pool_usd_for_token_b.into() + ); + } + + let next_pool_usd_for_token_a = calc::sum_return_uint_256( + pool_usd_for_token_a, params.usd_delta_for_token_a + ); + let next_pool_usd_for_token_b = calc::sum_return_uint_256( + pool_usd_for_token_b, params.usd_delta_for_token_b + ); + PoolParams { - pool_usd_for_token_a: 0, - pool_usd_for_token_b: 0, - next_pool_usd_for_token_a: 0, - next_pool_usd_for_token_b: 0, + pool_usd_for_token_a, + pool_usd_for_token_b, + next_pool_usd_for_token_a, + next_pool_usd_for_token_b, } } @@ -140,18 +281,36 @@ fn get_next_pool_amount_params( fn get_swap_fees( data_store: IDataStoreDispatcher, market_token: ContractAddress, - amount: u128, + amount: u256, for_positive_impact: bool, ui_fee_receiver: ContractAddress, ) -> SwapFees { - // TODO - let address_zero: ContractAddress = 0.try_into().unwrap(); + // note that since it is possible to incur both positive and negative price impact values + // and the negative price impact factor may be larger than the positive impact factor + // it is possible for the balance to be improved overall but for the price impact to still be negative + // in this case the fee factor for the negative price impact would be charged + // a user could split the order into two, to incur a smaller fee, reducing the fee through this should not be a large issue + + let fee_factor = data_store + .get_u256(keys::swap_fee_factor_key(market_token, for_positive_impact)); + let swap_fee_receiver_factor = data_store.get_u256(keys::swap_fee_receiver_factor()); + + let fee_amount = precision::apply_factor_u256(amount, fee_factor); + + let fee_receiver_amount = precision::apply_factor_u256(fee_amount, swap_fee_receiver_factor); + let fee_amount_for_pool = fee_amount - fee_receiver_amount; + + let ui_fee_receiver_factor = market_utils::get_ui_fee_factor(data_store, ui_fee_receiver); + let ui_fee_amount = precision::apply_factor_u256(amount, ui_fee_receiver_factor); + + let amount_after_fees = amount - fee_amount - ui_fee_amount; + SwapFees { - fee_receiver_amount: 0, - fee_amount_for_pool: 0, - amount_after_fees: 0, - ui_fee_receiver: address_zero, - ui_fee_receiver_factor: 0, - ui_fee_amount: 0, + fee_receiver_amount, + fee_amount_for_pool, + amount_after_fees, + ui_fee_receiver, + ui_fee_receiver_factor, + ui_fee_amount, } } diff --git a/src/reader/reader.cairo b/src/reader/reader.cairo index 6480cf8c..3530cc78 100644 --- a/src/reader/reader.cairo +++ b/src/reader/reader.cairo @@ -29,20 +29,20 @@ use satoru::withdrawal::withdrawal::Withdrawal; use satoru::position::{position_utils, position::Position}; use satoru::pricing::swap_pricing_utils::SwapFees; use satoru::deposit::deposit::Deposit; -use satoru::utils::{i128::{StoreI128, I128Serde, u128_to_i128, i128_to_u128, I128Div, I128Mul}}; +use satoru::utils::i256::i256; #[derive(Drop, starknet::Store, Serde)] struct VirtualInventory { - virtual_pool_amount_for_long_token: u128, - virtual_pool_amount_for_short_token: u128, - virtual_inventory_for_positions: i128, + virtual_pool_amount_for_long_token: u256, + virtual_pool_amount_for_short_token: u256, + virtual_inventory_for_positions: i256, } #[derive(Drop, starknet::Store, Serde)] struct MarketInfo { market: Market, - borrowing_factor_per_second_for_longs: u128, - borrowing_factor_per_second_for_shorts: u128, + borrowing_factor_per_second_for_longs: u256, + borrowing_factor_per_second_for_shorts: u256, base_funding: BaseFundingValues, next_funding: GetNextFundingAmountPerSizeResult, virtual_inventory: VirtualInventory, @@ -128,8 +128,8 @@ trait IReader { market: Market, prices: MarketPrices, position_key: felt252, - size_delta_usd: u128 - ) -> (i128, i128, u128); + size_delta_usd: u256 + ) -> (i256, i256, u256); /// Retrieve an array of position data associated with a specific account within a specified range. /// # Arguments @@ -182,7 +182,7 @@ trait IReader { referral_storage: IReferralStorageDispatcher, position_key: felt252, prices: MarketPrices, - size_delta_usd: u128, + size_delta_usd: u256, ui_fee_receiver: ContractAddress, use_position_size_as_size_delta_usd: bool ) -> PositionInfo; @@ -263,7 +263,7 @@ trait IReader { short_token_price: Price, pnl_factor_type: felt252, maximize: bool - ) -> (i128, MarketPoolValueInfo); + ) -> (i256, MarketPoolValueInfo); /// Calculate and return the net profit and loss (PnL) for a specific market based on various input parameters. /// # Arguments @@ -279,7 +279,7 @@ trait IReader { market: Market, index_token_price: Price, maximize: bool - ) -> i128; + ) -> i256; /// Calculate and return the profit and loss (PnL) for a specific market position, either long or short, based on various input parameters. /// # Arguments @@ -296,7 +296,7 @@ trait IReader { index_token_price: Price, is_long: bool, maximize: bool - ) -> i128; + ) -> i256; /// Calculate and return the open interest with profit and loss (PnL) for a specific market position. /// # Arguments @@ -313,7 +313,7 @@ trait IReader { index_token_price: Price, is_long: bool, maximize: bool - ) -> i128; + ) -> i256; /// Calculate and return the profit and loss (PnL) to pool factor for a specific market position. @@ -331,7 +331,7 @@ trait IReader { prices: MarketPrices, is_long: bool, maximize: bool - ) -> i128; + ) -> i256; /// Calculate and return various values related to a swap operation, including the amount of the output token, fees associated with the swap, and other information. /// # Arguments @@ -351,9 +351,9 @@ trait IReader { market: Market, prices: MarketPrices, token_in: ContractAddress, - amount_in: u128, + amount_in: u256, ui_fee_receiver: ContractAddress - ) -> (u128, i128, SwapFees); + ) -> (u256, i256, SwapFees); /// Calculate and return information about the virtual inventory for a specific market. /// # Arguments @@ -381,9 +381,9 @@ trait IReader { data_store: IDataStoreDispatcher, market_key: ContractAddress, index_token_price: Price, - position_size_in_usd: u128, - position_size_in_token: u128, - size_delta_usd: i128, + position_size_in_usd: u256, + position_size_in_token: u256, + size_delta_usd: i256, is_long: bool ) -> ExecutionPriceResult; @@ -404,10 +404,10 @@ trait IReader { market_key: ContractAddress, token_in: ContractAddress, token_out: ContractAddress, - amount_in: u128, + amount_in: u256, token_in_price: Price, token_out_price: Price - ) -> (i128, i128); + ) -> (i256, i256); /// Retrieve and return the state of the Account Deleveraging (ADL) system for a specific market and position (either long or short). /// # Arguments @@ -424,7 +424,17 @@ trait IReader { market: ContractAddress, is_long: bool, prices: MarketPrices - ) -> (u64, bool, i128, u128); + ) -> (u64, bool, i256, u256); + + fn is_position_liquidable( + self: @TContractState, + data_store: IDataStoreDispatcher, + referral_storage: IReferralStorageDispatcher, + position: Position, + market: Market, + prices: MarketPrices, + should_validate_min_collateral_usd: bool, + ) -> (bool, felt252); } #[starknet::contract] @@ -450,7 +460,7 @@ mod Reader { market_utils, market_utils::GetNextFundingAmountPerSizeResult, market::Market, market_utils::MarketPrices, market_pool_value_info::MarketPoolValueInfo, }; - use satoru::utils::{i128::{StoreI128, I128Serde, u128_to_i128, i128_to_u128, I128Div, I128Mul}}; + use satoru::utils::i256::i256; use satoru::withdrawal::withdrawal::Withdrawal; use satoru::position::{position_utils, position::Position}; use satoru::pricing::swap_pricing_utils::SwapFees; @@ -475,43 +485,43 @@ mod Reader { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl Reader of super::IReader { fn get_market( self: @ContractState, data_store: IDataStoreDispatcher, key: ContractAddress ) -> Market { - data_store.get_market(key).unwrap() + data_store.get_market(key) } fn get_market_by_salt( self: @ContractState, data_store: IDataStoreDispatcher, salt: felt252 ) -> Market { - data_store.get_by_salt_market(salt).unwrap() + data_store.get_by_salt_market(salt) } fn get_deposit( self: @ContractState, data_store: IDataStoreDispatcher, key: felt252 ) -> Deposit { - data_store.get_deposit(key).unwrap() + data_store.get_deposit(key) } fn get_withdrawal( self: @ContractState, data_store: IDataStoreDispatcher, key: felt252 ) -> Withdrawal { - data_store.get_withdrawal(key).unwrap() + data_store.get_withdrawal(key) } fn get_position( self: @ContractState, data_store: IDataStoreDispatcher, key: felt252 ) -> Position { - data_store.get_position(key).unwrap() + data_store.get_position(key) } fn get_order( self: @ContractState, data_store: IDataStoreDispatcher, key: felt252 ) -> Order { - data_store.get_order(key).unwrap() + data_store.get_order(key) } fn get_position_pnl_usd( @@ -520,9 +530,9 @@ mod Reader { market: Market, prices: MarketPrices, position_key: felt252, - size_delta_usd: u128 - ) -> (i128, i128, u128) { - let position = data_store.get_position(position_key).unwrap(); + size_delta_usd: u256 + ) -> (i256, i256, u256) { + let position = data_store.get_position(position_key); position_utils::get_position_pnl_usd( data_store, market, prices, position, size_delta_usd ) @@ -543,7 +553,7 @@ mod Reader { if i == length { break; } - let position = data_store.get_position(*position_keys.at(i)).unwrap(); + let position = data_store.get_position(*position_keys.at(i)); positions.append(position); i += 1; }; @@ -588,7 +598,7 @@ mod Reader { referral_storage: IReferralStorageDispatcher, position_key: felt252, prices: MarketPrices, - size_delta_usd: u128, + size_delta_usd: u256, ui_fee_receiver: ContractAddress, use_position_size_as_size_delta_usd: bool ) -> PositionInfo { @@ -618,7 +628,7 @@ mod Reader { if i == length { break; } - let order = data_store.get_order(*order_keys.at(i)).unwrap(); + let order = data_store.get_order(*order_keys.at(i)); orders.append(order); i += 1; }; @@ -636,7 +646,7 @@ mod Reader { if i == length { break; } - let market = data_store.get_market(*market_keys.at(i)).unwrap(); + let market = data_store.get_market(*market_keys.at(i)); markets.append(market); i += 1; }; @@ -672,7 +682,7 @@ mod Reader { prices: MarketPrices, market_key: ContractAddress ) -> MarketInfo { - let market = data_store.get_market(market_key).unwrap(); + let market = data_store.get_market(market_key); let borrowing_factor_per_second_for_longs = market_utils::get_borrowing_factor_per_second( data_store, market, prices, true @@ -690,8 +700,7 @@ mod Reader { let virtual_inventory = self.get_virtual_inventory(data_store, market); let is_disabled = data_store - .get_bool(keys::is_market_disabled_key(market.market_token)) - .unwrap(); + .get_bool(keys::is_market_disabled_key(market.market_token)); MarketInfo { market, borrowing_factor_per_second_for_longs, @@ -712,7 +721,7 @@ mod Reader { short_token_price: Price, pnl_factor_type: felt252, maximize: bool - ) -> (i128, MarketPoolValueInfo) { + ) -> (i256, MarketPoolValueInfo) { market_utils::get_market_token_price( data_store, market, @@ -730,7 +739,7 @@ mod Reader { market: Market, index_token_price: Price, maximize: bool - ) -> i128 { + ) -> i256 { market_utils::get_net_pnl(data_store, @market, @index_token_price, maximize) } @@ -741,7 +750,7 @@ mod Reader { index_token_price: Price, is_long: bool, maximize: bool - ) -> i128 { + ) -> i256 { market_utils::get_pnl(data_store, @market, @index_token_price, is_long, maximize) } @@ -752,9 +761,9 @@ mod Reader { index_token_price: Price, is_long: bool, maximize: bool - ) -> i128 { + ) -> i256 { market_utils::get_open_interest_with_pnl( - data_store, market, index_token_price, is_long, maximize + data_store, @market, @index_token_price, is_long, maximize ) } @@ -765,10 +774,10 @@ mod Reader { prices: MarketPrices, is_long: bool, maximize: bool - ) -> i128 { - let market = data_store.get_market(market_address).unwrap(); + ) -> i256 { + let market = data_store.get_market(market_address); market_utils::get_pnl_to_pool_factor_from_prices( - data_store, market, prices, is_long, maximize + data_store, @market, @prices, is_long, maximize ) } @@ -778,9 +787,9 @@ mod Reader { market: Market, prices: MarketPrices, token_in: ContractAddress, - amount_in: u128, + amount_in: u256, ui_fee_receiver: ContractAddress - ) -> (u128, i128, SwapFees) { + ) -> (u256, i256, SwapFees) { reader_pricing_utils::get_swap_amount_out( data_store, market, prices, token_in, amount_in, ui_fee_receiver ) @@ -809,12 +818,12 @@ mod Reader { data_store: IDataStoreDispatcher, market_key: ContractAddress, index_token_price: Price, - position_size_in_usd: u128, - position_size_in_token: u128, - size_delta_usd: i128, + position_size_in_usd: u256, + position_size_in_token: u256, + size_delta_usd: i256, is_long: bool ) -> ExecutionPriceResult { - let market = data_store.get_market(market_key).unwrap(); + let market = data_store.get_market(market_key); reader_pricing_utils::get_execution_price( data_store, market, @@ -832,11 +841,11 @@ mod Reader { market_key: ContractAddress, token_in: ContractAddress, token_out: ContractAddress, - amount_in: u128, + amount_in: u256, token_in_price: Price, token_out_price: Price - ) -> (i128, i128) { - let market = data_store.get_market(market_key).unwrap(); + ) -> (i256, i256) { + let market = data_store.get_market(market_key); reader_pricing_utils::get_swap_price_impact( data_store, market, token_in, token_out, amount_in, token_in_price, token_out_price ) @@ -848,15 +857,34 @@ mod Reader { market: ContractAddress, is_long: bool, prices: MarketPrices - ) -> (u64, bool, i128, u128) { + ) -> (u64, bool, i256, u256) { let latest_adl_block = adl_utils::get_latest_adl_block(data_store, market, is_long); let _market = market_utils::get_enabled_market(data_store, market); let (should_enabled_ald, pnl_to_pool_factor, max_pnl_factor) = - market_utils::is_pnl_factor_exceeded_direct( + market_utils::is_pnl_factor_exceeded_check( data_store, _market, prices, is_long, keys::max_pnl_factor_for_adl() ); (latest_adl_block, should_enabled_ald, pnl_to_pool_factor, max_pnl_factor) } + + fn is_position_liquidable( + self: @ContractState, + data_store: IDataStoreDispatcher, + referral_storage: IReferralStorageDispatcher, + position: Position, + market: Market, + prices: MarketPrices, + should_validate_min_collateral_usd: bool + ) -> (bool, felt252) { + position_utils::is_position_liquiditable( + data_store, + referral_storage, + position, + market, + prices, + should_validate_min_collateral_usd + ) + } } } diff --git a/src/reader/reader_pricing_utils.cairo b/src/reader/reader_pricing_utils.cairo index ee7a023e..e9517714 100644 --- a/src/reader/reader_pricing_utils.cairo +++ b/src/reader/reader_pricing_utils.cairo @@ -27,6 +27,7 @@ use satoru::pricing::{ swap_pricing_utils::{SwapFees, get_swap_fees, get_price_impact_usd, GetPriceImpactUsdParams} }; use satoru::reader::error::ReaderError; +use satoru::utils::calc; use satoru::utils::span32::{Span32, Array32Trait}; use satoru::swap::{ swap_utils::SwapCache, swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait} @@ -35,16 +36,15 @@ use satoru::swap::{ use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; - use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; -use satoru::utils::i128::{StoreI128, I128Serde, I128Div, I128Mul, i128_to_u128, u128_to_i128}; +use satoru::utils::{i256::{i256, i256_neg}, error_utils}; -#[derive(Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde)] struct ExecutionPriceResult { - price_impact_usd: i128, - price_impact_diff_usd: u128, - execution_price: u128, + price_impact_usd: i256, + price_impact_diff_usd: u256, + execution_price: u256, } #[derive(Drop, starknet::Store, Serde)] @@ -52,17 +52,17 @@ struct PositionInfo { position: Position, fees: PositionFees, execution_price_result: ExecutionPriceResult, - base_pnl_usd: i128, - pnl_after_price_impact_usd: i128, + base_pnl_usd: i256, + pnl_after_price_impact_usd: i256, } #[derive(Drop, starknet::Store, Serde)] struct GetPositionInfoCache { market: Market, collateral_token_price: Price, - pending_borrowing_fee_usd: u128, - latest_long_token_funding_amount_per_size: i128, - latest_short_token_funding_amount_per_size: i128, + pending_borrowing_fee_usd: u256, + latest_long_token_funding_amount_per_size: i256, + latest_short_token_funding_amount_per_size: i256, } /// Calculates the output amount and fees for a token swap operation. @@ -80,49 +80,54 @@ fn get_swap_amount_out( market: Market, prices: MarketPrices, token_in: ContractAddress, - amount_in: u128, + amount_in: u256, ui_fee_receiver: ContractAddress -) -> (u128, i128, SwapFees) { +) -> (u256, i256, SwapFees) { let mut cache: SwapCache = Default::default(); if (token_in != market.long_token && token_in != market.short_token) { ReaderError::INVALID_TOKEN_IN(token_in, market.long_token); } - validate_swap_market(@data_store, @market); + validate_swap_market(data_store, market); - cache.token_out = get_opposite_token(@market, token_in); + cache.token_out = get_opposite_token(token_in, @market); cache.token_in_price = get_cached_token_price(token_in, market, prices); cache.token_out_price = get_cached_token_price(cache.token_out, market, prices); - let param: GetPriceImpactUsdParams = GetPriceImpactUsdParams { - dataStore: data_store, - market: market, + let param = GetPriceImpactUsdParams { + data_store, + market, token_a: token_in, token_b: cache.token_out, price_for_token_a: cache.token_in_price.mid_price(), price_for_token_b: cache.token_out_price.mid_price(), - usd_delta_for_token_a: u128_to_i128(amount_in * cache.token_in_price.mid_price()), - usd_delta_for_token_b: -u128_to_i128(amount_in * cache.token_in_price.mid_price()) + usd_delta_for_token_a: calc::to_signed(amount_in * cache.token_in_price.mid_price(), true), + usd_delta_for_token_b: calc::to_signed(amount_in * cache.token_in_price.mid_price(), false) }; - let price_impact_usd: i128 = get_price_impact_usd(param); + let price_impact_usd: i256 = get_price_impact_usd(param); let fees: SwapFees = get_swap_fees( - data_store, market.market_token, amount_in, price_impact_usd > 0, ui_fee_receiver + data_store, + market.market_token, + amount_in, + price_impact_usd > Zeroable::zero(), + ui_fee_receiver ); - let mut impact_amount: i128 = 0; + let mut impact_amount: i256 = Zeroable::zero(); - if (price_impact_usd > 0) { + if (price_impact_usd > Zeroable::zero()) { // when there is a positive price impact factor, additional tokens from the swap impact pool // are withdrawn for the user // for example, if 50,000 USDC is swapped out and there is a positive price impact // 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; cache.pool_amount_out = cache.amount_out; @@ -135,7 +140,7 @@ fn get_swap_amount_out( price_impact_usd ); - cache.amount_out += i128_to_u128(impact_amount); + cache.amount_out += calc::to_unsigned(impact_amount); } else { // when there is a negative price impact factor, // less of the input amount is sent to the pool @@ -148,7 +153,8 @@ fn get_swap_amount_out( data_store, market.market_token, token_in, cache.token_in_price, price_impact_usd ); - cache.amount_in = fees.amount_after_fees - i128_to_u128(-impact_amount); + cache.amount_in = fees.amount_after_fees - calc::to_unsigned(i256_neg(impact_amount)); + 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; cache.pool_amount_out = cache.amount_out; } @@ -170,9 +176,9 @@ fn get_execution_price( data_store: IDataStoreDispatcher, market: Market, index_token_price: Price, - position_size_in_usd: u128, - position_size_in_tokens: u128, - size_delta_usd: i128, + position_size_in_usd: u256, + position_size_in_tokens: u256, + size_delta_usd: i256, is_long: bool ) -> ExecutionPriceResult { let mut params: UpdatePositionParams = Default::default(); @@ -180,15 +186,15 @@ fn get_execution_price( params.contracts.data_store = data_store; params.market = market; - let size_delta_usd_abs = if size_delta_usd > 0 { + let size_delta_usd_abs = if size_delta_usd > Zeroable::zero() { size_delta_usd } else { - -size_delta_usd + i256_neg(size_delta_usd) }; - params.order.size_delta_usd = i128_to_u128(size_delta_usd_abs); + params.order.size_delta_usd = calc::to_unsigned(size_delta_usd_abs); params.order.is_long = is_long; - let is_increase: bool = size_delta_usd > 0; + let is_increase: bool = size_delta_usd > Zeroable::zero(); let should_execution_price_be_smaller = if is_increase { is_long } else { @@ -208,9 +214,9 @@ fn get_execution_price( params.position.is_long = is_long; let mut result: ExecutionPriceResult = ExecutionPriceResult { - price_impact_usd: 0, price_impact_diff_usd: 0, execution_price: 0, + price_impact_usd: Zeroable::zero(), price_impact_diff_usd: 0, execution_price: 0, }; - if size_delta_usd > 0 { + if size_delta_usd > Zeroable::zero() { let (price_impact_usd, _, _, execution_price) = increase_position_utils::get_execution_price( params, index_token_price @@ -247,27 +253,27 @@ fn get_swap_price_impact( market: Market, token_in: ContractAddress, token_out: ContractAddress, - amount_in: u128, + amount_in: u256, token_in_price: Price, token_out_price: Price -) -> (i128, i128) { +) -> (i256, i256) { let mut cache: SwapCache = Default::default(); let param: GetPriceImpactUsdParams = GetPriceImpactUsdParams { - dataStore: data_store, + data_store: data_store, market: market, token_a: token_in, token_b: token_out, price_for_token_a: token_in_price.mid_price(), price_for_token_b: token_out_price.mid_price(), - usd_delta_for_token_a: u128_to_i128(amount_in * token_in_price.mid_price()), - usd_delta_for_token_b: -u128_to_i128(amount_in * token_in_price.mid_price()) + usd_delta_for_token_a: calc::to_signed(amount_in * token_in_price.mid_price(), true), + usd_delta_for_token_b: calc::to_signed(amount_in * token_in_price.mid_price(), false) }; - let price_impact_usd_before_cap: i128 = get_price_impact_usd(param); + let price_impact_usd_before_cap: i256 = get_price_impact_usd(param); - let mut price_impact_amount = 0; - if price_impact_usd_before_cap > 0 { + let mut price_impact_amount = Zeroable::zero(); + if price_impact_usd_before_cap > Zeroable::zero() { price_impact_amount = get_swap_impact_amount_with_cap( data_store, diff --git a/src/reader/reader_utils.cairo b/src/reader/reader_utils.cairo index f9331b8f..ed80617b 100644 --- a/src/reader/reader_utils.cairo +++ b/src/reader/reader_utils.cairo @@ -6,7 +6,7 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; use core::traits::TryInto; use result::ResultTrait; @@ -17,35 +17,38 @@ use satoru::reader::reader_pricing_utils::ExecutionPriceResult; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::market::{ - market::Market, market_utils::PositionType, market_utils::MarketPrices, + market_utils, market::Market, market_utils::PositionType, market_utils::MarketPrices, market_utils::CollateralType, market_utils::GetNextFundingAmountPerSizeResult }; +use satoru::position::position_utils; +use satoru::reader::reader_pricing_utils; use satoru::price::price::Price; +use satoru::pricing::position_pricing_utils; use satoru::pricing::position_pricing_utils::PositionBorrowingFees; use satoru::pricing::position_pricing_utils::PositionReferralFees; use satoru::pricing::position_pricing_utils::PositionFundingFees; use satoru::pricing::position_pricing_utils::PositionUiFees; use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; -use satoru::utils::i128::{StoreI128, u128_to_i128, I128Serde, I128Div, I128Mul}; +use satoru::utils::{calc, i256::i256}; -#[derive(Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde)] struct PositionInfo { position: Position, fees: PositionFees, execution_price_result: ExecutionPriceResult, - base_pnl_usd: i128, - uncapped_base_pnl_usd: i128, - pnl_after_price_impact_usd: i128, + base_pnl_usd: i256, + uncapped_base_pnl_usd: i256, + pnl_after_price_impact_usd: i256, } -#[derive(Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde)] struct GetPositionInfoCache { market: Market, collateral_token_price: Price, - pending_borrowing_fee_usd: u128, + pending_borrowing_fee_usd: u256, } -#[derive(Drop, starknet::Store, Serde)] +#[derive(Default, Drop, starknet::Store, Serde)] struct BaseFundingValues { funding_fee_amount_per_size: PositionType, claimable_funding_amount_per_size: PositionType, @@ -61,28 +64,23 @@ struct BaseFundingValues { /// Returns an unsigned integer representing the calculated borrowing fees for the specified position within the market. fn get_next_borrowing_fees( data_store: IDataStoreDispatcher, position: Position, market: Market, prices: MarketPrices -) -> u128 { - // TODO - 0 +) -> u256 { + market_utils::get_next_borrowing_fees(data_store, @position, @market, @prices) } /// Designed to calculate and return borrowing fees for a specific position. /// # Arguments /// * `data_store` - The `DataStore` contract dispatcher. /// * `collateral_token_price` - Struct representing the price properties of the collateral token used in the position. -/// * `borrwing_fee_usd` - Parameter representing the borrowing fees in USD. +/// * `borrowing_fee_usd` - Parameter representing the borrowing fees in USD. /// # Returns /// Struct containing information about the borrowing fees for the specified position. fn get_borrowing_fees( - data_store: IDataStoreDispatcher, collateral_token_price: Price, borrwing_fee_usd: u128 + data_store: IDataStoreDispatcher, collateral_token_price: Price, borrowing_fee_usd: u256 ) -> PositionBorrowingFees { - // TODO - PositionBorrowingFees { - borrowing_fee_usd: 0, - borrowing_fee_amount: 0, - borrowing_fee_receiver_factor: 0, - borrowing_fee_amount_for_fee_receiver: 0, - } + position_pricing_utils::get_borrowing_fees( + data_store, collateral_token_price, borrowing_fee_usd + ) } @@ -93,32 +91,72 @@ fn get_borrowing_fees( /// # Returns /// Struct containing base funding values. fn get_base_funding_values(data_store: IDataStoreDispatcher, market: Market) -> BaseFundingValues { - // TODO - let collateral_type = CollateralType { long_token: 0, short_token: 0, }; + let mut values: BaseFundingValues = Default::default(); + values + .funding_fee_amount_per_size + .long + .long_token = + market_utils::get_funding_fee_amount_per_size( + data_store, market.market_token, market.long_token, true // is_long + ); - let funding_fee_amount_per_size_collateral_type_long = CollateralType { - long_token: 0, short_token: 0, - }; - let funding_fee_amount_per_size_collateral_type_short = CollateralType { - long_token: 0, short_token: 0, - }; - let claimable_funding_amount_per_size_type_long = CollateralType { - long_token: 0, short_token: 0, - }; - let claimable_funding_amount_per_size_type_short = CollateralType { - long_token: 0, short_token: 0, - }; + values + .funding_fee_amount_per_size + .long + .short_token = + market_utils::get_funding_fee_amount_per_size( + data_store, market.market_token, market.short_token, true // is_long + ); - let funding_fee_amount_per_size = PositionType { - long: funding_fee_amount_per_size_collateral_type_long, - short: funding_fee_amount_per_size_collateral_type_short, - }; + values + .funding_fee_amount_per_size + .short + .long_token = + market_utils::get_funding_fee_amount_per_size( + data_store, market.market_token, market.long_token, false // is_long + ); - let claimable_funding_amount_per_size = PositionType { - long: claimable_funding_amount_per_size_type_long, - short: claimable_funding_amount_per_size_type_short, - }; - BaseFundingValues { funding_fee_amount_per_size, claimable_funding_amount_per_size, } + values + .funding_fee_amount_per_size + .short + .short_token = + market_utils::get_funding_fee_amount_per_size( + data_store, market.market_token, market.short_token, false // is_long + ); + + values + .claimable_funding_amount_per_size + .long + .long_token = + market_utils::get_funding_fee_amount_per_size( + data_store, market.market_token, market.long_token, true // is_long + ); + + values + .claimable_funding_amount_per_size + .long + .short_token = + market_utils::get_funding_fee_amount_per_size( + data_store, market.market_token, market.short_token, true // is_long + ); + + values + .claimable_funding_amount_per_size + .short + .long_token = + market_utils::get_funding_fee_amount_per_size( + data_store, market.market_token, market.long_token, false // is_long + ); + + values + .claimable_funding_amount_per_size + .short + .short_token = + market_utils::get_funding_fee_amount_per_size( + data_store, market.market_token, market.short_token, false // is_long + ); + + values } /// Calculate and return the next funding amount per size for a specific market. @@ -131,35 +169,7 @@ fn get_base_funding_values(data_store: IDataStoreDispatcher, market: Market) -> fn get_next_funding_amount_per_size( data_store: IDataStoreDispatcher, market: Market, prices: MarketPrices ) -> GetNextFundingAmountPerSizeResult { - // TODO - let funding_fee_amount_per_size_collateral_type_long = CollateralType { - long_token: 0, short_token: 0, - }; - let funding_fee_amount_per_size_collateral_type_short = CollateralType { - long_token: 0, short_token: 0, - }; - let claimable_funding_amount_per_size_type_long = CollateralType { - long_token: 0, short_token: 0, - }; - let claimable_funding_amount_per_size_type_short = CollateralType { - long_token: 0, short_token: 0, - }; - - let funding_fee_amount_per_size = PositionType { - long: funding_fee_amount_per_size_collateral_type_long, - short: funding_fee_amount_per_size_collateral_type_short, - }; - - let claimable_funding_amount_per_size = PositionType { - long: claimable_funding_amount_per_size_type_long, - short: claimable_funding_amount_per_size_type_short, - }; - GetNextFundingAmountPerSizeResult { - longs_pay_shorts: true, - funding_factor_per_second: 0, - funding_fee_amount_per_size_delta: funding_fee_amount_per_size, - claimable_funding_amount_per_size_delta: claimable_funding_amount_per_size, - } + market_utils::get_next_funding_amount_per_size(data_store, market, prices) } @@ -179,86 +189,188 @@ fn get_position_info( referral_storage: IReferralStorageDispatcher, position_key: felt252, prices: MarketPrices, - size_delta_usd: u128, + mut size_delta_usd: u256, ui_fee_receiver: ContractAddress, use_position_size_as_size_delta_usd: bool ) -> PositionInfo { - let address_zero: ContractAddress = 0.try_into().unwrap(); - let position_referral_fees = PositionReferralFees { - referral_code: 0, - affiliate: address_zero, - trader: address_zero, - total_rebate_factor: 0, - trader_discount_factor: 0, - total_rebate_amount: 0, - trader_discount_amount: 0, - affiliate_reward_amount: 0, - }; + let mut position_info: PositionInfo = Default::default(); + let mut cache: GetPositionInfoCache = Default::default(); - let position = Position { - key: 0, - account: 0.try_into().unwrap(), - market: 0.try_into().unwrap(), - collateral_token: 0.try_into().unwrap(), - size_in_usd: 0, - size_in_tokens: 0, - collateral_amount: 0, - borrowing_factor: 0, - funding_fee_amount_per_size: 0, - long_token_claimable_funding_amount_per_size: 0, - short_token_claimable_funding_amount_per_size: 0, - increased_at_block: 0, - decreased_at_block: 0, - is_long: true, - }; + position_info.position = data_store.get_position(position_key); + cache.market = data_store.get_market(position_info.position.market); + cache + .collateral_token_price = + market_utils::get_cached_token_price( + position_info.position.collateral_token, cache.market, prices + ); - let position_funding_fees = PositionFundingFees { - funding_fee_amount: 0, - claimable_long_token_amount: 0, - claimable_short_token_amount: 0, - latest_funding_fee_amount_per_size: 0, - latest_long_token_claimable_funding_amount_per_size: 0, - latest_short_token_claimable_funding_amount_per_size: 0, - }; - let position_borrowing_fees = PositionBorrowingFees { - borrowing_fee_usd: 0, - borrowing_fee_amount: 0, - borrowing_fee_receiver_factor: 0, - borrowing_fee_amount_for_fee_receiver: 0, - }; - let position_ui_fees = PositionUiFees { - ui_fee_receiver: address_zero, ui_fee_receiver_factor: 0, ui_fee_amount: 0, - }; + if (use_position_size_as_size_delta_usd) { + size_delta_usd = position_info.position.size_in_usd; + } - let execution_price_result = ExecutionPriceResult { - price_impact_usd: 0, price_impact_diff_usd: 0, execution_price: 0, - }; + let size_delta_usd_int = calc::to_signed(size_delta_usd, false); + + position_info + .execution_price_result = + reader_pricing_utils::get_execution_price( + data_store, + cache.market, + prices.index_token_price, + position_info.position.size_in_usd, + position_info.position.size_in_tokens, + size_delta_usd_int, + position_info.position.is_long + ); - let price = Price { min: 0, max: 0, }; - - let position_fees = PositionFees { - referral: position_referral_fees, - funding: position_funding_fees, - borrowing: position_borrowing_fees, - ui: position_ui_fees, - collateral_token_price: price, - position_fee_factor: 0, - protocol_fee_amount: 0, - position_fee_receiver_factor: 0, - fee_receiver_amount: 0, - fee_amount_for_pool: 0, - position_fee_amount_for_pool: 0, - position_fee_amount: 0, - total_cost_amount_excluding_funding: 0, - total_cost_amount: 0, + let get_position_fees_params = position_pricing_utils::GetPositionFeesParams { + data_store, + referral_storage, + position: position_info.position, + collateral_token_price: cache.collateral_token_price, + for_positive_impact: position_info + .execution_price_result + .price_impact_usd > Zeroable::zero(), + long_token: cache.market.long_token, + short_token: cache.market.short_token, + size_delta_usd, + ui_fee_receiver }; - PositionInfo { - position: position, - fees: position_fees, - execution_price_result: execution_price_result, - base_pnl_usd: 0, - uncapped_base_pnl_usd: 0, - pnl_after_price_impact_usd: 0, + position_info.fees = position_pricing_utils::get_position_fees(get_position_fees_params); + + // borrowing and funding fees need to be overwritten with pending values otherwise they + // would be using storage values that have not yet been updated + cache + .pending_borrowing_fee_usd = + get_next_borrowing_fees(data_store, position_info.position, cache.market, prices); + + position_info + .fees + .borrowing = + get_borrowing_fees( + data_store, cache.collateral_token_price, cache.pending_borrowing_fee_usd + ); + + let next_funding_amount_result = market_utils::get_next_funding_amount_per_size( + data_store, cache.market, prices + ); + + position_info + .fees + .funding + .latest_funding_fee_amount_per_size = + market_utils::get_funding_fee_amount_per_size( + data_store, + position_info.position.market, + position_info.position.collateral_token, + position_info.position.is_long + ); + + position_info + .fees + .funding + .latest_long_token_claimable_funding_amount_per_size = + market_utils::get_claimable_funding_amount_per_size( + data_store, + position_info.position.market, + cache.market.long_token, + position_info.position.is_long + ); + + position_info + .fees + .funding + .latest_short_token_claimable_funding_amount_per_size = + market_utils::get_claimable_funding_amount_per_size( + data_store, + position_info.position.market, + cache.market.short_token, + position_info.position.is_long + ); + + if (position_info.position.is_long) { + position_info + .fees + .funding + .latest_long_token_claimable_funding_amount_per_size += next_funding_amount_result + .claimable_funding_amount_per_size_delta + .long + .long_token; + position_info + .fees + .funding + .latest_short_token_claimable_funding_amount_per_size += next_funding_amount_result + .claimable_funding_amount_per_size_delta + .long + .short_token; + + if (position_info.position.collateral_token == cache.market.long_token) { + position_info + .fees + .funding + .latest_funding_fee_amount_per_size += next_funding_amount_result + .funding_fee_amount_per_size_delta + .long + .long_token; + } else { + position_info + .fees + .funding + .latest_funding_fee_amount_per_size += next_funding_amount_result + .funding_fee_amount_per_size_delta + .long + .short_token; + } + } else { + position_info + .fees + .funding + .latest_long_token_claimable_funding_amount_per_size += next_funding_amount_result + .claimable_funding_amount_per_size_delta + .short + .long_token; + position_info + .fees + .funding + .latest_short_token_claimable_funding_amount_per_size += next_funding_amount_result + .claimable_funding_amount_per_size_delta + .short + .short_token; + + if (position_info.position.collateral_token == cache.market.long_token) { + position_info + .fees + .funding + .latest_funding_fee_amount_per_size += next_funding_amount_result + .funding_fee_amount_per_size_delta + .short + .long_token; + } else { + position_info + .fees + .funding + .latest_funding_fee_amount_per_size += next_funding_amount_result + .funding_fee_amount_per_size_delta + .short + .short_token; + } } + + position_info + .fees + .funding = + position_pricing_utils::get_funding_fees( + position_info.fees.funding, position_info.position + ); + + let (base_pnl_usd, uncapped_base_pnl_usd, _) = position_utils::get_position_pnl_usd( + data_store, cache.market, prices, position_info.position, size_delta_usd + ); + + position_info.base_pnl_usd = base_pnl_usd; + position_info.uncapped_base_pnl_usd = uncapped_base_pnl_usd; + + position_info.pnl_after_price_impact_usd = position_info.execution_price_result.price_impact_usd + + position_info.base_pnl_usd; + position_info } diff --git a/src/referral/referral_tier.cairo b/src/referral/referral_tier.cairo index 0dc30662..e821a637 100644 --- a/src/referral/referral_tier.cairo +++ b/src/referral/referral_tier.cairo @@ -3,7 +3,7 @@ #[derive(Drop, starknet::Store, Serde)] struct ReferralTier { /// The total rebate for the tier (affiliate reward + trader discount). - total_rebate: u128, + total_rebate: u256, /// The share of the totalRebate for traders. - discount_share: u128 + discount_share: u256 } diff --git a/src/referral/referral_utils.cairo b/src/referral/referral_utils.cairo index 36ee49f6..a9aaba58 100644 --- a/src/referral/referral_utils.cairo +++ b/src/referral/referral_utils.cairo @@ -4,7 +4,7 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; use result::ResultTrait; // Local imports. @@ -24,7 +24,6 @@ use satoru::referral::referral_tier::ReferralTier; /// * `referral_storage` - The referral storage instance to use. /// * `account` - The account of the trader. /// * `referral_code` - The referral code. -#[inline(always)] fn set_trader_referral_code( referral_storage: IReferralStorageDispatcher, account: ContractAddress, referral_code: felt252 ) { @@ -48,15 +47,15 @@ fn increment_affiliate_reward( market: ContractAddress, token: ContractAddress, affiliate: ContractAddress, - delta: u128 + delta: u256 ) { if (delta == 0) { return; } - let next_value: u128 = data_store - .increment_u128(keys::affiliate_reward_for_account_key(market, token, affiliate), delta); - let next_pool_value: u128 = data_store - .increment_u128(keys::affiliate_reward_key(market, token), delta); + let next_value: u256 = data_store + .increment_u256(keys::affiliate_reward_for_account_key(market, token, affiliate), delta); + let next_pool_value: u256 = data_store + .increment_u256(keys::affiliate_reward_key(market, token), delta); event_emitter .emit_affiliate_reward_updated( @@ -72,18 +71,18 @@ fn increment_affiliate_reward( /// The referral code, the affiliate's address, the total rebate, and the discount share. fn get_referral_info( referral_storage: IReferralStorageDispatcher, trader: ContractAddress -) -> (felt252, ContractAddress, u128, u128) { +) -> (felt252, ContractAddress, u256, u256) { let code: felt252 = referral_storage.trader_referral_codes(trader); - let mut affiliate: ContractAddress = 0.try_into().unwrap(); - let mut total_rebate: u128 = 0; - let mut discount_share: u128 = 0; + let mut affiliate = contract_address_const::<0>(); + let mut total_rebate: u256 = 0; + let mut discount_share: u256 = 0; if (code != 0) { affiliate = referral_storage.code_owners(code); - let referral_tier_level: u128 = referral_storage.referrer_tiers(affiliate); + let referral_tier_level: u256 = referral_storage.referrer_tiers(affiliate); let referral_tier: ReferralTier = referral_storage.tiers(referral_tier_level); total_rebate = referral_tier.total_rebate; discount_share = referral_tier.discount_share; - let custom_discount_share: u128 = referral_storage.referrer_discount_shares(affiliate); + let custom_discount_share: u256 = referral_storage.referrer_discount_shares(affiliate); if (custom_discount_share != 0) { discount_share = custom_discount_share; } @@ -114,17 +113,17 @@ fn claim_affiliate_reward( token: ContractAddress, account: ContractAddress, receiver: ContractAddress -) -> u128 { +) -> u256 { let key: felt252 = keys::affiliate_reward_for_account_key(market, token, account); - let reward_amount: u128 = data_store.get_u128(key); - data_store.set_u128(key, 0); + let reward_amount: u256 = data_store.get_u256(key); + data_store.set_u256(key, 0); - let next_pool_value: u128 = data_store - .decrement_u128(keys::affiliate_reward_key(market, token), reward_amount); + let next_pool_value: u256 = data_store + .decrement_u256(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(market, token, receiver, reward_amount); market_utils::validate_market_token_balance_with_address(data_store, market); diff --git a/src/role/error.cairo b/src/role/error.cairo index 1b998415..3db56a47 100644 --- a/src/role/error.cairo +++ b/src/role/error.cairo @@ -1,3 +1,4 @@ mod RoleError { const UNAUTHORIZED_ACCESS: felt252 = 'unauthorized_access'; + const UNAUTHORIZED_CHANGE: felt252 = 'unauthorized_change'; } diff --git a/src/role/role_module.cairo b/src/role/role_module.cairo index 67ac5aaf..ec610112 100644 --- a/src/role/role_module.cairo +++ b/src/role/role_module.cairo @@ -58,7 +58,7 @@ mod RoleModule { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl RoleModule of super::IRoleModule { fn initialize(ref self: ContractState, role_store_address: ContractAddress) { self.role_store.write(IRoleStoreDispatcher { contract_address: role_store_address }); diff --git a/src/role/role_store.cairo b/src/role/role_store.cairo index 7ee4f6a7..b91955c0 100644 --- a/src/role/role_store.cairo +++ b/src/role/role_store.cairo @@ -43,32 +43,30 @@ trait IRoleStore { /// Returns the number of roles stored in the contract. /// # Return /// The number of roles. - fn get_role_count(self: @TContractState) -> u128; -/// # [TO FIX] -/// Returns the keys of the roles stored in the contract. -/// # Arguments -/// `start` - The starting index of the range of roles to return. -/// `end` - The ending index of the range of roles to return. -/// # Return -/// The keys of the roles. -/// fn get_roles(self: @TContractState, start: u32, end: u32) -> Array; - -/// # [TO DO] -/// Returns the number of members of the specified role. -/// # Arguments -/// `role_key` - The key of the role. -/// # Return -/// The number of members of the role. -/// fn get_role_member_count(self: @TContractState, role_key: felt252) -> u128; - -/// Returns the members of the specified role. -/// # Arguments -/// `role_key` - The key of the role. -/// `start` - The start index, the value for this index will be included. -/// `end` - The end index, the value for this index will not be included. -/// # Return -/// The members of the role. -/// fn get_role_members(self: @TContractState, role_key: felt252, start: u128, end: u128) -> Array; + fn get_role_count(self: @TContractState) -> u32; + /// Returns the keys of the roles stored in the contract. + /// # Arguments + /// `start` - The starting index of the range of roles to return. + /// `end` - The ending index of the range of roles to return. + /// # Return + /// The keys of the roles. + fn get_roles(self: @TContractState, start: u32, end: u32) -> Array; + /// Returns the number of members of the specified role. + /// # Arguments + /// `role_key` - The key of the role. + /// # Return + /// The number of members of the role. + fn get_role_member_count(self: @TContractState, role_key: felt252) -> u32; + /// Returns the members of the specified role. + /// # Arguments + /// `role_key` - The key of the role. + /// `start` - The start index, the value for this index will be included. + /// `end` - The end index, the value for this index will not be included. + /// # Return + /// The members of the role. + fn get_role_members( + self: @TContractState, role_key: felt252, start: u32, end: u32 + ) -> Array; } #[starknet::contract] @@ -78,25 +76,30 @@ mod RoleStore { // ************************************************************************* // Core lib imports. - use starknet::{ContractAddress, get_caller_address}; - //use array::ArrayTrait; + use core::zeroable::Zeroable; + use starknet::{ContractAddress, get_caller_address, contract_address_const}; // Local imports. use satoru::role::{role, error::RoleError}; + // ************************************************************************* // STORAGE // ************************************************************************* #[storage] struct Storage { /// Maps accounts to their roles. - role_members: LegacyMap::<(felt252, ContractAddress), bool>, + has_role: LegacyMap::<(felt252, ContractAddress), bool>, + /// Stores the number of the indexes used to a specific role. + role_members_count: LegacyMap::, + /// Stores all the account that have a specific role. + role_members: LegacyMap::<(felt252, u32), ContractAddress>, /// Stores unique role names. role_names: LegacyMap::, - /// Store the number of unique roles. - role_count: u128, - /// List of all role keys. - ///role_keys: Array, + /// Store the number of indexes of the roles. + roles_count: u32, + /// List of all role keys. + roles: LegacyMap::, } // ************************************************************************* @@ -136,35 +139,35 @@ mod RoleStore { // CONSTRUCTOR // ************************************************************************* #[constructor] - fn constructor(ref self: ContractState) { - let caller = get_caller_address(); + fn constructor(ref self: ContractState, admin: ContractAddress) { // Grant the caller admin role. - self._grant_role(caller, role::ROLE_ADMIN); - // Initialize the role_count to 1 due to the line just above. - self.role_count.write(1); + self._grant_role(admin, role::ROLE_ADMIN); + // Initialize the role_count to 1 due to the line just above. } // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl RoleStore of super::IRoleStore { fn has_role(self: @ContractState, account: ContractAddress, role_key: felt252) -> bool { self._has_role(account, role_key) } fn grant_role(ref self: ContractState, account: ContractAddress, role_key: felt252) { - let caller = get_caller_address(); // Check that the caller has the admin role. - self._assert_only_role(caller, role::ROLE_ADMIN); + self._assert_only_role(get_caller_address(), role::ROLE_ADMIN); // Grant the role. self._grant_role(account, role_key); } fn revoke_role(ref self: ContractState, account: ContractAddress, role_key: felt252) { - let caller = get_caller_address(); // Check that the caller has the admin role. - self._assert_only_role(caller, role::ROLE_ADMIN); + self._assert_only_role(get_caller_address(), role::ROLE_ADMIN); + // check that the are more than 1 RoleAdmin + if role_key == role::ROLE_ADMIN { + assert(self.get_role_member_count(role_key) > 1, RoleError::UNAUTHORIZED_CHANGE); + } // Revoke the role. self._revoke_role(account, role_key); } @@ -173,29 +176,78 @@ mod RoleStore { self._assert_only_role(account, role_key); } - fn get_role_count(self: @ContractState) -> u128 { - return self.role_count.read(); + fn get_role_count(self: @ContractState) -> u32 { + let mut count = 0; + let mut i = 1; + loop { + if i > self.roles_count.read() { + break; + } + if !self.roles.read(i).is_zero() { + count += 1; + } + i += 1; + }; + count + } + + fn get_roles(self: @ContractState, start: u32, mut end: u32) -> Array { + let mut arr = array![]; + let roles_count = self.roles_count.read(); + if end > roles_count { + end = roles_count; + } + let mut i = start; + loop { + if i > end { + break; + } + let role = self.roles.read(i); + if !role.is_zero() { + arr.append(role); + } + i += 1; + }; + arr + } + + fn get_role_member_count(self: @ContractState, role_key: felt252) -> u32 { + let mut count = 0; + let mut i = 1; + loop { + if i > self.role_members_count.read(role_key) { + break; + } + if !(self.role_members.read((role_key, i)) == contract_address_const::<0>()) { + count += 1; + } + i += 1; + }; + count + } + + fn get_role_members( + self: @ContractState, role_key: felt252, start: u32, mut end: u32 + ) -> Array { + let mut arr: Array = array![]; + let mut i = start; + loop { + if i > end || i > self.role_members_count.read(role_key) { + break; + } + let role_member = self.role_members.read((role_key, i)); + // Since some role members will have indexes with zero address if a zero address + // is found end increase by 1 to mock array behaviour. + if role_member.is_zero() { + end += 1; + } + if !(role_member == contract_address_const::<0>()) { + arr.append(role_member); + } + i += 1; + }; + arr } - //fn get_roles(self: @ContractState, start: u32, end: u32) -> Array { - // Create a new array to store the result. - //let mut result = ArrayTrait::::new(); - //let role_keys_length = self.role_keys.read().len(); - // Ensure the range is valid. - //assert(start < end, "InvalidRange"); - //assert(end <= role_keys_length, "EndOutOfBounds"); - //let mut current_index = start; - //loop { - // Check if we've reached the end of the specified range. - //if current_index >= end { - //break; - //} - //let key = *self.role_keys.read().at(current_index); - //result.append(key); - // Increment the index. - //current_index += 1; - //}; - //return result; - //} } // ************************************************************************* @@ -203,12 +255,10 @@ mod RoleStore { // ************************************************************************* #[generate_trait] impl InternalFunctions of InternalFunctionsTrait { - #[inline(always)] fn _has_role(self: @ContractState, account: ContractAddress, role_key: felt252) -> bool { - self.role_members.read((role_key, account)) + self.has_role.read((role_key, account)) } - #[inline(always)] fn _assert_only_role(self: @ContractState, account: ContractAddress, role_key: felt252) { assert(self._has_role(account, role_key), RoleError::UNAUTHORIZED_ACCESS); } @@ -216,31 +266,80 @@ mod RoleStore { fn _grant_role(ref self: ContractState, account: ContractAddress, role_key: felt252) { // Only grant the role if the account doesn't already have it. if !self._has_role(account, role_key) { - let caller: ContractAddress = get_caller_address(); - self.role_members.write((role_key, account), true); + self.has_role.write((role_key, account), true); + // Iterates through indexes for role members, if an index has zero ContractAddress + // it writes the account to that index for the role. + let roles_members_count = self.role_members_count.read(role_key); + let current_roles_count = self.roles_count.read(); + let mut i = 1; + loop { + let stored_role_member = self.role_members.read((role_key, i)); + if stored_role_member.is_zero() { + self.role_members.write((role_key, i), account); + self.role_members_count.write(role_key, roles_members_count + 1); + break; + } + i += 1; + }; + // Store the role name if it's not already stored. if self.role_names.read(role_key) == false { self.role_names.write(role_key, true); - let mut current_count: u128 = self.role_count.read(); - self.role_count.write(current_count + 1); - // Read the current state of role_keys into a local variable. - // let mut local_role_keys = self.role_keys.read(); - // Modify the local variable. - // local_role_keys.append(role_key); - // Write back the modified local variable to the contract state. - // self.role_keys.write(local_role_keys); + self.roles_count.write(current_roles_count + 1); } - self.emit(RoleGranted { role_key, account, sender: caller }); + self.emit(RoleGranted { role_key, account, sender: get_caller_address() }); } + // Iterates through indexes in stored_roles and if a value for the index is zero + // it writes the role_key to that index. + let mut i = 1; + loop { + let stored_role = self.roles.read(i); + if stored_role.is_zero() { + self.roles.write(i, role_key); + break; + } + i += 1; + }; } fn _revoke_role(ref self: ContractState, account: ContractAddress, role_key: felt252) { + let current_roles_count = self.roles_count.read(); // Only revoke the role if the account has it. if self._has_role(account, role_key) { - let caller: ContractAddress = get_caller_address(); - self.role_members.write((role_key, account), false); - self.emit(RoleRevoked { role_key, account, sender: caller }); + self.has_role.write((role_key, account), false); + self.emit(RoleRevoked { role_key, account, sender: get_caller_address() }); + let current_role_members_count = self.role_members_count.read(role_key); + let mut i = 1; + loop { + let stored_role_member = self.role_members.read((role_key, i)); + if stored_role_member == account { + self.role_members.write((role_key, i), contract_address_const::<0>()); + break; + } + i += 1; + }; + // If the role has no members remove the role from roles. + if self.get_role_member_count(role_key).is_zero() { + let role_index = self._find_role_index(role_key); + self.roles.write(role_index, Zeroable::zero()); + } } } + + fn _find_role_index(ref self: ContractState, role_key: felt252) -> u32 { + let mut index = 0; + let mut i = 1; + loop { + if i > self.roles_count.read() { + break; + } + if self.roles.read(i) == role_key { + index = i; + break; + } + i += 1; + }; + index + } } } diff --git a/src/router/error.cairo b/src/router/error.cairo index 61bfe0a3..e86cb69d 100644 --- a/src/router/error.cairo +++ b/src/router/error.cairo @@ -1,3 +1,36 @@ mod RouterError { + use starknet::ContractAddress; + const ALREADY_INITIALIZED: felt252 = 'already_initialized'; + const DEPOSIT_NOT_VALID: felt252 = 'deposit_not_valid'; + const WITHDRAWAL_NOT_VALID: felt252 = 'withdrawal_not_valid'; + const ORDER_NOT_VALID: felt252 = 'order_not_valid'; + const EMPTY_DEPOSIT: felt252 = 'empty_deposit'; + const EMPTY_ORDER: felt252 = 'empty_order'; + + fn UNAUTHORIZED(sender: ContractAddress, message: felt252) { + let mut data = array![message.into()]; + data.append(sender.into()); + panic(data) + } + + fn INVALID_CLAIM_FUNDING_FEES_INPUT(markets_len: u32, tokens_len: u32) { + let mut data = array![markets_len.into(), tokens_len.into()]; + panic(data) + } + + fn INVALID_CLAIM_COLLATERAL_INPUT(markets_len: u32, tokens_len: u32, time_keys_len: u32) { + let mut data = array![markets_len.into(), tokens_len.into(), time_keys_len.into()]; + panic(data) + } + + fn INVALID_CLAIM_AFFILIATE_REWARDS_INPUT(markets_len: u32, tokens_len: u32) { + let mut data = array![markets_len.into(), tokens_len.into()]; + panic(data) + } + + fn INVALID_CLAIM_UI_FEES_INPUT(markets_len: u32, tokens_len: u32) { + let mut data = array![markets_len.into(), tokens_len.into()]; + panic(data) + } } diff --git a/src/router/exchange_router.cairo b/src/router/exchange_router.cairo index 1d655509..3b050093 100644 --- a/src/router/exchange_router.cairo +++ b/src/router/exchange_router.cairo @@ -10,8 +10,6 @@ use starknet::ContractAddress; use core::zeroable::Zeroable; -use debug::PrintTrait; - // Local imports. use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; @@ -38,7 +36,7 @@ trait IExchangeRouter { /// * `receiver` - The address of the receiver. /// * `amount` - The amount of tokens to transfer. fn send_tokens( - ref self: TContractState, token: ContractAddress, receiver: ContractAddress, amount: u128 + ref self: TContractState, token: ContractAddress, receiver: ContractAddress, amount: u256 ); /// Creates a new deposit with the given params. The deposit is created by transferring the specified amounts of @@ -110,19 +108,19 @@ trait IExchangeRouter { /// * `acceptable_price` - The new acceptable price for the order. /// * `trigger_price` - The new trigger price for the order. /// * `min_output_amout` - The minimum required output amount in the transaction. - fn update_order( - ref self: TContractState, - key: felt252, - size_delta_usd: u128, - acceptable_price: u128, - trigger_price: u128, - min_output_amout: u128 - ); + // fn update_order( + // ref self: TContractState, + // key: felt252, + // size_delta_usd: u256, + // acceptable_price: u256, + // trigger_price: u256, + // min_output_amout: u256 + // ); /// Cancels the given order. /// # Arguments /// * `key` - The unique ID of the order to be cancelled. - fn cancel_order(ref self: TContractState, key: felt252); + // fn cancel_order(ref self: TContractState, key: felt252); /// Claims funding fees for the given markets and tokens on behalf of the caller, and sends the /// fees to the specified receiver. The length of the `markets` and `tokens` arrays must be the same. @@ -137,15 +135,15 @@ trait IExchangeRouter { markets: Array, tokens: Array, receiver: ContractAddress - ) -> Array; + ) -> Array; fn claim_collateral( ref self: TContractState, markets: Array, tokens: Array, - time_keys: Array, + time_keys: Array, receiver: ContractAddress - ) -> Array; + ) -> Array; /// Claims affiliate rewards for the given markets and tokens on behalf of the caller, and sends the rewards to the specified receiver. /// # Arguments @@ -157,16 +155,16 @@ trait IExchangeRouter { markets: Array, tokens: Array, receiver: ContractAddress - ) -> Array; + ) -> Array; - fn set_ui_fee_factor(ref self: TContractState, ui_fee_factor: u128); + fn set_ui_fee_factor(ref self: TContractState, ui_fee_factor: u256); fn claim_ui_fees( ref self: TContractState, markets: Array, tokens: Array, receiver: ContractAddress - ) -> Array; + ) -> Array; } #[starknet::contract] @@ -176,10 +174,11 @@ mod ExchangeRouter { // ************************************************************************* // Core lib imports. - use starknet::ContractAddress; + use starknet::{ + get_caller_address, ContractAddress, contract_address_const, get_contract_address + }; use core::zeroable::Zeroable; - use debug::PrintTrait; // Local imports. use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; @@ -194,9 +193,20 @@ mod ExchangeRouter { use super::IExchangeRouter; use satoru::deposit::deposit_utils::CreateDepositParams; - use satoru::withdrawal::withdrawal_utils::CreateWithdrawalParams; + use satoru::withdrawal::{withdrawal::Withdrawal, withdrawal_utils::CreateWithdrawalParams}; use satoru::order::base_order_utils::CreateOrderParams; use satoru::oracle::oracle_utils::SimulatePricesParams; + use satoru::utils::account_utils; + use satoru::utils::global_reentrancy_guard; + use satoru::router::error::RouterError; + use satoru::deposit::deposit::Deposit; + use satoru::order::order::Order; + use satoru::callback::callback_utils; + use satoru::feature::feature_utils; + use satoru::market::market_utils; + use satoru::data::keys; + use satoru::referral::referral_utils; + use satoru::fee::fee_utils; // ************************************************************************* // STORAGE @@ -263,86 +273,274 @@ mod ExchangeRouter { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl ExchangeRouterImpl of super::IExchangeRouter { fn send_tokens( - ref self: ContractState, token: ContractAddress, receiver: ContractAddress, amount: u128 - ) { //TODO + ref self: ContractState, token: ContractAddress, receiver: ContractAddress, amount: u256 + ) { + account_utils::validate_receiver(receiver); + let account = get_caller_address(); + self.router.read().plugin_transfer(token, account, receiver, amount); } fn create_deposit(ref self: ContractState, params: CreateDepositParams) -> felt252 { - //TODO - 0 + let data_store = self.data_store.read(); + // global_reentrancy_guard::non_reentrant_before(data_store); //TODO uncomment + + let account = get_caller_address(); + + let key = self.deposit_handler.read().create_deposit(account, params); + + // global_reentrancy_guard::non_reentrant_after(data_store); //TODO uncomment + + key } - fn cancel_deposit(ref self: ContractState, key: felt252) { //TODO + fn cancel_deposit(ref self: ContractState, key: felt252) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let deposit = data_store.get_deposit(key); + + if (deposit.account == contract_address_const::<0>()) { + panic_with_felt252(RouterError::EMPTY_DEPOSIT) + } + + if (deposit.account != get_caller_address()) { + RouterError::UNAUTHORIZED(get_caller_address(), 'account for cancel_deposit') + } + + self.deposit_handler.read().cancel_deposit(key); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn create_withdrawal(ref self: ContractState, params: CreateWithdrawalParams) -> felt252 { - //TODO - 0 + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let account = get_caller_address(); + + let key = self.withdrawal_handler.read().create_withdrawal(account, params); + + global_reentrancy_guard::non_reentrant_after(data_store); + + key } - fn cancel_withdrawal(ref self: ContractState, key: felt252) { //TODO + fn cancel_withdrawal(ref self: ContractState, key: felt252) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let withdrawal = data_store.get_withdrawal(key); + + if (withdrawal.account != get_caller_address()) { + RouterError::UNAUTHORIZED(get_caller_address(), 'account for cancel_withdrawal') + } + + self.withdrawal_handler.read().cancel_withdrawal(key); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn create_order(ref self: ContractState, params: CreateOrderParams) -> felt252 { - //TODO - 0 + let data_store = self.data_store.read(); + // global_reentrancy_guard::non_reentrant_before(data_store); //TODO uncomment + + let account = get_caller_address(); + + let key = self.order_handler.read().create_order(account, params); + + // global_reentrancy_guard::non_reentrant_after(data_store); //TODO uncomment + + key } fn set_saved_callback_contract( ref self: ContractState, market: ContractAddress, callback_contract: ContractAddress - ) { //TODO + ) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + callback_utils::set_saved_callback_contract( + data_store, get_caller_address(), market, callback_contract + ); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn simulate_execute_deposit( ref self: ContractState, key: felt252, simulated_oracle_params: SimulatePricesParams - ) { //TODO + ) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + self.deposit_handler.read().simulate_execute_deposit(key, simulated_oracle_params); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn simulate_execute_withdrawal( ref self: ContractState, key: felt252, simulated_oracle_params: SimulatePricesParams - ) { //TODO + ) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + self + .withdrawal_handler + .read() + .simulate_execute_withdrawal(key, simulated_oracle_params); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn simulate_execute_order( ref self: ContractState, key: felt252, simulated_oracle_params: SimulatePricesParams - ) { //TODO - } + ) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); - fn update_order( - ref self: ContractState, - key: felt252, - size_delta_usd: u128, - acceptable_price: u128, - trigger_price: u128, - min_output_amout: u128 - ) { //TODO - } + self.order_handler.read().simulate_execute_order(key, simulated_oracle_params); - fn cancel_order(ref self: ContractState, key: felt252) { //TODO + global_reentrancy_guard::non_reentrant_after(data_store); } + // fn update_order( + // ref self: ContractState, + // key: felt252, + // size_delta_usd: u256, + // acceptable_price: u256, + // trigger_price: u256, + // min_output_amout: u256 + // ) { + // let data_store = self.data_store.read(); + // global_reentrancy_guard::non_reentrant_before(data_store); + + // let order = data_store.get_order(key); + + // if (order.account != get_caller_address()) { + // RouterError::UNAUTHORIZED(get_caller_address(), 'account for update_order') + // } + // self + // .order_handler + // .read() + // .update_order( + // key, size_delta_usd, acceptable_price, trigger_price, min_output_amout, order + // ); + + // global_reentrancy_guard::non_reentrant_after(data_store); + // } + + // fn cancel_order(ref self: ContractState, key: felt252) { + // let data_store = self.data_store.read(); + // global_reentrancy_guard::non_reentrant_before(data_store); + + // let order = data_store.get_order(key); + + // if (order.account != get_caller_address()) { + // RouterError::UNAUTHORIZED(get_caller_address(), 'account for cancel_order') + // } + // self.order_handler.read().cancel_order(key); + + // global_reentrancy_guard::non_reentrant_after(data_store); + // } + fn claim_funding_fees( ref self: ContractState, markets: Array, tokens: Array, receiver: ContractAddress - ) -> Array { - //TODO - ArrayTrait::new() + ) -> Array { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + if (markets.len() != tokens.len()) { + RouterError::INVALID_CLAIM_FUNDING_FEES_INPUT(markets.len(), tokens.len()) + } + + feature_utils::validate_feature( + data_store, keys::claim_funding_fees_feature_disabled_key(get_contract_address()) + ); + + account_utils::validate_receiver(receiver); + + let account = get_caller_address(); + + let mut claimed_amounts: Array = ArrayTrait::new(); + + let mut i = 0; + loop { + if i == markets.len() { + break; + } + claimed_amounts + .append( + market_utils::claim_funding_fees( + data_store, + self.event_emitter.read(), + *markets[i], + *tokens[i], + account, + receiver + ) + ); + i += 1; + }; + + global_reentrancy_guard::non_reentrant_after(data_store); + + claimed_amounts } fn claim_collateral( ref self: ContractState, markets: Array, tokens: Array, - time_keys: Array, + time_keys: Array, receiver: ContractAddress - ) -> Array { - //TODO - ArrayTrait::new() + ) -> Array { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + if (markets.len() != tokens.len() || tokens.len() != time_keys.len()) { + RouterError::INVALID_CLAIM_COLLATERAL_INPUT( + markets.len(), tokens.len(), time_keys.len() + ) + } + + feature_utils::validate_feature( + data_store, keys::claim_collateral_feature_disabled_key(get_contract_address()) + ); + + account_utils::validate_receiver(receiver); + + let account = get_caller_address(); + + let mut claimed_amounts: Array = ArrayTrait::new(); + + let mut i = 0; + loop { + if i == markets.len() { + break; + } + claimed_amounts + .append( + market_utils::claim_collateral( + data_store, + self.event_emitter.read(), + *markets[i], + *tokens[i], + *time_keys[i], + account, + receiver + ) + ); + i += 1; + }; + + global_reentrancy_guard::non_reentrant_after(data_store); + + claimed_amounts } fn claim_affiliate_rewards( @@ -350,12 +548,57 @@ mod ExchangeRouter { markets: Array, tokens: Array, receiver: ContractAddress - ) -> Array { - //TODO - ArrayTrait::new() + ) -> Array { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + if (markets.len() != tokens.len()) { + RouterError::INVALID_CLAIM_AFFILIATE_REWARDS_INPUT(markets.len(), tokens.len()) + } + + feature_utils::validate_feature( + data_store, + keys::claim_affiliate_rewards_feature_disabled_key(get_contract_address()) + ); + + let account = get_caller_address(); + + let mut claimed_amounts: Array = ArrayTrait::new(); + + let mut i = 0; + loop { + if i == markets.len() { + break; + } + claimed_amounts + .append( + referral_utils::claim_affiliate_reward( + data_store, + self.event_emitter.read(), + *markets[i], + *tokens[i], + account, + receiver + ) + ); + i = i + 1; + }; + + global_reentrancy_guard::non_reentrant_after(data_store); + + claimed_amounts } - fn set_ui_fee_factor(ref self: ContractState, ui_fee_factor: u128) { //TODO + fn set_ui_fee_factor(ref self: ContractState, ui_fee_factor: u256) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let account = get_caller_address(); + market_utils::set_ui_fee_factor( + data_store, self.event_emitter.read(), account, ui_fee_factor + ); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn claim_ui_fees( @@ -363,9 +606,44 @@ mod ExchangeRouter { markets: Array, tokens: Array, receiver: ContractAddress - ) -> Array { - //TODO - ArrayTrait::new() + ) -> Array { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + if (markets.len() != tokens.len()) { + RouterError::INVALID_CLAIM_UI_FEES_INPUT(markets.len(), tokens.len()) + } + + feature_utils::validate_feature( + data_store, keys::claim_ui_fees_feature_disabled_key(get_contract_address()) + ); + + let ui_fee_receiver = get_caller_address(); + + let mut claimed_amounts: Array = ArrayTrait::new(); + + let mut i = 0; + loop { + if i == markets.len() { + break; + } + claimed_amounts + .append( + fee_utils::claim_ui_fees( + data_store, + self.event_emitter.read(), + ui_fee_receiver, + *markets[i], + *tokens[i], + receiver + ) + ); + i += 1; + }; + + global_reentrancy_guard::non_reentrant_after(data_store); + + claimed_amounts } } } diff --git a/src/router/router.cairo b/src/router/router.cairo index 1be2c3d0..47b7ef33 100644 --- a/src/router/router.cairo +++ b/src/router/router.cairo @@ -24,7 +24,7 @@ trait IRouter { token: ContractAddress, account: ContractAddress, receiver: ContractAddress, - amount: u128 + amount: u256 ); } @@ -37,7 +37,6 @@ mod Router { // Core lib imports. use core::zeroable::Zeroable; - use debug::PrintTrait; use starknet::{ContractAddress, get_caller_address}; @@ -71,19 +70,19 @@ mod Router { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl RouterImpl of super::IRouter { fn plugin_transfer( ref self: ContractState, token: ContractAddress, account: ContractAddress, receiver: ContractAddress, - amount: u128 + amount: u256 ) { - let mut role_module: RoleModule::ContractState = - RoleModule::unsafe_new_contract_state(); - // Check that the caller has the `ROUTER_PLUGIN` role. - role_module.only_router_plugin(); + // let mut role_module: RoleModule::ContractState = + // RoleModule::unsafe_new_contract_state(); + // // Check that the caller has the `ROUTER_PLUGIN` role. + // role_module.only_router_plugin(); // Transfer tokens from account to receiver. // It requires that account's allowance to this contract is at least `amount`. diff --git a/src/swap/error.cairo b/src/swap/error.cairo index a29cbae6..1fb0c9fa 100644 --- a/src/swap/error.cairo +++ b/src/swap/error.cairo @@ -1,12 +1,13 @@ mod SwapError { use starknet::ContractAddress; + use satoru::utils::i256::i256; const ALREADY_INITIALIZED: felt252 = 'already_initialized'; - fn INSUFFICIENT_OUTPUT_AMOUNT(amount_in: u128, min_output_amount: u128) { + fn INSUFFICIENT_OUTPUT_AMOUNT(amount_in: u256, min_output_amount: u256) { let mut data = array!['insufficient output amount']; - data.append(amount_in.into()); - data.append(min_output_amount.into()); + data.append(amount_in.try_into().expect('u256 into felt failed')); + data.append(min_output_amount.try_into().expect('u256 into felt failed')); panic(data) } @@ -16,11 +17,11 @@ mod SwapError { data.append(expected_token.into()); panic(data) } - // TODO: negative_impact_amount should be a i128 - fn SWAP_PRICE_IMPACT_EXCEEDS_AMOUNT_IN(amount_after_fees: u128, negative_impact_amount: u128) { + + fn SWAP_PRICE_IMPACT_EXCEEDS_AMOUNT_IN(amount_after_fees: u256, negative_impact_amount: i256) { let mut data = array!['price impact exceeds amount']; - data.append(amount_after_fees.into()); - data.append(negative_impact_amount.into()); + data.append(amount_after_fees.try_into().expect('u256 into felt failed')); + data.append(negative_impact_amount.try_into().expect('i256 into felt failed')); panic(data) } @@ -29,4 +30,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) + } } diff --git a/src/swap/swap_handler.cairo b/src/swap/swap_handler.cairo index b8988086..ba5d8a4c 100644 --- a/src/swap/swap_handler.cairo +++ b/src/swap/swap_handler.cairo @@ -19,7 +19,7 @@ trait ISwapHandler { /// * `params` - SwapParams. /// # Returns /// * (outputToken, outputAmount) - fn swap(ref self: TContractState, params: SwapParams) -> (ContractAddress, u128); + fn swap(ref self: TContractState, params: SwapParams) -> (ContractAddress, u256); } #[starknet::contract] @@ -34,12 +34,19 @@ mod SwapHandler { use satoru::swap::swap_utils::SwapParams; use satoru::swap::swap_utils; use satoru::role::role_module::{RoleModule, IRoleModule}; + use satoru::utils::i256::i256; + use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; + use satoru::utils::global_reentrancy_guard; + // ************************************************************************* // STORAGE // ************************************************************************* #[storage] - struct Storage {} + struct Storage { + /// Interface to interact with the `DataStore` contract. + data_store: IDataStoreDispatcher + } // ************************************************************************* // CONSTRUCTOR @@ -47,23 +54,31 @@ mod SwapHandler { /// Constructor of the contract. #[constructor] - fn constructor(ref self: ContractState, role_store_address: ContractAddress) { + fn constructor(ref self: ContractState, role_store_address: ContractAddress,) { let mut role_module: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); - IRoleModule::initialize(ref role_module, role_store_address) + IRoleModule::initialize(ref role_module, role_store_address); } // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl SwapHandler of super::ISwapHandler { - fn swap(ref self: ContractState, params: SwapParams) -> (ContractAddress, u128) { - //TODO nonReentrant when openzeppelin is available + fn swap(ref self: ContractState, params: SwapParams) -> (ContractAddress, u256) { let mut role_module: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); role_module.only_controller(); - swap_utils::swap(@params) + + // TODO replace global reentrancy guard with simple one + // let data_store = self.data_store.read(); + // global_reentrancy_guard::non_reentrant_before(data_store); + + let (token_out, swap_output_amount) = swap_utils::swap(@params); + + // global_reentrancy_guard::non_reentrant_after(data_store); + + (token_out, swap_output_amount) } } } diff --git a/src/swap/swap_utils.cairo b/src/swap/swap_utils.cairo index 911adb0a..a38a1056 100644 --- a/src/swap/swap_utils.cairo +++ b/src/swap/swap_utils.cairo @@ -3,13 +3,6 @@ // ************************************************************************* // Core lib imports. use starknet::{ContractAddress, contract_address_const}; -use result::ResultTrait; -use core::traits::{Into, TryInto}; -use core::integer::I128Neg; -use satoru::utils::i128::{ - StoreI128, I128Serde, I128Div, I128Mul, I128Default, i128_to_u128, u128_to_i128 -}; - // Local imports. use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; @@ -17,12 +10,16 @@ use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatc use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; use satoru::market::{market::Market, market_utils}; use satoru::fee::fee_utils; -use satoru::utils::{store_arrays::StoreMarketSpan, traits::ContractAddressDefault}; +use satoru::utils::{calc, store_arrays::StoreMarketSpan, traits::ContractAddressDefault}; +use satoru::utils::i256::{i256, i256_neg}; use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; use satoru::swap::error::SwapError; use satoru::data::keys; use satoru::pricing::swap_pricing_utils; use satoru::price::price::{Price, PriceTrait, PriceDefault}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use debug::PrintTrait; + /// Parameters to execute a swap. #[derive(Drop, Copy, starknet::Store, Serde)] @@ -40,11 +37,11 @@ struct SwapParams { /// The address of the token that is being swapped. token_in: ContractAddress, /// The amount of the token that is being swapped. - amount_in: u128, + amount_in: u256, /// An array of market properties, specifying the markets in which the swap should be executed. swap_path_markets: Span, /// The minimum amount of tokens that should be received as part of the swap. - min_output_amount: u128, + min_output_amount: u256, /// The minimum amount of tokens that should be received as part of the swap. receiver: ContractAddress, /// The address of the ui fee receiver. @@ -77,7 +74,7 @@ struct _SwapParams { /// The address of the token that is being swapped. token_in: ContractAddress, /// The amount of the token that is being swapped. - amount_in: u128, + amount_in: u256, /// The address to which the swapped tokens should be sent. receiver: ContractAddress, } @@ -91,15 +88,15 @@ struct SwapCache { /// The price of the token that is being received as part of the swap. token_out_price: Price, /// The amount of the token that is being swapped. - amount_in: u128, + amount_in: u256, /// The amount of the token that is being received as part of the swap. - amount_out: u128, + amount_out: u256, /// The total amount of the token that is being received by all users in the swap pool. - pool_amount_out: u128, + pool_amount_out: u256, /// The price impact of the swap in USD. - price_impact_usd: i128, + price_impact_usd: i256, /// The price impact of the swap in tokens. - price_impact_amount: i128, + price_impact_amount: i256, } /// Swaps a given amount of a given token for another token based on a @@ -109,46 +106,63 @@ struct SwapCache { /// # Returns /// A tuple containing the address of the token that was received as /// part of the swap and the amount of the received token. -fn swap(params: @SwapParams) -> (ContractAddress, u128) { +fn swap(params: @SwapParams) -> (ContractAddress, u256) { if (*params.amount_in == 0) { return (*params.token_in, *params.amount_in); } - let array_length = (*params.swap_path_markets).len(); - if (array_length == 0) { + let swap_path_array_length = (*params.swap_path_markets).len(); + if (swap_path_array_length == 0) { if (*params.amount_in < *params.min_output_amount) { SwapError::INSUFFICIENT_OUTPUT_AMOUNT(*params.amount_in, *params.min_output_amount); } if (params.bank.contract_address != params.receiver) { - (*params.bank).transfer_out(*params.token_in, *params.receiver, *params.amount_in); + (*params.bank) + .transfer_out( + *params.bank.contract_address, + *params.token_in, + *params.receiver, + *params.amount_in + ); } return (*params.token_in, *params.amount_in); } + + //TODO let first_path: Market = *params.swap_path_markets[0]; if (params.bank.contract_address != @first_path.market_token) { - (*params.bank).transfer_out(*params.token_in, first_path.market_token, *params.amount_in); + (*params.bank) + .transfer_out( + *params.bank.contract_address, + *params.token_in, + first_path.market_token, + *params.amount_in + ); } + let mut token_out = *params.token_in; let mut output_amount = *params.amount_in; + let mut i = 0; loop { - if (i >= array_length) { + if (i >= swap_path_array_length) { break; } let market: Market = *params.swap_path_markets[i]; let flag_exists = (*params.data_store) - .get_bool(keys::swap_path_market_flag_key(market.market_token)) - .unwrap(); + .get_bool(keys::swap_path_market_flag_key(market.market_token)); if (flag_exists) { SwapError::DUPLICATED_MARKET_IN_SWAP_PATH(market.market_token); } (*params.data_store).set_bool(keys::swap_path_market_flag_key(market.market_token), true); let next_index = i + 1; - let path: Market = *params.swap_path_markets[next_index]; - let receiver = if (next_index < array_length) { - path.market_token + let mut receiver: ContractAddress = Default::default(); + if next_index < (*params.swap_path_markets).len() { + let market: Market = *params.swap_path_markets[next_index]; + receiver = market.market_token; } else { - *params.receiver - }; + receiver = *params.receiver; + } + let _params = _SwapParams { market: market, token_in: token_out, amount_in: output_amount, receiver: receiver, }; @@ -157,9 +171,13 @@ fn swap(params: @SwapParams) -> (ContractAddress, u128) { output_amount = _output_amount_res; i += 1; }; + + // let balance_ETH_after = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + // .balance_of(contract_address_const::<'caller'>()); + i = 0; loop { - if (i >= array_length) { + if (i >= swap_path_array_length) { break; } let market: Market = *params.swap_path_markets[i]; @@ -178,37 +196,34 @@ fn swap(params: @SwapParams) -> (ContractAddress, u128) { /// * `_params` - The parameters for the swap on this specific market. /// # Returns /// The token and amount that was swapped. -fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u128) { +fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u256) { if (_params.token_in != _params.market.long_token && _params.token_in != _params.market.short_token) { SwapError::INVALID_TOKEN_IN(*_params.token_in, *_params.market.long_token); } let mut cache: SwapCache = Default::default(); + market_utils::validate_swap_market(*params.data_store, *_params.market); - market_utils::validate_swap_market(params.data_store, _params.market); - - cache.token_out = market_utils::get_opposite_token(_params.market, *_params.token_in); + cache.token_out = market_utils::get_opposite_token(*_params.token_in, _params.market); cache.token_in_price = (*params.oracle).get_primary_price(*_params.token_in); cache.token_out_price = (*params.oracle).get_primary_price(cache.token_out); let usd_delta_for_token_felt252: felt252 = (*_params.amount_in * cache.token_out_price.mid_price()) - .into(); + .try_into() + .expect('u256 into felt failed'); + let usd_delta = *_params.amount_in * cache.token_out_price.mid_price(); let price_impact_usd = swap_pricing_utils::get_price_impact_usd( swap_pricing_utils::GetPriceImpactUsdParams { - dataStore: *params.data_store, + data_store: *params.data_store, market: *_params.market, token_a: *_params.token_in, token_b: cache.token_out, price_for_token_a: cache.token_in_price.mid_price(), price_for_token_b: cache.token_out_price.mid_price(), - usd_delta_for_token_a: u128_to_i128( - *_params.amount_in * cache.token_in_price.mid_price() - ), - usd_delta_for_token_b: -u128_to_i128( - *_params.amount_in * cache.token_in_price.mid_price() - ) + usd_delta_for_token_a: calc::to_signed(usd_delta, true), + usd_delta_for_token_b: calc::to_signed(usd_delta, false), } ); @@ -216,7 +231,7 @@ fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u128) *params.data_store, *_params.market.market_token, *_params.amount_in, - price_impact_usd > 0, + price_impact_usd > Zeroable::zero(), *params.ui_fee_receiver ); @@ -238,8 +253,9 @@ fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u128) fees.ui_fee_amount, keys::swap_fee_type(), ); - let mut price_impact_amount: i128 = 0; - if (price_impact_usd > 0) { + + let mut price_impact_amount: i256 = Zeroable::zero(); + if (price_impact_usd > Zeroable::zero()) { // when there is a positive price impact factor, additional tokens from the swap impact pool // are withdrawn for the user // for example, if 50,000 USDC is swapped out and there is a positive price impact @@ -260,7 +276,7 @@ fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u128) price_impact_usd ); - cache.amount_out += i128_to_u128(price_impact_amount); + cache.amount_out += calc::to_unsigned(price_impact_amount); } else { // when there is a negative price impact factor, // less of the input amount is sent to the pool @@ -277,13 +293,13 @@ fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u128) price_impact_usd ); - // TODO should be -price_impact_amount when i128 supported - if (fees.amount_after_fees <= i128_to_u128(price_impact_amount)) { + if fees.amount_after_fees <= calc::to_unsigned(i256_neg(price_impact_amount)) { SwapError::SWAP_PRICE_IMPACT_EXCEEDS_AMOUNT_IN( - fees.amount_after_fees, i128_to_u128(price_impact_amount) + fees.amount_after_fees, price_impact_amount ); } - cache.amount_in = fees.amount_after_fees - i128_to_u128(price_impact_amount); + + cache.amount_in = fees.amount_after_fees - calc::to_unsigned(i256_neg(price_impact_amount)); cache.amount_out = cache.amount_in * cache.token_in_price.min / cache.token_out_price.max; cache.pool_amount_out = cache.amount_out; } @@ -291,18 +307,18 @@ fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u128) // the amountOut value includes the positive price impact amount if (_params.receiver != _params.market.market_token) { IBankDispatcher { contract_address: *_params.market.market_token } - .transfer_out(cache.token_out, *_params.receiver, cache.amount_out); + .transfer_out( + *_params.market.market_token, cache.token_out, *_params.receiver, cache.amount_out + ); } - let mut delta_felt252: felt252 = (cache.amount_in + fees.fee_amount_for_pool).into(); market_utils::apply_delta_to_pool_amount( *params.data_store, *params.event_emitter, *_params.market, *_params.token_in, - delta_felt252.try_into().unwrap(), + calc::to_signed((cache.amount_in + fees.fee_amount_for_pool), true), ); - // the poolAmountOut excludes the positive price impact amount // as that is deducted from the swap impact pool instead market_utils::apply_delta_to_pool_amount( @@ -310,7 +326,7 @@ fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u128) *params.event_emitter, *_params.market, cache.token_out, - cache.pool_amount_out // TODO: should be -price_impact_amount when i128 supported + calc::to_signed(cache.pool_amount_out, false), ); let prices = market_utils::MarketPrices { @@ -328,8 +344,8 @@ fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u128) }; market_utils::validate_pool_amount(params.data_store, _params.market, *_params.token_in); - market_utils::validata_reserve( - params.data_store, _params.market, @prices, cache.token_out == *_params.market.long_token + market_utils::validate_reserve( + *params.data_store, _params.market, @prices, cache.token_out == *_params.market.long_token ); let (pnl_factor_type_for_longs, pnl_factor_type_for_shorts) = if (cache .token_out == *_params @@ -343,7 +359,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 { @@ -369,7 +385,7 @@ fn _swap(params: @SwapParams, _params: @_SwapParams) -> (ContractAddress, u128) cache.amount_in, cache.amount_out, price_impact_usd, - price_impact_amount + price_impact_amount, ); (*params.event_emitter) diff --git a/src/test_utils/deposit_setup.cairo b/src/test_utils/deposit_setup.cairo new file mode 100644 index 00000000..a1270acb --- /dev/null +++ b/src/test_utils/deposit_setup.cairo @@ -0,0 +1,406 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +use result::ResultTrait; +use debug::PrintTrait; +use traits::{TryInto, Into}; +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, + ClassHash, +}; +use snforge_std::{declare, start_prank, stop_prank, start_roll, ContractClassTrait, ContractClass}; + + +// Local imports. +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::order::order_utils::{IOrderUtilsDispatcher, IOrderUtilsDispatcherTrait}; +use satoru::market::market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; +use satoru::deposit::deposit::Deposit; +use satoru::withdrawal::withdrawal::Withdrawal; + +use satoru::exchange::withdrawal_handler::{ + IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait +}; +use satoru::exchange::deposit_handler::{IDepositHandlerDispatcher, IDepositHandlerDispatcherTrait}; +use satoru::router::exchange_router::{IExchangeRouterDispatcher, IExchangeRouterDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::reader::reader::{IReaderDispatcher, IReaderDispatcherTrait}; +use satoru::market::market::{Market, UniqueIdMarket}; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::role::role; +use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::test_utils::tests_lib; +use satoru::deposit::deposit_utils::CreateDepositParams; +use satoru::utils::span32::{Span32, DefaultSpan32, Array32Trait}; +use satoru::deposit::deposit_utils; +use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; +use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::withdrawal::withdrawal_vault::{ + IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait +}; +use satoru::data::keys; +use satoru::market::market_utils; +use satoru::price::price::{Price, PriceTrait}; +use satoru::position::position_utils; +use satoru::withdrawal::withdrawal_utils; + +use satoru::exchange::liquidation_handler::{ + ILiquidationHandlerDispatcher, ILiquidationHandlerDispatcherTrait +}; +use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; +use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; +use satoru::order::base_order_utils::{CreateOrderParams}; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::market::{market::{UniqueIdMarketImpl},}; +use satoru::exchange::order_handler::{ + OrderHandler, IOrderHandlerDispatcher, IOrderHandlerDispatcherTrait +}; +use satoru::test_utils::tests_lib::{setup, create_market, teardown}; + +fn deposit_setup( + long_token_amount: u256, short_token_amount: u256 +) -> ( + // This caller address will be used with `start_prank` cheatcode to mock the caller address., + ContractAddress, + // Address of the `MarketFactory` contract. + ContractAddress, + // Address of the `RoleStore` contract. + ContractAddress, + // Address of the `DataStore` contract. + ContractAddress, + // The `MarketToken` class hash for the factory. + ContractClass, + // Interface to interact with the `MarketFactory` contract. + IMarketFactoryDispatcher, + // Interface to interact with the `RoleStore` contract. + IRoleStoreDispatcher, + // Interface to interact with the `DataStore` contract. + IDataStoreDispatcher, + // Interface to interact with the `EventEmitter` contract. + IEventEmitterDispatcher, + // Interface to interact with the `ExchangeRouter` contract. + IExchangeRouterDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositHandlerDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositVaultDispatcher, + IOracleDispatcher, + IOrderHandlerDispatcher, + IOrderVaultDispatcher, + IReaderDispatcher, + IReferralStorageDispatcher, + IWithdrawalHandlerDispatcher, + IWithdrawalVaultDispatcher, + ILiquidationHandlerDispatcher, + Market, +) { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + ) = + setup(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // Create a market. + let market = data_store.get_market(create_market(market_factory)); + + // Set params in data_store + data_store.set_address(keys::fee_token(), market.index_token); + data_store.set_u256(keys::max_swap_path_length(), 5); + + // Set max pool amount. + data_store + .set_u256( + keys::max_pool_amount_key(market.market_token, market.long_token), + 5000000000000000000000000000000000000000000 //500 000 ETH + ); + data_store + .set_u256( + keys::max_pool_amount_key(market.market_token, market.short_token), + 2500000000000000000000000000000000000000000000 //250 000 000 USDC + ); + + // Long setups + + let factor_for_deposits: felt252 = keys::max_pnl_factor_for_deposits(); + data_store + .set_u256( + keys::max_pnl_factor_key(factor_for_deposits, market.market_token, true), + 50000000000000000000000000000000000000000000000 + ); + let factor_for_withdrawal: felt252 = keys::max_pnl_factor_for_withdrawals(); + data_store + .set_u256( + keys::max_pnl_factor_key(factor_for_withdrawal, market.market_token, true), + 50000000000000000000000000000000000000000000000 + ); + data_store.set_u256(keys::reserve_factor_key(market.market_token, true), 1000000000000000000); + data_store + .set_u256( + keys::open_interest_reserve_factor_key(market.market_token, true), 1000000000000000000 + ); + + // Short setup + + let factor_for_deposits: felt252 = keys::max_pnl_factor_for_deposits(); + data_store + .set_u256( + keys::max_pnl_factor_key(factor_for_deposits, market.market_token, false), + 50000000000000000000000000000000000000000000000 + ); + let factor_for_withdrawal: felt252 = keys::max_pnl_factor_for_withdrawals(); + data_store + .set_u256( + keys::max_pnl_factor_key(factor_for_withdrawal, market.market_token, false), + 50000000000000000000000000000000000000000000000 + ); + data_store.set_u256(keys::reserve_factor_key(market.market_token, false), 1000000000000000000); + data_store + .set_u256( + keys::open_interest_reserve_factor_key(market.market_token, false), 1000000000000000000 + ); + + data_store.set_bool('REENTRANCY_GUARD_STATUS', false); + + 'fill the pool'.print(); + // Fill the pool. + IERC20Dispatcher { contract_address: market.long_token } + .mint(market.market_token, 50000000000000000000000000000000000000); // 5 ETH + IERC20Dispatcher { contract_address: market.short_token } + .mint(market.market_token, 25000000000000000000000000000000000000000); // 25000 USDC + 'filled pool 1'.print(); + + IERC20Dispatcher { contract_address: market.long_token } + .mint(caller_address, 9999999999999000000); // 9.999 ETH + IERC20Dispatcher { contract_address: market.short_token } + .mint(caller_address, 49999999999999999000000); // 49.999 UDC + 'filled account'.print(); + + // INITIAL LONG TOKEN IN POOL : 5 ETH + // INITIAL SHORT TOKEN IN POOL : 25000 USDC + + let balance_deposit_vault_before = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(deposit_vault.contract_address); + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_deposit_vault_before == 0, 'balance deposit should be 0'); + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + // Send token to deposit in the deposit vault (this should be in a multi call with create_deposit) + start_prank(market.long_token, caller_address); + start_prank(market.short_token, caller_address); + IERC20Dispatcher { contract_address: market.long_token } + .approve(caller_address, long_token_amount); + IERC20Dispatcher { contract_address: market.short_token } + .approve(caller_address, short_token_amount); + + IERC20Dispatcher { contract_address: market.long_token } + .mint(caller_address, long_token_amount); // 20 ETH + IERC20Dispatcher { contract_address: market.short_token } + .mint(caller_address, short_token_amount); // 100 000 USDC + + // role_store.grant_role(exchange_router.contract_address, role::ROUTER_PLUGIN); + // role_store.grant_role(caller_address, role::ROUTER_PLUGIN); + + exchange_router + .send_tokens(market.long_token, deposit_vault.contract_address, long_token_amount); + exchange_router + .send_tokens(market.short_token, deposit_vault.contract_address, short_token_amount); + + stop_prank(market.long_token); + stop_prank(market.short_token); + + // Create Deposit + + let addresss_zero: ContractAddress = 0.try_into().unwrap(); + + let params = CreateDepositParams { + receiver: caller_address, + callback_contract: addresss_zero, + ui_fee_receiver: addresss_zero, + market: market.market_token, + initial_long_token: market.long_token, + initial_short_token: market.short_token, + long_token_swap_path: Array32Trait::::span32(@array![]), + short_token_swap_path: Array32Trait::::span32(@array![]), + min_market_tokens: 0, + execution_fee: 0, + callback_gas_limit: 0, + }; + 'create deposit'.print(); + + start_roll(deposit_handler.contract_address, 1910); + let key = deposit_handler.create_deposit(caller_address, params); + let first_deposit = data_store.get_deposit(key); + + 'created deposit'.print(); + + assert(first_deposit.account == caller_address, 'Wrong account depositer'); + assert(first_deposit.receiver == caller_address, 'Wrong account receiver'); + assert(first_deposit.initial_long_token == market.long_token, 'Wrong initial long token'); + assert( + first_deposit.initial_long_token_amount == long_token_amount, + 'Wrong initial long token amount' + ); + assert( + first_deposit.initial_short_token_amount == short_token_amount, + 'Wrong init short token amount' + ); + + let price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![4000, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + start_prank(role_store.contract_address, caller_address); + + role_store.grant_role(caller_address, role::ORDER_KEEPER); + role_store.grant_role(caller_address, role::ROLE_ADMIN); + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + 'execute deposit'.print(); + + // Execute Deposit + start_roll(deposit_handler.contract_address, 1915); + deposit_handler.execute_deposit(key, price_params); + + 'executed deposit'.print(); + + // let pool_value_info = market_utils::get_pool_value_info( + // data_store, + // market, + // Price { min: 2000, max: 2000 }, + // Price { min: 2000, max: 2000 }, + // Price { min: 2000, max: 2000 }, + // keys::max_pnl_factor_for_deposits(), + // true, + // ); + + // assert(pool_value_info.pool_value.mag == 42000000000000000000000, 'wrong pool value amount'); + // assert(pool_value_info.long_token_amount == 6000000000000000000, 'wrong long token amount'); + // assert(pool_value_info.short_token_amount == 30000000000000000000000, 'wrong short token amount'); + + let not_deposit = data_store.get_deposit(key); + let default_deposit: Deposit = Default::default(); + assert(not_deposit == default_deposit, 'Still existing deposit'); + + let market_token_dispatcher = IMarketTokenDispatcher { contract_address: market.market_token }; + let balance_market_token = market_token_dispatcher.balance_of(caller_address); + + assert(balance_market_token != 0, 'should receive market token'); + + let balance_deposit_vault_after = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(deposit_vault.contract_address); + + ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market + ) +} + +fn exec_order( + order_handler: ContractAddress, + role_store: ContractAddress, + key: felt252, + long_token_price: u256, + short_token_price: u256 +) -> () { + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![ + long_token_price, short_token_price + ], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + IRoleStoreDispatcher { contract_address: role_store } + .grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler); + start_prank(order_handler, keeper_address); + // TODO add real signatures check on Oracle Account + IOrderHandlerDispatcher { contract_address: order_handler } + .execute_order(key, set_price_params); +} diff --git a/src/test_utils/tests_lib.cairo b/src/test_utils/tests_lib.cairo new file mode 100644 index 00000000..31f96bdc --- /dev/null +++ b/src/test_utils/tests_lib.cairo @@ -0,0 +1,753 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +use result::ResultTrait; +use debug::PrintTrait; +use traits::{TryInto, Into}; +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, + ClassHash, +}; +use snforge_std::{declare, start_prank, stop_prank, start_roll, ContractClassTrait, ContractClass}; + + +// Local imports. +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::order::order_utils::{IOrderUtilsDispatcher, IOrderUtilsDispatcherTrait}; +use satoru::market::market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; +use satoru::deposit::deposit::Deposit; +use satoru::withdrawal::withdrawal::Withdrawal; + +use satoru::exchange::withdrawal_handler::{ + IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait +}; +use satoru::exchange::deposit_handler::{IDepositHandlerDispatcher, IDepositHandlerDispatcherTrait}; +use satoru::router::exchange_router::{IExchangeRouterDispatcher, IExchangeRouterDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::reader::reader::{IReaderDispatcher, IReaderDispatcherTrait}; +use satoru::market::market::{Market, UniqueIdMarket}; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::role::role; +use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::deposit::deposit_utils::CreateDepositParams; +use satoru::utils::span32::{Span32, DefaultSpan32, Array32Trait}; +use satoru::deposit::deposit_utils; +use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; +use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::withdrawal::withdrawal_vault::{ + IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait +}; +use satoru::data::keys; +use satoru::market::market_utils; +use satoru::price::price::{Price, PriceTrait}; +use satoru::position::position_utils; +use satoru::withdrawal::withdrawal_utils; + +use satoru::exchange::liquidation_handler::{ + ILiquidationHandlerDispatcher, ILiquidationHandlerDispatcherTrait +}; +use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; +use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; +use satoru::order::base_order_utils::{CreateOrderParams}; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::market::{market::{UniqueIdMarketImpl},}; +use satoru::exchange::order_handler::{ + OrderHandler, IOrderHandlerDispatcher, IOrderHandlerDispatcherTrait +}; +const INITIAL_TOKENS_MINTED: felt252 = 1000; + +fn create_market(market_factory: IMarketFactoryDispatcher) -> ContractAddress { + // Create a market. + let (index_token, short_token) = deploy_tokens(); + let market_type = 'market_type'; + + // Index token is the same as long token here. + market_factory.create_market(index_token, index_token, short_token, market_type) +} + +/// Utility functions to deploy tokens for a market. +fn deploy_tokens() -> (ContractAddress, ContractAddress) { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let contract = declare('ERC20'); + + let eth_address = contract_address_const::<'ETH'>(); + let constructor_calldata = array!['Ethereum', 'ETH', 1000000, 0, caller_address.into()]; + // let constructor_calldata = array!['Ethereum', 'ETH', 10000000000000000000, 0, caller_address.into()]; + contract.deploy_at(@constructor_calldata, eth_address).unwrap(); + + let usdc_address = contract_address_const::<'USDC'>(); + let constructor_calldata = array!['usdc', 'USDC', 1000000, 0, caller_address.into()]; + // let constructor_calldata = array!['usdc', 'USDC', 100000000000000000000000, 0, caller_address.into()]; + contract.deploy_at(@constructor_calldata, usdc_address).unwrap(); + (eth_address, usdc_address) +} + + +/// 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, + // Address of the `MarketFactory` contract. + ContractAddress, + // Address of the `RoleStore` contract. + ContractAddress, + // Address of the `DataStore` contract. + ContractAddress, + // The `MarketToken` class hash for the factory. + ContractClass, + // Interface to interact with the `MarketFactory` contract. + IMarketFactoryDispatcher, + // Interface to interact with the `RoleStore` contract. + IRoleStoreDispatcher, + // Interface to interact with the `DataStore` contract. + IDataStoreDispatcher, + // Interface to interact with the `EventEmitter` contract. + IEventEmitterDispatcher, + // Interface to interact with the `ExchangeRouter` contract. + IExchangeRouterDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositHandlerDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositVaultDispatcher, + IOracleDispatcher, + IOrderHandlerDispatcher, + IOrderVaultDispatcher, + IReaderDispatcher, + IReferralStorageDispatcher, + IWithdrawalHandlerDispatcher, + IWithdrawalVaultDispatcher, + ILiquidationHandlerDispatcher, +) { + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + ) = + setup_contracts(); + grant_roles_and_prank(caller_address, role_store, data_store, market_factory); + ( + caller_address, + market_factory.contract_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + ) +} + +// 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. +/// * `role_store` - The interface to interact with the `RoleStore` contract. +/// * `data_store` - The interface to interact with the `DataStore` contract. +/// * `market_factory` - The interface to interact with the `MarketFactory` contract. +fn grant_roles_and_prank( + caller_address: ContractAddress, + role_store: IRoleStoreDispatcher, + data_store: IDataStoreDispatcher, + market_factory: IMarketFactoryDispatcher, +) { + start_prank(role_store.contract_address, caller_address); + + // Grant the caller the `CONTROLLER` role. + role_store.grant_role(caller_address, role::CONTROLLER); + + // Grant the call the `MARKET_KEEPER` role. + // This role is required to create a market. + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + // 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); + + // Start pranking the `MarketFactory` contract. This is necessary to mock the behavior of the contract + // for testing purposes. + start_prank(market_factory.contract_address, caller_address); +} + +/// Utility function to teardown the test environment. +fn teardown(data_store: IDataStoreDispatcher, market_factory: IMarketFactoryDispatcher) { + stop_prank(data_store.contract_address); + stop_prank(market_factory.contract_address); +} + +/// Setup required contracts. +fn setup_contracts() -> ( + // This caller address will be used with `start_prank` cheatcode to mock the caller address., + ContractAddress, + // Address of the `MarketFactory` contract. + ContractAddress, + // Address of the `RoleStore` contract. + ContractAddress, + // Address of the `DataStore` contract. + ContractAddress, + // The `MarketToken` class hash for the factory. + ContractClass, + // Interface to interact with the `MarketFactory` contract. + IMarketFactoryDispatcher, + // Interface to interact with the `RoleStore` contract. + IRoleStoreDispatcher, + // Interface to interact with the `DataStore` contract. + IDataStoreDispatcher, + // Interface to interact with the `EventEmitter` contract. + IEventEmitterDispatcher, + // Interface to interact with the `ExchangeRouter` contract. + IExchangeRouterDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositHandlerDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositVaultDispatcher, + IOracleDispatcher, + IOrderHandlerDispatcher, + IOrderVaultDispatcher, + IReaderDispatcher, + IReferralStorageDispatcher, + IWithdrawalHandlerDispatcher, + IWithdrawalVaultDispatcher, + ILiquidationHandlerDispatcher, +) { + // 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 }; + + // Declare the `MarketToken` contract. + let market_token_class_hash = declare_market_token(); + + // Deploy the event emitter contract. + let event_emitter_address = deploy_event_emitter(); + // Create a safe dispatcher to interact with the contract. + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + + // Deploy the router contract. + let router_address = deploy_router(role_store_address); + + // Deploy the market factory. + let market_factory_address = deploy_market_factory( + data_store_address, role_store_address, event_emitter_address, market_token_class_hash + ); + // Create a safe dispatcher to interact with the contract. + let market_factory = IMarketFactoryDispatcher { contract_address: market_factory_address }; + + let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); + let oracle_address = deploy_oracle( + role_store_address, oracle_store_address, contract_address_const::<'pragma'>() + ); + + let oracle = IOracleDispatcher { contract_address: oracle_address }; + + let deposit_vault_address = deploy_deposit_vault(role_store_address, data_store_address); + + let deposit_vault = IDepositVaultDispatcher { contract_address: deposit_vault_address }; + let deposit_handler_address = deploy_deposit_handler( + data_store_address, + role_store_address, + event_emitter_address, + deposit_vault_address, + oracle_address + ); + let deposit_handler = IDepositHandlerDispatcher { contract_address: deposit_handler_address }; + + let withdrawal_vault_address = deploy_withdrawal_vault(data_store_address, role_store_address); + let withdrawal_handler_address = deploy_withdrawal_handler( + data_store_address, + role_store_address, + event_emitter_address, + withdrawal_vault_address, + oracle_address + ); + + let order_vault_address = deploy_order_vault( + data_store.contract_address, role_store.contract_address + ); + let order_vault = IOrderVaultDispatcher { contract_address: order_vault_address }; + + let swap_handler_address = deploy_swap_handler_address(role_store_address, data_store_address); + let referral_storage_address = deploy_referral_storage(event_emitter_address); + let increase_order_class_hash = declare_increase_order(); + let decrease_order_class_hash = declare_decrease_order(); + let swap_order_class_hash = declare_swap_order(); + + let order_utils_class_hash = declare_order_utils(); + + let order_handler_address = deploy_order_handler( + data_store_address, + role_store_address, + event_emitter_address, + order_vault_address, + oracle_address, + swap_handler_address, + referral_storage_address, + order_utils_class_hash, + increase_order_class_hash, + decrease_order_class_hash, + swap_order_class_hash + ); + let order_handler = IOrderHandlerDispatcher { contract_address: order_handler_address }; + + let exchange_router_address = deploy_exchange_router( + router_address, + data_store_address, + role_store_address, + event_emitter_address, + deposit_handler_address, + withdrawal_handler_address, + order_handler_address + ); + let exchange_router = IExchangeRouterDispatcher { contract_address: exchange_router_address }; + + 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 }; + + let reader_address = deploy_reader(); + let reader = IReaderDispatcher { contract_address: reader_address }; + + let referal_storage = IReferralStorageDispatcher { contract_address: referral_storage_address }; + + let withdrawal_handler = IWithdrawalHandlerDispatcher { + contract_address: withdrawal_handler_address + }; + let withdrawal_vault = IWithdrawalVaultDispatcher { + contract_address: withdrawal_vault_address + }; + let liquidation_handler_address = deploy_liquidation_handler( + data_store_address, + role_store_address, + event_emitter_address, + order_vault_address, + oracle_address, + swap_handler_address, + referral_storage_address, + order_utils_class_hash, + increase_order_class_hash, + decrease_order_class_hash, + swap_order_class_hash + ); + + let liquidation_handler = ILiquidationHandlerDispatcher { + contract_address: liquidation_handler_address + }; + ( + contract_address_const::<'caller'>(), + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + ) +} + +/// Utility function to declare a `MarketToken` contract. +fn declare_market_token() -> ContractClass { + declare('MarketToken') +} + +/// Utility function to deploy a market factory contract and return its address. +fn deploy_market_factory( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + market_token_class_hash: ContractClass, +) -> ContractAddress { + let contract = declare('MarketFactory'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'market_factory'>(); + start_prank(deployed_contract_address, caller_address); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + constructor_calldata.append(event_emitter_address.into()); + constructor_calldata.append(market_token_class_hash.class_hash.into()); + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address: ContractAddress = 0x1.try_into().unwrap(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_router(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('Router'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'router'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_deposit_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + deposit_vault_address: ContractAddress, + oracle_address: ContractAddress +) -> ContractAddress { + let contract = declare('DepositHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'deposit_handler'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + deposit_vault_address.into(), + oracle_address.into() + ], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_oracle_store( + role_store_address: ContractAddress, event_emitter_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OracleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle_store'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![role_store_address.into(), event_emitter_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_oracle( + role_store_address: ContractAddress, + oracle_store_address: ContractAddress, + pragma_address: ContractAddress +) -> ContractAddress { + let contract = declare('Oracle'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![role_store_address.into(), oracle_store_address.into(), pragma_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_deposit_vault( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('DepositVault'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'deposit_vault'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![data_store_address.into(), role_store_address.into()], deployed_contract_address + ) + .unwrap() +} + +fn deploy_withdrawal_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + withdrawal_vault_address: ContractAddress, + oracle_address: ContractAddress +) -> ContractAddress { + let contract = declare('WithdrawalHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'withdrawal_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + withdrawal_vault_address.into(), + oracle_address.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_withdrawal_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('WithdrawalVault'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'withdrawal_vault'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![data_store_address.into(), role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_order_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + order_vault_address: ContractAddress, + oracle_address: ContractAddress, + swap_handler_address: ContractAddress, + referral_storage_address: ContractAddress, + order_utils_class_hash: ClassHash, + increase_order_class_hash: ClassHash, + decrease_order_class_hash: ClassHash, + swap_order_class_hash: ClassHash +) -> ContractAddress { + let contract = declare('OrderHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'order_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + order_vault_address.into(), + oracle_address.into(), + swap_handler_address.into(), + referral_storage_address.into(), + order_utils_class_hash.into(), + increase_order_class_hash.into(), + decrease_order_class_hash.into(), + swap_order_class_hash.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_liquidation_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + order_vault_address: ContractAddress, + oracle_address: ContractAddress, + swap_handler_address: ContractAddress, + referral_storage_address: ContractAddress, + order_utils_class_hash: ClassHash, + increase_order_class_hash: ClassHash, + decrease_order_class_hash: ClassHash, + swap_order_class_hash: ClassHash +) -> ContractAddress { + let contract = declare('LiquidationHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'liquidation_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + order_vault_address.into(), + oracle_address.into(), + swap_handler_address.into(), + referral_storage_address.into(), + order_utils_class_hash.into(), + increase_order_class_hash.into(), + decrease_order_class_hash.into(), + swap_order_class_hash.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_swap_handler_address( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('SwapHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'swap_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'referral_storage'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_exchange_router( + router_address: ContractAddress, + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + deposit_handler_address: ContractAddress, + withdrawal_handler_address: ContractAddress, + order_handler_address: ContractAddress +) -> ContractAddress { + let contract = declare('ExchangeRouter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'exchange_router'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + router_address.into(), + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + deposit_handler_address.into(), + withdrawal_handler_address.into(), + order_handler_address.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_order_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OrderVault'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'order_vault'>(); + start_prank(deployed_contract_address, caller_address); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn declare_increase_order() -> ClassHash { + declare('IncreaseOrderUtils').class_hash +} +fn declare_decrease_order() -> ClassHash { + declare('DecreaseOrderUtils').class_hash +} +fn declare_swap_order() -> ClassHash { + declare('SwapOrderUtils').class_hash +} + + +fn declare_order_utils() -> ClassHash { + declare('OrderUtils').class_hash +} + +fn deploy_bank( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let bank_address: ContractAddress = contract_address_const::<'bank'>(); + let contract = declare('Bank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + start_prank(data_store_address, caller_address); + contract.deploy_at(@constructor_calldata, bank_address).unwrap() +} + +fn deploy_strict_bank( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let strict_bank_address: ContractAddress = contract_address_const::<'strict_bank'>(); + let contract = declare('StrictBank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + start_prank(strict_bank_address, caller_address); + contract.deploy_at(@constructor_calldata, strict_bank_address).unwrap() +} + +fn deploy_reader() -> ContractAddress { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let reader_address: ContractAddress = contract_address_const::<'reader'>(); + let contract = declare('Reader'); + let mut constructor_calldata = array![]; + start_prank(reader_address, caller_address); + contract.deploy_at(@constructor_calldata, reader_address).unwrap() +} + +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() +} diff --git a/src/tests/deposit/test_deposit_vault.cairo b/src/tests/deposit/test_deposit_vault.cairo deleted file mode 100644 index 192cf0b8..00000000 --- a/src/tests/deposit/test_deposit_vault.cairo +++ /dev/null @@ -1,150 +0,0 @@ -//! Test file for `src/deposit/deposit_vault.cairo`. - -// ************************************************************************* -// IMPORTS -// ************************************************************************* - -// Core lib imports. - -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}; - - -// Local imports. -use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; -use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use satoru::role::role; - -/// TODO: Implement actual test and change the name of this function. -// #[test] -// fn init_deposit_vault_test() { -// // ********************************************************************************************* -// // * SETUP * -// // ********************************************************************************************* - -// let (caller_address, deposit_vault, role_store, data_store) = setup(); -// // ********************************************************************************************* -// // * TEST LOGIC * -// // ********************************************************************************************* - -// // Empty test for now. - -// // ********************************************************************************************* -// // * TEARDOWN * -// // ********************************************************************************************* -// teardown(data_store, deposit_vault); -// } - -/// 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) -} - -// 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); - - // 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); - - // 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); -} - -/// Utility function to teardown the test environment. -fn teardown(data_store: IDataStoreDispatcher, deposit_vault: IDepositVaultDispatcher) { - stop_prank(data_store.contract_address); - stop_prank(deposit_vault.contract_address); -} - -/// 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. - IDataStoreDispatcher, -) { - 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 }; - - // 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 `DepositVault` contract. - let deposit_vault_address = deploy_deposit_vault(role_store_address, data_store_address); - // Create a safe dispatcher to interact with the contract. - 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) -} - - -/// Utility function to deploy a `DepositVault` contract and return its address. -fn deploy_deposit_vault( - role_store_address: ContractAddress, data_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() -} - - -/// 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`. -/// TODO: Find a way to share this code. -fn deploy_role_store() -> ContractAddress { - let contract = declare('RoleStore'); - contract.deploy(@ArrayTrait::new()).unwrap() -} diff --git a/src/tests/event/test_position_events_emitted.cairo b/src/tests/event/test_position_events_emitted.cairo deleted file mode 100644 index 8a70574e..00000000 --- a/src/tests/event/test_position_events_emitted.cairo +++ /dev/null @@ -1,589 +0,0 @@ -use starknet::{ContractAddress, contract_address_const}; -use snforge_std::{ - declare, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, event_name_hash, Event, - EventAssertions -}; -use satoru::tests_lib::setup_event_emitter; -use satoru::position::{ - position_event_utils::PositionIncreaseParams, position::Position, - position_utils::{DecreasePositionCollateralValues, DecreasePositionCollateralValuesOutput} -}; -use satoru::pricing::position_pricing_utils::{ - PositionFees, PositionUiFees, PositionBorrowingFees, PositionReferralFees, PositionFundingFees -}; -use satoru::order::order::OrderType; -use satoru::price::price::Price; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::utils::i128::{StoreI128, u128_to_i128, I128Serde, I128Div, I128Mul}; - -#[test] -fn given_normal_conditions_when_emit_position_increase_then_works() { - // ********************************************************************************************* - // * SETUP * - // ********************************************************************************************* - let (contract_address, event_emitter) = setup_event_emitter(); - let mut spy = spy_events(SpyOn::One(contract_address)); - - // ********************************************************************************************* - // * TEST LOGIC * - // ********************************************************************************************* - - // Create a dummy data. - let dummy_position_increase_params = create_dummy_position_increase_params(event_emitter); - - // Create the expected data. - let mut expected_data: Array = array![ - dummy_position_increase_params.position.account.into(), - dummy_position_increase_params.position.market.into(), - dummy_position_increase_params.position.collateral_token.into(), - dummy_position_increase_params.position.size_in_usd.into(), - dummy_position_increase_params.position.size_in_tokens.into(), - dummy_position_increase_params.position.collateral_amount.into(), - dummy_position_increase_params.position.borrowing_factor.into(), - dummy_position_increase_params.position.funding_fee_amount_per_size.into(), - dummy_position_increase_params.position.long_token_claimable_funding_amount_per_size.into(), - dummy_position_increase_params - .position - .short_token_claimable_funding_amount_per_size - .into(), - dummy_position_increase_params.execution_price.into(), - dummy_position_increase_params.index_token_price.max.into(), - dummy_position_increase_params.index_token_price.min.into(), - dummy_position_increase_params.collateral_token_price.max.into(), - dummy_position_increase_params.collateral_token_price.min.into(), - dummy_position_increase_params.size_delta_usd.into(), - dummy_position_increase_params.size_delta_in_tokens.into(), - ]; - - // serialize orderType enum then we have to serialize the other params event - dummy_position_increase_params.order_type.serialize(ref expected_data); - dummy_position_increase_params.collateral_delta_amount.serialize(ref expected_data); - dummy_position_increase_params.price_impact_usd.serialize(ref expected_data); - dummy_position_increase_params.price_impact_amount.serialize(ref expected_data); - dummy_position_increase_params.position.is_long.serialize(ref expected_data); - dummy_position_increase_params.order_key.serialize(ref expected_data); - dummy_position_increase_params.position_key.serialize(ref expected_data); - - // Emit the event. - event_emitter.emit_position_increase(dummy_position_increase_params); - - // Assert the event was emitted. - spy - .assert_emitted( - @array![ - Event { - from: contract_address, - name: 'PositionIncrease', - keys: array![], - data: expected_data - } - ] - ); - // Assert there are no more events. - assert(spy.events.len() == 0, 'There should be no events'); -} - -#[test] -fn given_normal_conditions_when_emit_position_decrease_then_works() { - // ********************************************************************************************* - // * SETUP * - // ********************************************************************************************* - let (contract_address, event_emitter) = setup_event_emitter(); - let mut spy = spy_events(SpyOn::One(contract_address)); - - // ********************************************************************************************* - // * TEST LOGIC * - // ********************************************************************************************* - - // Create a dummy data. - let dummy_position = create_dummy_position(); - let order_key = 'order_key'; - let position_key = 'position_key'; - let size_delta_usd = 100; - let collateral_delta_amount = 200; - let order_type = OrderType::MarketSwap(()); - let index_token_price = Price { min: 100, max: 100 }; - let collateral_token_price = Price { min: 80, max: 85 }; - let dummy_collateral_values = create_dummy_dec_pos_collateral_values(); - - // Create the expected data. - let mut expected_data: Array = array![ - dummy_position.account.into(), - dummy_position.market.into(), - dummy_position.collateral_token.into(), - dummy_position.size_in_usd.into(), - dummy_position.size_in_tokens.into(), - dummy_position.collateral_amount.into(), - dummy_position.borrowing_factor.into(), - dummy_position.funding_fee_amount_per_size.into(), - dummy_position.long_token_claimable_funding_amount_per_size.into(), - dummy_position.short_token_claimable_funding_amount_per_size.into(), - dummy_collateral_values.execution_price.into(), - index_token_price.max.into(), - index_token_price.min.into(), - collateral_token_price.max.into(), - collateral_token_price.min.into(), - size_delta_usd.into(), - dummy_collateral_values.size_delta_in_tokens.into(), - collateral_delta_amount.into(), - dummy_collateral_values.price_impact_diff_usd.into(), - ]; - - // serialize orderType enum then we have to serialize the other params event - order_type.serialize(ref expected_data); - dummy_collateral_values.price_impact_usd.serialize(ref expected_data); - dummy_collateral_values.base_pnl_usd.serialize(ref expected_data); - dummy_collateral_values.uncapped_base_pnl_usd.serialize(ref expected_data); - dummy_position.is_long.serialize(ref expected_data); - order_key.serialize(ref expected_data); - position_key.serialize(ref expected_data); - - // Emit the event. - event_emitter - .emit_position_decrease( - order_key, - position_key, - dummy_position, - size_delta_usd, - collateral_delta_amount, - order_type, - dummy_collateral_values, - index_token_price, - collateral_token_price - ); - - // Assert the event was emitted. - spy - .assert_emitted( - @array![ - Event { - from: contract_address, - name: 'PositionDecrease', - keys: array![], - data: expected_data - } - ] - ); - // Assert there are no more events. - assert(spy.events.len() == 0, 'There should be no events'); -} - - -#[test] -fn given_normal_conditions_when_emit_insolvent_close_then_works() { - // ********************************************************************************************* - // * SETUP * - // ********************************************************************************************* - let (contract_address, event_emitter) = setup_event_emitter(); - let mut spy = spy_events(SpyOn::One(contract_address)); - - // ********************************************************************************************* - // * TEST LOGIC * - // ********************************************************************************************* - - // Create a dummy data. - let order_key = 'order_key'; - let position_collateral_amount = 100; - let base_pnl_usd = 50; - let remaining_cost_usd = 75; - - // Create the expected data. - let expected_data: Array = array![ - order_key, - position_collateral_amount.into(), - base_pnl_usd.into(), - remaining_cost_usd.into(), - ]; - - // Emit the event. - event_emitter - .emit_insolvent_close_info( - order_key, position_collateral_amount, base_pnl_usd, remaining_cost_usd - ); - - // Assert the event was emitted. - spy - .assert_emitted( - @array![ - Event { - from: contract_address, - name: 'InsolventClose', - keys: array![], - data: expected_data - } - ] - ); - // Assert there are no more events. - assert(spy.events.len() == 0, 'There should be no events'); -} - - -#[test] -fn given_normal_conditions_when_emit_insufficient_funding_fee_payment_then_works() { - // ********************************************************************************************* - // * SETUP * - // ********************************************************************************************* - let (contract_address, event_emitter) = setup_event_emitter(); - let mut spy = spy_events(SpyOn::One(contract_address)); - - // ********************************************************************************************* - // * TEST LOGIC * - // ********************************************************************************************* - - // Create a dummy data. - let market = contract_address_const::<'market'>(); - let token = contract_address_const::<'token'>(); - let expected_amount = 100; - let amount_paid_in_collateral_token = 50; - let amount_paid_in_secondary_output_token = 75; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), - token.into(), - expected_amount.into(), - amount_paid_in_collateral_token.into(), - amount_paid_in_secondary_output_token.into(), - ]; - - // Emit the event. - event_emitter - .emit_insufficient_funding_fee_payment( - market, - token, - expected_amount, - amount_paid_in_collateral_token, - amount_paid_in_secondary_output_token - ); - - // Assert the event was emitted. - spy - .assert_emitted( - @array![ - Event { - from: contract_address, - name: 'InsufficientFundingFeePayment', - keys: array![], - data: expected_data - } - ] - ); - // Assert there are no more events. - assert(spy.events.len() == 0, 'There should be no events'); -} - - -#[test] -fn given_normal_conditions_when_emit_position_fees_collected_then_works() { - // ********************************************************************************************* - // * SETUP * - // ********************************************************************************************* - let (contract_address, event_emitter) = setup_event_emitter(); - let mut spy = spy_events(SpyOn::One(contract_address)); - - // ********************************************************************************************* - // * TEST LOGIC * - // ********************************************************************************************* - - // Create a dummy data. - let order_key = 'order_key'; - let position_key = 'position_key'; - let market = contract_address_const::<'market'>(); - let collateral_token = contract_address_const::<'collateral_token'>(); - let trade_size_usd = 100; - let is_increase = true; - let dummy_position_fees = create_dummy_position_fees(); - - // Create the expected data. - let expected_data: Array = array![ - order_key, - position_key, - dummy_position_fees.referral.referral_code, - market.into(), - collateral_token.into(), - dummy_position_fees.referral.affiliate.into(), - dummy_position_fees.referral.trader.into(), - dummy_position_fees.ui.ui_fee_receiver.into(), - dummy_position_fees.collateral_token_price.min.into(), - dummy_position_fees.collateral_token_price.max.into(), - trade_size_usd.into(), - dummy_position_fees.referral.total_rebate_factor.into(), - dummy_position_fees.referral.trader_discount_factor.into(), - dummy_position_fees.referral.total_rebate_amount.into(), - dummy_position_fees.referral.trader_discount_amount.into(), - dummy_position_fees.referral.affiliate_reward_amount.into(), - dummy_position_fees.funding.funding_fee_amount.into(), - dummy_position_fees.funding.claimable_long_token_amount.into(), - dummy_position_fees.funding.claimable_short_token_amount.into(), - dummy_position_fees.funding.latest_funding_fee_amount_per_size.into(), - dummy_position_fees.funding.latest_long_token_claimable_funding_amount_per_size.into(), - dummy_position_fees.funding.latest_short_token_claimable_funding_amount_per_size.into(), - dummy_position_fees.borrowing.borrowing_fee_usd.into(), - dummy_position_fees.borrowing.borrowing_fee_amount.into(), - dummy_position_fees.borrowing.borrowing_fee_receiver_factor.into(), - dummy_position_fees.borrowing.borrowing_fee_amount_for_fee_receiver.into(), - dummy_position_fees.position_fee_factor.into(), - dummy_position_fees.protocol_fee_amount.into(), - dummy_position_fees.position_fee_receiver_factor.into(), - dummy_position_fees.fee_receiver_amount.into(), - dummy_position_fees.fee_amount_for_pool.into(), - dummy_position_fees.position_fee_amount_for_pool.into(), - dummy_position_fees.position_fee_amount.into(), - dummy_position_fees.total_cost_amount.into(), - dummy_position_fees.ui.ui_fee_receiver_factor.into(), - dummy_position_fees.ui.ui_fee_amount.into(), - is_increase.into() - ]; - - // Emit the event. - event_emitter - .emit_position_fees_collected( - order_key, - position_key, - market, - collateral_token, - trade_size_usd, - is_increase, - dummy_position_fees - ); - - // Assert the event was emitted. - spy - .assert_emitted( - @array![ - Event { - from: contract_address, - name: 'PositionFeesCollected', - keys: array![], - data: expected_data - } - ] - ); - // Assert there are no more events. - assert(spy.events.len() == 0, 'There should be no events'); -} - -#[test] -fn given_normal_conditions_when_emit_position_fees_info_then_works() { - // ********************************************************************************************* - // * SETUP * - // ********************************************************************************************* - let (contract_address, event_emitter) = setup_event_emitter(); - let mut spy = spy_events(SpyOn::One(contract_address)); - - // ********************************************************************************************* - // * TEST LOGIC * - // ********************************************************************************************* - - // Create a dummy data. - let order_key = 'order_key'; - let position_key = 'position_key'; - let market = contract_address_const::<'market'>(); - let collateral_token = contract_address_const::<'collateral_token'>(); - let trade_size_usd = 100; - let is_increase = true; - let dummy_position_fees = create_dummy_position_fees(); - - // Create the expected data. - let expected_data: Array = array![ - order_key, - position_key, - dummy_position_fees.referral.referral_code, - market.into(), - collateral_token.into(), - dummy_position_fees.referral.affiliate.into(), - dummy_position_fees.referral.trader.into(), - dummy_position_fees.ui.ui_fee_receiver.into(), - dummy_position_fees.collateral_token_price.min.into(), - dummy_position_fees.collateral_token_price.max.into(), - trade_size_usd.into(), - dummy_position_fees.referral.total_rebate_factor.into(), - dummy_position_fees.referral.trader_discount_factor.into(), - dummy_position_fees.referral.total_rebate_amount.into(), - dummy_position_fees.referral.trader_discount_amount.into(), - dummy_position_fees.referral.affiliate_reward_amount.into(), - dummy_position_fees.funding.funding_fee_amount.into(), - dummy_position_fees.funding.claimable_long_token_amount.into(), - dummy_position_fees.funding.claimable_short_token_amount.into(), - dummy_position_fees.funding.latest_funding_fee_amount_per_size.into(), - dummy_position_fees.funding.latest_long_token_claimable_funding_amount_per_size.into(), - dummy_position_fees.funding.latest_short_token_claimable_funding_amount_per_size.into(), - dummy_position_fees.borrowing.borrowing_fee_usd.into(), - dummy_position_fees.borrowing.borrowing_fee_amount.into(), - dummy_position_fees.borrowing.borrowing_fee_receiver_factor.into(), - dummy_position_fees.borrowing.borrowing_fee_amount_for_fee_receiver.into(), - dummy_position_fees.position_fee_factor.into(), - dummy_position_fees.protocol_fee_amount.into(), - dummy_position_fees.position_fee_receiver_factor.into(), - dummy_position_fees.fee_receiver_amount.into(), - dummy_position_fees.fee_amount_for_pool.into(), - dummy_position_fees.position_fee_amount_for_pool.into(), - dummy_position_fees.position_fee_amount.into(), - dummy_position_fees.total_cost_amount.into(), - dummy_position_fees.ui.ui_fee_receiver_factor.into(), - dummy_position_fees.ui.ui_fee_amount.into(), - is_increase.into() - ]; - - // Emit the event. - event_emitter - .emit_position_fees_info( - order_key, - position_key, - market, - collateral_token, - trade_size_usd, - is_increase, - dummy_position_fees - ); - - // Assert the event was emitted. - spy - .assert_emitted( - @array![ - Event { - from: contract_address, - name: 'PositionFeesInfo', - keys: array![], - data: expected_data - } - ] - ); - // Assert there are no more events. - assert(spy.events.len() == 0, 'There should be no events'); -} - -fn create_dummy_position_increase_params( - event_emitter: IEventEmitterDispatcher -) -> PositionIncreaseParams { - PositionIncreaseParams { - event_emitter: event_emitter, - order_key: 'order_key', - position_key: 'position_key', - position: create_dummy_position(), - index_token_price: Price { min: 100, max: 100 }, - collateral_token_price: Price { min: 80, max: 85 }, - execution_price: 100, - size_delta_usd: 3, - size_delta_in_tokens: 1, - collateral_delta_amount: 2, - price_impact_usd: 1, - price_impact_amount: 1, - order_type: OrderType::MarketSwap(()) - } -} - -fn create_dummy_position() -> Position { - Position { - key: 1, - account: contract_address_const::<'account'>(), - market: contract_address_const::<'market'>(), - collateral_token: contract_address_const::<'collateral_token'>(), - size_in_usd: 100, - size_in_tokens: 1, - collateral_amount: 2, - borrowing_factor: 3, - funding_fee_amount_per_size: 4, - long_token_claimable_funding_amount_per_size: 5, - short_token_claimable_funding_amount_per_size: 6, - increased_at_block: 15000, - decreased_at_block: 15001, - is_long: false - } -} - - -fn create_dummy_dec_pos_collateral_values() -> DecreasePositionCollateralValues { - let dummy_values_output = DecreasePositionCollateralValuesOutput { - output_token: 0x102.try_into().unwrap(), - output_amount: 5, - secondary_output_token: 0x103.try_into().unwrap(), - secondary_output_amount: 8, - }; - - DecreasePositionCollateralValues { - execution_price: 10, - remaining_collateral_amount: 10, - base_pnl_usd: 10, - uncapped_base_pnl_usd: 10, - size_delta_in_tokens: 10, - price_impact_usd: 10, - price_impact_diff_usd: 10, - output: dummy_values_output - } -} - -fn create_dummy_position_fees() -> PositionFees { - let collateral_token_price = Price { min: 100, max: 102 }; - - let dummy_pos_referral_fees = PositionReferralFees { - /// The referral code used. - referral_code: 'referral_code', - /// The referral affiliate of the trader. - affiliate: contract_address_const::<'affiliate'>(), - /// The trader address. - trader: contract_address_const::<'trader'>(), - /// The total rebate factor. - total_rebate_factor: 100, - /// The trader discount factor. - trader_discount_factor: 2, - /// The total rebate amount. - total_rebate_amount: 1, - /// The discount amount for the trader. - trader_discount_amount: 2, - /// The affiliate reward amount. - affiliate_reward_amount: 1, - }; - - let dummy_pos_funding_fees = PositionFundingFees { - /// The amount of funding fees in tokens. - funding_fee_amount: 10, - /// The negative funding fee in long token that is claimable. - claimable_long_token_amount: 10, - /// The negative funding fee in short token that is claimable. - claimable_short_token_amount: 10, - /// The latest long token funding fee amount per size for the market. - latest_funding_fee_amount_per_size: 10, - /// The latest long token funding amount per size for the market. - latest_long_token_claimable_funding_amount_per_size: 10, - /// The latest short token funding amount per size for the market. - latest_short_token_claimable_funding_amount_per_size: 10, - }; - - let dummy_pos_borrowing_fees = PositionBorrowingFees { - /// The borrowing fees amount in USD. - borrowing_fee_usd: 10, - /// The borrowing fees amount in tokens. - borrowing_fee_amount: 2, - /// The borrowing fees factor for receiver. - borrowing_fee_receiver_factor: 1, - /// The borrowing fees amount in tokens for fee receiver. - borrowing_fee_amount_for_fee_receiver: 3, - }; - - let dummy_pos_ui_fees = PositionUiFees { - /// The ui fee receiver address - ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), - /// The factor for fee receiver. - ui_fee_receiver_factor: 2, - /// The ui fee amount in tokens. - ui_fee_amount: 3, - }; - - PositionFees { - referral: dummy_pos_referral_fees, - funding: dummy_pos_funding_fees, - borrowing: dummy_pos_borrowing_fees, - ui: dummy_pos_ui_fees, - collateral_token_price: collateral_token_price, - position_fee_factor: 7, - protocol_fee_amount: 5, - position_fee_receiver_factor: 5, - fee_receiver_amount: 5, - fee_amount_for_pool: 5, - position_fee_amount_for_pool: 10, - position_fee_amount: 10, - total_cost_amount_excluding_funding: 10, - total_cost_amount: 10 - } -} diff --git a/src/tests/exchange/test_base_order_handler.cairo b/src/tests/exchange/test_base_order_handler.cairo deleted file mode 100644 index c57fd161..00000000 --- a/src/tests/exchange/test_base_order_handler.cairo +++ /dev/null @@ -1,126 +0,0 @@ -use starknet::{ - ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const -}; -use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; - -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::exchange::withdrawal_handler::{ - IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait -}; -use satoru::withdrawal::withdrawal_vault::{ - IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait -}; -use satoru::fee::fee_handler::{IFeeHandlerDispatcher, IFeeHandlerDispatcherTrait}; -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; -use satoru::data::keys::{ - claim_fee_amount_key, claim_ui_fee_amount_key, claim_ui_fee_amount_for_account_key -}; -use satoru::oracle::oracle_utils::{SetPricesParams, SimulatePricesParams}; -use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use satoru::role::role; -use satoru::withdrawal::withdrawal_utils::CreateWithdrawalParams; -use satoru::withdrawal::withdrawal::Withdrawal; -use traits::Default; - -// TODO test when all functions called within get_execute_order_params are implemented - -fn deploy_base_order_handler( - data_store_address: ContractAddress, - role_store_address: ContractAddress, - event_emitter_address: ContractAddress, - order_vault_address: ContractAddress, - oracle_address: ContractAddress, - swap_handler_address: ContractAddress, - referral_storage_address: ContractAddress -) -> ContractAddress { - let contract = declare('BaseOrderHandler'); - let constructor_calldata = array![ - data_store_address.into(), - role_store_address.into(), - event_emitter_address.into(), - order_vault_address.into(), - oracle_address.into(), - swap_handler_address.into(), - referral_storage_address.into(), - ]; - contract.deploy(@constructor_calldata).unwrap() -} - -fn deploy_oracle( - oracle_store_address: ContractAddress, role_store_address: ContractAddress -) -> ContractAddress { - let contract = declare('Oracle'); - let constructor_calldata = array![role_store_address.into(), oracle_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -fn deploy_oracle_store( - role_store_address: ContractAddress, event_emitter_address: ContractAddress -) -> ContractAddress { - let contract = declare('OracleStore'); - let constructor_calldata = array![role_store_address.into(), event_emitter_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -fn deploy_order_vault(order_vault_address: ContractAddress) -> ContractAddress { - let contract = declare('OrderVault'); - let constructor_calldata = array![order_vault_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -fn deploy_strict_bank( - data_store_address: ContractAddress, role_store_address: ContractAddress -) -> ContractAddress { - let contract = declare('StrictBank'); - let constructor_calldata = array![data_store_address.into(), role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -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_role_store() -> ContractAddress { - let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() -} - -fn deploy_event_emitter() -> ContractAddress { - let contract = declare('EventEmitter'); - contract.deploy(@array![]).unwrap() -} - -fn setup() -> ( - ContractAddress, IDataStoreDispatcher, IEventEmitterDispatcher, IWithdrawalHandlerDispatcher -) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); - let order_keeper: ContractAddress = 0x2233.try_into().unwrap(); - let role_store_address = deploy_role_store(); - let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; - let data_store_address = deploy_data_store(role_store_address); - let data_store = IDataStoreDispatcher { contract_address: data_store_address }; - let event_emitter_address = deploy_event_emitter(); - let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; - let strict_bank_address = deploy_strict_bank(data_store_address, role_store_address); - let order_vault_address = deploy_order_vault(strict_bank_address); - let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); - let oracle_address = deploy_oracle(oracle_store_address, role_store_address); - let withdrawal_handler_address = deploy_withdrawal_handler( - data_store_address, - role_store_address, - event_emitter_address, - withdrawal_vault_address, - oracle_address - ); - - let withdrawal_handler = IWithdrawalHandlerDispatcher { - contract_address: withdrawal_handler_address - }; - start_prank(role_store_address, caller_address); - role_store.grant_role(caller_address, role::CONTROLLER); - role_store.grant_role(order_keeper, role::ORDER_KEEPER); - start_prank(data_store_address, caller_address); - (caller_address, data_store, event_emitter, withdrawal_handler) -} diff --git a/src/tests/exchange/test_liquidation_handler.cairo b/src/tests/exchange/test_liquidation_handler.cairo deleted file mode 100644 index e45e0fa6..00000000 --- a/src/tests/exchange/test_liquidation_handler.cairo +++ /dev/null @@ -1,157 +0,0 @@ -use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait, ContractClass}; -use satoru::exchange::liquidation_handler::{ - LiquidationHandler, ILiquidationHandlerDispatcher, ILiquidationHandler, - ILiquidationHandlerDispatcherTrait -}; -use starknet::{ContractAddress, contract_address_const, ClassHash, Felt252TryIntoContractAddress}; -use debug::PrintTrait; -use satoru::mock::referral_storage; -use traits::Default; -use satoru::oracle::oracle_utils::SetPricesParams; -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait, IDataStore}; -use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use satoru::role::role; -use satoru::role::role_module::{IRoleModuleDispatcher, IRoleModuleDispatcherTrait}; -use satoru::order::order::{Order, OrderType, OrderTrait, DecreasePositionSwapType}; -use satoru::utils::span32::{Span32, Array32Trait}; - -#[test] -fn test_exec_liquidation_true() { - let collateral_token: ContractAddress = contract_address_const::<1>(); - let ( - data_store, liquidation_keeper, liquidation_handler_address, liquidation_handler_dispatcher - ) = - _setup(); - start_prank(liquidation_handler_address, liquidation_keeper); - //TODO: add test for execute_liquidation - liquidation_handler_dispatcher - .execute_liquidation( - account: contract_address_const::<'account'>(), - market: contract_address_const::<'market'>(), - collateral_token: collateral_token, - is_long: true, - oracle_params: Default::default() - ); -} - -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_role_store() -> ContractAddress { - let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() -} - -fn deploy_event_emitter() -> ContractAddress { - let contract = declare('EventEmitter'); - contract.deploy(@array![]).unwrap() -} - -fn deploy_order_vault( - data_store_address: ContractAddress, role_store_address: ContractAddress -) -> ContractAddress { - let contract = declare('OrderVault'); - contract.deploy(@array![data_store_address.into(), role_store_address.into()]).unwrap() -} - -fn deploy_liquidation_handler( - role_store_address: ContractAddress, - data_store_address: ContractAddress, - event_emitter_address: ContractAddress, - order_vault_address: ContractAddress, - swap_handler_address: ContractAddress, - oracle_address: ContractAddress -) -> ContractAddress { - let contract = declare('LiquidationHandler'); - contract - .deploy( - @array![ - data_store_address.into(), - role_store_address.into(), - event_emitter_address.into(), - order_vault_address.into(), - oracle_address.into(), - swap_handler_address.into(), - Default::default() - ] - ) - .unwrap() -} - -fn deploy_oracle( - role_store_address: ContractAddress, - oracle_store_address: ContractAddress, - pragma_address: ContractAddress -) -> ContractAddress { - let contract = declare('Oracle'); - contract - .deploy( - @array![role_store_address.into(), oracle_store_address.into(), pragma_address.into()] - ) - .unwrap() -} - -fn deploy_swap_handler(role_store_address: ContractAddress) -> ContractAddress { - let contract = declare('SwapHandler'); - contract.deploy(@array![role_store_address.into()]).unwrap() -} - -fn deploy_referral_storage() -> ContractAddress { - let contract = declare('ReferralStorage'); - contract.deploy(@array![]).unwrap() -} - -fn deploy_oracle_store( - role_store_address: ContractAddress, event_emitter_address: ContractAddress, -) -> ContractAddress { - let contract = declare('OracleStore'); - contract.deploy(@array![role_store_address.into(), event_emitter_address.into()]).unwrap() -} - -fn deploy_role_module(role_store_address: ContractAddress) -> IRoleModuleDispatcher { - let contract = declare('RoleModule'); - let role_module_address = contract.deploy(@array![role_store_address.into()]).unwrap(); - IRoleModuleDispatcher { contract_address: role_module_address } -} - -fn _setup() -> ( - IDataStoreDispatcher, ContractAddress, ContractAddress, ILiquidationHandlerDispatcher -) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); - let liquidation_keeper: ContractAddress = 0x2233.try_into().unwrap(); - let role_store_address = deploy_role_store(); - let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; - let data_store_address = deploy_data_store(role_store_address); - let data_store = IDataStoreDispatcher { contract_address: data_store_address }; - let event_emitter_address = deploy_event_emitter(); - let order_vault_address = deploy_order_vault(data_store_address, role_store_address); - let swap_handler_address = deploy_swap_handler(role_store_address); - let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); - let oracle_address = deploy_oracle( - role_store_address, oracle_store_address, contract_address_const::<'pragma'>() - ); - //let referral_storage_address = deploy_referral_storage(); - let liquidation_handler_address = deploy_liquidation_handler( - role_store_address, - data_store_address, - event_emitter_address, - order_vault_address, - swap_handler_address, - oracle_address - ); - let liquidation_handler_dispatcher = ILiquidationHandlerDispatcher { - contract_address: liquidation_handler_address - }; - let role_module = deploy_role_module(role_store_address); - start_prank(role_store_address, caller_address); - role_store.grant_role(caller_address, role::CONTROLLER); - role_store.grant_role(liquidation_keeper, role::LIQUIDATION_KEEPER); - role_store.grant_role(liquidation_keeper, role::ORDER_KEEPER); - role_store.grant_role(liquidation_handler_address, role::FROZEN_ORDER_KEEPER); - role_store.grant_role(liquidation_handler_address, role::CONTROLLER); - start_prank(data_store_address, caller_address); - (data_store, liquidation_keeper, liquidation_handler_address, liquidation_handler_dispatcher) -} diff --git a/src/tests/exchange/test_withdrawal_handler.cairo b/src/tests/exchange/test_withdrawal_handler.cairo deleted file mode 100644 index 16b01f42..00000000 --- a/src/tests/exchange/test_withdrawal_handler.cairo +++ /dev/null @@ -1,323 +0,0 @@ -use starknet::{ - ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const -}; -use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; - -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::exchange::withdrawal_handler::{ - IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait -}; -use satoru::withdrawal::withdrawal_vault::{ - IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait -}; -use satoru::fee::fee_handler::{IFeeHandlerDispatcher, IFeeHandlerDispatcherTrait}; -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; -use satoru::data::keys::{ - claim_fee_amount_key, claim_ui_fee_amount_key, claim_ui_fee_amount_for_account_key -}; -use satoru::oracle::oracle_utils::{SetPricesParams, SimulatePricesParams}; -use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use satoru::role::role; -use satoru::withdrawal::withdrawal_utils::CreateWithdrawalParams; -use satoru::withdrawal::withdrawal::Withdrawal; -use traits::Default; - -// TODO: Add more tests after withdraw_utils implementation done. -#[test] -fn given_normal_conditions_when_create_withdrawal_then_works() { - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); - - let account: ContractAddress = 0x123.try_into().unwrap(); - let receiver: ContractAddress = 0x234.try_into().unwrap(); - let ui_fee_receiver: ContractAddress = 0x345.try_into().unwrap(); - let market: ContractAddress = 0x456.try_into().unwrap(); - - start_prank(withdrawal_handler.contract_address, caller_address); - - let params: CreateWithdrawalParams = CreateWithdrawalParams { - receiver, - callback_contract: receiver, - ui_fee_receiver, - market, - long_token_swap_path: Default::default(), - short_token_swap_path: Default::default(), - min_long_token_amount: Default::default(), - min_short_token_amount: Default::default(), - execution_fee: Default::default(), - callback_gas_limit: Default::default(), - }; - - withdrawal_handler.create_withdrawal(account, params); -} - -#[test] -#[should_panic(expected: ('unauthorized_access',))] -fn given_caller_not_controller_when_create_withdrawal_then_fails() { - // Should revert, call from anyone else then controller. - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); - let caller: ContractAddress = 0x847.try_into().unwrap(); - start_prank(withdrawal_handler.contract_address, caller); - - let params: CreateWithdrawalParams = CreateWithdrawalParams { - receiver: 0x785.try_into().unwrap(), - callback_contract: 0x786.try_into().unwrap(), - ui_fee_receiver: 0x345.try_into().unwrap(), - market: 0x346.try_into().unwrap(), - long_token_swap_path: Default::default(), - short_token_swap_path: Default::default(), - min_long_token_amount: Default::default(), - min_short_token_amount: Default::default(), - execution_fee: Default::default(), - callback_gas_limit: Default::default(), - }; - - withdrawal_handler.create_withdrawal(caller, params); -} - -#[test] -fn given_normal_conditions_when_cancel_withdrawal_then_works() { - let withdrawal = Withdrawal { - key: Default::default(), - account: 0x785.try_into().unwrap(), - receiver: 0x787.try_into().unwrap(), - callback_contract: 0x348.try_into().unwrap(), - ui_fee_receiver: 0x345.try_into().unwrap(), - market: 0x346.try_into().unwrap(), - long_token_swap_path: Default::default(), - short_token_swap_path: Default::default(), - market_token_amount: Default::default(), - min_long_token_amount: Default::default(), - min_short_token_amount: Default::default(), - updated_at_block: Default::default(), - execution_fee: Default::default(), - callback_gas_limit: Default::default(), - }; - - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); - start_prank(withdrawal_handler.contract_address, caller_address); - - let withdrawal_key = 'SAMPLE_WITHDRAW'; - data_store.set_withdrawal(withdrawal_key, withdrawal); - - // Key cleaning should be done in withdrawal_utils. We only check call here. - withdrawal_handler.cancel_withdrawal(withdrawal_key); -} - -#[test] -#[should_panic(expected: ('Option::unwrap failed.',))] -fn given_unexisting_key_when_cancel_withdrawal_then_fails() { - let withdrawal = Withdrawal { - key: Default::default(), - account: 0x785.try_into().unwrap(), - receiver: 0x787.try_into().unwrap(), - callback_contract: 0x348.try_into().unwrap(), - ui_fee_receiver: 0x345.try_into().unwrap(), - market: 0x346.try_into().unwrap(), - long_token_swap_path: Default::default(), - short_token_swap_path: Default::default(), - market_token_amount: Default::default(), - min_long_token_amount: Default::default(), - min_short_token_amount: Default::default(), - updated_at_block: Default::default(), - execution_fee: Default::default(), - callback_gas_limit: Default::default(), - }; - - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); - start_prank(withdrawal_handler.contract_address, caller_address); - - let withdrawal_key = 'SAMPLE_WITHDRAW'; - - // Key cleaning should be done in withdrawal_utils. We only check call here. - withdrawal_handler.cancel_withdrawal(withdrawal_key); -} - -#[test] -#[should_panic(expected: ('unauthorized_access',))] -fn given_caller_not_controller_when_execute_withdrawal_then_fails() { - let oracle_params = SetPricesParams { - signer_info: Default::default(), - tokens: Default::default(), - compacted_min_oracle_block_numbers: Default::default(), - compacted_max_oracle_block_numbers: Default::default(), - compacted_oracle_timestamps: Default::default(), - compacted_decimals: Default::default(), - compacted_min_prices: Default::default(), - compacted_min_prices_indexes: Default::default(), - compacted_max_prices: Default::default(), - compacted_max_prices_indexes: Default::default(), - signatures: Default::default(), - price_feed_tokens: Default::default(), - }; - - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); - - let withdrawal_key = 'SAMPLE_WITHDRAW'; - - withdrawal_handler.execute_withdrawal(withdrawal_key, oracle_params); -} - -// Panics due to the absence of a mocked withdrawal, resulting in Option::None being returned. -#[test] -#[should_panic(expected: ('invalid withdrawal key', 'SAMPLE_WITHDRAW'))] -fn given_invalid_withdrawal_key_when_execute_withdrawal_then_fails() { - let oracle_params = SetPricesParams { - signer_info: Default::default(), - tokens: Default::default(), - compacted_min_oracle_block_numbers: Default::default(), - compacted_max_oracle_block_numbers: Default::default(), - compacted_oracle_timestamps: Default::default(), - compacted_decimals: Default::default(), - compacted_min_prices: Default::default(), - compacted_min_prices_indexes: Default::default(), - compacted_max_prices: Default::default(), - compacted_max_prices_indexes: Default::default(), - signatures: Default::default(), - price_feed_tokens: Default::default(), - }; - - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); - let order_keeper = contract_address_const::<0x2233>(); - start_prank(withdrawal_handler.contract_address, order_keeper); - - let withdrawal_key = 'SAMPLE_WITHDRAW'; - - withdrawal_handler.execute_withdrawal(withdrawal_key, oracle_params); -} - -#[test] -#[should_panic(expected: ('unauthorized_access',))] -fn given_caller_not_controller_when_simulate_execute_withdrawal_then_fails() { - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); - let caller: ContractAddress = contract_address_const::<0x847>(); - start_prank(withdrawal_handler.contract_address, caller); - - let oracle_params = SimulatePricesParams { - primary_tokens: Default::default(), primary_prices: Default::default(), - }; - - let withdrawal_key = 'SAMPLE_WITHDRAW'; - - withdrawal_handler.simulate_execute_withdrawal(withdrawal_key, oracle_params); -} - -// Panics due to the absence of a mocked withdrawal, resulting in Option::None being returned. -#[test] -#[should_panic(expected: ('invalid withdrawal key', 'SAMPLE_WITHDRAW'))] -fn given_invalid_withdrawal_key_when_simulate_execute_withdrawal_then_fails() { - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); - let oracle_params = SimulatePricesParams { - primary_tokens: Default::default(), primary_prices: Default::default(), - }; - - start_prank(withdrawal_handler.contract_address, caller_address); - - let withdrawal_key = 'SAMPLE_WITHDRAW'; - - withdrawal_handler.simulate_execute_withdrawal(withdrawal_key, oracle_params); -} - - -fn deploy_withdrawal_handler( - data_store_address: ContractAddress, - role_store_address: ContractAddress, - event_emitter_address: ContractAddress, - withdrawal_vault_address: ContractAddress, - oracle_address: ContractAddress -) -> ContractAddress { - let contract = declare('WithdrawalHandler'); - let constructor_calldata = array![ - data_store_address.into(), - role_store_address.into(), - event_emitter_address.into(), - withdrawal_vault_address.into(), - oracle_address.into() - ]; - contract.deploy(@constructor_calldata).unwrap() -} - -fn deploy_oracle( - oracle_store_address: ContractAddress, - role_store_address: ContractAddress, - pragma_address: ContractAddress -) -> ContractAddress { - let contract = declare('Oracle'); - let constructor_calldata = array![ - role_store_address.into(), oracle_store_address.into(), pragma_address.into() - ]; - contract.deploy(@constructor_calldata).unwrap() -} - -fn deploy_oracle_store( - role_store_address: ContractAddress, event_emitter_address: ContractAddress -) -> ContractAddress { - let contract = declare('OracleStore'); - let constructor_calldata = array![role_store_address.into(), event_emitter_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -fn deploy_withdrawal_vault(strict_bank_address: ContractAddress) -> ContractAddress { - let contract = declare('WithdrawalVault'); - let constructor_calldata = array![strict_bank_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -fn deploy_strict_bank( - data_store_address: ContractAddress, role_store_address: ContractAddress -) -> ContractAddress { - let contract = declare('StrictBank'); - let constructor_calldata = array![data_store_address.into(), role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -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_role_store() -> ContractAddress { - let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() -} - -fn deploy_event_emitter() -> ContractAddress { - let contract = declare('EventEmitter'); - contract.deploy(@array![]).unwrap() -} - -fn setup() -> ( - ContractAddress, IDataStoreDispatcher, IEventEmitterDispatcher, IWithdrawalHandlerDispatcher -) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); - let order_keeper: ContractAddress = 0x2233.try_into().unwrap(); - let role_store_address = deploy_role_store(); - let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; - let data_store_address = deploy_data_store(role_store_address); - let data_store = IDataStoreDispatcher { contract_address: data_store_address }; - let event_emitter_address = deploy_event_emitter(); - let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; - let strict_bank_address = deploy_strict_bank(data_store_address, role_store_address); - let withdrawal_vault_address = deploy_withdrawal_vault(strict_bank_address); - let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); - let oracle_address = deploy_oracle( - oracle_store_address, role_store_address, contract_address_const::<'pragma'>() - ); - let withdrawal_handler_address = deploy_withdrawal_handler( - data_store_address, - role_store_address, - event_emitter_address, - withdrawal_vault_address, - oracle_address - ); - - let withdrawal_handler = IWithdrawalHandlerDispatcher { - contract_address: withdrawal_handler_address - }; - start_prank(role_store_address, caller_address); - role_store.grant_role(caller_address, role::CONTROLLER); - role_store.grant_role(order_keeper, role::ORDER_KEEPER); - role_store.grant_role(withdrawal_handler_address, role::CONTROLLER); - start_prank(data_store_address, caller_address); - (caller_address, data_store, event_emitter, withdrawal_handler) -} diff --git a/src/tests/mock/test_governable.cairo b/src/tests/mock/test_governable.cairo deleted file mode 100644 index 2b4ce316..00000000 --- a/src/tests/mock/test_governable.cairo +++ /dev/null @@ -1,145 +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 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 snforge_std::{declare, start_prank, ContractClassTrait}; - -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() -} - -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) -} - -fn setup_with_other_address() -> ( - ContractAddress, - IRoleStoreDispatcher, - IDataStoreDispatcher, - IEventEmitterDispatcher, - IReferralStorageDispatcher, - IGovernableDispatcher -) { - let caller_address: ContractAddress = 0x102.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_only_gov_then_works() { - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); - governable.only_gov(); - teardown(data_store.contract_address); -} - -#[test] -#[should_panic(expected: ('Unauthorized gov caller',))] -fn given_forbidden_when_only_gov_then_fails() { - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup_with_other_address(); - governable.only_gov(); - teardown(data_store.contract_address); -} - -#[test] -fn given_normal_conditions_when_transfer_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(); - governable.transfer_ownership(new_caller_address); - teardown(data_store.contract_address); -} diff --git a/src/tests/mock/test_referral_storage.cairo b/src/tests/mock/test_referral_storage.cairo deleted file mode 100644 index 220c94ca..00000000 --- a/src/tests/mock/test_referral_storage.cairo +++ /dev/null @@ -1,145 +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 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 snforge_std::{declare, start_prank, ContractClassTrait}; - -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() -} - -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_setting_and_fetching_code_owner_from_storage_then_works() { - let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = - setup(); - - //setting the code_owner and fetching it from storage - let code: felt252 = 'EBDW'; - let new_account: ContractAddress = contract_address_const::<'new_account'>(); - - 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'); - - 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(); - - //fetching the code owner from storage before setting it - let code: felt252 = 'EBDW'; - let new_account: ContractAddress = contract_address_const::<'new_account'>(); - - let res: ContractAddress = referral_storage.code_owners(code); - assert(res == 0.try_into().unwrap(), 'the address is wrong'); - - 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(); - - //setting the referrer_id and fetching it from storage - let tier_id: u128 = 3; - let new_account: ContractAddress = contract_address_const::<'new_account'>(); - - 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'); - - 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(); - - //fetching the referrer_tier from storage before setting it - let tier_id: u128 = 3; - let new_account: ContractAddress = contract_address_const::<'new_account'>(); - - let res: u128 = referral_storage.referrer_tiers(new_account); - assert(res == 0, 'the tier_id is wrong'); - - teardown(data_store.contract_address); -} diff --git a/src/tests/mock/test_referral_utils.cairo b/src/tests/mock/test_referral_utils.cairo deleted file mode 100644 index 759fc9fc..00000000 --- a/src/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 == 2000000000000000000000000000, 'the total_rebate is wrong'); - assert(discount_share == 3000000000000000000000000000, '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/src/tests/order/test_base_order_utils.cairo b/src/tests/order/test_base_order_utils.cairo deleted file mode 100644 index e04da2ee..00000000 --- a/src/tests/order/test_base_order_utils.cairo +++ /dev/null @@ -1,78 +0,0 @@ -use starknet::ContractAddress; -use snforge_std::{start_mock_call, stop_mock_call}; - -use satoru::data::data_store::IDataStoreDispatcherTrait; -use satoru::nonce::nonce_utils::{get_current_nonce, increment_nonce, compute_key}; -use satoru::tests_lib::{setup, teardown}; -use satoru::oracle::oracle::{IOracleSafeDispatcher, IOracleDispatcher, IOracleDispatcherTrait}; -use satoru::order::{ - error::OrderError, order::{Order, SecondaryOrderType, OrderType, DecreasePositionSwapType}, -}; -use satoru::price::price::{Price, PriceTrait}; -use satoru::order::base_order_utils::{ - is_market_order, is_limit_order, is_swap_order, is_position_order, is_increase_order, - is_decrease_order, is_liquidation_order, validate_order_trigger_price, - get_execution_price_for_increase, get_execution_price_for_decrease, validate_non_empty_order -}; - -#[test] -fn given_normal_conditions_when_is_position_order_then_works() { - assert(!is_position_order(OrderType::MarketSwap), 'Should not be position'); - assert(is_position_order(OrderType::MarketIncrease), 'Should be position'); -} - -#[test] -fn given_normal_conditions_when_validate_order_trigger_price_then_works() { - // TODO when oracle - // let oracle_address: ContractAddress = 'oracle'.try_into().unwrap(); - // start_mock_call(oracle_address, 'get_primary_price', Price { min: 9, max: 11 }); - // let oracle = IOracleSafeDispatcher { contract_address: oracle_address }; - // validate_order_trigger_price( - // oracle, - // index_token: 'token'.try_into().unwrap(), - // order_type: OrderType::LimitIncrease, - // trigger_price: 10, - // is_long: true, - // ); - // stop_mock_call(oracle_address, 'get_primary_price'); - assert(true, 'Tautology'); -} - -#[test] -fn given_normal_conditions_when_get_execution_price_for_increase_then_works() { - let price = get_execution_price_for_increase( - size_delta_usd: 200, size_delta_in_tokens: 20, acceptable_price: 10, is_long: true, - ); - assert(price == 10, 'Should be 10'); -} - -#[test] -fn given_normal_conditions_when_get_execution_price_for_decrease_then_works() { - let price = get_execution_price_for_decrease( - index_token_price: Price { min: 9, max: 11 }, - position_size_in_usd: 1000, - position_size_in_tokens: 100, - size_delta_usd: 200, - price_impact_usd: 1, - acceptable_price: 8, - is_long: true, - ); - assert(price == 9, 'Should be 9'); -} - -#[test] -fn given_normal_conditions_when_validate_non_empty_order_then_works() { - let mut order: Order = Default::default(); - order.account = 32.try_into().unwrap(); - order.size_delta_usd = 1; - order.initial_collateral_delta_amount = 1; - validate_non_empty_order(@order); -} - -#[test] -#[should_panic(expected: ('empty_order',))] -fn given_empty_order_when_validate_non_empty_order_then_fails() { - let order: Order = Default::default(); - validate_non_empty_order(@order); -} - diff --git a/src/tests/position/test_position_utils.cairo b/src/tests/position/test_position_utils.cairo deleted file mode 100644 index b17a1a8b..00000000 --- a/src/tests/position/test_position_utils.cairo +++ /dev/null @@ -1,300 +0,0 @@ -// ************************************************************************* -// IMPORTS -// ************************************************************************* - -// Core lib imports. - -use result::ResultTrait; -use traits::{TryInto, Into}; -use starknet::{ - ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, - ClassHash, -}; -use debug::PrintTrait; -use snforge_std::{declare, start_prank, stop_prank, start_warp, ContractClassTrait, ContractClass}; -use poseidon::poseidon_hash_span; -use zeroable::Zeroable; -// Local imports. -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; -use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use satoru::chain::chain::{IChainDispatcher, IChainDispatcherTrait}; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::market::market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait}; -use satoru::market::market::{Market, UniqueIdMarket, IntoMarketToken}; -use satoru::market::{market_utils::MarketPrices}; -use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; -use satoru::data::keys; -use satoru::role::role; -use satoru::price::price::{Price, PriceTrait}; -use satoru::position::{position::Position, position_utils::UpdatePositionParams, position_utils}; -use satoru::tests_lib::{setup, setup_event_emitter, teardown}; -use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; -use satoru::pricing::{position_pricing_utils::PositionFees}; -use satoru::order::{ - order::{Order, SecondaryOrderType, OrderType, DecreasePositionSwapType}, - order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait} -}; -use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; -use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; -use satoru::order::base_order_utils::ExecuteOrderParamsContracts; - -#[test] -fn given_normal_conditions_when_get_position_key_then_works() { - // - // Setup - // - let account: ContractAddress = 'account'.try_into().unwrap(); - let market: ContractAddress = 'market'.try_into().unwrap(); - let token: ContractAddress = 'token'.try_into().unwrap(); - let mut data = array![account.into(), market.into(), token.into(), false.into()]; - let mut data2 = array![account.into(), market.into(), token.into(), true.into()]; - let key_1 = poseidon_hash_span(data.span()); - let key_2 = poseidon_hash_span(data2.span()); - - // Test - let retrieved_key1 = position_utils::get_position_key(account, market, token, false); - let retrieved_key2 = position_utils::get_position_key(account, market, token, true); - assert(key_1 == retrieved_key1, 'invalid key1'); - assert(key_2 == retrieved_key2, 'invalid key2'); -} - - -#[test] -#[should_panic(expected: ('empty_position',))] -fn given_empty_position_when_validate_non_empty_position_then_fails() { - // - // Setup - // - let position: Position = Default::default(); - - // Test - position_utils::validate_non_empty_position(position); -} - -#[test] -fn given_normal_conditions_when_validate_non_empty_position_then_works() { - // - // Setup - // - - let mut position: Position = Default::default(); - position.size_in_tokens = 123; - - // Test - - position_utils::validate_non_empty_position(position); - - position.size_in_tokens = 0; - position.collateral_amount = 123; - - position_utils::validate_non_empty_position(position); -} - -#[test] -#[should_panic(expected: ('invalid_position_size_values',))] -fn given_invalid_position_size_when_validate_position_then_fails() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - - let referral_storage = IReferralStorageDispatcher { - contract_address: '12345'.try_into().unwrap() - }; - - let position: Position = Default::default(); - let market: Market = Default::default(); - let price = Price { min: 0, max: 0 }; - let prices: MarketPrices = MarketPrices { - index_token_price: price, long_token_price: price, short_token_price: price - }; - // Test - position_utils::validate_position( - data_store, referral_storage, position, market, prices, false, false - ); - teardown(data_store.contract_address); -} - -#[test] -#[should_panic(expected: ('empty_market',))] -fn given_empty_market_when_validate_position_then_fails() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - - let referral_storage = IReferralStorageDispatcher { - contract_address: '12345'.try_into().unwrap() - }; - - let mut position: Position = Default::default(); - // Set valid pos size valeus - position.size_in_usd = 100; - position.size_in_tokens = 10; - - let market: Market = Default::default(); - let price = Price { min: 0, max: 0 }; - let prices: MarketPrices = MarketPrices { - index_token_price: price, long_token_price: price, short_token_price: price - }; - - // Test - // Should fail at 'validate_enabled_market' - position_utils::validate_position( - data_store, referral_storage, position, market, prices, false, false - ); - teardown(data_store.contract_address); -} - - -#[test] -#[should_panic(expected: ('minumum position size',))] -fn given_minumum_position_size_when_validate_position_then_fails() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - - let referral_storage = IReferralStorageDispatcher { - contract_address: '12345'.try_into().unwrap() - }; - let token: ContractAddress = 'token'.try_into().unwrap(); - - let mut position: Position = Default::default(); - let mut market: Market = Default::default(); - // Set valid pos size valeus - position.size_in_usd = 100; - position.size_in_tokens = 10; - - // Set valid market colleteral tokens (positon.collateral_token == market.long_token || token == market.short_token;) - position.collateral_token = token; - market.long_token = token; - market.market_token = 'market_token'.try_into().unwrap(); - - let price = Price { min: 0, max: 0 }; - let prices: MarketPrices = MarketPrices { - index_token_price: price, long_token_price: price, short_token_price: price - }; - let should_validate_min_position_size = true; - - // Test - - let min_size: u128 = 1000000; - data_store.set_u128(keys::min_position_size_usd(), min_size); - data_store.set_bool(keys::is_market_disabled_key(market.market_token), false); - // Check key assigned - let retrieved_size = data_store.get_u128(keys::min_position_size_usd()); - assert(retrieved_size == min_size, 'invalid key assignment'); - - // Fail - position_utils::validate_position( - data_store, - referral_storage, - position, - market, - prices, - should_validate_min_position_size, - false - ); - teardown(data_store.contract_address); -} - -#[test] -fn given_normal_conditions_when_increment_claimable_funding_amount_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (event_emitter_address, event_emitter) = setup_event_emitter(); - - let market_token: ContractAddress = 'market_token'.try_into().unwrap(); - let long_token: ContractAddress = 'long_token'.try_into().unwrap(); - let short_token: ContractAddress = 'short_token'.try_into().unwrap(); - let account: ContractAddress = 'account'.try_into().unwrap(); - let long_token_amount: u128 = 10000; - let short_token_amount: u128 = 20000; - - let mut fees: PositionFees = Default::default(); - - let mut params: position_utils::UpdatePositionParams = UpdatePositionParams { - contracts: ExecuteOrderParamsContracts { - data_store, - event_emitter, - order_vault: IOrderVaultDispatcher { contract_address: Zeroable::zero() }, - oracle: IOracleDispatcher { contract_address: Zeroable::zero() }, - swap_handler: ISwapHandlerDispatcher { contract_address: Zeroable::zero() }, - referral_storage: IReferralStorageDispatcher { contract_address: Zeroable::zero() }, - }, - market: Market { market_token, index_token: Zeroable::zero(), long_token, short_token, }, - order: Default::default(), - order_key: 0, - position: Default::default(), - position_key: 0, - secondary_order_type: SecondaryOrderType::None, - }; - - params.order.account = account; - fees.funding.claimable_long_token_amount = long_token_amount; - fees.funding.claimable_short_token_amount = short_token_amount; - - // Test - - position_utils::increment_claimable_funding_amount(params, fees,); - - let claimable_fund_long_key = keys::claimable_funding_amount_by_account_key( - market_token, long_token, account - ); - let claimable_fund_short_key = keys::claimable_funding_amount_by_account_key( - market_token, short_token, account - ); - - // Check funding amounts increased for long and short tokens - let retrieved_claimable_long = data_store.get_u128(claimable_fund_long_key); - let retrieved_claimable_short = data_store.get_u128(claimable_fund_short_key); - assert(retrieved_claimable_long == long_token_amount, 'Invalid claimable for long'); - assert(retrieved_claimable_short == short_token_amount, 'Invalid claimable for short'); - - let mut fees2: PositionFees = Default::default(); - fees2.funding.claimable_long_token_amount = 0; - fees2.funding.claimable_short_token_amount = 0; - position_utils::increment_claimable_funding_amount(params, fees2); - - // Check funding amounts doesnt change - let retrieved_claimable_long = data_store.get_u128(claimable_fund_long_key); - let retrieved_claimable_short = data_store.get_u128(claimable_fund_short_key); - assert(retrieved_claimable_long == long_token_amount, 'Invalid claimable for long'); - assert(retrieved_claimable_short == short_token_amount, 'Invalid claimable for short'); - - teardown(data_store.contract_address); -} -// TODO -// Missing libraries -//fn test_is_position_liquiditable() { -// - -// TODO -// Missing libraries -//fn test_will_position_collateral_be_sufficient() { -// - -// TODO -// Missing libraries -//fn test_update_funding_and_borrowing_state() { -// - -// TODO -// Missing libraries -//fn test_update_total_borrowing() { -// - -// TODO -// Missing libraries -//fn test_update_open_interest() { -// - -// TODO -// Missing libraries -//fn test_handle_referral() { -// - - diff --git a/src/tests/reader/test_reader.cairo b/src/tests/reader/test_reader.cairo deleted file mode 100644 index 4eaee1b7..00000000 --- a/src/tests/reader/test_reader.cairo +++ /dev/null @@ -1,469 +0,0 @@ -use starknet::{ContractAddress, contract_address_const}; - -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; -use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use satoru::reader::reader::{IReaderDispatcher, IReaderDispatcherTrait}; - -use satoru::role::role; -use satoru::order::order::{Order, OrderType, OrderTrait, DecreasePositionSwapType}; -use satoru::tests_lib::{setup, teardown}; -use satoru::utils::span32::{Span32, Array32Trait}; -use satoru::market::market::{Market}; -use snforge_std::{PrintTrait, declare, start_prank, stop_prank, ContractClassTrait}; -use poseidon::poseidon_hash_span; -use satoru::deposit::deposit::{Deposit}; -use satoru::withdrawal::withdrawal::{Withdrawal}; -use satoru::position::position::{Position}; -use satoru::data::keys; -use satoru::price::price::{Price, PriceTrait}; - -#[test] -fn given_normal_conditions_when_get_market_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (reader_address, reader) = setup_reader(); - - let key: ContractAddress = 123456789.try_into().unwrap(); - let mut market = Market { - market_token: key, - index_token: 11111.try_into().unwrap(), - long_token: 22222.try_into().unwrap(), - short_token: 33333.try_into().unwrap(), - }; - start_prank(role_store.contract_address, caller_address); - role_store.grant_role(caller_address, role::MARKET_KEEPER); - stop_prank(role_store.contract_address); - - // Test logic - - data_store.set_market(key, 0, market); - - let market_by_key = reader.get_market(data_store, key); - assert(market_by_key == market, 'Invalid market by key'); - - teardown(data_store.contract_address); -} - -#[test] -fn given_normal_conditions_when_get_market_by_salt_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (reader_address, reader) = setup_reader(); - - let key: ContractAddress = 123456789.try_into().unwrap(); - let mut market = Market { - market_token: key, - index_token: 11111.try_into().unwrap(), - long_token: 22222.try_into().unwrap(), - short_token: 33333.try_into().unwrap(), - }; - let key2: ContractAddress = 222222222222.try_into().unwrap(); - - let mut market2 = Market { - market_token: key, - index_token: 12345.try_into().unwrap(), - long_token: 56678.try_into().unwrap(), - short_token: 8901234.try_into().unwrap(), - }; - - start_prank(role_store.contract_address, caller_address); - role_store.grant_role(caller_address, role::MARKET_KEEPER); - stop_prank(role_store.contract_address); - - let salt: felt252 = 'satoru_market'; - let salt2: felt252 = 'satoru_market2'; - - // Test logic - - data_store.set_market(key, salt, market); - data_store.set_market(key2, salt2, market2); - - let market_by_key = reader.get_market_by_salt(data_store, salt); - assert(market_by_key == market, 'Invalid market by key'); - - let market_by_key2 = reader.get_market_by_salt(data_store, salt2); - assert(market_by_key2 == market2, 'Invalid market2 by key'); - - teardown(data_store.contract_address); -} - - -#[test] -fn given_normal_conditions_when_get_deposit_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (reader_address, reader) = setup_reader(); - - let key = 123456789; - // Create random deposit - let mut deposit: Deposit = Default::default(); - deposit.key = 123456789; - deposit.account = 'account'.try_into().unwrap(); - deposit.receiver = 'receiver'.try_into().unwrap(); - deposit.initial_long_token_amount = 1000000; - deposit.initial_short_token_amount = 2222222; - - // Test logic - - data_store.set_deposit(key, deposit); - - let deposit_by_key = reader.get_deposit(data_store, key); - assert(deposit_by_key == deposit, 'Invalid deposit by key'); - - teardown(data_store.contract_address); -} - - -#[test] -fn given_normal_conditions_when_get_withdrawal_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (reader_address, reader) = setup_reader(); - - let key = 123456789; - // Create random withdrawal - let mut withdrawal: Withdrawal = Default::default(); - withdrawal.key = 123456789; - withdrawal.account = 'account'.try_into().unwrap(); - withdrawal.receiver = 'receiver'.try_into().unwrap(); - withdrawal.market_token_amount = 1000000; - withdrawal.min_short_token_amount = 2222222; - - // Test logic - - data_store.set_withdrawal(key, withdrawal); - - let withdrawal_by_key = reader.get_withdrawal(data_store, key); - assert(withdrawal_by_key == withdrawal, 'Invalid withdrawal by key'); - - teardown(data_store.contract_address); -} - - -#[test] -fn given_normal_conditions_when_get_position_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (reader_address, reader) = setup_reader(); - let key = 123456789; - // Create random position - let mut position: Position = Default::default(); - position.key = 123456789; - position.account = 'account'.try_into().unwrap(); - position.market = 'market'.try_into().unwrap(); - position.size_in_usd = 1000000; - position.funding_fee_amount_per_size = 3333333333; - - // Test logic - - data_store.set_position(key, position); - - let position_by_key = reader.get_position(data_store, key); - assert(position_by_key == position, 'Invalid position by key'); - - teardown(data_store.contract_address); -} - - -#[test] -fn given_normal_conditions_when_get_order_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (reader_address, reader) = setup_reader(); - - let key = 123456789; - // Create random order - let mut order: Order = Default::default(); - order.key = 123456789; - order.account = 'account'.try_into().unwrap(); - order.market = 'market'.try_into().unwrap(); - order.trigger_price = 1000000; - order.callback_gas_limit = 3333333333; - - // Test logic - - data_store.set_order(key, order); - - let order_by_key = reader.get_order(data_store, key); - assert(order_by_key == order, 'Invalid order by key'); - - teardown(data_store.contract_address); -} - -//TODO missing libraries market_utils::get_capped_pnl not implemented -//fn given_normal_conditions_when_get_position_pnl_usd_then_works() - -#[test] -fn given_normal_conditions_when_get_account_positions_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (reader_address, reader) = setup_reader(); - - let key_1 = 1111111111; - let account = 'account'.try_into().unwrap(); - // Create random position - let mut position1: Position = Default::default(); - position1.key = key_1; - position1.market = 'market1'.try_into().unwrap(); - position1.size_in_usd = 1000000; - position1.account = account; - - let key_2 = 22222222222; - let mut position2: Position = Default::default(); - position2.key = key_2; - position2.market = 'market2'.try_into().unwrap(); - position2.size_in_usd = 2000000; - position2.account = account; - - let key_3 = 33333333333; - let mut position3: Position = Default::default(); - position3.key = key_3; - position3.market = 'market3'.try_into().unwrap(); - position3.size_in_usd = 3000000; - position3.account = account; - - let key_4 = 44444444444; - let mut position4: Position = Default::default(); - position4.key = key_4; - position4.market = 'market4'.try_into().unwrap(); - position4.size_in_usd = 4000000; - position4.account = account; - - // Test logic - - data_store.set_position(key_1, position1); - data_store.set_position(key_2, position2); - data_store.set_position(key_3, position3); - data_store.set_position(key_4, position4); - - let account_position = reader.get_account_positions(data_store, account, 0, 10); - assert(account_position.len() == 4, 'invalid position len'); - assert(account_position.at(0) == @position1, 'invalid position1'); - assert(account_position.at(1) == @position2, 'invalid position2'); - assert(account_position.at(2) == @position3, 'invalid position3'); - assert(account_position.at(3) == @position4, 'invalid position4'); - - teardown(data_store.contract_address); -} - -//TODO missing libraries reader_utils::get_position_info not implemented -//fn given_normal_conditions_when_get_position_info_then_works() - -//TODO missing libraries reader_utils::get_position_info not implemented -//fn given_normal_conditions_when_get_account_position_info_list_then_works() - -#[test] -fn given_normal_conditions_when_get_account_orders_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (reader_address, reader) = setup_reader(); - - let key_1 = 1111111111; - let account = 'account'.try_into().unwrap(); - // Create random order - let mut order1: Order = Default::default(); - order1.key = key_1; - order1.market = 'market1'.try_into().unwrap(); - order1.size_delta_usd = 1000000; - order1.account = account; - - let key_2 = 22222222222; - let mut order2: Order = Default::default(); - order2.key = key_2; - order2.market = 'market2'.try_into().unwrap(); - order2.size_delta_usd = 2000000; - order2.account = account; - - let key_3 = 33333333333; - let mut order3: Order = Default::default(); - order3.key = key_3; - order3.market = 'market3'.try_into().unwrap(); - order3.size_delta_usd = 3000000; - order3.account = account; - - let key_4 = 44444444444; - let mut order4: Order = Default::default(); - order4.key = key_4; - order4.market = 'market4'.try_into().unwrap(); - order4.size_delta_usd = 4000000; - order4.account = account; - - // Test logic - - data_store.set_order(key_1, order1); - data_store.set_order(key_2, order2); - data_store.set_order(key_3, order3); - data_store.set_order(key_4, order4); - - let account_order = reader.get_account_orders(data_store, account, 0, 10); - assert(account_order.len() == 4, 'invalid order len'); - assert(account_order.at(0) == @order1, 'invalid order1'); - assert(account_order.at(1) == @order2, 'invalid order2'); - assert(account_order.at(2) == @order3, 'invalid order3'); - assert(account_order.at(3) == @order4, 'invalid order4'); - - teardown(data_store.contract_address); -} - - -#[test] -fn given_normal_conditions_when_get_markets_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (reader_address, reader) = setup_reader(); - - let key_1: ContractAddress = 1111111111.try_into().unwrap(); - let key_2: ContractAddress = 22222222222.try_into().unwrap(); - let key_3: ContractAddress = 33333333333.try_into().unwrap(); - let key_4: ContractAddress = 44444444444.try_into().unwrap(); - // Create random market - let mut market1: Market = Default::default(); - market1.market_token = key_1; - market1.index_token = 'index1'.try_into().unwrap(); - - let mut market2: Market = Default::default(); - market2.market_token = key_2; - market2.index_token = 'index2'.try_into().unwrap(); - - let mut market3: Market = Default::default(); - market3.market_token = key_3; - market3.index_token = 'index3'.try_into().unwrap(); - - let mut market4: Market = Default::default(); - market4.market_token = key_4; - market4.index_token = 'index4'.try_into().unwrap(); - - start_prank(role_store.contract_address, caller_address); - role_store.grant_role(caller_address, role::MARKET_KEEPER); - stop_prank(role_store.contract_address); - - // Test logic - - data_store.set_market(key_1, 1, market1); - data_store.set_market(key_2, 2, market2); - data_store.set_market(key_3, 3, market3); - data_store.set_market(key_4, 4, market4); - - let markets = reader.get_markets(data_store, 0, 10); - assert(markets.len() == 4, 'invalid market len'); - assert(markets.at(0) == @market1, 'invalid market1'); - assert(markets.at(1) == @market2, 'invalid market2'); - assert(markets.at(2) == @market3, 'invalid market3'); - assert(markets.at(3) == @market4, 'invalid market4'); - - teardown(data_store.contract_address); -} - -// TODO missing libraries 'market_utils::get_borrowing_factor_per_second', 'reader_utils::get_base_funding_values' not implemented -//fn given_normal_conditions_when_get_market_info_then_works() - -// TODO missing libraries 'market_utils::get_borrowing_factor_per_second', 'reader_utils::get_base_funding_values' not implemented -//fn given_normal_conditions_when_get_market_info_list_then_works() - -// TODO missing libraries 'market_utils::get_market_token_price' not implemented -//fn given_normal_conditions_when_get_market_token_price_then_works() - -// TODO missing libraries 'market_utils::get_net_pnl' not implemented -//fn given_normal_conditions_when_get_net_pnl_then_works() - -#[test] -fn given_normal_conditions_when_get_pnl_then_works() { - // - // Setup - // - let (caller_address, role_store, data_store) = setup(); - let (reader_address, reader) = setup_reader(); - - let market_token_address = contract_address_const::<'market_token'>(); - let market = Market { - market_token: market_token_address, - index_token: contract_address_const::<'index_token'>(), - long_token: contract_address_const::<'long_token'>(), - short_token: contract_address_const::<'short_token'>(), - }; - let is_long = true; - let maximize = true; - let price = Price { min: 10, max: 50 }; - - // Test logic - - // Set open interest for long token. - let open_interest_key_for_long = keys::open_interest_key( - market_token_address, market.long_token, is_long - ); - data_store.set_u128(open_interest_key_for_long, 100); - // Set open interest for short token. - let open_interest_key_for_short = keys::open_interest_key( - market_token_address, market.short_token, is_long - ); - data_store.set_u128(open_interest_key_for_short, 150); - - // Set open interest in tokens for long token. - let open_interest_in_tokens_key_for_long = keys::open_interest_in_tokens_key( - market_token_address, market.long_token, is_long - ); - data_store.set_u128(open_interest_in_tokens_key_for_long, 200); - - // Set open interest in tokens for short token. - let open_interest_in_tokens_key_for_short = keys::open_interest_in_tokens_key( - market_token_address, market.short_token, is_long - ); - data_store.set_u128(open_interest_in_tokens_key_for_short, 250); - - // Actual test case. - let pnl = reader.get_pnl(data_store, market, price, is_long, maximize); - - // Perform assertions. - assert(pnl == 22250, 'wrong pnl'); - - teardown(data_store.contract_address); -} -// TODO missing libraries 'market_utils::get_open_interest_with_pnl' not implemented -//fn given_normal_conditions_when_get_open_interest_with_pnl_then_works() - -// TODO missing libraries 'market_utils::get_pnl_to_pool_factor' not implemented -//fn given_normal_conditions_when_get_pnl_to_pool_factor_then_works() - -// TODO missing libraries reader_pricing_utils::get_swap_amount_out use not implemented functions -//fn given_normal_conditions_when_get_swap_amount_out_then_works() { - -// TODO missing libraries 'market_utils::get_virtual_inventory_for_swaps' and 'market_utils::get_virtual_inventory_for_positions' not implemented -//fn given_normal_conditions_when_get_virtual_inventory_then_works() { - -// TODO missing libraries 'increase_position_utils::get_execution_price' and 'decrease_position_collateral_utils::get_execution_price' not implemented -//fn given_normal_conditions_when_get_execution_price_then_works() { - -// TODO missing libraries 'swap_pricing_utils::get_price_impact_usd' and 'market_utils::get_swap_impact_amount_with_cap' not implemented -//fn given_normal_conditions_when_get_swap_price_impact_then_works() { - -// TODO missing libraries 'market_utils::is_pnl_factor_exceeded_direct' and 'market_utils::get_enabled_market' not implemented -//fn given_normal_conditions_when_get_adl_state_then_works() { - -// ************************************************************************* -// SETUP READER -// ************************************************************************* - -fn setup_reader() -> (ContractAddress, IReaderDispatcher) { - let contract = declare('Reader'); - let reader_address = contract.deploy(@array![]).unwrap(); - let reader = IReaderDispatcher { contract_address: reader_address }; - (reader_address, reader) -} diff --git a/src/tests/role/test_role_store.cairo b/src/tests/role/test_role_store.cairo deleted file mode 100644 index 0fe91617..00000000 --- a/src/tests/role/test_role_store.cairo +++ /dev/null @@ -1,172 +0,0 @@ -use result::ResultTrait; -use traits::TryInto; -use starknet::{ContractAddress, contract_address_const}; -use starknet::Felt252TryIntoContractAddress; -use snforge_std::{declare, start_prank, ContractClassTrait}; -//use array::ArrayTrait; - -use satoru::role::role::ROLE_ADMIN; -use satoru::role::role::CONTROLLER; -use satoru::role::role::MARKET_KEEPER; -use satoru::role::role_store::IRoleStoreDispatcher; -use satoru::role::role_store::IRoleStoreDispatcherTrait; - -#[test] -fn given_normal_conditions_when_grant_role_then_works() { - // ********************************************************************************************* - // * SETUP * - // ********************************************************************************************* - let role_store = setup(); - // ********************************************************************************************* - // * TEST LOGIC * - // ********************************************************************************************* - - // Use the address that has been used to deploy role_store. - let caller_address: ContractAddress = 0x101.try_into().unwrap(); - start_prank(role_store.contract_address, caller_address); - - let account_address: ContractAddress = contract_address_const::<1>(); - - // Check that the account address does not have the admin role. - assert(!role_store.has_role(account_address, ROLE_ADMIN), 'Invalid role'); - // Grant admin role to account address. - role_store.grant_role(account_address, ROLE_ADMIN); - // Check that the account address has the admin role. - assert(role_store.has_role(account_address, ROLE_ADMIN), 'Invalid role'); - - // ********************************************************************************************* - // * TEARDOWN * - // ********************************************************************************************* - teardown(); -} - -#[test] -fn given_normal_conditions_when_revoke_role_then_works() { - // ********************************************************************************************* - // * SETUP * - // ********************************************************************************************* - let role_store = setup(); - - // ********************************************************************************************* - // * TEST LOGIC * - // ********************************************************************************************* - - // Use the address that has been used to deploy role_store. - let caller_address: ContractAddress = 0x101.try_into().unwrap(); - start_prank(role_store.contract_address, caller_address); - - let account_address: ContractAddress = contract_address_const::<1>(); - - // Grant admin role to account address. - role_store.grant_role(account_address, ROLE_ADMIN); - // Check that the account address has the admin role. - assert(role_store.has_role(account_address, ROLE_ADMIN), 'Invalid role'); - // Revoke admin role from account address. - role_store.revoke_role(account_address, ROLE_ADMIN); - // Check that the account address does not have the admin role. - assert(!role_store.has_role(account_address, ROLE_ADMIN), 'Invalid role'); - - // ********************************************************************************************* - // * TEARDOWN * - // ********************************************************************************************* - teardown(); -} - -#[test] -fn test_get_role_count() { - // ********************************************************************************************* - // * SETUP * - // ********************************************************************************************* - let role_store = setup(); - - // ********************************************************************************************* - // * TEST LOGIC * - // ********************************************************************************************* - - // Use the address that has been used to deploy role_store. - let caller_address: ContractAddress = 0x101.try_into().unwrap(); - start_prank(role_store.contract_address, caller_address); - - let account_address: ContractAddress = contract_address_const::<1>(); - - // Here, we will test the role count. Initially, it should be 1. - assert(role_store.get_role_count() == 1, 'Initial role count should be 1'); - - // Grant CONTROLLER role to account address. - role_store.grant_role(account_address, CONTROLLER); - // After granting the role CONTROLLER, the count should be 2. - assert(role_store.get_role_count() == 2, 'Role count should be 2'); - // Grant MARKET_KEEPER role to account address. - role_store.grant_role(account_address, MARKET_KEEPER); - // After granting the role MARKET_KEEPER, the count should be 3. - assert(role_store.get_role_count() == 3, 'Role count should be 3'); - - // The ROLE_ADMIN role is already assigned, let's try to reassign it to see if duplicates are managed. - // Grant ROLE_ADMIN role to account address. - role_store.grant_role(account_address, ROLE_ADMIN); - // Duplicates, the count should be 3. - assert(role_store.get_role_count() == 3, 'Role count should be 3'); - - // ********************************************************************************************* - // * TEARDOWN * - // ********************************************************************************************* - teardown(); -} - -//#[test] -//fn test_get_roles() { -// ********************************************************************************************* -// * SETUP * -// ********************************************************************************************* -//let role_store = setup(); - -// ********************************************************************************************* -// * TEST LOGIC * -// ********************************************************************************************* - -// Use the address that has been used to deploy role_store. -//let caller_address: ContractAddress = 0x101.try_into().unwrap(); -//start_prank(role_store.contract_address, caller_address); - -//let account_address: ContractAddress = contract_address_const::<1>(); - -// Grant CONTROLLER role to account address. -//role_store.grant_role(account_address, CONTROLLER); - -// Grant MARKET_KEEPER role to account address. -//role_store.grant_role(account_address, MARKET_KEEPER); - -// Get roles from index 0 to 2 (should return ROLE_ADMIN and CONTROLLER). -//let roles_0_to_2 = role_store.get_roles(0, 2); -//let first_role = roles_0_to_2.at(0); -//let second_role = roles_0_to_2.at(1); -//assert(*first_role == ROLE_ADMIN, '1 should be ROLE_ADMIN'); -//assert(*second_role == CONTROLLER, '2 should be CONTROLLER'); - -// Get roles from index 1 to 3 (should return CONTROLLER and MARKET_KEEPER). -//let roles_1_to_3 = role_store.get_roles(1, 3); -//let first_role = roles_1_to_3.at(0); -//let second_role = roles_1_to_3.at(1); -//assert(*first_role == CONTROLLER, '3 should be CONTROLLER'); -//assert(*second_role == MARKET_KEEPER, '4 should be MARKET_KEEPER'); - -// ********************************************************************************************* -// * TEARDOWN * -// ********************************************************************************************* -//teardown(); -//} - -/// Utility function to setup the test environment. -fn setup() -> IRoleStoreDispatcher { - IRoleStoreDispatcher { contract_address: deploy_role_store() } -} - -/// Utility function to teardown the test environment. -fn teardown() {} - -// Utility function to deploy a role store contract and return its address. -fn deploy_role_store() -> ContractAddress { - let contract = declare('RoleStore'); - contract.deploy(@ArrayTrait::new()).unwrap() -} - diff --git a/src/tests/swap/test_swap_handler.cairo b/src/tests/swap/test_swap_handler.cairo deleted file mode 100644 index 9b31b19b..00000000 --- a/src/tests/swap/test_swap_handler.cairo +++ /dev/null @@ -1,199 +0,0 @@ -use satoru::tests_lib::{teardown}; -use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; -use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; -use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; -use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use snforge_std::{declare, ContractClassTrait, start_prank}; -use satoru::swap::swap_utils::SwapParams; -use core::traits::Into; -use satoru::role::role; -use satoru::market::market::Market; -use starknet::{get_caller_address, ContractAddress, contract_address_const,}; -use array::ArrayTrait; - -//TODO Tests need to be added after implementation of swap_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() -} - -/// Utility function to deploy a `EventEmitter` contract and return its dispatcher. -fn deploy_event_emitter() -> ContractAddress { - let contract = declare('EventEmitter'); - contract.deploy(@array![]).unwrap() -} - -/// Utility function to deploy a `Oracle` contract and return its dispatcher. -fn deploy_oracle( - role_store_address: ContractAddress, - oracle_address: ContractAddress, - pragma_address: ContractAddress -) -> ContractAddress { - let contract = declare('Oracle'); - let mut constructor_calldata = array![]; - constructor_calldata.append(role_store_address.into()); - constructor_calldata.append(oracle_address.into()); - constructor_calldata.append(pragma_address.into()); - contract.deploy(@constructor_calldata).unwrap() -} - -/// Utility function to deploy a `Bank` contract and return its dispatcher. -fn deploy_bank_address( - 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 `SwapHandler` contract and return its dispatcher. -fn deploy_swap_handler_address(role_store_address: ContractAddress) -> ContractAddress { - let contract = declare('SwapHandler'); - let constructor_calldata = array![role_store_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. -/// * `IEventEmitterDispatcher` - The event emitter dispatcher. -/// * `IOracleDispatcher` - The oracle dispatcher dispatcher. -/// * `IBankDispatcher` - The bank dispatcher. -/// * `IRoleStoreDispatcher` - The role store dispatcher. -/// * `ISwapHandlerDispatcher` - The swap handler dispatcher. -fn setup() -> ( - ContractAddress, - IDataStoreDispatcher, - IEventEmitterDispatcher, - IOracleDispatcher, - IBankDispatcher, - IRoleStoreDispatcher, - ISwapHandlerDispatcher -) { - 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 data_store_address = deploy_data_store(role_store_address); - let data_store = IDataStoreDispatcher { contract_address: data_store_address }; - - let event_emitter_address = deploy_event_emitter(); - let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; - - let oracle_address = deploy_oracle( - role_store_address, - contract_address_const::<'oracle'>(), - contract_address_const::<'pragma'>() - ); - let oracle = IOracleDispatcher { contract_address: oracle_address }; - - let bank_address = deploy_bank_address(data_store_address, role_store_address); - let bank = IBankDispatcher { contract_address: bank_address }; - - let swap_handler_address = deploy_swap_handler_address(role_store_address); - let swap_handler = ISwapHandlerDispatcher { contract_address: swap_handler_address }; - - start_prank(role_store_address, caller_address); - start_prank(data_store_address, caller_address); - start_prank(event_emitter_address, caller_address); - start_prank(oracle_address, caller_address); - start_prank(bank_address, caller_address); - start_prank(swap_handler_address, caller_address); - - // Grant the caller the `CONTROLLER` role. - role_store.grant_role(caller_address, role::CONTROLLER); - - (caller_address, data_store, event_emitter, oracle, bank, role_store, swap_handler) -} - - -#[test] -#[should_panic(expected: ('unauthorized_access',))] -fn given_caller_not_controller_when_swap_then_fails() { - let (caller_address, data_store, event_emitter, oracle, bank, role_store, swap_handler) = - setup(); - - // Revoke the caller the `CONTROLLER` role. - role_store.revoke_role(caller_address, role::CONTROLLER); - - let mut market = Market { - market_token: contract_address_const::<'market_token'>(), - index_token: contract_address_const::<'index_token'>(), - long_token: contract_address_const::<'long_token'>(), - short_token: contract_address_const::<'short_token'>(), - }; - - let mut swap = SwapParams { - data_store: data_store, - event_emitter: event_emitter, - oracle: oracle, - bank: bank, - key: 1, - token_in: contract_address_const::<'token_in'>(), - amount_in: 1, - swap_path_markets: ArrayTrait::new().span(), - min_output_amount: 1, - receiver: contract_address_const::<'receiver'>(), - ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), - }; - - swap_handler.swap(swap); - teardown(data_store.contract_address); -} - - -#[test] -fn given_normal_conditions_when_swap_then_works() { - //Change that when swap_handler has been implemented - let (caller_address, data_store, event_emitter, oracle, bank, role_store, swap_handler) = - setup(); - - let mut market = Market { - market_token: contract_address_const::<'market_token'>(), - index_token: contract_address_const::<'index_token'>(), - long_token: contract_address_const::<'long_token'>(), - short_token: contract_address_const::<'short_token'>(), - }; - - let mut swap = SwapParams { - data_store: data_store, - event_emitter: event_emitter, - oracle: oracle, - bank: bank, - key: 1, - token_in: contract_address_const::<'token_in'>(), - amount_in: 0, - swap_path_markets: ArrayTrait::new().span(), - min_output_amount: 1, - receiver: contract_address_const::<'receiver'>(), - ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), - }; - - let swap_result = swap_handler.swap(swap); - - assert(swap_result == (contract_address_const::<'token_in'>(), 0), 'Error'); - - teardown(role_store.contract_address); -} -//TODO add more tested when swap_handler has been implemented - - diff --git a/src/tests/utils/test_calc.cairo b/src/tests/utils/test_calc.cairo deleted file mode 100644 index c8fc8ee3..00000000 --- a/src/tests/utils/test_calc.cairo +++ /dev/null @@ -1,231 +0,0 @@ -use integer::BoundedInt; - -use satoru::role::role; -use satoru::utils::calc::{ - roundup_division, roundup_magnitude_division, sum_return_uint_128, sum_return_int_128, diff, - bounded_add, bounded_sub, to_signed, max_i128, min_i128 -}; - -fn max_i128_as_u128() -> u128 { - 170_141_183_460_469_231_731_687_303_715_884_105_727 -} - -#[test] -#[should_panic(expected: ('i128_add Overflow',))] -fn given_overflow_when_max_i128_then_fails() { - max_i128() + 1; -} - -#[test] -#[should_panic(expected: ('i128_sub Underflow',))] -fn given_underflow_when_max_i128_then_fails() { - min_i128() - 1; -} - -#[test] -fn given_normal_conditions_when_roundup_division_then_works() { - assert(roundup_division(12, 3) == 4, '12/3 should be 4'); - assert(roundup_division(13, 3) == 5, '13/3 should be 4'); - assert(roundup_division(13, 5) == 3, '13/5 should be 3'); - assert(roundup_division(9, 9) == 1, '9/9 should be 1'); - assert(roundup_division(9, 18) == 1, '9/18 should be 1'); - assert(roundup_division(9, 99) == 1, '9/99 should be 1'); - assert(roundup_division(max_i128_as_u128(), max_i128_as_u128()) == 1, 'max/max should be 1'); - assert(roundup_division(0, 99) == 0, '0/99 should be 0'); -} - -#[test] -#[should_panic(expected: ('u128 is 0',))] -fn given_division_by_0_when_roundup_division_then_fails() { - roundup_division(4, 0); -} - -#[test] -fn given_normal_conditions_when_roundup_magnitude_division_then_works() { - assert(roundup_magnitude_division(12, 3) == 4, '12/3 should be 4'); - assert(roundup_magnitude_division(-12, 3) == -4, '-12/3 should be -4'); - assert(roundup_magnitude_division(13, 3) == 5, '13/3 should be 4'); - assert(roundup_magnitude_division(-13, 3) == -4, '-13/3 should be -4'); - assert(roundup_magnitude_division(13, 5) == 3, '13/5 should be 3'); - assert(roundup_magnitude_division(-13, 5) == -2, '-13/5 should be -2'); - assert(roundup_magnitude_division(9, 9) == 1, '9/9 should be 1'); - assert(roundup_magnitude_division(-9, 9) == -1, '-9/9 should be -1'); - assert(roundup_magnitude_division(9, 18) == 1, '9/18 should be 1'); - assert(roundup_magnitude_division(-9, 18) == 0, '-9/18 should be 0'); - assert(roundup_magnitude_division(9, 99) == 1, '9/99 should be 1'); - assert(roundup_magnitude_division(-9, 99) == 0, '-9/99 should be 0'); - assert(roundup_magnitude_division(max_i128(), max_i128_as_u128()) == 1, 'max/max should be 1'); - assert( - roundup_magnitude_division(min_i128() + 1, max_i128_as_u128()) == -1, 'min/max should be -1' - ); - assert(roundup_magnitude_division(0, 12) == 0, '0/12 should be 0'); -} - -#[test] -#[should_panic(expected: ('i128_sub Overflow',))] -fn given_overflow_when_roundup_magnitude_division_then_works() { - // Because here min is 1 bigger than max, there is an overflow - roundup_magnitude_division(min_i128(), 1); -} - -#[test] -#[should_panic(expected: ('u128 is 0',))] -fn given_division_by_0_when_roundup_magnitude_division_then_fails() { - roundup_magnitude_division(4, 0); -} - -#[test] -fn given_normal_conditions_when_sum_return_uint_128_then_works() { - assert(sum_return_uint_128(12, 3) == 15, 'Should be 15'); - assert(sum_return_uint_128(12, -3) == 9, 'Should be 9'); - assert(sum_return_uint_128(0, 3) == 3, 'Should be 3'); - assert(sum_return_uint_128(12, 0) == 12, 'Should be 12'); - assert(sum_return_uint_128(BoundedInt::max(), 0) == BoundedInt::max(), 'Should be max'); - - assert( - sum_return_uint_128(BoundedInt::max(), -1) == BoundedInt::max() - 1, 'Should be max - 1' - ); - assert( - sum_return_uint_128(BoundedInt::max(), min_i128() + 1) == max_i128_as_u128() + 1, - 'Should be max/2 +1 (1)' - ); - - assert(sum_return_uint_128(0, max_i128()) == max_i128_as_u128(), 'Should be max/2 (2)'); -} - -#[test] -#[should_panic(expected: ('i128_sub Overflow',))] -fn given_i128_sub_overflow_when_sum_return_uint_128_then_fails() { - // Because here min is 1 bigger than max, there is an overflow - sum_return_uint_128(BoundedInt::max(), min_i128()); -} - -#[test] -#[should_panic(expected: ('u128_add Overflow',))] -fn given_add_overflow_when_sum_return_uint_128_then_fails() { - sum_return_uint_128(BoundedInt::max(), 1); -} - -#[test] -#[should_panic(expected: ('u128_sub Overflow',))] -fn given_u128_sub_overflow_when_sum_return_uint_128_then_fails() { - sum_return_uint_128(0, -1); -} - -#[test] -fn given_normal_conditions_when_sum_return_int_128_then_works() { - assert(sum_return_int_128(12, 3) == 15, 'Should be 15'); - assert(sum_return_int_128(12, -3) == 9, 'Should be 9'); - assert(sum_return_int_128(0, 3) == 3, 'Should be 3'); - assert(sum_return_int_128(0, -3) == -3, 'Should be -3'); - - assert( - sum_return_int_128(max_i128_as_u128() - 3, 2) == max_i128() - 1, 'Should be max_i128 -1 (1)' - ); - - assert(sum_return_int_128(max_i128_as_u128() - 1, 1) == max_i128(), 'Should be max_i128'); - assert( - sum_return_int_128(max_i128_as_u128(), -1) == max_i128() - 1, 'Should be max_i128 - 1 (2)' - ); -} - -#[test] -#[should_panic(expected: ('i128 Overflow',))] -fn given_i128_overflow_when_sum_return_int_128_then_fails() { - sum_return_int_128(max_i128_as_u128() + 1, 3); -} - -#[test] -#[should_panic(expected: ('i128_add Overflow',))] -fn given_i128_add_overflow_when_sum_return_int_128_then_fails() { - sum_return_int_128(max_i128_as_u128() - 1, 2); -} - -#[test] -fn given_normal_conditions_when_diff_then_works() { - assert(diff(12, 3) == 9, 'Should be 9'); - assert(diff(3, 11) == 8, 'Should be 8'); - assert(diff(0, 5) == 5, 'Should be 5'); - assert(diff(6, 0) == 6, 'Should be 6'); - assert(diff(3, 3) == 0, 'Should be 0 (1)'); - - let max = BoundedInt::max(); - assert(diff(max, max) == 0, 'Should be 0 (2)'); - assert(diff(max - 1, max) == 1, 'Should be 1 (1))'); - assert(diff(max, max - 1) == 1, 'Should be 1 (2)'); -} - -#[test] -fn given_normal_conditions_when_bounded_add_then_works() { - // This tests the first if - assert(bounded_add(0, 3) == 3, 'Should be 3'); - assert(bounded_add(4, 0) == 4, 'Should be 4'); - assert(bounded_add(42, 41) == 83, 'Shoud be 83'); - assert(bounded_add(42, 42) == 84, 'Should be 84'); - assert(bounded_add(-10, -12) == -22, 'Should be -22'); - assert(bounded_add(-10, -10) == -20, 'Should be -20'); - - let max = max_i128(); - let min = min_i128(); - // This tests the second if - assert(bounded_add(min, -1) == min, 'Should be min (1)'); - assert(bounded_add(min + 1, -1) == min, 'Should be min (2)'); - // This tests the third if - assert(bounded_add(max, 1) == max, 'Should be max (1)'); - assert(bounded_add(max - 1, 1) == max, 'Should be max (2)'); - - // Mixing signing - assert(bounded_add(-10, 10) == 0, 'Should be 0 (1)'); - assert(bounded_add(10, -10) == 0, 'Should be 0 (2)'); - assert(bounded_add(-10, -10) == -20, 'Shoud be -20'); -} - -#[test] -fn given_normal_conditions_when_bounded_sub_then_works() { - // This tests the first if - assert(bounded_sub(0, 3) == -3, 'Should be -3'); - assert(bounded_sub(3, 0) == 3, 'Should be 3'); - assert(bounded_sub(42, 41) == 1, 'Shoud be 1'); - assert(bounded_sub(41, 42) == -1, 'Should be -1'); - assert(bounded_sub(-10, -12) == 2, 'Should be 2'); - assert(bounded_sub(-12, -10) == -2, 'Should be -2'); - - let max = max_i128(); - let min = min_i128(); - // This tests the second if - assert(bounded_sub(max, -1) == max, 'Should be max (1)'); - assert(bounded_sub(max - 1, -1) == max, 'Should be max (2)'); - // This tests the third if - assert(bounded_sub(min, 1) == min, 'Should be min (1)'); - assert(bounded_sub(min + 1, 1) == min, 'Should be min (2)'); - - // Zero test case - assert(bounded_sub(10, 10) == 0, 'Shoud be 0'); - // Mixing signing - assert(bounded_sub(-10, 10) == -20, 'Should be -20'); - assert(bounded_sub(10, -10) == 20, 'Should be 20'); -} - -#[test] -fn given_normal_conditions_when_to_signed_then_works() { - assert(to_signed(12, true) == 12, 'Should be 12'); - assert(to_signed(12, false) == -12, 'Should be -12'); - - let max = max_i128(); - let min = min_i128(); - assert(to_signed(max_i128_as_u128(), true) == max, 'Should be max'); - assert(to_signed(max_i128_as_u128(), false) == min + 1, 'Should be min)'); -} - -#[test] -#[should_panic(expected: ('i128 Overflow',))] -fn given_i128_overflow_when_to_signed_then_fails() { - to_signed(BoundedInt::max(), true); -} - - -#[test] -#[should_panic(expected: ('i128 Overflow',))] -fn given_i128_overflow_neg_when_to_signed_then_fails() { - to_signed(BoundedInt::max(), false); -} diff --git a/src/tests/utils/test_i128.cairo b/src/tests/utils/test_i128.cairo deleted file mode 100644 index aeda8a5e..00000000 --- a/src/tests/utils/test_i128.cairo +++ /dev/null @@ -1,114 +0,0 @@ -use satoru::utils::i128::{I128Div, I128Mul, I128Serde}; -use snforge_std::{declare, ContractClassTrait}; - - -// Div -#[test] -fn test_i128_division() { - assert(12_i128 / 3 == 4, 'should be 4'); -} - -#[test] -fn test_i128_division_lhs_neg() { - assert(-12_i128 / 3 == -4, 'should be -4'); -} - -#[test] -fn test_i128_division_rhs_neg() { - assert(12_i128 / -3 == -4, 'should be -4'); -} - -#[test] -fn test_i128_division_both_neg() { - assert(-12_i128 / -3 == 4, 'should be 4'); -} - -#[test] -fn test_i128_division_zero() { - assert(0_i128 / -3 == 0, 'should be 0'); -} - -#[test] -#[should_panic(expected: ('Division by 0',))] -fn test_i128_division_by_zero() { - -12_i128 / 0; -} - -// Mul -#[test] -fn test_i128_multiplication() { - assert(12_i128 * 3 == 36, 'should be 36'); -} - -#[test] -fn test_i128_multiplication_lhs_neg() { - assert(-12_i128 * 3 == -36, 'should be -36'); -} - -#[test] -fn test_i128_multiplication_rhs_neg() { - assert(12_i128 * -3 == -36, 'should be -36'); -} - -#[test] -fn test_i128_multiplication_both_neg() { - assert(-12_i128 * -3 == 36, 'should be 36'); -} - -#[test] -fn test_i128_multiplication_zero() { - assert(0_i128 * -3 == 0, 'should be 0'); -} - -#[test] -fn test_i128_multiplication_by_zero() { - assert(-3_i128 * 0 == 0, 'should be 0'); -} - -#[starknet::interface] -trait ITestI128Storage { - fn set_i128(ref self: TContractState, new_val: i128); - fn get_i128(self: @TContractState) -> i128; -} - -#[starknet::contract] -mod test_i128_storage_contract { - use satoru::utils::i128::{StoreI128, I128Serde}; - use super::ITestI128Storage; - - - #[storage] - struct Storage { - my_i128: i128 - } - - #[external(v0)] - impl Public of ITestI128Storage { - fn set_i128(ref self: ContractState, new_val: i128) { - self.my_i128.write(new_val); - } - fn get_i128(self: @ContractState) -> i128 { - self.my_i128.read() - } - } -} - -use starknet::{deploy_syscall, ClassHash}; - -fn deploy() -> ITestI128StorageDispatcher { - let contract = declare('test_i128_storage_contract'); - let contract_address = contract.deploy(@array![]).unwrap(); - ITestI128StorageDispatcher { contract_address } -} - -#[test] -fn test_i128_storage() { - let dispatcher = deploy(); - assert(dispatcher.get_i128() == 0, 'should be 0'); - dispatcher.set_i128(12); - assert(dispatcher.get_i128() == 12, 'should be 12'); - dispatcher.set_i128(-42); - assert(dispatcher.get_i128() == -42, 'should be -42'); - dispatcher.set_i128(0); - assert(dispatcher.get_i128() == 0, 'should be back to 0'); -} diff --git a/src/tests/utils/test_precision.cairo b/src/tests/utils/test_precision.cairo deleted file mode 100644 index 5aba0379..00000000 --- a/src/tests/utils/test_precision.cairo +++ /dev/null @@ -1,138 +0,0 @@ -// IMPORTS -use satoru::utils::precision; -use satoru::utils::precision::{ - FLOAT_PRECISION, FLOAT_PRECISION_SQRT, WEI_PRECISION, BASIS_POINTS_DIVISOR, FLOAT_TO_WEI_DIVISOR -}; - -#[test] -fn test_apply_factor_u128() { - let value: u128 = 10; - let factor: u128 = 1_000_000_000_000_000_000_000_000_000_000_000; - let result = precision::apply_factor_u128(value, factor); - assert(result == 10000, 'should be 10000.'); -} - -#[test] -fn test_apply_factor_i128() { - let value: u128 = 10; - let factor: i128 = -1_000_000_000_000_000_000_000_000_000_000_000; - let result = precision::apply_factor_i128(value, factor); - assert(result == -10000, 'should be -1OOOO.'); -} - -#[test] -fn test_apply_factor_roundup_magnitude_positive() { - let value: u128 = 15; - let factor: i128 = 300_000_000_000_000_000_000_000_000_000; - let roundup_magnitude = true; - let result = precision::apply_factor_roundup_magnitude(value, factor, roundup_magnitude); - assert(result == 5, 'should be 5.'); -} - -#[test] -fn test_apply_factor_roundup_magnitude_negative() { - let value: u128 = 15; - let factor: i128 = -300_000_000_000_000_000_000_000_000_000; - let roundup_magnitude = true; - let result = precision::apply_factor_roundup_magnitude(value, factor, roundup_magnitude); - assert(result == -5, 'should be -5.'); -} - -#[test] -fn test_apply_factor_roundup_magnitude_no_rounding() { - let value: u128 = 15; - let factor: i128 = -300_000_000_000_000_000_000_000_000_000; - let roundup_magnitude = false; - let result = precision::apply_factor_roundup_magnitude(value, factor, roundup_magnitude); - assert(result == -4, 'should be -4.'); -} - -#[test] -fn test_mul_div_ival_negative() { - let value: i128 = -42; - let factor: u128 = 10; - let denominator = 8; - let result = precision::mul_div_ival(value, factor, denominator); - assert(result == -52, 'should be -52.'); -} - -#[test] -fn test_mul_div_inum_positive() { - let value: u128 = 42; - let factor: i128 = 10; - let denominator = 8; - let result = precision::mul_div_inum(value, factor, denominator); - assert(result == 52, 'should be 52.'); -} - -#[test] -fn test_mul_div_inum_roundup_negative() { - let value: u128 = 42; - let factor: i128 = -10; - let denominator = 8; - let result = precision::mul_div_inum_roundup(value, factor, denominator, true); - assert(result == -53, 'should be -53.'); -} - -#[test] -fn test_mul_div_inum_roundup_positive() { - let value: u128 = 42; - let factor: i128 = 10; - let denominator = 8; - let result = precision::mul_div_inum_roundup(value, factor, denominator, true); - assert(result == 53, 'should be 53.'); -} - -#[test] -fn test_to_factor_roundup() { - let value: u128 = 450000; - let divisor: u128 = 200_000_000_000_000_000_000_000_000_000_000_000; //2*10^35 - let roundup_magnitude = true; - let result = precision::to_factor_roundup(value, divisor, roundup_magnitude); - assert(result == 3, 'should be 3.'); -} - -#[test] -fn test_to_factor() { - let value: u128 = 450000; - let divisor: u128 = 200_000_000_000_000_000_000_000_000_000_000_000; // 2*10^35 - let result = precision::to_factor(value, divisor); - assert(result == 2, 'should be 2.'); -} - -#[test] -fn test_to_factor_ival_positive() { - let value: i128 = 450000; - let divisor: u128 = 200_000_000_000_000_000_000_000_000_000_000_000; // 2*10^35 - let result = precision::to_factor_ival(value, divisor); - assert(result == 2, 'from positive integer value.'); -} - -#[test] -fn test_to_factor_ival_negative() { - let value: i128 = -450000; - let divisor: u128 = 200_000_000_000_000_000_000_000_000_000_000_000; // 2*10^35 - let result = precision::to_factor_ival(value, divisor); - assert(result == -2, 'should be -2.'); -} - -#[test] -fn test_float_to_wei() { - let float_value: u128 = 1_000_000_000_000_000; - let result = precision::float_to_wei(float_value); - assert(result == 1000, 'should be 10^3'); -} - -#[test] -fn test_wei_to_float() { - let wei_value: u128 = 10_000_000_000_000_000_000_000_000; //10^25 - let result = precision::wei_to_float(wei_value); - assert(result == 10_000_000_000_000_000_000_000_000_000_000_000_000, 'should be 10^37'); -} - -#[test] -fn test_basis_points_to_float() { - let basis_point: u128 = 1000; - let result = precision::basis_points_to_float(basis_point); - assert(result == 100_000_000_000_000_000_000_000_000_000, 'should be 10^29'); -} diff --git a/src/tests_lib.cairo b/src/tests_lib.cairo deleted file mode 100644 index 1032831d..00000000 --- a/src/tests_lib.cairo +++ /dev/null @@ -1,137 +0,0 @@ -use starknet::{ContractAddress, Felt252TryIntoContractAddress, contract_address_const}; -use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; - -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; -use satoru::role::role; - -/// Utility function to deploy a data store contract and return its address. -/// -/// # Arguments -/// -/// * `role_store_address` - The address of the role store contract. -/// -/// # Returns -/// -/// * `ContractAddress` - The address of the deployed data store contract. -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() -} - -/// Utility function to deploy a role store contract and return its address. -/// -/// # Returns -/// -/// * `ContractAddress` - The address of the deployed role store contract. -fn deploy_role_store() -> ContractAddress { - let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() -} - -/// Utility function to deploy a `EventEmitter` contract and return its dispatcher. -fn deploy_event_emitter() -> ContractAddress { - let contract = declare('EventEmitter'); - contract.deploy(@array![]).unwrap() -} - -/// Utility function to deploy a `EventEmitter` contract and return its dispatcher. -fn deploy_oracle_store( - role_store_address: ContractAddress, event_emitter_address: ContractAddress, -) -> ContractAddress { - let contract = declare('OracleStore'); - let constructor_calldata = array![role_store_address.into(), event_emitter_address.into()]; - contract.deploy(@constructor_calldata).unwrap() -} - -/// Utility function to deploy a `EventEmitter` contract and return its dispatcher. -fn deploy_oracle( - role_store_address: ContractAddress, - oracle_store_address: ContractAddress, - pragma_address: ContractAddress -) -> ContractAddress { - let contract = declare('Oracle'); - let constructor_calldata = array![ - role_store_address.into(), oracle_store_address.into(), pragma_address.into() - ]; - contract.deploy(@constructor_calldata).unwrap() -} - - -/// Utility function to setup the test environment. -/// -/// # Returns -/// -/// * `ContractAddress` - The address of the caller. -/// * `IRoleStoreDispatcher` - The role store dispatcher. -/// * `IDataStoreDispatcher` - The data store dispatcher. -fn setup() -> (ContractAddress, IRoleStoreDispatcher, IDataStoreDispatcher) { - 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 data_store_address = deploy_data_store(role_store_address); - let data_store = IDataStoreDispatcher { contract_address: data_store_address }; - start_prank(role_store_address, caller_address); - role_store.grant_role(caller_address, role::CONTROLLER); - start_prank(data_store_address, caller_address); - (caller_address, role_store, data_store) -} - -/// Utility function to setup the test environment. -/// -/// # Returns -/// -/// * `ContractAddress` - The address of the event emitter contract. -/// * `IEventEmitterDispatcher` - The event emitter store dispatcher. -fn setup_event_emitter() -> (ContractAddress, IEventEmitterDispatcher) { - let event_emitter_address = deploy_event_emitter(); - let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; - (event_emitter_address, event_emitter) -} - - -/// 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 store dispatcher. -/// * `IOracleDispatcher` - The oracle dispatcher. -fn setup_oracle_and_store() -> ( - ContractAddress, - IRoleStoreDispatcher, - IDataStoreDispatcher, - IEventEmitterDispatcher, - IOracleDispatcher -) { - 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 data_store_address = deploy_data_store(role_store_address); - let data_store = IDataStoreDispatcher { contract_address: data_store_address }; - start_prank(role_store_address, caller_address); - role_store.grant_role(caller_address, role::CONTROLLER); - start_prank(data_store_address, caller_address); - let (event_emitter_address, event_emitter) = setup_event_emitter(); - let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); - let oracle_address = deploy_oracle( - role_store_address, oracle_store_address, contract_address_const::<'pragma'>() - ); - let oracle = IOracleDispatcher { contract_address: oracle_address }; - (caller_address, role_store, data_store, event_emitter, oracle) -} - -/// Utility function to teardown the test environment. -/// -/// # Arguments -/// -/// * `data_store_address` - The address of the data store contract. -fn teardown(data_store_address: ContractAddress) { - stop_prank(data_store_address); -} - diff --git a/src/token/erc20/erc20.cairo b/src/token/erc20/erc20.cairo index 9aa2a667..af2071f6 100644 --- a/src/token/erc20/erc20.cairo +++ b/src/token/erc20/erc20.cairo @@ -4,6 +4,7 @@ mod ERC20 { use starknet::ContractAddress; use starknet::get_caller_address; use zeroable::Zeroable; + use debug::PrintTrait; use satoru::token::erc20::interface::IERC20; @@ -53,7 +54,7 @@ mod ERC20 { // External // - #[external(v0)] + #[abi(embed_v0)] impl ERC20Impl of IERC20 { fn name(self: @ContractState) -> felt252 { self._name.read() @@ -94,7 +95,7 @@ mod ERC20 { amount: u256 ) -> bool { let caller = get_caller_address(); - self._spend_allowance(sender, caller, amount); + // self._spend_allowance(sender, caller, amount); self._transfer(sender, recipient, amount); true } @@ -104,6 +105,10 @@ mod ERC20 { self._approve(caller, spender, amount); true } + + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + self._mint(recipient, amount); + } } #[external(v0)] diff --git a/src/token/erc20/interface.cairo b/src/token/erc20/interface.cairo index ec5d9d85..3b6cdc05 100644 --- a/src/token/erc20/interface.cairo +++ b/src/token/erc20/interface.cairo @@ -13,4 +13,5 @@ trait IERC20 { ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 ) -> bool; fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + fn mint(ref self: TState, recipient: ContractAddress, amount: u256); } diff --git a/src/token/token_utils.cairo b/src/token/token_utils.cairo index 66407a40..da07c747 100644 --- a/src/token/token_utils.cairo +++ b/src/token/token_utils.cairo @@ -6,17 +6,14 @@ use satoru::data::keys; use satoru::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; use satoru::utils::account_utils::validate_receiver; use satoru::bank::error::BankError; -use integer::u256_from_felt252; - fn fee_token(data_store: IDataStoreDispatcher) -> ContractAddress { - // TODO - ContractAddressZeroable::zero() + data_store.get_address(keys::fee_token()) } - +use debug::PrintTrait; // Transfers the specified amount of `token` from the caller to `receiver`. // # Arguments -// dataStore - The data store that contains the `tokenTransferGasLimit` for the specified `token`. +// data_store - The data store that contains the `tokenTransferGasLimit` for the specified `token`. // token - The address of the ERC20 token that is being transferred. // receiver - The address of the recipient of the `token` transfer. // amount - The amount of `token` to transfer. @@ -24,7 +21,7 @@ fn transfer( data_store: IDataStoreDispatcher, token: ContractAddress, receiver: ContractAddress, - amount: u128 + amount: u256 ) { if (amount.is_zero()) { return (); @@ -34,7 +31,7 @@ fn transfer( // TODO: implement gas limit // transfer tokens to receiver and return if it suceeeds - let amount_u256 = u256_from_felt252(amount.into()); + let amount_u256 = amount.into(); let success0 = IERC20Dispatcher { contract_address: token } .transfer(recipient: receiver, amount: amount_u256); if (success0 == true) { @@ -44,7 +41,7 @@ fn transfer( // in case transfers to the receiver fail due to blacklisting or other reasons send the tokens to a holding address to avoid possible gaming through reverting transfers let holding_address = data_store.get_address(keys::holding_address()); assert(!holding_address.is_zero(), 'empty_holding_address'); - let amount_u256 = u256_from_felt252(amount.into()); + let amount_u256 = amount.into(); let success1 = IERC20Dispatcher { contract_address: token } .transfer(recipient: holding_address, amount: amount_u256); diff --git a/src/utils/arrays.cairo b/src/utils/arrays.cairo index 6c7da178..796179cf 100644 --- a/src/utils/arrays.cairo +++ b/src/utils/arrays.cairo @@ -2,6 +2,7 @@ // IMPORTS // ************************************************************************* // Core lib imports. +use satoru::utils::{error_utils, calc}; /// Gets the value of the element at the specified index in the given array. If the index is out of bounds, returns 0. /// # Arguments @@ -16,7 +17,7 @@ fn get_felt252(arr: Span, index: usize) -> felt252 { } } -fn get_u128(arr: @Array, index: usize) -> u128 { +fn get_u256(arr: @Array, index: usize) -> u256 { match arr.get(index) { Option::Some(value) => *value.unbox(), Option::None => 0, @@ -29,17 +30,13 @@ fn get_u128(arr: @Array, index: usize) -> u128 { /// * `value` - The value to compare the elements to. /// # Returns /// Wether all of the elements in the array are equal to the given value. -fn are_eq(mut arr: Span, value: u128) -> bool { +fn are_eq(mut arr: Span, value: u256) -> bool { loop { match arr.pop_front() { - Option::Some(item) => { - if *item != value { - break false; - } - }, - Option::None => { - break true; - }, + Option::Some(item) => { if *item != value { + break false; + } }, + Option::None => { break true; }, }; } } @@ -50,17 +47,13 @@ fn are_eq(mut arr: Span, value: u128) -> bool { /// * `value` - The value to compare the elements to. /// # Returns /// true if all of the elements in the array are greater than the specified value, false otherwise. -fn are_gt(mut arr: Span, value: u128) -> bool { +fn are_gt(mut arr: Span, value: u256) -> bool { loop { match arr.pop_front() { - Option::Some(item) => { - if *item <= value { - break false; - } - }, - Option::None => { - break true; - }, + Option::Some(item) => { if *item <= value { + break false; + } }, + Option::None => { break true; }, }; } } @@ -71,17 +64,13 @@ fn are_gt(mut arr: Span, value: u128) -> bool { /// * `value` - The value to compare the elements to. /// # Returns /// true if all of the elements in the array are greater than or equal to the specified value, false otherwise. -fn u64_are_gte(mut arr: Span, value: u64) -> bool { +fn are_gte_u64(mut arr: Span, value: u64) -> bool { loop { match arr.pop_front() { - Option::Some(item) => { - if *item < value { - break false; - } - }, - Option::None => { - break true; - }, + Option::Some(item) => { if *item < value { + break false; + } }, + Option::None => { break true; }, }; } } @@ -92,17 +81,13 @@ fn u64_are_gte(mut arr: Span, value: u64) -> bool { /// * `value` - The value to compare the elements to. /// # Returns /// true if all of the elements in the array are greater than or equal to the specified value, false otherwise. -fn are_gte(mut arr: Span, value: u128) -> bool { +fn are_gte(mut arr: Span, value: u256) -> bool { loop { match arr.pop_front() { - Option::Some(item) => { - if *item < value { - break false; - } - }, - Option::None => { - break true; - }, + Option::Some(item) => { if *item < value { + break false; + } }, + Option::None => { break true; }, }; } } @@ -113,17 +98,13 @@ fn are_gte(mut arr: Span, value: u128) -> bool { /// * `value` - The value to compare the elements to. /// # Returns /// true if all of the elements in the array are less than the specified value, false otherwise. -fn are_lt(mut arr: Span, value: u128) -> bool { +fn are_lt(mut arr: Span, value: u256) -> bool { loop { match arr.pop_front() { - Option::Some(item) => { - if *item >= value { - break false; - } - }, - Option::None => { - break true; - }, + Option::Some(item) => { if *item >= value { + break false; + } }, + Option::None => { break true; }, }; } } @@ -134,17 +115,30 @@ fn are_lt(mut arr: Span, value: u128) -> bool { /// * `value` - The value to compare the elements to. /// # Returns /// true if all of the elements in the array are less than or equal to the specified value, false otherwise. -fn are_lte(mut arr: Span, value: u128) -> bool { +fn are_lte(mut arr: Span, value: u256) -> bool { loop { match arr.pop_front() { - Option::Some(item) => { - if *item > value { - break false; - } - }, - Option::None => { - break true; - }, + Option::Some(item) => { if *item > value { + break false; + } }, + Option::None => { break true; }, + }; + } +} + +/// Determines whether all of the elements in the given array are less than or equal to the specified value. +/// # Arguments +/// * `arr` - the array to check the elements of. +/// * `value` - The value to compare the elements to. +/// # Returns +/// true if all of the elements in the array are less than or equal to the specified value, false otherwise. +fn are_lte_u64(mut arr: Span, value: u64) -> bool { + loop { + match arr.pop_front() { + Option::Some(item) => { if *item > value { + break false; + } }, + Option::None => { break true; }, }; } } @@ -156,12 +150,12 @@ fn are_lte(mut arr: Span, value: u128) -> bool { /// * `arr` - the array to get the median of. /// # Returns /// the median value of the elements in the given array. -fn get_median(arr: Span) -> u128 { +fn get_median(arr: Span) -> u256 { if arr.len() % 2 == 1 { - *arr.get(arr.len() / 2).unwrap().unbox() + *arr.get(arr.len() / 2).expect('array.get failed').unbox() } else { - let left = *arr.get(arr.len() / 2 - 1).unwrap().unbox(); - let right = *arr.get(arr.len() / 2).unwrap().unbox(); + let left = *arr.get(arr.len() / 2 - 1).expect('array.get failed').unbox(); + let right = *arr.get(arr.len() / 2).expect('array.get failed').unbox(); (left + right) / 2 } } @@ -176,23 +170,31 @@ fn get_median(arr: Span) -> u128 { /// # Returns /// The uncompacted value at the specified index in the array of compacted values. fn get_uncompacted_value( - compacted_values: Span, + compacted_values: Span, index: usize, compacted_value_bit_length: usize, - bit_mask: u128, + bit_mask: u256, label: felt252 -) -> u128 { - let compacted_values_per_slot = 128 / compacted_value_bit_length; +) -> u256 { + error_utils::check_division_by_zero( + compacted_value_bit_length.into(), 'compacted_value_bit_length' + ); + let compacted_values_per_slot = 256 / compacted_value_bit_length; // 256 / 32 = 8 - let slot_index = index / compacted_values_per_slot; + error_utils::check_division_by_zero( + compacted_values_per_slot.into(), 'compacted_values_per_slot' + ); + let slot_index = index / compacted_values_per_slot; // 1 / 4 = 0 if slot_index >= compacted_values.len() { panic(array!['CompactedArrayOutOfBounds', index.into(), slot_index.into(), label]); } - let slot_bits = *compacted_values.at(slot_index); - let offset = (index - slot_index * compacted_values_per_slot) * compacted_value_bit_length; + let slot_bits = *compacted_values.at(slot_index); // 4294967346000000 + let offset = (index - slot_index * compacted_values_per_slot) + * compacted_value_bit_length; // = 32 - let value = (slot_bits / pow(2, offset)) & bit_mask; + let value = (slot_bits / pow(2, offset)) + & bit_mask; // 4294967346000000 / 2^32 = 1000000 & bit_mask value } @@ -201,8 +203,8 @@ fn get_uncompacted_value( /// * `x` - The number to raise. /// * `n` - The exponent. /// # Returns -/// * `u128` - The result of x raised to the power of n. -fn pow(x: u128, n: usize) -> u128 { +/// * `u256` - The result of x raised to the power of n. +fn pow(x: u256, n: usize) -> u256 { if n == 0 { 1 } else if n == 1 { @@ -245,7 +247,7 @@ impl StoreContractAddressSpan of Store> { } let value = Store::::read_at_offset(address_domain, base, offset) - .unwrap(); + .expect('read_at_offset failed'); arr.append(value); offset += Store::::size(); }; @@ -260,7 +262,7 @@ impl StoreContractAddressSpan of Store> { mut offset: u8, mut value: Span ) -> SyscallResult<()> { - // // Store the length of the array in the first storage slot. + // Store the length of the array in the first storage slot. let len: u8 = value.len().try_into().expect('Storage - Span too large'); Store::::write_at_offset(address_domain, base, offset, len); offset += 1; @@ -269,14 +271,12 @@ impl StoreContractAddressSpan of Store> { loop { match value.pop_front() { Option::Some(element) => { - Store::::write_at_offset( - address_domain, base, offset, *element - ); + Store::< + ContractAddress + >::write_at_offset(address_domain, base, offset, *element); offset += Store::::size(); }, - Option::None(_) => { - break Result::Ok(()); - } + Option::None(_) => { break Result::Ok(()); } }; } } @@ -285,3 +285,34 @@ impl StoreContractAddressSpan of Store> { 255 * Store::::size() } } + +/// Gets the uncompacted value at the specified index in the given array of compacted values. +/// # Arguments +/// * `compacted_values` - the array of compacted values to get the uncompacted value from. +/// * `index` - the index of the uncompacted value in the array. +/// * `compacted_value_bit_length` - the length of each compacted value, in bits. +/// * `bit_mask` - the bitmask to use to extract the uncompacted value from the compacted value. +/// * `label` - the array of compacted values to get the uncompacted value from. +/// # Returns +/// The uncompacted value at the specified index in the array of compacted values. +fn get_uncompacted_value_u64( + compacted_values: Span, + index: usize, + compacted_value_bit_length: usize, + bit_mask: u64, + label: felt252 +) -> u64 { + let compacted_values_per_slot = 64 / compacted_value_bit_length; + + let slot_index = index / compacted_values_per_slot; + if slot_index >= compacted_values.len() { + panic(array!['CompactedArrayOutOfBounds', index.into(), slot_index.into(), label]); + } + + let slot_bits = *compacted_values.at(slot_index); + let offset = (index - slot_index * compacted_values_per_slot) * compacted_value_bit_length; + + let value = (slot_bits / calc::pow_u64(2, offset)) & bit_mask; + + value +} diff --git a/src/utils/bits.cairo b/src/utils/bits.cairo index c4f68a6b..a1de7950 100644 --- a/src/utils/bits.cairo +++ b/src/utils/bits.cairo @@ -2,10 +2,10 @@ // IMPORTS // ************************************************************************* -const BITMASK_8: u128 = 127; +const BITMASK_8: u256 = 127; -const BITMASK_16: u128 = 32767; +const BITMASK_16: u256 = 32767; -const BITMASK_32: u128 = 2147483647; +const BITMASK_32: u256 = 2147483647; -const BITMASK_64: u128 = 9223372036854775807; +const BITMASK_64: u64 = 9223372036854775807; diff --git a/src/utils/calc.cairo b/src/utils/calc.cairo index 9b2fc8c0..360f7a17 100644 --- a/src/utils/calc.cairo +++ b/src/utils/calc.cairo @@ -2,7 +2,10 @@ // IMPORTS // ************************************************************************* // Core lib imports. - +use satoru::utils::error_utils; +use integer::{BoundedInt, u256_checked_add, U256PartialOrd}; +use satoru::utils::i256::{i256, i256_new, i256_neg, i256Zeroable, i256_add}; +use debug::PrintTrait; /// Calculates the result of dividing the first number by the second number /// rounded up to the nearest integer. /// # Arguments @@ -10,7 +13,7 @@ /// * `b` - the divisor. /// # Return /// The result of dividing the first number by the second number, rounded up to the nearest integer. -fn roundup_division(a: u128, b: u128) -> u128 { +fn roundup_division(a: u256, b: u256) -> u256 { (a + b - 1) / b } @@ -23,28 +26,13 @@ fn roundup_division(a: u128, b: u128) -> u128 { /// * `b` - the divisor. /// # Return /// The result of dividing the first number by the second number, rounded up to the nearest integer. -// TODO Update to use i128 division when available -fn roundup_magnitude_division(a: i128, b: u128) -> i128 { - let a_abs = if a < 0 { - -a - } else { - a - }; - // TODO remove all felt conversion when possible to try_into from u128 -> i128 - let a_felt: felt252 = a_abs.into(); - let a_u128: u128 = a_felt.try_into().unwrap(); - if a < 0 { - if a_u128 < b { - return 0; - } - let response_u128 = (a_u128 - b + 1) / b; - let response_felt: felt252 = response_u128.into(); - -response_felt.try_into().unwrap() - 1 - } else { - let response_u128 = (a_u128 + b - 1) / b; - let response_felt: felt252 = response_u128.into(); - response_felt.try_into().unwrap() +// TODO function doesn't really do what the comments tell +fn roundup_magnitude_division(a: i256, b: u256) -> i256 { + error_utils::check_division_by_zero(b, 'roundup_magnitude_division'); + if (a < Zeroable::zero()) { + return ((a - i256_new(b, false) + i256_new(1, false)) / i256_new(b, false)); } + return ((a + i256_new(b, false) - i256_new(1, false)) / i256_new(b, false)); } /// Adds two numbers together and return an u256 value, treating the second number as a signed integer, @@ -53,15 +41,9 @@ fn roundup_magnitude_division(a: i128, b: u128) -> i128 { /// * `b` - second number. /// # Return /// the result of adding the two numbers together. -fn sum_return_uint_128(a: u128, b: i128) -> u128 { - let b_abs = if b < 0 { - -b - } else { - b - }; - let b_abs: felt252 = b_abs.into(); - let b_abs: u128 = b_abs.try_into().unwrap(); - if (b > 0) { +fn sum_return_uint_256(a: u256, b: i256) -> u256 { + let b_abs = b.mag; + if (b > Zeroable::zero()) { a + b_abs } else { a - b_abs @@ -74,10 +56,9 @@ fn sum_return_uint_128(a: u128, b: i128) -> u128 { /// * `b` - second number. /// # Return /// the result of adding the two numbers together. -fn sum_return_int_128(a: u128, b: i128) -> i128 { - let a: felt252 = a.into(); - let a: i128 = a.try_into().expect('i128 Overflow'); - a + b +fn sum_return_int_256(a: u256, b: i256) -> i256 { + let a_i256 = i256_new(a, false); + a_i256 + b } /// Calculates the absolute difference between two numbers, @@ -86,7 +67,7 @@ fn sum_return_int_128(a: u128, b: i128) -> i128 { /// * `b` - second number. /// # Return /// the absolute difference between the two numbers. -fn diff(a: u128, b: u128) -> u128 { +fn diff(a: u256, b: u256) -> u256 { if a > b { a - b } else { @@ -100,21 +81,24 @@ fn diff(a: u128, b: u128) -> u128 { /// * `b` - second number. /// # Return /// the result of adding the two numbers together. -fn bounded_add(a: i128, b: i128) -> i128 { - if (a == 0 || b == 0 || (a < 0 && b > 0) || (a > 0 && b < 0)) { +fn bounded_add(a: i256, b: i256) -> i256 { + if (a == Zeroable::zero() + || b == Zeroable::zero() + || (a < Zeroable::zero() && b > Zeroable::zero()) + || (a > Zeroable::zero() && b < Zeroable::zero())) { return a + b; } // if adding `b` to `a` would result in a value less than the min int256 value // then return the min int256 value - if (a < 0 && b <= min_i128() - a) { - return min_i128(); + if (a < Zeroable::zero() && b <= min_i256() - a) { + return min_i256(); } // if adding `b` to `a` would result in a value more than the max int256 value // then return the max int256 value - if (a > 0 && b >= max_i128() - a) { - return max_i128(); + if (a > Zeroable::zero() && b >= max_i256() - a) { + return max_i256(); } return a + b; @@ -126,21 +110,24 @@ fn bounded_add(a: i128, b: i128) -> i128 { /// * `b` - second number. /// # Return /// the bounded result of a - b. -fn bounded_sub(a: i128, b: i128) -> i128 { - if (a == 0 || b == 0 || (a > 0 && b > 0) || (a < 0 && b < 0)) { +fn bounded_sub(a: i256, b: i256) -> i256 { + if (a == Zeroable::zero() + || b == Zeroable::zero() + || (a > Zeroable::zero() && b > Zeroable::zero()) + || (a < Zeroable::zero() && b < Zeroable::zero())) { return a - b; } // if adding `-b` to `a` would result in a value greater than the max int256 value // then return the max int256 value - if (a > 0 && -b >= max_i128() - a) { - return max_i128(); + if (a > Zeroable::zero() && i256_neg(b) >= max_i256() - a) { + return max_i256(); } // if subtracting `b` from `a` would result in a value less than the min int256 value // then return the min int256 value - if (a < 0 && -b <= min_i128() - a) { - return min_i128(); + if (a < Zeroable::zero() && i256_neg(b) <= min_i256() - a) { + return min_i256(); } return a - b; @@ -152,24 +139,44 @@ fn bounded_sub(a: i128, b: i128) -> i128 { /// * `b` - second number. /// # Return /// The signed integer. -fn to_signed(a: u128, is_positive: bool) -> i128 { - if is_positive { - let a_felt: felt252 = a.into(); - a_felt.try_into().expect('i128 Overflow') - } else { - let a_felt: felt252 = a.into(); - -a_felt.try_into().expect('i128 Overflow') +fn to_signed(a: u256, mut is_positive: bool) -> i256 { + // let a_felt: felt252 = a.into(); + // let a_signed = a_felt.try_into().expect('i256 Overflow'); + if (a == 0) { + is_positive = true; } + i256_new(a, !is_positive) +} + +/// Converts the given signed integer to an unsigned integer, panics otherwise +/// # Return +/// The unsigned integer. +fn to_unsigned(value: i256) -> u256 { + assert(value >= Zeroable::zero(), 'to_unsigned: value is negative'); + return value.mag; +} + +fn max_i256() -> i256 { + i256 { mag: (BoundedInt::::max() - 1), sign: false } } -// TODO use BoundedInt::max() && BoundedInt::mint() when possible -// Can't impl trait BoundedInt because of "-" that can panic (unless I can do it without using the minus operator) -fn max_i128() -> i128 { - // Comes from https://doc.rust-lang.org/std/i128/constant.MAX.html - 170_141_183_460_469_231_731_687_303_715_884_105_727 +fn min_i256() -> i256 { + i256 { mag: BoundedInt::::max(), sign: true } } -fn min_i128() -> i128 { - // Comes from https://doc.rust-lang.org/std/i128/constant.MIN.html - -170_141_183_460_469_231_731_687_303_715_884_105_728 +/// Raise a number to a power, computes x^n. +/// * `x` - The number to raise. +/// * `n` - The exponent. +/// # Returns +/// * `u64` - The result of x raised to the power of n. +fn pow_u64(x: u64, n: usize) -> u64 { + if n == 0 { + 1 + } else if n == 1 { + x + } else if (n & 1) == 1 { + x * pow_u64(x * x, n / 2) + } else { + pow_u64(x * x, n / 2) + } } diff --git a/src/utils/default.cairo b/src/utils/default.cairo new file mode 100644 index 00000000..25fa131d --- /dev/null +++ b/src/utils/default.cairo @@ -0,0 +1,10 @@ +// Core lib imports +use core::option::OptionTrait; +use core::traits::TryInto; +use starknet::ContractAddress; + +impl DefaultContractAddress of Default { + fn default() -> ContractAddress { + 0.try_into().unwrap() + } +} diff --git a/src/utils/enumerable_set.cairo b/src/utils/enumerable_set.cairo index 5efa0017..c0a1460b 100644 --- a/src/utils/enumerable_set.cairo +++ b/src/utils/enumerable_set.cairo @@ -175,7 +175,7 @@ impl ContractAddressSetImpl of SetTrait { } } -impl U128SetImpl of SetTrait { +impl U256SetImpl of SetTrait { /// Creates a new set. /// # Returns /// * A new set. @@ -188,8 +188,8 @@ impl U128SetImpl of SetTrait { /// * `value` - The value to add. /// # Returns /// * `true` if the value was added to the set, `false` otherwise. - fn add(ref self: Set, value: u128) -> bool { - Felt252SetImpl::add(ref self, value.into()) + fn add(ref self: Set, value: u256) -> bool { + Felt252SetImpl::add(ref self, value.try_into().expect('u256 into felt failed')) } /// Removes a value from the set. @@ -197,8 +197,8 @@ impl U128SetImpl of SetTrait { /// * `value` - The value to remove. /// # Returns /// * `true` if the value was removed from the set, `false` otherwise. - fn remove(ref self: Set, value: u128) -> bool { - Felt252SetImpl::remove(ref self, value.into()) + fn remove(ref self: Set, value: u256) -> bool { + Felt252SetImpl::remove(ref self, value.try_into().expect('u256 into felt failed')) } /// Checks if a value is in the set. @@ -206,8 +206,8 @@ impl U128SetImpl of SetTrait { /// * `value` - The value to check. /// # Returns /// * `true` if the value is in the set, `false` otherwise. - fn contains(ref self: Set, value: u128) -> bool { - Felt252SetImpl::contains(ref self, value.into()) + fn contains(ref self: Set, value: u256) -> bool { + Felt252SetImpl::contains(ref self, value.try_into().expect('u256 into felt failed')) } /// Returns the number of elements in the set. @@ -220,15 +220,15 @@ impl U128SetImpl of SetTrait { /// Returns the value stored at position `index` in the set. /// # Arguments /// * `index` - The index of the value to return. - fn at(ref self: Set, index: felt252) -> u128 { - self.values.get(index).try_into().expect('Invalid u128') + fn at(ref self: Set, index: felt252) -> u256 { + self.values.get(index).into() } /// Returns the entire set as an array. /// # Returns /// * The entire set as an array. - fn values(ref self: Set) -> Array { - let mut values = ArrayTrait::::new(); + fn values(ref self: Set) -> Array { + let mut values = ArrayTrait::::new(); let mut i = self.length; loop { if i == 0 { diff --git a/src/utils/enumerable_values.cairo b/src/utils/enumerable_values.cairo index 2e4bc24c..414c96dc 100644 --- a/src/utils/enumerable_values.cairo +++ b/src/utils/enumerable_values.cairo @@ -30,14 +30,14 @@ fn values_at_address() -> Array { // TODO ArrayTrait::new() } -/// Returns an array of u128 values from the given set, starting at the given +/// Returns an array of u256 values from the given set, starting at the given /// start index and ending before the given end index.. /// # Arguments /// * `set` - The set to get the values from. /// * `start` - The starting index. /// * `end` - The ending index. /// # Returns -/// An array of u128 values. -fn values_at_u128() -> Array { // TODO +/// An array of u256 values. +fn values_at_u256() -> Array { // TODO ArrayTrait::new() } diff --git a/src/utils/error_utils.cairo b/src/utils/error_utils.cairo index e7b84ffb..37815636 100644 --- a/src/utils/error_utils.cairo +++ b/src/utils/error_utils.cairo @@ -12,3 +12,9 @@ fn get_revert_message(reason_bytes: Span) -> felt252 { // TODO 0 } + +fn check_division_by_zero(divisor: u256, variable_name: felt252) { + if divisor.is_zero() { + panic(array!['division by zero', variable_name]) + } +} diff --git a/src/utils/felt_math.cairo b/src/utils/felt_math.cairo new file mode 100644 index 00000000..15ef12b1 --- /dev/null +++ b/src/utils/felt_math.cairo @@ -0,0 +1,32 @@ +use integer::u256_from_felt252; + +const HALF_PRIME: felt252 = + 1809251394333065606848661391547535052811553607665798349986546028067936010240; + +// Returns the sign of a signed `felt252` as with signed magnitude representation. +// +// # Arguments +// * `a` - The number to check the sign of. +// +// # Returns +// * `bool` - The sign of the number. +fn felt_sign(a: felt252) -> bool { + u256_from_felt252(a) > u256_from_felt252(HALF_PRIME) +} + +// Returns the absolute value of a signed `felt252`. +// +// # Arguments +// * `a` - The number to get the absolute value of. +// +// # Returns +// * `felt252` - The absolute value of the number. +fn felt_abs(a: felt252) -> felt252 { + let a_sign = felt_sign(a); + + if a_sign { + a * -1 + } else { + a + } +} diff --git a/src/utils/global_reentrancy_guard.cairo b/src/utils/global_reentrancy_guard.cairo index 5a5ca086..ad674418 100644 --- a/src/utils/global_reentrancy_guard.cairo +++ b/src/utils/global_reentrancy_guard.cairo @@ -14,10 +14,7 @@ const REENTRANCY_GUARD_STATUS: felt252 = 'REENTRANCY_GUARD_STATUS'; /// Modifier to avoid reentrancy. fn non_reentrant_before(data_store: IDataStoreDispatcher) { // Read key from Data Store. - let status = match data_store.get_bool(REENTRANCY_GUARD_STATUS) { - Option::Some(v) => v, - Option::None => false, - }; + let status = data_store.get_bool(REENTRANCY_GUARD_STATUS); // Check if reentrancy is happening. assert(!status, ReentrancyGuardError::REENTRANT_CALL); diff --git a/src/utils/i128.cairo b/src/utils/i128.cairo index 8bc71fe7..d47e6bc4 100644 --- a/src/utils/i128.cairo +++ b/src/utils/i128.cairo @@ -1,4 +1,190 @@ -// TODO Remove all below once natif +// This code uses a portion of the code from the YAS project under the Apache 2.0 license. +// Here is the link to the original project: +// https://github.com/lambdaclass/yet-another-swap + +// The original source code is subject to the Apache 2.0 license, the terms of which can be found here: +// http://www.apache.org/licenses/LICENSE-2.0 + +/// Trait +/// +/// new - Constructs a new `signed_integer +/// div_rem - Computes `signed_integer` division and modulus simultaneously +/// abs - Computes the absolute value of the given `signed_integer` +/// max - Returns the maximum between two `signed_integer` +/// min - Returns the minimum between two `signed_integer` +trait IntegerTrait { + /// # IntegerTrait::new + /// + /// ```rust + /// fn new(mag: U, sign: bool) -> T; + /// ``` + /// + /// Returns a new signed integer. + /// + /// ## Args + /// + /// * `mag`(`U`) - The magnitude of the integer. + /// * `sign`(`bool`) - The sign of the integer, where `true` represents a negative number. + /// + /// > _`` generic type depends on the uint type (u8, u16, u32, u64, u128)._ + /// + /// ## Panics + /// + /// Panics if `mag` is out of range. + /// + /// ## Returns + /// + /// A new signed integer. + /// + /// ## Examples + /// + /// ```rust + /// fn new_i8_example() -> i8 { + /// IntegerTrait::::new(42_u8, true) + /// } + /// >>> {mag: 42, sign: true} // = -42 + /// ``` + /// + /// ```rust + /// fn panic_i8_example() -> i8 { + /// IntegerTrait::::new(129_u8, true) + /// } + /// >>> panics with "int: out of range" + /// ``` + /// + fn new(mag: U, sign: bool) -> T; + /// # int.div_rem + /// + /// ```rust + /// fn div_rem(self: T, other: T) -> (T, T); + /// ``` + /// + /// Computes signed\_integer division and modulus simultaneously + /// + /// ## Args + /// + /// * `self`(`T`) - The dividend + /// * `other`(`T`) - The divisor + /// + /// ## Panics + /// + /// Panics if the divisor is zero. + /// + /// ## Returns + /// + /// A tuple of signed integer ``, containing the quotient and the remainder of the division. + /// + /// ## Examples + /// + /// ```rust + /// fn div_rem_example() -> (i32, i32) { + /// // We instantiate signed integers here. + /// let a = IntegerTrait::::new(13, false); + /// let b = IntegerTrait::::new(5, false); + /// + /// // We can call `div_rem` function as follows. + /// a.div_rem(b) + /// } + /// >>> ({mag: 2, sign: false}, {mag: 3, sign: false}) // = (2, 3) + /// ``` + /// + fn div_rem(self: T, other: T) -> (T, T); + /// # int.abs + /// + /// ```rust + /// fn abs(self: T) -> T; + /// ``` + /// + /// Computes the absolute value of a signed\_integer. + /// + /// ## Args + /// + /// `self`(`T`) - The signed integer to which the absolute value is applied + /// + /// ## Returns + /// + /// A signed integer ``, representing the absolute value of `self` . + /// + /// ## Examples + /// + /// ```rust + /// fn abs_example() -> i32 { + /// // We instantiate signed integers here. + /// let int = IntegerTrait::::new(42, true); + /// + /// // We can call `abs` function as follows. + /// a.abs() + /// } + /// >>> {mag: 42, sign: false} // = 42 + /// ``` + /// + fn abs(self: T) -> T; + /// # int.max + /// + /// ```rust + /// fn max(self: T, other: T) -> T; + /// ``` + /// + /// Returns the maximum between two signed\_integer. + /// + /// ## Args + /// + /// *`self`(`T`) - The first signed integer to compare. + /// * `other`(`T`) - The second signed integer to compare. + /// + /// ## Returns + /// + /// A signed integer ``, The maximum between `self` and `other`. + /// + /// ## Examples + /// + /// ```rust + /// fn max_example() -> i32 { + /// // We instantiate signed integer here. + /// let a = IntegerTrait::::new(42, true); + /// let b = IntegerTrait::::new(13, false); + /// + /// // We can call `max` function as follows. + /// a.max(b) + /// } + /// >>> {mag: 13, sign: false} // as 13 > -42 + /// ``` + /// + fn max(self: T, other: T) -> T; + /// # int.min + /// + /// ```rust + /// fn min(self: T, other: T) -> T; + /// ``` + /// + /// Returns the minimum between two signed\_integer. + /// + /// ## Args + /// + /// `self`(`T`) - The first signed integer to compare. + /// `other`(`T`) - The second signed integer to compare. + /// + /// ## Returns + /// + /// A signed integer ``, The minimum between `self` and `other`. + /// + /// ## Examples + /// + /// + /// ```rust + /// fn min_example() -> i32 { + /// // We instantiate signed integer here. + /// let a = IntegerTrait::::new(42, true); + /// let b = IntegerTrait::::new(13, false); + /// + /// // We can call `max` function as follows. + /// a.min(b) + /// } + /// >>> {mag: 42, sign: true} // as -42 < 13 + /// ``` + /// + fn min(self: T, other: T) -> T; +} /// Core lib imports. use starknet::{ @@ -8,103 +194,535 @@ use starknet::{ use integer::BoundedInt; -impl I128Div of Div { - fn div(lhs: i128, rhs: i128) -> i128 { - assert(rhs != 0, 'Division by 0'); - let u_lhs = abs(lhs); - let u_rhs = abs(rhs); - let response = u_lhs / u_rhs; - let response: felt252 = response.into(); - if (lhs > 0 && rhs > 0) || (lhs < 0 && rhs < 0) { - response.try_into().expect('i128 Overflow') +// ====================== INT 128 ====================== + +// i128 represents a 128-bit integer. +// The mag field holds the absolute value of the integer. +// The sign field is true for negative integers, and false for non-negative integers. +#[derive(Serde, Copy, Drop, Hash, starknet::Store)] +struct i128 { + mag: u128, + sign: bool, +} + +impl i128Impl of IntegerTrait { + fn new(mag: u128, sign: bool) -> i128 { + i128_new(mag, sign) + } + + fn div_rem(self: i128, other: i128) -> (i128, i128) { + i128_div_rem(self, other) + } + + fn abs(self: i128) -> i128 { + i128_abs(self) + } + + fn max(self: i128, other: i128) -> i128 { + i128_max(self, other) + } + + fn min(self: i128, other: i128) -> i128 { + i128_min(self, other) + } +} + +// Implements the Into trait for i128. +impl i32Into of Into { + fn into(self: i128) -> felt252 { + let mag_felt = self.mag.into(); + + if (self.sign == true) { + return mag_felt * -1; } else { - -response.try_into().expect('i128 Overflow') + return mag_felt; } } } -impl I128Mul of Mul { +impl u128Intoi128 of Into { + fn into(self: u128) -> i128 { + IntegerTrait::::new(self, false) + } +} + +impl Felt252TryIntoI128 of TryInto { + fn try_into(self: felt252) -> Option { + let try_to_u128: Option = self.try_into(); + + match try_to_u128 { + Option::Some(data) => { + return Option::Some( + IntegerTrait::::new(data, false) + ); //TODO check if the sign might be negative sometimes + }, + Option::None => { return Option::None; } + } + } +} + + +impl I128Default of Default { + fn default() -> i128 { + Zeroable::zero() + } +} + + +// Implements the Add trait for i128. +impl i128Add of Add { + fn add(lhs: i128, rhs: i128) -> i128 { + i128_add(lhs, rhs) + } +} + +// Implements the AddEq trait for i128. +impl i128AddEq of AddEq { + fn add_eq(ref self: i128, other: i128) { + self = Add::add(self, other); + } +} + +// Implements the Sub trait for i128. +impl i128Sub of Sub { + fn sub(lhs: i128, rhs: i128) -> i128 { + i128_sub(lhs, rhs) + } +} + +// Implements the SubEq trait for i128. +impl i128SubEq of SubEq { + fn sub_eq(ref self: i128, other: i128) { + self = Sub::sub(self, other); + } +} + +// Implements the Mul trait for i128. +impl i128Mul of Mul { fn mul(lhs: i128, rhs: i128) -> i128 { - let u_lhs = abs(lhs); - let u_rhs = abs(rhs); - let response = u_lhs * u_rhs; - let response: felt252 = response.into(); - if (lhs > 0 && rhs > 0) || (lhs < 0 && rhs < 0) { - response.try_into().expect('i128 Overflow') + i128_mul(lhs, rhs) + } +} + +// Implements the MulEq trait for i128. +impl i128MulEq of MulEq { + fn mul_eq(ref self: i128, other: i128) { + self = Mul::mul(self, other); + } +} + +// Implements the Div trait for i128. +impl i128Div of Div { + fn div(lhs: i128, rhs: i128) -> i128 { + i128_div(lhs, rhs) + } +} + +// Implements the DivEq trait for i128. +impl i128DivEq of DivEq { + fn div_eq(ref self: i128, other: i128) { + self = Div::div(self, other); + } +} + +// Implements the Rem trait for i128. +impl i128Rem of Rem { + fn rem(lhs: i128, rhs: i128) -> i128 { + i128_rem(lhs, rhs) + } +} + +// Implements the RemEq trait for i128. +impl i128RemEq of RemEq { + fn rem_eq(ref self: i128, other: i128) { + self = Rem::rem(self, other); + } +} + +// Implements the PartialEq trait for i128. +impl i128PartialEq of PartialEq { + fn eq(lhs: @i128, rhs: @i128) -> bool { + i128_eq(*lhs, *rhs) + } + + fn ne(lhs: @i128, rhs: @i128) -> bool { + i128_ne(*lhs, *rhs) + } +} + +// Implements the PartialOrd trait for i128. +impl i128PartialOrd of PartialOrd { + fn le(lhs: i128, rhs: i128) -> bool { + i128_le(lhs, rhs) + } + fn ge(lhs: i128, rhs: i128) -> bool { + i128_ge(lhs, rhs) + } + + fn lt(lhs: i128, rhs: i128) -> bool { + i128_lt(lhs, rhs) + } + fn gt(lhs: i128, rhs: i128) -> bool { + i128_gt(lhs, rhs) + } +} + +// Implements the Neg trait for i128. +impl i128Neg of Neg { + fn neg(a: i128) -> i128 { + i128_neg(a) + } +} + +impl i128Zeroable of Zeroable { + fn zero() -> i128 { + IntegerTrait::::new(0, false) + } + fn is_zero(self: i128) -> bool { + self == Zeroable::zero() + } + fn is_non_zero(self: i128) -> bool { + self != Zeroable::zero() + } +} + + +// Checks if the given i128 integer is zero and has the correct sign. +// # Arguments +// * `x` - The i128 integer to check. +// # Panics +// Panics if `x` is zero and has a sign that is not false. +fn i128_check_sign_zero(x: i128) { + if x.mag == 0_u128 { + assert(x.sign == false, 'sign of 0 must be false'); + } +} + +/// Cf: IntegerTrait::new docstring +fn i128_new(mag: u128, sign: bool) -> i128 { + if sign == true { + assert(mag <= 170141183460469231731687303715884105728_u128, 'i128 Overflow'); + } else { + assert(mag <= 170141183460469231731687303715884105727_u128, 'i128 Overflow'); + } + i128 { mag, sign } +} + +// Adds two i128 integers. +// # Arguments +// * `a` - The first i128 to add. +// * `b` - The second i128 to add. +// # Returns +// * `i128` - The sum of `a` and `b`. +fn i128_add(a: i128, b: i128) -> i128 { + i128_check_sign_zero(a); + i128_check_sign_zero(b); + + // If both integers have the same sign, + // the sum of their absolute values can be returned. + if a.sign == b.sign { + let sum = a.mag + b.mag; + if (sum == 0_u128) { + return IntegerTrait::new(sum, false); + } + return ensure_non_negative_zero(sum, a.sign); + } else { + // If the integers have different signs, + // the larger absolute value is subtracted from the smaller one. + let (larger, smaller) = if a.mag >= b.mag { + (a, b) } else { - -response.try_into().expect('i128 Overflow') + (b, a) + }; + let difference = larger.mag - smaller.mag; + + if (difference == 0_u128) { + return IntegerTrait::new(difference, false); } + return ensure_non_negative_zero(difference, larger.sign); + } +} + +// Subtracts two i128 integers. +// # Arguments +// * `a` - The first i128 to subtract. +// * `b` - The second i128 to subtract. +// # Returns +// * `i128` - The difference of `a` and `b`. +fn i128_sub(a: i128, b: i128) -> i128 { + i128_check_sign_zero(a); + i128_check_sign_zero(b); + + if (b.mag == 0_u128) { + return a; } + + // The subtraction of `a` to `b` is achieved by negating `b` sign and adding it to `a`. + let neg_b = ensure_non_negative_zero(b.mag, !b.sign); + return a + neg_b; } -fn abs(signed_integer: i128) -> u128 { - let response = if signed_integer < 0 { - -signed_integer +// Multiplies two i128 integers. +// +// # Arguments +// +// * `a` - The first i128 to multiply. +// * `b` - The second i128 to multiply. +// +// # Returns +// +// * `i128` - The product of `a` and `b`. +fn i128_mul(a: i128, b: i128) -> i128 { + i128_check_sign_zero(a); + i128_check_sign_zero(b); + + // The sign of the product is the XOR of the signs of the operands. + let sign = a.sign ^ b.sign; + // The product is the product of the absolute values of the operands. + let mag = a.mag * b.mag; + + if (mag == 0_u128) { + return IntegerTrait::new(mag, false); + } + + return ensure_non_negative_zero(mag, sign); +} + +// Divides the first i128 by the second i128. +// # Arguments +// * `a` - The i128 dividend. +// * `b` - The i128 divisor. +// # Returns +// * `i128` - The quotient of `a` and `b`. +fn i128_div(a: i128, b: i128) -> i128 { + i128_check_sign_zero(a); + // Check that the divisor is not zero. + assert(b.mag != 0_u128, 'Division by 0'); + + // The sign of the quotient is the XOR of the signs of the operands. + let sign = a.sign ^ b.sign; + + if (sign == false) { + // If the operands are positive, the quotient is simply their absolute value quotient. + return ensure_non_negative_zero(a.mag / b.mag, sign); + } + + // If the operands have different signs, rounding is necessary. + // First, check if the quotient is an integer. + if (a.mag % b.mag == 0_u128) { + let quotient = a.mag / b.mag; + if (quotient == 0_u128) { + return IntegerTrait::new(quotient, false); + } + return ensure_non_negative_zero(quotient, sign); + } + + // If the quotient is not an integer, multiply the dividend by 10 to move the decimal point over. + let quotient = (a.mag * 10_u128) / b.mag; + let last_digit = quotient % 10_u128; + + if (quotient == 0_u128) { + return IntegerTrait::new(quotient, false); + } + + // Check the last digit to determine rounding direction. + if (last_digit <= 5_u128) { + return ensure_non_negative_zero(quotient / 10_u128, sign); } else { - signed_integer - }; - let response: felt252 = response.into(); - response.try_into().expect('u128 Overflow') + return ensure_non_negative_zero((quotient / 10_u128) + 1_u128, sign); + } } -impl StoreI128 of Store { - fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult { - Result::Ok( - Store::::read(address_domain, base)?.try_into().expect('StoreI128 - non i128') - ) +// Calculates the remainder of the division of a first i128 by a second i128. +// # Arguments +// * `a` - The i128 dividend. +// * `b` - The i128 divisor. +// # Returns +// * `i128` - The remainder of dividing `a` by `b`. +fn i128_rem(a: i128, b: i128) -> i128 { + i128_check_sign_zero(a); + // Check that the divisor is not zero. + assert(b.mag != 0_u128, 'Division by 0'); + + return a - (b * (a / b)); +} + +/// Cf: IntegerTrait::div_rem docstring +fn i128_div_rem(a: i128, b: i128) -> (i128, i128) { + let quotient = i128_div(a, b); + let remainder = i128_rem(a, b); + + return (quotient, remainder); +} + +// Compares two i128 integers for equality. +// # Arguments +// * `a` - The first i128 integer to compare. +// * `b` - The second i128 integer to compare. +// # Returns +// * `bool` - `true` if the two integers are equal, `false` otherwise. +fn i128_eq(a: i128, b: i128) -> bool { + // Check if the two integers have the same sign and the same absolute value. + if a.sign == b.sign && a.mag == b.mag { + return true; } - #[inline(always)] - fn write(address_domain: u32, base: StorageBaseAddress, value: i128) -> SyscallResult<()> { - Store::::write(address_domain, base, value.into()) + + return false; +} + +// Compares two i128 integers for inequality. +// # Arguments +// * `a` - The first i128 integer to compare. +// * `b` - The second i128 integer to compare. +// # Returns +// * `bool` - `true` if the two integers are not equal, `false` otherwise. +fn i128_ne(a: i128, b: i128) -> bool { + // The result is the inverse of the equal function. + return !i128_eq(a, b); +} + +// Compares two i128 integers for greater than. +// # Arguments +// * `a` - The first i128 integer to compare. +// * `b` - The second i128 integer to compare. +// # Returns +// * `bool` - `true` if `a` is greater than `b`, `false` otherwise. +fn i128_gt(a: i128, b: i128) -> bool { + // Check if `a` is negative and `b` is positive. + if (a.sign & !b.sign) { + return false; } - #[inline(always)] - fn read_at_offset( - address_domain: u32, base: StorageBaseAddress, offset: u8 - ) -> SyscallResult { - Result::Ok( - Store::::read_at_offset(address_domain, base, offset)? - .try_into() - .expect('StoreI128 - non i128') - ) + // Check if `a` is positive and `b` is negative. + if (!a.sign & b.sign) { + return true; } - #[inline(always)] - fn write_at_offset( - address_domain: u32, base: StorageBaseAddress, offset: u8, value: i128 - ) -> SyscallResult<()> { - Store::::write_at_offset(address_domain, base, offset, value.into()) + // If `a` and `b` have the same sign, compare their absolute values. + if (a.sign & b.sign) { + return a.mag < b.mag; + } else { + return a.mag > b.mag; } - #[inline(always)] - fn size() -> u8 { - 1_u8 +} + +// Determines whether the first i128 is less than the second i128. +// # Arguments +// * `a` - The i128 to compare against the second i128. +// * `b` - The i128 to compare against the first i128. +// # Returns +// * `bool` - `true` if `a` is less than `b`, `false` otherwise. +fn i128_lt(a: i128, b: i128) -> bool { + if (a.sign != b.sign) { + return a.sign; + } else { + return a.mag != b.mag && (a.mag < b.mag) ^ a.sign; } } -impl I128Serde of Serde { - fn serialize(self: @i128, ref output: Array) { - output.append((*self).into()); +// Checks if the first i128 integer is less than or equal to the second. +// # Arguments +// * `a` - The first i128 integer to compare. +// * `b` - The second i128 integer to compare. +// # Returns +// * `bool` - `true` if `a` is less than or equal to `b`, `false` otherwise. +fn i128_le(a: i128, b: i128) -> bool { + if (a == b || i128_lt(a, b) == true) { + return true; + } else { + return false; } - fn deserialize(ref serialized: Span) -> Option { - let felt_val = *(serialized.pop_front().expect('i128 deserialize')); - let i128_val = felt_val.try_into().expect('i128 Overflow'); - Option::Some(i128_val) +} + +// Checks if the first i128 integer is greater than or equal to the second. +// # Arguments +// * `a` - The first i128 integer to compare. +// * `b` - The second i128 integer to compare. +// # Returns +// * `bool` - `true` if `a` is greater than or equal to `b`, `false` otherwise. +fn i128_ge(a: i128, b: i128) -> bool { + if (a == b || i128_gt(a, b) == true) { + return true; + } else { + return false; } } -fn u128_to_i128(value: u128) -> i128 { - assert(value <= BoundedInt::max(), 'u128_to_i128: value too large'); - let value: felt252 = value.into(); - value.try_into().unwrap() +// Negates the given i128 integer. +// # Arguments +// * `x` - The i128 integer to negate. +// # Returns +// * `i128` - The negation of `x`. +fn i128_neg(x: i128) -> i128 { + // The negation of an integer is obtained by flipping its sign. + return ensure_non_negative_zero(x.mag, !x.sign); } -fn i128_to_u128(value: i128) -> u128 { - assert(value >= 0, 'i128_to_u128: value is negative'); - let value: felt252 = value.into(); - value.try_into().unwrap() +/// Cf: IntegerTrait::abs docstring +fn i128_abs(x: i128) -> i128 { + return IntegerTrait::new(x.mag, false); } -impl I128Default of Default { - #[inline(always)] - fn default() -> i128 { - 0 +/// Cf: IntegerTrait::max docstring +fn i128_max(a: i128, b: i128) -> i128 { + if (a > b) { + return a; + } else { + return b; } } + +/// Cf: IntegerTrait::new docstring +fn i128_min(a: i128, b: i128) -> i128 { + if (a < b) { + return a; + } else { + return b; + } +} + +fn ensure_non_negative_zero(mag: u128, sign: bool) -> i128 { + if mag == 0 { + IntegerTrait::::new(mag, false) + } else { + IntegerTrait::::new(mag, sign) + } +} +// impl I128Store of Store { +// fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult { +// Result::Ok( +// Store::::read(address_domain, base)?.try_into().expect('I128Store - non i128') +// ) +// } +// // fn write(address_domain: u32, base: StorageBaseAddress, value: i128) -> SyscallResult<()> { +// Store::::write(address_domain, base, value.into()) +// } +// // fn read_at_offset( +// address_domain: u32, base: StorageBaseAddress, offset: u8 +// ) -> SyscallResult { +// Result::Ok( +// Store::::read_at_offset(address_domain, base, offset)? +// .try_into() +// .expect('I128Store - non i128') +// ) +// } +// // fn write_at_offset( +// address_domain: u32, base: StorageBaseAddress, offset: u8, value: i128 +// ) -> SyscallResult<()> { +// Store::::write_at_offset(address_domain, base, offset, value.into()) +// } +// // fn size() -> u8 { +// 1_u8 +// } +// } + +// impl I128Serde of Serde { +// fn serialize(self: @i128, ref output: Array) { +// output.append((*self).into()); +// } +// fn deserialize(ref serialized: Span) -> Option { +// let felt_val = *(serialized.pop_front().expect('i128 deserialize')); +// let i128_val = felt_val.try_into().expect('i128 Overflow'); +// Option::Some(i128_val) +// } +// } + + diff --git a/src/utils/i128_test_storage_contract.cairo b/src/utils/i128_test_storage_contract.cairo new file mode 100644 index 00000000..52b25028 --- /dev/null +++ b/src/utils/i128_test_storage_contract.cairo @@ -0,0 +1,30 @@ +// use satoru::utils::i128::I128Serde; + +// #[starknet::interface] +// trait ITestI128Storage { +// fn set_i128(ref self: TContractState, new_val: i128); +// fn get_i128(self: @TContractState) -> i128; +// } + +// #[starknet::contract] +// mod test_i128_storage_contract { +// use satoru::utils::i128::{I128Store, I128Serde}; +// use super::ITestI128Storage; + +// #[storage] +// struct Storage { +// my_i128: i128 +// } + +// #[external(v0)] +// impl Public of ITestI128Storage { +// fn set_i128(ref self: ContractState, new_val: i128) { +// self.my_i128.write(new_val); +// } +// fn get_i128(self: @ContractState) -> i128 { +// self.my_i128.read() +// } +// } +// } + + diff --git a/src/utils/i256.cairo b/src/utils/i256.cairo new file mode 100644 index 00000000..8eed828a --- /dev/null +++ b/src/utils/i256.cairo @@ -0,0 +1,724 @@ +// This code uses a portion of the code from the YAS project under the Apache 2.0 license. +// Here is the link to the original project: +// https://github.com/lambdaclass/yet-another-swap + +// The original source code is subject to the Apache 2.0 license, the terms of which can be found here: +// http://www.apache.org/licenses/LICENSE-2.0 + +/// Trait +/// +/// new - Constructs a new `signed_integer +/// div_rem - Computes `signed_integer` division and modulus simultaneously +/// abs - Computes the absolute value of the given `signed_integer` +/// max - Returns the maximum between two `signed_integer` +/// min - Returns the minimum between two `signed_integer` +trait IntegerTrait { + /// # IntegerTrait::new + /// + /// ```rust + /// fn new(mag: U, sign: bool) -> T; + /// ``` + /// + /// Returns a new signed integer. + /// + /// ## Args + /// + /// * `mag`(`U`) - The magnitude of the integer. + /// * `sign`(`bool`) - The sign of the integer, where `true` represents a negative number. + /// + /// > _`` generic type depends on the uint type (u8, u16, u32, u64, u256)._ + /// + /// ## Panics + /// + /// Panics if `mag` is out of range. + /// + /// ## Returns + /// + /// A new signed integer. + /// + /// ## Examples + /// + /// ```rust + /// fn new_i8_example() -> i8 { + /// IntegerTrait::::new(42_u8, true) + /// } + /// >>> {mag: 42, sign: true} // = -42 + /// ``` + /// + /// ```rust + /// fn panic_i8_example() -> i8 { + /// IntegerTrait::::new(129_u8, true) + /// } + /// >>> panics with "int: out of range" + /// ``` + /// + fn new(mag: U, sign: bool) -> T; + /// # int.div_rem + /// + /// ```rust + /// fn div_rem(self: T, other: T) -> (T, T); + /// ``` + /// + /// Computes signed\_integer division and modulus simultaneously + /// + /// ## Args + /// + /// * `self`(`T`) - The dividend + /// * `other`(`T`) - The divisor + /// + /// ## Panics + /// + /// Panics if the divisor is zero. + /// + /// ## Returns + /// + /// A tuple of signed integer ``, containing the quotient and the remainder of the division. + /// + /// ## Examples + /// + /// ```rust + /// fn div_rem_example() -> (i32, i32) { + /// // We instantiate signed integers here. + /// let a = IntegerTrait::::new(13, false); + /// let b = IntegerTrait::::new(5, false); + /// + /// // We can call `div_rem` function as follows. + /// a.div_rem(b) + /// } + /// >>> ({mag: 2, sign: false}, {mag: 3, sign: false}) // = (2, 3) + /// ``` + /// + fn div_rem(self: T, other: T) -> (T, T); + /// # int.abs + /// + /// ```rust + /// fn abs(self: T) -> T; + /// ``` + /// + /// Computes the absolute value of a signed\_integer. + /// + /// ## Args + /// + /// `self`(`T`) - The signed integer to which the absolute value is applied + /// + /// ## Returns + /// + /// A signed integer ``, representing the absolute value of `self` . + /// + /// ## Examples + /// + /// ```rust + /// fn abs_example() -> i32 { + /// // We instantiate signed integers here. + /// let int = IntegerTrait::::new(42, true); + /// + /// // We can call `abs` function as follows. + /// a.abs() + /// } + /// >>> {mag: 42, sign: false} // = 42 + /// ``` + /// + fn abs(self: T) -> T; + /// # int.max + /// + /// ```rust + /// fn max(self: T, other: T) -> T; + /// ``` + /// + /// Returns the maximum between two signed\_integer. + /// + /// ## Args + /// + /// *`self`(`T`) - The first signed integer to compare. + /// * `other`(`T`) - The second signed integer to compare. + /// + /// ## Returns + /// + /// A signed integer ``, The maximum between `self` and `other`. + /// + /// ## Examples + /// + /// ```rust + /// fn max_example() -> i32 { + /// // We instantiate signed integer here. + /// let a = IntegerTrait::::new(42, true); + /// let b = IntegerTrait::::new(13, false); + /// + /// // We can call `max` function as follows. + /// a.max(b) + /// } + /// >>> {mag: 13, sign: false} // as 13 > -42 + /// ``` + /// + fn max(self: T, other: T) -> T; + /// # int.min + /// + /// ```rust + /// fn min(self: T, other: T) -> T; + /// ``` + /// + /// Returns the minimum between two signed\_integer. + /// + /// ## Args + /// + /// `self`(`T`) - The first signed integer to compare. + /// `other`(`T`) - The second signed integer to compare. + /// + /// ## Returns + /// + /// A signed integer ``, The minimum between `self` and `other`. + /// + /// ## Examples + /// + /// + /// ```rust + /// fn min_example() -> i32 { + /// // We instantiate signed integer here. + /// let a = IntegerTrait::::new(42, true); + /// let b = IntegerTrait::::new(13, false); + /// + /// // We can call `max` function as follows. + /// a.min(b) + /// } + /// >>> {mag: 42, sign: true} // as -42 < 13 + /// ``` + /// + fn min(self: T, other: T) -> T; +} + +use integer::{BoundedInt, u256_wide_mul}; +use satoru::utils::felt_math::{felt_abs, felt_sign}; +// ====================== INT 256 ====================== + +// i256 represents a 256-bit integer. +// The mag field holds the absolute value of the integer. +// The sign field is true for negative integers, and false for non-negative integers. +#[derive(Serde, Copy, Drop, Hash, starknet::Store)] +struct i256 { + mag: u256, + sign: bool, +} + +impl i256Impl of IntegerTrait { + fn new(mag: u256, sign: bool) -> i256 { + i256_new(mag, sign) + } + + fn div_rem(self: i256, other: i256) -> (i256, i256) { + i256_div_rem(self, other) + } + + fn abs(self: i256) -> i256 { + i256_abs(self) + } + + fn max(self: i256, other: i256) -> i256 { + i256_max(self, other) + } + + fn min(self: i256, other: i256) -> i256 { + i256_min(self, other) + } +} + +impl I256Default of Default { + fn default() -> i256 { + Zeroable::zero() + } +} + +// Implements the Add trait for i256. +impl i256Add of Add { + fn add(lhs: i256, rhs: i256) -> i256 { + i256_add(lhs, rhs) + } +} + +// Implements the AddEq trait for i256. +impl i256AddEq of AddEq { + #[inline(always)] + fn add_eq(ref self: i256, other: i256) { + self = Add::add(self, other); + } +} + +// Implements the Sub trait for i256. +impl i256Sub of Sub { + fn sub(lhs: i256, rhs: i256) -> i256 { + i256_sub(lhs, rhs) + } +} + +// Implements the SubEq trait for i256. +impl i256SubEq of SubEq { + #[inline(always)] + fn sub_eq(ref self: i256, other: i256) { + self = Sub::sub(self, other); + } +} + +// Implements the Mul trait for i256. +impl i256Mul of Mul { + fn mul(lhs: i256, rhs: i256) -> i256 { + i256_mul(lhs, rhs) + } +} + +// Implements the MulEq trait for i256. +impl i256MulEq of MulEq { + #[inline(always)] + fn mul_eq(ref self: i256, other: i256) { + self = Mul::mul(self, other); + } +} + +// Implements the Div trait for i256. +impl i256Div of Div { + fn div(lhs: i256, rhs: i256) -> i256 { + i256_div(lhs, rhs) + } +} + +// Implements the DivEq trait for i256. +impl i256DivEq of DivEq { + #[inline(always)] + fn div_eq(ref self: i256, other: i256) { + self = Div::div(self, other); + } +} + +// Implements the Rem trait for i256. +impl i256Rem of Rem { + fn rem(lhs: i256, rhs: i256) -> i256 { + i256_rem(lhs, rhs) + } +} + +// Implements the RemEq trait for i256. +impl i256RemEq of RemEq { + #[inline(always)] + fn rem_eq(ref self: i256, other: i256) { + self = Rem::rem(self, other); + } +} + +// Implements the PartialEq trait for i256. +impl i256PartialEq of PartialEq { + fn eq(lhs: @i256, rhs: @i256) -> bool { + i256_eq(*lhs, *rhs) + } + + fn ne(lhs: @i256, rhs: @i256) -> bool { + i256_ne(*lhs, *rhs) + } +} + +// Implements the PartialOrd trait for i256. +impl i256PartialOrd of PartialOrd { + fn le(lhs: i256, rhs: i256) -> bool { + i256_le(lhs, rhs) + } + fn ge(lhs: i256, rhs: i256) -> bool { + i256_ge(lhs, rhs) + } + + fn lt(lhs: i256, rhs: i256) -> bool { + i256_lt(lhs, rhs) + } + fn gt(lhs: i256, rhs: i256) -> bool { + i256_gt(lhs, rhs) + } +} + +// Implements the Neg trait for i256. +impl i256Neg of Neg { + fn neg(a: i256) -> i256 { + i256_neg(a) + } +} + +impl i256TryIntou256 of TryInto { + fn try_into(self: i256) -> Option { + assert(self.sign == false, 'The sign must be positive'); + Option::Some(self.mag) + } +} + +impl u256Intoi256 of Into { + fn into(self: u256) -> i256 { + IntegerTrait::::new(self, false) + } +} + +impl I256TryIntoFelt252 of TryInto { + fn try_into(self: i256) -> Option { + let val: Option = self.mag.try_into(); + match val { + Option::Some(val) => Option::Some(val * if self.sign { + -1 + } else { + 1 + }), + Option::None(()) => Option::None(()) + } + } +} + +impl I256IntoFelt252 of Into { + fn into(self: i256) -> felt252 { + let mag_felt: felt252 = self.mag.try_into().unwrap(); + if self.sign { + mag_felt * -1 + } else { + mag_felt + } + } +} + +impl Felt252IntoI256 of Into { + fn into(self: felt252) -> i256 { + let mag = felt_abs(self).into(); + IntegerTrait::::new(mag, felt_sign(self)) + } +} + +impl i256Zeroable of Zeroable { + fn zero() -> i256 { + IntegerTrait::::new(0, false) + } + #[inline(always)] + fn is_zero(self: i256) -> bool { + self == i256Zeroable::zero() + } + #[inline(always)] + fn is_non_zero(self: i256) -> bool { + self != i256Zeroable::zero() + } +} + +// Checks if the given i256 integer is zero and has the correct sign. +// # Arguments +// * `x` - The i256 integer to check. +// # Panics +// Panics if `x` is zero and has a sign that is not false. +fn i256_check_sign_zero(x: i256) { + if x.mag == 0_u256 { + assert(x.sign == false, 'sign of 0 must be false'); + } +} + +/// Cf: IntegerTrait::new docstring +fn i256_new(mag: u256, sign: bool) -> i256 { + if sign == true { + assert(mag <= BoundedInt::::max(), 'i256 Overflow'); + } else { + assert(mag <= (BoundedInt::::max() - 1), 'i256 Overflow'); + } + i256 { mag, sign } +} + +// Adds two i256 integers. +// # Arguments +// * `a` - The first i256 to add. +// * `b` - The second i256 to add. +// # Returns +// * `i256` - The sum of `a` and `b`. +fn i256_add(a: i256, b: i256) -> i256 { + i256_check_sign_zero(a); + i256_check_sign_zero(b); + + // If both integers have the same sign, + // the sum of their absolute values can be returned. + if a.sign == b.sign { + let sum = a.mag + b.mag; + if (sum == 0_u256) { + return IntegerTrait::new(sum, false); + } + return ensure_non_negative_zero(sum, a.sign); + } else { + // If the integers have different signs, + // the larger absolute value is subtracted from the smaller one. + let (larger, smaller) = if a.mag >= b.mag { + (a, b) + } else { + (b, a) + }; + let difference = larger.mag - smaller.mag; + + if (difference == 0_u256) { + return IntegerTrait::new(difference, false); + } + return ensure_non_negative_zero(difference, larger.sign); + } +} + +// Subtracts two i256 integers. +// # Arguments +// * `a` - The first i256 to subtract. +// * `b` - The second i256 to subtract. +// # Returns +// * `i256` - The difference of `a` and `b`. +fn i256_sub(a: i256, b: i256) -> i256 { + i256_check_sign_zero(a); + i256_check_sign_zero(b); + + if a.sign == b.sign { + if a.mag > b.mag { + return ensure_non_negative_zero(a.mag - b.mag, a.sign); + } else if a.mag < b.mag { + return ensure_non_negative_zero(b.mag - a.mag, !a.sign); + } else { + return ensure_non_negative_zero(0, false); + } + } else if a.sign { + return ensure_non_negative_zero(a.mag + b.mag, true); + } else { + return ensure_non_negative_zero(a.mag + b.mag, false); + } +} + +// Multiplies two i256 integers. +// +// # Arguments +// +// * `a` - The first i256 to multiply. +// * `b` - The second i256 to multiply. +// +// # Returns +// +// * `i256` - The product of `a` and `b`. +fn i256_mul(a: i256, b: i256) -> i256 { + i256_check_sign_zero(a); + i256_check_sign_zero(b); + + // The sign of the product is the XOR of the signs of the operands. + let sign = a.sign ^ b.sign; + // The product is the product of the absolute values of the operands. + let mag_512 = u256_wide_mul(a.mag, b.mag); + assert(mag_512.limb2 == 0 && mag_512.limb3 == 0, 'mul i256 overflow'); + + let result = u256 { low: mag_512.limb0, high: mag_512.limb1 }; + + if (result == 0) { + return IntegerTrait::new(result, false); + } + + return ensure_non_negative_zero(result, sign); +} + +// Divides the first i256 by the second i256. +// # Arguments +// * `a` - The i256 dividend. +// * `b` - The i256 divisor. +// # Returns +// * `i256` - The quotient of `a` and `b`. +fn i256_div(a: i256, b: i256) -> i256 { + i256_check_sign_zero(a); + // Check that the divisor is not zero. + assert(b.mag != 0_u256, 'b can not be 0'); + + // The sign of the quotient is the XOR of the signs of the operands. + let sign = a.sign ^ b.sign; + + if (sign == false) { + // If the operands are positive, the quotient is simply their absolute value quotient. + return ensure_non_negative_zero(a.mag / b.mag, sign); + } + + // If the operands have different signs, rounding is necessary. + // First, check if the quotient is an integer. + if (a.mag % b.mag == 0) { + let quotient = a.mag / b.mag; + if (quotient == 0) { + return IntegerTrait::new(quotient, false); + } + return ensure_non_negative_zero(quotient, sign); + } + + // If the quotient is not an integer, multiply the dividend by 10 to move the decimal point over. + let quotient = (a.mag * 10) / b.mag; + let last_digit = quotient % 10; + + if (quotient == 0) { + return IntegerTrait::new(quotient, false); + } + + return ensure_non_negative_zero(quotient / 10_u256, sign); +} + +// Calculates the remainder of the division of a first i256 by a second i256. +// # Arguments +// * `a` - The i256 dividend. +// * `b` - The i256 divisor. +// # Returns +// * `i256` - The remainder of dividing `a` by `b`. +fn i256_rem(a: i256, b: i256) -> i256 { + i256_check_sign_zero(a); + // Check that the divisor is not zero. + assert(b.mag != 0_u256, 'b can not be 0'); + + return IntegerTrait::new((a - (b * (a / b))).mag, a.sign); +} + +/// Cf: IntegerTrait::div_rem docstring +fn i256_div_rem(a: i256, b: i256) -> (i256, i256) { + let quotient = i256_div(a, b); + let remainder = i256_rem(a, b); + + return (quotient, remainder); +} + +// Compares two i256 integers for equality. +// # Arguments +// * `a` - The first i256 integer to compare. +// * `b` - The second i256 integer to compare. +// # Returns +// * `bool` - `true` if the two integers are equal, `false` otherwise. +fn i256_eq(a: i256, b: i256) -> bool { + // Check if the two integers have the same sign and the same absolute value. + if a.sign == b.sign && a.mag == b.mag { + return true; + } + + return false; +} + +// Compares two i256 integers for inequality. +// # Arguments +// * `a` - The first i256 integer to compare. +// * `b` - The second i256 integer to compare. +// # Returns +// * `bool` - `true` if the two integers are not equal, `false` otherwise. +fn i256_ne(a: i256, b: i256) -> bool { + // The result is the inverse of the equal function. + return !i256_eq(a, b); +} + +// Compares two i256 integers for greater than. +// # Arguments +// * `a` - The first i256 integer to compare. +// * `b` - The second i256 integer to compare. +// # Returns +// * `bool` - `true` if `a` is greater than `b`, `false` otherwise. +fn i256_gt(a: i256, b: i256) -> bool { + // Check if `a` is negative and `b` is positive. + if (a.sign & !b.sign) { + return false; + } + // Check if `a` is positive and `b` is negative. + if (!a.sign & b.sign) { + return true; + } + // If `a` and `b` have the same sign, compare their absolute values. + if (a.sign & b.sign) { + return a.mag < b.mag; + } else { + return a.mag > b.mag; + } +} + +// Determines whether the first i256 is less than the second i256. +// # Arguments +// * `a` - The i256 to compare against the second i256. +// * `b` - The i256 to compare against the first i256. +// # Returns +// * `bool` - `true` if `a` is less than `b`, `false` otherwise. +fn i256_lt(a: i256, b: i256) -> bool { + if (a.sign != b.sign) { + return a.sign; + } else { + return a.mag != b.mag && (a.mag < b.mag) ^ a.sign; + } +} + +// Checks if the first i256 integer is less than or equal to the second. +// # Arguments +// * `a` - The first i256 integer to compare. +// * `b` - The second i256 integer to compare. +// # Returns +// * `bool` - `true` if `a` is less than or equal to `b`, `false` otherwise. +fn i256_le(a: i256, b: i256) -> bool { + if (a == b || i256_lt(a, b) == true) { + return true; + } else { + return false; + } +} + +// Checks if the first i256 integer is greater than or equal to the second. +// # Arguments +// * `a` - The first i256 integer to compare. +// * `b` - The second i256 integer to compare. +// # Returns +// * `bool` - `true` if `a` is greater than or equal to `b`, `false` otherwise. +fn i256_ge(a: i256, b: i256) -> bool { + if (a == b || i256_gt(a, b) == true) { + return true; + } else { + return false; + } +} + +// Negates the given i256 integer. +// # Arguments +// * `x` - The i256 integer to negate. +// # Returns +// * `i256` - The negation of `x`. +fn i256_neg(x: i256) -> i256 { + // The negation of an integer is obtained by flipping its sign. + return ensure_non_negative_zero(x.mag, !x.sign); +} + +/// Cf: IntegerTrait::abs docstring +fn i256_abs(x: i256) -> i256 { + return IntegerTrait::new(x.mag, false); +} + +/// Cf: IntegerTrait::max docstring +fn i256_max(a: i256, b: i256) -> i256 { + if (a > b) { + return a; + } else { + return b; + } +} + +/// Cf: IntegerTrait::new docstring +fn i256_min(a: i256, b: i256) -> i256 { + if (a < b) { + return a; + } else { + return b; + } +} + +fn ensure_non_negative_zero(mag: u256, sign: bool) -> i256 { + if mag == 0 { + IntegerTrait::::new(mag, false) + } else { + IntegerTrait::::new(mag, sign) + } +} + +fn two_complement_if_nec(x: i256) -> i256 { + let mag = if x.sign { + ~(x.mag) + 1 + } else { + x.mag + }; + + i256 { mag: mag, sign: x.sign } +} + +fn bitwise_or(x: i256, y: i256) -> i256 { + let x = two_complement_if_nec(x); + let y = two_complement_if_nec(y); + let sign = x.sign || y.sign; + let mag = if sign { + ~(x.mag | y.mag) + 1 + } else { + x.mag | y.mag + }; + + IntegerTrait::::new(mag, sign) +} diff --git a/src/utils/i256_test_storage_contract.cairo b/src/utils/i256_test_storage_contract.cairo new file mode 100644 index 00000000..3b05e07a --- /dev/null +++ b/src/utils/i256_test_storage_contract.cairo @@ -0,0 +1,30 @@ +// use satoru::utils::i256::I256Serde; + +// #[starknet::interface] +// trait ITestI256Storage { +// fn set_i256(ref self: TContractState, new_val: i256); +// fn get_i256(self: @TContractState) -> i256; +// } + +// #[starknet::contract] +// mod test_i256_storage_contract { +// use satoru::utils::i256::{I256Store, I256Serde}; +// use super::ITestI256Storage; + +// #[storage] +// struct Storage { +// my_i256: i256 +// } + +// #[external(v0)] +// impl Public of ITestI256Storage { +// fn set_i256(ref self: ContractState, new_val: i256) { +// self.my_i256.write(new_val); +// } +// fn get_i256(self: @ContractState) -> i256 { +// self.my_i256.read() +// } +// } +// } + + diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 80664a66..63c8e31d 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -4,20 +4,20 @@ // Core lib imports. use alexandria_math::pow; use integer::{ - i128_to_felt252, u128_to_felt252, u256_wide_mul, u512_safe_div_rem_by_u256, BoundedU256, - u256_try_as_non_zero + u256_wide_mul, u512_safe_div_rem_by_u256, BoundedU256, u256_try_as_non_zero, U256TryIntoFelt252 }; +use satoru::utils::i256::{i256, i256_neg}; use core::traits::TryInto; use core::option::Option; use satoru::utils::calc::{roundup_division, roundup_magnitude_division}; -const FLOAT_PRECISION: u128 = 1_000_000_000_000_000_000_000_000_000_000; // 10^30 -const FLOAT_PRECISION_SQRT: u128 = 1_000_000_000_000_000; // 10^15 +const FLOAT_PRECISION: u256 = 100_000_000_000_000_000_000; // 10^20 +const FLOAT_PRECISION_SQRT: u256 = 10_000_000_000; // 10^10 -const WEI_PRECISION: u128 = 1_000_000_000_000_000_000; // 10^18 -const BASIS_POINTS_DIVISOR: u128 = 10000; +const WEI_PRECISION: u256 = 1_000_000_000_000_000_000; // 10^18 +const BASIS_POINTS_DIVISOR: u256 = 10000; -const FLOAT_TO_WEI_DIVISOR: u128 = 1_000_000_000_000; // 10^12 +const FLOAT_TO_WEI_DIVISOR: u256 = 1_000_000_000_000; // 10^12 /// Applies the given factor to the given value and returns the result. /// # Arguments @@ -25,7 +25,7 @@ const FLOAT_TO_WEI_DIVISOR: u128 = 1_000_000_000_000; // 10^12 /// * `factor` - The factor to apply. /// # Returns /// The result of applying the factor to the value. -fn apply_factor_u128(value: u128, factor: u128) -> u128 { +fn apply_factor_u256(value: u256, factor: u256) -> u256 { return mul_div(value, factor, FLOAT_PRECISION); } @@ -35,7 +35,7 @@ fn apply_factor_u128(value: u128, factor: u128) -> u128 { /// * `factor` - The factor to apply. /// # Returns /// The result of applying the factor to the value. -fn apply_factor_i128(value: u128, factor: i128) -> i128 { +fn apply_factor_i256(value: u256, factor: i256) -> i256 { return mul_div_inum(value, factor, FLOAT_PRECISION); } @@ -45,7 +45,7 @@ fn apply_factor_i128(value: u128, factor: i128) -> i128 { /// * `factor` - The factor to apply. /// # Returns /// The result of applying the factor to the value. -fn apply_factor_roundup_magnitude(value: u128, factor: i128, roundup_magnitude: bool) -> i128 { +fn apply_factor_roundup_magnitude(value: u256, factor: i256, roundup_magnitude: bool) -> i256 { return mul_div_inum_roundup(value, factor, FLOAT_PRECISION, roundup_magnitude); } @@ -54,16 +54,13 @@ fn apply_factor_roundup_magnitude(value: u128, factor: i128, roundup_magnitude: /// * `value` - The value muldiv is applied to. /// * `numerator` - The numerator that multiplies value. /// * `divisor` - The denominator that divides value. -fn mul_div(value: u128, numerator: u128, denominator: u128) -> u128 { - let value = u256 { low: value, high: 0 }; - let numerator = u256 { low: numerator, high: 0 }; - let denominator = u256 { low: denominator, high: 0 }; +fn mul_div(value: u256, numerator: u256, denominator: u256) -> u256 { let product = u256_wide_mul(value, numerator); let (q, _) = u512_safe_div_rem_by_u256( product, u256_try_as_non_zero(denominator).expect('MulDivByZero') ); - assert(q.limb1 == 0 && q.limb2 == 0 && q.limb3 == 0, 'MulDivOverflow'); - q.limb0 + assert(q.limb2 == 0 && q.limb3 == 0, 'MulDivOverflow'); + u256 { low: q.limb0, high: q.limb1 } } /// Apply multiplication then division to value. @@ -71,7 +68,7 @@ fn mul_div(value: u128, numerator: u128, denominator: u128) -> u128 { /// * `value` - The integer value muldiv is applied to. /// * `numerator` - The numerator that multiplies value. /// * `divisor` - The denominator that divides value. -fn mul_div_ival(value: i128, numerator: u128, denominator: u128) -> i128 { +fn mul_div_ival(value: i256, numerator: u256, denominator: u256) -> i256 { return mul_div_inum(numerator, value, denominator); } @@ -80,21 +77,21 @@ fn mul_div_ival(value: i128, numerator: u128, denominator: u128) -> i128 { /// * `value` - The value muldiv is applied to. /// * `numerator` - The integer numerator that multiplies value. /// * `divisor` - The denominator that divides value. -fn mul_div_inum(value: u128, numerator: i128, denominator: u128) -> i128 { - let numerator_abs = if numerator < 0 { - -numerator +fn mul_div_inum(value: u256, numerator: i256, denominator: u256) -> i256 { + let numerator_abs = if numerator < Zeroable::zero() { + i256_neg(numerator) } else { numerator }; - let felt252_numerator: felt252 = i128_to_felt252(numerator_abs); - let u128_numerator = felt252_numerator.try_into().unwrap(); - let result: u128 = mul_div(value, u128_numerator, denominator); - let felt252_result: felt252 = u128_to_felt252(result); - let i128_result: i128 = felt252_result.try_into().unwrap(); - if numerator > 0 { - return i128_result; + let felt252_numerator: felt252 = numerator_abs.try_into().expect('i256 into felt failed'); + let u256_numerator = felt252_numerator.into(); + let result: u256 = mul_div(value, u256_numerator, denominator); + let felt252_result: felt252 = result.try_into().expect('u256 into felt failed'); + let i256_result: i256 = felt252_result.into(); + if numerator > Zeroable::zero() { + return i256_result; } else { - return -i128_result; + return i256_neg(i256_result); } } @@ -104,22 +101,22 @@ fn mul_div_inum(value: u128, numerator: i128, denominator: u128) -> i128 { /// * `numerator` - The integer numerator that multiplies value. /// * `divisor` - The denominator that divides value. fn mul_div_inum_roundup( - value: u128, numerator: i128, denominator: u128, roundup_magnitude: bool -) -> i128 { - let numerator_abs = if numerator < 0 { - -numerator + value: u256, numerator: i256, denominator: u256, roundup_magnitude: bool +) -> i256 { + let numerator_abs = if numerator < Zeroable::zero() { + i256_neg(numerator) } else { numerator }; - let felt252_numerator: felt252 = i128_to_felt252(numerator_abs); - let u128_numerator = felt252_numerator.try_into().unwrap(); - let result: u128 = mul_div_roundup(value, u128_numerator, denominator, roundup_magnitude); - let felt252_result: felt252 = u128_to_felt252(result); - let i128_result: i128 = felt252_result.try_into().unwrap(); - if numerator > 0 { - return i128_result; + let felt252_numerator: felt252 = numerator_abs.try_into().expect('i256 into felt failed'); + let u256_numerator = felt252_numerator.into(); + let result: u256 = mul_div_roundup(value, u256_numerator, denominator, roundup_magnitude); + let felt252_result: felt252 = result.try_into().expect('u256 into felt failed'); + let i256_result: i256 = felt252_result.into(); + if numerator > Zeroable::zero() { + return i256_result; } else { - return -i128_result; + return i256_neg(i256_result); } } @@ -129,25 +126,19 @@ fn mul_div_inum_roundup( /// * `numerator` - The numerator that multiplies value. /// * `divisor` - The denominator that divides value. fn mul_div_roundup( - value: u128, numerator: u128, denominator: u128, roundup_magnitude: bool -) -> u128 { - let value = u256 { low: value, high: 0 }; - let numerator = u256 { low: numerator, high: 0 }; - let denominator = u256 { low: denominator, high: 0 }; + value: u256, numerator: u256, denominator: u256, roundup_magnitude: bool +) -> u256 { let product = u256_wide_mul(value, numerator); let (q, r) = u512_safe_div_rem_by_u256( product, u256_try_as_non_zero(denominator).expect('MulDivByZero') ); if roundup_magnitude && r > 0 { let result = u256 { low: q.limb0, high: q.limb1 }; - assert( - result != BoundedU256::max() && q.limb1 == 0 && q.limb2 == 0 && q.limb3 == 0, - 'MulDivOverflow' - ); - q.limb0 + 1 + assert(result != BoundedU256::max() && q.limb2 == 0 && q.limb3 == 0, 'MulDivOverflow'); + u256 { low: q.limb0, high: q.limb1 } + 1 } else { - assert(q.limb1 == 0 && q.limb2 == 0 && q.limb3 == 0, 'MulDivOverflow'); - q.limb0 + assert(q.limb2 == 0 && q.limb3 == 0, 'MulDivOverflow'); + u256 { low: q.limb0, high: q.limb1 } } } @@ -155,28 +146,419 @@ fn mul_div_roundup( /// # Arguments /// * `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 +fn apply_exponent_factor(float_value: u256, exponent_factor: u256) -> u256 { + 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 wei_u256: u256 = wei_result.try_into().unwrap(); + let float_result = wei_to_float(wei_u256); + float_result +//0 +} + +//use starknet::cairo::common::cairo_builtins::bitwise_and; +//use starknet::{*}; +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'); + } + 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. + 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) { + if (x & 0x8000000000000000 > 0) { + result = BitShift::shr(result * 0x16A09E667F3BCC909, 64); + } + if (x & 0x4000000000000000 > 0) { + result = BitShift::shr(result * 0x1306FE0A31B7152DF, 64); + } + if (x & 0x2000000000000000 > 0) { + result = BitShift::shr(result * 0x1172B83C7D517ADCE, 64); + } + if (x & 0x1000000000000000 > 0) { + result = BitShift::shr(result * 0x10B5586CF9890F62A, 64); + } + if (x & 0x800000000000000 > 0) { + result = BitShift::shr(result * 0x1059B0D31585743AE, 64); + } + if (x & 0x400000000000000 > 0) { + result = BitShift::shr(result * 0x102C9A3E778060EE7, 64); + } + if (x & 0x200000000000000 > 0) { + result = BitShift::shr(result * 0x10163DA9FB33356D8, 64); + } + if (x & 0x100000000000000 > 0) { + result = BitShift::shr(result * 0x100B1AFA5ABCBED61, 64); + } + } + + if (x & 0xFF000000000000 > 0) { + if (x & 0x80000000000000 > 0) { + result = BitShift::shr(result * 0x10058C86DA1C09EA2, 64); + } + if (x & 0x40000000000000 > 0) { + result = BitShift::shr(result * 0x1002C605E2E8CEC50, 64); + } + if (x & 0x20000000000000 > 0) { + result = BitShift::shr(result * 0x100162F3904051FA1, 64); + } + if (x & 0x10000000000000 > 0) { + result = BitShift::shr(result * 0x1000B175EFFDC76BA, 64); + } + if (x & 0x8000000000000 > 0) { + result = BitShift::shr(result * 0x100058BA01FB9F96D, 64); + } + if (x & 0x4000000000000 > 0) { + result = BitShift::shr(result * 0x10002C5CC37DA9492, 64); + } + if (x & 0x2000000000000 > 0) { + result = BitShift::shr(result * 0x1000162E525EE0547, 64); + } + if (x & 0x1000000000000 > 0) { + result = BitShift::shr(result * 0x10000B17255775C04, 64); + } + } + + if (x & 0xFF0000000000 > 0) { + if (x & 0x800000000000 > 0) { + result = BitShift::shr(result * 0x1000058B91B5BC9AE, 64); + } + if (x & 0x400000000000 > 0) { + result = BitShift::shr(result * 0x100002C5C89D5EC6D, 64); + } + if (x & 0x200000000000 > 0) { + result = BitShift::shr(result * 0x10000162E43F4F831, 64); + } + if (x & 0x100000000000 > 0) { + result = BitShift::shr(result * 0x100000B1721BCFC9A, 64); + } + if (x & 0x80000000000 > 0) { + result = BitShift::shr(result * 0x10000058B90CF1E6E, 64); + } + if (x & 0x40000000000 > 0) { + result = BitShift::shr(result * 0x1000002C5C863B73F, 64); + } + if (x & 0x20000000000 > 0) { + result = BitShift::shr(result * 0x100000162E430E5A2, 64); + } + if (x & 0x10000000000 > 0) { + result = BitShift::shr(result * 0x1000000B172183551, 64); + } + } + + if (x & 0xFF00000000 > 0) { + if (x & 0x8000000000 > 0) { + result = BitShift::shr(result * 0x100000058B90C0B49, 64); + } + if (x & 0x4000000000 > 0) { + result = BitShift::shr(result * 0x10000002C5C8601CC, 64); + } + if (x & 0x2000000000 > 0) { + result = BitShift::shr(result * 0x1000000162E42FFF0, 64); + } + if (x & 0x1000000000 > 0) { + result = BitShift::shr(result * 0x10000000B17217FBB, 64); + } + if (x & 0x800000000 > 0) { + result = BitShift::shr(result * 0x1000000058B90BFCE, 64); + } + if (x & 0x400000000 > 0) { + result = BitShift::shr(result * 0x100000002C5C85FE3, 64); + } + if (x & 0x200000000 > 0) { + result = BitShift::shr(result * 0x10000000162E42FF1, 64); + } + if (x & 0x100000000 > 0) { + result = BitShift::shr(result * 0x100000000B17217F8, 64); + } + } + + if (x & 0xFF000000 > 0) { + if (x & 0x80000000 > 0) { + result = BitShift::shr(result * 0x10000000058B90BFC, 64); + } + if (x & 0x40000000 > 0) { + result = BitShift::shr(result * 0x1000000002C5C85FE, 64); + } + if (x & 0x20000000 > 0) { + result = BitShift::shr(result * 0x100000000162E42FF, 64); + } + if (x & 0x10000000 > 0) { + result = BitShift::shr(result * 0x1000000000B17217F, 64); + } + if (x & 0x8000000 > 0) { + result = BitShift::shr(result * 0x100000000058B90C0, 64); + } + if (x & 0x4000000 > 0) { + result = BitShift::shr(result * 0x10000000002C5C860, 64); + } + if (x & 0x2000000 > 0) { + result = BitShift::shr(result * 0x1000000000162E430, 64); + } + if (x & 0x1000000 > 0) { + result = BitShift::shr(result * 0x10000000000B17218, 64); + } + } + + if (x & 0xFF0000 > 0) { + if (x & 0x800000 > 0) { + result = BitShift::shr(result * 0x1000000000058B90C, 64); + } + if (x & 0x400000 > 0) { + result = BitShift::shr(result * 0x100000000002C5C86, 64); + } + if (x & 0x200000 > 0) { + result = BitShift::shr(result * 0x10000000000162E43, 64); + } + if (x & 0x100000 > 0) { + result = BitShift::shr(result * 0x100000000000B1721, 64); + } + if (x & 0x80000 > 0) { + result = BitShift::shr(result * 0x10000000000058B91, 64); + } + if (x & 0x40000 > 0) { + result = BitShift::shr(result * 0x1000000000002C5C8, 64); + } + if (x & 0x20000 > 0) { + result = BitShift::shr(result * 0x100000000000162E4, 64); + } + if (x & 0x10000 > 0) { + result = BitShift::shr(result * 0x1000000000000B172, 64); + } + } + + if (x & 0xFF00 > 0) { + if (x & 0x8000 > 0) { + result = BitShift::shr(result * 0x100000000000058B9, 64); + } + if (x & 0x4000 > 0) { + result = BitShift::shr(result * 0x10000000000002C5D, 64); + } + if (x & 0x2000 > 0) { + result = BitShift::shr(result * 0x1000000000000162E, 64); + } + if (x & 0x1000 > 0) { + result = BitShift::shr(result * 0x10000000000000B17, 64); + } + if (x & 0x800 > 0) { + result = BitShift::shr(result * 0x1000000000000058C, 64); + } + if (x & 0x400 > 0) { + result = BitShift::shr(result * 0x100000000000002C6, 64); + } + if (x & 0x200 > 0) { + result = BitShift::shr(result * 0x10000000000000163, 64); + } + if (x & 0x100 > 0) { + result = BitShift::shr(result * 0x100000000000000B1, 64); + } + } + + if (x & 0xFF > 0) { + if (x & 0x80 > 0) { + result = BitShift::shr(result * 0x10000000000000059, 64); + } + if (x & 0x40 > 0) { + result = BitShift::shr(result * 0x1000000000000002C, 64); + } + if (x & 0x20 > 0) { + result = BitShift::shr(result * 0x10000000000000016, 64); + } + if (x & 0x10 > 0) { + result = BitShift::shr(result * 0x1000000000000000B, 64); + } + if (x & 0x8 > 0) { + result = BitShift::shr(result * 0x10000000000000006, 64); + } + if (x & 0x4 > 0) { + result = BitShift::shr(result * 0x10000000000000003, 64); + } + if (x & 0x2 > 0) { + result = BitShift::shr(result * 0x10000000000000001, 64); + } + if (x & 0x1 > 0) { + result = BitShift::shr(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::shr(result, 191 - BitShift::shr(x, 64)); + 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) } +/// 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_decimal(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 / 1000000000000000000); + } // 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. /// * `divisor` - The divisor to compute the factor. /// # Returns /// The factor between value and divisor. -fn to_factor_roundup(value: u128, divisor: u128, roundup_magnitude: bool) -> u128 { +fn to_factor_roundup(value: u256, divisor: u256, roundup_magnitude: bool) -> u256 { if (value == 0) { return 0; } @@ -193,7 +575,7 @@ fn to_factor_roundup(value: u128, divisor: u128, roundup_magnitude: bool) -> u12 /// * `divisor` - The divisor to compute the factor. /// # Returns /// The factor between value and divisor. -fn to_factor(value: u128, divisor: u128) -> u128 { +fn to_factor(value: u256, divisor: u256) -> u256 { return to_factor_roundup(value, divisor, false); } @@ -203,21 +585,21 @@ fn to_factor(value: u128, divisor: u128) -> u128 { /// * `divisor` - The divisor to compute the factor. /// # Returns /// The factor between value and divisor. -fn to_factor_ival(value: i128, divisor: u128) -> i128 { - let value_abs = if value < 0 { - -value +fn to_factor_ival(value: i256, divisor: u256) -> i256 { + let value_abs = if value < Zeroable::zero() { + i256_neg(value) } else { value }; - let felt252_value: felt252 = i128_to_felt252(value_abs); - let u128_value = felt252_value.try_into().unwrap(); - let result: u128 = to_factor(u128_value, divisor); - let felt252_result: felt252 = u128_to_felt252(result); - let i128_result: i128 = felt252_result.try_into().unwrap(); - if value > 0 { - i128_result + let felt252_value: felt252 = value_abs.try_into().expect('i256 into felt failed'); + let u256_value = felt252_value.into(); + let result: u256 = to_factor(u256_value, divisor); + let felt252_result: felt252 = result.try_into().expect('u256 into felt252 failed'); + let i256_result: i256 = felt252_result.into(); + if value > Zeroable::zero() { + i256_result } else { - -i128_result + i256_neg(i256_result) } } @@ -226,7 +608,7 @@ fn to_factor_ival(value: i128, divisor: u128) -> i128 { /// * `value` - The value to convert. /// # Returns /// The wei value. -fn float_to_wei(value: u128) -> u128 { +fn float_to_wei(value: u256) -> u256 { return value / FLOAT_TO_WEI_DIVISOR; } @@ -235,7 +617,7 @@ fn float_to_wei(value: u128) -> u128 { /// * `value` - The value to convert /// # Returns /// The float value. -fn wei_to_float(value: u128) -> u128 { +fn wei_to_float(value: u256) -> u256 { return value * FLOAT_TO_WEI_DIVISOR; } @@ -244,6 +626,6 @@ fn wei_to_float(value: u128) -> u128 { /// * `value` - The value to convert /// # Returns /// The float value. -fn basis_points_to_float(basis_point: u128) -> u128 { +fn basis_points_to_float(basis_point: u256) -> u256 { return basis_point * FLOAT_PRECISION / BASIS_POINTS_DIVISOR; } diff --git a/src/utils/serializable_dict.cairo b/src/utils/serializable_dict.cairo new file mode 100644 index 00000000..fbd6c2e5 --- /dev/null +++ b/src/utils/serializable_dict.cairo @@ -0,0 +1,296 @@ +use core::serde::Serde; +use core::array::SpanTrait; +use core::array::ArrayTrait; +use core::traits::Into; +use starknet::{get_caller_address, ContractAddress, contract_address_const}; +use traits::Default; +use dict::{Felt252DictTrait, Felt252Dict}; +use nullable::{nullable_from_box, match_nullable, FromNullableResult, Nullable}; + +use alexandria_data_structures::array_ext::ArrayTraitExt; + +/// +/// Item +/// +/// Enumeration used to store a value in a SerializableDict. +/// It allows to store either a simple value (Single) or a +/// Span & comes with utilities functions. +/// +#[derive(Drop, Copy)] +enum Item { + Single: T, + Span: Span +} + +#[generate_trait] +impl ItemImpl of ItemTrait { + fn is_single(self: @Item) -> bool { + match self { + Item::Single(v) => true, + Item::Span(arr) => false + } + } + + fn is_span(self: @Item) -> bool { + !self.is_single() + } + + fn len(self: @Item) -> usize { + match self { + Item::Single(v) => 1, + Item::Span(s) => (*s).len() + } + } + + fn unwrap_single>(self: @Item) -> T { + match self { + Item::Single(v) => (*v), + Item::Span(arr) => panic_with_felt252('should be single') + } + } + + fn unwrap_span(self: @Item) -> Span { + match self { + Item::Single(v) => panic_with_felt252('should be a span'), + Item::Span(arr) => *arr + } + } +} + +impl ItemPartialEq, +PartialEq, +Drop> of PartialEq> { + fn eq(lhs: @Item, rhs: @Item) -> bool { + if lhs.is_single() && rhs.is_single() { + return lhs.unwrap_single() == rhs.unwrap_single(); + } else if lhs.is_span() && rhs.is_span() { + return lhs.unwrap_span() == rhs.unwrap_span(); + } + return false; + } + fn ne(lhs: @Item, rhs: @Item) -> bool { + if lhs.is_single() && rhs.is_single() { + return !(lhs.unwrap_single() == rhs.unwrap_single()); + } else if lhs.is_span() && rhs.is_span() { + return !(lhs.unwrap_span() == rhs.unwrap_span()); + } + return true; + } +} + +/// +/// SerializableFelt252Dict +/// +/// Wrapper around the Felt252Dict. +/// It behaves the same as a regular dict but has also a keys parameter +/// that keeps track of the keys registered. +/// This allows us to serialize & deserialize the struct, which is not +/// possible with a regular Felt252Dict. +/// The values are wrapped around an Item struct that allows to store +/// different types of data: a simple value or a span. +/// +#[derive(Default)] +struct SerializableFelt252Dict { + keys: Array, + values: Felt252Dict>> +} + +impl SerializableFelt252DictDestruct< + T, +Drop, +Felt252DictValue +> of Destruct> { + fn destruct(self: SerializableFelt252Dict) nopanic { + self.values.squash(); + self.keys.destruct(); + } +} + +trait SerializableFelt252DictTrait { + /// Creates a new SerializableFelt252Dict object. + fn new() -> SerializableFelt252Dict; + /// Adds an element. + fn insert_single(ref self: SerializableFelt252Dict, key: felt252, value: T); + /// Adds an array of elements. + fn insert_span(ref self: SerializableFelt252Dict, key: felt252, values: Span); + /// Gets an element. + fn get(ref self: SerializableFelt252Dict, key: felt252) -> Option>; + /// Checks if a key is in the dictionnary. + fn contains(self: @SerializableFelt252Dict, key: felt252) -> bool; + /// Number of keys in the dictionnary. + fn len(self: @SerializableFelt252Dict) -> usize; + /// Checks if a dictionnary is empty. + fn is_empty(self: @SerializableFelt252Dict) -> bool; + /// Serializes the dictionnary & return the result + fn serialize_into(ref self: SerializableFelt252Dict) -> Array; + /// Serializes the dictionnary into the provided output array + fn serialize(ref self: SerializableFelt252Dict, ref output: Array); + /// Deserializes the serialized array & return the dictionnary + fn deserialize(ref serialized: Span) -> Option>; +} + +impl SerializableFelt252DictTraitImpl< + T, + impl TDefault: Felt252DictValue, + impl TDrop: Drop, + impl TCopy: Copy, + impl FeltIntoT: Into, + impl TIntoFelt: Into, +> of SerializableFelt252DictTrait { + fn new() -> SerializableFelt252Dict { + SerializableFelt252Dict { keys: array![], values: Default::default() } + } + + fn insert_single(ref self: SerializableFelt252Dict, key: felt252, value: T) { + let value = Item::Single(value); + if !self.keys.contains(key) { + self.keys.append(key); + } + self.values.insert(key, nullable_from_box(BoxTrait::new(value))); + } + + fn insert_span(ref self: SerializableFelt252Dict, key: felt252, values: Span) { + let values = Item::Span(values); + if !self.keys.contains(key) { + self.keys.append(key); + } + self.values.insert(key, nullable_from_box(BoxTrait::new(values))); + } + + fn get(ref self: SerializableFelt252Dict, key: felt252) -> Option> { + match match_nullable(self.values.get(key)) { + FromNullableResult::Null(()) => Option::None, + FromNullableResult::NotNull(val) => Option::Some(val.unbox()), + } + } + + fn contains(self: @SerializableFelt252Dict, key: felt252) -> bool { + let mut keys: Span = self.keys.span(); + let mut contains_key: bool = false; + loop { + match keys.pop_front() { + Option::Some(value) => { if *value == key { + contains_key = true; + break; + } }, + Option::None => { break; }, + }; + }; + return contains_key; + } + + fn len(self: @SerializableFelt252Dict) -> usize { + self.keys.len() + } + + fn is_empty(self: @SerializableFelt252Dict) -> bool { + self.len() == 0 + } + + fn serialize_into(ref self: SerializableFelt252Dict) -> Array { + let mut serialized_data: Array = array![]; + self.serialize(ref serialized_data); + serialized_data + } + + // + // Serialization of an SerializableFelt252Dict + // + // An SerializableFelt252Dict is serialized as follow: + // [ KEY | NB_ELEMENTS | X | Y | ... | KEY | NB_ELEMENTS | X | ...] + // + // + // e.g. if we try to serialize this Dict: + // keys: [0, 1] + // values: { + // 0: 1, + // 1: [1, 2, 3] + // } + // + // will give: + // + // key: 0 key: 1 + // | ------ | ----------- | + // [0, 1, 1, 1, 3, 1, 2, 3] (Array) + // + fn serialize(ref self: SerializableFelt252Dict, ref output: Array) { + let mut keys: Span = self.keys.span(); + loop { + match keys.pop_front() { + Option::Some(key) => { + let value: Item = self.get(*key).expect('key should exist'); + match value { + Item::Single(v) => { + output.append(*key); // key + output.append(1); // len + output.append(v.into()); // value + }, + Item::Span(mut arr) => { + output.append(*key); // key + output.append(arr.len().into()); // len + loop { // append each values + match arr.pop_front() { + Option::Some(v) => { output.append((*v).into()); }, + Option::None => { break; } + }; + }; + }, + }; + }, + Option::None => { break; }, + }; + }; + } + + // + // Deserialization of an SerializableFelt252Dict + // + // An SerializableFelt252Dict is serialized as follow: + // [ KEY | NB_ELEMENTS | X | Y | ... | KEY | NB_ELEMENTS | X | ...] + // + fn deserialize(ref serialized: Span) -> Option> { + let mut d: SerializableFelt252Dict = SerializableFelt252Dict { + keys: array![], values: Default::default() + }; + loop { + // Try to retrive the next key + match serialized.pop_front() { + Option::Some(key) => { + // Key found; try to retrieved the size of elements + match serialized.pop_front() { + Option::Some(size) => { + // If only one element, insert it & quit + if ((*size) == 1) { + let value: T = match serialized.pop_front() { + Option::Some(value) => (*value).into(), + Option::None => panic_with_felt252('err getting value') + }; + let value: Item = Item::Single(value); + d.keys.append(*key); + d.values.insert(*key, nullable_from_box(BoxTrait::new(value))); + continue; + }; + // Else append all elements into an array ... + let mut arr_size: felt252 = *size; + let mut arr_values: Array = array![]; + loop { + if (arr_size) == 0 { + break; + }; + let value: T = match serialized.pop_front() { + Option::Some(value) => (*value).into(), + Option::None => panic_with_felt252('err getting value') + }; + arr_values.append(value); + arr_size -= 1; + }; + // ... & insert it + let values: Item = Item::Span(arr_values.span()); + d.keys.append(*key); + d.values.insert(*key, nullable_from_box(BoxTrait::new(values))); + }, + Option::None => panic_with_felt252('err getting size') + } + }, + Option::None => { break; }, + }; + }; + Option::Some(d) + } +} diff --git a/src/utils/span32.cairo b/src/utils/span32.cairo index 7c6c1e56..3fd9a467 100644 --- a/src/utils/span32.cairo +++ b/src/utils/span32.cairo @@ -15,7 +15,7 @@ struct Span32 { snapshot: Span } -fn serialize_array_helper, impl TDrop: Drop>( +fn serialize_array_helper, +Drop>( mut input: Span32, ref output: Array ) { match input.pop_front() { @@ -27,19 +27,17 @@ fn serialize_array_helper, impl TDrop: Drop>( } } -fn deserialize_array_helper, impl TDrop: Drop>( +fn deserialize_array_helper, +Drop>( ref serialized: Span, mut curr_output: Array, remaining: felt252 ) -> Option> { if remaining == 0 { return Option::Some(curr_output); } - curr_output.append(TSerde::deserialize(ref serialized)?); + curr_output.append(Serde::deserialize(ref serialized)?); deserialize_array_helper(ref serialized, curr_output, remaining - 1) } -impl Span32Serde< - T, impl TSerde: Serde, impl TDrop: Drop, impl TCopy: Copy -> of Serde> { +impl Span32Serde, +Drop, +Copy> of Serde> { fn serialize(self: @Span32, ref output: Array) { (*self).len().serialize(ref output); serialize_array_helper(*self, ref output) @@ -53,45 +51,37 @@ impl Span32Serde< } #[generate_trait] -impl Span32Impl> of Span32Trait { - #[inline(always)] +impl Span32Impl> of Span32Trait { fn pop_front(ref self: Span32) -> Option<@T> { self.snapshot.pop_front() } - #[inline(always)] fn pop_back(ref self: Span32) -> Option<@T> { self.snapshot.pop_back() } - #[inline(always)] fn get(self: Span32, index: usize) -> Option> { self.snapshot.get(index) } - #[inline(always)] fn at(self: Span32, index: usize) -> @T { self.snapshot.at(index) } - #[inline(always)] fn slice(self: Span32, start: usize, length: usize) -> Span32 { Span32 { snapshot: self.snapshot.slice(start, length) } } - #[inline(always)] fn len(self: Span32) -> usize { self.snapshot.len() } - #[inline(always)] fn is_empty(self: Span32) -> bool { self.snapshot.is_empty() } } -impl DefaultSpan32> of Default> { +impl DefaultSpan32> of Default> { fn default() -> Span32 { Array32Trait::::span32(@ArrayTrait::new()) } } impl Span32Index of IndexView, usize, @T> { - #[inline(always)] fn index(self: @Span32, index: usize) -> @T { self.snapshot.index(index) } @@ -102,7 +92,6 @@ trait Array32Trait { } impl Array32 of Array32Trait { - #[inline(always)] fn span32(self: @Array) -> Span32 { assert(self.len() <= 32, 'array too big'); Span32 { snapshot: Span { snapshot: self } } @@ -140,7 +129,7 @@ impl StoreContractAddressSpan32 of Store> { } let value = Store::::read_at_offset(address_domain, base, offset) - .unwrap(); + .expect('read_ad_offset failed'); arr.append(value); offset += Store::::size(); }; @@ -164,14 +153,12 @@ impl StoreContractAddressSpan32 of Store> { loop { match value.pop_front() { Option::Some(element) => { - Store::::write_at_offset( - address_domain, base, offset, *element - ); + Store::< + ContractAddress + >::write_at_offset(address_domain, base, offset, *element); offset += Store::::size(); }, - Option::None(_) => { - break Result::Ok(()); - } + Option::None(_) => { break Result::Ok(()); } }; } } diff --git a/src/utils/starknet_utils.cairo b/src/utils/starknet_utils.cairo index c13ce32b..61e10521 100644 --- a/src/utils/starknet_utils.cairo +++ b/src/utils/starknet_utils.cairo @@ -2,37 +2,35 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use integer::Felt252TryIntoU128; +use integer::Felt252IntoU256; use option::OptionTrait; use array::ArrayTrait; /// gasleft() mock implementation. /// Accepts Array because we don't know how many parameters we need in future. /// In mock way, the first element of array returned as result of gasleft. -#[inline(always)] -fn sn_gasleft(params: Array) -> u128 { +fn sn_gasleft(params: Array) -> u256 { if (params.len() == 0) { - return 0_u128; + return 0_u256; } let value: felt252 = *params.at(0); - let result: u128 = value.try_into().unwrap(); + let result: u256 = value.into(); result } /// tx.gasprice mock implementation. /// If its mock implementation, returns first element of parameter as result. -#[inline(always)] -fn sn_gasprice(params: Array) -> u128 { +fn sn_gasprice(params: Array) -> u256 { if (params.len() == 0) { - return 0_u128; + return 0_u256; } let value: felt252 = *params.at(0); - let result: u128 = value.try_into().unwrap(); + let result: u256 = value.into(); result } diff --git a/src/utils/store_arrays.cairo b/src/utils/store_arrays.cairo index b10467ec..ba374a1e 100644 --- a/src/utils/store_arrays.cairo +++ b/src/utils/store_arrays.cairo @@ -31,7 +31,8 @@ impl StoreContractAddressArray of Store> { let mut arr: Array = array![]; // Read the stored array's length. If the length is superior to 255, the read will fail. - let len: u8 = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let len: u8 = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); offset += 1; // Sequentially read all stored elements and append them to the array. @@ -42,7 +43,7 @@ impl StoreContractAddressArray of Store> { } let value = Store::::read_at_offset(address_domain, base, offset) - .unwrap(); + .expect('read_at_offset failed'); arr.append(value); offset += Store::::size(); }; @@ -67,12 +68,10 @@ impl StoreContractAddressArray of Store> { match value.pop_front() { Option::Some(element) => { Store::::write_at_offset(address_domain, base, offset, element) - .unwrap(); + .expect('write_at_offset failed'); offset += Store::::size(); }, - Option::None(_) => { - break Result::Ok(()); - } + Option::None(_) => { break Result::Ok(()); } }; } } @@ -99,7 +98,8 @@ impl StoreMarketArray of Store> { let mut arr: Array = array![]; // Read the stored array's length. If the length is superior to 255, the read will fail. - let len: u8 = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let len: u8 = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); offset += 1; // Sequentially read all stored elements and append them to the array. @@ -109,7 +109,8 @@ impl StoreMarketArray of Store> { break; } - let value = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let value = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); arr.append(value); offset += Store::::size(); }; @@ -131,12 +132,10 @@ impl StoreMarketArray of Store> { match value.pop_front() { Option::Some(element) => { Store::::write_at_offset(address_domain, base, offset, element) - .unwrap(); + .expect('write_at_offset failed'); offset += Store::::size(); }, - Option::None(_) => { - break Result::Ok(()); - } + Option::None(_) => { break Result::Ok(()); } }; } } @@ -163,7 +162,8 @@ impl StoreMarketSpan of Store> { let mut arr: Array = array![]; // Read the stored array's length. If the length is superior to 255, the read will fail. - let len: u8 = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let len: u8 = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); offset += 1; // Sequentially read all stored elements and append them to the array. @@ -173,7 +173,8 @@ impl StoreMarketSpan of Store> { break; } - let value = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let value = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); arr.append(value); offset += Store::::size(); }; @@ -195,12 +196,10 @@ impl StoreMarketSpan of Store> { match value.pop_front() { Option::Some(element) => { Store::::write_at_offset(address_domain, base, offset, *element) - .unwrap(); + .expect('write_at_offset failed'); offset += Store::::size(); }, - Option::None(_) => { - break Result::Ok(()); - } + Option::None(_) => { break Result::Ok(()); } }; } } @@ -227,7 +226,8 @@ impl StorePriceArray of Store> { let mut arr: Array = array![]; // Read the stored array's length. If the length is superior to 255, the read will fail. - let len: u8 = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let len: u8 = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); offset += 1; // Sequentially read all stored elements and append them to the array. @@ -237,7 +237,8 @@ impl StorePriceArray of Store> { break; } - let value = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let value = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); arr.append(value); offset += Store::::size(); }; @@ -258,12 +259,11 @@ impl StorePriceArray of Store> { loop { match value.pop_front() { Option::Some(element) => { - Store::::write_at_offset(address_domain, base, offset, element).unwrap(); + Store::::write_at_offset(address_domain, base, offset, element) + .expect('write_at_offset failed'); offset += Store::::size(); }, - Option::None(_) => { - break Result::Ok(()); - } + Option::None(_) => { break Result::Ok(()); } }; } } @@ -273,24 +273,25 @@ impl StorePriceArray of Store> { } } -impl StoreU128Array of Store> { - fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult> { - StoreU128Array::read_at_offset(address_domain, base, 0) +impl StoreU256Array of Store> { + fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult> { + StoreU256Array::read_at_offset(address_domain, base, 0) } fn write( - address_domain: u32, base: StorageBaseAddress, value: Array + address_domain: u32, base: StorageBaseAddress, value: Array ) -> SyscallResult<()> { - StoreU128Array::write_at_offset(address_domain, base, 0, value) + StoreU256Array::write_at_offset(address_domain, base, 0, value) } fn read_at_offset( address_domain: u32, base: StorageBaseAddress, mut offset: u8 - ) -> SyscallResult> { - let mut arr: Array = array![]; + ) -> SyscallResult> { + let mut arr: Array = array![]; // Read the stored array's length. If the length is superior to 255, the read will fail. - let len: u8 = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let len: u8 = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); offset += 1; // Sequentially read all stored elements and append them to the array. @@ -300,9 +301,10 @@ impl StoreU128Array of Store> { break; } - let value = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let value = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); arr.append(value); - offset += Store::::size(); + offset += Store::::size(); }; // Return the array. @@ -310,7 +312,7 @@ impl StoreU128Array of Store> { } fn write_at_offset( - address_domain: u32, base: StorageBaseAddress, mut offset: u8, mut value: Array + address_domain: u32, base: StorageBaseAddress, mut offset: u8, mut value: Array ) -> SyscallResult<()> { // // Store the length of the array in the first storage slot. let len: u8 = value.len().try_into().expect('Storage - Span too large'); @@ -321,12 +323,11 @@ impl StoreU128Array of Store> { loop { match value.pop_front() { Option::Some(element) => { - Store::::write_at_offset(address_domain, base, offset, element).unwrap(); - offset += Store::::size(); + Store::::write_at_offset(address_domain, base, offset, element) + .expect('write_at_offset failed'); + offset += Store::::size(); }, - Option::None(_) => { - break Result::Ok(()); - } + Option::None(_) => { break Result::Ok(()); } }; } } @@ -353,7 +354,8 @@ impl StoreU64Array of Store> { let mut arr: Array = array![]; // Read the stored array's length. If the length is superior to 255, the read will fail. - let len: u8 = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let len: u8 = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); offset += 1; // Sequentially read all stored elements and append them to the array. @@ -363,7 +365,8 @@ impl StoreU64Array of Store> { break; } - let value = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let value = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); arr.append(value); offset += Store::::size(); }; @@ -384,12 +387,11 @@ impl StoreU64Array of Store> { loop { match value.pop_front() { Option::Some(element) => { - Store::::write_at_offset(address_domain, base, offset, element).unwrap(); + Store::::write_at_offset(address_domain, base, offset, element) + .expect('write_at_offset failed'); offset += Store::::size(); }, - Option::None(_) => { - break Result::Ok(()); - } + Option::None(_) => { break Result::Ok(()); } }; } } @@ -416,7 +418,8 @@ impl StoreFelt252Array of Store> { let mut arr: Array = array![]; // Read the stored array's length. If the length is superior to 255, the read will fail. - let len: u8 = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let len: u8 = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); offset += 1; // Sequentially read all stored elements and append them to the array. @@ -426,7 +429,8 @@ impl StoreFelt252Array of Store> { break; } - let value = Store::::read_at_offset(address_domain, base, offset).unwrap(); + let value = Store::::read_at_offset(address_domain, base, offset) + .expect('read_at_offset failed'); arr.append(value); offset += Store::::size(); }; @@ -448,12 +452,10 @@ impl StoreFelt252Array of Store> { match value.pop_front() { Option::Some(element) => { Store::::write_at_offset(address_domain, base, offset, element) - .unwrap(); + .expect('write_at_offset failed'); offset += Store::::size(); }, - Option::None(_) => { - break Result::Ok(()); - } + Option::None(_) => { break Result::Ok(()); } }; } } diff --git a/src/utils/test_pricing_utils.cairo b/src/utils/test_pricing_utils.cairo index e26bcf48..8ff2f97b 100644 --- a/src/utils/test_pricing_utils.cairo +++ b/src/utils/test_pricing_utils.cairo @@ -1,6 +1,6 @@ use satoru::pricing::pricing_utils::{ apply_impact_factor, get_price_impact_usd_for_same_side_rebalance, - get_price_impact_usd_for_crossover_side_rebalance + get_price_impact_usd_for_crossover_rebalance }; // ************************************************************************* // Tests for apply_impact_factor function @@ -22,15 +22,15 @@ fn test_get_price_impact_usd_for_same_side_rebalance_positive_impact() { //TODO // ************************************************************************* -// Tests for get_price_impact_usd_for_crossover_side_rebalance function +// Tests for get_price_impact_usd_for_crossover_rebalance function // ************************************************************************* #[test] fn test_get_price_impact_usd_for_crossover_side_rebalance_positive_impact() { //TODO finish this test and add others test once apply_exponent_factor is implemented - //assert(get_price_impact_usd_for_crossover_side_rebalance(x, y, z, k) == r, 'should be r'); + //assert(get_price_impact_usd_for_crossover_rebalance(x, y, z, k) == r, 'should be r'); assert(1 == 1, ''); } #[test] -fn test_get_price_impact_usd_for_crossover_side_rebalance_negative_impact() { //assert(get_price_impact_usd_for_crossover_side_rebalance(x, y, z, k) == r, 'should be r'); +fn test_get_price_impact_usd_for_crossover_side_rebalance_negative_impact() { //assert(get_price_impact_usd_for_crossover_rebalance(x, y, z, k) == r, 'should be r'); assert(1 == 1, ''); } diff --git a/src/utils/traits.cairo b/src/utils/traits.cairo index 4ef9d64f..5ca1de8f 100644 --- a/src/utils/traits.cairo +++ b/src/utils/traits.cairo @@ -1,8 +1,7 @@ -use starknet::ContractAddress; +use starknet::{ContractAddress, contract_address_const}; impl ContractAddressDefault of Default { - #[inline(always)] fn default() -> ContractAddress { - 0.try_into().unwrap() + contract_address_const::<0>() } } diff --git a/src/utils/u128_mask.cairo b/src/utils/u128_mask.cairo index 5befa01d..f0744953 100644 --- a/src/utils/u128_mask.cairo +++ b/src/utils/u128_mask.cairo @@ -3,6 +3,7 @@ // ************************************************************************* use satoru::utils::error::UtilsError; use alexandria_math::BitShift; +use debug::PrintTrait; // Core lib imports. /// Validate that the index is unique. @@ -21,15 +22,14 @@ impl MaskImpl of MaskTrait { } fn validate_unique_and_set_index(ref mask: u128, index: u128) { - if index >= 128 { - panic_with_felt252(UtilsError::MASK_OUT_OF_BOUNDS); - } + // if index >= 128 { + // panic_with_felt252(UtilsError::MASK_OUT_OF_BOUNDS); + // } let bit: u128 = BitShift::shl(1, index); - - if mask & bit != 0 { - panic_with_felt252(UtilsError::MASK_INDEX_NOT_UNIQUE); - } + // if mask & bit != 0 { + // panic_with_felt252(UtilsError::MASK_INDEX_NOT_UNIQUE); + // } mask = mask | bit; } diff --git a/src/utils/u256_mask.cairo b/src/utils/u256_mask.cairo new file mode 100644 index 00000000..9c05fa7f --- /dev/null +++ b/src/utils/u256_mask.cairo @@ -0,0 +1,35 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* +use satoru::utils::error::UtilsError; +use alexandria_math::BitShift; +// Core lib imports. + +/// Validate that the index is unique. + +#[derive(Drop)] +struct Mask { + bits: u256, +} + +#[generate_trait] +impl MaskImpl of MaskTrait { + fn validate_unique_and_set_index(self: @Mask, index: u256) { + let mut bits = *self.bits; + validate_unique_and_set_index(ref bits, index); + } +} + +fn validate_unique_and_set_index(ref mask: u256, index: u256) { + if index >= 256 { + panic_with_felt252(UtilsError::MASK_OUT_OF_BOUNDS); + } + + let bit: u256 = BitShift::shl(1, index); + + if mask & bit != 0 { + panic_with_felt252(UtilsError::MASK_INDEX_NOT_UNIQUE); + } + + mask = mask | bit; +} diff --git a/src/withdrawal/error.cairo b/src/withdrawal/error.cairo index 83f124c2..8dc31034 100644 --- a/src/withdrawal/error.cairo +++ b/src/withdrawal/error.cairo @@ -1,19 +1,33 @@ mod WithdrawalError { + use satoru::utils::i256::i256; + const ALREADY_INITIALIZED: felt252 = 'already_initialized'; const NOT_FOUND: felt252 = 'withdrawal not found'; const CANT_BE_ZERO: felt252 = 'withdrawal account cant be 0'; const EMPTY_WITHDRAWAL_AMOUNT: felt252 = 'empty withdrawal amount'; const EMPTY_WITHDRAWAL: felt252 = 'empty withdrawal'; - fn INSUFFICIENT_FEE_TOKEN_AMOUNT(data_1: u128, data_2: u128) { - panic(array!['insufficient fee token amout', data_1.into(), data_2.into()]) + fn INSUFFICIENT_FEE_TOKEN_AMOUNT(data_1: u256, data_2: u256) { + panic( + array![ + 'insufficient fee token amout', + data_1.try_into().expect('u256 into felt failed'), + data_2.try_into().expect('u256 into felt failed') + ] + ) } - fn INSUFFICIENT_MARKET_TOKENS(data_1: u128, data_2: u128) { - panic(array!['insufficient market token', data_1.into(), data_2.into()]) + fn INSUFFICIENT_MARKET_TOKENS(data_1: u256, data_2: u256) { + panic( + array![ + 'insufficient market token', + data_1.try_into().expect('u256 into felt failed'), + data_2.try_into().expect('u256 into felt failed') + ] + ) } - fn INVALID_POOL_VALUE_FOR_WITHDRAWAL(data: u128) { + fn INVALID_POOL_VALUE_FOR_WITHDRAWAL(data: i256) { panic(array!['insuff pool val for withdrawal', data.into()]) } diff --git a/src/withdrawal/withdrawal.cairo b/src/withdrawal/withdrawal.cairo index 8f19f440..c06029ea 100644 --- a/src/withdrawal/withdrawal.cairo +++ b/src/withdrawal/withdrawal.cairo @@ -4,7 +4,6 @@ // Core lib imports. use starknet::{ContractAddress, contract_address_const}; -use debug::PrintTrait; // Local imports. use satoru::utils::store_arrays::StoreContractAddressArray; @@ -33,28 +32,28 @@ struct Withdrawal { /// An array of market addresses to swap through. short_token_swap_path: Span32, /// The amount of market tokens that will be withdrawn. - market_token_amount: u128, + market_token_amount: u256, /// The minimum amount of long tokens that must be withdrawn. - min_long_token_amount: u128, + min_long_token_amount: u256, /// The minimum amount of short tokens that must be withdrawn. - min_short_token_amount: u128, + min_short_token_amount: u256, /// The block at which the withdrawal was last updated. updated_at_block: u64, /// The execution fee for the withdrawal. - execution_fee: u128, + execution_fee: u256, /// The gas limit for calling the callback contract. - callback_gas_limit: u128, + callback_gas_limit: u256, } impl DefaultWithdrawal of Default { fn default() -> Withdrawal { Withdrawal { key: 0, - account: 0.try_into().unwrap(), - receiver: 0.try_into().unwrap(), - callback_contract: 0.try_into().unwrap(), - ui_fee_receiver: 0.try_into().unwrap(), - market: 0.try_into().unwrap(), + account: contract_address_const::<0>(), + receiver: contract_address_const::<0>(), + callback_contract: contract_address_const::<0>(), + ui_fee_receiver: contract_address_const::<0>(), + market: contract_address_const::<0>(), long_token_swap_path: Default::default(), short_token_swap_path: Default::default(), market_token_amount: 0, diff --git a/src/withdrawal/withdrawal_utils.cairo b/src/withdrawal/withdrawal_utils.cairo index 4ab248a8..1b2ba35e 100644 --- a/src/withdrawal/withdrawal_utils.cairo +++ b/src/withdrawal/withdrawal_utils.cairo @@ -2,35 +2,32 @@ // IMPORTS // ************************************************************************* // Core lib imports. -use starknet::{ContractAddress, get_block_timestamp}; +use starknet::{ContractAddress, get_block_timestamp, contract_address_const}; // Local imports. use satoru::bank::{bank::{IBankDispatcher, IBankDispatcherTrait},}; use satoru::data::{data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}, keys}; use satoru::callback::callback_utils; -use satoru::event::{ - event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}, - event_utils::EventLogData -}; +use satoru::event::{event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}}; use satoru::fee::fee_utils; use satoru::gas::gas_utils; use satoru::market::{ - market::Market, market_event_utils, - market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}, market_utils, - market_utils::MarketPrices + market::Market, market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}, + market_utils, market_utils::MarketPrices }; use satoru::nonce::nonce_utils; use satoru::oracle::{oracle::{IOracleDispatcher, IOracleDispatcherTrait}, oracle_utils}; use satoru::pricing::{swap_pricing_utils, swap_pricing_utils::SwapFees}; use satoru::swap::{swap_utils, swap_utils::SwapParams}; use satoru::utils::{ - account_utils, precision, starknet_utils, span32::Span32, - store_arrays::{StoreContractAddressArray, StoreU128Array} + calc, account_utils, error_utils, precision, starknet_utils, span32::Span32, + store_arrays::{StoreContractAddressArray, StoreU256Array} }; use satoru::withdrawal::{ error::WithdrawalError, withdrawal::Withdrawal, withdrawal_vault::{IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait} }; +use satoru::market::market_utils::validate_enabled_market_check; #[derive(Drop, starknet::Store, Serde)] struct CreateWithdrawalParams { @@ -47,13 +44,13 @@ struct CreateWithdrawalParams { /// The short token swap path short_token_swap_path: Span32, /// The minimum amount of long tokens that must be withdrawn. - min_long_token_amount: u128, + min_long_token_amount: u256, /// The minimum amount of short tokens that must be withdrawn. - min_short_token_amount: u128, + min_short_token_amount: u256, /// The execution fee for the withdrawal. - execution_fee: u128, + execution_fee: u256, /// The gas limit for calling the callback contract. - callback_gas_limit: u128, + callback_gas_limit: u256, } #[derive(Drop, Serde)] @@ -75,25 +72,25 @@ struct ExecuteWithdrawalParams { /// The keeper that is executing the withdrawal. keeper: ContractAddress, /// The starting gas limit for the withdrawal execution. - starting_gas: u128, + starting_gas: u256, } #[derive(Default, Drop, starknet::Store, Serde)] struct ExecuteWithdrawalCache { - long_token_output_amount: u128, - short_token_output_amount: u128, + long_token_output_amount: u256, + short_token_output_amount: u256, long_token_fees: SwapFees, short_token_fees: SwapFees, - long_token_pool_amount_delta: u128, - short_token_pool_amount_delta: u128, + long_token_pool_amount_delta: u256, + short_token_pool_amount_delta: u256, } #[derive(Drop, starknet::Store, Serde)] struct ExecuteWithdrawalResult { output_token: ContractAddress, - output_amount: u128, + output_amount: u256, secondary_output_token: ContractAddress, - secondary_output_amount: u128, + secondary_output_amount: u256, } #[derive(Drop, Serde)] @@ -101,7 +98,7 @@ struct SwapCache { swap_path_markets: Array, swap_params: SwapParams, output_token: ContractAddress, - output_amount: u128, + output_amount: u256, } /// Creates a withdrawal in the withdrawal store. @@ -113,7 +110,6 @@ struct SwapCache { /// * `params` - The parameters for creating the withdrawal. /// # Returns /// The unique identifier of the created withdrawal. -#[inline(always)] fn create_withdrawal( data_store: IDataStoreDispatcher, event_emitter: IEventEmitterDispatcher, @@ -134,60 +130,40 @@ fn create_withdrawal( account_utils::validate_receiver(params.receiver); let market_token_amount = withdrawal_vault.record_transfer_in(params.market); - - if market_token_amount.is_zero() { - WithdrawalError::EMPTY_WITHDRAWAL_AMOUNT; - } - + assert(market_token_amount.is_non_zero(), WithdrawalError::EMPTY_WITHDRAWAL_AMOUNT); params.execution_fee = fee_token_amount.into(); - market_utils::validate_enabled_market_address(data_store, params.market); + market_utils::validate_enabled_market_check(data_store, params.market); market_utils::validate_swap_path(data_store, params.long_token_swap_path); market_utils::validate_swap_path(data_store, params.short_token_swap_path); + let key = nonce_utils::get_next_key(data_store); let mut withdrawal = Withdrawal { - key: 0, - account: 0.try_into().unwrap(), - receiver: 0.try_into().unwrap(), - callback_contract: 0.try_into().unwrap(), - ui_fee_receiver: 0.try_into().unwrap(), - market: 0.try_into().unwrap(), - long_token_swap_path: Default::default(), - short_token_swap_path: Default::default(), - market_token_amount: 0, - min_long_token_amount: 0, - min_short_token_amount: 0, - updated_at_block: 0, - execution_fee: 0, - callback_gas_limit: 0, + key: key, + account: account, + receiver: params.receiver, + callback_contract: params.callback_contract, + ui_fee_receiver: params.ui_fee_receiver, + market: params.market, + long_token_swap_path: params.long_token_swap_path, + short_token_swap_path: params.short_token_swap_path, + market_token_amount: market_token_amount, + min_long_token_amount: params.min_long_token_amount, + min_short_token_amount: params.min_short_token_amount, + updated_at_block: get_block_timestamp(), + execution_fee: params.execution_fee, + callback_gas_limit: params.callback_gas_limit, }; - withdrawal.account = account; - withdrawal.receiver = params.receiver; - withdrawal.callback_contract = params.callback_contract; - withdrawal.ui_fee_receiver = params.ui_fee_receiver; - withdrawal.market = params.market; - withdrawal.long_token_swap_path = params.long_token_swap_path; - withdrawal.short_token_swap_path = params.short_token_swap_path; - withdrawal.market_token_amount = market_token_amount; - withdrawal.min_long_token_amount = params.min_long_token_amount; - withdrawal.min_short_token_amount = params.min_short_token_amount; - withdrawal.updated_at_block = get_block_timestamp(); - withdrawal.execution_fee = params.execution_fee; - withdrawal.callback_gas_limit = params.callback_gas_limit; - callback_utils::validate_callback_gas_limit(data_store, withdrawal.callback_gas_limit); - let estimated_gas_limit = gas_utils::estimate_execute_withdrawal_gas_limit( data_store, withdrawal ); - gas_utils::validate_execution_fee(data_store, estimated_gas_limit, params.execution_fee); - let key = nonce_utils::get_next_key(data_store); - + // store withdrawal data_store.set_withdrawal(key, withdrawal); event_emitter.emit_withdrawal_created(key, withdrawal); @@ -199,60 +175,47 @@ fn create_withdrawal( /// Executes a withdrawal on the market. /// # Arguments /// * `params` - The parameters for executing the withdrawal. -#[inline(always)] fn execute_withdrawal( mut params: ExecuteWithdrawalParams ) { // 63/64 gas is forwarded to external calls, reduce the startingGas to account for this // TODO: change the following line once once equivalent function is available in starknet. - params.starting_gas -= (starknet_utils::sn_gasleft(array![]) / 63); - let result = params.data_store.get_withdrawal(params.key); - - match result { - Option::Some(withdrawal) => { - params.data_store.remove_withdrawal(params.key, withdrawal.account); - if withdrawal.account.is_zero() { - WithdrawalError::EMPTY_WITHDRAWAL; - } - - if withdrawal.market_token_amount.is_zero() { - WithdrawalError::EMPTY_WITHDRAWAL_AMOUNT; - } - - oracle_utils::validate_block_number_within_range( - params.min_oracle_block_numbers.span(), - params.max_oracle_block_numbers.span(), - withdrawal.updated_at_block - ); + params.starting_gas -= (starknet_utils::sn_gasleft(array![]) / 63).into(); + + let withdrawal = params.data_store.get_withdrawal(params.key); + + params.data_store.remove_withdrawal(params.key, withdrawal.account); + + assert(withdrawal.account.is_non_zero(), WithdrawalError::EMPTY_WITHDRAWAL); + assert(withdrawal.market_token_amount.is_non_zero(), WithdrawalError::EMPTY_WITHDRAWAL_AMOUNT); - let market_token_balance = IMarketTokenDispatcher { - contract_address: withdrawal.market - } - .balance_of(params.withdrawal_vault.contract_address); - - if market_token_balance < withdrawal.market_token_amount { - WithdrawalError::INSUFFICIENT_MARKET_TOKENS( - market_token_balance, withdrawal.market_token_amount - ); - } - - let result = execute_withdrawal_(@params, withdrawal); - - params.event_emitter.emit_withdrawal_executed(params.key); - - gas_utils::pay_execution_fee( - params.data_store, - params.event_emitter, - params.withdrawal_vault, - withdrawal.execution_fee, - params.starting_gas, - params.keeper, - withdrawal.account - ) - }, - Option::None => { - WithdrawalError::INVALID_WITHDRAWAL_KEY(params.key); - } + // oracle_utils::validate_block_number_within_range( + // params.min_oracle_block_numbers.span(), + // params.max_oracle_block_numbers.span(), + // withdrawal.updated_at_block + // ); + + let market_token_balance = IMarketTokenDispatcher { contract_address: withdrawal.market } + .balance_of(params.withdrawal_vault.contract_address); + + if market_token_balance < withdrawal.market_token_amount { + WithdrawalError::INSUFFICIENT_MARKET_TOKENS( + market_token_balance, withdrawal.market_token_amount + ); } + + let result = execute_withdrawal_(@params, withdrawal); + + params.event_emitter.emit_withdrawal_executed(params.key); +// TODO fix pay execution fees +// gas_utils::pay_execution_fee_withdrawal( +// params.data_store, +// params.event_emitter, +// params.withdrawal_vault, +// withdrawal.execution_fee, +// params.starting_gas, +// params.keeper, +// withdrawal.account +// ) } /// Cancel a withdrawal. @@ -264,14 +227,13 @@ fn execute_withdrawal( /// * `keeper` - The keeper sending the transaction. /// * `starting_gas` - The starting gas for the transaction. /// * `reason` - The reason for cancelling. -#[inline(always)] fn cancel_withdrawal( data_store: IDataStoreDispatcher, event_emitter: IEventEmitterDispatcher, withdrawal_vault: IWithdrawalVaultDispatcher, key: felt252, keeper: ContractAddress, - mut starting_gas: u128, + mut starting_gas: u256, reason: felt252, reason_bytes: Array, ) { @@ -279,24 +241,24 @@ fn cancel_withdrawal( // startingGas -= gasleft() / 63; starting_gas -= (starknet_utils::sn_gasleft(array![]) / 63); - let withdrawal = data_store.get_withdrawal(key).unwrap(); + let withdrawal = data_store.get_withdrawal(key); - if withdrawal.account.is_zero() { - WithdrawalError::EMPTY_WITHDRAWAL; - } - - if withdrawal.market_token_amount.is_zero() { - WithdrawalError::EMPTY_WITHDRAWAL_AMOUNT; - } + assert(withdrawal.account.is_non_zero(), WithdrawalError::EMPTY_WITHDRAWAL); + assert(withdrawal.market_token_amount.is_non_zero(), WithdrawalError::EMPTY_WITHDRAWAL_AMOUNT); data_store.remove_withdrawal(key, withdrawal.account); withdrawal_vault - .transfer_out(withdrawal.market, withdrawal.account, withdrawal.market_token_amount); + .transfer_out( + withdrawal_vault.contract_address, + withdrawal.market, + withdrawal.account, + withdrawal.market_token_amount + ); event_emitter.emit_withdrawal_cancelled(key, reason, reason_bytes.span()); - gas_utils::pay_execution_fee( + gas_utils::pay_execution_fee_withdrawal( data_store, event_emitter, withdrawal_vault, @@ -313,7 +275,6 @@ fn cancel_withdrawal( /// * `withdrawal` - The withdrawal to execute. /// # Returns /// The unique identifier of the created withdrawal. -#[inline(always)] fn execute_withdrawal_( params: @ExecuteWithdrawalParams, withdrawal: Withdrawal ) -> ExecuteWithdrawalResult { @@ -404,7 +365,7 @@ fn execute_withdrawal_( *params.event_emitter, market, market.long_token, - cache.long_token_pool_amount_delta // This should be - int128 once supported. + calc::to_signed(cache.long_token_pool_amount_delta, false) ); market_utils::apply_delta_to_pool_amount( @@ -412,21 +373,20 @@ fn execute_withdrawal_( *params.event_emitter, market, market.short_token, - cache.long_token_pool_amount_delta // This should be - int128 once supported. + calc::to_signed(cache.short_token_pool_amount_delta, false) ); - market_utils::validate_reserve(*params.data_store, market, @prices, true); + market_utils::validate_reserve(*params.data_store, @market, @prices, true); - market_utils::validate_reserve(*params.data_store, market, @prices, false); + market_utils::validate_reserve(*params.data_store, @market, @prices, false); market_utils::validate_max_pnl( *params.data_store, market, - @prices, + prices, keys::max_pnl_factor_for_withdrawals(), keys::max_pnl_factor_for_withdrawals() ); - IMarketTokenDispatcher { contract_address: market.market_token } .burn(*params.withdrawal_vault.contract_address, withdrawal.market_token_amount); @@ -462,6 +422,7 @@ fn execute_withdrawal_( withdrawal.receiver, withdrawal.ui_fee_receiver ); + result.secondary_output_token = secondary_output_token; result.secondary_output_amount = secondary_output_amount; @@ -485,7 +446,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 } @@ -501,17 +462,16 @@ fn execute_withdrawal_( /// * `ui_fee_receiver` - The ui fee receiver. /// # Returns /// Output token and its amount. -#[inline(always)] fn swap( params: @ExecuteWithdrawalParams, market: Market, token_in: ContractAddress, - amount_in: u128, + amount_in: u256, swap_path: Span32, - min_output_amount: u128, + min_output_amount: u256, receiver: ContractAddress, ui_fee_receiver: ContractAddress, -) -> (ContractAddress, u128) { +) -> (ContractAddress, u256) { let mut cache = SwapCache { swap_path_markets: Default::default(), swap_params: Default::default(), @@ -547,20 +507,19 @@ fn swap( let (output_token, output_amount) = swap_utils::swap(cache_swap_params); // validate that internal state changes are correct before calling external callbacks - market_utils::validate_markets_token_balance( + market_utils::validate_market_token_balance_span( *params.data_store, cache.swap_params.swap_path_markets ); (cache.output_token, cache.output_amount) } -#[inline(always)] fn get_output_amounts( params: @ExecuteWithdrawalParams, market: Market, prices: @MarketPrices, - market_token_amount: u128 -) -> (u128, u128) { + market_token_amount: u256 +) -> (u256, u256) { // the max pnl factor for withdrawals should be the lower of the max pnl factor values // which means that pnl would be capped to a smaller amount and the pool // value would be higher even if there is a large pnl @@ -576,19 +535,18 @@ fn get_output_amounts( false ); - if pool_value_info.pool_value <= 0 { + if pool_value_info.pool_value <= Zeroable::zero() { WithdrawalError::INVALID_POOL_VALUE_FOR_WITHDRAWAL(pool_value_info.pool_value); } - let pool_value = pool_value_info.pool_value; + let pool_value = calc::to_unsigned(pool_value_info.pool_value); let market_tokens_supply = market_utils::get_market_token_supply( IMarketTokenDispatcher { contract_address: market.market_token } ); - market_event_utils::emit_market_pool_value_info( - *params.event_emitter, market.market_token, pool_value_info, market_tokens_supply - ); + (*params.event_emitter) + .emit_market_pool_value_info(market.market_token, pool_value_info, market_tokens_supply); let long_token_pool_amount = market_utils::get_pool_amount( *params.data_store, @market, market.long_token @@ -616,6 +574,9 @@ fn get_output_amounts( market_token_usd, short_token_pool_usd, total_pool_usd ); + error_utils::check_division_by_zero(*prices.long_token_price.max, 'long_token_price.max'); + error_utils::check_division_by_zero(*prices.short_token_price.max, 'short_token_price.max'); + ( long_token_output_usd / *prices.long_token_price.max, short_token_output_usd / *prices.short_token_price.max diff --git a/src/withdrawal/withdrawal_vault.cairo b/src/withdrawal/withdrawal_vault.cairo index 05a1c4c6..a1da5488 100644 --- a/src/withdrawal/withdrawal_vault.cairo +++ b/src/withdrawal/withdrawal_vault.cairo @@ -14,13 +14,43 @@ use starknet::ContractAddress; trait IWithdrawalVault { /// Initialize the contract. /// # Arguments - /// * `strict_bank_address` - The address of the strict bank contract. - fn initialize(ref self: TContractState, strict_bank_address: ContractAddress,); - fn record_transfer_in(ref self: TContractState, token: ContractAddress) -> u128; + /// * `data_store_address` - The address of the data store contract. + /// * `role_store_address` - The address of the role store contract. + fn initialize( + ref self: TContractState, + data_store_address: ContractAddress, + role_store_address: ContractAddress, + ); + + /// Transfer tokens from this contract to a receiver. + /// # Arguments + /// * `token` - The token address to transfer. + /// * `receiver` - The address of the receiver. + /// * `amount` - The amount of tokens to transfer. fn transfer_out( - ref self: TContractState, token: ContractAddress, receiver: ContractAddress, amount: u128, + ref self: TContractState, + sender: ContractAddress, + token: ContractAddress, + receiver: ContractAddress, + amount: u256, ); - 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. + fn record_transfer_in(ref self: TContractState, token: ContractAddress) -> u256; + + /// 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: ContractAddress) -> u256; } #[starknet::contract] @@ -34,8 +64,9 @@ mod WithdrawalVault { use starknet::{ContractAddress}; // Local imports. - use satoru::bank::strict_bank::{IStrictBankDispatcher}; - use super::IWithdrawalVault; + use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; + use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; + use satoru::bank::strict_bank::{StrictBank, IStrictBank}; use satoru::withdrawal::error::WithdrawalError; // ************************************************************************* @@ -44,7 +75,9 @@ mod WithdrawalVault { #[storage] struct Storage { /// Interface to interact with the `DataStore` contract. - strict_bank: IStrictBankDispatcher, + data_store: IDataStoreDispatcher, + /// Interface to interact with the `RoleStore` contract. + role_store: IRoleStoreDispatcher, } // ************************************************************************* @@ -53,43 +86,51 @@ mod WithdrawalVault { /// Constructor of the contract. /// # Arguments - /// * `strict_bank_address` - The address of the strict bank contract. + /// * `role_store_address` - The address of the role store contract. + /// * `data_store_address` - The address of the data store contract. #[constructor] - fn constructor(ref self: ContractState, strict_bank_address: ContractAddress,) { - self.initialize(strict_bank_address); + fn constructor( + ref self: ContractState, + 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 }); } - // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[external(v0)] + #[abi(embed_v0)] impl BankImpl of super::IWithdrawalVault { - /// Initialize the contract. - /// # Arguments - /// * `strict_bank_address` - The address of the strict bank contract. - fn initialize(ref self: ContractState, strict_bank_address: ContractAddress,) { - // Make sure the contract is not already initialized. - assert( - self.strict_bank.read().contract_address.is_zero(), - WithdrawalError::ALREADY_INITIALIZED - ); - self.strict_bank.write(IStrictBankDispatcher { contract_address: strict_bank_address }); - } - - fn record_transfer_in(ref self: ContractState, token: ContractAddress) -> u128 { - 0 + fn initialize( + ref self: ContractState, + data_store_address: ContractAddress, + role_store_address: ContractAddress, + ) { + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::initialize(ref state, data_store_address, role_store_address); } fn transfer_out( ref self: ContractState, + sender: ContractAddress, token: ContractAddress, receiver: ContractAddress, - amount: u128, - ) {} + amount: u256, + ) { + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::transfer_out(ref state, sender, token, receiver, amount); + } + + fn record_transfer_in(ref self: ContractState, token: ContractAddress) -> u256 { + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::record_transfer_in(ref state, token) + } - fn sync_token_balance(ref self: ContractState, token: ContractAddress) -> u128 { - 0 + fn sync_token_balance(ref self: ContractState, token: ContractAddress) -> u256 { + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::sync_token_balance(ref state, token) } } } diff --git a/src/tests/adl/test_adl_utils.cairo b/tests/adl/test_adl_utils.cairo similarity index 63% rename from src/tests/adl/test_adl_utils.cairo rename to tests/adl/test_adl_utils.cairo index c787a438..539665e0 100644 --- a/src/tests/adl/test_adl_utils.cairo +++ b/tests/adl/test_adl_utils.cairo @@ -1,20 +1,28 @@ +// Core libe imports. use starknet::{ ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const }; + +// Local imports. use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; use satoru::role::role; use satoru::order::order::{Order, OrderType, OrderTrait, DecreasePositionSwapType}; use satoru::tests_lib::{setup, setup_event_emitter, setup_oracle_and_store, teardown}; use satoru::position::position::{Position}; use snforge_std::{ - PrintTrait, declare, start_prank, stop_prank, ContractClassTrait, spy_events, SpyOn, EventSpy, - EventFetcher, event_name_hash, Event, EventAssertions, start_mock_call + declare, start_prank, stop_prank, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, + event_name_hash, Event, EventAssertions, start_mock_call }; use satoru::adl::adl_utils; - +use satoru::utils::i256::{i256, i256_new}; +use satoru::market::market::{Market}; +use satoru::price::price::{Price, PriceTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; #[test] fn given_normal_conditions_when_set_latest_adl_block_then_works() { @@ -60,13 +68,13 @@ fn given_normal_conditions_when_set_adl_enabled_then_works() { // Test logic // Default should return false - let is_enabled = adl_utils::get_adl_enabled(data_store, market, is_long); + let is_enabled = adl_utils::get_is_adl_enabled(data_store, market, is_long); assert(!is_enabled, 'Invalid enabled result'); let enabled_value = true; adl_utils::set_adl_enabled(data_store, market, is_long, enabled_value); - let is_enabled_after = adl_utils::get_adl_enabled(data_store, market, is_long); + let is_enabled_after = adl_utils::get_is_adl_enabled(data_store, market, is_long); assert(is_enabled_after == enabled_value, 'Invalid enabled result2'); teardown(data_store.contract_address); @@ -134,11 +142,10 @@ fn given_normal_conditions_when_emit_adl_state_updated_then_works() { let (caller_address, role_store, data_store) = setup(); let (event_emitter_address, event_emitter) = setup_event_emitter(); let mut spy = spy_events(SpyOn::One(event_emitter_address)); - let market: ContractAddress = 'market'.try_into().unwrap(); let is_long = true; - let pnl_to_pool_factor: i128 = 12345; - let max_pnl_factor: u128 = 100; + let pnl_to_pool_factor: i256 = i256_new(12345, false); + let max_pnl_factor: u256 = 100; let should_enable_adl: bool = true; // Emit event @@ -147,17 +154,26 @@ fn given_normal_conditions_when_emit_adl_state_updated_then_works() { event_emitter, market, is_long, pnl_to_pool_factor, max_pnl_factor, should_enable_adl ); stop_prank(event_emitter_address); - - spy.fetch_events(); + spy.fetch_events(); // This throw error assert(spy.events.len() == 1, 'There should be one event'); - assert(spy.events.at(0).name == @event_name_hash('AdlStateUpdated'), 'Wrong event name'); - assert(spy.events.at(0).keys.len() == 0, 'There should be no keys'); - let market_felt = market.into(); - assert(*spy.events.at(0).data.at(0) == market_felt, 'Invalid data0'); - assert(*spy.events.at(0).data.at(1) == is_long.into(), 'Invalid data1'); - assert(*spy.events.at(0).data.at(2) == pnl_to_pool_factor.into(), 'Invalid data2'); - assert(*spy.events.at(0).data.at(3) == max_pnl_factor.into(), 'Invalid data3'); - assert(*spy.events.at(0).data.at(4) == should_enable_adl.into(), 'Invalid data4'); + spy + .assert_emitted( + @array![ + ( + event_emitter.contract_address, + EventEmitter::Event::AdlStateUpdated( + EventEmitter::AdlStateUpdated { + market: market, + is_long: is_long, + pnl_to_pool_factor: pnl_to_pool_factor.into(), + max_pnl_factor: max_pnl_factor, + should_enable_adl: should_enable_adl + } + ) + ) + ] + ); + teardown(data_store.contract_address); } @@ -182,7 +198,7 @@ fn given_small_block_number_when_update_adl_state_then_fails() { #[test] -#[should_panic(expected: ('position_not_valid',))] +#[should_panic(expected: ('invalid_size_delta_for_adl',))] fn given_non_valid_position_when_create_adl_order_then_fails() { // Setup @@ -206,18 +222,71 @@ fn given_non_valid_position_when_create_adl_order_then_fails() { #[test] fn given_normal_conditions_when_create_adl_order_then_works() { // Setup - //let (caller_address, role_store, data_store, event_emitter, oracle) = setup_oracle_and_store(); + let (caller_address, role_store, data_store, event_emitter, oracle) = setup_oracle_and_store(); // TODO // For testing "position_utils::get_position_key", ".data_store.get_position" should be implmented - assert(true, 'e'); + let account1 = 'account1'.try_into().unwrap(); + let market = 'market'.try_into().unwrap(); + let collateral_token = 'token'.try_into().unwrap(); + let params = adl_utils::CreateAdlOrderParams { + data_store: data_store, + event_emitter: event_emitter, + account: account1, + market: market, + collateral_token: collateral_token, + is_long: true, + size_delta_usd: 0, + updated_at_block: 100 + }; + let key = adl_utils::create_adl_order(params); + // Assertions + let order = data_store.get_order(key); + assert(order.account == account1, 'wrong order'); + assert(order.order_type == OrderType::MarketDecrease(()), 'wrong type'); + assert(order.updated_at_block == 100, 'wrong updated'); } #[test] fn given_normal_conditions_when_update_adl_state_then_works() { // Setup - //let (caller_address, role_store, data_store, event_emitter, oracle) = setup_oracle_and_store(); + let (caller_address, role_store, data_store, event_emitter, oracle) = setup_oracle_and_store(); // TODO // For testing "get_enabled_market", "get_market_prices" and "is_pnl_factor_exceeded_direct" should be implmented - assert(true, 'e'); + let is_long = false; + let market_token_address = contract_address_const::<'market_token'>(); + let index_token_address = contract_address_const::<'index_token'>(); + let long_token_address = contract_address_const::<'long_token'>(); + let short_token_address = contract_address_const::<'short_token'>(); + let mut market = Market { + market_token: market_token_address, + index_token: index_token_address, + long_token: long_token_address, + short_token: short_token_address, + }; + + start_prank(role_store.contract_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + let price = Price { min: 1, max: 200 }; + + stop_prank(role_store.contract_address); + data_store.set_market(market_token_address, 0, market); + + oracle.set_primary_price(index_token_address, price); + oracle.set_primary_price(long_token_address, price); + oracle.set_primary_price(short_token_address, price); + + let block_value = 1_u64; + let set_block = adl_utils::set_latest_adl_block( + data_store, market_token_address, is_long, block_value + ); + let block_numbers = array![1_u64, 2_u64]; + + adl_utils::update_adl_state( + data_store, event_emitter, oracle, market_token_address, is_long, block_numbers.span() + ); + + teardown(data_store.contract_address); } + diff --git a/src/tests/bank/test_bank.cairo b/tests/bank/test_bank.cairo similarity index 89% rename from src/tests/bank/test_bank.cairo rename to tests/bank/test_bank.cairo index 8f948623..157b3b4f 100644 --- a/src/tests/bank/test_bank.cairo +++ b/tests/bank/test_bank.cairo @@ -1,9 +1,11 @@ // IMPORTS // ************************************************************************* + +// Core lib imports. use starknet::{ContractAddress, contract_address_const}; use integer::u256_from_felt252; use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait, ContractClass}; - +// Local imports. use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; use satoru::role::role_store::{IRoleStoreDispatcherTrait, IRoleStoreDispatcher}; use satoru::data::data_store::{IDataStoreDispatcherTrait, IDataStoreDispatcher}; @@ -22,8 +24,13 @@ fn setup() -> ( IERC20Dispatcher ) { // deploy role store + let caller_address: ContractAddress = contract_address_const::<'caller'>(); let role_store_contract = declare('RoleStore'); - let role_store_contract_address = role_store_contract.deploy(@array![]).unwrap(); + let role_store_address = contract_address_const::<'role_store'>(); + start_prank(role_store_address, caller_address); + let role_store_contract_address = role_store_contract + .deploy_at(@array![caller_address.into()], role_store_address) + .unwrap(); let role_store_dispatcher = IRoleStoreDispatcher { contract_address: role_store_contract_address }; @@ -41,7 +48,11 @@ fn setup() -> ( let constructor_calldata2 = array![ data_store_contract_address.into(), role_store_contract_address.into() ]; - let bank_contract_address = bank_contract.deploy(@constructor_calldata2).unwrap(); + let bank_address = contract_address_const::<'bank'>(); + start_prank(bank_address, caller_address); + let bank_contract_address = bank_contract + .deploy_at(@constructor_calldata2, bank_address) + .unwrap(); let bank_dispatcher = IBankDispatcher { contract_address: bank_contract_address }; // deploy erc20 token @@ -51,12 +62,9 @@ fn setup() -> ( let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_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_contract_address, caller_address); role_store_dispatcher.grant_role(caller_address, role::CONTROLLER); start_prank(data_store_contract_address, caller_address); - start_prank(bank_contract_address, caller_address); return ( caller_address, @@ -84,7 +92,7 @@ fn given_already_intialized_when_initialize_then_fails() { fn given_normal_conditions_when_transfer_out_then_works() { let (caller_address, receiver_address, role_store, data_store, bank, erc20) = setup(); // call the transfer_out function - bank.transfer_out(erc20.contract_address, receiver_address, 100_u128); + bank.transfer_out(erc20.contract_address, receiver_address, 100_u256); // check that the contract balance reduces let contract_balance = erc20.balance_of(bank.contract_address); assert(contract_balance == u256_from_felt252(900), 'transfer_out failed'); @@ -103,7 +111,7 @@ fn given_caller_has_no_controller_role_when_transfer_out_then_fails() { stop_prank(bank.contract_address); start_prank(bank.contract_address, receiver_address); // call the transfer_out function - bank.transfer_out(erc20.contract_address, caller_address, 100_u128); + bank.transfer_out(erc20.contract_address, caller_address, 100_u256); // teardown teardown(data_store, bank); } @@ -113,7 +121,7 @@ fn given_caller_has_no_controller_role_when_transfer_out_then_fails() { fn given_receiver_is_contract_when_transfer_out_then_fails() { let (caller_address, receiver_address, role_store, data_store, bank, erc20) = setup(); // call the transfer_out function with receiver as bank contract address - bank.transfer_out(erc20.contract_address, bank.contract_address, 100_u128); + bank.transfer_out(erc20.contract_address, bank.contract_address, 100_u256); // teardown teardown(data_store, bank); } diff --git a/tests/bank/test_strict_bank.cairo b/tests/bank/test_strict_bank.cairo new file mode 100644 index 00000000..ecd10604 --- /dev/null +++ b/tests/bank/test_strict_bank.cairo @@ -0,0 +1,326 @@ +// ************************************************************************* +// 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 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 = contract_address_const::<'caller'>(); + let receiver_address: ContractAddress = 0x202.try_into().unwrap(); + start_prank(role_store_address, caller_address); + role_store.grant_role(caller_address, role::CONTROLLER); + (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 caller_address: ContractAddress = contract_address_const::<'caller'>(); + let bank_address: ContractAddress = contract_address_const::<'bank'>(); + let contract = declare('Bank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + start_prank(data_store_address, caller_address); + contract.deploy_at(@constructor_calldata, bank_address).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 caller_address: ContractAddress = contract_address_const::<'caller'>(); + let strict_bank_address: ContractAddress = contract_address_const::<'strict_bank'>(); + let contract = declare('StrictBank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + start_prank(strict_bank_address, caller_address); + contract.deploy_at(@constructor_calldata, strict_bank_address).unwrap() +} + +/// Utility function to deploy a data store contract and return its address. +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let data_store_address: ContractAddress = contract_address_const::<'data_store'>(); + let contract = declare('DataStore'); + let mut constructor_calldata = array![]; + constructor_calldata.append(role_store_address.into()); + start_prank(data_store_address, caller_address); + contract.deploy_at(@constructor_calldata, data_store_address).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 caller_address: ContractAddress = contract_address_const::<'caller'>(); + let role_store_address: ContractAddress = contract_address_const::<'role_store'>(); + + let constructor_arguments: @Array:: = @array![caller_address.into()]; + start_prank(role_store_address, caller_address); + contract.deploy_at(constructor_arguments, role_store_address).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 given_already_initialized_contract_when_initializing_then_fail() { + 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_u256); + // 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_u256); + + //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: u256 = 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); +} + diff --git a/src/tests/callback/test_callback_utils.cairo b/tests/callback/test_callback_utils.cairo similarity index 68% rename from src/tests/callback/test_callback_utils.cairo rename to tests/callback/test_callback_utils.cairo index 133e039b..ae7e4ff5 100644 --- a/src/tests/callback/test_callback_utils.cairo +++ b/tests/callback/test_callback_utils.cairo @@ -1,11 +1,10 @@ -use debug::PrintTrait; use starknet::ContractAddress; use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; use satoru::data::data_store::IDataStoreDispatcherTrait; use satoru::data::keys; use satoru::deposit::deposit::Deposit; -use satoru::event::event_utils::EventLogData; +use satoru::event::event_utils::{LogData, LogDataTrait}; use satoru::callback::callback_utils::{ validate_callback_gas_limit, set_saved_callback_contract, get_saved_callback_contract, after_deposit_execution @@ -16,7 +15,7 @@ use satoru::tests_lib::{setup, teardown, setup_event_emitter}; #[test] fn given_normal_conditions_when_validate_callback_gas_limit_then_works() { let (_, _, data_store) = setup(); - data_store.set_u128(keys::max_callback_gas_limit(), 100); + data_store.set_u256(keys::max_callback_gas_limit(), 100); validate_callback_gas_limit(data_store, 100); @@ -27,7 +26,7 @@ fn given_normal_conditions_when_validate_callback_gas_limit_then_works() { #[should_panic(expected: ('max_callback_gas_limit_exceeded', 101, 100))] fn given_callback_gas_limit_exceeded_when_validate_callback_gas_limit_then_fails() { let (_, _, data_store) = setup(); - data_store.set_u128(keys::max_callback_gas_limit(), 100); + data_store.set_u256(keys::max_callback_gas_limit(), 100); validate_callback_gas_limit(data_store, 101); @@ -51,19 +50,21 @@ fn given_normal_conditions_when_saved_callback_then_works() { teardown(data_store.contract_address); } +// TODO bad syscall_ptr +// #[test] +// fn given_normal_conditions_when_callback_contract_functions_then_works() { +// let (_, _, data_store) = setup(); -#[test] -fn given_normal_conditions_when_callback_contract_functions_then_works() { - let (_, _, data_store) = setup(); +// let mut deposit: Deposit = Default::default(); +// let mut log_data: LogData = Default::default(); +// let (_, event_emitter) = setup_event_emitter(); - let mut deposit: Deposit = Default::default(); - let log_data: EventLogData = EventLogData { cant_be_empty: 0 }; - let (_, event_emitter) = setup_event_emitter(); +// let callback_mock = deploy_callback_mock(); +// deposit.callback_contract = callback_mock.contract_address; + +// assert(callback_mock.get_counter() == 1, 'should be 1'); +// after_deposit_execution(42, deposit, log_data); +// assert(callback_mock.get_counter() == 2, 'should be 2'); +// } - let callback_mock = deploy_callback_mock(); - deposit.callback_contract = callback_mock.contract_address; - assert(callback_mock.get_counter() == 1, 'should be 1'); - after_deposit_execution(42, deposit, log_data, event_emitter); - assert(callback_mock.get_counter() == 2, 'should be 2'); -} diff --git a/src/tests/config/test_config.cairo b/tests/config/test_config.cairo similarity index 91% rename from src/tests/config/test_config.cairo rename to tests/config/test_config.cairo index 3228b721..a7fc5577 100644 --- a/src/tests/config/test_config.cairo +++ b/tests/config/test_config.cairo @@ -7,7 +7,6 @@ use result::ResultTrait; use traits::{TryInto, Into}; use starknet::{ContractAddress, get_caller_address, contract_address_const, ClassHash,}; -use debug::PrintTrait; use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; // Local imports. @@ -244,7 +243,7 @@ fn setup_contracts() -> ( // Create a safe dispatcher to interact with the contract. let config = IConfigDispatcher { contract_address: config_address }; - (0x101.try_into().unwrap(), config, role_store, data_store, event_emitter) + (contract_address_const::<'caller'>(), config, role_store, data_store, event_emitter) } /// Utility function to deploy a market factory contract and return its address. @@ -254,20 +253,26 @@ fn deploy_config( event_emitter_address: ContractAddress, ) -> ContractAddress { let contract = declare('Config'); + let caller_address = contract_address_const::<'caller'>(); + let config_address = contract_address_const::<'config'>(); + start_prank(config_address, caller_address); let mut constructor_calldata = array![]; constructor_calldata.append(role_store_address.into()); constructor_calldata.append(data_store_address.into()); constructor_calldata.append(event_emitter_address.into()); - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, config_address).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 caller_address = contract_address_const::<'caller'>(); + let data_store_address = contract_address_const::<'data_store'>(); + start_prank(data_store_address, caller_address); let mut constructor_calldata = array![]; constructor_calldata.append(role_store_address.into()); - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, data_store_address).unwrap() } /// Utility function to deploy a data store contract and return its address. @@ -275,13 +280,19 @@ fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { /// TODO: Find a way to share this code. fn deploy_role_store() -> ContractAddress { let contract = declare('RoleStore'); - let constructor_arguments: @Array:: = @ArrayTrait::new(); - contract.deploy(constructor_arguments).unwrap() + let caller_address = contract_address_const::<'caller'>(); + let role_store_address = contract_address_const::<'role_store'>(); + start_prank(role_store_address, caller_address); + let constructor_arguments: @Array:: = @array![caller_address.into()]; + contract.deploy_at(constructor_arguments, role_store_address).unwrap() } /// Utility function to deploy a `EventEmitter` contract and return its address. fn deploy_event_emitter() -> ContractAddress { let contract = declare('EventEmitter'); + let caller_address = contract_address_const::<'caller'>(); + let event_emitter_address = contract_address_const::<'event_emitter'>(); + start_prank(event_emitter_address, caller_address); let constructor_arguments: @Array:: = @ArrayTrait::new(); - contract.deploy(constructor_arguments).unwrap() + contract.deploy_at(constructor_arguments, event_emitter_address).unwrap() } diff --git a/src/tests/data/test_data_store.cairo b/tests/data/test_data_store.cairo similarity index 90% rename from src/tests/data/test_data_store.cairo rename to tests/data/test_data_store.cairo index bb5021d1..c5cfd9b4 100644 --- a/src/tests/data/test_data_store.cairo +++ b/tests/data/test_data_store.cairo @@ -1,9 +1,10 @@ use starknet::{ContractAddress, contract_address_const}; use satoru::data::data_store::IDataStoreDispatcherTrait; -use satoru::role::role_store::IRoleStoreDispatcherTrait; use satoru::order::order::{Order, OrderType, OrderTrait}; use satoru::tests_lib::{setup, teardown}; +use satoru::utils::i256::{i256, i256_new}; + #[test] fn given_normal_conditions_when_felt252_functions_then_expected_results() { @@ -65,12 +66,12 @@ fn given_normal_conditions_when_bool_functions_then_expected_results() { // Safe to unwrap because we know that the key exists and if it doesn't the test should fail. let value = data_store.get_bool(1); // Check that the value read is true. - assert(value.unwrap() == true, 'Invalid value'); + assert(value == true, 'Invalid value'); // Remove key 1. data_store.remove_bool(1); // Check that the key was removed. - assert(data_store.get_bool(1) == Option::None, 'Key was not deleted'); + assert(data_store.get_bool(1) == false, 'Key was not deleted'); // ********************************************************************************************* // * TEARDOWN * @@ -124,7 +125,7 @@ fn given_normal_conditions_when_u256_functions_then_expected_results() { } #[test] -fn given_normal_conditions_when_i128_functions_then_expected_results() { +fn given_normal_conditions_when_i256_functions_then_expected_results() { // ********************************************************************************************* // * SETUP * // ********************************************************************************************* @@ -135,31 +136,31 @@ fn given_normal_conditions_when_i128_functions_then_expected_results() { // ********************************************************************************************* // Set key 1 to value 42. - data_store.set_i128(1, 42); - let value = data_store.get_i128(1); + data_store.set_i256(1, i256_new(42, false)); + let value = data_store.get_i256(1); // Check that the value read is 42. - assert(value == 42, 'Invalid value'); + assert(value == i256_new(42, false), 'Invalid value'); // Increment key 1 by 5. - let new_value = data_store.increment_i128(1, 5); + let new_value = data_store.increment_i256(1, i256_new(5, false)); // Check that the new value is 47. - assert(new_value == 47, 'Invalid value'); - let value = data_store.get_i128(1); + assert(new_value == i256_new(47, false), 'Invalid value'); + let value = data_store.get_i256(1); // Check that the value read is 47. - assert(value == 47, 'Invalid value'); + assert(value == i256_new(47, false), 'Invalid value'); // Decrement key 1 by 2. - let new_value = data_store.decrement_i128(1, 2); + let new_value = data_store.decrement_i256(1, i256_new(2, false)); // Check that the new value is 45. - assert(new_value == 45, 'Invalid value'); - let value = data_store.get_i128(1); + assert(new_value == i256_new(45, false), 'Invalid value'); + let value = data_store.get_i256(1); // Check that the value read is 45. - assert(value == 45, 'Invalid value'); + assert(value == i256_new(45, false), 'Invalid value'); // Remove key 1. - data_store.remove_i128(1); + data_store.remove_i256(1); // Check that the key was removed. - assert(data_store.get_i128(1) == Default::default(), 'Key was not deleted'); + assert(data_store.get_i256(1) == Default::default(), 'Key was not deleted'); // ********************************************************************************************* // * TEARDOWN * diff --git a/src/tests/data/test_deposit_store.cairo b/tests/data/test_deposit_store.cairo similarity index 81% rename from src/tests/data/test_deposit_store.cairo rename to tests/data/test_deposit_store.cairo index 84b2caa2..ba3c1f5a 100644 --- a/src/tests/data/test_deposit_store.cairo +++ b/tests/data/test_deposit_store.cairo @@ -12,14 +12,20 @@ use snforge_std::{declare, start_prank, ContractClassTrait}; /// 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 caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address: ContractAddress = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); let constructor_calldata = array![role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).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() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address: ContractAddress = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } @@ -31,7 +37,7 @@ fn deploy_role_store() -> ContractAddress { /// * `IDataStoreDispatcher` - The data store dispatcher. /// * `IRoleStoreDispatcher` - The role store dispatcher. fn setup() -> (ContractAddress, IRoleStoreDispatcher, IDataStoreDispatcher) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); let role_store_address = deploy_role_store(); let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; let data_store_address = deploy_data_store(role_store_address); @@ -62,7 +68,7 @@ fn given_normal_conditions_when_set_and_override_new_deposit_then_works() { // Test set_deposit function with a new key. data_store.set_deposit(key, deposit); - let deposit_by_key = data_store.get_deposit(key).unwrap(); + let deposit_by_key = data_store.get_deposit(key); assert(deposit_by_key == deposit, 'Invalid deposit by key'); let deposit_count = data_store.get_deposit_count(); @@ -79,7 +85,7 @@ fn given_normal_conditions_when_set_and_override_new_deposit_then_works() { deposit.market = market; data_store.set_deposit(key, deposit); - let deposit_by_key = data_store.get_deposit(key).unwrap(); + let deposit_by_key = data_store.get_deposit(key); assert(deposit_by_key == deposit, 'Invalid deposit by key'); let account_deposit_count = data_store.get_account_deposit_count(account); @@ -101,10 +107,9 @@ fn given_deposit_account_0_when_set_deposit_then_fails() { let (caller_address, role_store, data_store) = setup(); let key: felt252 = 123456789; - let account = 0.try_into().unwrap(); let mut deposit: Deposit = create_new_deposit( key, - account, + contract_address_const::<0>(), contract_address_const::<'receiver1'>(), contract_address_const::<'market1'>(), deposit_no: 1 @@ -163,7 +168,7 @@ fn given_normal_conditions_when_get_deposit_keys_then_works() { // Then let deposit_by_key = data_store.get_deposit(key); - assert(deposit_by_key.is_none(), 'deposit should be removed'); + assert(deposit_by_key.account.is_zero(), 'deposit should be removed'); let deposit_count = data_store.get_deposit_count(); assert(deposit_count == 0, 'Invalid key deposit count'); @@ -197,7 +202,7 @@ fn given_normal_conditions_when_remove_one_deposit_then_works() { // Then let deposit_by_key = data_store.get_deposit(key); - assert(deposit_by_key.is_none(), 'deposit should be removed'); + assert(deposit_by_key.account.is_zero(), 'deposit should be removed'); let deposit_count = data_store.get_deposit_count(); assert(deposit_count == 0, 'Invalid key deposit count'); @@ -249,10 +254,64 @@ fn given_normal_conditions_when_remove_1_of_n_deposit_then_works() { // Then let deposit_1_by_key = data_store.get_deposit(key_1); - assert(deposit_1_by_key.is_none(), 'deposit1 shouldnt be removed'); + assert(deposit_1_by_key.account.is_zero(), 'deposit1 should be removed'); + + let deposit_2_by_key = data_store.get_deposit(key_2); + assert(deposit_2_by_key.account.is_non_zero(), 'deposit2 shouldnt be removed'); + + let deposit_count = data_store.get_deposit_count(); + assert(deposit_count == 1, 'deposit # should be 1'); + + let account_deposit_count = data_store.get_account_deposit_count(account); + assert(account_deposit_count == 1, 'Acc deposit # should be 1'); + + let account_deposit_keys = data_store.get_account_deposit_keys(account, 0, 10); + assert(account_deposit_keys.len() == 1, 'Acc withdraw # not 1'); + + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_remove_last_deposit_then_works() { + // Setup + let (caller_address, role_store, data_store) = setup(); + let key_1: felt252 = 123456789; + let account = 'account'.try_into().unwrap(); + let mut deposit_1: Deposit = create_new_deposit( + key_1, + account, + contract_address_const::<'receiver1'>(), + contract_address_const::<'market1'>(), + deposit_no: 1 + ); + + let key_2: felt252 = 22222222222; + + let mut deposit_2: Deposit = create_new_deposit( + key_2, + account, + contract_address_const::<'receiver1'>(), + contract_address_const::<'market1'>(), + deposit_no: 1 + ); + + data_store.set_deposit(key_1, deposit_1); + data_store.set_deposit(key_2, deposit_2); + + let deposit_count = data_store.get_deposit_count(); + assert(deposit_count == 2, 'Invalid key deposit count'); + + let account_deposit_count = data_store.get_account_deposit_count(account); + assert(account_deposit_count == 2, 'Acc deposit # should be 2'); + // Given + data_store.remove_deposit(key_2, account); + + // Then + let deposit_1_by_key = data_store.get_deposit(key_1); + assert(deposit_1_by_key.account.is_non_zero(), 'deposit1 shouldnt be removed'); let deposit_2_by_key = data_store.get_deposit(key_2); - assert(deposit_2_by_key.is_some(), 'deposit2 shouldnt be removed'); + assert(deposit_2_by_key.account.is_zero(), 'deposit2 should be removed'); let deposit_count = data_store.get_deposit_count(); assert(deposit_count == 1, 'deposit # should be 1'); @@ -290,7 +349,7 @@ fn given_caller_not_controller_when_remove_deposit_then_fails() { // Then let deposit_by_key = data_store.get_deposit(key); - assert(deposit_by_key.is_none(), 'deposit should be removed'); + assert(deposit_by_key.account.is_zero(), 'deposit should be removed'); let account_deposit_count = data_store.get_account_deposit_count(account); assert(account_deposit_count == 0, 'Acc deposit # should be 0'); let account_deposit_keys = data_store.get_account_deposit_keys(account, 0, 10); @@ -344,10 +403,10 @@ fn given_normal_conditions_when_multiple_get_account_deposit_keys_then_works() { data_store.set_deposit(key_3, deposit_3); data_store.set_deposit(key_4, deposit_4); - let deposit_by_key3 = data_store.get_deposit(key_3).unwrap(); + let deposit_by_key3 = data_store.get_deposit(key_3); assert(deposit_by_key3 == deposit_3, 'Invalid deposit by key3'); - let deposit_by_key4 = data_store.get_deposit(key_4).unwrap(); + let deposit_by_key4 = data_store.get_deposit(key_4); assert(deposit_by_key4 == deposit_4, 'Invalid deposit by key4'); let deposit_count = data_store.get_deposit_count(); @@ -393,7 +452,7 @@ fn create_new_deposit( account: ContractAddress, receiver: ContractAddress, market: ContractAddress, - deposit_no: u128, + deposit_no: u256, ) -> Deposit { let callback_contract = contract_address_const::<'callback_contract'>(); let ui_fee_receiver = contract_address_const::<'ui_fee_receiver'>(); diff --git a/src/tests/data/test_keys.cairo b/tests/data/test_keys.cairo similarity index 100% rename from src/tests/data/test_keys.cairo rename to tests/data/test_keys.cairo diff --git a/src/tests/data/test_market.cairo b/tests/data/test_market.cairo similarity index 79% rename from src/tests/data/test_market.cairo rename to tests/data/test_market.cairo index 9207a03c..c2bc9be4 100644 --- a/src/tests/data/test_market.cairo +++ b/tests/data/test_market.cairo @@ -3,7 +3,6 @@ use starknet::{ }; use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; use poseidon::poseidon_hash_span; -use debug::PrintTrait; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; @@ -19,7 +18,7 @@ use satoru::market::market::{Market}; /// * `IRoleStoreDispatcher` - The role store dispatcher. /// * `IDataStoreDispatcher` - The data store dispatcher. fn setup() -> (ContractAddress, IRoleStoreDispatcher, IDataStoreDispatcher) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); let role_store_address = deploy_role_store(); let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; let data_store_address = deploy_data_store(role_store_address); @@ -42,8 +41,11 @@ fn setup() -> (ContractAddress, IRoleStoreDispatcher, IDataStoreDispatcher) { /// * `ContractAddress` - The address of the deployed data store contract. fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address: ContractAddress = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); let constructor_calldata = array![role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() } /// Utility function to deploy a role store contract and return its address. @@ -53,16 +55,19 @@ fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { /// * `ContractAddress` - The address of the deployed role store contract. fn deploy_role_store() -> ContractAddress { let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address: ContractAddress = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } #[test] fn given_normal_conditions_when_set_market_new_and_override_then_works() { // Setup let (caller_address, role_store, data_store) = setup(); - let address_zero: ContractAddress = 0.try_into().unwrap(); + let address_zero = contract_address_const::<0>(); - let key: ContractAddress = 123456789.try_into().unwrap(); + let key = contract_address_const::<123456789>(); let mut market = Market { market_token: key, index_token: address_zero, @@ -75,7 +80,7 @@ fn given_normal_conditions_when_set_market_new_and_override_then_works() { // Test set_market function with a new key. data_store.set_market(key, 0, market); - let market_by_key = data_store.get_market(key).unwrap(); + let market_by_key = data_store.get_market(key); assert(market_by_key == market, 'Invalid market by key'); // Update the market using the set_market function and then retrieve it to check the update was successful @@ -83,7 +88,7 @@ fn given_normal_conditions_when_set_market_new_and_override_then_works() { market.index_token = address_one; data_store.set_market(key, 0, market); - let market_by_key = data_store.get_market(key).unwrap(); + let market_by_key = data_store.get_market(key); assert(market_by_key == market, 'Invalid market by key'); assert(market_by_key.index_token == address_one, 'Invalid market value'); @@ -93,9 +98,9 @@ fn given_normal_conditions_when_set_market_new_and_override_then_works() { fn given_normal_conditions_when_set_market_and_get_by_salt_then_works() { // Setup let (caller_address, role_store, data_store) = setup(); - let address_zero: ContractAddress = 0.try_into().unwrap(); + let address_zero = contract_address_const::<0>(); - let key: ContractAddress = 123456789.try_into().unwrap(); + let key = contract_address_const::<123456789>(); let mut market = Market { market_token: key, index_token: address_zero, @@ -110,7 +115,7 @@ fn given_normal_conditions_when_set_market_and_get_by_salt_then_works() { // Test set_market function with a new key. data_store.set_market(key, salt, market); - let market_by_key = data_store.get_by_salt_market(salt).unwrap(); + let market_by_key = data_store.get_by_salt_market(salt); assert(market_by_key == market, 'Invalid market by key'); teardown(data_store.contract_address); @@ -122,9 +127,9 @@ fn given_not_market_keeper_when_set_market_then_fails() { // Setup let (caller_address, role_store, data_store) = setup(); role_store.revoke_role(caller_address, role::MARKET_KEEPER); - let address_zero: ContractAddress = 0.try_into().unwrap(); + let address_zero = contract_address_const::<0>(); - let key: ContractAddress = 123456789.try_into().unwrap(); + let key = contract_address_const::<123456789>(); let mut market = Market { market_token: key, index_token: address_zero, @@ -144,9 +149,9 @@ fn given_not_market_keeper_when_set_market_then_fails() { fn given_normal_conditions_when_get_market_keys_then_works() { // Setup let (caller_address, role_store, data_store) = setup(); - let address_zero: ContractAddress = 0.try_into().unwrap(); + let address_zero = contract_address_const::<0>(); - let key: ContractAddress = 123456789.try_into().unwrap(); + let key = contract_address_const::<123456789>(); let mut market = Market { market_token: key, index_token: address_zero, @@ -177,9 +182,9 @@ fn given_normal_conditions_when_get_market_keys_then_works() { fn given_normal_conditions_when_remove_only_one_market_then_works() { // Setup let (caller_address, role_store, data_store) = setup(); - let address_zero: ContractAddress = 0.try_into().unwrap(); + let address_zero = contract_address_const::<0>(); - let key: ContractAddress = 123456789.try_into().unwrap(); + let key = contract_address_const::<123456789>(); let mut market = Market { market_token: key, index_token: address_zero, @@ -194,7 +199,7 @@ fn given_normal_conditions_when_remove_only_one_market_then_works() { // Then let market_by_key = data_store.get_market(key); - assert(market_by_key.is_none(), 'market should be removed'); + assert(market_by_key.market_token.is_zero(), 'market should be removed'); teardown(data_store.contract_address); } @@ -203,10 +208,10 @@ fn given_normal_conditions_when_remove_only_one_market_then_works() { fn given_normal_conditions_when_remove_1_of_n_market_then_works() { // Setup let (caller_address, role_store, data_store) = setup(); - let address_zero: ContractAddress = 0.try_into().unwrap(); + let address_zero = contract_address_const::<0>(); let address_one: ContractAddress = 1.try_into().unwrap(); - let key: ContractAddress = 123456789.try_into().unwrap(); + let key = contract_address_const::<123456789>(); let mut market = Market { market_token: key, index_token: address_zero, @@ -230,10 +235,10 @@ fn given_normal_conditions_when_remove_1_of_n_market_then_works() { // Then let market_by_key = data_store.get_market(key); - assert(market_by_key.is_none(), 'market1 shouldnt be removed'); + assert(market_by_key.market_token.is_zero(), 'market1 shouldnt be removed'); let market_2_by_key = data_store.get_market(key_2); - assert(market_2_by_key.is_some(), 'market2 shouldnt be removed'); + assert(market_2_by_key.market_token.is_non_zero(), 'market2 shouldnt be removed'); teardown(data_store.contract_address); } @@ -245,9 +250,9 @@ fn given_caller_not_market_keeper_when_remove_market_then_fails() { // Setup let (caller_address, role_store, data_store) = setup(); role_store.revoke_role(caller_address, role::MARKET_KEEPER); - let address_zero: ContractAddress = 0.try_into().unwrap(); + let address_zero = contract_address_const::<0>(); - let key: ContractAddress = 123456789.try_into().unwrap(); + let key = contract_address_const::<123456789>(); let mut market = Market { market_token: key, index_token: address_zero, diff --git a/src/tests/data/test_order.cairo b/tests/data/test_order.cairo similarity index 84% rename from src/tests/data/test_order.cairo rename to tests/data/test_order.cairo index eaf6fbdc..a278d169 100644 --- a/src/tests/data/test_order.cairo +++ b/tests/data/test_order.cairo @@ -7,7 +7,7 @@ use satoru::order::order::{Order, OrderType, OrderTrait, DecreasePositionSwapTyp use satoru::tests_lib::{setup, teardown}; use satoru::utils::span32::{Span32, Array32Trait}; -use snforge_std::{PrintTrait, declare, start_prank, stop_prank, ContractClassTrait}; +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; #[test] fn given_normal_conditions_when_set_order_new_and_override_then_works() { @@ -32,7 +32,7 @@ fn given_normal_conditions_when_set_order_new_and_override_then_works() { // Test set_order function with a new key. data_store.set_order(key, order); - let order_by_key = data_store.get_order(key).unwrap(); + let order_by_key = data_store.get_order(key); assert(order_by_key == order, 'Invalid order by key'); let order_count = data_store.get_order_count(); @@ -49,7 +49,7 @@ fn given_normal_conditions_when_set_order_new_and_override_then_works() { order.receiver = receiver; data_store.set_order(key, order); - let order_by_key = data_store.get_order(key).unwrap(); + let order_by_key = data_store.get_order(key); assert(order_by_key == order, 'Invalid order by key'); assert(order_by_key.receiver == receiver, 'Invalid order value'); @@ -73,7 +73,7 @@ fn given_order_account_0_when_set_order_then_fails() { let (caller_address, role_store, data_store) = setup(); let key: felt252 = 123456789; - let account = 0.try_into().unwrap(); + let account = contract_address_const::<0>(); let mut order: Order = create_new_order( key, account, @@ -147,7 +147,7 @@ fn given_caller_not_controller_when_get_order_keys_then_fails() { // Then let order_by_key = data_store.get_order(key); - assert(order_by_key.is_none(), 'order should be removed'); + assert(order_by_key.account.is_zero(), 'order should be removed'); let order_count = data_store.get_order_count(); assert(order_count == 0, 'Invalid key order count'); @@ -184,7 +184,7 @@ fn given_normal_conditions_when_remove_only_order_then_works() { // Then let order_by_key = data_store.get_order(key); - assert(order_by_key.is_none(), 'order should be removed'); + assert(order_by_key.account.is_zero(), 'order should be removed'); let order_count = data_store.get_order_count(); assert(order_count == 0, 'Invalid key order count'); @@ -242,10 +242,70 @@ fn given_normal_conditions_when_remove_1_of_n_order_then_works() { // Then let order_1_by_key = data_store.get_order(key_1); - assert(order_1_by_key.is_none(), 'order1 shouldnt be removed'); + assert(order_1_by_key.account.is_zero(), 'order1 should be removed'); + + let order_2_by_key = data_store.get_order(key_2); + assert(order_2_by_key.account.is_non_zero(), 'order2 shouldnt be removed'); + + let order_count = data_store.get_order_count(); + assert(order_count == 1, 'order # should be 1'); + + let account_order_count = data_store.get_account_order_count(account); + assert(account_order_count == 1, 'Acc order # should be 1'); + + let account_order_keys = data_store.get_account_order_keys(account, 0, 10); + assert(account_order_keys.len() == 1, 'Acc withdraw # not 1'); + + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_remove_last_order_then_works() { + // Setup + let (caller_address, role_store, data_store) = setup(); + let key_1: felt252 = 123456789; + let account = 'account'.try_into().unwrap(); + let mut order_1: Order = create_new_order( + key_1, + account, + contract_address_const::<'receiver1'>(), + contract_address_const::<'market1'>(), + contract_address_const::<'token1'>(), + is_long: false, + is_frozen: false, + order_no: 1 + ); + + let key_2: felt252 = 22222222222; + + let mut order_2: Order = create_new_order( + key_2, + account, + contract_address_const::<'receiver1'>(), + contract_address_const::<'market1'>(), + contract_address_const::<'token1'>(), + is_long: false, + is_frozen: false, + order_no: 1 + ); + + data_store.set_order(key_1, order_1); + data_store.set_order(key_2, order_2); + + let order_count = data_store.get_order_count(); + assert(order_count == 2, 'Invalid key order count'); + + let account_order_count = data_store.get_account_order_count(account); + assert(account_order_count == 2, 'Acc order # should be 2'); + // Given + data_store.remove_order(key_2, account); + + // Then + let order_1_by_key = data_store.get_order(key_1); + assert(order_1_by_key.account.is_non_zero(), 'order1 shouldnt be removed'); let order_2_by_key = data_store.get_order(key_2); - assert(order_2_by_key.is_some(), 'order2 shouldnt be removed'); + assert(order_2_by_key.account.is_zero(), 'order2 should be removed'); let order_count = data_store.get_order_count(); assert(order_count == 1, 'order # should be 1'); @@ -286,7 +346,7 @@ fn given_caller_not_controller_when_remove_order_then_fails() { // Then let order_by_key = data_store.get_order(key); - assert(order_by_key.is_none(), 'order should be removed'); + assert(order_by_key.account.is_zero(), 'order should be removed'); let account_order_count = data_store.get_account_order_count(account); assert(account_order_count == 0, 'Acc order # should be 0'); let account_order_keys = data_store.get_account_order_keys(account, 0, 10); @@ -354,10 +414,10 @@ fn given_normal_conditions_when_multiple_account_keys_then_works() { data_store.set_order(key_3, order_3); data_store.set_order(key_4, order_4); - let order_by_key3 = data_store.get_order(key_3).unwrap(); + let order_by_key3 = data_store.get_order(key_3); assert(order_by_key3 == order_3, 'Invalid order by key3'); - let order_by_key4 = data_store.get_order(key_4).unwrap(); + let order_by_key4 = data_store.get_order(key_4); assert(order_by_key4 == order_4, 'Invalid order by key4'); let order_count = data_store.get_order_count(); @@ -416,7 +476,7 @@ fn create_new_order( initial_collateral_token: ContractAddress, is_long: bool, is_frozen: bool, - order_no: u128 + order_no: u256 ) -> Order { let order_type = OrderType::StopLossDecrease; let decrease_position_swap_type = DecreasePositionSwapType::NoSwap(()); @@ -430,7 +490,7 @@ fn create_new_order( let initial_collateral_delta_amount = 1000 * order_no; let trigger_price = 11111 * order_no; let acceptable_price = 11111 * order_no; - let execution_fee: u128 = 10 * order_no.into(); + let execution_fee: u256 = 10 * order_no.into(); let min_output_amount = 10 * order_no; let updated_at_block = 1; diff --git a/src/tests/data/test_position.cairo b/tests/data/test_position.cairo similarity index 82% rename from src/tests/data/test_position.cairo rename to tests/data/test_position.cairo index 0d6adda8..c7ae87aa 100644 --- a/src/tests/data/test_position.cairo +++ b/tests/data/test_position.cairo @@ -6,7 +6,7 @@ use satoru::role::role; use satoru::position::position::Position; use satoru::tests_lib::{setup, teardown}; -use snforge_std::{PrintTrait, declare, start_prank, stop_prank, ContractClassTrait}; +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; #[test] fn given_normal_conditions_when_set_position_new_and_override_then_works() { @@ -29,7 +29,7 @@ fn given_normal_conditions_when_set_position_new_and_override_then_works() { // Test set_position function with a new key. data_store.set_position(key, position); - let position_by_key = data_store.get_position(key).unwrap(); + let position_by_key = data_store.get_position(key); assert(position_by_key == position, 'Invalid position by key'); let position_count = data_store.get_position_count(); @@ -46,7 +46,7 @@ fn given_normal_conditions_when_set_position_new_and_override_then_works() { position.market = market; data_store.set_position(key, position); - let position_by_key = data_store.get_position(key).unwrap(); + let position_by_key = data_store.get_position(key); assert(position_by_key == position, 'Invalid position by key'); let account_position_count = data_store.get_account_position_count(account); @@ -63,13 +63,13 @@ fn given_normal_conditions_when_set_position_new_and_override_then_works() { #[test] -#[should_panic(expected: ('position account cant be 0',))] +#[should_panic(expected: ('position_account_cant_be_0',))] fn given_position_account_0_when_set_position_then_fails() { // Setup let (caller_address, role_store, data_store) = setup(); let key: felt252 = 123456789; - let account = 0.try_into().unwrap(); + let account = contract_address_const::<0>(); let mut position: Position = create_new_position( key, account, @@ -137,7 +137,7 @@ fn given_normal_conditions_when_get_position_keys_then_works() { // Then let position_by_key = data_store.get_position(key); - assert(position_by_key.is_none(), 'position should be removed'); + assert(position_by_key.account.is_zero(), 'position should be removed'); let position_count = data_store.get_position_count(); assert(position_count == 0, 'Invalid key position count'); @@ -172,7 +172,7 @@ fn given_normal_conditions_when_remove_only_position_then_works() { // Then let position_by_key = data_store.get_position(key); - assert(position_by_key.is_none(), 'position should be removed'); + assert(position_by_key.account.is_zero(), 'position should be removed'); let position_count = data_store.get_position_count(); assert(position_count == 0, 'Invalid key position count'); @@ -225,10 +225,65 @@ fn given_normal_conditions_when_remove_1_of_n_position_then_works() { // Then let position_1_by_key = data_store.get_position(key_1); - assert(position_1_by_key.is_none(), 'position1 shouldnt be removed'); + assert(position_1_by_key.account.is_zero(), 'position1 should be removed'); + + let position_2_by_key = data_store.get_position(key_2); + assert(position_2_by_key.account.is_non_zero(), 'position2 shouldnt be removed'); + + let position_count = data_store.get_position_count(); + assert(position_count == 1, 'position # should be 1'); + + let account_position_count = data_store.get_account_position_count(account); + assert(account_position_count == 1, 'Acc position # should be 1'); + + let account_position_keys = data_store.get_account_position_keys(account, 0, 10); + assert(account_position_keys.len() == 1, 'Acc position # not 1'); + + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_remove_last_position_then_works() { + // Setup + let (caller_address, role_store, data_store) = setup(); + let key_1: felt252 = 123456789; + let account = 'account'.try_into().unwrap(); + let mut position_1: Position = create_new_position( + key_1, + account, + contract_address_const::<'market1'>(), + contract_address_const::<'token1'>(), + is_long: false, + position_no: 1 + ); + + let key_2: felt252 = 22222222222; + let mut position_2: Position = create_new_position( + key_2, + account, + contract_address_const::<'market1'>(), + contract_address_const::<'token1'>(), + is_long: false, + position_no: 1 + ); + + data_store.set_position(key_1, position_1); + data_store.set_position(key_2, position_2); + + let position_count = data_store.get_position_count(); + assert(position_count == 2, 'Invalid key position count'); + + let account_position_count = data_store.get_account_position_count(account); + assert(account_position_count == 2, 'Acc position # should be 2'); + // Given + data_store.remove_position(key_2, account); + + // Then + let position_1_by_key = data_store.get_position(key_1); + assert(position_1_by_key.account.is_non_zero(), 'position1 shouldnt be removed'); let position_2_by_key = data_store.get_position(key_2); - assert(position_2_by_key.is_some(), 'position2 shouldnt be removed'); + assert(position_2_by_key.account.is_zero(), 'position2 should be removed'); let position_count = data_store.get_position_count(); assert(position_count == 1, 'position # should be 1'); @@ -267,7 +322,7 @@ fn given_caller_not_controller_when_remove_1_of_n_position_then_fails() { // Then let position_by_key = data_store.get_position(key); - assert(position_by_key.is_none(), 'position should be removed'); + assert(position_by_key.account.is_zero(), 'position should be removed'); let account_position_count = data_store.get_account_position_count(account); assert(account_position_count == 0, 'Acc position # should be 0'); let account_position_keys = data_store.get_account_position_keys(account, 0, 10); @@ -326,10 +381,10 @@ fn given_caller_not_controller_when_multiple_account_keys_then_fails() { data_store.set_position(key_3, position_3); data_store.set_position(key_4, position_4); - let position_by_key3 = data_store.get_position(key_3).unwrap(); + let position_by_key3 = data_store.get_position(key_3); assert(position_by_key3 == position_3, 'Invalid position by key3'); - let position_by_key4 = data_store.get_position(key_4).unwrap(); + let position_by_key4 = data_store.get_position(key_4); assert(position_by_key4 == position_4, 'Invalid position by key4'); let position_count = data_store.get_position_count(); @@ -376,7 +431,7 @@ fn create_new_position( market: ContractAddress, collateral_token: ContractAddress, is_long: bool, - position_no: u128 + position_no: u256 ) -> Position { let size_in_usd = 1000 * position_no; let size_in_tokens = 1000 * position_no; diff --git a/src/tests/data/test_withdrawal.cairo b/tests/data/test_withdrawal.cairo similarity index 79% rename from src/tests/data/test_withdrawal.cairo rename to tests/data/test_withdrawal.cairo index b3c0b7cf..88f4ad3c 100644 --- a/src/tests/data/test_withdrawal.cairo +++ b/tests/data/test_withdrawal.cairo @@ -8,7 +8,6 @@ use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::role::role; use satoru::withdrawal::withdrawal::Withdrawal; use satoru::utils::span32::{Span32, Array32Trait}; -use debug::PrintTrait; /// Utility function to setup the test environment. /// @@ -18,7 +17,7 @@ use debug::PrintTrait; /// * `IRoleStoreDispatcher` - The role store dispatcher. /// * `IDataStoreDispatcher` - The data store dispatcher. fn setup() -> (ContractAddress, IRoleStoreDispatcher, IDataStoreDispatcher) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); let role_store_address = deploy_role_store(); let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; let data_store_address = deploy_data_store(role_store_address); @@ -40,8 +39,11 @@ fn setup() -> (ContractAddress, IRoleStoreDispatcher, IDataStoreDispatcher) { /// * `ContractAddress` - The address of the deployed data store contract. fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address: ContractAddress = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); let constructor_calldata = array![role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() } /// Utility function to deploy a role store contract and return its address. @@ -51,7 +53,10 @@ fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { /// * `ContractAddress` - The address of the deployed role store contract. fn deploy_role_store() -> ContractAddress { let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address: ContractAddress = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } #[test] @@ -59,7 +64,6 @@ fn given_normal_conditions_when_set_withdrawal_new_and_override_then_works() { // Setup let (caller_address, role_store, data_store) = setup(); let account = 'account'.try_into().unwrap(); - // TODO make these Span32 let long_token_swap_path: Span32 = array![ 1.try_into().unwrap(), 2.try_into().unwrap(), 3.try_into().unwrap() ] @@ -92,7 +96,7 @@ fn given_normal_conditions_when_set_withdrawal_new_and_override_then_works() { // Test set_withdrawal function with a new key. data_store.set_withdrawal(key, withdrawal); - let withdrawal_by_key = data_store.get_withdrawal(key).unwrap(); + let withdrawal_by_key = data_store.get_withdrawal(key); assert(withdrawal_by_key == withdrawal, 'Invalid withdrawal by key'); let account_withdrawal_count = data_store.get_account_withdrawal_count(account); @@ -106,7 +110,7 @@ fn given_normal_conditions_when_set_withdrawal_new_and_override_then_works() { withdrawal.receiver = receiver; data_store.set_withdrawal(key, withdrawal); - let withdrawal_by_key = data_store.get_withdrawal(key).unwrap(); + let withdrawal_by_key = data_store.get_withdrawal(key); assert(withdrawal_by_key == withdrawal, 'Invalid withdrawal by key'); assert(withdrawal_by_key.receiver == receiver, 'Invalid withdrawal value'); @@ -125,8 +129,7 @@ fn given_normal_conditions_when_set_withdrawal_new_and_override_then_works() { fn given_withdrawal_account_0_when_set_withdrawal_then_fails() { // Setup let (caller_address, role_store, data_store) = setup(); - let account = 0.try_into().unwrap(); - // TODO make these Span32 + let account = contract_address_const::<0>(); let long_token_swap_path: Span32 = array![ 1.try_into().unwrap(), 2.try_into().unwrap(), 3.try_into().unwrap() ] @@ -212,7 +215,6 @@ fn given_caller_not_controller_when_get_withdrawal_keys_then_fails() { let (caller_address, role_store, data_store) = setup(); let account = 'account'.try_into().unwrap(); role_store.revoke_role(caller_address, role::CONTROLLER); - // TODO make these Span32 let long_token_swap_path: Span32 = array![ 1.try_into().unwrap(), 2.try_into().unwrap(), 3.try_into().unwrap() ] @@ -246,7 +248,7 @@ fn given_caller_not_controller_when_get_withdrawal_keys_then_fails() { // Then let withdrawal_by_key = data_store.get_withdrawal(key); - assert(withdrawal_by_key.is_none(), 'withdrawal should be removed'); + assert(withdrawal_by_key.account.is_zero(), 'withdrawal should be removed'); let account_withdrawal_count = data_store.get_account_withdrawal_count(account); assert(account_withdrawal_count == 0, 'Acc withdrawal # should be 0'); let account_withdrawal_keys = data_store.get_account_withdrawal_keys(account, 0, 10); @@ -260,7 +262,6 @@ fn given_normal_conditions_when_remove_only_withdrawal_then_works() { // Setup let (caller_address, role_store, data_store) = setup(); let account = 'account'.try_into().unwrap(); - // TODO make these Span32 let long_token_swap_path: Span32 = array![ 1.try_into().unwrap(), 2.try_into().unwrap(), 3.try_into().unwrap() ] @@ -295,7 +296,7 @@ fn given_normal_conditions_when_remove_only_withdrawal_then_works() { // Then let withdrawal_by_key = data_store.get_withdrawal(key); - assert(withdrawal_by_key.is_none(), 'withdrawal should be removed'); + assert(withdrawal_by_key.account.is_zero(), 'withdrawal should be removed'); let account_withdrawal_count = data_store.get_account_withdrawal_count(account); assert(account_withdrawal_count == 0, 'Acc withdrawal # should be 0'); @@ -311,7 +312,6 @@ fn given_normal_conditions_when_remove_1_of_n_withdrawal_then_works() { // Setup let (caller_address, role_store, data_store) = setup(); let account = 'account'.try_into().unwrap(); - // TODO make these Span32 let long_token_swap_path: Span32 = array![ 1.try_into().unwrap(), 2.try_into().unwrap(), 3.try_into().unwrap() ] @@ -365,10 +365,82 @@ fn given_normal_conditions_when_remove_1_of_n_withdrawal_then_works() { // Then let withdrawal_1_by_key = data_store.get_withdrawal(key_1); - assert(withdrawal_1_by_key.is_none(), 'withdrawal1 shouldnt be removed'); + assert(withdrawal_1_by_key.account.is_zero(), 'withdrawal1 should be removed'); + + let withdrawal_2_by_key = data_store.get_withdrawal(key_2); + assert(withdrawal_2_by_key.account.is_non_zero(), 'withdrawal2 shouldnt be removed'); + + let account_withdrawal_count = data_store.get_account_withdrawal_count(account); + assert(account_withdrawal_count == 1, 'Acc withdrawal # should be 1'); + + let account_withdrawal_keys = data_store.get_account_withdrawal_keys(account, 0, 10); + assert(account_withdrawal_keys.len() == 1, 'Acc withdraw # not 1'); + + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_remove_last_withdrawal_then_works() { + // Setup + let (caller_address, role_store, data_store) = setup(); + let account = 'account'.try_into().unwrap(); + let long_token_swap_path: Span32 = array![ + 1.try_into().unwrap(), 2.try_into().unwrap(), 3.try_into().unwrap() + ] + .span32(); + let short_token_swap_path: Span32 = array![ + 4.try_into().unwrap(), 5.try_into().unwrap(), 6.try_into().unwrap() + ] + .span32(); + + let key_1: felt252 = 123456789; + let mut withdrawal_1 = Withdrawal { + key: key_1, + account, + receiver: account, + callback_contract: 1.try_into().unwrap(), + ui_fee_receiver: 1.try_into().unwrap(), + market: 1.try_into().unwrap(), + long_token_swap_path, + short_token_swap_path, + market_token_amount: 1, + min_long_token_amount: 1, + min_short_token_amount: 1, + updated_at_block: 1, + execution_fee: 1, + callback_gas_limit: 1, + }; + + let key_2: felt252 = 987654321; + let mut withdrawal_2 = Withdrawal { + key: key_2, + account, + receiver: account, + callback_contract: 1.try_into().unwrap(), + ui_fee_receiver: 1.try_into().unwrap(), + market: 1.try_into().unwrap(), + long_token_swap_path, + short_token_swap_path, + market_token_amount: 1, + min_long_token_amount: 1, + min_short_token_amount: 1, + updated_at_block: 1, + execution_fee: 1, + callback_gas_limit: 1, + }; + + data_store.set_withdrawal(key_1, withdrawal_1); + data_store.set_withdrawal(key_2, withdrawal_2); + + // Given + data_store.remove_withdrawal(key_2, account); + + // Then + let withdrawal_1_by_key = data_store.get_withdrawal(key_1); + assert(withdrawal_1_by_key.account.is_non_zero(), 'withdrawal1 shouldnt be removed'); let withdrawal_2_by_key = data_store.get_withdrawal(key_2); - assert(withdrawal_2_by_key.is_some(), 'withdrawal2 shouldnt be removed'); + assert(withdrawal_2_by_key.account.is_zero(), 'withdrawal2 should be removed'); let account_withdrawal_count = data_store.get_account_withdrawal_count(account); assert(account_withdrawal_count == 1, 'Acc withdrawal # should be 1'); @@ -387,7 +459,6 @@ fn given_caller_not_controller_when_remove_withdrawal_then_fails() { let (caller_address, role_store, data_store) = setup(); let account = 'account'.try_into().unwrap(); role_store.revoke_role(caller_address, role::CONTROLLER); - // TODO make these Span32 let long_token_swap_path: Span32 = array![ 1.try_into().unwrap(), 2.try_into().unwrap(), 3.try_into().unwrap() ] @@ -421,7 +492,7 @@ fn given_caller_not_controller_when_remove_withdrawal_then_fails() { // Then let withdrawal_by_key = data_store.get_withdrawal(key); - assert(withdrawal_by_key.is_none(), 'withdrawal should be removed'); + assert(withdrawal_by_key.account.is_zero(), 'withdrawal should be removed'); let account_withdrawal_count = data_store.get_account_withdrawal_count(account); assert(account_withdrawal_count == 0, 'Acc withdrawal # should be 0'); let account_withdrawal_keys = data_store.get_account_withdrawal_keys(account, 0, 10); diff --git a/src/tests/deposit/test_deposit_utils.cairo b/tests/deposit/test_deposit_utils.cairo similarity index 57% rename from src/tests/deposit/test_deposit_utils.cairo rename to tests/deposit/test_deposit_utils.cairo index d1d4bdd3..eec57766 100644 --- a/src/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 = contract_address_const::<'caller'>(); + 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/deposit/test_deposit_vault.cairo b/tests/deposit/test_deposit_vault.cairo new file mode 100644 index 00000000..e4894dc1 --- /dev/null +++ b/tests/deposit/test_deposit_vault.cairo @@ -0,0 +1,243 @@ +//! Test file for `src/deposit/deposit_vault.cairo`. + +// ************************************************************************* +// IMPORTS +// ************************************************************************* +// Core lib imports. +use integer::{u256_from_felt252}; +use result::ResultTrait; +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, + ClassHash, +}; +use snforge_std::{declare, start_prank, stop_prank, start_mock_call, ContractClassTrait}; +use traits::{TryInto, Into}; + +// Local imports. +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); +} + +#[test] +fn given_normal_conditions_when_transfer_out_then_works() { + let (_, receiver_address, _, data_store, deposit_vault, erc20) = setup(); + + let amount_to_transfer: u256 = 100; + deposit_vault.transfer_out(erc20.contract_address, receiver_address, amount_to_transfer); + + // 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.try_into().expect('u256 into felt failed') + ); + assert(contract_balance == expected_balance, 'transfer_out failed'); + + // 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'); + + teardown(data_store, deposit_vault); +} + +#[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(); + + let amount_to_transfer: u256 = u256_from_felt252(INITIAL_TOKENS_MINTED + 1); + deposit_vault.transfer_out(erc20.contract_address, receiver_address, amount_to_transfer); + + teardown(data_store, deposit_vault); +} + +#[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_u256); + 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_u256); + teardown(data_store, deposit_vault); +} + +#[test] +fn given_normal_conditions_when_record_transfer_in_then_works() { + let (_, _, _, data_store, deposit_vault, erc20) = setup(); + + let initial_balance: u256 = u256_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u256 = 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: u256 = u256_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u256 = deposit_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + let tokens_transfered_in: u256 = 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: u256 = 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: ('u256_sub Overflow',))] +fn given_less_balance_when_2nd_record_transfer_in_then_fails() { + let (_, _, _, data_store, deposit_vault, erc20) = setup(); + + let initial_balance: u256 = u256_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u256 = deposit_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + let tokens_transfered_out: u256 = 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); +} + +// ********************************************************************************************* +// * 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 +) { + // get caller_address, role store and data_store from tests_lib::setup() + let (caller_address, role_store, data_store) = tests_lib::setup(); + + // get receiver_address + let receiver_address: ContractAddress = 0x202.try_into().unwrap(); + + // 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 }; + + // 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 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( + data_store_address: ContractAddress, role_store_address: ContractAddress +) -> ContractAddress { + 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 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() +} + +// ********************************************************************************************* +// * TEARDOWN * +// ********************************************************************************************* +fn teardown(data_store: IDataStoreDispatcher, deposit_vault: IDepositVaultDispatcher) { + tests_lib::teardown(data_store.contract_address); + stop_prank(deposit_vault.contract_address); +} diff --git a/src/tests/deposit/test_execute_deposit_utils.cairo b/tests/deposit/test_execute_deposit_utils.cairo similarity index 100% rename from src/tests/deposit/test_execute_deposit_utils.cairo rename to tests/deposit/test_execute_deposit_utils.cairo diff --git a/src/tests/event/test_adl_events_emitted.cairo b/tests/event/test_adl_events_emitted.cairo similarity index 70% rename from src/tests/event/test_adl_events_emitted.cairo rename to tests/event/test_adl_events_emitted.cairo index 2131d93d..1222c9f6 100644 --- a/src/tests/event/test_adl_events_emitted.cairo +++ b/tests/event/test_adl_events_emitted.cairo @@ -5,7 +5,9 @@ use snforge_std::{ }; use option::OptionTrait; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; use satoru::tests_lib::setup_event_emitter; @@ -25,18 +27,9 @@ fn given_normal_conditions_when_emit_adl_state_updated_then_works() { let market: ContractAddress = contract_address_const::<'market'>(); let is_long: bool = true; let pnl_to_pool_factor: felt252 = 1; - let max_pnl_factor: u128 = 10; + let max_pnl_factor: u256 = 10; let should_enable_adl: bool = false; - // Create the expected data. - let expected_data: Array = array![ - market.into(), - is_long.into(), - pnl_to_pool_factor, - max_pnl_factor.into(), - should_enable_adl.into() - ]; - // Emit the event. event_emitter .emit_adl_state_updated( @@ -46,12 +39,18 @@ fn given_normal_conditions_when_emit_adl_state_updated_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AdlStateUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AdlStateUpdated( + EventEmitter::AdlStateUpdated { + market: market.into(), + is_long: is_long.into(), + pnl_to_pool_factor: pnl_to_pool_factor, + max_pnl_factor: max_pnl_factor.into(), + should_enable_adl: should_enable_adl.into() + } + ) + ) ] ); // Assert there are no more events. diff --git a/src/tests/event/test_callback_events_emitted.cairo b/tests/event/test_callback_events_emitted.cairo similarity index 83% rename from src/tests/event/test_callback_events_emitted.cairo rename to tests/event/test_callback_events_emitted.cairo index 590377b3..22d91874 100644 --- a/src/tests/event/test_callback_events_emitted.cairo +++ b/tests/event/test_callback_events_emitted.cairo @@ -6,7 +6,16 @@ use snforge_std::{ use option::OptionTrait; use satoru::tests_lib::setup_event_emitter; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; + +use satoru::event::event_emitter::EventEmitter::{ + AfterDepositExecutionError, AfterDepositCancellationError, AfterWithdrawalExecutionError, + AfterWithdrawalCancellationError, AfterOrderExecutionError, AfterOrderCancellationError, + AfterOrderFrozenError +}; + use satoru::deposit::deposit::Deposit; use satoru::withdrawal::withdrawal::Withdrawal; use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; @@ -28,22 +37,18 @@ fn given_normal_conditions_when_emit_after_deposit_execution_error_then_works() let key = 'deposit_execution_error'; let deposit_data: Deposit = create_dummy_deposit(key); - // Create the expected data. - let mut expected_data: Array = array![key]; - deposit_data.serialize(ref expected_data); - // Emit the event. event_emitter.emit_after_deposit_execution_error(key, deposit_data); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AfterDepositExecutionError', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AfterDepositExecutionError( + AfterDepositExecutionError { key: key, deposit: deposit_data } + ) + ) ] ); // Assert there are no more events. @@ -66,22 +71,18 @@ fn given_normal_conditions_when_emit_after_deposit_cancellation_error_then_works let key = 'deposit_cancellation_error'; let deposit_data: Deposit = create_dummy_deposit(key); - // Create the expected data. - let mut expected_data: Array = array![key]; - deposit_data.serialize(ref expected_data); - // Emit the event. event_emitter.emit_after_deposit_cancellation_error(key, deposit_data); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AfterDepositCancellationError', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AfterDepositCancellationError( + AfterDepositCancellationError { key: key, deposit: deposit_data } + ) + ) ] ); // Assert there are no more events. @@ -104,22 +105,18 @@ fn given_normal_conditions_when_emit_after_withdrawal_execution_error_then_works let key = 'withdrawal_execution_error'; let withdrawal_data: Withdrawal = create_dummy_withdrawal(); - // Create the expected data. - let mut expected_data: Array = array![key]; - withdrawal_data.serialize(ref expected_data); - // Emit the event. event_emitter.emit_after_withdrawal_execution_error(key, withdrawal_data); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AfterWithdrawalExecutionError', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AfterWithdrawalExecutionError( + AfterWithdrawalExecutionError { key: key, withdrawal: withdrawal_data } + ) + ) ] ); // Assert there are no more events. @@ -142,22 +139,18 @@ fn given_normal_conditions_when_emit_after_withdrawal_cancellation_error_then_wo let key = 'withdrawal_cancel_error'; let withdrawal_data: Withdrawal = create_dummy_withdrawal(); - // Create the expected data. - let mut expected_data: Array = array![key]; - withdrawal_data.serialize(ref expected_data); - // Emit the event. event_emitter.emit_after_withdrawal_cancellation_error(key, withdrawal_data); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AfterWithdrawalCancelError', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AfterWithdrawalCancellationError( + AfterWithdrawalCancellationError { key: key, withdrawal: withdrawal_data } + ) + ) ] ); // Assert there are no more events. @@ -190,12 +183,12 @@ fn given_normal_conditions_when_emit_after_order_execution_error_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AfterOrderExecutionError', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AfterOrderExecutionError( + AfterOrderExecutionError { key: key, order: order_data } + ) + ) ] ); // Assert there are no more events. @@ -218,22 +211,18 @@ fn given_normal_conditions_when_emit_after_order_cancellation_error_then_works() let key = 'order_cancellation_error'; let order_data: Order = create_dummy_order(key); - // Create the expected data. - let mut expected_data: Array = array![key]; - order_data.serialize(ref expected_data); - // Emit the event. event_emitter.emit_after_order_cancellation_error(key, order_data); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AfterOrderCancellationError', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AfterOrderCancellationError( + AfterOrderCancellationError { key: key, order: order_data } + ) + ) ] ); // Assert there are no more events. @@ -256,22 +245,18 @@ fn given_normal_conditions_when_emit_after_order_frozen_error_then_works() { let key = 'order_frozen_error'; let order_data: Order = create_dummy_order(key); - // Create the expected data. - let mut expected_data: Array = array![key]; - order_data.serialize(ref expected_data); - // Emit the event. event_emitter.emit_after_order_frozen_error(key, order_data); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AfterOrderFrozenError', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AfterOrderFrozenError( + AfterOrderFrozenError { key: key, order: order_data } + ) + ) ] ); // Assert there are no more events. diff --git a/src/tests/event/test_config_events_emitted.cairo b/tests/event/test_config_events_emitted.cairo similarity index 79% rename from src/tests/event/test_config_events_emitted.cairo rename to tests/event/test_config_events_emitted.cairo index b60993d0..8d120141 100644 --- a/src/tests/event/test_config_events_emitted.cairo +++ b/tests/event/test_config_events_emitted.cairo @@ -6,8 +6,11 @@ use snforge_std::{ use satoru::tests_lib::setup_event_emitter; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; +use satoru::event::event_emitter::EventEmitter::{SetBool, SetAddress, SetFelt252, SetUint, SetInt}; #[test] fn given_normal_conditions_when_emit_set_bool_then_works() { @@ -26,20 +29,18 @@ fn given_normal_conditions_when_emit_set_bool_then_works() { let data = array!['0x01']; let value = true; - // Create the expected data. - let mut expected_data: Array = array![key]; - data.serialize(ref expected_data); - expected_data.append(value.into()); - // Emit the event. event_emitter.emit_set_bool(key, data.span(), value); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, name: 'SetBool', keys: array![], data: expected_data - } + ( + contract_address, + EventEmitter::Event::SetBool( + SetBool { key: key, data_bytes: data.span(), value: value } + ) + ) ] ); // Assert there are no more events. @@ -63,20 +64,18 @@ fn given_normal_conditions_when_emit_set_address_then_works() { let data = array!['0x01']; let value = contract_address_const::<'dummy_address'>(); - // Create the expected data. - let mut expected_data: Array = array![key]; - data.serialize(ref expected_data); - expected_data.append(value.into()); - // Emit the event. event_emitter.emit_set_address(key, data.span(), value); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, name: 'SetAddress', keys: array![], data: expected_data - } + ( + contract_address, + EventEmitter::Event::SetAddress( + SetAddress { key: key, data_bytes: data.span(), value: value } + ) + ) ] ); // Assert there are no more events. @@ -100,20 +99,18 @@ fn given_normal_conditions_when_emit_set_felt252_then_works() { let data = array!['0x01']; let value = 'bytes32'; - // Create the expected data. - let mut expected_data: Array = array![key]; - data.serialize(ref expected_data); - expected_data.append(value.into()); - // Emit the event. event_emitter.emit_set_felt252(key, data.span(), value); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, name: 'SetFelt252', keys: array![], data: expected_data - } + ( + contract_address, + EventEmitter::Event::SetFelt252( + SetFelt252 { key: key, data_bytes: data.span(), value: value } + ) + ) ] ); // Assert there are no more events. @@ -135,12 +132,7 @@ fn given_normal_conditions_when_emit_set_uint_then_works() { // Create dummy data. let key = 'set_address'; let data = array!['0x01']; - let value: u128 = 10; - - // Create the expected data. - let mut expected_data: Array = array![key]; - data.serialize(ref expected_data); - expected_data.append(value.into()); + let value: u256 = 10; // Emit the event. event_emitter.emit_set_uint(key, data.span(), value); @@ -148,9 +140,12 @@ fn given_normal_conditions_when_emit_set_uint_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, name: 'SetUint', keys: array![], data: expected_data - } + ( + contract_address, + EventEmitter::Event::SetUint( + SetUint { key: key, data_bytes: data.span(), value: value } + ) + ) ] ); // Assert there are no more events. @@ -175,20 +170,18 @@ fn given_normal_conditions_when_emit_set_int_then_works() { let data = array!['0x01']; let value = -10; - // Create the expected data. - let mut expected_data: Array = array![key]; - data.serialize(ref expected_data); - expected_data.append(value); - // Emit the event. event_emitter.emit_set_int(key, data.span(), value); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, name: 'SetInt', keys: array![], data: expected_data - } + ( + contract_address, + EventEmitter::Event::SetInt( + SetInt { key: key, data_bytes: data.span(), value: value } + ) + ) ] ); // Assert there are no more events. diff --git a/tests/event/test_event_utils.cairo b/tests/event/test_event_utils.cairo new file mode 100644 index 00000000..381bf693 --- /dev/null +++ b/tests/event/test_event_utils.cairo @@ -0,0 +1,169 @@ +//! Test file for `src/event/event_utils.cairo`. + +// ************************************************************************* +// IMPORTS +// ************************************************************************* +use starknet::{ + get_caller_address, ContractAddress, Felt252TryIntoContractAddress, ContractAddressIntoFelt252, + contract_address_const +}; +use satoru::event::event_utils::{ + Felt252IntoBool, Felt252IntoContractAddress, I256252DictValue, ContractAddressDictValue, + LogData, LogDataTrait, U256252DictValue, U256IntoFelt252 +}; +use satoru::utils::traits::{ContractAddressDefault}; +use traits::Default; +use satoru::utils::serializable_dict::{ + Item, ItemTrait, SerializableFelt252Dict, SerializableFelt252DictTrait, +}; + +// ********************************************************************************************* +// * TEST LOGIC * +// ********************************************************************************************* + +#[test] +fn test_log_data_default() { + let mut log_data: LogData = Default::default(); + + // try to add things + log_data.address_dict.insert_single('test', contract_address_const::<0>()); + log_data.uint_dict.insert_single('test', 12_u256); + + // assert results OK + let addr_item = log_data.address_dict.get('test').expect('key not found'); + let addr_value = addr_item.unwrap_single(); + assert(addr_value == contract_address_const::<0>(), 'addr value wrong'); + + let uint_item = log_data.uint_dict.get('test').expect('key not found'); + let uint_value = uint_item.unwrap_single(); + assert(uint_value == 12_u256, 'uint value wrong'); +} + +#[test] +fn test_log_data_default_each() { + let mut log_data: LogData = LogData { + address_dict: Default::default(), + uint_dict: Default::default(), + int_dict: Default::default(), + bool_dict: Default::default(), + felt252_dict: Default::default(), + string_dict: Default::default() + }; + + // try to add things + log_data.address_dict.insert_single('test', contract_address_const::<0>()); + log_data.uint_dict.insert_single('test', 12_u256); + + // assert results OK + let addr_item = log_data.address_dict.get('test').expect('key not found'); + let addr_value = addr_item.unwrap_single(); + assert(addr_value == contract_address_const::<0>(), 'addr value wrong'); + + let uint_item = log_data.uint_dict.get('test').expect('key not found'); + let uint_value = uint_item.unwrap_single(); + assert(uint_value == 12_u256, 'uint value wrong'); +} + +#[test] +fn test_log_data_multiple_types() { + let mut log_data: LogData = Default::default(); + + let arr_to_add: Array = array![ + contract_address_const::<'cairo'>(), + contract_address_const::<'starknet'>(), + contract_address_const::<'rust'>() + ]; + + // try to add unique + log_data.address_dict.insert_single('test', contract_address_const::<0>()); + log_data.address_dict.insert_span('test_arr', arr_to_add.span()); + + // assert results OK + let addr_item = log_data.address_dict.get('test').expect('key not found'); + let addr_value = addr_item.unwrap_single(); + assert(addr_value == contract_address_const::<0>(), 'addr value wrong'); + + let addr_span_item: Item = log_data + .address_dict + .get('test_arr') + .expect('key should be in dict'); + let out_span: Span = addr_span_item.unwrap_span(); + assert(out_span.at(0) == arr_to_add.at(0), 'wrong at idx 0'); + assert(out_span.at(1) == arr_to_add.at(1), 'wrong at idx 1'); + assert(out_span.at(2) == arr_to_add.at(2), 'wrong at idx 2'); +} + +#[test] +fn test_log_data_serialization() { + let mut log_data: LogData = Default::default(); + + log_data.address_dict.insert_single('addr_test', contract_address_const::<42>()); + log_data.bool_dict.insert_single('bool_test', false); + log_data.felt252_dict.insert_single('felt_test', 1); + log_data.felt252_dict.insert_single('felt_test_two', 2); + log_data.string_dict.insert_single('string_test', 'hello world'); + log_data + .string_dict + .insert_span('string_arr_test', array!['hello', 'world', 'from', 'starknet'].span()); + + // serialize the data + let mut serialized_data = log_data.serialize_into().span(); + + // deserialize + let mut d_log_data: LogData = LogDataTrait::deserialize(ref serialized_data) + .expect('err while deserializing'); + + // Check the values inserted before + // addr dict + let mut expected_dict = log_data.address_dict; + let mut out_dict = d_log_data.address_dict; + assert_same_single_value_for_dicts(ref expected_dict, ref out_dict, 'addr_test'); + + // bool dict + let mut expected_dict = log_data.bool_dict; + let mut out_dict = d_log_data.bool_dict; + assert_same_single_value_for_dicts(ref expected_dict, ref out_dict, 'bool_test'); + + // felt252 dict + let mut expected_dict = log_data.felt252_dict; + let mut out_dict = d_log_data.felt252_dict; + assert_same_single_value_for_dicts(ref expected_dict, ref out_dict, 'felt_test'); + assert_same_single_value_for_dicts(ref expected_dict, ref out_dict, 'felt_test_two'); + + // string dict + assert(d_log_data.string_dict.contains('string_arr_test'), 'key not found'); + let v: Item = d_log_data.string_dict.get('string_arr_test').unwrap(); + let span_strings: Span = v.unwrap_span(); + assert(span_strings.len() == 4, 'err span len'); + assert(span_strings.at(0) == @'hello', 'err idx 0'); + assert(span_strings.at(1) == @'world', 'err idx 1'); + assert(span_strings.at(2) == @'from', 'err idx 2'); + assert(span_strings.at(3) == @'starknet', 'err idx 3'); +} + + +// ********************************************************************************************* +// * UTILITIES * +// ********************************************************************************************* + +use debug::PrintTrait; + +fn assert_same_single_value_for_dicts< + T, + +Felt252DictValue, + +Drop, + +Copy, + +Into, + +Into, + +PartialEq, +>( + ref lhs: SerializableFelt252Dict, ref rhs: SerializableFelt252Dict, key: felt252 +) { + assert(lhs.contains(key), 'key not found: lhs'); + assert(rhs.contains(key), 'key not found: rhs'); + + let lhs_value: Item = lhs.get(key).unwrap(); + let rhs_value: Item = rhs.get(key).unwrap(); + + assert(lhs_value == rhs_value, 'err value'); +} diff --git a/src/tests/event/test_gas_events_emitted.cairo b/tests/event/test_gas_events_emitted.cairo similarity index 74% rename from src/tests/event/test_gas_events_emitted.cairo rename to tests/event/test_gas_events_emitted.cairo index 188d5984..2ed804b6 100644 --- a/src/tests/event/test_gas_events_emitted.cairo +++ b/tests/event/test_gas_events_emitted.cairo @@ -6,8 +6,11 @@ use snforge_std::{ use satoru::tests_lib::setup_event_emitter; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; +use satoru::event::event_emitter::EventEmitter::{ExecutionFeeRefund, KeeperExecutionFee}; #[test] fn given_normal_conditions_when_emit_execution_fee_refund_then_works() { @@ -23,10 +26,7 @@ fn given_normal_conditions_when_emit_execution_fee_refund_then_works() { // Create dummy data. let receiver = contract_address_const::<'receiver'>(); - let refund_fee_amount: u128 = 1; - - // Create the expected data. - let expected_data: Array = array![receiver.into(), refund_fee_amount.into()]; + let refund_fee_amount: u256 = 1; // Emit the event. event_emitter.emit_execution_fee_refund(receiver, refund_fee_amount); @@ -34,12 +34,14 @@ fn given_normal_conditions_when_emit_execution_fee_refund_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'ExecutionFeeRefund', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::ExecutionFeeRefund( + ExecutionFeeRefund { + receiver: receiver, refund_fee_amount: refund_fee_amount + } + ) + ) ] ); // Assert there are no more events. @@ -60,10 +62,7 @@ fn given_normal_conditions_when_emit_keeper_execution_fee_then_works() { // Create dummy data. let keeper = contract_address_const::<'keeper'>(); - let execution_fee_amount: u128 = 1; - - // Create the expected data. - let expected_data: Array = array![keeper.into(), execution_fee_amount.into()]; + let execution_fee_amount: u256 = 1; // Emit the event. event_emitter.emit_keeper_execution_fee(keeper, execution_fee_amount); @@ -71,12 +70,14 @@ fn given_normal_conditions_when_emit_keeper_execution_fee_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'KeeperExecutionFee', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::KeeperExecutionFee( + KeeperExecutionFee { + keeper: keeper, execution_fee_amount: execution_fee_amount + } + ) + ) ] ); // Assert there are no more events. diff --git a/src/tests/event/test_market_events_emitted.cairo b/tests/event/test_market_events_emitted.cairo similarity index 70% rename from src/tests/event/test_market_events_emitted.cairo rename to tests/event/test_market_events_emitted.cairo index 4abfd328..f4995989 100644 --- a/src/tests/event/test_market_events_emitted.cairo +++ b/tests/event/test_market_events_emitted.cairo @@ -6,8 +6,21 @@ use snforge_std::{ use satoru::tests_lib::setup_event_emitter; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; + +use satoru::event::event_emitter::EventEmitter::{ + MarketPoolValueInfoEvent, PoolAmountUpdated, SwapImpactPoolAmountUpdated, + PositionImpactPoolAmountUpdated, OpenInterestInTokensUpdated, OpenInterestUpdated, + VirtualSwapInventoryUpdated, VirtualPositionInventoryUpdated, CollateralSumUpdated, + CumulativeBorrowingFactorUpdated, FundingFeeAmountPerSizeUpdated, + ClaimableFundingAmountPerSizeUpdated, ClaimableFundingUpdated, FundingFeesClaimed, + ClaimableCollateralUpdated, CollateralClaimed, UiFeeFactorUpdated, MarketCreated +}; + use satoru::market::market_pool_value_info::MarketPoolValueInfo; +use satoru::utils::i256::{i256, i256_new}; #[test] fn given_normal_conditions_when_emit_market_pool_value_info_then_works() { @@ -24,12 +37,7 @@ fn given_normal_conditions_when_emit_market_pool_value_info_then_works() { // Create dummy data. let market = contract_address_const::<'market'>(); let market_pool_value_info: MarketPoolValueInfo = create_dummy_market_pool_value_info(); - let market_tokens_supply: u128 = 1; - - // Create the expected data. - let mut expected_data: Array = array![market.into()]; - market_pool_value_info.serialize(ref expected_data); - expected_data.append(market_tokens_supply.into()); + let market_tokens_supply: u256 = 1; // Emit the event. event_emitter.emit_market_pool_value_info(market, market_pool_value_info, market_tokens_supply); @@ -37,12 +45,16 @@ fn given_normal_conditions_when_emit_market_pool_value_info_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'MarketPoolValueInfoEvent', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::MarketPoolValueInfoEvent( + MarketPoolValueInfoEvent { + market: market, + market_pool_value_info: market_pool_value_info, + market_tokens_supply: market_tokens_supply + } + ) + ) ] ); // Assert there are no more events. @@ -64,13 +76,8 @@ fn given_normal_conditions_when_emit_pool_amount_updated_then_works() { // Create dummy data. let market = contract_address_const::<'market'>(); let token = contract_address_const::<'token'>(); - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), token.into(), delta.into(), next_value.into() - ]; + let delta: i256 = i256_new(1, false); + let next_value: u256 = 2; // Emit the event. event_emitter.emit_pool_amount_updated(market, token, delta, next_value); @@ -78,12 +85,14 @@ fn given_normal_conditions_when_emit_pool_amount_updated_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'PoolAmountUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::PoolAmountUpdated( + PoolAmountUpdated { + market: market, token: token, delta: delta, next_value: next_value + } + ) + ) ] ); // Assert there are no more events. @@ -105,13 +114,8 @@ fn given_normal_conditions_when_emit_swap_impact_pool_amount_updated_then_works( // Create dummy data. let market = contract_address_const::<'market'>(); let token = contract_address_const::<'token'>(); - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), token.into(), delta.into(), next_value.into() - ]; + let delta: i256 = i256_new(1, false); + let next_value: u256 = 2; // Emit the event. event_emitter.emit_swap_impact_pool_amount_updated(market, token, delta, next_value); @@ -119,12 +123,14 @@ fn given_normal_conditions_when_emit_swap_impact_pool_amount_updated_then_works( spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SwapImpactPoolAmountUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SwapImpactPoolAmountUpdated( + SwapImpactPoolAmountUpdated { + market: market, token: token, delta: delta, next_value: next_value + } + ) + ) ] ); // Assert there are no more events. @@ -145,11 +151,8 @@ fn given_normal_conditions_when_emit_position_impact_pool_amount_updated_then_wo // Create dummy data. let market = contract_address_const::<'market'>(); - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![market.into(), delta.into(), next_value.into()]; + let delta: i256 = i256_new(1, false); + let next_value: u256 = 2; // Emit the event. event_emitter.emit_position_impact_pool_amount_updated(market, delta, next_value); @@ -157,12 +160,14 @@ fn given_normal_conditions_when_emit_position_impact_pool_amount_updated_then_wo spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'PositionImpactPoolAmountUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::PositionImpactPoolAmountUpdated( + PositionImpactPoolAmountUpdated { + market: market, delta: delta, next_value: next_value + } + ) + ) ] ); // Assert there are no more events. @@ -185,13 +190,8 @@ fn given_normal_conditions_when_emit_open_interest_in_tokens_updated_then_works( let market = contract_address_const::<'market'>(); let collateral_token = contract_address_const::<'collateral_token'>(); let is_long: bool = true; - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), collateral_token.into(), is_long.into(), delta.into(), next_value.into() - ]; + let delta: i256 = i256_new(1, false); + let next_value: u256 = 2; // Emit the event. event_emitter @@ -200,12 +200,18 @@ fn given_normal_conditions_when_emit_open_interest_in_tokens_updated_then_works( spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'OpenInterestInTokensUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::OpenInterestInTokensUpdated( + OpenInterestInTokensUpdated { + market: market, + collateral_token: collateral_token, + is_long: is_long, + delta: delta, + next_value: next_value + } + ) + ) ] ); // Assert there are no more events. @@ -228,13 +234,8 @@ fn given_normal_conditions_when_emit_open_interest_updated_then_works() { let market = contract_address_const::<'market'>(); let collateral_token = contract_address_const::<'collateral_token'>(); let is_long: bool = true; - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), collateral_token.into(), is_long.into(), delta.into(), next_value.into() - ]; + let delta: i256 = i256_new(1, false); + let next_value: u256 = 2; // Emit the event. event_emitter.emit_open_interest_updated(market, collateral_token, is_long, delta, next_value); @@ -242,12 +243,18 @@ fn given_normal_conditions_when_emit_open_interest_updated_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'OpenInterestUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::OpenInterestUpdated( + OpenInterestUpdated { + market: market, + collateral_token: collateral_token, + is_long: is_long, + delta: delta, + next_value: next_value + } + ) + ) ] ); // Assert there are no more events. @@ -270,13 +277,8 @@ fn given_normal_conditions_when_emit_virtual_swap_inventory_updated_then_works() let market = contract_address_const::<'market'>(); let is_long_token: bool = true; let virtual_market_id = 'virtual_market_id'; - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), is_long_token.into(), virtual_market_id, delta.into(), next_value.into() - ]; + let delta: i256 = i256_new(1, false); + let next_value: u256 = 2; // Emit the event. event_emitter @@ -287,12 +289,18 @@ fn given_normal_conditions_when_emit_virtual_swap_inventory_updated_then_works() spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'VirtualSwapInventoryUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::VirtualSwapInventoryUpdated( + VirtualSwapInventoryUpdated { + market: market, + is_long_token: is_long_token, + virtual_market_id: virtual_market_id, + delta: delta, + next_value: next_value + } + ) + ) ] ); // Assert there are no more events. @@ -314,13 +322,8 @@ fn given_normal_conditions_when_emit_virtual_position_inventory_updated_then_wor // Create dummy data. let token = contract_address_const::<'token'>(); let virtual_token_id = 'virtual_token_id'; - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - token.into(), virtual_token_id, delta.into(), next_value.into() - ]; + let delta: i256 = i256_new(1, false); + let next_value: i256 = i256_new(2, false); // Emit the event. event_emitter @@ -329,12 +332,17 @@ fn given_normal_conditions_when_emit_virtual_position_inventory_updated_then_wor spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'VirtualPositionInventoryUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::VirtualPositionInventoryUpdated( + VirtualPositionInventoryUpdated { + token: token, + virtual_token_id: virtual_token_id, + delta: delta, + next_value: next_value, + } + ) + ) ] ); // Assert there are no more events. @@ -357,13 +365,8 @@ fn given_normal_conditions_when_emit_collateral_sum_updated_then_works() { let market = contract_address_const::<'market'>(); let collateral_token = contract_address_const::<'collateral_token'>(); let is_long: bool = true; - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), collateral_token.into(), is_long.into(), delta.into(), next_value.into() - ]; + let delta: i256 = i256_new(1, false); + let next_value: u256 = 2; // Emit the event. event_emitter.emit_collateral_sum_updated(market, collateral_token, is_long, delta, next_value); @@ -371,12 +374,18 @@ fn given_normal_conditions_when_emit_collateral_sum_updated_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'CollateralSumUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::CollateralSumUpdated( + CollateralSumUpdated { + market: market, + collateral_token: collateral_token, + is_long: is_long, + delta: delta, + next_value: next_value + } + ) + ) ] ); // Assert there are no more events. @@ -399,13 +408,8 @@ fn given_normal_conditions_when_emit_cumulative_borrowing_factor_updated_then_wo let market = contract_address_const::<'market'>(); let is_long: bool = true; - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let mut expected_data: Array = array![ - market.into(), is_long.into(), delta.into(), next_value.into() - ]; + let delta: u256 = 1; + let next_value: u256 = 2; // Emit the event. event_emitter.emit_cumulative_borrowing_factor_updated(market, is_long, delta, next_value); @@ -413,12 +417,14 @@ fn given_normal_conditions_when_emit_cumulative_borrowing_factor_updated_then_wo spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'CumulativeBorrowingFactorUpdatd', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::CumulativeBorrowingFactorUpdated( + CumulativeBorrowingFactorUpdated { + market: market, is_long: is_long, delta: delta, next_value: next_value + } + ) + ) ] ); // Assert there are no more events. @@ -442,13 +448,8 @@ fn given_normal_conditions_when_emit_funding_fee_amount_per_size_updated_then_wo let collateral_token = contract_address_const::<'collateral_token'>(); let is_long: bool = true; - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), collateral_token.into(), is_long.into(), delta.into(), next_value.into() - ]; + let delta: u256 = 1; + let next_value: u256 = 2; // Emit the event. event_emitter @@ -459,12 +460,18 @@ fn given_normal_conditions_when_emit_funding_fee_amount_per_size_updated_then_wo spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'FundingFeeAmountPerSizeUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::FundingFeeAmountPerSizeUpdated( + FundingFeeAmountPerSizeUpdated { + market: market, + collateral_token: collateral_token, + is_long: is_long, + delta: delta, + next_value: next_value + } + ) + ) ] ); // Assert there are no more events. @@ -487,13 +494,8 @@ fn given_normal_conditions_when_emit_claimable_funding_amount_per_size_updated_t let market = contract_address_const::<'market'>(); let collateral_token = contract_address_const::<'collateral_token'>(); let is_long: bool = true; - let delta: u128 = 1; - let next_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), collateral_token.into(), is_long.into(), delta.into(), next_value.into() - ]; + let delta: u256 = 1; + let next_value: u256 = 2; // Emit the event. event_emitter @@ -504,12 +506,18 @@ fn given_normal_conditions_when_emit_claimable_funding_amount_per_size_updated_t spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'ClaimableFundingPerSizeUpdatd', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::ClaimableFundingAmountPerSizeUpdated( + ClaimableFundingAmountPerSizeUpdated { + market: market, + collateral_token: collateral_token, + is_long: is_long, + delta: delta, + next_value: next_value + } + ) + ) ] ); // Assert there are no more events. @@ -532,19 +540,9 @@ fn given_normal_conditions_when_emit_claimable_funding_updated_then_works() { let market = contract_address_const::<'market'>(); let token = contract_address_const::<'token'>(); let account = contract_address_const::<'account'>(); - let delta: u128 = 1; - let next_value: u128 = 2; - let next_pool_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), - token.into(), - account.into(), - delta.into(), - next_value.into(), - next_pool_value.into() - ]; + let delta: u256 = 1; + let next_value: u256 = 2; + let next_pool_value: u256 = 2; // Emit the event. event_emitter @@ -553,12 +551,19 @@ fn given_normal_conditions_when_emit_claimable_funding_updated_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'ClaimableFundingUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::ClaimableFundingUpdated( + ClaimableFundingUpdated { + market: market, + token: token, + account: account, + delta: delta, + next_value: next_value, + next_pool_value: next_pool_value + } + ) + ) ] ); // Assert there are no more events. @@ -582,32 +587,29 @@ fn given_normal_conditions_when_emit_funding_fees_claimed_then_works() { let token = contract_address_const::<'token'>(); let account = contract_address_const::<'account'>(); let receiver = contract_address_const::<'receiver'>(); - let amount: u128 = 1; - let next_pool_value: u128 = 2; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), - token.into(), - account.into(), - receiver.into(), - amount.into(), - next_pool_value.into() - ]; + let amount: u256 = 1; + let next_pool_value: u256 = 2; // Emit the event. event_emitter - .emit_founding_fees_claimed(market, token, account, receiver, amount, next_pool_value); + .emit_funding_fees_claimed(market, token, account, receiver, amount, next_pool_value); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'FundingFeesClaimed', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::FundingFeesClaimed( + FundingFeesClaimed { + market: market, + token: token, + account: account, + receiver: receiver, + amount: amount, + next_pool_value: next_pool_value + } + ) + ) ] ); // Assert there are no more events. @@ -631,21 +633,10 @@ fn given_normal_conditions_when_emit_claimable_collateral_updated_then_works() { let token = contract_address_const::<'token'>(); let account = contract_address_const::<'account'>(); - let time_key: u128 = 1; - let delta: u128 = 2; - let next_value: u128 = 3; - let next_pool_value: u128 = 4; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), - token.into(), - account.into(), - time_key.into(), - delta.into(), - next_value.into(), - next_pool_value.into() - ]; + let time_key: u256 = 1; + let delta: u256 = 2; + let next_value: u256 = 3; + let next_pool_value: u256 = 4; // Emit the event. event_emitter @@ -656,12 +647,20 @@ fn given_normal_conditions_when_emit_claimable_collateral_updated_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'ClaimableCollateralUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::ClaimableCollateralUpdated( + ClaimableCollateralUpdated { + market: market, + token: token, + account: account, + time_key: time_key, + delta: delta, + next_value: next_value, + next_pool_value: next_pool_value + } + ) + ) ] ); // Assert there are no more events. @@ -686,20 +685,9 @@ fn given_normal_conditions_when_emit_collateral_claimed_then_works() { let account = contract_address_const::<'account'>(); let receiver = contract_address_const::<'receiver'>(); - let time_key: u128 = 1; - let amount: u128 = 2; - let next_pool_value: u128 = 3; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), - token.into(), - account.into(), - receiver.into(), - time_key.into(), - amount.into(), - next_pool_value.into() - ]; + let time_key: u256 = 1; + let amount: u256 = 2; + let next_pool_value: u256 = 3; // Emit the event. event_emitter @@ -710,12 +698,20 @@ fn given_normal_conditions_when_emit_collateral_claimed_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'CollateralClaimed', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::CollateralClaimed( + CollateralClaimed { + market: market, + token: token, + account: account, + receiver: receiver, + time_key: time_key, + amount: amount, + next_pool_value: next_pool_value + } + ) + ) ] ); // Assert there are no more events. @@ -736,10 +732,7 @@ fn given_normal_conditions_when_emit_ui_fee_factor_updated_then_works() { // Create dummy data. let account = contract_address_const::<'account'>(); - let ui_fee_factor: u128 = 1; - - // Create the expected data. - let expected_data: Array = array![account.into(), ui_fee_factor.into()]; + let ui_fee_factor: u256 = 1; // Emit the event. event_emitter.emit_ui_fee_factor_updated(account, ui_fee_factor); @@ -747,12 +740,12 @@ fn given_normal_conditions_when_emit_ui_fee_factor_updated_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'UiFeeFactorUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::UiFeeFactorUpdated( + UiFeeFactorUpdated { account: account, ui_fee_factor: ui_fee_factor } + ) + ) ] ); // Assert there are no more events. @@ -779,16 +772,6 @@ fn given_normal_conditions_when_emit_market_created_then_works() { let short_token = contract_address_const::<'short_token'>(); let market_type = 'type'; - // Create the expected data. - let expected_data: Array = array![ - creator.into(), - market_token.into(), - index_token.into(), - long_token.into(), - short_token.into(), - market_type - ]; - // Emit the event. event_emitter .emit_market_created( @@ -798,12 +781,19 @@ fn given_normal_conditions_when_emit_market_created_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'MarketCreated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::MarketCreated( + MarketCreated { + creator: creator, + market_token: market_token, + index_token: index_token, + long_token: long_token, + short_token: short_token, + market_type: market_type + } + ) + ) ] ); // Assert there are no more events. @@ -812,10 +802,10 @@ fn given_normal_conditions_when_emit_market_created_then_works() { fn create_dummy_market_pool_value_info() -> MarketPoolValueInfo { MarketPoolValueInfo { - pool_value: 1, - long_pnl: 2, - short_pnl: 3, - net_pnl: 4, + pool_value: i256_new(1, false), + long_pnl: i256_new(2, false), + short_pnl: i256_new(3, false), + net_pnl: i256_new(4, false), long_token_amount: 5, short_token_amount: 6, long_token_usd: 7, diff --git a/src/tests/event/test_oracle_events_emitted.cairo b/tests/event/test_oracle_events_emitted.cairo similarity index 78% rename from src/tests/event/test_oracle_events_emitted.cairo rename to tests/event/test_oracle_events_emitted.cairo index 565ea443..e9fbaee7 100644 --- a/src/tests/event/test_oracle_events_emitted.cairo +++ b/tests/event/test_oracle_events_emitted.cairo @@ -6,7 +6,11 @@ use snforge_std::{ use satoru::tests_lib::setup_event_emitter; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; + +use satoru::event::event_emitter::EventEmitter::{OraclePriceUpdate, SignerAdded, SignerRemoved}; #[test] @@ -23,27 +27,27 @@ fn given_normal_conditions_when_emit_oracle_price_update_then_works() { // Create dummy data. let token = contract_address_const::<'token'>(); - let min_price: u128 = 1; - let max_price: u128 = 2; + let min_price: u256 = 1; + let max_price: u256 = 2; let is_price_feed: bool = true; - // Create the expected data. - let expected_data: Array = array![ - token.into(), min_price.into(), max_price.into(), is_price_feed.into() - ]; - // Emit the event. event_emitter.emit_oracle_price_update(token, min_price, max_price, is_price_feed); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'OraclePriceUpdate', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::OraclePriceUpdate( + OraclePriceUpdate { + token: token, + min_price: min_price, + max_price: max_price, + is_price_feed: is_price_feed + } + ) + ) ] ); // Assert there are no more events. @@ -65,18 +69,16 @@ fn given_normal_conditions_when_emit_signer_added_then_works() { // Create dummy data. let account = contract_address_const::<'account'>(); - // Create the expected data. - let expected_data: Array = array![account.into()]; - // Emit the event. event_emitter.emit_signer_added(account); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, name: 'SignerAdded', keys: array![], data: expected_data - } + ( + contract_address, + EventEmitter::Event::SignerAdded(SignerAdded { account: account }) + ) ] ); // Assert there are no more events. @@ -98,21 +100,16 @@ fn given_normal_conditions_when_emit_signer_removed_then_works() { // Create dummy data. let account = contract_address_const::<'account'>(); - // Create the expected data. - let expected_data: Array = array![account.into()]; - // Emit the event. event_emitter.emit_signer_removed(account); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SignerRemoved', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SignerRemoved(SignerRemoved { account: account }) + ) ] ); // Assert there are no more events. diff --git a/src/tests/event/test_order_events_emitted.cairo b/tests/event/test_order_events_emitted.cairo similarity index 77% rename from src/tests/event/test_order_events_emitted.cairo rename to tests/event/test_order_events_emitted.cairo index 73c60f68..9463fd00 100644 --- a/src/tests/event/test_order_events_emitted.cairo +++ b/tests/event/test_order_events_emitted.cairo @@ -4,13 +4,20 @@ use snforge_std::{ EventAssertions }; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; + +use satoru::event::event_emitter::EventEmitter::{ + OrderCreated, OrderExecuted, OrderUpdated, OrderSizeDeltaAutoUpdated, + OrderCollateralDeltaAmountAutoUpdated, OrderCancelled, OrderFrozen, +}; + + use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; use satoru::tests_lib::setup_event_emitter; use satoru::utils::span32::{Span32, Array32Trait}; -//TODO: OrderCollatDeltaAmountAutoUpdtd must be renamed back to OrderCollateralDeltaAmountAutoUpdated when string will be allowed as event argument - #[test] fn given_normal_conditions_when_emit_order_created_then_works() { // ********************************************************************************************* @@ -27,10 +34,6 @@ fn given_normal_conditions_when_emit_order_created_then_works() { let key: felt252 = 100; let order: Order = create_dummy_order(key); - // Create the expected data. - let mut expected_data: Array = array![key]; - order.serialize(ref expected_data); - // Emit the event. event_emitter.emit_order_created(key, order); @@ -38,12 +41,10 @@ fn given_normal_conditions_when_emit_order_created_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'OrderCreated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::OrderCreated(OrderCreated { key: key, order: order }) + ) ] ); // Assert there are no more events. @@ -66,10 +67,6 @@ fn given_normal_conditions_when_emit_order_executed_then_works() { let key: felt252 = 100; let secondary_order_type: SecondaryOrderType = SecondaryOrderType::None(()); - // Create the expected data. - let mut expected_data: Array = array![key]; - secondary_order_type.serialize(ref expected_data); - // Emit the event. event_emitter.emit_order_executed(key, secondary_order_type); @@ -77,12 +74,12 @@ fn given_normal_conditions_when_emit_order_executed_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'OrderExecuted', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::OrderExecuted( + OrderExecuted { key: key, secondary_order_type: secondary_order_type } + ) + ) ] ); // Assert there are no more events. @@ -103,19 +100,10 @@ fn given_normal_conditions_when_emit_order_updated_then_works() { // Create dummy data. let key = 100; - let size_delta_usd: u128 = 200; - let acceptable_price: u128 = 300; - let trigger_price: u128 = 400; - let min_output_amount: u128 = 500; - - // Create the expected data. - let expected_data: Array = array![ - key, - size_delta_usd.into(), - acceptable_price.into(), - trigger_price.into(), - min_output_amount.into() - ]; + let size_delta_usd: u256 = 200; + let acceptable_price: u256 = 300; + let trigger_price: u256 = 400; + let min_output_amount: u256 = 500; // Emit the event. event_emitter @@ -127,12 +115,18 @@ fn given_normal_conditions_when_emit_order_updated_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'OrderUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::OrderUpdated( + OrderUpdated { + key: key, + size_delta_usd: size_delta_usd, + acceptable_price: acceptable_price, + trigger_price: trigger_price, + min_output_amount: min_output_amount + } + ) + ) ] ); // Assert there are no more events. @@ -153,13 +147,8 @@ fn given_normal_conditions_when_emit_order_size_delta_auto_updated_then_works() // Create dummy data. let key = 100; - let size_delta_usd: u128 = 200; - let next_size_delta_usd: u128 = 300; - - // Create the expected data. - let expected_data: Array = array![ - key, size_delta_usd.into(), next_size_delta_usd.into(), - ]; + let size_delta_usd: u256 = 200; + let next_size_delta_usd: u256 = 300; // Emit the event. event_emitter.emit_order_size_delta_auto_updated(key, size_delta_usd, next_size_delta_usd); @@ -168,12 +157,16 @@ fn given_normal_conditions_when_emit_order_size_delta_auto_updated_then_works() spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'OrderSizeDeltaAutoUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::OrderSizeDeltaAutoUpdated( + OrderSizeDeltaAutoUpdated { + key: key, + size_delta_usd: size_delta_usd, + next_size_delta_usd: next_size_delta_usd, + } + ) + ) ] ); // Assert there are no more events. @@ -194,13 +187,8 @@ fn given_normal_conditions_when_emit_order_collateral_delta_amount_auto_updated_ // Create dummy data. let key = 100; - let collateral_delta_amount: u128 = 200; - let next_collateral_delta_amount: u128 = 300; - - // Create the expected data. - let expected_data: Array = array![ - key, collateral_delta_amount.into(), next_collateral_delta_amount.into(), - ]; + let collateral_delta_amount: u256 = 200; + let next_collateral_delta_amount: u256 = 300; // Emit the event. event_emitter @@ -212,12 +200,16 @@ fn given_normal_conditions_when_emit_order_collateral_delta_amount_auto_updated_ spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'OrderCollatDeltaAmountAutoUpdtd', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::OrderCollateralDeltaAmountAutoUpdated( + OrderCollateralDeltaAmountAutoUpdated { + key: key, + collateral_delta_amount: collateral_delta_amount, + next_collateral_delta_amount: next_collateral_delta_amount, + } + ) + ) ] ); // Assert there are no more events. @@ -241,10 +233,6 @@ fn given_normal_conditions_when_emit_order_cancelled_then_works() { let reason = 'none'; let reason_bytes = array!['0x00', '0x01']; - // Create the expected data. - let mut expected_data: Array = array![key, reason.into()]; - reason_bytes.serialize(ref expected_data); - // Emit the event. event_emitter.emit_order_cancelled(key, reason, reason_bytes.span()); @@ -252,12 +240,14 @@ fn given_normal_conditions_when_emit_order_cancelled_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'OrderCancelled', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::OrderCancelled( + OrderCancelled { + key: key, reason: reason, reason_bytes: reason_bytes.span(), + } + ) + ) ] ); // Assert there are no more events. @@ -281,10 +271,6 @@ fn given_normal_conditions_when_emit_order_frozen_then_works() { let reason = 'frozen'; let reason_bytes = array!['0x00', '0x01']; - // Create the expected data. - let mut expected_data: Array = array![key, reason.into()]; - reason_bytes.serialize(ref expected_data); - // Emit the event. event_emitter.emit_order_frozen(key, reason, reason_bytes.span()); @@ -292,9 +278,12 @@ fn given_normal_conditions_when_emit_order_frozen_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, name: 'OrderFrozen', keys: array![], data: expected_data - } + ( + contract_address, + EventEmitter::Event::OrderFrozen( + OrderFrozen { key: key, reason: reason, reason_bytes: reason_bytes.span(), } + ) + ) ] ); // Assert there are no more events. diff --git a/tests/event/test_position_events_emitted.cairo b/tests/event/test_position_events_emitted.cairo new file mode 100644 index 00000000..836c284f --- /dev/null +++ b/tests/event/test_position_events_emitted.cairo @@ -0,0 +1,657 @@ +use starknet::{ContractAddress, contract_address_const}; +use snforge_std::{ + declare, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, event_name_hash, Event, + EventAssertions +}; +use satoru::tests_lib::setup_event_emitter; +use satoru::position::{ + position_event_utils::PositionIncreaseParams, position::Position, + position_utils::{DecreasePositionCollateralValues, DecreasePositionCollateralValuesOutput} +}; +use satoru::pricing::position_pricing_utils::{ + PositionFees, PositionUiFees, PositionBorrowingFees, PositionReferralFees, PositionFundingFees +}; +use satoru::order::order::OrderType; +use satoru::price::price::Price; + +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; + +use satoru::event::event_emitter::EventEmitter::{ + PositionIncrease, PositionDecrease, InsolventClose, InsufficientFundingFeePayment, + PositionFeesInfo, PositionFeesCollected +}; + + +use satoru::utils::i256::{i256, i256_new}; + +#[test] +fn given_normal_conditions_when_emit_position_increase_then_works() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let (contract_address, event_emitter) = setup_event_emitter(); + let mut spy = spy_events(SpyOn::One(contract_address)); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // Create a dummy data. + let dummy_position_increase_params = create_dummy_position_increase_params(event_emitter); + + // Emit the event. + event_emitter.emit_position_increase(dummy_position_increase_params); + + // Refetch the data for expected since dummy_position_increase_params was moved + let dummy_position_increase_params = create_dummy_position_increase_params(event_emitter); + + // Assert the event was emitted. + spy + .assert_emitted( + @array![ + ( + contract_address, + EventEmitter::Event::PositionIncrease( + PositionIncrease { + account: dummy_position_increase_params.position.account, + market: dummy_position_increase_params.position.market, + collateral_token: dummy_position_increase_params + .position + .collateral_token, + size_in_usd: dummy_position_increase_params.position.size_in_usd, + size_in_tokens: dummy_position_increase_params.position.size_in_tokens, + collateral_amount: dummy_position_increase_params + .position + .collateral_amount, + borrowing_factor: dummy_position_increase_params + .position + .borrowing_factor, + funding_fee_amount_per_size: dummy_position_increase_params + .position + .funding_fee_amount_per_size, + long_token_claimable_funding_amount_per_size: dummy_position_increase_params + .position + .long_token_claimable_funding_amount_per_size, + short_token_claimable_funding_amount_per_size: dummy_position_increase_params + .position + .short_token_claimable_funding_amount_per_size, + execution_price: dummy_position_increase_params.execution_price, + index_token_price_max: dummy_position_increase_params + .index_token_price + .max, + index_token_price_min: dummy_position_increase_params + .index_token_price + .min, + collateral_token_price_max: dummy_position_increase_params + .collateral_token_price + .max, + collateral_token_price_min: dummy_position_increase_params + .collateral_token_price + .min, + size_delta_usd: dummy_position_increase_params.size_delta_usd, + size_delta_in_tokens: dummy_position_increase_params + .size_delta_in_tokens, + order_type: dummy_position_increase_params.order_type, + collateral_delta_amount: dummy_position_increase_params + .collateral_delta_amount, + price_impact_usd: dummy_position_increase_params.price_impact_usd, + price_impact_amount: dummy_position_increase_params.price_impact_amount, + is_long: dummy_position_increase_params.position.is_long, + order_key: dummy_position_increase_params.order_key, + position_key: dummy_position_increase_params.position_key, + } + ) + ) + ] + ); + // Assert there are no more events. + assert(spy.events.len() == 0, 'There should be no events'); +} + +#[test] +fn given_normal_conditions_when_emit_position_decrease_then_works() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let (contract_address, event_emitter) = setup_event_emitter(); + let mut spy = spy_events(SpyOn::One(contract_address)); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // Create a dummy data. + let dummy_position = create_dummy_position(); + let order_key = 'order_key'; + let position_key = 'position_key'; + let size_delta_usd = 100; + let collateral_delta_amount = 200; + let order_type = OrderType::MarketSwap(()); + let index_token_price = Price { min: 100, max: 100 }; + let collateral_token_price = Price { min: 80, max: 85 }; + let dummy_collateral_values = create_dummy_dec_pos_collateral_values(); + + // Emit the event. + event_emitter + .emit_position_decrease( + order_key, + position_key, + dummy_position, + size_delta_usd, + collateral_delta_amount, + order_type, + dummy_collateral_values, + index_token_price, + collateral_token_price + ); + + // Assert the event was emitted. + spy + .assert_emitted( + @array![ + ( + contract_address, + EventEmitter::Event::PositionDecrease( + PositionDecrease { + account: dummy_position.account, + market: dummy_position.market, + collateral_token: dummy_position.collateral_token, + size_in_usd: dummy_position.size_in_usd, + size_in_tokens: dummy_position.size_in_tokens, + collateral_amount: dummy_position.collateral_amount, + borrowing_factor: dummy_position.borrowing_factor, + funding_fee_amount_per_size: dummy_position.funding_fee_amount_per_size, + long_token_claimable_funding_amount_per_size: dummy_position + .long_token_claimable_funding_amount_per_size, + short_token_claimable_funding_amount_per_size: dummy_position + .short_token_claimable_funding_amount_per_size, + execution_price: dummy_collateral_values.execution_price, + index_token_price_max: index_token_price.max, + index_token_price_min: index_token_price.min, + collateral_token_price_max: collateral_token_price.max, + collateral_token_price_min: collateral_token_price.min, + size_delta_usd: size_delta_usd, + size_delta_in_tokens: dummy_collateral_values.size_delta_in_tokens, + collateral_delta_amount: collateral_delta_amount, + price_impact_diff_usd: dummy_collateral_values.price_impact_diff_usd, + order_type: order_type, + price_impact_usd: dummy_collateral_values.price_impact_usd, + base_pnl_usd: dummy_collateral_values.base_pnl_usd, + uncapped_base_pnl_usd: dummy_collateral_values.uncapped_base_pnl_usd, + is_long: dummy_position.is_long, + order_key: order_key, + position_key: position_key, + } + ) + ) + ] + ); + // Assert there are no more events. + assert(spy.events.len() == 0, 'There should be no events'); +} + + +#[test] +fn given_normal_conditions_when_emit_insolvent_close_then_works() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let (contract_address, event_emitter) = setup_event_emitter(); + let mut spy = spy_events(SpyOn::One(contract_address)); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // Create a dummy data. + let order_key = 'order_key'; + let position_collateral_amount = 100; + let base_pnl_usd = i256_new(50, false); + let remaining_cost_usd = 75; + + // Emit the event. + event_emitter + .emit_insolvent_close_info( + order_key, position_collateral_amount, base_pnl_usd, remaining_cost_usd + ); + + // Assert the event was emitted. + spy + .assert_emitted( + @array![ + ( + contract_address, + EventEmitter::Event::InsolventClose( + InsolventClose { + order_key: order_key, + position_collateral_amount: position_collateral_amount, + base_pnl_usd: base_pnl_usd, + remaining_cost_usd: remaining_cost_usd + } + ) + ) + ] + ); + // Assert there are no more events. + assert(spy.events.len() == 0, 'There should be no events'); +} + + +#[test] +fn given_normal_conditions_when_emit_insufficient_funding_fee_payment_then_works() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let (contract_address, event_emitter) = setup_event_emitter(); + let mut spy = spy_events(SpyOn::One(contract_address)); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // Create a dummy data. + let market = contract_address_const::<'market'>(); + let token = contract_address_const::<'token'>(); + let expected_amount = 100; + let amount_paid_in_collateral_token = 50; + let amount_paid_in_secondary_output_token = 75; + + // Emit the event. + event_emitter + .emit_insufficient_funding_fee_payment( + market, + token, + expected_amount, + amount_paid_in_collateral_token, + amount_paid_in_secondary_output_token + ); + + // Assert the event was emitted. + spy + .assert_emitted( + @array![ + ( + contract_address, + EventEmitter::Event::InsufficientFundingFeePayment( + InsufficientFundingFeePayment { + market: market, + token: token, + expected_amount: expected_amount, + amount_paid_in_collateral_token: amount_paid_in_collateral_token, + amount_paid_in_secondary_output_token: amount_paid_in_secondary_output_token + } + ) + ) + ] + ); + // Assert there are no more events. + assert(spy.events.len() == 0, 'There should be no events'); +} + + +#[test] +fn given_normal_conditions_when_emit_position_fees_collected_then_works() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let (contract_address, event_emitter) = setup_event_emitter(); + let mut spy = spy_events(SpyOn::One(contract_address)); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // Create a dummy data. + let order_key = 'order_key'; + let position_key = 'position_key'; + let market = contract_address_const::<'market'>(); + let collateral_token = contract_address_const::<'collateral_token'>(); + let trade_size_usd = 100; + let is_increase = true; + let dummy_position_fees = create_dummy_position_fees(); + + // Emit the event. + event_emitter + .emit_position_fees_collected( + order_key, + position_key, + market, + collateral_token, + trade_size_usd, + is_increase, + dummy_position_fees + ); + + // Assert the event was emitted. + spy + .assert_emitted( + @array![ + ( + contract_address, + EventEmitter::Event::PositionFeesCollected( + PositionFeesCollected { + order_key: order_key, + position_key: position_key, + referral_code: dummy_position_fees.referral.referral_code, + market: market, + collateral_token: collateral_token, + affiliate: dummy_position_fees.referral.affiliate, + trader: dummy_position_fees.referral.trader, + ui_fee_receiver: dummy_position_fees.ui.ui_fee_receiver, + collateral_token_price_min: dummy_position_fees + .collateral_token_price + .min, + collateral_token_price_max: dummy_position_fees + .collateral_token_price + .max, + trade_size_usd: trade_size_usd, + total_rebate_factor: dummy_position_fees.referral.total_rebate_factor, + trader_discount_factor: dummy_position_fees + .referral + .trader_discount_factor, + total_rebate_amount: dummy_position_fees.referral.total_rebate_amount, + trader_discount_amount: dummy_position_fees + .referral + .trader_discount_amount, + affiliate_reward_amount: dummy_position_fees + .referral + .affiliate_reward_amount, + funding_fee_amount: dummy_position_fees.funding.funding_fee_amount, + claimable_long_token_amount: dummy_position_fees + .funding + .claimable_long_token_amount, + claimable_short_token_amount: dummy_position_fees + .funding + .claimable_short_token_amount, + latest_funding_fee_amount_per_size: dummy_position_fees + .funding + .latest_funding_fee_amount_per_size, + latest_long_token_claimable_funding_amount_per_size: dummy_position_fees + .funding + .latest_long_token_claimable_funding_amount_per_size, + latest_short_token_claimable_funding_amount_per_size: dummy_position_fees + .funding + .latest_short_token_claimable_funding_amount_per_size, + borrowing_fee_usd: dummy_position_fees.borrowing.borrowing_fee_usd, + borrowing_fee_amount: dummy_position_fees + .borrowing + .borrowing_fee_amount, + borrowing_fee_receiver_factor: dummy_position_fees + .borrowing + .borrowing_fee_receiver_factor, + borrowing_fee_amount_for_fee_receiver: dummy_position_fees + .borrowing + .borrowing_fee_amount_for_fee_receiver, + position_fee_factor: dummy_position_fees.position_fee_factor, + protocol_fee_amount: dummy_position_fees.protocol_fee_amount, + position_fee_receiver_factor: dummy_position_fees + .position_fee_receiver_factor, + fee_receiver_amount: dummy_position_fees.fee_receiver_amount, + fee_amount_for_pool: dummy_position_fees.fee_amount_for_pool, + position_fee_amount_for_pool: dummy_position_fees + .position_fee_amount_for_pool, + position_fee_amount: dummy_position_fees.position_fee_amount, + total_cost_amount: dummy_position_fees.total_cost_amount, + ui_fee_receiver_factor: dummy_position_fees.ui.ui_fee_receiver_factor, + ui_fee_amount: dummy_position_fees.ui.ui_fee_amount, + is_increase: is_increase + } + ) + ) + ] + ); + // Assert there are no more events. + assert(spy.events.len() == 0, 'There should be no events'); +} + +#[test] +fn given_normal_conditions_when_emit_position_fees_info_then_works() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let (contract_address, event_emitter) = setup_event_emitter(); + let mut spy = spy_events(SpyOn::One(contract_address)); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // Create a dummy data. + let order_key = 'order_key'; + let position_key = 'position_key'; + let market = contract_address_const::<'market'>(); + let collateral_token = contract_address_const::<'collateral_token'>(); + let trade_size_usd = 100; + let is_increase = true; + let dummy_position_fees = create_dummy_position_fees(); + + // Emit the event. + event_emitter + .emit_position_fees_info( + order_key, + position_key, + market, + collateral_token, + trade_size_usd, + is_increase, + dummy_position_fees + ); + + // Assert the event was emitted. + spy + .assert_emitted( + @array![ + ( + contract_address, + EventEmitter::Event::PositionFeesInfo( + PositionFeesInfo { + order_key: order_key, + position_key: position_key, + referral_code: dummy_position_fees.referral.referral_code, + market: market, + collateral_token: collateral_token, + affiliate: dummy_position_fees.referral.affiliate, + trader: dummy_position_fees.referral.trader, + ui_fee_receiver: dummy_position_fees.ui.ui_fee_receiver, + collateral_token_price_min: dummy_position_fees + .collateral_token_price + .min, + collateral_token_price_max: dummy_position_fees + .collateral_token_price + .max, + trade_size_usd: trade_size_usd, + total_rebate_factor: dummy_position_fees.referral.total_rebate_factor, + trader_discount_factor: dummy_position_fees + .referral + .trader_discount_factor, + total_rebate_amount: dummy_position_fees.referral.total_rebate_amount, + trader_discount_amount: dummy_position_fees + .referral + .trader_discount_amount, + affiliate_reward_amount: dummy_position_fees + .referral + .affiliate_reward_amount, + funding_fee_amount: dummy_position_fees.funding.funding_fee_amount, + claimable_long_token_amount: dummy_position_fees + .funding + .claimable_long_token_amount, + claimable_short_token_amount: dummy_position_fees + .funding + .claimable_short_token_amount, + latest_funding_fee_amount_per_size: dummy_position_fees + .funding + .latest_funding_fee_amount_per_size, + latest_long_token_claimable_funding_amount_per_size: dummy_position_fees + .funding + .latest_long_token_claimable_funding_amount_per_size, + latest_short_token_claimable_funding_amount_per_size: dummy_position_fees + .funding + .latest_short_token_claimable_funding_amount_per_size, + borrowing_fee_usd: dummy_position_fees.borrowing.borrowing_fee_usd, + borrowing_fee_amount: dummy_position_fees + .borrowing + .borrowing_fee_amount, + borrowing_fee_receiver_factor: dummy_position_fees + .borrowing + .borrowing_fee_receiver_factor, + borrowing_fee_amount_for_fee_receiver: dummy_position_fees + .borrowing + .borrowing_fee_amount_for_fee_receiver, + position_fee_factor: dummy_position_fees.position_fee_factor, + protocol_fee_amount: dummy_position_fees.protocol_fee_amount, + position_fee_receiver_factor: dummy_position_fees + .position_fee_receiver_factor, + fee_receiver_amount: dummy_position_fees.fee_receiver_amount, + fee_amount_for_pool: dummy_position_fees.fee_amount_for_pool, + position_fee_amount_for_pool: dummy_position_fees + .position_fee_amount_for_pool, + position_fee_amount: dummy_position_fees.position_fee_amount, + total_cost_amount: dummy_position_fees.total_cost_amount, + ui_fee_receiver_factor: dummy_position_fees.ui.ui_fee_receiver_factor, + ui_fee_amount: dummy_position_fees.ui.ui_fee_amount, + is_increase: is_increase + } + ) + ) + ] + ); + // Assert there are no more events. + assert(spy.events.len() == 0, 'There should be no events'); +} + +fn create_dummy_position_increase_params( + event_emitter: IEventEmitterDispatcher +) -> PositionIncreaseParams { + PositionIncreaseParams { + event_emitter: event_emitter, + order_key: 'order_key', + position_key: 'position_key', + position: create_dummy_position(), + index_token_price: Price { min: 100, max: 100 }, + collateral_token_price: Price { min: 80, max: 85 }, + execution_price: 100, + size_delta_usd: 3, + size_delta_in_tokens: 1, + collateral_delta_amount: i256_new(2, false), + price_impact_usd: i256_new(1, false), + price_impact_amount: i256_new(1, false), + order_type: OrderType::MarketSwap(()) + } +} + +fn create_dummy_position() -> Position { + Position { + key: 1, + account: contract_address_const::<'account'>(), + market: contract_address_const::<'market'>(), + collateral_token: contract_address_const::<'collateral_token'>(), + size_in_usd: 100, + size_in_tokens: 1, + collateral_amount: 2, + borrowing_factor: 3, + funding_fee_amount_per_size: 4, + long_token_claimable_funding_amount_per_size: 5, + short_token_claimable_funding_amount_per_size: 6, + increased_at_block: 15000, + decreased_at_block: 15001, + is_long: false + } +} + + +fn create_dummy_dec_pos_collateral_values() -> DecreasePositionCollateralValues { + let dummy_values_output = DecreasePositionCollateralValuesOutput { + output_token: 0x102.try_into().unwrap(), + output_amount: 5, + secondary_output_token: 0x103.try_into().unwrap(), + secondary_output_amount: 8, + }; + + DecreasePositionCollateralValues { + execution_price: 10, + remaining_collateral_amount: 10, + base_pnl_usd: i256_new(10, false), + uncapped_base_pnl_usd: i256_new(10, false), + size_delta_in_tokens: 10, + price_impact_usd: i256_new(10, false), + price_impact_diff_usd: 10, + output: dummy_values_output + } +} + +fn create_dummy_position_fees() -> PositionFees { + let collateral_token_price = Price { min: 100, max: 102 }; + + let dummy_pos_referral_fees = PositionReferralFees { + /// The referral code used. + referral_code: 'referral_code', + /// The referral affiliate of the trader. + affiliate: contract_address_const::<'affiliate'>(), + /// The trader address. + trader: contract_address_const::<'trader'>(), + /// The total rebate factor. + total_rebate_factor: 100, + /// The trader discount factor. + trader_discount_factor: 2, + /// The total rebate amount. + total_rebate_amount: 1, + /// The discount amount for the trader. + trader_discount_amount: 2, + /// The affiliate reward amount. + affiliate_reward_amount: 1, + }; + + let dummy_pos_funding_fees = PositionFundingFees { + /// The amount of funding fees in tokens. + funding_fee_amount: 10, + /// The negative funding fee in long token that is claimable. + claimable_long_token_amount: 10, + /// The negative funding fee in short token that is claimable. + claimable_short_token_amount: 10, + /// The latest long token funding fee amount per size for the market. + latest_funding_fee_amount_per_size: 10, + /// The latest long token funding amount per size for the market. + latest_long_token_claimable_funding_amount_per_size: 10, + /// The latest short token funding amount per size for the market. + latest_short_token_claimable_funding_amount_per_size: 10, + }; + + let dummy_pos_borrowing_fees = PositionBorrowingFees { + /// The borrowing fees amount in USD. + borrowing_fee_usd: 10, + /// The borrowing fees amount in tokens. + borrowing_fee_amount: 2, + /// The borrowing fees factor for receiver. + borrowing_fee_receiver_factor: 1, + /// The borrowing fees amount in tokens for fee receiver. + borrowing_fee_amount_for_fee_receiver: 3, + }; + + let dummy_pos_ui_fees = PositionUiFees { + /// The ui fee receiver address + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + /// The factor for fee receiver. + ui_fee_receiver_factor: 2, + /// The ui fee amount in tokens. + ui_fee_amount: 3, + }; + + PositionFees { + referral: dummy_pos_referral_fees, + funding: dummy_pos_funding_fees, + borrowing: dummy_pos_borrowing_fees, + ui: dummy_pos_ui_fees, + collateral_token_price: collateral_token_price, + position_fee_factor: 7, + protocol_fee_amount: 5, + position_fee_receiver_factor: 5, + fee_receiver_amount: 5, + fee_amount_for_pool: 5, + position_fee_amount_for_pool: 10, + position_fee_amount: 10, + total_cost_amount_excluding_funding: 10, + total_cost_amount: 10 + } +} diff --git a/src/tests/event/test_pricing_events_emitted.cairo b/tests/event/test_pricing_events_emitted.cairo similarity index 65% rename from src/tests/event/test_pricing_events_emitted.cairo rename to tests/event/test_pricing_events_emitted.cairo index 12e5c135..b6aa1d8f 100644 --- a/src/tests/event/test_pricing_events_emitted.cairo +++ b/tests/event/test_pricing_events_emitted.cairo @@ -5,8 +5,15 @@ use snforge_std::{ }; use satoru::tests_lib::setup_event_emitter; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; + +use satoru::event::event_emitter::EventEmitter::{SwapInfo, SwapFeesCollected}; + + use satoru::pricing::swap_pricing_utils::SwapFees; +use satoru::utils::i256::{i256, i256_new}; #[test] fn given_normal_conditions_when_emit_swap_info_then_works() { @@ -26,29 +33,13 @@ fn given_normal_conditions_when_emit_swap_info_then_works() { let receiver = contract_address_const::<'receiver'>(); let token_in = contract_address_const::<'token_in'>(); let token_out = contract_address_const::<'token_out'>(); - let token_in_price: u128 = 1; - let token_out_price: u128 = 2; - let amount_in: u128 = 3; - let amount_in_after_fees: u128 = 4; - let amount_out: u128 = 5; - let price_impact_usd: i128 = 6; - let price_impact_amount: i128 = 7; - - // Create the expected data. - let expected_data: Array = array![ - order_key, - market.into(), - receiver.into(), - token_in.into(), - token_out.into(), - token_in_price.into(), - token_out_price.into(), - amount_in.into(), - amount_in_after_fees.into(), - amount_out.into(), - price_impact_usd.into(), - price_impact_amount.into() - ]; + let token_in_price: u256 = 1; + let token_out_price: u256 = 2; + let amount_in: u256 = 3; + let amount_in_after_fees: u256 = 4; + let amount_out: u256 = 5; + let price_impact_usd: i256 = i256_new(6, false); + let price_impact_amount: i256 = i256_new(7, false); // Emit the event. event_emitter @@ -71,9 +62,25 @@ fn given_normal_conditions_when_emit_swap_info_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, name: 'SwapInfo', keys: array![], data: expected_data - } + ( + contract_address, + EventEmitter::Event::SwapInfo( + SwapInfo { + order_key: order_key, + market: market, + receiver: receiver, + token_in: token_in, + token_out: token_out, + token_in_price: token_in_price, + token_out_price: token_out_price, + amount_in: amount_in, + amount_in_after_fees: amount_in_after_fees, + amount_out: amount_out, + price_impact_usd: price_impact_usd, + price_impact_amount: price_impact_amount + } + ) + ) ] ); // Assert there are no more events. @@ -95,7 +102,7 @@ fn given_normal_conditions_when_emit_swap_fees_collected_then_works() { // Create a dummy data. let market = contract_address_const::<'market'>(); let token = contract_address_const::<'token'>(); - let token_price: u128 = 1; + let token_price: u256 = 1; let action = 'action'; let fees: SwapFees = SwapFees { fee_receiver_amount: 1, @@ -105,11 +112,6 @@ fn given_normal_conditions_when_emit_swap_fees_collected_then_works() { ui_fee_receiver_factor: 4, ui_fee_amount: 5, }; - // Create the expected data. - let mut expected_data: Array = array![ - market.into(), token.into(), token_price.into(), action - ]; - fees.serialize(ref expected_data); // Emit the event. event_emitter.emit_swap_fees_collected(market, token, token_price, action, fees); @@ -118,12 +120,18 @@ fn given_normal_conditions_when_emit_swap_fees_collected_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SwapFeesCollected', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SwapFeesCollected( + SwapFeesCollected { + market: market, + token: token, + token_price: token_price, + action: action, + fees: fees, + } + ) + ) ] ); // Assert there are no more events. diff --git a/src/tests/event/test_referral_events_emitted.cairo b/tests/event/test_referral_events_emitted.cairo similarity index 68% rename from src/tests/event/test_referral_events_emitted.cairo rename to tests/event/test_referral_events_emitted.cairo index e55b36a1..805c2679 100644 --- a/src/tests/event/test_referral_events_emitted.cairo +++ b/tests/event/test_referral_events_emitted.cairo @@ -4,7 +4,13 @@ use snforge_std::{ EventAssertions }; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; + +use satoru::event::event_emitter::EventEmitter::{AffiliateRewardUpdated, AffiliateRewardClaimed}; + + use satoru::tests_lib::setup_event_emitter; #[test] @@ -23,19 +29,9 @@ fn given_normal_conditions_when_emit_affiliate_reward_updated_then_works() { let market = contract_address_const::<'market'>(); let token = contract_address_const::<'token'>(); let affiliate = contract_address_const::<'affiliate'>(); - let delta: u128 = 100; - let next_value: u128 = 200; - let next_pool_value: u128 = 300; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), - token.into(), - affiliate.into(), - delta.into(), - next_value.into(), - next_pool_value.into() - ]; + let delta: u256 = 100; + let next_value: u256 = 200; + let next_pool_value: u256 = 300; // Emit the event. event_emitter @@ -47,12 +43,19 @@ fn given_normal_conditions_when_emit_affiliate_reward_updated_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AffiliateRewardUpdated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AffiliateRewardUpdated( + AffiliateRewardUpdated { + market: market, + token: token, + affiliate: affiliate, + delta: delta, + next_value: next_value, + next_pool_value: next_pool_value + } + ) + ) ] ); // Assert there are no more events. @@ -76,18 +79,8 @@ fn given_normal_conditions_when_emit_affiliate_reward_claimed_then_works() { let token = contract_address_const::<'token'>(); let affiliate = contract_address_const::<'affiliate'>(); let receiver = contract_address_const::<'receiver'>(); - let amount: u128 = 100; - let next_pool_value: u128 = 200; - - // Create the expected data. - let expected_data: Array = array![ - market.into(), - token.into(), - affiliate.into(), - receiver.into(), - amount.into(), - next_pool_value.into() - ]; + let amount: u256 = 100; + let next_pool_value: u256 = 200; // Emit the event. event_emitter @@ -97,12 +90,19 @@ fn given_normal_conditions_when_emit_affiliate_reward_claimed_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AffiliateRewardClaimed', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AffiliateRewardClaimed( + AffiliateRewardClaimed { + market: market, + token: token, + affiliate: affiliate, + receiver: receiver, + amount: amount, + next_pool_value: next_pool_value + } + ) + ) ] ); // Assert there are no more events. diff --git a/src/tests/event/test_swap_events_emitted.cairo b/tests/event/test_swap_events_emitted.cairo similarity index 78% rename from src/tests/event/test_swap_events_emitted.cairo rename to tests/event/test_swap_events_emitted.cairo index c2a8e1aa..3195d479 100644 --- a/src/tests/event/test_swap_events_emitted.cairo +++ b/tests/event/test_swap_events_emitted.cairo @@ -9,7 +9,12 @@ use satoru::pricing::position_pricing_utils::{ PositionFees, PositionUiFees, PositionBorrowingFees, PositionReferralFees, PositionFundingFees }; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; + +use satoru::event::event_emitter::EventEmitter::{SwapReverted}; + #[test] fn given_normal_conditions_when_emit_swap_reverted_then_works() { @@ -27,11 +32,6 @@ fn given_normal_conditions_when_emit_swap_reverted_then_works() { let reason = 'reverted'; let reason_bytes = array!['0x01']; - // Create the expected data. - let mut expected_data: Array = array![reason]; - - reason_bytes.serialize(ref expected_data); - // Emit the event. event_emitter.emit_swap_reverted(reason, reason_bytes.span()); @@ -39,12 +39,12 @@ fn given_normal_conditions_when_emit_swap_reverted_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SwapReverted', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SwapReverted( + SwapReverted { reason: reason, reason_bytes: reason_bytes.span() } + ) + ) ] ); // Assert there are no more events. diff --git a/src/tests/event/test_timelock_events_emitted.cairo b/tests/event/test_timelock_events_emitted.cairo similarity index 78% rename from src/tests/event/test_timelock_events_emitted.cairo rename to tests/event/test_timelock_events_emitted.cairo index 7b3b2995..0861c73a 100644 --- a/src/tests/event/test_timelock_events_emitted.cairo +++ b/tests/event/test_timelock_events_emitted.cairo @@ -6,7 +6,15 @@ use snforge_std::{ use satoru::tests_lib::setup_event_emitter; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; + +use satoru::event::event_emitter::EventEmitter::{ + SignalAddOracleSigner, AddOracleSigner, SignalSetFeeReceiver, SignalRemoveOracleSigner, + RemoveOracleSigner, SetFeeReceiver, SignalGrantRole, GrantRole, SignalRevokeRole, RevokeRole, + SignalSetPriceFeed, SetPriceFeed, SignalPendingAction, ClearPendingAction +}; #[test] @@ -25,21 +33,18 @@ fn given_normal_conditions_when_emit_signal_add_oracle_signer_then_works() { let action_key = 'SignalAddOracleSigner'; let account = contract_address_const::<'account'>(); - // Create the expected data. - let expected_data: Array = array![action_key, account.into()]; - // Emit the event. event_emitter.emit_signal_add_oracle_signer(action_key, account); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SignalAddOracleSigner', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SignalAddOracleSigner( + SignalAddOracleSigner { action_key: action_key, account: account } + ) + ) ] ); // Assert there are no more events. @@ -62,9 +67,6 @@ fn given_normal_conditions_when_emit_add_oracle_signer_then_works() { let action_key = 'AddOracleSigner'; let account = contract_address_const::<'account'>(); - // Create the expected data. - let expected_data: Array = array![action_key, account.into()]; - // Emit the event. event_emitter.emit_add_oracle_signer(action_key, account); @@ -72,12 +74,12 @@ fn given_normal_conditions_when_emit_add_oracle_signer_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'AddOracleSigner', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::AddOracleSigner( + AddOracleSigner { action_key: action_key, account: account } + ) + ) ] ); // Assert there are no more events. @@ -100,21 +102,18 @@ fn given_normal_conditions_when_emit_signal_remove_oracle_signer_then_works() { let action_key = 'SignalRemoveOracleSigner'; let account = contract_address_const::<'account'>(); - // Create the expected data. - let expected_data: Array = array![action_key, account.into()]; - // Emit the event. event_emitter.emit_signal_remove_oracle_signer(action_key, account); // Assert the event was emitted. spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SignalRemoveOracleSigner', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SignalRemoveOracleSigner( + SignalRemoveOracleSigner { action_key: action_key, account: account } + ) + ) ] ); // Assert there are no more events. @@ -137,9 +136,6 @@ fn given_normal_conditions_when_emit_remove_oracle_signer_then_works() { let action_key = 'RemoveOracleSigner'; let account = contract_address_const::<'account'>(); - // Create the expected data. - let expected_data: Array = array![action_key, account.into()]; - // Emit the event. event_emitter.emit_remove_oracle_signer(action_key, account); @@ -147,12 +143,12 @@ fn given_normal_conditions_when_emit_remove_oracle_signer_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'RemoveOracleSigner', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::RemoveOracleSigner( + RemoveOracleSigner { action_key: action_key, account: account } + ) + ) ] ); // Assert there are no more events. @@ -175,9 +171,6 @@ fn given_normal_conditions_when_emit_signal_set_fee_receiver_then_works() { let action_key = 'SignalSetFeeReceiver'; let account = contract_address_const::<'account'>(); - // Create the expected data. - let expected_data: Array = array![action_key, account.into()]; - // Emit the event. event_emitter.emit_signal_set_fee_receiver(action_key, account); @@ -185,12 +178,12 @@ fn given_normal_conditions_when_emit_signal_set_fee_receiver_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SignalSetFeeReceiver', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SignalSetFeeReceiver( + SignalSetFeeReceiver { action_key: action_key, account: account } + ) + ) ] ); // Assert there are no more events. @@ -213,9 +206,6 @@ fn given_normal_conditions_when_emit_set_fee_receiver_then_works() { let action_key = 'SetFeeReceiver'; let account = contract_address_const::<'account'>(); - // Create the expected data. - let expected_data: Array = array![action_key, account.into()]; - // Emit the event. event_emitter.emit_set_fee_receiver(action_key, account); @@ -223,12 +213,12 @@ fn given_normal_conditions_when_emit_set_fee_receiver_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SetFeeReceiver', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SetFeeReceiver( + SetFeeReceiver { action_key: action_key, account: account } + ) + ) ] ); // Assert there are no more events. @@ -252,9 +242,6 @@ fn given_normal_conditions_when_emit_signal_grant_role_then_works() { let account = contract_address_const::<'account'>(); let role_key = 'Admin'; - // Create the expected data. - let expected_data: Array = array![action_key, account.into(), role_key]; - // Emit the event. event_emitter.emit_signal_grant_role(action_key, account, role_key); @@ -262,12 +249,14 @@ fn given_normal_conditions_when_emit_signal_grant_role_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SignalGrantRole', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SignalGrantRole( + SignalGrantRole { + action_key: action_key, account: account, role_key: role_key + } + ) + ) ] ); // Assert there are no more events. @@ -291,9 +280,6 @@ fn given_normal_conditions_when_emit_grant_role_then_works() { let account = contract_address_const::<'account'>(); let role_key = 'Admin'; - // Create the expected data. - let expected_data: Array = array![action_key, account.into(), role_key]; - // Emit the event. event_emitter.emit_grant_role(action_key, account, role_key); @@ -301,9 +287,12 @@ fn given_normal_conditions_when_emit_grant_role_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, name: 'GrantRole', keys: array![], data: expected_data - } + ( + contract_address, + EventEmitter::Event::GrantRole( + GrantRole { action_key: action_key, account: account, role_key: role_key } + ) + ) ] ); // Assert there are no more events. @@ -327,9 +316,6 @@ fn given_normal_conditions_when_emit_signal_revoke_role_then_works() { let account = contract_address_const::<'account'>(); let role_key = 'Admin'; - // Create the expected data. - let expected_data: Array = array![action_key, account.into(), role_key]; - // Emit the event. event_emitter.emit_signal_revoke_role(action_key, account, role_key); @@ -337,12 +323,14 @@ fn given_normal_conditions_when_emit_signal_revoke_role_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SignalRevokeRole', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SignalRevokeRole( + SignalRevokeRole { + action_key: action_key, account: account, role_key: role_key + } + ) + ) ] ); // Assert there are no more events. @@ -366,9 +354,6 @@ fn given_normal_conditions_when_emit_revoke_role_then_works() { let account = contract_address_const::<'account'>(); let role_key = 'Admin'; - // Create the expected data. - let expected_data: Array = array![action_key, account.into(), role_key]; - // Emit the event. event_emitter.emit_revoke_role(action_key, account, role_key); @@ -376,9 +361,12 @@ fn given_normal_conditions_when_emit_revoke_role_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, name: 'RevokeRole', keys: array![], data: expected_data - } + ( + contract_address, + EventEmitter::Event::RevokeRole( + RevokeRole { action_key: action_key, account: account, role_key: role_key } + ) + ) ] ); // Assert there are no more events. @@ -401,19 +389,9 @@ fn given_normal_conditions_when_emit_signal_set_price_feed_then_works() { let action_key = 'SignalSetPriceFeed'; let token = contract_address_const::<'token'>(); let price_feed = contract_address_const::<'priceFeed'>(); - let price_feed_multiplier: u128 = 1; - let price_feed_heartbeat_duration: u128 = 2; - let stable_price: u128 = 3; - - // Create the expected data. - let expected_data: Array = array![ - action_key, - token.into(), - price_feed.into(), - price_feed_multiplier.into(), - price_feed_heartbeat_duration.into(), - stable_price.into() - ]; + let price_feed_multiplier: u256 = 1; + let price_feed_heartbeat_duration: u256 = 2; + let stable_price: u256 = 3; // Emit the event. event_emitter @@ -430,12 +408,19 @@ fn given_normal_conditions_when_emit_signal_set_price_feed_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SignalSetPriceFeed', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SignalSetPriceFeed( + SignalSetPriceFeed { + action_key: action_key, + token: token, + price_feed: price_feed, + price_feed_multiplier: price_feed_multiplier, + price_feed_heartbeat_duration: price_feed_heartbeat_duration, + stable_price: stable_price + } + ) + ) ] ); // Assert there are no more events. @@ -459,19 +444,9 @@ fn given_normal_conditions_when_emit_set_price_feed_then_works() { let action_key = 'SetPriceFeed'; let token = contract_address_const::<'token'>(); let price_feed = contract_address_const::<'priceFeed'>(); - let price_feed_multiplier: u128 = 1; - let price_feed_heartbeat_duration: u128 = 2; - let stable_price: u128 = 3; - - // Create the expected data. - let mut expected_data: Array = array![ - action_key, - token.into(), - price_feed.into(), - price_feed_multiplier.into(), - price_feed_heartbeat_duration.into(), - stable_price.into() - ]; + let price_feed_multiplier: u256 = 1; + let price_feed_heartbeat_duration: u256 = 2; + let stable_price: u256 = 3; // Emit the event. event_emitter @@ -488,12 +463,19 @@ fn given_normal_conditions_when_emit_set_price_feed_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SetPriceFeed', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SetPriceFeed( + SetPriceFeed { + action_key: action_key, + token: token, + price_feed: price_feed, + price_feed_multiplier: price_feed_multiplier, + price_feed_heartbeat_duration: price_feed_heartbeat_duration, + stable_price: stable_price + } + ) + ) ] ); // Assert there are no more events. @@ -516,9 +498,6 @@ fn given_normal_conditions_when_emit_signal_pending_action_then_works() { let action_key = 'SignalPendingAction'; let action_label = 'SignalPendingAction'; - // Create the expected data. - let expected_data: Array = array![action_key, action_label]; - // Emit the event. event_emitter.emit_signal_pending_action(action_key, action_label); @@ -526,12 +505,12 @@ fn given_normal_conditions_when_emit_signal_pending_action_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'SignalPendingAction', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::SignalPendingAction( + SignalPendingAction { action_key: action_key, action_label: action_label } + ) + ) ] ); // Assert there are no more events. @@ -554,9 +533,6 @@ fn given_normal_conditions_when_emit_clear_pending_action_then_works() { let action_key = 'ClearPendingAction'; let action_label = 'ClearPendingAction'; - // Create the expected data. - let expected_data: Array = array![action_key, action_label]; - // Emit the event. event_emitter.emit_clear_pending_action(action_key, action_label); @@ -564,12 +540,12 @@ fn given_normal_conditions_when_emit_clear_pending_action_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'ClearPendingAction', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::ClearPendingAction( + ClearPendingAction { action_key: action_key, action_label: action_label } + ) + ) ] ); // Assert there are no more events. diff --git a/src/tests/event/test_withdrawal_events_emitted.cairo b/tests/event/test_withdrawal_events_emitted.cairo similarity index 71% rename from src/tests/event/test_withdrawal_events_emitted.cairo rename to tests/event/test_withdrawal_events_emitted.cairo index 00262870..75ddde82 100644 --- a/src/tests/event/test_withdrawal_events_emitted.cairo +++ b/tests/event/test_withdrawal_events_emitted.cairo @@ -4,7 +4,15 @@ use snforge_std::{ EventAssertions }; -use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::event::event_emitter::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; + +use satoru::event::event_emitter::EventEmitter::{ + WithdrawalCreated, WithdrawalExecuted, WithdrawalCancelled +}; + + use satoru::withdrawal::withdrawal::Withdrawal; use satoru::tests_lib::setup_event_emitter; @@ -24,21 +32,6 @@ fn given_normal_conditions_when_emit_withdrawal_created_then_works() { let key: felt252 = 100; let withdrawal: Withdrawal = create_dummy_withdrawal(key); - // Create the expected data. - let mut expected_data: Array = array![ - key, - withdrawal.account.into(), - withdrawal.receiver.into(), - withdrawal.callback_contract.into(), - withdrawal.market.into(), - withdrawal.market_token_amount.into(), - withdrawal.min_long_token_amount.into(), - withdrawal.min_short_token_amount.into(), - withdrawal.updated_at_block.into(), - withdrawal.execution_fee.into(), - withdrawal.callback_gas_limit.into(), - ]; - // Emit the event. event_emitter.emit_withdrawal_created(key, withdrawal); @@ -46,12 +39,26 @@ fn given_normal_conditions_when_emit_withdrawal_created_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'WithdrawalCreated', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::WithdrawalCreated( + WithdrawalCreated { + key: key, + account: withdrawal.account, + receiver: withdrawal.receiver, + callback_contract: withdrawal.callback_contract, + market: withdrawal.market, + long_token_swap_path: withdrawal.long_token_swap_path, + short_token_swap_path: withdrawal.short_token_swap_path, + market_token_amount: withdrawal.market_token_amount, + min_long_token_amount: withdrawal.min_long_token_amount, + min_short_token_amount: withdrawal.min_short_token_amount, + updated_at_block: withdrawal.updated_at_block, + execution_fee: withdrawal.execution_fee, + callback_gas_limit: withdrawal.callback_gas_limit, + } + ) + ) ] ); // Assert there are no more events. @@ -73,9 +80,6 @@ fn given_normal_conditions_when_emit_withdrawal_executed_then_works() { // Create dummy data. let key: felt252 = 100; - // Create the expected data. - let mut expected_data: Array = array![key]; - // Emit the event. event_emitter.emit_withdrawal_executed(key); @@ -83,12 +87,10 @@ fn given_normal_conditions_when_emit_withdrawal_executed_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'WithdrawalExecuted', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::WithdrawalExecuted(WithdrawalExecuted { key: key, }) + ) ] ); // Assert there are no more events. @@ -112,10 +114,6 @@ fn given_normal_conditions_when_emit_withdrawal_cancelled_then_works() { let reason: felt252 = 'cancel'; let reason_bytes = array!['0x00', '0x01']; - // Create the expected data. - let mut expected_data: Array = array![key, reason]; - reason_bytes.serialize(ref expected_data); - // Emit the event. event_emitter.emit_withdrawal_cancelled(key, reason, reason_bytes.span()); @@ -123,12 +121,14 @@ fn given_normal_conditions_when_emit_withdrawal_cancelled_then_works() { spy .assert_emitted( @array![ - Event { - from: contract_address, - name: 'WithdrawalCancelled', - keys: array![], - data: expected_data - } + ( + contract_address, + EventEmitter::Event::WithdrawalCancelled( + WithdrawalCancelled { + key: key, reason: reason, reason_bytes: reason_bytes.span() + } + ) + ) ] ); // Assert there are no more events. diff --git a/tests/exchange/test_base_order_handler.cairo b/tests/exchange/test_base_order_handler.cairo new file mode 100644 index 00000000..af6193ae --- /dev/null +++ b/tests/exchange/test_base_order_handler.cairo @@ -0,0 +1,386 @@ +//! Test file for `src/exchange/base_order_handler.cairo`. + +// ************************************************************************* +// IMPORTS +// ************************************************************************* +// Core lib imports. +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const +}; +use snforge_std::{ + declare, start_prank, stop_prank, start_mock_call, test_address, ContractClassTrait +}; +use traits::Default; +use poseidon::poseidon_hash_span; +// Local imports. +use satoru::role::role; +use satoru::tests_lib; + +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; +use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; +use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; +use satoru::order::base_order_utils::ExecuteOrderParamsContracts; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::utils::span32::{Span32, Array32}; +use satoru::market::market::{Market, UniqueIdMarketImpl}; + +use satoru::exchange::base_order_handler::BaseOrderHandler; +use satoru::exchange::base_order_handler::{ + IBaseOrderHandlerDispatcher, IBaseOrderHandlerDispatcherTrait +}; + +// ********************************************************************************************* +// * TEST LOGIC * +// ********************************************************************************************* +#[test] +#[should_panic(expected: ('already_initialized',))] +fn given_already_intialized_state_when_initialize_then_fails() { + let ( + caller_address, + role_store, + data_store, + event_emitter, + order_vault, + swap_handler, + referral_storage, + oracle, + mut base_order_handler_state + ) = + setup_contracts(); + BaseOrderHandler::BaseOrderHandlerImpl::initialize( + ref base_order_handler_state, + data_store.contract_address, + role_store.contract_address, + event_emitter.contract_address, + order_vault.contract_address, + oracle.contract_address, + swap_handler.contract_address, + referral_storage.contract_address, + ); +} + +#[test] +fn given_normal_conditions_when_get_execute_order_params_then_works() { + // Setup + let ( + caller_address, + role_store, + data_store, + event_emitter, + order_vault, + oracle, + swap_handler, + referral_storage, + mut base_order_handler_state + ) = + setup_contracts(); + + let key: felt252 = mock_key(); + let set_prices_params = mock_set_prices_params(); + let starting_gas = 10000; + let secondary_order_type = SecondaryOrderType::Adl(()); + + let mock_order: Order = Default::::default(); + start_mock_call(data_store.contract_address, 'get_order', mock_order); + + // test call + let execute_order_params = BaseOrderHandler::InternalImpl::get_execute_order_params( + ref base_order_handler_state, + key, + set_prices_params, + caller_address, + starting_gas, + secondary_order_type + ); + + // assertions + _assert_contracts_are_equals( + contracts: execute_order_params.contracts, + data_store_address: data_store.contract_address, + event_emitter_address: event_emitter.contract_address, + order_vault_address: order_vault.contract_address, + oracle_address: oracle.contract_address, + swap_handler_address: swap_handler.contract_address, + referral_storage_address: referral_storage.contract_address + ); + assert(execute_order_params.key == key, 'wrong key'); + assert(execute_order_params.order == Default::default(), 'wrong order'); + assert( + *execute_order_params.min_oracle_block_numbers.at(0) == 6301, + 'wrong_min_oracle_block_numbers' + ); + assert( + *execute_order_params.max_oracle_block_numbers.at(0) == 6400, + 'wrong max_oracle_block_numbers' + ); + assert(execute_order_params.market == Default::default(), 'wrong execute_order_params'); + assert(execute_order_params.keeper == caller_address, 'wrong keeper'); + assert(execute_order_params.starting_gas == starting_gas, 'wrong starting_gas'); + assert( + execute_order_params.secondary_order_type == secondary_order_type, + 'wrong secondary_order_type' + ); + + // teardown + tests_lib::teardown(data_store.contract_address); +} + +#[test] +fn given_non_found_order_when_get_execute_order_params_then_returns_empty_order() { + let ( + caller_address, + role_store, + data_store, + event_emitter, + order_vault, + oracle, + swap_handler, + referral_storage, + mut base_order_handler_state + ) = + setup_contracts(); + + let key: felt252 = mock_key(); + let set_prices_params = mock_set_prices_params(); + let starting_gas = 10000; + let secondary_order_type = SecondaryOrderType::Adl(()); + + let execute_order_params = BaseOrderHandler::InternalImpl::get_execute_order_params( + ref base_order_handler_state, + key, + set_prices_params, + caller_address, + starting_gas, + secondary_order_type + ); + + assert(execute_order_params.order.account.is_zero(), 'order shouldnt exists'); + + tests_lib::teardown(data_store.contract_address); +} + +// TODO: more tests when all the functions are implemented (order utils ; oracle ...) + +// ********************************************************************************************* +// * ASSERTION UTILITIES * +// ********************************************************************************************* + +fn _assert_contracts_are_equals( + contracts: ExecuteOrderParamsContracts, + data_store_address: ContractAddress, + event_emitter_address: ContractAddress, + order_vault_address: ContractAddress, + oracle_address: ContractAddress, + swap_handler_address: ContractAddress, + referral_storage_address: ContractAddress +) { + assert(contracts.data_store.contract_address == data_store_address, 'wrong data_store'); + assert( + contracts.event_emitter.contract_address == event_emitter_address, 'wrong event_emitter' + ); + assert(contracts.order_vault.contract_address == order_vault_address, 'wrong order_vault'); + assert(contracts.oracle.contract_address == oracle_address, 'wrong oracle'); + assert(contracts.swap_handler.contract_address == swap_handler_address, 'wrong swap_handler'); + assert( + contracts.referral_storage.contract_address == referral_storage_address, + 'wrong referral_storage' + ); +} + +// ********************************************************************************************* +// * MOCKS * +// ********************************************************************************************* +fn mock_market() -> Market { + let address_zero = contract_address_const::<0>(); + let key = contract_address_const::<123456789>(); + + Market { + market_token: key, + index_token: address_zero, + long_token: address_zero, + short_token: address_zero, + } +} + +fn mock_key() -> felt252 { + poseidon_hash_span(array!['my', 'amazing', 'key'].span()) +} + +fn mock_set_prices_params() -> SetPricesParams { + SetPricesParams { + signer_info: 1, + tokens: array![ + contract_address_const::<'ETH'>(), + contract_address_const::<'USDC'>(), + contract_address_const::<'DAI'>() + ], + compacted_min_oracle_block_numbers: array![6301, 6301, 6301], + compacted_max_oracle_block_numbers: array![6400, 6400, 6400], + compacted_oracle_timestamps: array![101, 101, 103], + compacted_decimals: array![18, 18, 18], + compacted_min_prices: array![0, 0, 0], + compacted_min_prices_indexes: array![1, 2, 3], + compacted_max_prices: array![0, 0, 0], + compacted_max_prices_indexes: array![1, 2, 3], + signatures: array![array!['signatures'].span()], + price_feed_tokens: array![ + contract_address_const::<'ETH'>(), + contract_address_const::<'USDC'>(), + contract_address_const::<'DAI'>() + ] + } +} + +fn mock_swap_path() -> Span32 { + array![contract_address_const::<'ETH'>(), contract_address_const::<'DAI'>()].span32() +} + +fn mock_order( + key: felt252, market_address: ContractAddress, swap_path: Span32 +) -> Order { + Order { + key, + order_type: OrderType::StopLossDecrease, + decrease_position_swap_type: DecreasePositionSwapType::SwapPnlTokenToCollateralToken(()), + account: contract_address_const::<'account'>(), + receiver: contract_address_const::<'receiver'>(), + callback_contract: contract_address_const::<'callback_contract'>(), + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + market: market_address, + initial_collateral_token: contract_address_const::<'initial_collateral_token'>(), + swap_path, + size_delta_usd: 1000, + initial_collateral_delta_amount: 500, + trigger_price: 2000, + acceptable_price: 2500, + execution_fee: 100, + callback_gas_limit: 300000, + min_output_amount: 100, + updated_at_block: 0, + is_long: true, + is_frozen: false, + } +} + +// ********************************************************************************************* +// * SETUP * +// ********************************************************************************************* +/// 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. +/// * `IOrderVaultDispatcher` - The order vault dispatcher. +/// * `IOracleDispatcher` - The base order handler dispatcher. +/// * `BaseOrderHandler::ContractState` - The base order handler state. +fn setup_contracts() -> ( + ContractAddress, + IRoleStoreDispatcher, + IDataStoreDispatcher, + IEventEmitterDispatcher, + IOrderVaultDispatcher, + IOracleDispatcher, + ISwapHandlerDispatcher, + IReferralStorageDispatcher, + BaseOrderHandler::ContractState +) { + let (caller_address, role_store, data_store, event_emitter, oracle) = + tests_lib::setup_oracle_and_store(); + + let order_vault_address = deploy_order_vault( + data_store.contract_address, role_store.contract_address + ); + let order_vault = IOrderVaultDispatcher { contract_address: order_vault_address }; + + let swap_handler_address = deploy_swap_handler(role_store.contract_address); + let swap_handler = ISwapHandlerDispatcher { contract_address: swap_handler_address }; + + let referral_storage_address = deploy_referral_storage(event_emitter.contract_address); + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let base_order_handler_state = setup_base_order_handler_state( + data_store.contract_address, + role_store.contract_address, + event_emitter.contract_address, + order_vault_address, + oracle.contract_address, + swap_handler_address, + referral_storage_address + ); + + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + return ( + caller_address, + role_store, + data_store, + event_emitter, + order_vault, + oracle, + swap_handler, + referral_storage, + base_order_handler_state + ); +} + +/// Utility function to deploy a `BaseOrderhandler` contract and return its address. +fn setup_base_order_handler_state( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + order_vault_address: ContractAddress, + oracle_address: ContractAddress, + swap_handler_address: ContractAddress, + referral_storage_address: ContractAddress +) -> BaseOrderHandler::ContractState { + let mut base_order_handler_state = BaseOrderHandler::contract_state_for_testing(); + + BaseOrderHandler::BaseOrderHandlerImpl::initialize( + ref base_order_handler_state, + data_store_address, + role_store_address, + event_emitter_address, + order_vault_address, + oracle_address, + swap_handler_address, + referral_storage_address, + ); + return base_order_handler_state; +} + +/// Utility function to deploy an `OrderVault` contract and return its address. +fn deploy_order_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OrderVault'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + tests_lib::deploy_mock_contract(contract, @constructor_calldata) +} + +/// Utility function to deploy a `SwapHandler` contract and return its address. +fn deploy_swap_handler(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('SwapHandler'); + let constructor_calldata = array![role_store_address.into()]; + tests_lib::deploy_mock_contract(contract, @constructor_calldata) +} + +/// Utility function to deploy a `ReferralStorage` contract and return its address. +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let constructor_calldata = array![event_emitter_address.into()]; + tests_lib::deploy_mock_contract(contract, @constructor_calldata) +} diff --git a/tests/exchange/test_deposit_handler.cairo b/tests/exchange/test_deposit_handler.cairo new file mode 100644 index 00000000..70a263d1 --- /dev/null +++ b/tests/exchange/test_deposit_handler.cairo @@ -0,0 +1,190 @@ +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait, ContractClass}; +use starknet::{ContractAddress, contract_address_const, ClassHash, Felt252TryIntoContractAddress}; +use traits::Default; + +use satoru::deposit::deposit_utils::CreateDepositParams; +use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::exchange::deposit_handler::{IDepositHandlerDispatcher, IDepositHandlerDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::role::role; +use satoru::role::role_module::{IRoleModuleDispatcher, IRoleModuleDispatcherTrait}; +use satoru::utils::span32::{Span32, Array32Trait}; + +// TODO add assert and tests when deposit_vault will be implemented + +#[test] +fn given_normal_conditions_when_create_cancel_deposit_then_works() { + let deposit_handler = setup(); + + let account = contract_address_const::<'account'>(); + let params = create_deposit_params(); +// let key = deposit_handler.create_deposit(account, params); +// deposit_handler.cancel_deposit(key); +} + +#[test] +fn given_normal_conditions_when_create_execute_deposit_then_works() { + let deposit_handler = setup(); + + let account = contract_address_const::<'account'>(); + let params = create_deposit_params(); + + // let key = deposit_handler.create_deposit(account, params); + + let token1 = contract_address_const::<'token1'>(); + let price_feed_tokens1 = contract_address_const::<'price_feed_tokens'>(); + let oracle_params = SetPricesParams { + signer_info: 123, + tokens: array![token1], + compacted_min_oracle_block_numbers: array![1], + compacted_max_oracle_block_numbers: array![10], + compacted_oracle_timestamps: array![1123], + compacted_decimals: array![18], + compacted_min_prices: array![2], + compacted_min_prices_indexes: array![1], + compacted_max_prices: array![5], + compacted_max_prices_indexes: array![1], + signatures: array![array!['signatures'].span()], + price_feed_tokens: array![price_feed_tokens1], + }; +// deposit_handler.execute_deposit(key, oracle_params); +} + +fn create_deposit_params() -> CreateDepositParams { + CreateDepositParams { + receiver: contract_address_const::<'receiver'>(), + callback_contract: contract_address_const::<'callback_contract'>(), + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + market: contract_address_const::<'market'>(), + initial_long_token: contract_address_const::<'initial_long_token'>(), + initial_short_token: contract_address_const::<'initial_short_token'>(), + long_token_swap_path: Array32Trait::::span32(@ArrayTrait::new()), + short_token_swap_path: Array32Trait::::span32(@ArrayTrait::new()), + min_market_tokens: 10, + execution_fee: 0, + callback_gas_limit: 10, + } +} + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_oracle( + role_store_address: ContractAddress, + oracle_store_address: ContractAddress, + pragma_address: ContractAddress +) -> ContractAddress { + let contract = declare('Oracle'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![role_store_address.into(), oracle_store_address.into(), pragma_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_oracle_store( + role_store_address: ContractAddress, event_emitter_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OracleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle_store'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![role_store_address.into(), event_emitter_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_deposit_vault( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('DepositVault'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'deposit_vault'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![data_store_address.into(), role_store_address.into()], deployed_contract_address + ) + .unwrap() +} + +fn deploy_deposit_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + deposit_vault_address: ContractAddress, + oracle_address: ContractAddress +) -> ContractAddress { + let contract = declare('DepositHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'deposit_handler'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + deposit_vault_address.into(), + oracle_address.into() + ], + deployed_contract_address + ) + .unwrap() +} + +fn setup() -> IDepositHandlerDispatcher { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let role_store_address = deploy_role_store(); + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + let data_store_address = deploy_data_store(role_store_address); + let event_emitter_address = deploy_event_emitter(); + let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); + let oracle_address = deploy_oracle( + role_store_address, oracle_store_address, contract_address_const::<'pragma'>() + ); + let deposit_vault_address = deploy_deposit_vault(role_store_address, data_store_address); + + let deposit_handler_address = deploy_deposit_handler( + data_store_address, + role_store_address, + event_emitter_address, + deposit_vault_address, + oracle_address + ); + let deposit_handler = IDepositHandlerDispatcher { contract_address: deposit_handler_address }; + + start_prank(role_store_address, caller_address); + role_store.grant_role(caller_address, role::CONTROLLER); + start_prank(data_store_address, caller_address); + (deposit_handler) +} diff --git a/tests/exchange/test_exchange_utils.cairo b/tests/exchange/test_exchange_utils.cairo new file mode 100644 index 00000000..d1fa894b --- /dev/null +++ b/tests/exchange/test_exchange_utils.cairo @@ -0,0 +1,50 @@ +use starknet::{ + ContractAddress, get_caller_address, get_contract_address, Felt252TryIntoContractAddress, + contract_address_const +}; +use starknet::info::get_block_number; +use snforge_std::{declare, start_prank, stop_prank, start_roll, ContractClassTrait}; + +use satoru::data::keys; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::exchange::exchange_utils::validate_request_cancellation; +use satoru::tests_lib::{setup, teardown}; + +#[test] +fn given_exchange_utils_when_validate_request_cancellation_then_success() { + // Setup + let (_, _, data_store) = setup(); + let contract_address = contract_address_const::<0>(); + + // Test + let expiration_age = 5; + data_store.set_u256(keys::request_expiration_block_age(), expiration_age); + + let block_number = get_block_number(); + + let created_at_block = block_number - 5; + validate_request_cancellation(data_store, created_at_block, 'SOME_REQUEST_TYPE'); + + let created_at_block = block_number - 6; + validate_request_cancellation(data_store, created_at_block, 'SOME_REQUEST_TYPE'); + + // Teardown + teardown(data_store.contract_address); +} + +#[test] +#[should_panic(expected: ('request_not_yet_cancellable', 'SOME_REQUEST_TYPE'))] +fn given_exchange_utils_when_validate_request_cancellation_then_fails() { + // Setup + let (_, _, data_store) = setup(); + let contract_address = contract_address_const::<0>(); + + // Test + let expiration_age = 5; + data_store.set_u256(keys::request_expiration_block_age(), expiration_age); + + let block_number = get_block_number(); + let created_at_block = block_number - 4; + + validate_request_cancellation(data_store, created_at_block, 'SOME_REQUEST_TYPE'); +} diff --git a/tests/exchange/test_liquidation_handler.cairo b/tests/exchange/test_liquidation_handler.cairo new file mode 100644 index 00000000..6423d6b9 --- /dev/null +++ b/tests/exchange/test_liquidation_handler.cairo @@ -0,0 +1,674 @@ +use snforge_std::{ + declare, start_prank, stop_prank, start_roll, ContractClassTrait, ContractClass, PrintTrait +}; + +use satoru::exchange::liquidation_handler::{ + LiquidationHandler, ILiquidationHandlerDispatcher, ILiquidationHandler, + ILiquidationHandlerDispatcherTrait +}; +use starknet::{ + ContractAddress, contract_address_const, contract_address_to_felt252, ClassHash, + Felt252TryIntoContractAddress +}; +use satoru::mock::referral_storage; +use traits::Default; + +use satoru::role::{ + role, role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}, + role_module::{IRoleModuleDispatcher, IRoleModuleDispatcherTrait} +}; + +use satoru::order::order::{Order, OrderType, OrderTrait, DecreasePositionSwapType}; +use satoru::utils::span32::{Span32, Array32Trait}; +use satoru::position::{position::Position, position_utils::get_position_key}; +use satoru::liquidation::liquidation_utils::create_liquidation_order; +use satoru::exchange::base_order_handler::{ + IBaseOrderHandler, + BaseOrderHandler::{ + event_emitter::InternalContractMemberStateTrait, data_store::InternalContractMemberStateImpl + } +}; + +use satoru::event::event_emitter::{IEventEmitterDispatcher}; +use satoru::data::{data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}, keys}; +use satoru::oracle::{ + oracle::{Oracle, IOracleDispatcher, IOracleDispatcherTrait}, + oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}, + interfaces::account::{IAccount, IAccountDispatcher, IAccountDispatcherTrait}, + oracle_utils::SetPricesParams +}; + +use satoru::utils::precision; +use satoru::price::price::Price; +use satoru::market::market::{Market}; +use satoru::nonce::nonce_utils; +const max_u256: u256 = 340282366920938463463374607431768211455; + + +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_unauthorized_access_when_create_execute_liquidation_then_fails() { + // Setup + + let collateral_token: ContractAddress = contract_address_const::<1>(); + let ( + data_store, + liquidation_keeper, + liquidation_handler_address, + liquidation_handler_dispatcher, + _, + role_store, + _, + _, + _ + ) = + _setup(); + let oracle_params = Default::default(); + + // Test + // Check that the test function panics when the caller doesn't have the LIQUIDATION_KEEPER role + liquidation_handler_dispatcher + .execute_liquidation( + account: contract_address_const::<'account'>(), + market: contract_address_const::<'market'>(), + collateral_token: collateral_token, + is_long: true, + oracle_params: oracle_params + ); +} + +#[test] +#[should_panic(expected: ('empty price feed', 'ETH'))] +fn given_empty_price_feed_multiplier_when_create_execute_liquidation_then_fails() { + // Setup + let collateral_token: ContractAddress = contract_address_const::<1>(); + let ( + data_store, + liquidation_keeper, + liquidation_handler_address, + liquidation_handler_dispatcher, + _, + role_store, + _, + _, + _ + ) = + _setup(); + + start_prank(role_store.contract_address, admin()); + role_store.grant_role(liquidation_keeper, role::LIQUIDATION_KEEPER); + stop_prank(role_store.contract_address); + start_prank(liquidation_handler_address, liquidation_keeper); + + let collateral_token: ContractAddress = contract_address_const::<'USDC'>(); + let token1 = contract_address_const::<'ETH'>(); + let price_feed_tokens1 = contract_address_const::<'price_feed_tokens'>(); + + let price: Price = Default::default(); + + let mut oracle_params = mock_set_prices_params(token1, collateral_token); + oracle_params.price_feed_tokens = array![token1]; + + // Test + // Check that execute_liquidation calls 'with_oracle_prices_before' and fails + liquidation_handler_dispatcher + .execute_liquidation( + account: contract_address_const::<'account'>(), + market: contract_address_const::<'market'>(), + collateral_token: collateral_token, + is_long: true, + oracle_params: oracle_params + ); +} + + +#[test] +#[should_panic(expected: ('FeatureUtils: disabled feature',))] +fn given_disabled_feature_when_create_execute_liquidation_then_fails() { + // Setup + + let ( + data_store, + liquidation_keeper, + liquidation_handler_address, + liquidation_handler_dispatcher, + event_emitter, + role_store, + oracle, + signer1, + signer2 + ) = + _setup(); + let account = contract_address_const::<'account'>(); + + // Grant LIQUIDATION_KEEPER role + start_prank(role_store.contract_address, admin()); + role_store.grant_role(liquidation_keeper, role::LIQUIDATION_KEEPER); + stop_prank(role_store.contract_address); + start_prank(liquidation_handler_address, liquidation_keeper); + + let collateral_token: ContractAddress = contract_address_const::<'USDC'>(); + let token1 = contract_address_const::<'ETH'>(); + let token2 = contract_address_const::<'BTC'>(); + + // Use mock account to match keys + signer1 + .change_owner( + 1221698997303567203808303576674742997327105320284925779268978961645745386877, 0, 0 + ); + signer2 + .change_owner( + 1221698997303567203808303576674742997327105320284925779268978961645745386877, 0, 0 + ); + + data_store.set_token_id(token1, 1); + data_store.set_token_id(token2, 2); + data_store.set_token_id(collateral_token, 3); + + // Set price feed multiplier + data_store.set_u256(keys::price_feed_multiplier_key(token1), precision::FLOAT_PRECISION); + data_store.set_u256(keys::price_feed_multiplier_key(token2), precision::FLOAT_PRECISION); + data_store + .set_u256(keys::price_feed_multiplier_key(collateral_token), precision::FLOAT_PRECISION); + data_store.set_u256(keys::max_oracle_ref_price_deviation_factor(), max_u256); + + let usdc_price = Price { min: 1000000, max: 1000000 }; + let eth_price = Price { min: 17500000000000, max: 17500000000000 }; + let mut market = Market { + market_token: contract_address_const::<'market'>(), + index_token: collateral_token, + long_token: token1, + short_token: token1, + }; + data_store.set_market(market.market_token, 0, market); + + // Set position + let pos_key = get_position_key(account, market.market_token, collateral_token, true); + let mut position: Position = Default::default(); + position.account = account; + position.size_in_usd = precision::FLOAT_PRECISION_SQRT; + position.collateral_token = collateral_token; + position.is_long = true; + position.size_in_tokens = 100; + data_store.set_position(pos_key, position); + + // Disable feature + let key = keys::execute_order_feature_disabled_key( + liquidation_handler_address, OrderType::Liquidation + ); + data_store.set_bool(key, true); + + let oracle_params = mock_set_prices_params(token1, collateral_token); + + // Test + liquidation_handler_dispatcher + .execute_liquidation( + account: account, + market: market.market_token, + collateral_token: collateral_token, + is_long: true, + oracle_params: oracle_params + ); +} + + +#[test] +#[should_panic(expected: ('negative open interest',))] +fn given_negative_open_interest_when_create_execute_liquidation_then_fails() { + // Setup + + let ( + data_store, + liquidation_keeper, + liquidation_handler_address, + liquidation_handler_dispatcher, + event_emitter, + role_store, + oracle, + signer1, + signer2 + ) = + _setup(); + let account = contract_address_const::<'account'>(); + + // Grant LIQUIDATION_KEEPER role + start_prank(role_store.contract_address, admin()); + role_store.grant_role(liquidation_keeper, role::LIQUIDATION_KEEPER); + stop_prank(role_store.contract_address); + start_prank(liquidation_handler_address, liquidation_keeper); + + let collateral_token: ContractAddress = contract_address_const::<'USDC'>(); + let token1 = contract_address_const::<'ETH'>(); + let token2 = contract_address_const::<'BTC'>(); + + // Use mock account to match keys + signer1 + .change_owner( + 1221698997303567203808303576674742997327105320284925779268978961645745386877, 0, 0 + ); + signer2 + .change_owner( + 1221698997303567203808303576674742997327105320284925779268978961645745386877, 0, 0 + ); + + data_store.set_token_id(token1, 1); + data_store.set_token_id(token2, 2); + data_store.set_token_id(collateral_token, 3); + + // Set price feed multiplier + data_store.set_u256(keys::price_feed_multiplier_key(token1), precision::FLOAT_PRECISION); + data_store.set_u256(keys::price_feed_multiplier_key(token2), precision::FLOAT_PRECISION); + data_store + .set_u256(keys::price_feed_multiplier_key(collateral_token), precision::FLOAT_PRECISION); + data_store.set_u256(keys::max_oracle_ref_price_deviation_factor(), max_u256); + + let usdc_price = Price { min: 1000000, max: 1000000 }; + let eth_price = Price { min: 17500000000000, max: 17500000000000 }; + let mut market = Market { + market_token: contract_address_const::<'market'>(), + index_token: collateral_token, + long_token: token1, + short_token: token1, + }; + data_store.set_market(market.market_token, 0, market); + + // Set position + let pos_key = get_position_key(account, market.market_token, collateral_token, true); + let mut position: Position = Default::default(); + position.account = account; + position.size_in_usd = precision::FLOAT_PRECISION_SQRT; + position.collateral_token = collateral_token; + position.is_long = true; + position.size_in_tokens = 100; + data_store.set_position(pos_key, position); + + // Set open interest + let interest_key1 = keys::open_interest_key(market.market_token, market.long_token, true); + data_store.set_u256(interest_key1, 1000000000000000000000); + let oracle_params = mock_set_prices_params(token1, collateral_token); + + // Test + liquidation_handler_dispatcher + .execute_liquidation( + account: account, + market: market.market_token, + collateral_token: collateral_token, + is_long: true, + oracle_params: oracle_params + ); +} + + +#[test] +fn given_normal_conditions_when_create_execute_liquidation_then_works() { + // Setup + + let ( + data_store, + liquidation_keeper, + liquidation_handler_address, + liquidation_handler_dispatcher, + event_emitter, + role_store, + oracle, + signer1, + signer2 + ) = + _setup(); + let account = contract_address_const::<'account'>(); + + // Grant LIQUIDATION_KEEPER role + start_prank(role_store.contract_address, admin()); + role_store.grant_role(liquidation_keeper, role::LIQUIDATION_KEEPER); + stop_prank(role_store.contract_address); + start_prank(liquidation_handler_address, liquidation_keeper); + + let (collateral_token, token1, fee_token) = setup_tokens(); + let token2 = contract_address_const::<'BTC'>(); + + // Use mock account to match keys + signer1 + .change_owner( + 1221698997303567203808303576674742997327105320284925779268978961645745386877, 0, 0 + ); + signer2 + .change_owner( + 1221698997303567203808303576674742997327105320284925779268978961645745386877, 0, 0 + ); + + data_store.set_token_id(token1, 1); + data_store.set_token_id(token2, 2); + data_store.set_token_id(collateral_token, 3); + + data_store.set_address(keys::fee_token(), fee_token); + + // Set price feed multiplier + data_store.set_u256(keys::price_feed_multiplier_key(token1), precision::FLOAT_PRECISION); + data_store.set_u256(keys::price_feed_multiplier_key(token2), precision::FLOAT_PRECISION); + data_store + .set_u256(keys::price_feed_multiplier_key(collateral_token), precision::FLOAT_PRECISION); + data_store.set_u256(keys::max_oracle_ref_price_deviation_factor(), max_u256); + + let usdc_price = Price { min: 1000000, max: 1000000 }; + let eth_price = Price { min: 17500000000000, max: 17500000000000 }; + let mut market = Market { + market_token: contract_address_const::<'market'>(), + index_token: collateral_token, + long_token: token1, + short_token: token1, + }; + data_store.set_market(market.market_token, 0, market); + + // Set position + let pos_key = get_position_key(account, market.market_token, collateral_token, true); + let mut position: Position = Default::default(); + position.account = account; + position.size_in_usd = precision::FLOAT_PRECISION_SQRT; + position.collateral_token = collateral_token; + position.is_long = true; + position.size_in_tokens = 100; + data_store.set_position(pos_key, position); + + // Set open interest + let interest_key1 = keys::open_interest_key(market.market_token, market.long_token, true); + data_store.set_u256(interest_key1, 1000000000000000000000); + + let interest_key2 = keys::open_interest_key(market.market_token, collateral_token, true); + data_store.set_u256(interest_key2, 10000000000); + let interest_key3 = keys::open_interest_in_tokens_key( + market.market_token, collateral_token, true + ); + data_store.set_u256(interest_key3, 10000000000); + + let current_nonce = nonce_utils::get_current_nonce(data_store); + + let oracle_params = mock_set_prices_params(token1, collateral_token); + + // Test + liquidation_handler_dispatcher + .execute_liquidation( + account: account, + market: market.market_token, + collateral_token: collateral_token, + is_long: true, + oracle_params: oracle_params + ); + + // Check 'with_oracle_prices_after' clear the prices + let prices_count = oracle.get_tokens_with_prices_count(); + assert(prices_count == 0, 'invalid prices_count'); + + // Check new order is created and nonce is increased + let last_nonce = nonce_utils::get_current_nonce(data_store); + assert(last_nonce == current_nonce + 1, 'invalid last_nonce'); + + // Check new order removed + let order_key = nonce_utils::compute_key(data_store.contract_address, last_nonce); + let order_by_key = data_store.get_order(order_key); + assert(order_by_key == Default::default(), 'Invalid order by key'); +} + + +// ********************************************************************************************* +// * SETUP * +// ********************************************************************************************* + +fn mock_set_prices_params(token1: ContractAddress, token2: ContractAddress) -> SetPricesParams { + SetPricesParams { + signer_info: 1, + tokens: array![token1], + compacted_min_oracle_block_numbers: array![10,], + compacted_max_oracle_block_numbers: array![20], + compacted_oracle_timestamps: array![1000,], + compacted_decimals: array![18], + compacted_min_prices: array![1700,], + compacted_min_prices_indexes: array![0,], + compacted_max_prices: array![1750], + compacted_max_prices_indexes: array![0,], + signatures: array![array!['signature1', 'signature2'].span()], + price_feed_tokens: array![token2], + } +} + + +fn admin() -> ContractAddress { + contract_address_const::<'caller'>() +} + + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, admin()); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, admin()); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_order_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('OrderVault'); + + let deployed_contract_address = contract_address_const::<'order_vault'>(); + start_prank(deployed_contract_address, admin()); + contract + .deploy_at( + @array![data_store_address.into(), role_store_address.into()], deployed_contract_address + ) + .unwrap() +} + +fn deploy_liquidation_handler( + role_store_address: ContractAddress, + data_store_address: ContractAddress, + event_emitter_address: ContractAddress, + order_vault_address: ContractAddress, + swap_handler_address: ContractAddress, + oracle_address: ContractAddress, + ref_storage_address: ContractAddress +) -> ContractAddress { + let contract = declare('LiquidationHandler'); + + let deployed_contract_address = contract_address_const::<'liquidation_handler'>(); + start_prank(deployed_contract_address, admin()); + contract + .deploy_at( + @array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + order_vault_address.into(), + oracle_address.into(), + swap_handler_address.into(), + ref_storage_address.into() + ], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_oracle( + role_store_address: ContractAddress, + oracle_store_address: ContractAddress, + pragma_address: ContractAddress +) -> ContractAddress { + let contract = declare('Oracle'); + + let deployed_contract_address = contract_address_const::<'oracle'>(); + start_prank(deployed_contract_address, admin()); + contract + .deploy_at( + @array![role_store_address.into(), oracle_store_address.into(), pragma_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_swap_handler( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('SwapHandler'); + + let deployed_contract_address = contract_address_const::<'swap_handler'>(); + start_prank(deployed_contract_address, admin()); + contract.deploy_at(@array![role_store_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_referral_storage(event_emitter: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let deployed_contract_address = contract_address_const::<'referral_storage'>(); + start_prank(deployed_contract_address, admin()); + contract.deploy_at(@array![event_emitter.into()], deployed_contract_address).unwrap() +} + +fn deploy_oracle_store( + role_store_address: ContractAddress, event_emitter_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OracleStore'); + + let deployed_contract_address = contract_address_const::<'oracle_store'>(); + start_prank(deployed_contract_address, admin()); + contract + .deploy_at( + @array![role_store_address.into(), event_emitter_address.into()], + deployed_contract_address + ) + .unwrap() +} + + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let deployed_contract_address: ContractAddress = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, admin()); + contract.deploy_at(@array![admin().into()], deployed_contract_address).unwrap() +} + +fn deploy_price_feed() -> ContractAddress { + let contract = declare('PriceFeed'); + let deployed_contract_address: ContractAddress = contract_address_const::<'price_feed'>(); + start_prank(deployed_contract_address, admin()); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_signers( + signer1: ContractAddress, signer2: ContractAddress +) -> (ContractAddress, ContractAddress) { + let contract = declare('MockAccount'); + ( + contract.deploy_at(@array![], signer1).unwrap(), + contract.deploy_at(@array![], signer2).unwrap() + ) +} + + +fn setup_tokens() -> (ContractAddress, ContractAddress, ContractAddress) { + let contract = declare('ERC20'); + let deployed_contract_address: ContractAddress = contract_address_const::<'USDC'>(); + let mut constructor_calldata = array![ + 'USDC', 'USDC', 10000000000000000000000000000, 0, admin().into() + ]; + + let usdc_address = contract + .deploy_at(@constructor_calldata, deployed_contract_address) + .unwrap(); + + let deployed_contract_address2: ContractAddress = contract_address_const::<'ETH'>(); + let constructor_calldata2 = array![ + 'ETH', 'ETH', 100000000000000000000000000000, 0, admin().into() + ]; + let eth_address = contract + .deploy_at(@constructor_calldata2, deployed_contract_address2) + .unwrap(); + + let deployed_contract_address3: ContractAddress = contract_address_const::<'FEE'>(); + let constructor_calldata3 = array![ + 'FEE', 'FEE', 100000000000000000000000000000, 0, admin().into() + ]; + let fee_address = contract + .deploy_at(@constructor_calldata3, deployed_contract_address3) + .unwrap(); + + (usdc_address, eth_address, fee_address) +} + + +fn _setup() -> ( + IDataStoreDispatcher, + ContractAddress, + ContractAddress, + ILiquidationHandlerDispatcher, + IEventEmitterDispatcher, + IRoleStoreDispatcher, + IOracleDispatcher, + IAccountDispatcher, + IAccountDispatcher +) { + let liquidation_keeper: ContractAddress = 0x2233.try_into().unwrap(); + let role_store_address = deploy_role_store(); + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + + let data_store_address = deploy_data_store(role_store_address); + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + let event_emitter_address = deploy_event_emitter(); + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + let order_vault_address = deploy_order_vault(data_store_address, role_store_address); + let swap_handler_address = deploy_swap_handler(role_store_address, data_store_address); + let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); + let price_feed = deploy_price_feed(); + let oracle_address = deploy_oracle(role_store_address, oracle_store_address, price_feed); + + let ref_storage_address = deploy_referral_storage(event_emitter_address); + let liquidation_handler_address = deploy_liquidation_handler( + role_store_address, + data_store_address, + event_emitter_address, + order_vault_address, + swap_handler_address, + oracle_address, + ref_storage_address + ); + let liquidation_handler_dispatcher = ILiquidationHandlerDispatcher { + contract_address: liquidation_handler_address + }; + + let oracle_store = IOracleStoreDispatcher { contract_address: oracle_store_address }; + + let (signer1, signer2) = deploy_signers( + contract_address_const::<'signer1'>(), contract_address_const::<'signer2'>() + ); + start_prank(oracle_store_address, admin()); + oracle_store.add_signer(signer1); + oracle_store.add_signer(signer2); + stop_prank(oracle_store_address); + + start_prank(role_store.contract_address, admin()); + role_store.grant_role(liquidation_handler_address, role::CONTROLLER); + role_store.grant_role(admin(), role::MARKET_KEEPER); + role_store.grant_role(admin(), role::CONTROLLER); + stop_prank(role_store.contract_address); + + start_prank(data_store_address, admin()); + + ( + data_store, + liquidation_keeper, + liquidation_handler_address, + liquidation_handler_dispatcher, + event_emitter, + role_store, + IOracleDispatcher { contract_address: oracle_address }, + IAccountDispatcher { contract_address: signer1 }, + IAccountDispatcher { contract_address: signer2 }, + ) +} diff --git a/tests/exchange/test_withdrawal_handler.cairo b/tests/exchange/test_withdrawal_handler.cairo new file mode 100644 index 00000000..c97315c9 --- /dev/null +++ b/tests/exchange/test_withdrawal_handler.cairo @@ -0,0 +1,536 @@ +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const +}; +use snforge_std::{ + declare, start_prank, stop_prank, start_mock_call, stop_mock_call, ContractClassTrait +}; +use satoru::utils::span32::{Span32, Span32Trait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::exchange::withdrawal_handler::{ + IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait +}; +use satoru::withdrawal::withdrawal_vault::{ + IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait +}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::fee::fee_handler::{IFeeHandlerDispatcher, IFeeHandlerDispatcherTrait}; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::data::keys; +use satoru::oracle::oracle_utils::{SetPricesParams, SimulatePricesParams}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::role::role; +use satoru::withdrawal::withdrawal_utils::CreateWithdrawalParams; +use satoru::withdrawal::withdrawal::Withdrawal; +use satoru::market::market::Market; +use traits::Default; + +// This tests check withdrawal creation under normal condition +// It calls withdrawal_handler.create_withdrawal +// The test expects the call to succeed without error +#[test] +fn given_normal_conditions_when_create_withdrawal_then_works() { + let (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) = + setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let account = contract_address_const::<'account'>(); + let market_token = contract_address_const::<'market_token'>(); + let params = create_withrawal_params(market_token); + + let address_zero = contract_address_const::<0>(); + + let mut market = Market { + market_token: market_token, + index_token: address_zero, + long_token: address_zero, + short_token: address_zero, + }; + + data_store.set_market(market_token, 0, market); + start_mock_call(withdrawal_vault_address, 'record_transfer_in', 1_u256); + let key = withdrawal_handler.create_withdrawal(account, params); + + //check withdrawal datas created + let withdrawal = data_store.get_withdrawal(key); + assert(withdrawal.key == key, 'Invalid withdrawal key'); + assert(withdrawal.account == account, 'Invalid withdrawal account'); +} + +// This tests check withdrawal creation when market_token_amount is 0 +// It calls withdrawal_handler.create_withdrawal +// The test expects the call to panic with error empty withdrawal amount +#[test] +#[should_panic(expected: ('empty withdrawal amount',))] +fn given_market_token_amount_equal_zero_when_create_withdrawal_then_fails() { + let (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) = + setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let account = contract_address_const::<'account'>(); + let market_token = contract_address_const::<'market_token'>(); + let params = create_withrawal_params(market_token); + + withdrawal_handler.create_withdrawal(account, params); +} + +// This tests check withdrawal creation when fee token amount is lower than execution fee +// It calls withdrawal_handler.create_withdrawal +// The test expects the call to panic with the error 'insufficient fee token amout' +#[test] +#[should_panic(expected: ('insufficient fee token amout', 0, 1))] +fn given_fee_token_lower_than_execution_fee_conditions_when_create_withdrawal_then_fails() { + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let account = contract_address_const::<'account'>(); + let market_token = contract_address_const::<'market_token'>(); + let mut params = create_withrawal_params(market_token); + params.execution_fee = 1; + + withdrawal_handler.create_withdrawal(account, params); +} + +// This tests check withdrawal creation when caller address doesn't meet controller role +// It calls withdrawal_handler.create_withdrawal +// The test expects the call to panic with the error 'unauthorized_access'. +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_not_controller_when_create_withdrawal_then_fails() { + // Should revert, call from anyone else then controller. + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); + let caller: ContractAddress = 0x847.try_into().unwrap(); + start_prank(withdrawal_handler.contract_address, caller); + + let key = contract_address_const::<'market'>(); + + let params = create_withrawal_params(key); + + withdrawal_handler.create_withdrawal(caller, params); +} + +// This test checks withdrawal cancellation under normal condition +// It calls withdrawal_handler.cancel_withdrawal +// The test expects the call to succeed without error +#[test] +fn given_normal_conditions_when_cancel_withdrawal_then_works() { + let (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) = + setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let account = contract_address_const::<'account'>(); + let key = contract_address_const::<'market'>(); + + let market = Market { + market_token: key, + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + + data_store.set_market(key, 0, market); + + let params = create_withrawal_params(key); + start_mock_call(withdrawal_vault_address, 'record_transfer_in', 1_u256); + + let withdrawal_key = withdrawal_handler.create_withdrawal(account, params); + + start_mock_call( + withdrawal_vault_address, + 'transfer_out', + array![contract_address_const::<'market_token'>().into(), account.into(), '1'] + ); + // Key cleaning should be done in withdrawal_utils. We only check call here. + withdrawal_handler.cancel_withdrawal(withdrawal_key); + + //check withdrawal correctly removed + let address_zero = contract_address_const::<0>(); + let withdrawal = data_store.get_withdrawal(withdrawal_key); + + assert(withdrawal.key == 0, 'Invalid key'); + assert(withdrawal.account == address_zero, 'Invalid account'); + assert(withdrawal.receiver == address_zero, 'Invalid receiver'); + assert(withdrawal.callback_contract == address_zero, 'Invalid callback after'); + assert(withdrawal.ui_fee_receiver == address_zero, 'Invalid ui_fee_receiver'); + assert(withdrawal.long_token_swap_path.len() == 0, 'Invalid long_swap_path'); + assert(withdrawal.short_token_swap_path.len() == 0, 'Invalid short_swap_path'); + assert(withdrawal.market_token_amount == 0, 'Invalid market_token_amount'); + assert(withdrawal.min_long_token_amount == 0, 'Invalid long_token_amount'); + assert(withdrawal.min_short_token_amount == 0, 'Invalid short_token_amount'); + assert(withdrawal.updated_at_block == 0, 'Invalid block'); + assert(withdrawal.execution_fee == 0, 'Invalid execution_fee'); + assert(withdrawal.callback_gas_limit == 0, 'Invalid callback_gas_limit'); +} + +// This tests check withdrawal cancellation when key doesn't exist in store +// It calls withdrawal_handler.cancel_withdrawal +// The test expects the call to panic with the error 'get_withdrawal failed'. +#[test] +#[should_panic(expected: ('empty withdrawal',))] +fn given_unexisting_key_when_cancel_withdrawal_then_fails() { + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let withdrawal_key = 'SAMPLE_WITHDRAW'; + + // Key cleaning should be done in withdrawal_utils. We only check call here. + withdrawal_handler.cancel_withdrawal(withdrawal_key); +} + +// This tests check withdrawal cancellation when account address is 0 +// It calls withdrawal_handler.cancel_withdrawal +// The test expects the call to panic with the error 'empty withdrawal'. +#[test] +#[should_panic(expected: ('empty withdrawal',))] +fn given_account_address_zero_when_cancel_withdrawal_then_fails() { + let mut withdrawal = Withdrawal { + key: 0, + account: contract_address_const::<0>(), + receiver: contract_address_const::<0>(), + callback_contract: contract_address_const::<0>(), + ui_fee_receiver: contract_address_const::<0>(), + market: contract_address_const::<0>(), + long_token_swap_path: Default::default(), + short_token_swap_path: Default::default(), + market_token_amount: 0, + min_long_token_amount: 0, + min_short_token_amount: 0, + updated_at_block: 0, + execution_fee: 0, + callback_gas_limit: 0, + }; + + let (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) = + setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let account = contract_address_const::<'account'>(); + let key = contract_address_const::<'market'>(); + let mut params = create_withrawal_params(key); + + let market = Market { + market_token: key, + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + + data_store.set_market(key, 0, market); + + start_mock_call(withdrawal_vault_address, 'record_transfer_in', 1_u256); + + let withdrawal_key = withdrawal_handler.create_withdrawal(account, params); + + start_mock_call(data_store.contract_address, 'get_withdrawal', withdrawal); + + // Key cleaning should be done in withdrawal_utils. We only check call here. + withdrawal_handler.cancel_withdrawal(withdrawal_key); +} + + +// This tests check withdrawal cancellation when market token amount is 0 +// It calls withdrawal_handler.cancel_withdrawal +// The test expects the call to panic with the error 'empty withdrawal'. +#[test] +#[should_panic(expected: ('empty withdrawal amount',))] +fn given_market_token_equals_zero_when_cancel_withdrawal_then_fails() { + let mut withdrawal = Withdrawal { + key: 0, + account: contract_address_const::<'account'>(), + receiver: contract_address_const::<0>(), + callback_contract: contract_address_const::<0>(), + ui_fee_receiver: contract_address_const::<0>(), + market: contract_address_const::<0>(), + long_token_swap_path: Default::default(), + short_token_swap_path: Default::default(), + market_token_amount: 0, + min_long_token_amount: 0, + min_short_token_amount: 0, + updated_at_block: 0, + execution_fee: 0, + callback_gas_limit: 0, + }; + + let (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) = + setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let account = contract_address_const::<'account'>(); + let key = contract_address_const::<'market'>(); + let mut params = create_withrawal_params(key); + + let market = Market { + market_token: key, + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + + data_store.set_market(key, 0, market); + + start_mock_call(withdrawal_vault_address, 'record_transfer_in', 1_u256); + + let withdrawal_key = withdrawal_handler.create_withdrawal(account, params); + + stop_mock_call(withdrawal_vault_address, 'record_transfer_in'); + start_mock_call(data_store.contract_address, 'get_withdrawal', withdrawal); + + // Key cleaning should be done in withdrawal_utils. We only check call here. + withdrawal_handler.cancel_withdrawal(withdrawal_key); +} + +// This tests check withdrawal execution when caller address doesn't meet controller role +// It calls withdrawal_handler.execute_withdrawal +// The test expects the call to panic with the error 'unauthorized_access'. +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_not_keeper_when_execute_withdrawal_then_fails() { + let oracle_params = SetPricesParams { + signer_info: Default::default(), + tokens: Default::default(), + compacted_min_oracle_block_numbers: Default::default(), + compacted_max_oracle_block_numbers: Default::default(), + compacted_oracle_timestamps: Default::default(), + compacted_decimals: Default::default(), + compacted_min_prices: Default::default(), + compacted_min_prices_indexes: Default::default(), + compacted_max_prices: Default::default(), + compacted_max_prices_indexes: Default::default(), + signatures: Default::default(), + price_feed_tokens: Default::default(), + }; + + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); + + let withdrawal_key = 'SAMPLE_WITHDRAW'; + + withdrawal_handler.execute_withdrawal(withdrawal_key, oracle_params); +} + +// TODO crashes because of gas_left function. +// #[test] +// #[should_panic(expected: ('invalid withdrawal key', 'SAMPLE_WITHDRAW'))] +// fn given_invalid_withdrawal_key_when_execute_withdrawal_then_fails() { +// let oracle_params = SetPricesParams { +// signer_info: Default::default(), +// tokens: Default::default(), +// compacted_min_oracle_block_numbers: Default::default(), +// compacted_max_oracle_block_numbers: Default::default(), +// compacted_oracle_timestamps: Default::default(), +// compacted_decimals: Default::default(), +// compacted_min_prices: Default::default(), +// compacted_min_prices_indexes: Default::default(), +// compacted_max_prices: Default::default(), +// compacted_max_prices_indexes: Default::default(), +// signatures: Default::default(), +// price_feed_tokens: Default::default(), +// }; + +// let (caller_address, data_store, event_emitter, withdrawal_handler,_) = setup(); +// let order_keeper = contract_address_const::<0x2233>(); +// start_prank(withdrawal_handler.contract_address, order_keeper); + +// let withdrawal_key = 'SAMPLE_WITHDRAW'; + +// withdrawal_handler.execute_withdrawal(withdrawal_key, oracle_params); +// } + +// This tests check withdrawal simulation when when caller address doesn't meet controller role +// It calls withdrawal_handler.simulate_execute_withdrawal +// The test expects the call to panic with the error 'unauthorized_access'. +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_not_controller_when_simulate_execute_withdrawal_then_fails() { + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); + let caller: ContractAddress = contract_address_const::<0x847>(); + start_prank(withdrawal_handler.contract_address, caller); + + let oracle_params = SimulatePricesParams { + primary_tokens: Default::default(), primary_prices: Default::default(), + }; + + let withdrawal_key = 'SAMPLE_WITHDRAW'; + + withdrawal_handler.simulate_execute_withdrawal(withdrawal_key, oracle_params); +} + +// This tests check withdrawal simulation when key is unknown +// It calls withdrawal_handler.simulate_execute_withdrawal +// The test expects the call to panic with the error 'invalid withdrawal key','SAMPLE_WITHDRAW'. +#[test] +#[should_panic(expected: ('withdrawal not found',))] +fn given_invalid_withdrawal_key_when_simulate_execute_withdrawal_then_fails() { + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); + let oracle_params = SimulatePricesParams { + primary_tokens: Default::default(), primary_prices: Default::default(), + }; + + start_prank(withdrawal_handler.contract_address, caller_address); + + let withdrawal_key = 'SAMPLE_WITHDRAW'; + + withdrawal_handler.simulate_execute_withdrawal(withdrawal_key, oracle_params); +} + +fn create_withrawal_params(market: ContractAddress) -> CreateWithdrawalParams { + CreateWithdrawalParams { + receiver: contract_address_const::<'receiver'>(), + callback_contract: contract_address_const::<'callback_contract'>(), + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + market, + long_token_swap_path: Default::default(), + short_token_swap_path: Default::default(), + min_long_token_amount: Default::default(), + min_short_token_amount: Default::default(), + execution_fee: Default::default(), + callback_gas_limit: Default::default(), + } +} + +fn deploy_tokens() -> (ContractAddress, ContractAddress) { + let contract = declare('ERC20'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + + let fee_token_address = contract_address_const::<'fee_token'>(); + let constructor_calldata = array!['FEE_TOKEN', 'FEE', 1000000, 0, 0x101]; + contract.deploy_at(@constructor_calldata, fee_token_address).unwrap(); + + let market_token_address = contract_address_const::<'market_token'>(); + let constructor_calldata = array!['MARKET_TOKEN', 'MKT', 1000000, 0, caller_address.into()]; + contract.deploy_at(@constructor_calldata, market_token_address).unwrap(); + + (fee_token_address, market_token_address) +} + +fn deploy_withdrawal_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + withdrawal_vault_address: ContractAddress, + oracle_address: ContractAddress +) -> ContractAddress { + let contract = declare('WithdrawalHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'withdrawal_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + withdrawal_vault_address.into(), + oracle_address.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_oracle( + role_store_address: ContractAddress, + oracle_store_address: ContractAddress, + pragma_address: ContractAddress +) -> ContractAddress { + let contract = declare('Oracle'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![role_store_address.into(), oracle_store_address.into(), pragma_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_oracle_store( + role_store_address: ContractAddress, event_emitter_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OracleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle_store'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![role_store_address.into(), event_emitter_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_withdrawal_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('WithdrawalVault'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'withdrawal_vault'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![data_store_address.into(), role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn setup() -> ( + ContractAddress, + IDataStoreDispatcher, + IEventEmitterDispatcher, + IWithdrawalHandlerDispatcher, + ContractAddress +) { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let order_keeper: ContractAddress = 0x2233.try_into().unwrap(); + let (fee_token_address, _) = deploy_tokens(); + let role_store_address = deploy_role_store(); + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + let data_store_address = deploy_data_store(role_store_address); + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + let event_emitter_address = deploy_event_emitter(); + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + let withdrawal_vault_address = deploy_withdrawal_vault(data_store_address, role_store_address); + let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); + let oracle_address = deploy_oracle( + oracle_store_address, role_store_address, contract_address_const::<'pragma'>() + ); + let withdrawal_handler_address = deploy_withdrawal_handler( + data_store_address, + role_store_address, + event_emitter_address, + withdrawal_vault_address, + oracle_address + ); + + let withdrawal_handler = IWithdrawalHandlerDispatcher { + contract_address: withdrawal_handler_address + }; + start_prank(role_store_address, caller_address); + role_store.grant_role(caller_address, role::CONTROLLER); + role_store.grant_role(order_keeper, role::ORDER_KEEPER); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + role_store.grant_role(withdrawal_handler_address, role::CONTROLLER); + start_prank(data_store_address, caller_address); + + data_store.set_address(keys::fee_token(), fee_token_address); + //let market_token = IMarketTokenDispatcher { contract_address: market_token_address }; + + (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) +} diff --git a/src/tests/feature/test_feature_utils.cairo b/tests/feature/test_feature_utils.cairo similarity index 100% rename from src/tests/feature/test_feature_utils.cairo rename to tests/feature/test_feature_utils.cairo diff --git a/src/tests/fee/test_fee_handler.cairo b/tests/fee/test_fee_handler.cairo similarity index 70% rename from src/tests/fee/test_fee_handler.cairo rename to tests/fee/test_fee_handler.cairo index b7d15f0d..23b818b4 100644 --- a/src/tests/fee/test_fee_handler.cairo +++ b/tests/fee/test_fee_handler.cairo @@ -6,9 +6,6 @@ use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::fee::fee_handler::{IFeeHandlerDispatcher, IFeeHandlerDispatcherTrait}; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; -use satoru::data::keys::{ - claim_fee_amount_key, claim_ui_fee_amount_key, claim_ui_fee_amount_for_account_key -}; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::role::role; @@ -22,8 +19,7 @@ fn given_normal_conditions_when_fee_handler_then_works() { let tokens: Array = array![ 0x123.try_into().unwrap(), 0x234.try_into().unwrap(), 0x345.try_into().unwrap() ]; - - fee_handler.claim_fees(markets, tokens); +// fee_handler.claim_fees(markets, tokens); TODO wait for market_utils to be implemented } #[test] @@ -47,32 +43,44 @@ fn deploy_fee_handler( event_emitter_address: ContractAddress ) -> ContractAddress { let contract = declare('FeeHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'fee_handler'>(); + start_prank(deployed_contract_address, caller_address); let constructor_calldata = array![ data_store_address.into(), role_store_address.into(), event_emitter_address.into() ]; - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() } fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); let constructor_calldata = array![role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() } fn deploy_role_store() -> ContractAddress { let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } fn deploy_event_emitter() -> ContractAddress { let contract = declare('EventEmitter'); - contract.deploy(@array![]).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() } fn setup() -> ( ContractAddress, IDataStoreDispatcher, IEventEmitterDispatcher, IFeeHandlerDispatcher ) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); let role_store_address = deploy_role_store(); let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; let data_store_address = deploy_data_store(role_store_address); diff --git a/src/tests/fee/test_fee_utils.cairo b/tests/fee/test_fee_utils.cairo similarity index 65% rename from src/tests/fee/test_fee_utils.cairo rename to tests/fee/test_fee_utils.cairo index dee8e1f7..665fdf15 100644 --- a/src/tests/fee/test_fee_utils.cairo +++ b/tests/fee/test_fee_utils.cairo @@ -6,7 +6,7 @@ use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::data::keys::{ - claim_fee_amount_key, claim_ui_fee_amount_key, claim_ui_fee_amount_for_account_key + claimable_fee_amount_key, claimable_ui_fee_amount_key, claimable_ui_fee_amount_for_account_key }; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::role::role; @@ -19,21 +19,21 @@ fn given_normal_conditions_when_increment_claimable_fee_amount_then_works() { let market: ContractAddress = 0x555.try_into().unwrap(); let token: ContractAddress = 0x666.try_into().unwrap(); - let key = claim_fee_amount_key( + let key = claimable_fee_amount_key( market, token ); // Calculate slot key to get initial value of slot. - let initial_value = data_store.get_u128(key); - assert(initial_value == 0_u128, 'initial value wrong'); + let initial_value = data_store.get_u256(key); + assert(initial_value == 0_u256, 'initial value wrong'); // Change value with util function. - let delta = 50_u128; + let delta = 50_u256; let fee_type = 'FEE_TYPE'; increment_claimable_fee_amount(data_store, event_emitter, market, token, delta, fee_type); - let final_value = data_store.get_u128(key); + let final_value = data_store.get_u256(key); assert(final_value == delta, 'Final value wrong'); } @@ -46,57 +46,52 @@ fn given_normal_conditions_when_increment_claimable_ui_fee_amount_then_works() { let token: ContractAddress = 0x666.try_into().unwrap(); let ui_fee_receiver: ContractAddress = 0x777.try_into().unwrap(); - let key = claim_ui_fee_amount_for_account_key(market, token, ui_fee_receiver); - let pool_key = claim_ui_fee_amount_key(market, token); + let key = claimable_ui_fee_amount_for_account_key(market, token, ui_fee_receiver); + let pool_key = claimable_ui_fee_amount_key(market, token); - let initial_value = data_store.get_u128(key); - let initial_pool_value = data_store.get_u128(pool_key); + let initial_value = data_store.get_u256(key); + let initial_pool_value = data_store.get_u256(pool_key); assert(initial_value == 0, 'Initial value wrong'); assert(initial_pool_value == 0, 'Initial pool value wrong'); - let delta = 75_u128; + let delta = 75_u256; let fee_type = 'UI_FEE_TYPE'; increment_claimable_ui_fee_amount( data_store, event_emitter, ui_fee_receiver, market, token, delta, fee_type ); - let final_value = data_store.get_u128(key); - let final_pool_value = data_store.get_u128(pool_key); + let final_value = data_store.get_u256(key); + let final_pool_value = data_store.get_u256(pool_key); assert(final_value == delta, 'Final value wrong'); assert(final_pool_value == delta, 'Final pool value wrong'); } -/// Utility function to deploy a data store contract and return its address. -/// -/// # Arguments -/// -/// * `role_store_address` - The address of the role store contract. -/// -/// # Returns -/// -/// * `ContractAddress` - The address of the deployed data store contract. fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); let constructor_calldata = array![role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() } -/// Utility function to deploy a role store contract and return its address. -/// -/// # Returns -/// -/// * `ContractAddress` - The address of the deployed role store contract. fn deploy_role_store() -> ContractAddress { let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } fn deploy_event_emitter() -> ContractAddress { let contract = declare('EventEmitter'); - contract.deploy(@array![]).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() } /// Utility function to setup the test environment. @@ -107,7 +102,7 @@ fn deploy_event_emitter() -> ContractAddress { /// * `IRoleStoreDispatcher` - The role store dispatcher. /// * `IDataStoreDispatcher` - The data store dispatcher. fn setup() -> (ContractAddress, IDataStoreDispatcher, IEventEmitterDispatcher) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); let role_store_address = deploy_role_store(); let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; let data_store_address = deploy_data_store(role_store_address); diff --git a/tests/integration/swap_test.cairo b/tests/integration/swap_test.cairo new file mode 100644 index 00000000..e09f24d8 --- /dev/null +++ b/tests/integration/swap_test.cairo @@ -0,0 +1,279 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +use result::ResultTrait; +use debug::PrintTrait; +use traits::{TryInto, Into}; +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, + ClassHash, +}; +use snforge_std::{declare, start_prank, stop_prank, start_roll, ContractClassTrait, ContractClass}; + + +// Local imports. +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::order::order_utils::{IOrderUtilsDispatcher, IOrderUtilsDispatcherTrait}; +use satoru::market::market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; +use satoru::deposit::deposit::Deposit; +use satoru::withdrawal::withdrawal::Withdrawal; + +use satoru::exchange::withdrawal_handler::{ + IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait +}; +use satoru::exchange::deposit_handler::{IDepositHandlerDispatcher, IDepositHandlerDispatcherTrait}; +use satoru::router::exchange_router::{IExchangeRouterDispatcher, IExchangeRouterDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::reader::reader::{IReaderDispatcher, IReaderDispatcherTrait}; +use satoru::market::market::{Market, UniqueIdMarket}; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::role::role; +use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::test_utils::tests_lib; +use satoru::deposit::deposit_utils::CreateDepositParams; +use satoru::utils::span32::{Span32, DefaultSpan32, Array32Trait}; +use satoru::deposit::deposit_utils; +use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; +use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::withdrawal::withdrawal_vault::{ + IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait +}; +use satoru::data::keys; +use satoru::market::market_utils; +use satoru::price::price::{Price, PriceTrait}; +use satoru::position::position_utils; +use satoru::withdrawal::withdrawal_utils; + +use satoru::exchange::liquidation_handler::{ + ILiquidationHandlerDispatcher, ILiquidationHandlerDispatcherTrait +}; +use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; +use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; +use satoru::order::base_order_utils::{CreateOrderParams}; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::market::{market::{UniqueIdMarketImpl},}; +use satoru::exchange::order_handler::{ + OrderHandler, IOrderHandlerDispatcher, IOrderHandlerDispatcherTrait +}; +use satoru::test_utils::{tests_lib::{setup, create_market, teardown}, deposit_setup::deposit_setup}; +const INITIAL_TOKENS_MINTED: felt252 = 1000; + +#[test] +fn test_swap_market() { + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market, + ) = + deposit_setup( + 20000000000000000000, 100000000000000000000000 + ); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 5000, max: 5000, }, + Price { min: 5000, max: 5000, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + // 200 000 USD + assert(pool_value_info.pool_value.mag == 200000000000000000000000, 'wrong pool_value balance'); + // 20 ETH + assert(pool_value_info.long_token_amount == 20000000000000000000, 'wrong long_token balance'); + // 100 000 USDC + assert( + pool_value_info.short_token_amount == 100000000000000000000000, 'wrong short_token balance' + ); + + // // --------------------------------------------------SWAP TEST ETH->USDC -------------------------------------------------- + 'Swap ETH to USDC'.print(); + let balance_ETH_before_swap = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + let balance_USDC_before_swap = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + // 10 ETH + assert(balance_ETH_before_swap == 10000000000000000000, 'wrong balance ETH before swap'); + // 50 000 USDC + assert(balance_USDC_before_swap == 50000000000000000000000, 'wrong balance USDC before swap'); + + start_prank(contract_address_const::<'ETH'>(), caller_address); //change to switch swap + // Send token to order_vault in multicall with create_order + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } //change to switch swap + .transfer(order_vault.contract_address, 1000000000000000000); + + let balance_ETH_before = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + let balance_USDC_before = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + // Balance caller address after sending 1 ETH to the vault + // 9 ETH + assert(balance_ETH_before == 9000000000000000000, 'wrng ETH blce after vlt'); + // 50 000 USDC + assert(balance_USDC_before == 50000000000000000000000, 'wrng USDC blce after vlt'); + + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.long_token, caller_address); //change to switch swap + + let order_params = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: contract_address, + initial_collateral_token: market.long_token, //change to switch swap + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 1000000000000000000, + initial_collateral_delta_amount: 1000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 4999, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketSwap(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: false, + referral_code: 0 + }; + // Create the swap order. + start_roll(order_handler.contract_address, 1920); + + // Create the order but we do not execute it yet + let key = order_handler.create_order(caller_address, order_params); + + let got_order = data_store.get_order(key); + + // Execute the swap order. + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![5000, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let balance_ETH_before_execute = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + let balance_USDC_before_execute = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + // 9 ETH + assert(balance_ETH_before_execute == 9000000000000000000, 'wrng ETH blce bef execute'); + // 50 000 USDC + assert(balance_USDC_before_execute == 50000000000000000000000, 'wrng USDC blce bef execute'); + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1925); + order_handler.execute_order(key, set_price_params); + + let balance_ETH_after = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .balance_of(caller_address); + + let balance_USDC_after = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + // 9 ETH + assert(balance_ETH_after == 9000000000000000000, 'wrng ETH blce after exec'); + // 55 000 USDC + assert(balance_USDC_after == 55000000000000000000000, 'wrng USDC blce after exec'); + + let first_swap_pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 5000, max: 5000, }, + Price { min: 5000, max: 5000, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + // 200 000 USD + assert( + first_swap_pool_value_info.pool_value.mag == 200000000000000000000000, + 'wrong pool_value balance' + ); + // 21 ETH + assert( + first_swap_pool_value_info.long_token_amount == 21000000000000000000, + 'wrong long_token balance' + ); + // 95 000 USDC + assert( + first_swap_pool_value_info.short_token_amount == 95000000000000000000000, + 'wrong short_token balance' + ); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} diff --git a/tests/integration/test_create_and_execute_swap.cairo b/tests/integration/test_create_and_execute_swap.cairo new file mode 100644 index 00000000..6cd03176 --- /dev/null +++ b/tests/integration/test_create_and_execute_swap.cairo @@ -0,0 +1,325 @@ +//! Test file for `src/exchange/base_order_handler.cairo`. + +// ************************************************************************* +// IMPORTS +// ************************************************************************* +// Core lib imports. +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const +}; +use snforge_std::{ + declare, start_prank, stop_prank, start_mock_call, test_address, ContractClassTrait, + ContractClass, start_roll +}; +use traits::Default; +use poseidon::poseidon_hash_span; +use debug::PrintTrait; +// Local imports. +use satoru::role::role; +use satoru::tests_lib; + +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::data::keys; +use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; +use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; +use satoru::order::base_order_utils::{CreateOrderParams}; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::utils::span32::{Span32, Array32Trait}; +use satoru::market::{ + market::{Market, UniqueIdMarketImpl}, + market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait} +}; +use satoru::exchange::order_handler::{ + OrderHandler, IOrderHandlerDispatcher, IOrderHandlerDispatcherTrait +}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + +// ********************************************************************************************* +// * TESTS * +// ********************************************************************************************* +#[test] +fn given_right_swap_order_params_when_execute_order_then_success() { + // Setup + let ( + caller_address, + role_store, + data_store, + event_emitter, + order_vault, + oracle, + swap_handler, + referral_storage, + order_handler, + market_factory + ) = + setup_contracts(); + let contract_address = contract_address_const::<0>(); + // Test + // Create market. + let market = data_store.get_market(create_market(market_factory)); + + // Transfer tokens in the order_vault in order for initial_collateral_delta_amount to be non zero. + start_prank(contract_address_const::<'ETH'>(), caller_address); + start_prank(contract_address_const::<'USDC'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 5000000000000000000000000000000); + // IERC20Dispatcher {contract_address: contract_address_const::<'USDC'>()} + // .transfer(order_vault.contract_address, 5000000000000000000000000000000); + + // Fill the pool. + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(market.market_token, 100000000000000000000000000000); + IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } + .transfer(market.market_token, 300000000000000000000000000000000); + + // Set pool amount in data_store. + let mut key = keys::pool_amount_key(market.market_token, contract_address_const::<'ETH'>()); + data_store.set_u256(key, 100000000000000000000000000000); + key = keys::pool_amount_key(market.market_token, contract_address_const::<'USDC'>()); + data_store.set_u256(key, 300000000000000000000000000000000); + + // Set max pool amount. + data_store + .set_u256( + keys::max_pool_amount_key(market.market_token, contract_address_const::<'USDC'>()), + 5000000000000000000000000000000000000 + ); + data_store + .set_u256( + keys::max_pool_amount_key(market.market_token, contract_address_const::<'ETH'>()), + 5000000000000000000000000000000000000 + ); + // Set params in data_store. + data_store.set_address(keys::fee_token(), market.index_token); + data_store.set_u256(keys::max_swap_path_length(), 5); + + start_prank(market.long_token, caller_address); + let order_params = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: contract_address, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 1000, + initial_collateral_delta_amount: 5000000000000000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 0, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketSwap(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: false, + referral_code: 0 + }; + // Create the swap order. + start_roll(order_handler.contract_address, 1910); + let key = order_handler.create_order(caller_address, order_params); + + // data_store.set_u256(keys::pool_amount_key(market.market_token, contract_address_const::<'USDC'>()), ); + // data_store.set_u256(keys::pool_amount_key(market.market_token, contract_address_const::<'ETH'>()), 1000000); + // Execute the swap order. + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 1, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1900, 1900], + compacted_max_oracle_block_numbers: array![1910, 1910], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + start_prank(order_handler.contract_address, caller_address); + start_roll(order_handler.contract_address, 1915); + // TODO add real signatures check on Oracle Account + //order_handler.execute_order(key, set_price_params); + + // Teardown + tests_lib::teardown(data_store.contract_address); +} + +// ********************************************************************************************* +// * SETUP * +// ********************************************************************************************* + +/// Utility function to setup the test environment. +fn setup_contracts() -> ( + ContractAddress, + IRoleStoreDispatcher, + IDataStoreDispatcher, + IEventEmitterDispatcher, + IOrderVaultDispatcher, + IOracleDispatcher, + ISwapHandlerDispatcher, + IReferralStorageDispatcher, + IOrderHandlerDispatcher, + IMarketFactoryDispatcher +) { + let (caller_address, role_store, data_store, event_emitter, oracle) = + tests_lib::setup_oracle_and_store(); + + let order_vault_address = deploy_order_vault( + data_store.contract_address, role_store.contract_address + ); + let order_vault = IOrderVaultDispatcher { contract_address: order_vault_address }; + + let swap_handler_address = deploy_swap_handler(role_store.contract_address); + let swap_handler = ISwapHandlerDispatcher { contract_address: swap_handler_address }; + + let referral_storage_address = deploy_referral_storage(event_emitter.contract_address); + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let order_handler_address = deploy_order_handler( + data_store.contract_address, + role_store.contract_address, + event_emitter.contract_address, + order_vault_address, + oracle.contract_address, + swap_handler_address, + referral_storage_address + ); + let order_handler = IOrderHandlerDispatcher { contract_address: order_handler_address }; + + let market_token_class_hash = declare_market_token(); + let market_factory_address = deploy_market_factory( + data_store.contract_address, + role_store.contract_address, + event_emitter.contract_address, + market_token_class_hash + ); + // Create a safe dispatcher to interact with the contract. + let market_factory = IMarketFactoryDispatcher { contract_address: market_factory_address }; + + role_store.grant_role(caller_address, role::MARKET_KEEPER); + role_store.grant_role(caller_address, role::ORDER_KEEPER); + role_store.grant_role(order_handler.contract_address, role::CONTROLLER); + + return ( + caller_address, + role_store, + data_store, + event_emitter, + order_vault, + oracle, + swap_handler, + referral_storage, + order_handler, + market_factory + ); +} + +fn create_market(market_factory: IMarketFactoryDispatcher) -> ContractAddress { + // Create a market. + let (index_token, short_token) = deploy_tokens(); + let market_type = 'market_type'; + + // Index token is the same as long token here. + market_factory.create_market(index_token, index_token, short_token, market_type) +} + +/// Utility function to deploy an `OrderVault` contract and return its address. +fn deploy_order_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OrderVault'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + tests_lib::deploy_mock_contract(contract, @constructor_calldata) +} + +/// Utility functions to deploy tokens for a market. +fn deploy_tokens() -> (ContractAddress, ContractAddress) { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let contract = declare('ERC20'); + + let eth_address = contract_address_const::<'ETH'>(); + + let constructor_calldata = array![ + 'Ethereum', 'ETH', 50000000000000000000000000000000000000, 0, caller_address.into() + ]; + contract.deploy_at(@constructor_calldata, eth_address).unwrap(); + + let usdc_address = contract_address_const::<'USDC'>(); + let constructor_calldata = array![ + 'usdc', 'USDC', 50000000000000000000000000000000000000, 0, caller_address.into() + ]; + contract.deploy_at(@constructor_calldata, usdc_address).unwrap(); + (eth_address, usdc_address) +} + +/// Utility function to deploy an `OrderHandler` contract and return its address. +fn deploy_order_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + order_vault_address: ContractAddress, + oracle_address: ContractAddress, + swap_handler_address: ContractAddress, + referral_storage_address: ContractAddress +) -> ContractAddress { + let contract = declare('OrderHandler'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + constructor_calldata.append(event_emitter_address.into()); + constructor_calldata.append(order_vault_address.into()); + constructor_calldata.append(oracle_address.into()); + constructor_calldata.append(swap_handler_address.into()); + constructor_calldata.append(referral_storage_address.into()); + tests_lib::deploy_mock_contract(contract, @constructor_calldata) +} + +/// Utility function to deploy a `SwapHandler` contract and return its address. +fn deploy_swap_handler(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('SwapHandler'); + let constructor_calldata = array![role_store_address.into()]; + tests_lib::deploy_mock_contract(contract, @constructor_calldata) +} + +/// Utility function to deploy a `ReferralStorage` contract and return its address. +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let constructor_calldata = array![event_emitter_address.into()]; + tests_lib::deploy_mock_contract(contract, @constructor_calldata) +} + +/// Utility function to deploy a market factory contract and return its address. +fn deploy_market_factory( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + market_token_class_hash: ContractClass, +) -> ContractAddress { + let contract = declare('MarketFactory'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'market_factory'>(); + start_prank(deployed_contract_address, caller_address); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + constructor_calldata.append(event_emitter_address.into()); + constructor_calldata.append(market_token_class_hash.class_hash.into()); + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +/// Utility function to declare a `MarketToken` contract. +fn declare_market_token() -> ContractClass { + declare('MarketToken') +} diff --git a/tests/integration/test_deposit_withdrawal.cairo b/tests/integration/test_deposit_withdrawal.cairo new file mode 100644 index 00000000..496e3f14 --- /dev/null +++ b/tests/integration/test_deposit_withdrawal.cairo @@ -0,0 +1,1552 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +use result::ResultTrait; +use debug::PrintTrait; +use traits::{TryInto, Into}; +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, + ClassHash, +}; +use snforge_std::{declare, start_prank, stop_prank, start_roll, ContractClassTrait, ContractClass}; + + +// Local imports. +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::order::order_utils::{IOrderUtilsDispatcher, IOrderUtilsDispatcherTrait}; +use satoru::market::market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; +use satoru::deposit::deposit::Deposit; +use satoru::withdrawal::withdrawal::Withdrawal; + +use satoru::exchange::withdrawal_handler::{ + IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait +}; +use satoru::exchange::deposit_handler::{IDepositHandlerDispatcher, IDepositHandlerDispatcherTrait}; +use satoru::router::exchange_router::{IExchangeRouterDispatcher, IExchangeRouterDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::reader::reader::{IReaderDispatcher, IReaderDispatcherTrait}; +use satoru::market::market::{Market, UniqueIdMarket}; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::role::role; +use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::tests_lib; +use satoru::deposit::deposit_utils::CreateDepositParams; +use satoru::utils::span32::{Span32, DefaultSpan32, Array32Trait}; +use satoru::deposit::deposit_utils; +use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; +use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::withdrawal::withdrawal_vault::{ + IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait +}; +use satoru::data::keys; +use satoru::market::market_utils; +use satoru::price::price::{Price, PriceTrait}; +use satoru::position::position_utils; +use satoru::withdrawal::withdrawal_utils; + +use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; +use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; +use satoru::order::base_order_utils::{CreateOrderParams}; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::market::{market::{UniqueIdMarketImpl},}; +use satoru::exchange::order_handler::{ + OrderHandler, IOrderHandlerDispatcher, IOrderHandlerDispatcherTrait +}; +const INITIAL_TOKENS_MINTED: felt252 = 1000; + + +// #[test] +// #[should_panic(expected: ('unauthorized_access',))] +// fn given_normal_conditions_when_create_market_and_add_liquidity_then_market_is_created() { +// // ********************************************************************************************* +// // * SETUP * +// // ********************************************************************************************* +// let ( +// caller_address, +// market_factory_address, +// role_store_address, +// data_store_address, +// market_token_class_hash, +// market_factory, +// role_store, +// data_store, +// event_emitter, +// exchange_router, +// deposit_handler, +// deposit_vault, +// oracle, +// ) = +// setup(); + +// // ********************************************************************************************* +// // * TEST LOGIC * +// // ********************************************************************************************* + +// // Create a market. +// let market = data_store.get_market(create_market(market_factory)); +// // Set params in data_store +// data_store.set_address(keys::fee_token(), market.index_token); + +// let user1: ContractAddress = contract_address_const::<'user1'>(); +// let user2: ContractAddress = contract_address_const::<'user2'>(); + +// let addresss_zero: ContractAddress = 0.try_into().unwrap(); + +// let params = CreateDepositParams { +// receiver: user1, +// callback_contract: user2, +// ui_fee_receiver: addresss_zero, +// market: market.market_token, +// initial_long_token: market.long_token, +// initial_short_token: market.short_token, +// long_token_swap_path: Default::default(), +// short_token_swap_path: Default::default(), +// min_market_tokens: 0, +// execution_fee: 0, +// callback_gas_limit: 0, +// }; +// IERC20Dispatcher { contract_address: market.long_token } +// .mint(deposit_vault.contract_address, 100000000000000); +// IERC20Dispatcher { contract_address: market.short_token } +// .mint(deposit_vault.contract_address, 50000000000); +// start_roll(deposit_handler.contract_address, 1910); +// let key = deposit_handler.create_deposit(caller_address, params); +// let first_deposit = data_store.get_deposit(key); + +// assert(first_deposit.account == caller_address, 'Wrong account depositer'); +// assert(first_deposit.receiver == user1, 'Wrong account receiver'); +// assert(first_deposit.initial_long_token == market.long_token, 'Wrong initial long token'); +// assert( +// first_deposit.initial_long_token_amount == 1000000000000000000, +// 'Wrong initial long token amount' +// ); +// assert( +// first_deposit.initial_short_token_amount == 50000000000, 'Wrong init short token amount' +// ); + +// let price_params = SetPricesParams { +// signer_info: 1, +// tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], +// compacted_min_oracle_block_numbers: array![1900, 1900], +// compacted_max_oracle_block_numbers: array![1910, 1910], +// compacted_oracle_timestamps: array![9999, 9999], +// compacted_decimals: array![18, 18], +// compacted_min_prices: array![4294967346000000], // 50000000, 1000000 compacted +// compacted_min_prices_indexes: array![0], +// compacted_max_prices: array![4294967346000000], // 50000000, 1000000 compacted +// compacted_max_prices_indexes: array![0], +// signatures: array![ +// array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() +// ], +// price_feed_tokens: array![] +// }; + +// start_roll(deposit_handler.contract_address, 1915); +// deposit_handler.execute_deposit(key, price_params); + +// // ********************************************************************************************* +// // * TEARDOWN * +// // ********************************************************************************************* +// teardown(data_store, market_factory); +// } + +// #[test] +// fn test_swap_market_integration() { +// // ********************************************************************************************* +// // * SETUP * +// // ********************************************************************************************* +// let ( +// caller_address, +// market_factory_address, +// role_store_address, +// data_store_address, +// market_token_class_hash, +// market_factory, +// role_store, +// data_store, +// event_emitter, +// exchange_router, +// deposit_handler, +// deposit_vault, +// oracle, +// order_handler, +// order_vault, +// reader, +// referal_storage, +// ) = +// setup(); + +// // ********************************************************************************************* +// // * TEST LOGIC * +// // ********************************************************************************************* + +// // Create a market. +// let market = data_store.get_market(create_market(market_factory)); + +// // Set params in data_store +// data_store.set_address(keys::fee_token(), market.index_token); +// data_store.set_u256(keys::max_swap_path_length(), 5); + +// // Set max pool amount. +// data_store +// .set_u256( +// keys::max_pool_amount_key(market.market_token, market.long_token), 500000000000000000 +// ); +// data_store +// .set_u256( +// keys::max_pool_amount_key(market.market_token, market.short_token), 500000000000000000 +// ); + +// oracle.set_price_testing_eth(5000); + +// // Fill the pool. +// IERC20Dispatcher { contract_address: market.long_token }.mint(market.market_token, 50000000000); +// IERC20Dispatcher { contract_address: market.short_token } +// .mint(market.market_token, 50000000000); +// // TODO Check why we don't need to set pool_amount_key +// // // Set pool amount in data_store. +// // let mut key = keys::pool_amount_key(market.market_token, contract_address_const::<'ETH'>()); +// // data_store.set_u256(key, 50000000000); +// // key = keys::pool_amount_key(market.market_token, contract_address_const::<'USDC'>()); +// // data_store.set_u256(key, 50000000000); + +// // Send token to deposit in the deposit vault (this should be in a multi call with create_deposit) +// IERC20Dispatcher { contract_address: market.long_token } +// .mint(deposit_vault.contract_address, 50000000000); +// IERC20Dispatcher { contract_address: market.short_token } +// .mint(deposit_vault.contract_address, 50000000000); + +// let balance_deposit_vault_before = IERC20Dispatcher { contract_address: market.short_token } +// .balance_of(deposit_vault.contract_address); + +// // Create Deposit +// let user1: ContractAddress = contract_address_const::<'user1'>(); +// let user2: ContractAddress = contract_address_const::<'user2'>(); + +// let addresss_zero: ContractAddress = 0.try_into().unwrap(); + +// let params = CreateDepositParams { +// receiver: user1, +// callback_contract: user2, +// ui_fee_receiver: addresss_zero, +// market: market.market_token, +// initial_long_token: market.long_token, +// initial_short_token: market.short_token, +// long_token_swap_path: Array32Trait::::span32(@array![]), +// short_token_swap_path: Array32Trait::::span32(@array![]), +// min_market_tokens: 0, +// execution_fee: 0, +// callback_gas_limit: 0, +// }; + +// start_roll(deposit_handler.contract_address, 1910); +// let key = deposit_handler.create_deposit(caller_address, params); +// let first_deposit = data_store.get_deposit(key); + +// assert(first_deposit.account == caller_address, 'Wrong account depositer'); +// assert(first_deposit.receiver == user1, 'Wrong account receiver'); +// assert(first_deposit.initial_long_token == market.long_token, 'Wrong initial long token'); +// assert( +// first_deposit.initial_long_token_amount == 50000000000, 'Wrong initial long token amount' +// ); +// assert( +// first_deposit.initial_short_token_amount == 50000000000, 'Wrong init short token amount' +// ); + +// let price_params = SetPricesParams { // TODO +// signer_info: 1, +// tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], +// compacted_min_oracle_block_numbers: array![1900, 1900], +// compacted_max_oracle_block_numbers: array![1910, 1910], +// compacted_oracle_timestamps: array![9999, 9999], +// compacted_decimals: array![18, 18], +// compacted_min_prices: array![4294967346000000], // 50000000, 1000000 compacted +// compacted_min_prices_indexes: array![0], +// compacted_max_prices: array![4294967346000000], // 50000000, 1000000 compacted +// compacted_max_prices_indexes: array![0], +// signatures: array![ +// array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() +// ], +// price_feed_tokens: array![] +// }; + +// start_prank(role_store.contract_address, caller_address); + +// role_store.grant_role(caller_address, role::ORDER_KEEPER); +// role_store.grant_role(caller_address, role::ROLE_ADMIN); +// role_store.grant_role(caller_address, role::CONTROLLER); +// role_store.grant_role(caller_address, role::MARKET_KEEPER); + +// // Execute Deposit +// start_roll(deposit_handler.contract_address, 1915); +// deposit_handler.execute_deposit(key, price_params); + +// let pool_value_info = market_utils::get_pool_value_info( +// data_store, +// market, +// Price { min: 1999, max: 2000 }, +// Price { min: 1999, max: 2000 }, +// Price { min: 1999, max: 2000 }, +// keys::max_pnl_factor_for_deposits(), +// true, +// ); + +// assert(pool_value_info.pool_value.mag == 200000000000000, 'wrong pool value amount'); +// assert(pool_value_info.long_token_amount == 50000000000, 'wrong long token amount'); +// assert(pool_value_info.short_token_amount == 50000000000, 'wrong short token amount'); + +// let not_deposit = data_store.get_deposit(key); +// let default_deposit: Deposit = Default::default(); +// assert(not_deposit == default_deposit, 'Still existing deposit'); + +// // let market_token_dispatcher = IMarketTokenDispatcher { contract_address: market.market_token }; + +// // let balance = market_token_dispatcher.balance_of(user1); + +// let balance_deposit_vault = IERC20Dispatcher { contract_address: market.short_token } +// .balance_of(deposit_vault.contract_address); + +// let pool_value_info = market_utils::get_pool_value_info( +// data_store, +// market, +// Price { min: 5000, max: 5000, }, +// Price { min: 5000, max: 5000, }, +// Price { min: 1, max: 1, }, +// keys::max_pnl_factor_for_deposits(), +// true, +// ); + +// pool_value_info.pool_value.mag.print(); +// pool_value_info.long_token_amount.print(); +// pool_value_info.short_token_amount.print(); + +// // // --------------------SWAP TEST USDC->ETH -------------------- + +// let balance_ETH_before_swap = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } +// .balance_of(caller_address); +// assert(balance_ETH_before_swap == 1000000, 'wrong balance ETH before swap'); + +// let balance_USDC_before_swap = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } +// .balance_of(caller_address); +// assert(balance_USDC_before_swap == 1000000, 'wrong balance USDC before swap'); + +// start_prank(contract_address_const::<'ETH'>(), caller_address); //change to switch swap +// // Send token to order_vault in multicall with create_order +// IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } //change to switch swap +// .transfer(order_vault.contract_address, 1); + +// let balance_ETH_before = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } +// .balance_of(caller_address); +// let balance_USDC_before = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } +// .balance_of(caller_address); +// 'balance ETH: '.print(); +// balance_ETH_before.print(); +// 'balance USDC: '.print(); +// balance_USDC_before.print(); +// 'end first balances'.print(); +// // Create order_params Struct +// let contract_address = contract_address_const::<0>(); +// start_prank(market.long_token, caller_address); //change to switch swap +// let order_params = CreateOrderParams { +// receiver: caller_address, +// callback_contract: contract_address, +// ui_fee_receiver: contract_address, +// market: contract_address, +// initial_collateral_token: market.long_token, //change to switch swap +// swap_path: Array32Trait::::span32(@array![market.market_token]), +// size_delta_usd: 1, +// initial_collateral_delta_amount: 1, // 10^18 +// trigger_price: 0, +// acceptable_price: 0, +// execution_fee: 0, +// callback_gas_limit: 0, +// min_output_amount: 0, +// order_type: OrderType::MarketSwap(()), +// decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), +// is_long: false, +// referral_code: 0 +// }; +// // Create the swap order. +// start_roll(order_handler.contract_address, 1920); +// let key = order_handler.create_order(caller_address, order_params); + +// let got_order = data_store.get_order(key); +// // data_store.set_u256(keys::pool_amount_key(market.market_token, contract_address_const::<'USDC'>()), ); +// // data_store.set_u256(keys::pool_amount_key(market.market_token, contract_address_const::<'ETH'>()), 1000000); +// // Execute the swap order. +// let signatures: Span = array![0].span(); +// let set_price_params = SetPricesParams { +// signer_info: 2, +// tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], +// compacted_min_oracle_block_numbers: array![1910, 1910], +// compacted_max_oracle_block_numbers: array![1920, 1920], +// compacted_oracle_timestamps: array![9999, 9999], +// compacted_decimals: array![1, 1], +// compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted +// compacted_min_prices_indexes: array![0], +// compacted_max_prices: array![2147483648010000], // 500000, 10000 compacted +// compacted_max_prices_indexes: array![0], +// signatures: array![ +// array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() +// ], +// price_feed_tokens: array![] +// }; + +// let balance_ETH_before_execute = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } +// .balance_of(caller_address); +// let balance_USDC_before_execute = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } +// .balance_of(caller_address); + +// 'balance eth before execute'.print(); +// balance_ETH_before_execute.print(); +// // assert(balance_ETH_after == 999999, 'wrong balance ETH after swap'); +// 'balance usdc before execute'.print(); +// balance_USDC_before_execute.print(); + +// let keeper_address = contract_address_const::<'keeper'>(); +// role_store.grant_role(keeper_address, role::ORDER_KEEPER); + +// stop_prank(order_handler.contract_address); +// start_prank(order_handler.contract_address, keeper_address); +// start_roll(order_handler.contract_address, 1925); +// // TODO add real signatures check on Oracle Account +// order_handler.execute_order_keeper(key, set_price_params, keeper_address); + +// let balance_ETH_after = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } +// .balance_of(caller_address); +// let balance_USDC_after = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } +// .balance_of(caller_address); + +// 'balance eth after'.print(); +// balance_ETH_after.print(); +// // assert(balance_ETH_after == 999999, 'wrong balance ETH after swap'); +// 'balance usdc after'.print(); +// balance_USDC_after.print(); +// // assert(balance_USDC_after == 995000, 'wrong balance USDC after swap'); + +// let first_swap_pool_value_info = market_utils::get_pool_value_info( +// data_store, +// market, +// Price { +// min: 5000, +// max: 5000, +// } +// , +// Price { +// min: 5000, +// max: 5000, +// }, +// Price { +// min: 1, +// max: 1, +// }, +// keys::max_pnl_factor_for_deposits(), +// true, +// ); + +// first_swap_pool_value_info.pool_value.mag.print(); +// first_swap_pool_value_info.long_token_amount.print(); +// first_swap_pool_value_info.short_token_amount.print(); +// } + +#[test] +fn test_deposit_market_integration() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + ) = + setup(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // Create a market. + let market = data_store.get_market(create_market(market_factory)); + + // Set params in data_store + data_store.set_address(keys::fee_token(), market.index_token); + data_store.set_u256(keys::max_swap_path_length(), 5); + + // Set max pool amount. + data_store + .set_u256( + keys::max_pool_amount_key(market.market_token, market.long_token), 500000000000000000 + ); + data_store + .set_u256( + keys::max_pool_amount_key(market.market_token, market.short_token), 500000000000000000 + ); + + oracle.set_primary_prices(market.long_token, 5000); + oracle.set_primary_prices(market.short_token, 1); + + // Fill the pool. + IERC20Dispatcher { contract_address: market.long_token }.mint(market.market_token, 50000000000); + IERC20Dispatcher { contract_address: market.short_token } + .mint(market.market_token, 50000000000); + // TODO Check why we don't need to set pool_amount_key + // // Set pool amount in data_store. + // let mut key = keys::pool_amount_key(market.market_token, contract_address_const::<'ETH'>()); + // data_store.set_u256(key, 50000000000); + // key = keys::pool_amount_key(market.market_token, contract_address_const::<'USDC'>()); + // data_store.set_u256(key, 50000000000); + + // Send token to deposit in the deposit vault (this should be in a multi call with create_deposit) + IERC20Dispatcher { contract_address: market.long_token } + .mint(deposit_vault.contract_address, 50000000000); + IERC20Dispatcher { contract_address: market.short_token } + .mint(deposit_vault.contract_address, 50000000000); + + let balance_deposit_vault_before = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(deposit_vault.contract_address); + + // Create Deposit + let user1: ContractAddress = contract_address_const::<'user1'>(); + let user2: ContractAddress = contract_address_const::<'user2'>(); + + let addresss_zero: ContractAddress = 0.try_into().unwrap(); + + let params = CreateDepositParams { + receiver: user1, + callback_contract: user2, + ui_fee_receiver: addresss_zero, + market: market.market_token, + initial_long_token: market.long_token, + initial_short_token: market.short_token, + long_token_swap_path: Array32Trait::::span32(@array![]), + short_token_swap_path: Array32Trait::::span32(@array![]), + min_market_tokens: 0, + execution_fee: 0, + callback_gas_limit: 0, + }; + + start_roll(deposit_handler.contract_address, 1910); + let key = deposit_handler.create_deposit(caller_address, params); + let first_deposit = data_store.get_deposit(key); + + assert(first_deposit.account == caller_address, 'Wrong account depositer'); + assert(first_deposit.receiver == user1, 'Wrong account receiver'); + assert(first_deposit.initial_long_token == market.long_token, 'Wrong initial long token'); + assert( + first_deposit.initial_long_token_amount == 50000000000, 'Wrong initial long token amount' + ); + assert( + first_deposit.initial_short_token_amount == 50000000000, 'Wrong init short token amount' + ); + + let price_params = SetPricesParams { // TODO + signer_info: 1, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1900, 1900], + compacted_max_oracle_block_numbers: array![1910, 1910], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![18, 18], + compacted_min_prices: array![4294967346000000], // 50000000, 1000000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![4294967346000000], // 50000000, 1000000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + start_prank(role_store.contract_address, caller_address); + + role_store.grant_role(caller_address, role::ORDER_KEEPER); + role_store.grant_role(caller_address, role::ROLE_ADMIN); + role_store.grant_role(caller_address, role::CONTROLLER); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + // Execute Deposit + start_roll(deposit_handler.contract_address, 1915); + deposit_handler.execute_deposit(key, price_params); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 1999, max: 2000 }, + Price { min: 1999, max: 2000 }, + Price { min: 1999, max: 2000 }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert(pool_value_info.pool_value.mag == 200000000000000, 'wrong pool value amount'); + assert(pool_value_info.long_token_amount == 50000000000, 'wrong long token amount'); + assert(pool_value_info.short_token_amount == 50000000000, 'wrong short token amount'); + + let not_deposit = data_store.get_deposit(key); + let default_deposit: Deposit = Default::default(); + assert(not_deposit == default_deposit, 'Still existing deposit'); + + // let market_token_dispatcher = IMarketTokenDispatcher { contract_address: market.market_token }; + + // let balance = market_token_dispatcher.balance_of(user1); + + let balance_deposit_vault = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(deposit_vault.contract_address); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 5000, max: 5000, }, + Price { min: 5000, max: 5000, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + pool_value_info.pool_value.mag.print(); + pool_value_info.long_token_amount.print(); + pool_value_info.short_token_amount.print(); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} + +#[test] +fn test_deposit_withdraw_integration() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + ) = + setup(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // Create a market. + let market = data_store.get_market(create_market(market_factory)); + + // Set params in data_store + data_store.set_address(keys::fee_token(), market.index_token); + data_store.set_u256(keys::max_swap_path_length(), 5); + + // Set max pool amount. + data_store + .set_u256( + keys::max_pool_amount_key(market.market_token, market.long_token), 500000000000000000 + ); + data_store + .set_u256( + keys::max_pool_amount_key(market.market_token, market.short_token), 500000000000000000 + ); + + oracle.set_primary_prices(market.long_token, 5000); + oracle.set_primary_prices(market.short_token, 1); + + // Fill the pool. + IERC20Dispatcher { contract_address: market.long_token }.mint(market.market_token, 50000000000); + IERC20Dispatcher { contract_address: market.short_token } + .mint(market.market_token, 50000000000); + // TODO Check why we don't need to set pool_amount_key + // // Set pool amount in data_store. + // let mut key = keys::pool_amount_key(market.market_token, contract_address_const::<'ETH'>()); + // data_store.set_u256(key, 50000000000); + // key = keys::pool_amount_key(market.market_token, contract_address_const::<'USDC'>()); + // data_store.set_u256(key, 50000000000); + + // Send token to deposit in the deposit vault (this should be in a multi call with create_deposit) + IERC20Dispatcher { contract_address: market.long_token } + .mint(deposit_vault.contract_address, 50000000000); + IERC20Dispatcher { contract_address: market.short_token } + .mint(deposit_vault.contract_address, 50000000000); + + let balance_deposit_vault_before = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(deposit_vault.contract_address); + + // Create Deposit + let user1: ContractAddress = contract_address_const::<'user1'>(); + let user2: ContractAddress = contract_address_const::<'user2'>(); + + let addresss_zero: ContractAddress = 0.try_into().unwrap(); + + let params = CreateDepositParams { + receiver: caller_address, + callback_contract: addresss_zero, + ui_fee_receiver: addresss_zero, + market: market.market_token, + initial_long_token: market.long_token, + initial_short_token: market.short_token, + long_token_swap_path: Array32Trait::::span32(@array![]), + short_token_swap_path: Array32Trait::::span32(@array![]), + min_market_tokens: 0, + execution_fee: 0, + callback_gas_limit: 0, + }; + + start_roll(deposit_handler.contract_address, 1910); + let key = deposit_handler.create_deposit(caller_address, params); + let first_deposit = data_store.get_deposit(key); + + assert(first_deposit.account == caller_address, 'Wrong account depositer'); + assert(first_deposit.receiver == caller_address, 'Wrong account receiver'); + assert(first_deposit.initial_long_token == market.long_token, 'Wrong initial long token'); + assert( + first_deposit.initial_long_token_amount == 50000000000, 'Wrong initial long token amount' + ); + assert( + first_deposit.initial_short_token_amount == 50000000000, 'Wrong init short token amount' + ); + + let price_params = SetPricesParams { // TODO + signer_info: 1, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1900, 1900], + compacted_max_oracle_block_numbers: array![1910, 1910], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![18, 18], + compacted_min_prices: array![4294967346000000], // 50000000, 1000000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![4294967346000000], // 50000000, 1000000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + start_prank(role_store.contract_address, caller_address); + + role_store.grant_role(caller_address, role::ORDER_KEEPER); + role_store.grant_role(caller_address, role::ROLE_ADMIN); + role_store.grant_role(caller_address, role::CONTROLLER); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + // Execute Deposit + start_roll(deposit_handler.contract_address, 1915); + deposit_handler.execute_deposit(key, price_params); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 1999, max: 2000 }, + Price { min: 1999, max: 2000 }, + Price { min: 1999, max: 2000 }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert(pool_value_info.pool_value.mag == 200000000000000, 'wrong pool value amount'); + assert(pool_value_info.long_token_amount == 50000000000, 'wrong long token amount'); + assert(pool_value_info.short_token_amount == 50000000000, 'wrong short token amount'); + + let not_deposit = data_store.get_deposit(key); + let default_deposit: Deposit = Default::default(); + assert(not_deposit == default_deposit, 'Still existing deposit'); + + // let market_token_dispatcher = IMarketTokenDispatcher { contract_address: market.market_token }; + + // let balance = market_token_dispatcher.balance_of(user1); + + let balance_deposit_vault = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(deposit_vault.contract_address); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 5000, max: 5000, }, + Price { min: 5000, max: 5000, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + pool_value_info.pool_value.mag.print(); + pool_value_info.long_token_amount.print(); + pool_value_info.short_token_amount.print(); + + /////////////////////////////////// WITHDRAW ////////////////////////////////// + + 'balanceof mkt before withdrawal'.print(); + let balance_market_token = IERC20Dispatcher { contract_address: market.market_token } + .balance_of(caller_address); + balance_market_token.print(); + + start_prank(market.market_token, caller_address); + IERC20Dispatcher { contract_address: market.market_token } + .transfer(withdrawal_vault.contract_address, 125); + + let withdrawal_params = withdrawal_utils::CreateWithdrawalParams { + /// The address that will receive the withdrawal tokens. + receiver: caller_address, + /// The contract that will be called back. + callback_contract: addresss_zero, + /// The ui fee receiver. + ui_fee_receiver: addresss_zero, + /// The market on which the withdrawal will be executed. + market: market.market_token, + /// The swap path for the long token + long_token_swap_path: Array32Trait::::span32(@array![]), + /// The short token swap path + short_token_swap_path: Array32Trait::::span32(@array![]), + /// The minimum amount of long tokens that must be withdrawn. + min_long_token_amount: 25000000000, + /// The minimum amount of short tokens that must be withdrawn. + min_short_token_amount: 25000000000, + /// The execution fee for the withdrawal. + execution_fee: 0, + /// The gas limit for calling the callback contract. + callback_gas_limit: 0, + }; + + start_roll(withdrawal_handler.contract_address, 1930); + let key = withdrawal_handler.create_withdrawal(caller_address, withdrawal_params); + let first_withdrawal = data_store.get_withdrawal(key); + + assert(first_withdrawal.receiver == caller_address, 'Wrong account receiver'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 1999, max: 2000 }, + Price { min: 1999, max: 2000 }, + Price { min: 1, max: 1 }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert(pool_value_info.pool_value.mag == 100050000000000, 'wrong pool value amount'); + assert(pool_value_info.long_token_amount == 50000000000, 'wrong long token amount'); + assert(pool_value_info.short_token_amount == 50000000000, 'wrong short token amount'); + + let price_params_withdrawal = SetPricesParams { // TODO + signer_info: 1, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1920, 1920], + compacted_max_oracle_block_numbers: array![1930, 1930], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![18, 18], + compacted_min_prices: array![4294967346000000], // 50000000, 1000000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![4294967346000000], // 50000000, 1000000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + start_prank(role_store.contract_address, caller_address); + + role_store.grant_role(caller_address, role::ORDER_KEEPER); + role_store.grant_role(caller_address, role::ROLE_ADMIN); + role_store.grant_role(caller_address, role::CONTROLLER); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + // Execute Deposit + start_roll(withdrawal_handler.contract_address, 1935); + withdrawal_handler.execute_withdrawal(key, price_params_withdrawal); + + let not_withdrawal = data_store.get_withdrawal(key); + let default_withdrawal: Withdrawal = Default::default(); + assert(not_withdrawal == default_withdrawal, 'Still existing deposit'); + + 'balanceof mkt after withdrawal'.print(); + let balance_market_token_after = IERC20Dispatcher { contract_address: market.market_token } + .balance_of(caller_address); + balance_market_token_after.print(); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 1999, max: 2000, }, + Price { min: 1999, max: 2000, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert(pool_value_info.pool_value.mag == 50025000000000, 'wrong pool value amount'); + assert(pool_value_info.long_token_amount == 25000000000, 'wrong long token amount'); + assert(pool_value_info.short_token_amount == 25000000000, 'wrong short token amount'); + + pool_value_info.pool_value.mag.print(); + pool_value_info.long_token_amount.print(); + pool_value_info.short_token_amount.print(); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} + +fn create_market(market_factory: IMarketFactoryDispatcher) -> ContractAddress { + // Create a market. + let (index_token, short_token) = deploy_tokens(); + let market_type = 'market_type'; + + // Index token is the same as long token here. + market_factory.create_market(index_token, index_token, short_token, market_type) +} + +/// Utility functions to deploy tokens for a market. +fn deploy_tokens() -> (ContractAddress, ContractAddress) { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let contract = declare('ERC20'); + + let eth_address = contract_address_const::<'ETH'>(); + let constructor_calldata = array!['Ethereum', 'ETH', 1000000, 0, caller_address.into()]; + contract.deploy_at(@constructor_calldata, eth_address).unwrap(); + + let usdc_address = contract_address_const::<'USDC'>(); + let constructor_calldata = array!['usdc', 'USDC', 1000000, 0, caller_address.into()]; + contract.deploy_at(@constructor_calldata, usdc_address).unwrap(); + (eth_address, usdc_address) +} + +/// 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, + // Address of the `MarketFactory` contract. + ContractAddress, + // Address of the `RoleStore` contract. + ContractAddress, + // Address of the `DataStore` contract. + ContractAddress, + // The `MarketToken` class hash for the factory. + ContractClass, + // Interface to interact with the `MarketFactory` contract. + IMarketFactoryDispatcher, + // Interface to interact with the `RoleStore` contract. + IRoleStoreDispatcher, + // Interface to interact with the `DataStore` contract. + IDataStoreDispatcher, + // Interface to interact with the `EventEmitter` contract. + IEventEmitterDispatcher, + // Interface to interact with the `ExchangeRouter` contract. + IExchangeRouterDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositHandlerDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositVaultDispatcher, + IOracleDispatcher, + IOrderHandlerDispatcher, + IOrderVaultDispatcher, + IReaderDispatcher, + IReferralStorageDispatcher, + IWithdrawalHandlerDispatcher, + IWithdrawalVaultDispatcher, +) { + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + ) = + setup_contracts(); + grant_roles_and_prank(caller_address, role_store, data_store, market_factory); + ( + caller_address, + market_factory.contract_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_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. +/// * `role_store` - The interface to interact with the `RoleStore` contract. +/// * `data_store` - The interface to interact with the `DataStore` contract. +/// * `market_factory` - The interface to interact with the `MarketFactory` contract. +fn grant_roles_and_prank( + caller_address: ContractAddress, + role_store: IRoleStoreDispatcher, + data_store: IDataStoreDispatcher, + market_factory: IMarketFactoryDispatcher, +) { + start_prank(role_store.contract_address, caller_address); + + // Grant the caller the `CONTROLLER` role. + role_store.grant_role(caller_address, role::CONTROLLER); + + // Grant the call the `MARKET_KEEPER` role. + // This role is required to create a market. + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + // 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); + + // Start pranking the `MarketFactory` contract. This is necessary to mock the behavior of the contract + // for testing purposes. + start_prank(market_factory.contract_address, caller_address); +} + +/// Utility function to teardown the test environment. +fn teardown(data_store: IDataStoreDispatcher, market_factory: IMarketFactoryDispatcher) { + stop_prank(data_store.contract_address); + stop_prank(market_factory.contract_address); +} + +/// Setup required contracts. +fn setup_contracts() -> ( + // This caller address will be used with `start_prank` cheatcode to mock the caller address., + ContractAddress, + // Address of the `MarketFactory` contract. + ContractAddress, + // Address of the `RoleStore` contract. + ContractAddress, + // Address of the `DataStore` contract. + ContractAddress, + // The `MarketToken` class hash for the factory. + ContractClass, + // Interface to interact with the `MarketFactory` contract. + IMarketFactoryDispatcher, + // Interface to interact with the `RoleStore` contract. + IRoleStoreDispatcher, + // Interface to interact with the `DataStore` contract. + IDataStoreDispatcher, + // Interface to interact with the `EventEmitter` contract. + IEventEmitterDispatcher, + // Interface to interact with the `ExchangeRouter` contract. + IExchangeRouterDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositHandlerDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositVaultDispatcher, + IOracleDispatcher, + IOrderHandlerDispatcher, + IOrderVaultDispatcher, + IReaderDispatcher, + IReferralStorageDispatcher, + IWithdrawalHandlerDispatcher, + IWithdrawalVaultDispatcher, +) { + // 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 }; + + // Declare the `MarketToken` contract. + let market_token_class_hash = declare_market_token(); + + // Deploy the event emitter contract. + let event_emitter_address = deploy_event_emitter(); + // Create a safe dispatcher to interact with the contract. + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + + // Deploy the router contract. + let router_address = deploy_router(role_store_address); + + // Deploy the market factory. + let market_factory_address = deploy_market_factory( + data_store_address, role_store_address, event_emitter_address, market_token_class_hash + ); + // Create a safe dispatcher to interact with the contract. + let market_factory = IMarketFactoryDispatcher { contract_address: market_factory_address }; + + let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); + let oracle_address = deploy_oracle( + role_store_address, oracle_store_address, contract_address_const::<'pragma'>() + ); + + let oracle = IOracleDispatcher { contract_address: oracle_address }; + + let deposit_vault_address = deploy_deposit_vault(role_store_address, data_store_address); + + let deposit_vault = IDepositVaultDispatcher { contract_address: deposit_vault_address }; + let deposit_handler_address = deploy_deposit_handler( + data_store_address, + role_store_address, + event_emitter_address, + deposit_vault_address, + oracle_address + ); + let deposit_handler = IDepositHandlerDispatcher { contract_address: deposit_handler_address }; + + let withdrawal_vault_address = deploy_withdrawal_vault(data_store_address, role_store_address); + let withdrawal_handler_address = deploy_withdrawal_handler( + data_store_address, + role_store_address, + event_emitter_address, + withdrawal_vault_address, + oracle_address + ); + + let order_vault_address = deploy_order_vault( + data_store.contract_address, role_store.contract_address + ); + let order_vault = IOrderVaultDispatcher { contract_address: order_vault_address }; + + let swap_handler_address = deploy_swap_handler_address(role_store_address, data_store_address); + let referral_storage_address = deploy_referral_storage(event_emitter_address); + let increase_order_class_hash = declare_increase_order(); + let decrease_order_class_hash = declare_decrease_order(); + let swap_order_class_hash = declare_swap_order(); + + let order_utils_class_hash = declare_order_utils(); + + let order_handler_address = deploy_order_handler( + data_store_address, + role_store_address, + event_emitter_address, + order_vault_address, + oracle_address, + swap_handler_address, + referral_storage_address, + order_utils_class_hash, + increase_order_class_hash, + decrease_order_class_hash, + swap_order_class_hash + ); + let order_handler = IOrderHandlerDispatcher { contract_address: order_handler_address }; + + let exchange_router_address = deploy_exchange_router( + router_address, + data_store_address, + role_store_address, + event_emitter_address, + deposit_handler_address, + withdrawal_handler_address, + order_handler_address + ); + let exchange_router = IExchangeRouterDispatcher { contract_address: exchange_router_address }; + + 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 }; + + let reader_address = deploy_reader(); + let reader = IReaderDispatcher { contract_address: reader_address }; + + let referal_storage = IReferralStorageDispatcher { contract_address: referral_storage_address }; + + let withdrawal_handler = IWithdrawalHandlerDispatcher { + contract_address: withdrawal_handler_address + }; + let withdrawal_vault = IWithdrawalVaultDispatcher { + contract_address: withdrawal_vault_address + }; + ( + contract_address_const::<'caller'>(), + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + ) +} + +/// Utility function to declare a `MarketToken` contract. +fn declare_market_token() -> ContractClass { + declare('MarketToken') +} + +/// Utility function to deploy a market factory contract and return its address. +fn deploy_market_factory( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + market_token_class_hash: ContractClass, +) -> ContractAddress { + let contract = declare('MarketFactory'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'market_factory'>(); + start_prank(deployed_contract_address, caller_address); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + constructor_calldata.append(event_emitter_address.into()); + constructor_calldata.append(market_token_class_hash.class_hash.into()); + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address: ContractAddress = 0x1.try_into().unwrap(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_router(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('Router'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'router'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_deposit_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + deposit_vault_address: ContractAddress, + oracle_address: ContractAddress +) -> ContractAddress { + let contract = declare('DepositHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'deposit_handler'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + deposit_vault_address.into(), + oracle_address.into() + ], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_oracle_store( + role_store_address: ContractAddress, event_emitter_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OracleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle_store'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![role_store_address.into(), event_emitter_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_oracle( + role_store_address: ContractAddress, + oracle_store_address: ContractAddress, + pragma_address: ContractAddress +) -> ContractAddress { + let contract = declare('Oracle'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![role_store_address.into(), oracle_store_address.into(), pragma_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_deposit_vault( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('DepositVault'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'deposit_vault'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![data_store_address.into(), role_store_address.into()], deployed_contract_address + ) + .unwrap() +} + +fn deploy_withdrawal_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + withdrawal_vault_address: ContractAddress, + oracle_address: ContractAddress +) -> ContractAddress { + let contract = declare('WithdrawalHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'withdrawal_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + withdrawal_vault_address.into(), + oracle_address.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_withdrawal_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('WithdrawalVault'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'withdrawal_vault'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![data_store_address.into(), role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn declare_increase_order() -> ClassHash { + declare('IncreaseOrderUtils').class_hash +} +fn declare_decrease_order() -> ClassHash { + declare('DecreaseOrderUtils').class_hash +} +fn declare_swap_order() -> ClassHash { + declare('SwapOrderUtils').class_hash +} + + +fn declare_order_utils() -> ClassHash { + declare('OrderUtils').class_hash +} + +fn deploy_order_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + order_vault_address: ContractAddress, + oracle_address: ContractAddress, + swap_handler_address: ContractAddress, + referral_storage_address: ContractAddress, + order_utils_class_hash: ClassHash, + increase_order_class_hash: ClassHash, + decrease_order_class_hash: ClassHash, + swap_order_class_hash: ClassHash +) -> ContractAddress { + let contract = declare('OrderHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'order_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + order_vault_address.into(), + oracle_address.into(), + swap_handler_address.into(), + referral_storage_address.into(), + order_utils_class_hash.into(), + increase_order_class_hash.into(), + decrease_order_class_hash.into(), + swap_order_class_hash.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_swap_handler_address( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('SwapHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'swap_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'referral_storage'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_exchange_router( + router_address: ContractAddress, + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + deposit_handler_address: ContractAddress, + withdrawal_handler_address: ContractAddress, + order_handler_address: ContractAddress +) -> ContractAddress { + let contract = declare('ExchangeRouter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'exchange_router'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + router_address.into(), + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + deposit_handler_address.into(), + withdrawal_handler_address.into(), + order_handler_address.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_order_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OrderVault'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + tests_lib::deploy_mock_contract(contract, @constructor_calldata) +} + +fn deploy_bank( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let bank_address: ContractAddress = contract_address_const::<'bank'>(); + let contract = declare('Bank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + start_prank(data_store_address, caller_address); + contract.deploy_at(@constructor_calldata, bank_address).unwrap() +} + +fn deploy_strict_bank( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let strict_bank_address: ContractAddress = contract_address_const::<'strict_bank'>(); + let contract = declare('StrictBank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + start_prank(strict_bank_address, caller_address); + contract.deploy_at(@constructor_calldata, strict_bank_address).unwrap() +} + +fn deploy_reader() -> ContractAddress { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let reader_address: ContractAddress = contract_address_const::<'reader'>(); + let contract = declare('Reader'); + let mut constructor_calldata = array![]; + start_prank(reader_address, caller_address); + contract.deploy_at(@constructor_calldata, reader_address).unwrap() +} + +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() +} diff --git a/tests/integration/test_long_integration.cairo b/tests/integration/test_long_integration.cairo new file mode 100644 index 00000000..831621c4 --- /dev/null +++ b/tests/integration/test_long_integration.cairo @@ -0,0 +1,3009 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +use result::ResultTrait; +use debug::PrintTrait; +use traits::{TryInto, Into}; +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, + ClassHash, +}; +use snforge_std::{declare, start_prank, stop_prank, start_roll, ContractClassTrait, ContractClass}; + + +// Local imports. +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::order::order_utils::{IOrderUtilsDispatcher, IOrderUtilsDispatcherTrait}; +use satoru::market::market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; +use satoru::deposit::deposit::Deposit; +use satoru::withdrawal::withdrawal::Withdrawal; + +use satoru::exchange::withdrawal_handler::{ + IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait +}; +use satoru::exchange::deposit_handler::{IDepositHandlerDispatcher, IDepositHandlerDispatcherTrait}; +use satoru::router::exchange_router::{IExchangeRouterDispatcher, IExchangeRouterDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::reader::reader::{IReaderDispatcher, IReaderDispatcherTrait}; +use satoru::market::market::{Market, UniqueIdMarket}; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::role::role; +use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::test_utils::tests_lib; +use satoru::deposit::deposit_utils::CreateDepositParams; +use satoru::utils::span32::{Span32, DefaultSpan32, Array32Trait}; +use satoru::deposit::deposit_utils; +use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; +use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::withdrawal::withdrawal_vault::{ + IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait +}; +use satoru::data::keys; +use satoru::market::market_utils; +use satoru::price::price::{Price, PriceTrait}; +use satoru::position::position_utils; +use satoru::withdrawal::withdrawal_utils; + +use satoru::exchange::liquidation_handler::{ + ILiquidationHandlerDispatcher, ILiquidationHandlerDispatcherTrait +}; +use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; +use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; +use satoru::order::base_order_utils::{CreateOrderParams}; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::market::{market::{UniqueIdMarketImpl},}; +use satoru::exchange::order_handler::{ + OrderHandler, IOrderHandlerDispatcher, IOrderHandlerDispatcherTrait +}; +use satoru::test_utils::{ + tests_lib::{setup, create_market, teardown}, deposit_setup::{deposit_setup, exec_order} +}; + +#[test] +fn test_long_increase_decrease_close() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market, + ) = + deposit_setup( + 50000000000000000000000000000, 50000000000000000000000000000 + ); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + // let pool_value_info = market_utils::get_pool_value_info( + // data_store, + // market, + // Price { min: 5000, max: 5000, }, + // Price { min: 5000, max: 5000, }, + // Price { min: 1, max: 1, }, + // keys::max_pnl_factor_for_deposits(), + // true, + // ); + + // pool_value_info.pool_value.mag.print(); // 10000 000000000000000000 + // pool_value_info.long_token_amount.print(); // 5 000000000000000000 + // pool_value_info.short_token_amount.print(); // 25000 000000000000000000 + + // ************************************* TEST LONG ********************************************* + + 'Begining of LONG TEST'.print(); + + let key_open_interest = keys::open_interest_key( + market.market_token, contract_address_const::<'ETH'>(), true + ); + data_store.set_u256(key_open_interest, 1); + let max_key_open_interest = keys::max_open_interest_key(market.market_token, true); + data_store + .set_u256( + max_key_open_interest, 1000000000000000000000000000000000000000000000000000 + ); // 1 000 000 + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 1000000000000000000); // 1ETH + + 'transfer made'.print(); + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 3500000000000000000000, + initial_collateral_delta_amount: 1000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 3501, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the swap order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1930); + 'try to create prder'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long = exchange_router.create_order(order_params_long); + 'long created'.print(); + let got_order_long = data_store.get_order(key_long); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1935); + exec_order(order_handler.contract_address, role_store.contract_address, key_long, 3500, 1); + + 'long position SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position = data_store.get_position(position_key_1); + + assert(first_position.size_in_tokens == 1000000000000000000, 'Size token should be 1 ETH'); + assert(first_position.size_in_usd == 3500000000000000000000, 'Size should be 3500$'); + assert(first_position.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position.collateral_amount == 1000000000000000000, 'Collat should be 1 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + //////////////////////////////// INCREASE POSITION ////////////////////////////////// + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 2000000000000000000); // 2ETH + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_inc = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 7700000000000000000000, // 6000 + initial_collateral_delta_amount: 2000000000000000000, // 1 ETH 10^18 + trigger_price: 0, + acceptable_price: 3851, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1940); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_inc = exchange_router.create_order(order_params_long_inc); + 'Long increase created'.print(); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1945); + exec_order(order_handler.contract_address, role_store.contract_address, key_long_inc, 3850, 1); + 'long pos inc SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position_inc = data_store.get_position(position_key_1); + + assert(first_position_inc.size_in_tokens == 3000000000000000000, 'Size token should be 3 ETH'); + assert(first_position_inc.size_in_usd == 11200000000000000000000, 'Size should be 11200$'); + assert(first_position_inc.borrowing_factor == 0, 'borrow should be 0'); + assert(first_position_inc.collateral_amount == 3000000000000000000, 'Collat should be 3 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + 'pnl'.print(); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + //////////////////////////////////// DECREASE POSITION ////////////////////////////////////// + 'DECREASE POSITION'.print(); + + let balance_USDC_before = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_before = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + // Decrease 25% of the position + // Size = 11200$ -----> 25% = 2800 + // Collateral token amount = 3 ETH -----> 25% = 0.75 ETH + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 2800000000000000000000, // 2800 + initial_collateral_delta_amount: 750000000000000000, // 0.75 ETH 10^18 + trigger_price: 0, + acceptable_price: 3849, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1950); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec = exchange_router.create_order(order_params_long_dec); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1955); + exec_order(order_handler.contract_address, role_store.contract_address, key_long_dec, 3850, 1); + 'long pos dec SUCCEEDED'.print(); + + // Recieved 2974.999 USDC + + let first_position_dec = data_store.get_position(position_key_1); + + assert( + first_position_dec.size_in_tokens == 2250000000000000000, 'Size token should be 2.25 ETH' + ); + assert(first_position_dec.size_in_usd == 8400000000000000000000, 'Size should be 8400'); + assert(first_position_dec.borrowing_factor == 0, 'Borrow should be 0'); + assert( + first_position_dec.collateral_amount == 2250000000000000000, 'Collat should be 2.25 ETH' + ); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 262500000000000000000, 'PnL should be 262,5'); + + let balance_USDC_after = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_after = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .balance_of(caller_address); + + assert(balance_USDC_before == 50000000000000000000000, 'balance USDC should be 50000$'); + // Balance USDC after = (0.75 ETH * 3850$) + 87.499 (PnL) + assert(balance_USDC_after == 52974999999999999998950, 'balance USDC shld be 52974.99$'); + assert(balance_ETH_before == 7000000000000000000, 'balance ETH before 7'); + assert(balance_ETH_after == 7000000000000000000, 'balance ETH after 7'); + + //////////////////////////////////// CLOSE POSITION ////////////////////////////////////// + 'CLOSE POSITION'.print(); + + let balance_USDC_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 4000, max: 4000, }, + long_token_price: Price { min: 4000, max: 4000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 600000000000000000000, 'PnL should be 600$'); + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec_2 = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 8400000000000000000000, // 8400 + initial_collateral_delta_amount: 2250000000000000000, // 2.25 ETH 10^18 + trigger_price: 0, + acceptable_price: 3999, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1960); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec_2 = exchange_router.create_order(order_params_long_dec_2); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec_2); + + // Execute the long order. + start_roll(order_handler.contract_address, 1965); + exec_order( + order_handler.contract_address, role_store.contract_address, key_long_dec_2, 4000, 1 + ); + 'Long pos close SUCCEEDED'.print(); + + let first_position_close = data_store.get_position(position_key_1); + + assert(first_position_close.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_close.size_in_usd == 0, 'Size should be 0'); + assert(first_position_close.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_close.collateral_amount == 0, 'Collat should be 0'); + + let balance_USDC_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + assert(balance_USDC_bef_close == 52974999999999999998950, 'balance USDC shld be 52974.99$'); + assert(balance_USDC_af_close == 62574999999999999998950, 'balance USDC shld be 62574.99$'); + assert(balance_ETH_af_close == 7000000000000000000, 'balance ETH after 7'); + assert(balance_ETH_bef_close == 7000000000000000000, 'balance ETH after 7'); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} + +#[test] +fn test_takeprofit_long() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market, + ) = + deposit_setup( + 50000000000000000000000000000, 50000000000000000000000000000 + ); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + // let pool_value_info = market_utils::get_pool_value_info( + // data_store, + // market, + // Price { min: 5000, max: 5000, }, + // Price { min: 5000, max: 5000, }, + // Price { min: 1, max: 1, }, + // keys::max_pnl_factor_for_deposits(), + // true, + // ); + + // pool_value_info.pool_value.mag.print(); // 10000 000000000000000000 + // pool_value_info.long_token_amount.print(); // 5 000000000000000000 + // pool_value_info.short_token_amount.print(); // 25000 000000000000000000 + + // ************************************* TEST LONG ********************************************* + + 'Begining of LONG TEST'.print(); + + let key_open_interest = keys::open_interest_key( + market.market_token, contract_address_const::<'ETH'>(), true + ); + data_store.set_u256(key_open_interest, 1); + let max_key_open_interest = keys::max_open_interest_key(market.market_token, true); + data_store + .set_u256( + max_key_open_interest, 1000000000000000000000000000000000000000000000000000 + ); // 1 000 000 + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 1000000000000000000); // 1ETH + + 'transfer made'.print(); + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 3500000000000000000000, + initial_collateral_delta_amount: 1000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 3501, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the swap order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1930); + 'try to create prder'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long = exchange_router.create_order(order_params_long); + 'long created'.print(); + let got_order_long = data_store.get_order(key_long); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1935); + exec_order(order_handler.contract_address, role_store.contract_address, key_long, 3500, 1); + 'long position SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position = data_store.get_position(position_key_1); + + assert(first_position.size_in_tokens == 1000000000000000000, 'Size token should be 1 ETH'); + assert(first_position.size_in_usd == 3500000000000000000000, 'Size should be 3500$'); + assert(first_position.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position.collateral_amount == 1000000000000000000, 'Collat should be 1 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + //////////////////////////////// TRIGGER INCREASE POSITION ////////////////////////////////// + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 2000000000000000000); // 2ETH + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_inc = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 7700000000000000000000, // 7700 + initial_collateral_delta_amount: 2000000000000000000, // 2 ETH 10^18 + trigger_price: 3850, + acceptable_price: 3851, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1940); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_inc = exchange_router.create_order(order_params_long_inc); + 'Long increase created'.print(); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1945); + exec_order(order_handler.contract_address, role_store.contract_address, key_long_inc, 3850, 1); + 'long pos inc SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position_inc = data_store.get_position(position_key_1); + + first_position_inc.size_in_tokens.print(); + + assert(first_position_inc.size_in_tokens == 3000000000000000000, 'Size token should be 3 ETH'); + assert(first_position_inc.size_in_usd == 11200000000000000000000, 'Size should be 11200$'); + assert(first_position_inc.borrowing_factor == 0, 'borrow should be 0'); + assert(first_position_inc.collateral_amount == 3000000000000000000, 'Collat should be 3 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + 'pnl'.print(); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + //////////////////////////////////// TRIGGER DECREASE POSITION ////////////////////////////////////// + 'DECREASE POSITION'.print(); + + let balance_USDC_before = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_before = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + // Decrease 25% of the position + // Size = 11200$ -----> 25% = 2800 + // Collateral token amount = 3 ETH -----> 25% = 0.75 ETH + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 2800000000000000000000, // 2800 + initial_collateral_delta_amount: 750000000000000000, // 0.75 ETH 10^18 + trigger_price: 3950, + acceptable_price: 3949, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1950); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec = exchange_router.create_order(order_params_long_dec); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1955); + exec_order(order_handler.contract_address, role_store.contract_address, key_long_dec, 3950, 1); + 'long pos dec SUCCEEDED'.print(); + + // Recieved 2974.999 USDC + + let first_position_dec = data_store.get_position(position_key_1); + + assert( + first_position_dec.size_in_tokens == 2250000000000000000, 'Size token should be 2.25 ETH' + ); + assert(first_position_dec.size_in_usd == 8400000000000000000000, 'Size should be 8400'); + assert(first_position_dec.borrowing_factor == 0, 'Borrow should be 0'); + assert( + first_position_dec.collateral_amount == 2250000000000000000, 'Collat should be 2.25 ETH' + ); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3950, max: 3950, }, + long_token_price: Price { min: 3950, max: 3950, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 487500000000000000000, 'PnL should be 487,5'); + + let balance_USDC_after = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_after = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .balance_of(caller_address); + + assert(balance_USDC_before == 50000000000000000000000, 'balance USDC should be 50000$'); + // Balance USDC after = (0.75 ETH * 3950$) + 162.499 (PnL) + assert(balance_USDC_after == 53124999999999999996350, 'balance USDC shld be 53124.99$'); + assert(balance_ETH_before == 7000000000000000000, 'balance ETH before 7'); + assert(balance_ETH_after == 7000000000000000000, 'balance ETH after 7'); + + //////////////////////////////////// TRIGGER CLOSE POSITION ////////////////////////////////////// + 'CLOSE POSITION'.print(); + + let balance_USDC_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 4000, max: 4000, }, + long_token_price: Price { min: 4000, max: 4000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 600000000000000000000, 'PnL should be 600$'); + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec_2 = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 8400000000000000000000, // 8400 + initial_collateral_delta_amount: 2250000000000000000, // 2.25 ETH 10^18 + trigger_price: 4000, + acceptable_price: 3999, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1960); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec_2 = exchange_router.create_order(order_params_long_dec_2); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec_2); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1965); + exec_order( + order_handler.contract_address, role_store.contract_address, key_long_dec_2, 4000, 1 + ); + 'Long pos close SUCCEEDED'.print(); + + let first_position_close = data_store.get_position(position_key_1); + + assert(first_position_close.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_close.size_in_usd == 0, 'Size should be 0'); + assert(first_position_close.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_close.collateral_amount == 0, 'Collat should be 0'); + + let balance_USDC_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + assert(balance_USDC_bef_close == 53124999999999999996350, 'balance USDC shld be 52974.99$'); + assert(balance_USDC_af_close == 62724999999999999996350, 'balance USDC shld be 62724.99$'); + assert(balance_ETH_af_close == 7000000000000000000, 'balance ETH af close 7'); + assert(balance_ETH_bef_close == 7000000000000000000, 'balance ETH bef close 7'); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} + +#[test] +#[should_panic(expected: ('invalid_order_price', 'LimitIncrease',))] +fn test_takeprofit_long_increase_fails() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market, + ) = + deposit_setup( + 50000000000000000000000000000, 50000000000000000000000000000 + ); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + // let pool_value_info = market_utils::get_pool_value_info( + // data_store, + // market, + // Price { min: 5000, max: 5000, }, + // Price { min: 5000, max: 5000, }, + // Price { min: 1, max: 1, }, + // keys::max_pnl_factor_for_deposits(), + // true, + // ); + + // pool_value_info.pool_value.mag.print(); // 10000 000000000000000000 + // pool_value_info.long_token_amount.print(); // 5 000000000000000000 + // pool_value_info.short_token_amount.print(); // 25000 000000000000000000 + + // ************************************* TEST LONG ********************************************* + + 'Begining of LONG TEST'.print(); + + let key_open_interest = keys::open_interest_key( + market.market_token, contract_address_const::<'ETH'>(), true + ); + data_store.set_u256(key_open_interest, 1); + let max_key_open_interest = keys::max_open_interest_key(market.market_token, true); + data_store + .set_u256( + max_key_open_interest, 1000000000000000000000000000000000000000000000000000 + ); // 1 000 000 + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 1000000000000000000); // 1ETH + + 'transfer made'.print(); + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 3500000000000000000000, + initial_collateral_delta_amount: 1000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 3501, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the swap order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1930); + 'try to create prder'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long = exchange_router.create_order(order_params_long); + 'long created'.print(); + let got_order_long = data_store.get_order(key_long); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1935); + exec_order(order_handler.contract_address, role_store.contract_address, key_long, 3500, 1); + 'long position SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position = data_store.get_position(position_key_1); + + assert(first_position.size_in_tokens == 1000000000000000000, 'Size token should be 1 ETH'); + assert(first_position.size_in_usd == 3500000000000000000000, 'Size should be 3500$'); + assert(first_position.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position.collateral_amount == 1000000000000000000, 'Collat should be 1 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + //////////////////////////////// TRIGGER INCREASE POSITION ////////////////////////////////// + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 2000000000000000000); // 2ETH + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_inc = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 7700000000000000000000, // 7700 + initial_collateral_delta_amount: 2000000000000000000, // 2 ETH 10^18 + trigger_price: 3850, + acceptable_price: 3851, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1940); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_inc = exchange_router.create_order(order_params_long_inc); + 'Long increase created'.print(); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1945); + exec_order(order_handler.contract_address, role_store.contract_address, key_long_inc, 3860, 1); + 'long pos inc SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position_inc = data_store.get_position(position_key_1); + + first_position_inc.size_in_tokens.print(); + + assert(first_position_inc.size_in_tokens == 3000000000000000000, 'Size token should be 3 ETH'); + assert(first_position_inc.size_in_usd == 11200000000000000000000, 'Size should be 11200$'); + assert(first_position_inc.borrowing_factor == 0, 'borrow should be 0'); + assert(first_position_inc.collateral_amount == 3000000000000000000, 'Collat should be 3 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + 'pnl'.print(); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + //////////////////////////////////// TRIGGER DECREASE POSITION ////////////////////////////////////// + 'DECREASE POSITION'.print(); + + let balance_USDC_before = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_before = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + // Decrease 25% of the position + // Size = 11200$ -----> 25% = 2800 + // Collateral token amount = 3 ETH -----> 25% = 0.75 ETH + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 2800000000000000000000, // 2800 + initial_collateral_delta_amount: 750000000000000000, // 0.75 ETH 10^18 + trigger_price: 3950, + acceptable_price: 3949, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1950); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec = exchange_router.create_order(order_params_long_dec); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1955); + exec_order(order_handler.contract_address, role_store.contract_address, key_long_dec, 3950, 1); + 'long pos dec SUCCEEDED'.print(); + + // Recieved 2974.999 USDC + + let first_position_dec = data_store.get_position(position_key_1); + + assert( + first_position_dec.size_in_tokens == 2250000000000000000, 'Size token should be 2.25 ETH' + ); + assert(first_position_dec.size_in_usd == 8400000000000000000000, 'Size should be 8400'); + assert(first_position_dec.borrowing_factor == 0, 'Borrow should be 0'); + assert( + first_position_dec.collateral_amount == 2250000000000000000, 'Collat should be 2.25 ETH' + ); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3950, max: 3950, }, + long_token_price: Price { min: 3950, max: 3950, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 487500000000000000000, 'PnL should be 487,5'); + + let balance_USDC_after = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_after = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .balance_of(caller_address); + + assert(balance_USDC_before == 50000000000000000000000, 'balance USDC should be 50000$'); + // Balance USDC after = (0.75 ETH * 3950$) + 162.499 (PnL) + assert(balance_USDC_after == 53124999999999999996350, 'balance USDC shld be 53124.99$'); + assert(balance_ETH_before == 7000000000000000000, 'balance ETH before 7'); + assert(balance_ETH_after == 7000000000000000000, 'balance ETH after 7'); + + //////////////////////////////////// TRIGGER CLOSE POSITION ////////////////////////////////////// + 'CLOSE POSITION'.print(); + + let balance_USDC_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 4000, max: 4000, }, + long_token_price: Price { min: 4000, max: 4000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 600000000000000000000, 'PnL should be 600$'); + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec_2 = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 8400000000000000000000, // 8400 + initial_collateral_delta_amount: 2250000000000000000, // 2.25 ETH 10^18 + trigger_price: 4000, + acceptable_price: 3999, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1960); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec_2 = exchange_router.create_order(order_params_long_dec_2); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec_2); + // Execute the swap order. + start_roll(order_handler.contract_address, 1965); + exec_order( + order_handler.contract_address, role_store.contract_address, key_long_dec_2, 4000, 1 + ); + 'Long pos close SUCCEEDED'.print(); + + let first_position_close = data_store.get_position(position_key_1); + + assert(first_position_close.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_close.size_in_usd == 0, 'Size should be 0'); + assert(first_position_close.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_close.collateral_amount == 0, 'Collat should be 0'); + + let balance_USDC_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + assert(balance_USDC_bef_close == 53124999999999999996350, 'balance USDC shld be 52974.99$'); + assert(balance_USDC_af_close == 62724999999999999996350, 'balance USDC shld be 62724.99$'); + assert(balance_ETH_af_close == 7000000000000000000, 'balance ETH af close 7'); + assert(balance_ETH_bef_close == 7000000000000000000, 'balance ETH bef close 7'); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} + +#[test] +#[should_panic(expected: ('invalid_order_price', 'LimitDecrease',))] +fn test_takeprofit_long_decrease_fails() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market, + ) = + deposit_setup( + 50000000000000000000000000000, 50000000000000000000000000000 + ); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + // let pool_value_info = market_utils::get_pool_value_info( + // data_store, + // market, + // Price { min: 5000, max: 5000, }, + // Price { min: 5000, max: 5000, }, + // Price { min: 1, max: 1, }, + // keys::max_pnl_factor_for_deposits(), + // true, + // ); + + // pool_value_info.pool_value.mag.print(); // 10000 000000000000000000 + // pool_value_info.long_token_amount.print(); // 5 000000000000000000 + // pool_value_info.short_token_amount.print(); // 25000 000000000000000000 + + // ************************************* TEST LONG ********************************************* + + 'Begining of LONG TEST'.print(); + + let key_open_interest = keys::open_interest_key( + market.market_token, contract_address_const::<'ETH'>(), true + ); + data_store.set_u256(key_open_interest, 1); + let max_key_open_interest = keys::max_open_interest_key(market.market_token, true); + data_store + .set_u256( + max_key_open_interest, 1000000000000000000000000000000000000000000000000000 + ); // 1 000 000 + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 1000000000000000000); // 1ETH + + 'transfer made'.print(); + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 3500000000000000000000, + initial_collateral_delta_amount: 1000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 3501, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the swap order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1930); + 'try to create prder'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long = exchange_router.create_order(order_params_long); + 'long created'.print(); + let got_order_long = data_store.get_order(key_long); + + // Execute the swap order. + + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3500, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1935); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long, set_price_params); + 'long position SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position = data_store.get_position(position_key_1); + + assert(first_position.size_in_tokens == 1000000000000000000, 'Size token should be 1 ETH'); + assert(first_position.size_in_usd == 3500000000000000000000, 'Size should be 3500$'); + assert(first_position.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position.collateral_amount == 1000000000000000000, 'Collat should be 1 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + //////////////////////////////// TRIGGER INCREASE POSITION ////////////////////////////////// + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 2000000000000000000); // 2ETH + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_inc = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 7700000000000000000000, // 7700 + initial_collateral_delta_amount: 2000000000000000000, // 2 ETH 10^18 + trigger_price: 3850, + acceptable_price: 3851, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1940); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_inc = exchange_router.create_order(order_params_long_inc); + 'Long increase created'.print(); + + // Execute the swap order. + + let set_price_params_inc = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3850, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1945); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long_inc, set_price_params_inc); + 'long pos inc SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position_inc = data_store.get_position(position_key_1); + + first_position_inc.size_in_tokens.print(); + + assert(first_position_inc.size_in_tokens == 3000000000000000000, 'Size token should be 3 ETH'); + assert(first_position_inc.size_in_usd == 11200000000000000000000, 'Size should be 11200$'); + assert(first_position_inc.borrowing_factor == 0, 'borrow should be 0'); + assert(first_position_inc.collateral_amount == 3000000000000000000, 'Collat should be 3 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + 'pnl'.print(); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + //////////////////////////////////// TRIGGER DECREASE POSITION ////////////////////////////////////// + 'DECREASE POSITION'.print(); + + let balance_USDC_before = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_before = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + // Decrease 25% of the position + // Size = 11200$ -----> 25% = 2800 + // Collateral token amount = 3 ETH -----> 25% = 0.75 ETH + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 2800000000000000000000, // 2800 + initial_collateral_delta_amount: 750000000000000000, // 0.75 ETH 10^18 + trigger_price: 3950, + acceptable_price: 3949, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1950); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec = exchange_router.create_order(order_params_long_dec); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec); + + // Execute the swap order. + let set_price_params_dec = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3940, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1955); + order_handler.execute_order(key_long_dec, set_price_params_dec); + 'long pos dec SUCCEEDED'.print(); + + // Recieved 2974.999 USDC + + let first_position_dec = data_store.get_position(position_key_1); + + assert( + first_position_dec.size_in_tokens == 2250000000000000000, 'Size token should be 2.25 ETH' + ); + assert(first_position_dec.size_in_usd == 8400000000000000000000, 'Size should be 8400'); + assert(first_position_dec.borrowing_factor == 0, 'Borrow should be 0'); + assert( + first_position_dec.collateral_amount == 2250000000000000000, 'Collat should be 2.25 ETH' + ); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3950, max: 3950, }, + long_token_price: Price { min: 3950, max: 3950, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 487500000000000000000, 'PnL should be 487,5'); + + let balance_USDC_after = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_after = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .balance_of(caller_address); + + assert(balance_USDC_before == 50000000000000000000000, 'balance USDC before 50000$'); + // Balance USDC after = (0.75 ETH * 3950$) + 162.499 (PnL) + assert(balance_USDC_after == 53124999999999999996350, 'balance USDC after 53124.99$'); + assert(balance_ETH_before == 7000000000000000000, 'balance ETH before 7'); + assert(balance_ETH_after == 7000000000000000000, 'balance ETH after 7'); + + //////////////////////////////////// TRIGGER CLOSE POSITION ////////////////////////////////////// + 'CLOSE POSITION'.print(); + + let balance_USDC_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 4000, max: 4000, }, + long_token_price: Price { min: 4000, max: 4000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 600000000000000000000, 'PnL should be 600$'); + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec_2 = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 8400000000000000000000, // 8400 + initial_collateral_delta_amount: 2250000000000000000, // 2.25 ETH 10^18 + trigger_price: 4000, + acceptable_price: 3999, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1960); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec_2 = exchange_router.create_order(order_params_long_dec_2); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec_2); + // Execute the swap order. + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + let set_price_params_dec2 = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![4000, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1965); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long_dec_2, set_price_params_dec2); + 'Long pos close SUCCEEDED'.print(); + + let first_position_close = data_store.get_position(position_key_1); + + assert(first_position_close.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_close.size_in_usd == 0, 'Size should be 0'); + assert(first_position_close.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_close.collateral_amount == 0, 'Collat should be 0'); + + let balance_USDC_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + assert(balance_USDC_bef_close == 53124999999999999996350, 'balance USDC bef clse 53124.99$'); + assert(balance_USDC_af_close == 62724999999999999996350, 'balance USDC af close 62724.99$'); + assert(balance_ETH_af_close == 7000000000000000000, 'balance ETH af close 7'); + assert(balance_ETH_bef_close == 7000000000000000000, 'balance ETH bef close 7'); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} + +#[test] +#[should_panic(expected: ('invalid_order_price', 'LimitDecrease',))] +fn test_takeprofit_long_close_fails() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market, + ) = + deposit_setup( + 50000000000000000000000000000, 50000000000000000000000000000 + ); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + // let pool_value_info = market_utils::get_pool_value_info( + // data_store, + // market, + // Price { min: 5000, max: 5000, }, + // Price { min: 5000, max: 5000, }, + // Price { min: 1, max: 1, }, + // keys::max_pnl_factor_for_deposits(), + // true, + // ); + + // pool_value_info.pool_value.mag.print(); // 10000 000000000000000000 + // pool_value_info.long_token_amount.print(); // 5 000000000000000000 + // pool_value_info.short_token_amount.print(); // 25000 000000000000000000 + + // ************************************* TEST LONG ********************************************* + + 'Begining of LONG TEST'.print(); + + let key_open_interest = keys::open_interest_key( + market.market_token, contract_address_const::<'ETH'>(), true + ); + data_store.set_u256(key_open_interest, 1); + let max_key_open_interest = keys::max_open_interest_key(market.market_token, true); + data_store + .set_u256( + max_key_open_interest, 1000000000000000000000000000000000000000000000000000 + ); // 1 000 000 + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 1000000000000000000); // 1ETH + + 'transfer made'.print(); + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 3500000000000000000000, + initial_collateral_delta_amount: 1000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 3501, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the swap order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1930); + 'try to create prder'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long = exchange_router.create_order(order_params_long); + 'long created'.print(); + let got_order_long = data_store.get_order(key_long); + + // Execute the swap order. + + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3500, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1935); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long, set_price_params); + 'long position SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position = data_store.get_position(position_key_1); + + assert(first_position.size_in_tokens == 1000000000000000000, 'Size token should be 1 ETH'); + assert(first_position.size_in_usd == 3500000000000000000000, 'Size should be 3500$'); + assert(first_position.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position.collateral_amount == 1000000000000000000, 'Collat should be 1 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + //////////////////////////////// TRIGGER INCREASE POSITION ////////////////////////////////// + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 2000000000000000000); // 2ETH + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_inc = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 7700000000000000000000, // 7700 + initial_collateral_delta_amount: 2000000000000000000, // 2 ETH 10^18 + trigger_price: 3850, + acceptable_price: 3851, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1940); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_inc = exchange_router.create_order(order_params_long_inc); + 'Long increase created'.print(); + + // Execute the swap order. + + let set_price_params_inc = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3850, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1945); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long_inc, set_price_params_inc); + 'long pos inc SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position_inc = data_store.get_position(position_key_1); + + first_position_inc.size_in_tokens.print(); + + assert(first_position_inc.size_in_tokens == 3000000000000000000, 'Size token should be 3 ETH'); + assert(first_position_inc.size_in_usd == 11200000000000000000000, 'Size should be 11200$'); + assert(first_position_inc.borrowing_factor == 0, 'borrow should be 0'); + assert(first_position_inc.collateral_amount == 3000000000000000000, 'Collat should be 3 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + 'pnl'.print(); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + //////////////////////////////////// TRIGGER DECREASE POSITION ////////////////////////////////////// + 'DECREASE POSITION'.print(); + + let balance_USDC_before = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_before = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + // Decrease 25% of the position + // Size = 11200$ -----> 25% = 2800 + // Collateral token amount = 3 ETH -----> 25% = 0.75 ETH + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 2800000000000000000000, // 2800 + initial_collateral_delta_amount: 750000000000000000, // 0.75 ETH 10^18 + trigger_price: 3950, + acceptable_price: 3949, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1950); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec = exchange_router.create_order(order_params_long_dec); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec); + + // Execute the swap order. + let set_price_params_dec = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3950, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1955); + order_handler.execute_order(key_long_dec, set_price_params_dec); + 'long pos dec SUCCEEDED'.print(); + + // Recieved 2974.999 USDC + + let first_position_dec = data_store.get_position(position_key_1); + + assert( + first_position_dec.size_in_tokens == 2250000000000000000, 'Size token should be 2.25 ETH' + ); + assert(first_position_dec.size_in_usd == 8400000000000000000000, 'Size should be 8400'); + assert(first_position_dec.borrowing_factor == 0, 'Borrow should be 0'); + assert( + first_position_dec.collateral_amount == 2250000000000000000, 'Collat should be 2.25 ETH' + ); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3950, max: 3950, }, + long_token_price: Price { min: 3950, max: 3950, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 487500000000000000000, 'PnL should be 487,5'); + + let balance_USDC_after = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_after = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .balance_of(caller_address); + + assert(balance_USDC_before == 50000000000000000000000, 'balance USDC before 50000$'); + // Balance USDC after = (0.75 ETH * 3950$) + 162.499 (PnL) + assert(balance_USDC_after == 53124999999999999996350, 'balance USDC shld be 53124.99$'); + assert(balance_ETH_before == 7000000000000000000, 'balance ETH before 7'); + assert(balance_ETH_after == 7000000000000000000, 'balance ETH after 7'); + + //////////////////////////////////// TRIGGER CLOSE POSITION ////////////////////////////////////// + 'CLOSE POSITION'.print(); + + let balance_USDC_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 4000, max: 4000, }, + long_token_price: Price { min: 4000, max: 4000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 600000000000000000000, 'PnL should be 600$'); + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec_2 = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 8400000000000000000000, // 8400 + initial_collateral_delta_amount: 2250000000000000000, // 2.25 ETH 10^18 + trigger_price: 4000, + acceptable_price: 3999, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::LimitDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1960); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec_2 = exchange_router.create_order(order_params_long_dec_2); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec_2); + // Execute the swap order. + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + let set_price_params_dec2 = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3990, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1965); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long_dec_2, set_price_params_dec2); + 'Long pos close SUCCEEDED'.print(); + + let first_position_close = data_store.get_position(position_key_1); + + assert(first_position_close.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_close.size_in_usd == 0, 'Size should be 0'); + assert(first_position_close.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_close.collateral_amount == 0, 'Collat should be 0'); + + let balance_USDC_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + assert(balance_USDC_bef_close == 53124999999999999996350, 'balance USDC shld be 53124.99$'); + assert(balance_USDC_af_close == 62724999999999999996350, 'balance USDC shld be 62724.99$'); + assert(balance_ETH_af_close == 7000000000000000000, 'balance ETH af close 7'); + assert(balance_ETH_bef_close == 7000000000000000000, 'balance ETH bef close 7'); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} + +#[test] +fn test_long_liquidation() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market, + ) = + deposit_setup( + 50000000000000000000000000000, 50000000000000000000000000000 + ); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 3500, max: 3500, }, + Price { min: 3500, max: 3500, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert( + pool_value_info.pool_value.mag == 175050000000000000000000000000000, 'wrong pool value 1' + ); + assert( + pool_value_info.long_token_amount == 50000000000000000000000000000, + 'wrong long token amount 1' + ); + assert( + pool_value_info.short_token_amount == 50000000000000000000000000000, + 'wrong short token amount 1' + ); + + // ************************************* TEST LONG ********************************************* + + 'Begining of LONG TEST'.print(); + + let key_open_interest = keys::open_interest_key( + market.market_token, contract_address_const::<'ETH'>(), true + ); + data_store.set_u256(key_open_interest, 1); + let max_key_open_interest = keys::max_open_interest_key(market.market_token, true); + data_store + .set_u256( + max_key_open_interest, 1000000000000000000000000000000000000000000000000000 + ); // 1 000 000 + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 1000000000000000000); // 1ETH + + 'transfer made'.print(); + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 3500000000000000000000, + initial_collateral_delta_amount: 1000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 3501, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the swap order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1930); + 'try to create prder'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long = exchange_router.create_order(order_params_long); + 'long created'.print(); + let got_order_long = data_store.get_order(key_long); + + // Execute the swap order. + + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3500, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1935); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long, set_price_params); + 'long position SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position = data_store.get_position(position_key_1); + + assert(first_position.size_in_tokens == 1000000000000000000, 'Size token should be 1 ETH'); + assert(first_position.size_in_usd == 3500000000000000000000, 'Size should be 3500$'); + assert(first_position.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position.collateral_amount == 1000000000000000000, 'Collat should be 1 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + let balance_USDC = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } + .balance_of(caller_address); + + let balance_ETH = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .balance_of(caller_address); + + assert(balance_USDC == 50000000000000000000000, 'balance USDC 50 000$'); + assert(balance_ETH == 9000000000000000000, 'balance ETH 9'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 3500, max: 3500, }, + Price { min: 3500, max: 3500, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert( + pool_value_info.pool_value.mag == 175050000000000000000000000000001, 'wrong pool value 2' + ); + assert( + pool_value_info.long_token_amount == 50000000000000000000000000000, + 'wrong long token amount 2' + ); + assert( + pool_value_info.short_token_amount == 50000000000000000000000000000, + 'wrong short token amount 2' + ); + + /////////////////////////////////////// LIQUIDATION LONG /////////////////////////////////////// + + 'Check if liquidable 1000$'.print(); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 1000, max: 1000, }, + long_token_price: Price { min: 1000, max: 1000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let (is_liquiditable, reason) = reader + .is_position_liquidable( + data_store, referal_storage, first_position, market, market_prices, true + ); + + assert(is_liquiditable == true, 'Position is liquidable'); + + 'Check if liquidable 3000$'.print(); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3000, max: 3000, }, + long_token_price: Price { min: 3000, max: 3000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let (is_liquiditable, reason) = reader + .is_position_liquidable( + data_store, referal_storage, first_position, market, market_prices, true + ); + + assert(is_liquiditable == false, 'Position is not liquidable'); + + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![1000, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + // Execute Liquidation + liquidation_handler + .execute_liquidation( + first_position.account, + first_position.market, + first_position.collateral_token, + first_position.is_long, + set_price_params + ); + + let first_position_liq = data_store.get_position(position_key_1); + + assert(first_position_liq.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_liq.size_in_usd == 0, 'Size should be 0'); + assert(first_position_liq.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_liq.collateral_amount == 0, 'Collat should be 0'); + + let balance_USDC_af_liq = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_af_liq = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + assert(balance_USDC_af_liq == 50000000000000000000000, 'balance USDC 50 000$'); + assert(balance_ETH_af_liq == 9000000000000000000, 'balance ETH 9'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 3500, max: 3500, }, + Price { min: 3500, max: 3500, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert( + pool_value_info.pool_value.mag == 175050000003500000000000000000000, 'wrong pool value 3' + ); + assert( + pool_value_info.long_token_amount == 50000000001000000000000000000, + 'wrong long token amount 3' + ); + assert( + pool_value_info.short_token_amount == 50000000000000000000000000000, + 'wrong short token amount 3' + ); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} + +#[test] +fn test_long_leverage_positif_close() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market, + ) = + deposit_setup( + 50000000000000000000000000000, 50000000000000000000000000000 + ); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + // let pool_value_info = market_utils::get_pool_value_info( + // data_store, + // market, + // Price { min: 5000, max: 5000, }, + // Price { min: 5000, max: 5000, }, + // Price { min: 1, max: 1, }, + // keys::max_pnl_factor_for_deposits(), + // true, + // ); + + // pool_value_info.pool_value.mag.print(); // 10000 000000000000000000 + // pool_value_info.long_token_amount.print(); // 5 000000000000000000 + // pool_value_info.short_token_amount.print(); // 25000 000000000000000000 + + // ************************************* TEST LONG ********************************************* + + 'LONG TEST x10 leverage'.print(); + + let key_open_interest = keys::open_interest_key( + market.market_token, contract_address_const::<'ETH'>(), true + ); + data_store.set_u256(key_open_interest, 1); + let max_key_open_interest = keys::max_open_interest_key(market.market_token, true); + data_store + .set_u256( + max_key_open_interest, 1000000000000000000000000000000000000000000000000000 + ); // 1 000 000 + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 1000000000000000000); // 1ETH + + 'transfer made'.print(); + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 35000000000000000000000, + initial_collateral_delta_amount: 1000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 3501, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the swap order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1930); + 'try to create prder'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long = exchange_router.create_order(order_params_long); + 'long created'.print(); + let got_order_long = data_store.get_order(key_long); + + // Execute the swap order. + + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3500, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1935); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long, set_price_params); + 'long position SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position = data_store.get_position(position_key_1); + + assert(first_position.size_in_tokens == 10000000000000000000, 'Size token should be 1 ETH'); + assert(first_position.size_in_usd == 35000000000000000000000, 'Size should be 35000$'); + assert(first_position.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position.collateral_amount == 1000000000000000000, 'Collat should be 1 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 3500000000000000000000, 'PnL should be 3500$'); + + //////////////////////////////////// CLOSE POSITION ////////////////////////////////////// + 'CLOSE POSITION'.print(); + + let balance_USDC_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 4000, max: 4000, }, + long_token_price: Price { min: 4000, max: 4000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 5000000000000000000000, 'PnL should be 5000$'); + + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long_dec_2 = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 35000000000000000000000, // 8400 + initial_collateral_delta_amount: 1000000000000000000, // 2.25 ETH 10^18 + trigger_price: 0, + acceptable_price: 3850, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1960); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long_dec_2 = exchange_router.create_order(order_params_long_dec_2); + 'long decrease created'.print(); + let got_order_long_dec = data_store.get_order(key_long_dec_2); + // Execute the swap order. + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + let set_price_params_dec2 = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3850, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1965); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long_dec_2, set_price_params_dec2); + 'Long pos close SUCCEEDED'.print(); + + let first_position_close = data_store.get_position(position_key_1); + + assert(first_position_close.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_close.size_in_usd == 0, 'Size should be 0'); + assert(first_position_close.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_close.collateral_amount == 0, 'Collat should be 0'); + + let balance_USDC_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + assert(balance_USDC_bef_close == 50000000000000000000000, 'balance USDC shld be 50000$'); + assert(balance_USDC_af_close == 57349999999999999996500, 'balance USDC shld be 57350$'); + assert(balance_ETH_af_close == 9000000000000000000, 'balance ETH after 9'); + assert(balance_ETH_bef_close == 9000000000000000000, 'balance ETH after 9'); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} + +#[test] +fn test_long_leverage_liquidation() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market, + ) = + deposit_setup( + 50000000000000000000000000000, 50000000000000000000000000000 + ); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 3500, max: 3500, }, + Price { min: 3500, max: 3500, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert( + pool_value_info.pool_value.mag == 175050000000000000000000000000000, 'wrong pool value 1' + ); + assert( + pool_value_info.long_token_amount == 50000000000000000000000000000, + 'wrong long token amount 1' + ); + assert( + pool_value_info.short_token_amount == 50000000000000000000000000000, + 'wrong short token amount 1' + ); + + // ************************************* TEST LONG ********************************************* + + 'Begining of LONG TEST'.print(); + + let key_open_interest = keys::open_interest_key( + market.market_token, contract_address_const::<'ETH'>(), true + ); + data_store.set_u256(key_open_interest, 1); + let max_key_open_interest = keys::max_open_interest_key(market.market_token, true); + data_store + .set_u256( + max_key_open_interest, 1000000000000000000000000000000000000000000000000000 + ); // 1 000 000 + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 1000000000000000000); // 1ETH + + 'transfer made'.print(); + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 35000000000000000000000, + initial_collateral_delta_amount: 1000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 3501, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the swap order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1930); + 'try to create prder'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long = exchange_router.create_order(order_params_long); + 'long created'.print(); + let got_order_long = data_store.get_order(key_long); + + // Execute the swap order. + + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3500, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1935); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long, set_price_params); + 'long position SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position = data_store.get_position(position_key_1); + + assert(first_position.size_in_tokens == 10000000000000000000, 'Size token should be 10 ETH'); + assert(first_position.size_in_usd == 35000000000000000000000, 'Size should be 35000$'); + assert(first_position.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position.collateral_amount == 1000000000000000000, 'Collat should be 1 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 3500000000000000000000, 'PnL should be 3500$'); + + let balance_USDC = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } + .balance_of(caller_address); + + let balance_ETH = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .balance_of(caller_address); + + assert(balance_USDC == 50000000000000000000000, 'balance USDC 50 000$'); + assert(balance_ETH == 9000000000000000000, 'balance ETH 9'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 3500, max: 3500, }, + Price { min: 3500, max: 3500, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert( + pool_value_info.pool_value.mag == 175050000000000000000000000000001, 'wrong pool value 2' + ); + assert( + pool_value_info.long_token_amount == 50000000000000000000000000000, + 'wrong long token amount 2' + ); + assert( + pool_value_info.short_token_amount == 50000000000000000000000000000, + 'wrong short token amount 2' + ); + + /////////////////////////////////////// LIQUIDATION LONG /////////////////////////////////////// + + 'Check if liquidable 3000$'.print(); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3000, max: 3000, }, + long_token_price: Price { min: 3000, max: 3000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let (is_liquiditable, reason) = reader + .is_position_liquidable( + data_store, referal_storage, first_position, market, market_prices, true + ); + // position x10 leverage is liquidable at 3000$, position x1 leverage is not liquidable at 3000$ + assert(is_liquiditable == true, 'Position is liquidable'); + + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![1000, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + // Execute Liquidation + liquidation_handler + .execute_liquidation( + first_position.account, + first_position.market, + first_position.collateral_token, + first_position.is_long, + set_price_params + ); + + let first_position_liq = data_store.get_position(position_key_1); + + assert(first_position_liq.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_liq.size_in_usd == 0, 'Size should be 0'); + assert(first_position_liq.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_liq.collateral_amount == 0, 'Collat should be 0'); + + let balance_USDC_af_liq = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_af_liq = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + assert(balance_USDC_af_liq == 50000000000000000000000, 'balance USDC 50 000$'); + assert(balance_ETH_af_liq == 9000000000000000000, 'balance ETH 9'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 3500, max: 3500, }, + Price { min: 3500, max: 3500, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert( + pool_value_info.pool_value.mag == 175050000003500000000000000000000, 'wrong pool value 3' + ); + assert( + pool_value_info.long_token_amount == 50000000001000000000000000000, + 'wrong long token amount 3' + ); + assert( + pool_value_info.short_token_amount == 50000000000000000000000000000, + 'wrong short token amount 3' + ); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} diff --git a/tests/integration/test_short_integration.cairo b/tests/integration/test_short_integration.cairo new file mode 100644 index 00000000..4f02a9e9 --- /dev/null +++ b/tests/integration/test_short_integration.cairo @@ -0,0 +1,330 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +use result::ResultTrait; +use debug::PrintTrait; +use traits::{TryInto, Into}; +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, + ClassHash, +}; +use snforge_std::{declare, start_prank, stop_prank, start_roll, ContractClassTrait, ContractClass}; + + +// Local imports. +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::order::order_utils::{IOrderUtilsDispatcher, IOrderUtilsDispatcherTrait}; +use satoru::market::market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; +use satoru::deposit::deposit::Deposit; +use satoru::withdrawal::withdrawal::Withdrawal; + +use satoru::exchange::withdrawal_handler::{ + IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait +}; +use satoru::exchange::deposit_handler::{IDepositHandlerDispatcher, IDepositHandlerDispatcherTrait}; +use satoru::router::exchange_router::{IExchangeRouterDispatcher, IExchangeRouterDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::reader::reader::{IReaderDispatcher, IReaderDispatcherTrait}; +use satoru::market::market::{Market, UniqueIdMarket}; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::role::role; +use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::test_utils::tests_lib; +use satoru::deposit::deposit_utils::CreateDepositParams; +use satoru::utils::span32::{Span32, DefaultSpan32, Array32Trait}; +use satoru::deposit::deposit_utils; +use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; +use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::withdrawal::withdrawal_vault::{ + IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait +}; +use satoru::data::keys; +use satoru::market::market_utils; +use satoru::price::price::{Price, PriceTrait}; +use satoru::position::position_utils; +use satoru::withdrawal::withdrawal_utils; + +use satoru::exchange::liquidation_handler::{ + ILiquidationHandlerDispatcher, ILiquidationHandlerDispatcherTrait +}; +use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; +use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; +use satoru::order::base_order_utils::{CreateOrderParams}; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::market::{market::{UniqueIdMarketImpl},}; +use satoru::exchange::order_handler::{ + OrderHandler, IOrderHandlerDispatcher, IOrderHandlerDispatcherTrait +}; +use satoru::test_utils::{ + tests_lib::{setup, create_market, teardown}, deposit_setup::{deposit_setup, exec_order} +}; +const INITIAL_TOKENS_MINTED: felt252 = 1000; + +#[test] +fn test_short_increase_decrease_close() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + market, + ) = + deposit_setup( + 50000000000000000000000000000, 50000000000000000000000000000 + ); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 3500, max: 3500, }, + Price { min: 3500, max: 3500, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert( + pool_value_info.pool_value.mag == 175050000000000000000000000000000, 'wrong pool value 1' + ); + assert( + pool_value_info.long_token_amount == 50000000000000000000000000000, + 'wrong long token amount 1' + ); + assert( + pool_value_info.short_token_amount == 50000000000000000000000000000, + 'wrong short token amount 1' + ); + + // ************************************* TEST SHORT ********************************************* + + 'Begining of SHORT TEST'.print(); + + let key_open_interest = keys::open_interest_key( + market.market_token, contract_address_const::<'USDC'>(), false + ); + data_store.set_u256(key_open_interest, 1); + let max_key_open_interest = keys::max_open_interest_key(market.market_token, false); + data_store + .set_u256( + max_key_open_interest, 1000000000000000000000000000000000000000000000000000 + ); // 1 000 000 + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'USDC'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } + .transfer(order_vault.contract_address, 7000000000000000000000); // 7000 USDC + + 'transfer made'.print(); + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.market_token, caller_address); + start_prank(market.short_token, caller_address); + let order_params_short = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.short_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 7000000000000000000000, + initial_collateral_delta_amount: 7000000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 1, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: false, + referral_code: 0 + }; + // Create the short order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1930); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_short = exchange_router.create_order(order_params_short); + 'short created'.print(); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH caller 10 ETH'); + assert(balance_caller_USDC == 43000000000000000000000, 'USDC be 43 000 USDC'); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1935); + exec_order(order_handler.contract_address, role_store.contract_address, key_short, 3500, 1); + 'short position SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position = data_store.get_position(position_key_1); + + assert(first_position.size_in_tokens == 2000000000000000000, 'Size token should be 2 ETH'); + assert(first_position.size_in_usd == 7000000000000000000000, 'Size should be 7000$'); + assert(first_position.borrowing_factor == 0, 'Borrow should be 0'); + assert( + first_position.collateral_amount == 7000000000000000000000, 'Collat should be 7000 USDC' + ); + assert(first_position.collateral_token == market.short_token, 'should be USDC'); + + // Test the PnL if the price goes up + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + + // The sign field is true for negative integers, and false for non-negative integers. + assert(position_info.base_pnl_usd.sign == true, 'should be negative'); + assert(position_info.base_pnl_usd.mag == 700000000000000000000, 'PnL should be -700$'); + + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH caller 10 ETH'); + assert(balance_caller_USDC == 43000000000000000000000, 'USDC caller 43000 USDC'); + + // //////////////////////////////////// CLOSE POSITION ////////////////////////////////////// + 'CLOSE POSITION'.print(); + + let balance_USDC_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_bef_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3000, max: 3000, }, + long_token_price: Price { min: 3000, max: 3000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 1000000000000000000000, 'PnL should be 1000$'); + assert(position_info.base_pnl_usd.sign == false, 'should be positive'); + + start_prank(market.market_token, caller_address); + start_prank(market.short_token, caller_address); + let order_params_short_dec_2 = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.short_token, + swap_path: Array32Trait::::span32(@array![market.market_token]), + size_delta_usd: 7000000000000000000000, // 7000 + initial_collateral_delta_amount: 7000000000000000000000, // 7000 USDC 10^18 + trigger_price: 0, + acceptable_price: 1, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketDecrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: false, + referral_code: 0 + }; + // Create the long order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1960); + 'try to create order'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_short_dec_2 = exchange_router.create_order(order_params_short_dec_2); + 'short decrease created'.print(); + + // Execute the swap order. + start_roll(order_handler.contract_address, 1965); + exec_order( + order_handler.contract_address, role_store.contract_address, key_short_dec_2, 3000, 1 + ); + 'Short pos close SUCCEEDED'.print(); + + let first_position_close = data_store.get_position(position_key_1); + + assert(first_position_close.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_close.size_in_usd == 0, 'Size should be 0'); + assert(first_position_close.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_close.collateral_amount == 0, 'Collat should be 0'); + + let balance_USDC_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_af_close = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + assert(balance_USDC_bef_close == 43000000000000000000000, 'balance USDC bef close 43000$'); + assert(balance_USDC_af_close == 43000000000000000000000, 'balance USDC af close 43000$'); + assert(balance_ETH_af_close == 12666666666666666666, 'balance ETH af close 12.66'); + assert(balance_ETH_bef_close == 10000000000000000000, 'balance ETH bef close 10'); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +} diff --git a/tests/integration/test_swap_integration.cairo b/tests/integration/test_swap_integration.cairo new file mode 100644 index 00000000..71c97a38 --- /dev/null +++ b/tests/integration/test_swap_integration.cairo @@ -0,0 +1,955 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +use result::ResultTrait; +use debug::PrintTrait; +use traits::{TryInto, Into}; +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, + ClassHash, +}; +use snforge_std::{declare, start_prank, stop_prank, start_roll, ContractClassTrait, ContractClass}; + + +// Local imports. +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::market::market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; +use satoru::deposit::deposit::Deposit; +use satoru::withdrawal::withdrawal::Withdrawal; + +use satoru::exchange::withdrawal_handler::{ + IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait +}; +use satoru::exchange::deposit_handler::{IDepositHandlerDispatcher, IDepositHandlerDispatcherTrait}; +use satoru::router::exchange_router::{IExchangeRouterDispatcher, IExchangeRouterDispatcherTrait}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::reader::reader::{IReaderDispatcher, IReaderDispatcherTrait}; +use satoru::market::market::{Market, UniqueIdMarket}; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::role::role; +use satoru::oracle::oracle_utils::SetPricesParams; +use satoru::test_utils::tests_lib; +use satoru::deposit::deposit_utils::CreateDepositParams; +use satoru::utils::span32::{Span32, DefaultSpan32, Array32Trait}; +use satoru::deposit::deposit_utils; +use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; +use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::withdrawal::withdrawal_vault::{ + IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait +}; +use satoru::data::keys; +use satoru::market::market_utils; +use satoru::price::price::{Price, PriceTrait}; +use satoru::position::position_utils; +use satoru::withdrawal::withdrawal_utils; + +use satoru::order::order::{Order, OrderType, SecondaryOrderType, DecreasePositionSwapType}; +use satoru::order::order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}; +use satoru::order::base_order_utils::{CreateOrderParams}; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::market::{market::{UniqueIdMarketImpl},}; +use satoru::exchange::order_handler::{ + OrderHandler, IOrderHandlerDispatcher, IOrderHandlerDispatcherTrait +}; +const INITIAL_TOKENS_MINTED: felt252 = 1000; + +// #[test] +// fn test_swap_market_integration() { +// // ********************************************************************************************* +// // * SETUP * +// // ********************************************************************************************* +// let ( +// caller_address, +// market_factory_address, +// role_store_address, +// data_store_address, +// market_token_class_hash, +// market_factory, +// role_store, +// data_store, +// event_emitter, +// exchange_router, +// deposit_handler, +// deposit_vault, +// oracle, +// order_handler, +// order_vault, +// reader, +// referal_storage, +// ) = +// setup(); + +// // ********************************************************************************************* +// // * TEST LOGIC * +// // ********************************************************************************************* + +// // Create a market. +// let market = data_store.get_market(create_market(market_factory)); + +// // Set params in data_store +// data_store.set_address(keys::fee_token(), market.index_token); +// data_store.set_u256(keys::max_swap_path_length(), 5); + +// // Set max pool amount. +// data_store +// .set_u256( +// keys::max_pool_amount_key(market.market_token, market.long_token), 500000000000000000 +// ); +// data_store +// .set_u256( +// keys::max_pool_amount_key(market.market_token, market.short_token), 500000000000000000 +// ); + +// oracle.set_price_testing_eth(5000); + +// // Fill the pool. +// IERC20Dispatcher { contract_address: market.long_token }.mint(market.market_token, 50000000000); +// IERC20Dispatcher { contract_address: market.short_token } +// .mint(market.market_token, 50000000000); +// // TODO Check why we don't need to set pool_amount_key +// // // Set pool amount in data_store. +// // let mut key = keys::pool_amount_key(market.market_token, contract_address_const::<'ETH'>()); +// // data_store.set_u256(key, 50000000000); +// // key = keys::pool_amount_key(market.market_token, contract_address_const::<'USDC'>()); +// // data_store.set_u256(key, 50000000000); + +// // Send token to deposit in the deposit vault (this should be in a multi call with create_deposit) +// IERC20Dispatcher { contract_address: market.long_token } +// .mint(deposit_vault.contract_address, 50000000000); +// IERC20Dispatcher { contract_address: market.short_token } +// .mint(deposit_vault.contract_address, 50000000000); + +// let balance_deposit_vault_before = IERC20Dispatcher { contract_address: market.short_token } +// .balance_of(deposit_vault.contract_address); + +// // Create Deposit +// let user1: ContractAddress = contract_address_const::<'user1'>(); +// let user2: ContractAddress = contract_address_const::<'user2'>(); + +// let addresss_zero: ContractAddress = 0.try_into().unwrap(); + +// let params = CreateDepositParams { +// receiver: user1, +// callback_contract: user2, +// ui_fee_receiver: addresss_zero, +// market: market.market_token, +// initial_long_token: market.long_token, +// initial_short_token: market.short_token, +// long_token_swap_path: Array32Trait::::span32(@array![]), +// short_token_swap_path: Array32Trait::::span32(@array![]), +// min_market_tokens: 0, +// execution_fee: 0, +// callback_gas_limit: 0, +// }; + +// start_roll(deposit_handler.contract_address, 1910); +// let key = deposit_handler.create_deposit(caller_address, params); +// let first_deposit = data_store.get_deposit(key); + +// assert(first_deposit.account == caller_address, 'Wrong account depositer'); +// assert(first_deposit.receiver == user1, 'Wrong account receiver'); +// assert(first_deposit.initial_long_token == market.long_token, 'Wrong initial long token'); +// assert( +// first_deposit.initial_long_token_amount == 50000000000, 'Wrong initial long token amount' +// ); +// assert( +// first_deposit.initial_short_token_amount == 50000000000, 'Wrong init short token amount' +// ); + +// let price_params = SetPricesParams { // TODO +// signer_info: 1, +// tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], +// compacted_min_oracle_block_numbers: array![1900, 1900], +// compacted_max_oracle_block_numbers: array![1910, 1910], +// compacted_oracle_timestamps: array![9999, 9999], +// compacted_decimals: array![18, 18], +// compacted_min_prices: array![4294967346000000], // 50000000, 1000000 compacted +// compacted_min_prices_indexes: array![0], +// compacted_max_prices: array![4294967346000000], // 50000000, 1000000 compacted +// compacted_max_prices_indexes: array![0], +// signatures: array![ +// array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() +// ], +// price_feed_tokens: array![] +// }; + +// start_prank(role_store.contract_address, caller_address); + +// role_store.grant_role(caller_address, role::ORDER_KEEPER); +// role_store.grant_role(caller_address, role::ROLE_ADMIN); +// role_store.grant_role(caller_address, role::CONTROLLER); +// role_store.grant_role(caller_address, role::MARKET_KEEPER); + +// // Execute Deposit +// start_roll(deposit_handler.contract_address, 1915); +// deposit_handler.execute_deposit(key, price_params); + +// let pool_value_info = market_utils::get_pool_value_info( +// data_store, +// market, +// Price { min: 1999, max: 2000 }, +// Price { min: 1999, max: 2000 }, +// Price { min: 1999, max: 2000 }, +// keys::max_pnl_factor_for_deposits(), +// true, +// ); + +// assert(pool_value_info.pool_value.mag == 200000000000000, 'wrong pool value amount'); +// assert(pool_value_info.long_token_amount == 50000000000, 'wrong long token amount'); +// assert(pool_value_info.short_token_amount == 50000000000, 'wrong short token amount'); + +// let not_deposit = data_store.get_deposit(key); +// let default_deposit: Deposit = Default::default(); +// assert(not_deposit == default_deposit, 'Still existing deposit'); + +// // let market_token_dispatcher = IMarketTokenDispatcher { contract_address: market.market_token }; + +// // let balance = market_token_dispatcher.balance_of(user1); + +// let balance_deposit_vault = IERC20Dispatcher { contract_address: market.short_token } +// .balance_of(deposit_vault.contract_address); + +// let pool_value_info = market_utils::get_pool_value_info( +// data_store, +// market, +// Price { min: 5000, max: 5000, }, +// Price { min: 5000, max: 5000, }, +// Price { min: 1, max: 1, }, +// keys::max_pnl_factor_for_deposits(), +// true, +// ); + +// pool_value_info.pool_value.mag.print(); +// pool_value_info.long_token_amount.print(); +// pool_value_info.short_token_amount.print(); + +// // // --------------------SWAP TEST USDC->ETH -------------------- + +// let balance_ETH_before_swap = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } +// .balance_of(caller_address); +// assert(balance_ETH_before_swap == 1000000, 'wrong balance ETH before swap'); + +// let balance_USDC_before_swap = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } +// .balance_of(caller_address); +// assert(balance_USDC_before_swap == 1000000, 'wrong balance USDC before swap'); + +// start_prank(contract_address_const::<'ETH'>(), caller_address); //change to switch swap +// // Send token to order_vault in multicall with create_order +// IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } //change to switch swap +// .transfer(order_vault.contract_address, 1); + +// let balance_ETH_before = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } +// .balance_of(caller_address); +// let balance_USDC_before = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } +// .balance_of(caller_address); +// 'balance ETH: '.print(); +// balance_ETH_before.print(); +// 'balance USDC: '.print(); +// balance_USDC_before.print(); +// 'end first balances'.print(); +// // Create order_params Struct +// let contract_address = contract_address_const::<0>(); +// start_prank(market.long_token, caller_address); //change to switch swap +// let order_params = CreateOrderParams { +// receiver: caller_address, +// callback_contract: contract_address, +// ui_fee_receiver: contract_address, +// market: contract_address, +// initial_collateral_token: market.long_token, //change to switch swap +// swap_path: Array32Trait::::span32(@array![market.market_token]), +// size_delta_usd: 1, +// initial_collateral_delta_amount: 1, // 10^18 +// trigger_price: 0, +// acceptable_price: 0, +// execution_fee: 0, +// callback_gas_limit: 0, +// min_output_amount: 0, +// order_type: OrderType::MarketSwap(()), +// decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), +// is_long: false, +// referral_code: 0 +// }; +// // Create the swap order. +// start_roll(order_handler.contract_address, 1920); +// let key = order_handler.create_order(caller_address, order_params); + +// let got_order = data_store.get_order(key); +// // data_store.set_u256(keys::pool_amount_key(market.market_token, contract_address_const::<'USDC'>()), ); +// // data_store.set_u256(keys::pool_amount_key(market.market_token, contract_address_const::<'ETH'>()), 1000000); +// // Execute the swap order. +// let signatures: Span = array![0].span(); +// let set_price_params = SetPricesParams { +// signer_info: 2, +// tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], +// compacted_min_oracle_block_numbers: array![1910, 1910], +// compacted_max_oracle_block_numbers: array![1920, 1920], +// compacted_oracle_timestamps: array![9999, 9999], +// compacted_decimals: array![1, 1], +// compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted +// compacted_min_prices_indexes: array![0], +// compacted_max_prices: array![2147483648010000], // 500000, 10000 compacted +// compacted_max_prices_indexes: array![0], +// signatures: array![ +// array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() +// ], +// price_feed_tokens: array![] +// }; + +// let balance_ETH_before_execute = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } +// .balance_of(caller_address); +// let balance_USDC_before_execute = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } +// .balance_of(caller_address); + +// 'balance eth before execute'.print(); +// balance_ETH_before_execute.print(); +// // assert(balance_ETH_after == 999999, 'wrong balance ETH after swap'); +// 'balance usdc before execute'.print(); +// balance_USDC_before_execute.print(); + +// let keeper_address = contract_address_const::<'keeper'>(); +// role_store.grant_role(keeper_address, role::ORDER_KEEPER); + +// stop_prank(order_handler.contract_address); +// start_prank(order_handler.contract_address, keeper_address); +// start_roll(order_handler.contract_address, 1925); +// // TODO add real signatures check on Oracle Account +// order_handler.execute_order_keeper(key, set_price_params, keeper_address); + +// let balance_ETH_after = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } +// .balance_of(caller_address); +// let balance_USDC_after = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } +// .balance_of(caller_address); + +// 'balance eth after'.print(); +// balance_ETH_after.print(); +// // assert(balance_ETH_after == 999999, 'wrong balance ETH after swap'); +// 'balance usdc after'.print(); +// balance_USDC_after.print(); +// // assert(balance_USDC_after == 995000, 'wrong balance USDC after swap'); + +// let first_swap_pool_value_info = market_utils::get_pool_value_info( +// data_store, +// market, +// Price { +// min: 5000, +// max: 5000, +// } +// , +// Price { +// min: 5000, +// max: 5000, +// }, +// Price { +// min: 1, +// max: 1, +// }, +// keys::max_pnl_factor_for_deposits(), +// true, +// ); + +// first_swap_pool_value_info.pool_value.mag.print(); +// first_swap_pool_value_info.long_token_amount.print(); +// first_swap_pool_value_info.short_token_amount.print(); +// } + +fn create_market(market_factory: IMarketFactoryDispatcher) -> ContractAddress { + // Create a market. + let (index_token, short_token) = deploy_tokens(); + let market_type = 'market_type'; + + // Index token is the same as long token here. + market_factory.create_market(index_token, index_token, short_token, market_type) +} + +/// Utility functions to deploy tokens for a market. +fn deploy_tokens() -> (ContractAddress, ContractAddress) { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let contract = declare('ERC20'); + + let eth_address = contract_address_const::<'ETH'>(); + let constructor_calldata = array!['Ethereum', 'ETH', 1000000, 0, caller_address.into()]; + contract.deploy_at(@constructor_calldata, eth_address).unwrap(); + + let usdc_address = contract_address_const::<'USDC'>(); + let constructor_calldata = array!['usdc', 'USDC', 1000000, 0, caller_address.into()]; + contract.deploy_at(@constructor_calldata, usdc_address).unwrap(); + (eth_address, usdc_address) +} + +/// 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, + // Address of the `MarketFactory` contract. + ContractAddress, + // Address of the `RoleStore` contract. + ContractAddress, + // Address of the `DataStore` contract. + ContractAddress, + // The `MarketToken` class hash for the factory. + ContractClass, + // Interface to interact with the `MarketFactory` contract. + IMarketFactoryDispatcher, + // Interface to interact with the `RoleStore` contract. + IRoleStoreDispatcher, + // Interface to interact with the `DataStore` contract. + IDataStoreDispatcher, + // Interface to interact with the `EventEmitter` contract. + IEventEmitterDispatcher, + // Interface to interact with the `ExchangeRouter` contract. + IExchangeRouterDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositHandlerDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositVaultDispatcher, + IOracleDispatcher, + IOrderHandlerDispatcher, + IOrderVaultDispatcher, + IReaderDispatcher, + IReferralStorageDispatcher, + IWithdrawalHandlerDispatcher, + IWithdrawalVaultDispatcher, +) { + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + ) = + setup_contracts(); + grant_roles_and_prank(caller_address, role_store, data_store, market_factory); + ( + caller_address, + market_factory.contract_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_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. +/// * `role_store` - The interface to interact with the `RoleStore` contract. +/// * `data_store` - The interface to interact with the `DataStore` contract. +/// * `market_factory` - The interface to interact with the `MarketFactory` contract. +fn grant_roles_and_prank( + caller_address: ContractAddress, + role_store: IRoleStoreDispatcher, + data_store: IDataStoreDispatcher, + market_factory: IMarketFactoryDispatcher, +) { + start_prank(role_store.contract_address, caller_address); + + // Grant the caller the `CONTROLLER` role. + role_store.grant_role(caller_address, role::CONTROLLER); + + // Grant the call the `MARKET_KEEPER` role. + // This role is required to create a market. + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + // 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); + + // Start pranking the `MarketFactory` contract. This is necessary to mock the behavior of the contract + // for testing purposes. + start_prank(market_factory.contract_address, caller_address); +} + +/// Utility function to teardown the test environment. +fn teardown(data_store: IDataStoreDispatcher, market_factory: IMarketFactoryDispatcher) { + stop_prank(data_store.contract_address); + stop_prank(market_factory.contract_address); +} + +/// Setup required contracts. +fn setup_contracts() -> ( + // This caller address will be used with `start_prank` cheatcode to mock the caller address., + ContractAddress, + // Address of the `MarketFactory` contract. + ContractAddress, + // Address of the `RoleStore` contract. + ContractAddress, + // Address of the `DataStore` contract. + ContractAddress, + // The `MarketToken` class hash for the factory. + ContractClass, + // Interface to interact with the `MarketFactory` contract. + IMarketFactoryDispatcher, + // Interface to interact with the `RoleStore` contract. + IRoleStoreDispatcher, + // Interface to interact with the `DataStore` contract. + IDataStoreDispatcher, + // Interface to interact with the `EventEmitter` contract. + IEventEmitterDispatcher, + // Interface to interact with the `ExchangeRouter` contract. + IExchangeRouterDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositHandlerDispatcher, + // Interface to interact with the `DepositHandler` contract. + IDepositVaultDispatcher, + IOracleDispatcher, + IOrderHandlerDispatcher, + IOrderVaultDispatcher, + IReaderDispatcher, + IReferralStorageDispatcher, + IWithdrawalHandlerDispatcher, + IWithdrawalVaultDispatcher, +) { + // 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 }; + + // Declare the `MarketToken` contract. + let market_token_class_hash = declare_market_token(); + + // Deploy the event emitter contract. + let event_emitter_address = deploy_event_emitter(); + // Create a safe dispatcher to interact with the contract. + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + + // Deploy the router contract. + let router_address = deploy_router(role_store_address); + + // Deploy the market factory. + let market_factory_address = deploy_market_factory( + data_store_address, role_store_address, event_emitter_address, market_token_class_hash + ); + // Create a safe dispatcher to interact with the contract. + let market_factory = IMarketFactoryDispatcher { contract_address: market_factory_address }; + + let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); + let oracle_address = deploy_oracle( + role_store_address, oracle_store_address, contract_address_const::<'pragma'>() + ); + + let oracle = IOracleDispatcher { contract_address: oracle_address }; + + let deposit_vault_address = deploy_deposit_vault(role_store_address, data_store_address); + + let deposit_vault = IDepositVaultDispatcher { contract_address: deposit_vault_address }; + let deposit_handler_address = deploy_deposit_handler( + data_store_address, + role_store_address, + event_emitter_address, + deposit_vault_address, + oracle_address + ); + let deposit_handler = IDepositHandlerDispatcher { contract_address: deposit_handler_address }; + + let withdrawal_vault_address = deploy_withdrawal_vault(data_store_address, role_store_address); + let withdrawal_handler_address = deploy_withdrawal_handler( + data_store_address, + role_store_address, + event_emitter_address, + withdrawal_vault_address, + oracle_address + ); + + let order_vault_address = deploy_order_vault( + data_store.contract_address, role_store.contract_address + ); + let order_vault = IOrderVaultDispatcher { contract_address: order_vault_address }; + + let swap_handler_address = deploy_swap_handler_address(role_store_address, data_store_address); + let referral_storage_address = deploy_referral_storage(event_emitter_address); + let order_handler_address = deploy_order_handler( + data_store_address, + role_store_address, + event_emitter_address, + order_vault_address, + oracle_address, + swap_handler_address, + referral_storage_address + ); + let order_handler = IOrderHandlerDispatcher { contract_address: order_handler_address }; + + let exchange_router_address = deploy_exchange_router( + router_address, + data_store_address, + role_store_address, + event_emitter_address, + deposit_handler_address, + withdrawal_handler_address, + order_handler_address + ); + let exchange_router = IExchangeRouterDispatcher { contract_address: exchange_router_address }; + + 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 }; + + let reader_address = deploy_reader(); + let reader = IReaderDispatcher { contract_address: reader_address }; + + let referal_storage = IReferralStorageDispatcher { contract_address: referral_storage_address }; + + let withdrawal_handler = IWithdrawalHandlerDispatcher { + contract_address: withdrawal_handler_address + }; + let withdrawal_vault = IWithdrawalVaultDispatcher { + contract_address: withdrawal_vault_address + }; + ( + contract_address_const::<'caller'>(), + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + ) +} + +/// Utility function to declare a `MarketToken` contract. +fn declare_market_token() -> ContractClass { + declare('MarketToken') +} + +/// Utility function to deploy a market factory contract and return its address. +fn deploy_market_factory( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + market_token_class_hash: ContractClass, +) -> ContractAddress { + let contract = declare('MarketFactory'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'market_factory'>(); + start_prank(deployed_contract_address, caller_address); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + constructor_calldata.append(event_emitter_address.into()); + constructor_calldata.append(market_token_class_hash.class_hash.into()); + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address: ContractAddress = 0x1.try_into().unwrap(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_router(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('Router'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'router'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_deposit_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + deposit_vault_address: ContractAddress, + oracle_address: ContractAddress +) -> ContractAddress { + let contract = declare('DepositHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'deposit_handler'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + deposit_vault_address.into(), + oracle_address.into() + ], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_oracle_store( + role_store_address: ContractAddress, event_emitter_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OracleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle_store'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![role_store_address.into(), event_emitter_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_oracle( + role_store_address: ContractAddress, + oracle_store_address: ContractAddress, + pragma_address: ContractAddress +) -> ContractAddress { + let contract = declare('Oracle'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![role_store_address.into(), oracle_store_address.into(), pragma_address.into()], + deployed_contract_address + ) + .unwrap() +} + +fn deploy_deposit_vault( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('DepositVault'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'deposit_vault'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at( + @array![data_store_address.into(), role_store_address.into()], deployed_contract_address + ) + .unwrap() +} + +fn deploy_withdrawal_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + withdrawal_vault_address: ContractAddress, + oracle_address: ContractAddress +) -> ContractAddress { + let contract = declare('WithdrawalHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'withdrawal_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + withdrawal_vault_address.into(), + oracle_address.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_withdrawal_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('WithdrawalVault'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'withdrawal_vault'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![data_store_address.into(), role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_order_handler( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + order_vault_address: ContractAddress, + oracle_address: ContractAddress, + swap_handler_address: ContractAddress, + referral_storage_address: ContractAddress +) -> ContractAddress { + let contract = declare('OrderHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'order_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + order_vault_address.into(), + oracle_address.into(), + swap_handler_address.into(), + referral_storage_address.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_swap_handler_address( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('SwapHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'swap_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'referral_storage'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_exchange_router( + router_address: ContractAddress, + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + deposit_handler_address: ContractAddress, + withdrawal_handler_address: ContractAddress, + order_handler_address: ContractAddress +) -> ContractAddress { + let contract = declare('ExchangeRouter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'exchange_router'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + router_address.into(), + data_store_address.into(), + role_store_address.into(), + event_emitter_address.into(), + deposit_handler_address.into(), + withdrawal_handler_address.into(), + order_handler_address.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_order_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let contract = declare('OrderVault'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + tests_lib::deploy_mock_contract(contract, @constructor_calldata) +} + +fn deploy_bank( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let bank_address: ContractAddress = contract_address_const::<'bank'>(); + let contract = declare('Bank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + start_prank(data_store_address, caller_address); + contract.deploy_at(@constructor_calldata, bank_address).unwrap() +} + +fn deploy_strict_bank( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let strict_bank_address: ContractAddress = contract_address_const::<'strict_bank'>(); + let contract = declare('StrictBank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + start_prank(strict_bank_address, caller_address); + contract.deploy_at(@constructor_calldata, strict_bank_address).unwrap() +} + +fn deploy_reader() -> ContractAddress { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let reader_address: ContractAddress = contract_address_const::<'reader'>(); + let contract = declare('Reader'); + let mut constructor_calldata = array![]; + start_prank(reader_address, caller_address); + contract.deploy_at(@constructor_calldata, reader_address).unwrap() +} + +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() +} diff --git a/tests/lib.cairo b/tests/lib.cairo new file mode 100644 index 00000000..fe471ec9 --- /dev/null +++ b/tests/lib.cairo @@ -0,0 +1,129 @@ +// mod adl { +// mod test_adl_utils; +// } +// mod bank { +// mod test_bank; +// mod test_strict_bank; +// } +// mod callback { +// mod test_callback_utils; +// } +// mod config { +// mod test_config; +// } +// mod data { +// mod test_data_store; +// mod test_deposit_store; +// mod test_keys; +// mod test_market; +// mod test_order; +// mod test_position; +// mod test_withdrawal; +// } +// mod deposit { +// mod test_deposit_utils; +// mod test_deposit_vault; +// mod test_execute_deposit_utils; +// } +// mod event { +// mod test_adl_events_emitted; +// mod test_callback_events_emitted; +// mod test_config_events_emitted; +// mod test_gas_events_emitted; +// mod test_market_events_emitted; +// mod test_oracle_events_emitted; +// mod test_order_events_emitted; +// mod test_position_events_emitted; +// mod test_pricing_events_emitted; +// mod test_referral_events_emitted; +// mod test_swap_events_emitted; +// mod test_timelock_events_emitted; +// mod test_withdrawal_events_emitted; +// mod test_event_utils; +// } +// mod exchange { +// // mod test_liquidation_handler; +// mod test_withdrawal_handler; +// mod test_deposit_handler; +// mod test_exchange_utils; +// // mod test_base_order_handler; +// } +// mod feature { +// mod test_feature_utils; +// } +// mod fee { +// mod test_fee_handler; +// mod test_fee_utils; +// } +// mod market { +// mod test_market_factory; +// mod test_market_token; +// mod test_market_utils; +// } +// mod nonce { +// mod test_nonce_utils; +// } +// mod oracle { +// mod test_oracle; +// } +// mod order { +// mod test_base_order_utils; +// // mod test_increase_order_utils; +// mod test_order; +// } +// mod position { +// mod test_decrease_position_utils; +// mod test_decrease_position_swap_utils; +// mod test_position_utils; +// } +// mod price { +// mod test_price; +// } +// mod pricing { +// mod test_position_pricing_utils; +// mod test_swap_pricing_utils; +// } +// mod reader { +// mod test_reader; +// } +// mod role { +// mod test_role_module; +// mod test_role_store; +// } +// mod router { +// mod test_router; +// } +// mod swap { +// mod test_swap_handler; +// } +// mod utils { +// mod test_account_utils; +// mod test_arrays; +// mod test_basic_multicall; +// mod test_calc; +// mod test_enumerable_set; +// mod test_precision; +// mod test_reentrancy_guard; +// mod test_starknet_utils; +// // mod test_u128_mask; +// // mod test_i128; +// mod test_serializable_dict; +// } +// mod withdrawal { +// mod test_withdrawal_vault; +// } +// mod mock { +// mod test_governable; +// mod test_referral_storage; +// } +// mod referral { +// mod test_referral_utils; +// } + +mod integration { + // mod test_deposit_withdrawal; + mod test_short_integration; + // mod test_swap_integration; + mod test_long_integration; + mod swap_test; +} diff --git a/src/tests/market/test_market_factory.cairo b/tests/market/test_market_factory.cairo similarity index 89% rename from src/tests/market/test_market_factory.cairo rename to tests/market/test_market_factory.cairo index aebd10b5..26cb14e1 100644 --- a/src/tests/market/test_market_factory.cairo +++ b/tests/market/test_market_factory.cairo @@ -56,7 +56,7 @@ fn given_normal_conditions_when_create_market_then_market_is_created() { // Get the market from the data store. // This must not panic, because the market was created in the previous step. // Hence the market must exist in the data store and it's safe to unwrap. - let market = data_store.get_market(market_token_deployed_address).unwrap(); + let market = data_store.get_market(market_token_deployed_address); // Check the market is as expected. assert(market.index_token == index_token, 'bad_market'); @@ -253,16 +253,13 @@ fn setup_contracts() -> ( // Deploy the market factory. let market_factory_address = deploy_market_factory( - data_store_address, - role_store_address, - event_emitter_address, - market_token_class_hash.clone() + data_store_address, role_store_address, event_emitter_address, market_token_class_hash ); // Create a safe dispatcher to interact with the contract. let market_factory = IMarketFactoryDispatcher { contract_address: market_factory_address }; ( - 0x101.try_into().unwrap(), + contract_address_const::<'caller'>(), market_factory_address, role_store_address, data_store_address, @@ -287,35 +284,39 @@ fn deploy_market_factory( market_token_class_hash: ContractClass, ) -> ContractAddress { let contract = declare('MarketFactory'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'market_factory'>(); + start_prank(deployed_contract_address, caller_address); let mut constructor_calldata = array![]; constructor_calldata.append(data_store_address.into()); constructor_calldata.append(role_store_address.into()); constructor_calldata.append(event_emitter_address.into()); constructor_calldata.append(market_token_class_hash.class_hash.into()); - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).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() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).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'); - let constructor_arguments: @Array:: = @ArrayTrait::new(); - contract.deploy(constructor_arguments).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } -/// Utility function to deploy a `EventEmitter` contract and return its address. fn deploy_event_emitter() -> ContractAddress { let contract = declare('EventEmitter'); - let constructor_arguments: @Array:: = @ArrayTrait::new(); - contract.deploy(constructor_arguments).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() } diff --git a/src/tests/market/test_market_token.cairo b/tests/market/test_market_token.cairo similarity index 85% rename from src/tests/market/test_market_token.cairo rename to tests/market/test_market_token.cairo index c747f275..314572b5 100644 --- a/src/tests/market/test_market_token.cairo +++ b/tests/market/test_market_token.cairo @@ -46,7 +46,7 @@ fn setup() -> ( IRoleStoreDispatcher, // Interface to interact with the `MarketToken` contract. IMarketTokenDispatcher, ) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); // Deploy the role store contract. let role_store_address = deploy_role_store(); @@ -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,18 +83,19 @@ 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() } -/// 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'); - let constructor_arguments: @Array:: = @ArrayTrait::new(); - contract.deploy(constructor_arguments).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } diff --git a/src/tests/market/test_market_utils.cairo b/tests/market/test_market_utils.cairo similarity index 91% rename from src/tests/market/test_market_utils.cairo rename to tests/market/test_market_utils.cairo index 19487beb..28930dd5 100644 --- a/src/tests/market/test_market_utils.cairo +++ b/tests/market/test_market_utils.cairo @@ -10,7 +10,6 @@ use starknet::{ ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, ClassHash, }; -use debug::PrintTrait; use snforge_std::{declare, start_prank, stop_prank, start_warp, ContractClassTrait, ContractClass}; @@ -26,6 +25,7 @@ use satoru::market::market_utils; use satoru::data::keys; use satoru::role::role; use satoru::price::price::{Price, PriceTrait}; +use satoru::utils::i256::{i256, i256_new}; #[test] fn given_normal_conditions_when_get_open_interest_then_works() { @@ -62,7 +62,7 @@ fn given_normal_conditions_when_get_open_interest_then_works() { // Get the market from the data store. // This must not panic, because the market was created in the previous step. // Hence the market must exist in the data store and it's safe to unwrap. - let market = data_store.get_market(market_token_deployed_address).unwrap(); + let market = data_store.get_market(market_token_deployed_address); let collateral_token = contract_address_const::<'collateral_token'>(); let is_long = true; @@ -71,9 +71,9 @@ fn given_normal_conditions_when_get_open_interest_then_works() { let open_interest_data_store_key = keys::open_interest_key( market_token_deployed_address, collateral_token, is_long ); - data_store.set_u128(open_interest_data_store_key, 300); + data_store.set_u256(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. @@ -123,7 +123,7 @@ fn given_normal_conditions_when_get_open_interest_in_tokens_then_works() { let open_interest_in_tokens_key = keys::open_interest_in_tokens_key( market_address, collateral_token, is_long ); - data_store.set_u128(open_interest_in_tokens_key, 300); + data_store.set_u256(open_interest_in_tokens_key, 300); let open_interest_in_tokens = market_utils::get_open_interest_in_tokens( data_store, market_address, collateral_token, is_long, divisor @@ -176,13 +176,13 @@ fn given_normal_conditions_when_get_open_interest_in_tokens_for_market_then_work let open_interest_in_tokens_key_for_long = keys::open_interest_in_tokens_key( market_token_address, market.long_token, is_long ); - data_store.set_u128(open_interest_in_tokens_key_for_long, 100); + data_store.set_u256(open_interest_in_tokens_key_for_long, 100); // Set open interest for short token. let open_interest_in_tokens_key_for_short = keys::open_interest_in_tokens_key( market_token_address, market.short_token, is_long ); - data_store.set_u128(open_interest_in_tokens_key_for_short, 200); + data_store.set_u256(open_interest_in_tokens_key_for_short, 200); // Actual test case. let open_interest_in_tokens_for_market = market_utils::get_open_interest_in_tokens_for_market( @@ -236,7 +236,7 @@ fn given_normal_conditions_when_get_pool_amount_then_works() { short_token: contract_address_const::<'short_token'>(), }; let pool_amount_key = keys::pool_amount_key(market_token_address, token_address); - data_store.set_u128(pool_amount_key, 1000); + data_store.set_u256(pool_amount_key, 1000); let pool_amount = market_utils::get_pool_amount(data_store, @market, token_address); // long_token != short_token, so the pool amount is 1000 because the divisor is 1. @@ -254,7 +254,7 @@ fn given_normal_conditions_when_get_pool_amount_then_works() { short_token: contract_address_const::<'same_token'>(), }; let pool_amount_key_2 = keys::pool_amount_key(market_token_address_2, token_address_2); - data_store.set_u128(pool_amount_key_2, 1000); + data_store.set_u256(pool_amount_key_2, 1000); let pool_amount_2 = market_utils::get_pool_amount(data_store, @market_2, token_address_2); // long_token == short_token, so the pool amount is 500 because the divisor is 2. assert(pool_amount_2 == 500, 'wrong pool amount'); @@ -294,7 +294,7 @@ fn given_normal_conditions_when_get_max_pool_amount_then_works() { // Setup pre conditions. let max_pool_amount_key = keys::max_pool_amount_key(market_token_address, token_address); - data_store.set_u128(max_pool_amount_key, 1000); + data_store.set_u256(max_pool_amount_key, 1000); // Actual test case. @@ -343,7 +343,7 @@ fn given_normal_conditions_when_get_max_open_interest_then_works() { // Setup pre conditions. let max_open_interest_key = keys::max_open_interest_key(market_token_address, is_long); - data_store.set_u128(max_open_interest_key, 1000); + data_store.set_u256(max_open_interest_key, 1000); // Actual test case. @@ -391,7 +391,7 @@ fn given_normal_conditions_when_increment_claimable_collateral_amount_then_works let market_address = contract_address_const::<'market_address'>(); let token = contract_address_const::<'token'>(); let account = contract_address_const::<'account'>(); - let delta = 50; + let delta = i256_new(50, false); // The key for the claimable collateral amount for the account. // This is the key that will be used to assert the result. let claimable_collatoral_amount_for_account_key = @@ -407,21 +407,24 @@ fn given_normal_conditions_when_increment_claimable_collateral_amount_then_works start_warp(chain.contract_address, current_timestamp); // Fill required data store keys. - data_store.set_u128(keys::claimable_collateral_time_divisor(), 1); + data_store.set_u256(keys::claimable_collateral_time_divisor(), 1); - // Actual test case. - market_utils::increment_claimable_collateral_amount( - data_store, chain, event_emitter, market_address, token, account, delta - ); + // Actual test case. + // TODO uncomment below when we can use get_block_timestamp() with foundry + // market_utils::increment_claimable_collateral_amount( + // data_store, event_emitter, market_address, token, account, delta + // ); // Perform assertions. // The value of the claimable collateral amount for the account should now be 50. // Read the value from the data store using the hardcoded key and assert it. - assert(data_store.get_u128(claimable_collatoral_amount_for_account_key) == 50, 'wrong value'); + // TODO uncomment below when we can use get_block_timestamp() with foundry + //assert(data_store.get_u256(claimable_collatoral_amount_for_account_key) == 50, 'wrong value'); // The value of the claimable collateral amount for the market should now be 50. // Read the value from the data store using the hardcoded key and assert it. - assert(data_store.get_u128(claimable_collateral_amount_key) == 50, 'wrong value'); + // TODO uncomment below when we can use get_block_timestamp() with foundry + //assert(data_store.get_u256(claimable_collateral_amount_key) == 50, 'wrong value'); // ********************************************************************************************* // * TEARDOWN * @@ -475,10 +478,10 @@ fn given_normal_conditions_when_increment_claimable_funding_amount_then_works() // The value of the claimable funding amount for the account should now be 50. // Read the value from the data store using the hardcoded key and assert it. - assert(data_store.get_u128(claimable_funding_amount_for_account_key) == 50, 'wrong value'); + assert(data_store.get_u256(claimable_funding_amount_for_account_key) == 50, 'wrong value'); // The value of the claimable funding amount for the market should now be 50. // Read the value from the data store using the hardcoded key and assert it. - assert(data_store.get_u128(claimable_funding_amount_key) == 50, 'wrong value'); + assert(data_store.get_u256(claimable_funding_amount_key) == 50, 'wrong value'); // ********************************************************************************************* // * TEARDOWN * @@ -545,30 +548,30 @@ fn given_normal_conditions_when_get_pnl_then_works() { let open_interest_key_for_long = keys::open_interest_key( market_token_address, market.long_token, is_long ); - data_store.set_u128(open_interest_key_for_long, 100); + data_store.set_u256(open_interest_key_for_long, 100); // Set open interest for short token. let open_interest_key_for_short = keys::open_interest_key( market_token_address, market.short_token, is_long ); - data_store.set_u128(open_interest_key_for_short, 150); + data_store.set_u256(open_interest_key_for_short, 150); // Set open interest in tokens for long token. let open_interest_in_tokens_key_for_long = keys::open_interest_in_tokens_key( market_token_address, market.long_token, is_long ); - data_store.set_u128(open_interest_in_tokens_key_for_long, 200); + data_store.set_u256(open_interest_in_tokens_key_for_long, 200); // Set open interest in tokens for short token. let open_interest_in_tokens_key_for_short = keys::open_interest_in_tokens_key( market_token_address, market.short_token, is_long ); - data_store.set_u128(open_interest_in_tokens_key_for_short, 250); + data_store.set_u256(open_interest_in_tokens_key_for_short, 250); // Actual test case. let pnl = market_utils::get_pnl(data_store, @market, @price, is_long, maximize); // Perform assertions. - assert(pnl == 22250, 'wrong pnl'); + assert(pnl == i256_new(22250, false), 'wrong pnl'); // ********************************************************************************************* // * TEARDOWN * @@ -617,30 +620,30 @@ fn given_zero_open_interest_when_get_pnl_then_returns_zero_pnl() { let open_interest_key_for_long = keys::open_interest_key( market_token_address, market.long_token, is_long ); - data_store.set_u128(open_interest_key_for_long, 0); + data_store.set_u256(open_interest_key_for_long, 0); // Set open interest for short token. let open_interest_key_for_short = keys::open_interest_key( market_token_address, market.short_token, is_long ); - data_store.set_u128(open_interest_key_for_short, 0); + data_store.set_u256(open_interest_key_for_short, 0); // Set open interest in tokens for long token. let open_interest_in_tokens_key_for_long = keys::open_interest_in_tokens_key( market_token_address, market.long_token, is_long ); - data_store.set_u128(open_interest_in_tokens_key_for_long, 200); + data_store.set_u256(open_interest_in_tokens_key_for_long, 200); // Set open interest in tokens for short token. let open_interest_in_tokens_key_for_short = keys::open_interest_in_tokens_key( market_token_address, market.short_token, is_long ); - data_store.set_u128(open_interest_in_tokens_key_for_short, 250); + data_store.set_u256(open_interest_in_tokens_key_for_short, 250); // Actual test case. let pnl = market_utils::get_pnl(data_store, @market, @price, is_long, maximize); // Perform assertions. - assert(pnl == 0, 'wrong pnl'); + assert(pnl == i256_new(0, false), 'wrong pnl'); // ********************************************************************************************* // * TEARDOWN * @@ -689,30 +692,30 @@ fn given_zero_open_interest_in_tokens_when_get_pnl_then_returns_zero_pnl() { let open_interest_key_for_long = keys::open_interest_key( market_token_address, market.long_token, is_long ); - data_store.set_u128(open_interest_key_for_long, 100); + data_store.set_u256(open_interest_key_for_long, 100); // Set open interest for short token. let open_interest_key_for_short = keys::open_interest_key( market_token_address, market.short_token, is_long ); - data_store.set_u128(open_interest_key_for_short, 200); + data_store.set_u256(open_interest_key_for_short, 200); // Set open interest in tokens for long token. let open_interest_in_tokens_key_for_long = keys::open_interest_in_tokens_key( market_token_address, market.long_token, is_long ); - data_store.set_u128(open_interest_in_tokens_key_for_long, 0); + data_store.set_u256(open_interest_in_tokens_key_for_long, 0); // Set open interest in tokens for short token. let open_interest_in_tokens_key_for_short = keys::open_interest_in_tokens_key( market_token_address, market.short_token, is_long ); - data_store.set_u128(open_interest_in_tokens_key_for_short, 0); + data_store.set_u256(open_interest_in_tokens_key_for_short, 0); // Actual test case. let pnl = market_utils::get_pnl(data_store, @market, @price, is_long, maximize); // Perform assertions. - assert(pnl == 0, 'wrong pnl'); + assert(pnl == i256_new(0, false), 'wrong pnl'); // ********************************************************************************************* // * TEARDOWN * @@ -752,7 +755,7 @@ fn given_normal_conditions_when_get_position_impact_pool_amount_then_works() { let position_impact_pool_amount_key = keys::position_impact_pool_amount_key( market_token_address ); - data_store.set_u128(position_impact_pool_amount_key, 1000); + data_store.set_u256(position_impact_pool_amount_key, 1000); // Actual test case. let position_impact_pool_amount = market_utils::get_position_impact_pool_amount( @@ -802,7 +805,7 @@ fn given_normal_conditions_when_get_swap_impact_pool_amount_then_works() { let swap_impact_pool_amount_key = keys::swap_impact_pool_amount_key( market_token_address, token ); - data_store.set_u128(swap_impact_pool_amount_key, 1000); + data_store.set_u256(swap_impact_pool_amount_key, 1000); // Actual test case. let swap_impact_pool_amount = market_utils::get_swap_impact_pool_amount( @@ -844,13 +847,13 @@ fn given_normal_conditions_when_apply_delta_to_position_impact_pool_then_works() // Define variables for the test case. let market_token_address = contract_address_const::<'market_token'>(); - let delta = 50; + let delta = i256_new(50, false); // Setup pre conditions. // Fill required data store keys. let key = keys::position_impact_pool_amount_key(market_token_address); - data_store.set_u128(key, 1000); + data_store.set_u256(key, 1000); // Actual test case. let next_value = market_utils::apply_delta_to_position_impact_pool( @@ -893,13 +896,13 @@ fn given_normal_conditions_when_apply_delta_to_swap_impact_pool_then_works() { // Define variables for the test case. let market_token_address = contract_address_const::<'market_token'>(); let token = contract_address_const::<'token'>(); - let delta = 50; + let delta = i256_new(50, false); // Setup pre conditions. // Fill required data store keys. let key = keys::swap_impact_pool_amount_key(market_token_address, token); - data_store.set_u128(key, 1000); + data_store.set_u256(key, 1000); // Actual test case. let next_value = market_utils::apply_delta_to_swap_impact_pool( @@ -1064,16 +1067,13 @@ fn setup_contracts() -> ( // Deploy the market factory. let market_factory_address = deploy_market_factory( - data_store_address, - role_store_address, - event_emitter_address, - market_token_class_hash.clone() + data_store_address, role_store_address, event_emitter_address, market_token_class_hash ); // Create a safe dispatcher to interact with the contract. let market_factory = IMarketFactoryDispatcher { contract_address: market_factory_address }; ( - 0x101.try_into().unwrap(), + contract_address_const::<'caller'>(), market_factory_address, role_store_address, data_store_address, @@ -1094,30 +1094,41 @@ fn deploy_market_factory( market_token_class_hash: ContractClass, ) -> ContractAddress { let contract = declare('MarketFactory'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'market_factory'>(); + start_prank(deployed_contract_address, caller_address); let mut constructor_calldata = array![]; constructor_calldata.append(data_store_address.into()); constructor_calldata.append(role_store_address.into()); constructor_calldata.append(event_emitter_address.into()); constructor_calldata.append(market_token_class_hash.class_hash.into()); - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).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() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).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'); - let constructor_arguments: @Array:: = @ArrayTrait::new(); - contract.deploy(constructor_arguments).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() } /// Utility function to deploy a `Chain` contract and return its address. @@ -1127,9 +1138,3 @@ fn deploy_chain() -> ContractAddress { contract.deploy(constructor_arguments).unwrap() } -/// Utility function to deploy a `EventEmitter` contract and return its address. -fn deploy_event_emitter() -> ContractAddress { - let contract = declare('EventEmitter'); - let constructor_arguments: @Array:: = @ArrayTrait::new(); - contract.deploy(constructor_arguments).unwrap() -} diff --git a/tests/mock/test_governable.cairo b/tests/mock/test_governable.cairo new file mode 100644 index 00000000..43285288 --- /dev/null +++ b/tests/mock/test_governable.cairo @@ -0,0 +1,244 @@ +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 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 snforge_std::{declare, start_prank, ContractClassTrait}; + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'referral_storage'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_governable(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('Governable'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'governable'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn setup() -> ( + ContractAddress, + IRoleStoreDispatcher, + IDataStoreDispatcher, + IEventEmitterDispatcher, + IReferralStorageDispatcher, + IGovernableDispatcher +) { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + + 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) +} + +fn setup_with_other_address() -> ( + ContractAddress, + IRoleStoreDispatcher, + IDataStoreDispatcher, + IEventEmitterDispatcher, + IReferralStorageDispatcher, + IGovernableDispatcher +) { + let caller_address: ContractAddress = 0x102.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 + +// 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) = + setup(); + governable.only_gov(); + 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() { + let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = + setup_with_other_address(); + governable.only_gov(); + 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) = + setup(); + let new_caller_address: ContractAddress = 0x102.try_into().unwrap(); + 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); +} diff --git a/tests/mock/test_referral_storage.cairo b/tests/mock/test_referral_storage.cairo new file mode 100644 index 00000000..662e8bb1 --- /dev/null +++ b/tests/mock/test_referral_storage.cairo @@ -0,0 +1,393 @@ +//! 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::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::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); +} + +#[test] +fn given_normal_conditions_when_setting_handler_from_storage_than_work() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + referral_storage.set_handler(caller_address, true); + referral_storage.only_handler(); + + tests_lib::teardown(data_store.contract_address); +} + +#[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); +} + +#[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); +} + +#[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); +} + +#[test] +fn given_normal_conditions_when_setting_and_fetching_tier_from_storage_then_works() { + let (_, _, data_store, _, referral_storage, _) = setup(); + + let tier_id: u256 = 3; + let total_rebate: u256 = 10; + let discount_share: u256 = 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); +} + +#[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 tier_id: u256 = 3; + let total_rebate: u256 = 10001; + let discount_share: u256 = 10; + referral_storage.set_tier(tier_id, total_rebate, discount_share); + + tests_lib::teardown(data_store.contract_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 tier_id: u256 = 3; + let total_rebate: u256 = 10; + let discount_share: u256 = 10001; + referral_storage.set_tier(tier_id, total_rebate, discount_share); + + 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, _, data_store, _, referral_storage, _) = setup(); + + let tier_id: u256 = 3; + referral_storage.set_referrer_tier(caller_address, tier_id); + + let out: u256 = referral_storage.referrer_tiers(caller_address); + assert(out == tier_id, 'out tier_id is wrong'); + + 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, _, data_store, _, referral_storage, _) = setup(); + + let tier_id: u256 = 3; + + let out: u256 = 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: u256 = 1000; + referral_storage.set_referrer_discount_share(discount_share); + + let out: u256 = referral_storage.referrer_discount_shares(caller_address); + assert(out == discount_share, 'out discount_share is wrong'); + + tests_lib::teardown(data_store.contract_address); +} + +#[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: u256 = 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_referrer_referral_code_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + referral_storage.set_handler(caller_address, true); + + let code: felt252 = 'EBDW'; + referral_storage.set_trader_referral_code(caller_address, code); + + let out: felt252 = referral_storage.trader_referral_codes(caller_address); + assert(out == code, 'out code is wrong'); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +#[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(); + + let code: felt252 = 'EBDW'; + referral_storage.set_trader_referral_code_by_user(code); + + let out: felt252 = referral_storage.trader_referral_codes(caller_address); + assert(out == code, 'out code is wrong'); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_registering_code_and_fetching_address_from_storage_then_works() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + let code: felt252 = 'EBDW'; + referral_storage.register_code(code); + + let owner: ContractAddress = referral_storage.code_owners(code); + assert(owner == caller_address, 'out caller_address is wrong'); + + tests_lib::teardown(data_store.contract_address); +} + +#[test] +#[should_panic(expected: ('invalid code',))] +fn given_invalid_code_when_registering_code_from_storage_then_fails() { + let (caller_address, _, data_store, _, referral_storage, _) = setup(); + + 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 (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'); + + 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); +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'referral_storage'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_governable(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('Governable'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'governable'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} diff --git a/src/tests/nonce/test_nonce_utils.cairo b/tests/nonce/test_nonce_utils.cairo similarity index 100% rename from src/tests/nonce/test_nonce_utils.cairo rename to tests/nonce/test_nonce_utils.cairo diff --git a/src/tests/oracle/test_oracle.cairo b/tests/oracle/test_oracle.cairo similarity index 67% rename from src/tests/oracle/test_oracle.cairo rename to tests/oracle/test_oracle.cairo index db5a38bc..b03c14b1 100644 --- a/src/tests/oracle/test_oracle.cairo +++ b/tests/oracle/test_oracle.cairo @@ -1,6 +1,6 @@ use starknet::{ContractAddress, contract_address_const}; -use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait, PrintTrait}; +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; use satoru::data::data_store::{DataStore, IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::data::keys; @@ -12,17 +12,7 @@ use satoru::price::price::Price; use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; use satoru::role::role; use satoru::utils::precision; - -// NOTE: requires oracle_utils to be completed to not panic. -#[test] -#[should_panic()] -fn given_normal_conditions_when_set_prices_then_works() { - let (controller, data_store, event_emitter, oracle) = setup(); - let params = mock_set_prices_params(); - - start_prank(oracle.contract_address, controller); - oracle.set_prices(data_store, event_emitter, params); -} +use satoru::tests_lib; #[test] fn given_normal_conditions_when_set_primary_price_then_works() { @@ -38,6 +28,8 @@ fn given_normal_conditions_when_set_primary_price_then_works() { assert( price_from_view.min == price.min && price_from_view.max == price.max, 'wrong primary price' ); + // teardown + tests_lib::teardown(data_store.contract_address); } #[test] @@ -56,6 +48,8 @@ fn given_normal_conditions_when_clear_all_prices_then_works() { oracle.clear_all_prices(); assert(oracle.get_tokens_with_prices_count() == 0, 'wrong tokens count'); + // teardown + tests_lib::teardown(data_store.contract_address); } #[test] @@ -76,6 +70,8 @@ fn given_normal_conditions_when_tokens_with_prices_count_then_works() { oracle.set_primary_price(token3, price3); assert(oracle.get_tokens_with_prices_count() == 3, 'wrong tokens count'); + // teardown + tests_lib::teardown(data_store.contract_address); } #[test] @@ -115,6 +111,8 @@ fn given_normal_conditions_when_get_tokens_with_prices_then_works() { assert(prices == array![token3], 'wrong prices array 2-3'); let prices = oracle.get_tokens_with_prices(2, 5); assert(prices == array![token3], 'wrong prices array 2-5'); + // teardown + tests_lib::teardown(data_store.contract_address); } #[test] @@ -135,6 +133,8 @@ fn given_normal_conditions_when_get_primary_price_then_works() { assert(is_price_eq(oracle.get_primary_price(token1), price1), 'wrong price token-1'); assert(is_price_eq(oracle.get_primary_price(token2), price2), 'wrong price token-2'); assert(is_price_eq(oracle.get_primary_price(token3), price3), 'wrong price token-3'); + // teardown + tests_lib::teardown(data_store.contract_address); } #[test] @@ -144,28 +144,46 @@ fn given_normal_conditions_when_price_feed_multiplier_then_works() { let token = contract_address_const::<'ETH'>(); oracle.get_price_feed_multiplier(data_store, token); + // teardown + tests_lib::teardown(data_store.contract_address); } -#[test] -fn given_normal_conditions_when_validate_prices_then_works() { - let (controller, data_store, event_emitter, oracle) = setup(); - let params: SetPricesParams = mock_set_prices_params(); - let token1 = contract_address_const::<'ETH'>(); - let price1 = Price { min: 1700, max: 1701 }; - let token2 = contract_address_const::<'USDC'>(); - let price2 = Price { min: 20, max: 22 }; - let token3 = contract_address_const::<'DAI'>(); - let price3 = Price { min: 30, max: 33 }; - - start_prank(oracle.contract_address, controller); - oracle.set_primary_price(token1, price1); - oracle.set_primary_price(token2, price2); - oracle.set_primary_price(token3, price3); - let validated_prices = oracle.validate_prices(data_store, params); -} +// TODO for two next tests: +// Implement with a real signer that simulate keepers and signs Price Data + +// #[test] +// fn given_normal_conditions_when_validate_prices_then_works() { +// let (controller, data_store, event_emitter, oracle) = setup(); +// let params: SetPricesParams = mock_set_prices_params(); +// let token1 = contract_address_const::<'ETH'>(); +// let price1 = Price { min: 1700, max: 1701 }; +// let token2 = contract_address_const::<'USDC'>(); +// let price2 = Price { min: 20, max: 22 }; +// let token3 = contract_address_const::<'DAI'>(); +// let price3 = Price { min: 30, max: 33 }; + +// start_prank(oracle.contract_address, controller); +// oracle.set_primary_price(token1, price1); +// oracle.set_primary_price(token2, price2); +// oracle.set_primary_price(token3, price3); +// let validated_prices = oracle.validate_prices(data_store, params); +// // teardown +// tests_lib::teardown(data_store.contract_address); +// } + +// #[test] +// fn given_normal_conditions_when_set_prices_then_works() { +// let (controller, data_store, event_emitter, oracle) = setup(); +// let params = mock_set_prices_params(); + +// start_prank(oracle.contract_address, controller); +// oracle.set_prices(data_store, event_emitter, params); +// // teardown +// tests_lib::teardown(data_store.contract_address); +// } fn setup() -> (ContractAddress, IDataStoreDispatcher, IEventEmitterDispatcher, IOracleDispatcher) { - let caller_address = contract_address_const::<0x101>(); + let caller_address = contract_address_const::<'caller'>(); let order_keeper = contract_address_const::<0x2233>(); let role_store_address = deploy_role_store(); let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; @@ -184,30 +202,30 @@ fn setup() -> (ContractAddress, IDataStoreDispatcher, IEventEmitterDispatcher, I oracle_store.add_signer(contract_address_const::<'signer'>()); start_prank(data_store_address, caller_address); data_store - .set_u128( + .set_u256( keys::price_feed_multiplier_key(contract_address_const::<'ETH'>()), precision::FLOAT_PRECISION ); data_store - .set_u128( + .set_u256( keys::price_feed_multiplier_key(contract_address_const::<'USDC'>()), precision::FLOAT_PRECISION ); data_store - .set_u128( + .set_u256( keys::price_feed_multiplier_key(contract_address_const::<'DAI'>()), precision::FLOAT_PRECISION ); - data_store.set_u128(keys::max_oracle_ref_price_deviation_factor(), precision::FLOAT_PRECISION); - data_store.set_token_id(contract_address_const::<'ETH'>(), 'ETH/USD'); - data_store.set_token_id(contract_address_const::<'USDC'>(), 'USDC/USD'); - data_store.set_token_id(contract_address_const::<'DAI'>(), 'DAI/USD'); + data_store.set_u256(keys::max_oracle_ref_price_deviation_factor(), precision::FLOAT_PRECISION); (caller_address, data_store, event_emitter, oracle) } fn deploy_price_feed() -> ContractAddress { let contract = declare('PriceFeed'); - contract.deploy(@array![]).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'price_feed'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() } fn deploy_oracle( @@ -216,58 +234,65 @@ fn deploy_oracle( pragma_address: ContractAddress ) -> ContractAddress { let contract = declare('Oracle'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle'>(); + start_prank(deployed_contract_address, caller_address); let constructor_calldata = array![ role_store_address.into(), oracle_store_address.into(), pragma_address.into() ]; - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() } fn deploy_oracle_store( role_store_address: ContractAddress, event_emitter_address: ContractAddress ) -> ContractAddress { let contract = declare('OracleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle_store'>(); + start_prank(deployed_contract_address, caller_address); let constructor_calldata = array![role_store_address.into(), event_emitter_address.into()]; - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() } fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); let constructor_calldata = array![role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() } fn deploy_role_store() -> ContractAddress { let contract = declare('RoleStore'); - contract.deploy(@array![]).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } fn deploy_event_emitter() -> ContractAddress { let contract = declare('EventEmitter'); - contract.deploy(@array![]).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() } fn mock_set_prices_params() -> SetPricesParams { SetPricesParams { signer_info: 1, - tokens: array![ - contract_address_const::<'ETH'>(), - contract_address_const::<'USDC'>(), - contract_address_const::<'DAI'>() - ], - compacted_min_oracle_block_numbers: array![0, 0, 0], - compacted_max_oracle_block_numbers: array![6400, 6400, 6400], - compacted_oracle_timestamps: array![0, 0, 0], + tokens: array![contract_address_const::<'ETH'>(),], + compacted_min_oracle_block_numbers: array![10], + compacted_max_oracle_block_numbers: array![20], + compacted_oracle_timestamps: array![1000], compacted_decimals: array![18, 18, 18], - compacted_min_prices: array![0, 0, 0], - compacted_min_prices_indexes: array![1, 2, 3], - compacted_max_prices: array![0, 0, 0], - compacted_max_prices_indexes: array![1, 2, 3], - signatures: array![1, 2, 3], - price_feed_tokens: array![ - contract_address_const::<'ETH'>(), - contract_address_const::<'USDC'>(), - contract_address_const::<'DAI'>() - ] + compacted_min_prices: array![99999], + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![888888], + compacted_max_prices_indexes: array![0], + signatures: array![array!['signatures1', 'signatures2'].span()], + price_feed_tokens: array![] } } diff --git a/tests/order/test_base_order_utils.cairo b/tests/order/test_base_order_utils.cairo new file mode 100644 index 00000000..61def650 --- /dev/null +++ b/tests/order/test_base_order_utils.cairo @@ -0,0 +1,426 @@ +use starknet::{ContractAddress, contract_address_const}; +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; + +use satoru::order::{order::{OrderType, Order},}; +use satoru::price::price::{Price, PriceTrait}; +use satoru::order::base_order_utils::{ + is_market_order, is_limit_order, is_swap_order, is_position_order, is_increase_order, + is_decrease_order, is_liquidation_order, validate_order_trigger_price, + get_execution_price_for_increase, get_execution_price_for_decrease, validate_non_empty_order +}; + +use satoru::data::data_store::{DataStore, IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::event::event_emitter::{EventEmitter, IEventEmitterDispatcher}; +use satoru::oracle::oracle::{Oracle, IOracleDispatcher, IOracleDispatcherTrait, SetPricesParams}; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::oracle::price_feed::PriceFeed; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::role::role; +use satoru::utils::i256::{i256, i256_new}; + +#[test] +fn given_normal_conditions_when_is_market_order_then_works() { + // Test market orders + assert(is_market_order(OrderType::MarketSwap), 'invalid market swap res'); + assert(is_market_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(is_market_order(OrderType::MarketDecrease), 'invalid market dec. res'); + + // Test other orders + assert(!is_market_order(OrderType::LimitSwap), 'invalid limit swap res'); + assert(!is_market_order(OrderType::LimitIncrease), 'invalid limit inc. res'); + assert(!is_market_order(OrderType::StopLossDecrease), 'invalid stop loss res '); +} + +#[test] +fn given_normal_conditions_when_is_limit_order_then_works() { + // Test limit orders + assert(is_limit_order(OrderType::LimitSwap), 'invalid limit swap res'); + assert(is_limit_order(OrderType::LimitIncrease), 'invalid limit inc. res'); + assert(is_limit_order(OrderType::LimitDecrease), 'invalid limit dec. res'); + + // Test other orders + assert(!is_limit_order(OrderType::MarketSwap), 'invalid market swap res'); + assert(!is_limit_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(!is_limit_order(OrderType::StopLossDecrease), 'invalid stop loss res '); +} + +#[test] +fn given_normal_conditions_when_is_swap_order_then_works() { + // Test swap orders + assert(is_swap_order(OrderType::MarketSwap), 'invalid market swap res'); + assert(is_swap_order(OrderType::LimitSwap), 'invalid limit swap res'); + + // Test other orders + assert(!is_swap_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(!is_swap_order(OrderType::LimitIncrease), 'invalid limit inc. res '); +} + + +#[test] +fn given_normal_conditions_when_is_position_order_then_works() { + // Test position orders + assert(is_position_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(is_position_order(OrderType::LimitIncrease), 'invalid limit inc. res'); + assert(is_position_order(OrderType::StopLossDecrease), 'invalid stop loss res'); + assert(is_position_order(OrderType::Liquidation), 'invalid liquidation res'); + + // Test other orders + assert(!is_position_order(OrderType::LimitSwap), 'invalid limit swap res'); + assert(!is_position_order(OrderType::MarketSwap), 'invalid market swap res '); +} + + +#[test] +fn given_normal_conditions_when_is_increase_order_then_works() { + // Test position orders + assert(is_increase_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(is_increase_order(OrderType::LimitIncrease), 'invalid limit inc. res'); + + // Test other orders + assert(!is_increase_order(OrderType::MarketDecrease), 'invalid market dec. res'); + assert(!is_increase_order(OrderType::MarketSwap), 'invalid market swap res '); +} + +#[test] +fn given_normal_conditions_when_is_decrease_order_then_works() { + // Test position orders + assert(is_decrease_order(OrderType::MarketDecrease), 'invalid market dec. res'); + assert(is_decrease_order(OrderType::LimitDecrease), 'invalid limit dec. res'); + assert(is_decrease_order(OrderType::StopLossDecrease), 'invalid stop loss res'); + assert(is_decrease_order(OrderType::Liquidation), 'invalid liquidation res'); + + // Test other orders + assert(!is_decrease_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(!is_decrease_order(OrderType::LimitIncrease), 'invalid limit inc. res'); +} + +#[test] +fn given_normal_conditions_when_is_liquidation_order_then_works() { + // Test position orders + assert(is_liquidation_order(OrderType::Liquidation), 'invalid liquidation inc. res'); + // Test other orders + assert(!is_liquidation_order(OrderType::MarketDecrease), 'invalid market dec. res'); + assert(!is_liquidation_order(OrderType::MarketSwap), 'invalid market swap res '); +} + + +#[test] +fn given_normal_conditions_when_validate_order_trigger_price_then_works() { + // Setup + let (_, _, _, oracle) = setup(); + let index_token = contract_address_const::<'ETH'>(); + let price = Price { min: 100000, max: 200000 }; + oracle.set_primary_price(index_token, price); + + // Test + + // Test swap orders validates + validate_order_trigger_price(oracle, index_token, OrderType::MarketSwap, 100, true); + + // Test limit increase orders + validate_order_trigger_price( + oracle, index_token, OrderType::LimitIncrease, price.max + 1, true + ); + + validate_order_trigger_price( + oracle, index_token, OrderType::LimitIncrease, price.min - 1, false + ); + + // Test limit decrease orders + validate_order_trigger_price( + oracle, index_token, OrderType::LimitDecrease, price.min - 1, true + ); + + validate_order_trigger_price( + oracle, index_token, OrderType::LimitDecrease, price.max + 1, false + ); + + // Test stop loss orders + validate_order_trigger_price( + oracle, index_token, OrderType::StopLossDecrease, price.min + 1, true + ); + + validate_order_trigger_price( + oracle, index_token, OrderType::StopLossDecrease, price.max - 1, false + ); + + assert(true, 'e'); +} + +#[test] +#[should_panic( + expected: ('invalid_order_price', 100000, 200000, 199999, 6053968548023263173723725853541) +)] +fn given_limit_increase_price_higher_than_trigger_when_validate_order_trigger_price_then_fails() { + // Setup + let (_, _, _, oracle) = setup(); + let index_token = contract_address_const::<'ETH'>(); + let price = Price { min: 100000, max: 200000 }; + oracle.set_primary_price(index_token, price); + + // Test + validate_order_trigger_price( + oracle, index_token, OrderType::LimitIncrease, price.max - 1, true + ); +} + + +#[test] +#[should_panic( + expected: ('invalid_order_price', 100000, 200000, 199999, 6053968548022900352478745817957) +)] +fn given_limit_decrease_price_lower_than_trigger_when_validate_order_trigger_price_then_fails() { + // Setup + let (_, _, _, oracle) = setup(); + let index_token = contract_address_const::<'ETH'>(); + let price = Price { min: 100000, max: 200000 }; + oracle.set_primary_price(index_token, price); + + // Test + validate_order_trigger_price( + oracle, index_token, OrderType::LimitDecrease, price.max - 1, false + ); +} + +#[test] +#[should_panic( + expected: ( + 'invalid_order_price', 100000, 200000, 99999, 110930490330413861099797394456752255845 + ) +)] +fn given_stop_loss_price_lower_than_trigger_when_validate_order_trigger_price_then_fails() { + // Setup + let (_, _, _, oracle) = setup(); + let index_token = contract_address_const::<'ETH'>(); + let price = Price { min: 100000, max: 200000 }; + oracle.set_primary_price(index_token, price); + + // Test + validate_order_trigger_price( + oracle, index_token, OrderType::StopLossDecrease, price.min - 1, true + ); +} + + +#[test] +fn given_normal_conditions_when_get_execution_price_for_increase_then_works() { + let price = get_execution_price_for_increase( + size_delta_usd: 200, size_delta_in_tokens: 20, acceptable_price: 10, is_long: true, + ); + assert(price == 10, 'invalid price'); + + let price = get_execution_price_for_increase( + size_delta_usd: 400, size_delta_in_tokens: 10, acceptable_price: 20, is_long: false, + ); + assert(price == 40, 'invalid price2'); +} + + +#[test] +#[should_panic(expected: ('order_unfulfillable_at_price', 500, 10))] +fn given_order_not_fullfillable_when_get_execution_price_for_increase_then_fails() { + let price = get_execution_price_for_increase( + size_delta_usd: 5000, size_delta_in_tokens: 10, acceptable_price: 10, is_long: true, + ); +} + + +#[test] +fn given_normal_conditions_when_get_execution_price_for_decrease_then_works() { + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 9, max: 11 }, + position_size_in_usd: 1000, + position_size_in_tokens: 100, + size_delta_usd: 200, + price_impact_usd: i256_new(1, false), + acceptable_price: 8, + is_long: true, + ); + assert(price == 9, 'invalid price1'); + + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 1000, max: 1100 }, + position_size_in_usd: 200000000, + position_size_in_tokens: 30000, + size_delta_usd: 50000, + price_impact_usd: i256_new(15, false), + acceptable_price: 1001, + is_long: true, + ); + assert(price == 1002, 'invalid price2'); + + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 1000, max: 1100 }, + position_size_in_usd: 200000000, + position_size_in_tokens: 30000, + size_delta_usd: 50000, + price_impact_usd: i256_new(15, false), + acceptable_price: 1100, + is_long: false, + ); + assert(price == 1098, 'invalid price'); +} + + +#[test] +#[should_panic( + expected: ( + 'price_impact_too_large', + 3618502788666131213697322783095070105623107215331596699973092056135872020466, + 1 + ) +)] +fn given_price_impact_larger_than_order_when_get_execution_price_for_decrease_then_fails() { + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 1000, max: 1100 }, + position_size_in_usd: 200000000, + position_size_in_tokens: 30000, + size_delta_usd: 1, + price_impact_usd: i256_new(15, false), + acceptable_price: 1100, + is_long: false, + ); +} + +#[test] +#[should_panic( + expected: ( + 'negative_execution_price', + 3618502788666131213697322783095070105623107215331596699973092056135872020480, + 1, + 200000000, + 3618502788666131213697322783095070105623107215331596699973092056135872020466, + 50000 + ) +)] +fn given_negative_execution_price_than_order_when_get_execution_price_for_decrease_then_fails() { + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 1, max: 1 }, + position_size_in_usd: 200000000, + position_size_in_tokens: 30000, + size_delta_usd: 50000, + price_impact_usd: i256_new(15, false), + acceptable_price: 1100, + is_long: false, + ); +} + + +#[test] +#[should_panic(expected: ('order_unfulfillable_at_price', 1002, 10000,))] +fn given_not_acceptable_price_when_get_execution_price_for_decrease_then_fails() { + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 1000, max: 1100 }, + position_size_in_usd: 200000000, + position_size_in_tokens: 30000, + size_delta_usd: 50000, + price_impact_usd: i256_new(15, false), + acceptable_price: 10000, + is_long: true, + ); +} + + +#[test] +fn given_normal_conditions_when_validate_non_empty_order_then_works() { + let mut order: Order = Default::default(); + order.account = 32.try_into().unwrap(); + order.size_delta_usd = 1; + order.initial_collateral_delta_amount = 1; + validate_non_empty_order(@order); +} + +#[test] +#[should_panic(expected: ('empty_order',))] +fn given_empty_order_when_validate_non_empty_order_then_fails() { + let order: Order = Default::default(); + validate_non_empty_order(@order); +} + + +// ********************************************************************************************* +// * SETUP * +// ********************************************************************************************* + +fn setup() -> (ContractAddress, IDataStoreDispatcher, IEventEmitterDispatcher, IOracleDispatcher) { + let caller_address = contract_address_const::<'caller'>(); + let order_keeper = contract_address_const::<0x2233>(); + let role_store_address = deploy_role_store(); + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + let data_store_address = deploy_data_store(role_store_address); + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + let event_emitter_address = deploy_event_emitter(); + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); + let oracle_store = IOracleStoreDispatcher { contract_address: oracle_store_address }; + let pragma_address = deploy_price_feed(); + let oracle_address = deploy_oracle(oracle_store_address, role_store_address, pragma_address); + let oracle = IOracleDispatcher { contract_address: oracle_address }; + start_prank(role_store_address, caller_address); + role_store.grant_role(caller_address, role::CONTROLLER); + role_store.grant_role(order_keeper, role::ORDER_KEEPER); + oracle_store.add_signer(contract_address_const::<'signer'>()); + start_prank(data_store_address, caller_address); + start_prank(oracle_address, caller_address); + + (caller_address, data_store, event_emitter, oracle) +} + +fn deploy_price_feed() -> ContractAddress { + let contract = declare('PriceFeed'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'price_feed'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_oracle( + oracle_store_address: ContractAddress, + role_store_address: ContractAddress, + pragma_address: ContractAddress +) -> ContractAddress { + let contract = declare('Oracle'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![ + role_store_address.into(), oracle_store_address.into(), pragma_address.into() + ]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_oracle_store( + role_store_address: ContractAddress, event_emitter_address: ContractAddress +) -> ContractAddress { + let contract = declare('OracleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'oracle_store'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into(), event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} diff --git a/tests/order/test_increase_order_utils.cairo b/tests/order/test_increase_order_utils.cairo new file mode 100644 index 00000000..c905a61c --- /dev/null +++ b/tests/order/test_increase_order_utils.cairo @@ -0,0 +1,72 @@ +use starknet::ContractAddress; +use snforge_std::{start_mock_call, stop_mock_call}; + +use satoru::data::data_store::IDataStoreDispatcherTrait; +use satoru::nonce::nonce_utils::{get_current_nonce, increment_nonce, compute_key}; +use satoru::tests_lib::{setup, teardown}; +use satoru::oracle::oracle::{IOracleSafeDispatcher, IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::order::{ + error::OrderError, order::{Order, SecondaryOrderType, OrderType, DecreasePositionSwapType}, +}; +use satoru::order::increase_order_utils::{validate_oracle_block_numbers}; + +// TODO - Add tests for process_order + +#[test] +fn given_normal_conditions_when_validate_oracle_block_numbers_then_works() { + // Given + let min_oracle_block_numbers = array![1, 2, 3, 4].span(); + let max_oracle_block_numbers = array![6, 7, 8, 9].span(); + let order_type = OrderType::MarketIncrease; + let order_updated_at_block = 5; + + // When + validate_oracle_block_numbers( + min_oracle_block_numbers, max_oracle_block_numbers, order_type, order_updated_at_block, + ); +} + +#[test] +#[should_panic(expected: ('block numbers too small', 5, 0, 1, 2, 3, 4, 2))] +fn given_smaller_oracle_block_numbers_when_validate_oracle_block_numbers_then_throw_error() { + // Given + let min_oracle_block_numbers = array![0, 1, 2, 3, 4].span(); + let max_oracle_block_numbers = array![6, 7, 8, 9, 10].span(); + let order_type = OrderType::LimitIncrease; + let order_updated_at_block = 2; + + // When + validate_oracle_block_numbers( + min_oracle_block_numbers, max_oracle_block_numbers, order_type, order_updated_at_block, + ); +} + +#[test] +#[should_panic(expected: ('block number not in range', 5, 0, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 5))] +fn given_not_within_range_block_number_when_validate_oracle_block_numbers_then_throw_error() { + // Given + let min_oracle_block_numbers = array![0, 1, 2, 3, 4].span(); + let max_oracle_block_numbers = array![4, 5, 6, 7, 8].span(); + let order_type = OrderType::MarketIncrease; + let order_updated_at_block = 5; + + // When + validate_oracle_block_numbers( + min_oracle_block_numbers, max_oracle_block_numbers, order_type, order_updated_at_block, + ); +} + +#[test] +#[should_panic(expected: ('unsupported_order_type',))] +fn given_unsupported_order_type_when_validate_oracle_block_numbers_then_throw_error() { + // Given + let min_oracle_block_numbers = array![].span(); + let max_oracle_block_numbers = array![].span(); + let order_type = OrderType::MarketSwap; + let order_updated_at_block = 0; + + // When + validate_oracle_block_numbers( + min_oracle_block_numbers, max_oracle_block_numbers, order_type, order_updated_at_block, + ); +} diff --git a/src/tests/order/test_order.cairo b/tests/order/test_order.cairo similarity index 99% rename from src/tests/order/test_order.cairo rename to tests/order/test_order.cairo index 72491c82..f2dfae97 100644 --- a/src/tests/order/test_order.cairo +++ b/tests/order/test_order.cairo @@ -10,7 +10,6 @@ use starknet::{ ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, ClassHash, }; -use debug::PrintTrait; use snforge_std::{declare, ContractClassTrait, start_roll}; diff --git a/tests/order/test_order_vault.cairo b/tests/order/test_order_vault.cairo new file mode 100644 index 00000000..685dccc1 --- /dev/null +++ b/tests/order/test_order_vault.cairo @@ -0,0 +1,24 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +use result::ResultTrait; +use starknet::{ContractAddress, get_caller_address, contract_address_const, ClassHash}; +use snforge_std::{declare, ContractClassTrait, start_roll}; + +// TODO test when StrictBank functions will be implemented. + +// Local imports. +use satoru::utils::span32::{Span32, Array32Trait}; + +#[test] +fn given_normal_conditions_when_transfer_out_then_expect_balance_change() { // TODO +} + +/// Utility function to setup the test environment. +fn setup() -> (ContractAddress, IChainDispatcher,) {} + +/// Utility function to teardown the test environment. +fn teardown() {} diff --git a/tests/position/test_decrease_position_collateral_utils.cairo b/tests/position/test_decrease_position_collateral_utils.cairo new file mode 100644 index 00000000..1a5ec0c1 --- /dev/null +++ b/tests/position/test_decrease_position_collateral_utils.cairo @@ -0,0 +1,240 @@ +// Core lib imports. +use array::ArrayTrait; +use core::traits::{Into, TryInto}; +use snforge_std::{declare, ContractClassTrait, start_prank}; +use starknet::{ContractAddress, contract_address_const}; + +// Local imports. +use satoru::data::{data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}, keys}; +use satoru::event::event_emitter::IEventEmitterDispatcher; +use satoru::market::{market::Market, market_utils::MarketPrices}; +use satoru::mock::referral_storage::IReferralStorageDispatcher; +use satoru::oracle::oracle::IOracleDispatcher; +use satoru::order::{ + order::{DecreasePositionSwapType, Order, OrderType, SecondaryOrderType}, + base_order_utils::{ExecuteOrderParams, ExecuteOrderParamsContracts}, + order_vault::IOrderVaultDispatcher +}; +use satoru::position::{ + position_utils::{UpdatePositionParams, DecreasePositionCache, DecreasePositionCollateralValues}, + position::Position, decrease_position_collateral_utils +}; +use satoru::price::price::Price; +use satoru::swap::swap_handler::ISwapHandlerDispatcher; +use satoru::tests_lib::{setup, teardown, setup_event_emitter}; +use satoru::utils::span32::{Span32, Array32Trait}; + +/// Utility function to deploy a `SwapHandler` contract and return its dispatcher. +fn deploy_swap_handler_address(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('SwapHandler'); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + +fn deploy_token() -> ContractAddress { + let contract = declare('ERC20'); + let constructor_calldata = array!['Test', 'TST', 1000000, 0, 0x101]; + contract.deploy(@constructor_calldata).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() +} + +#[test] +fn given_good_params_when_process_collateral_then_succeed() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + let long_token_address = deploy_token(); + + // setting open_interest to 10_000 to allow decreasing position. + data_store + .set_u256( + keys::open_interest_key( + contract_address_const::<'market_token'>(), long_token_address, true + ), + 10_000 + ); + let swap_handler_address = deploy_swap_handler_address(role_store.contract_address); + let swap_handler = ISwapHandlerDispatcher { contract_address: swap_handler_address }; + let referral_storage_address = deploy_referral_storage(event_emitter_address); + + let params = create_new_update_position_params( + DecreasePositionSwapType::SwapCollateralTokenToPnlToken, + swap_handler, + data_store.contract_address, + event_emitter_address, + referral_storage_address, + long_token_address, + ); + + let values = create_new_decrease_position_cache(long_token_address); + + // + // Execution + // + let result = decrease_position_collateral_utils::process_collateral( + event_emitter, params, values + ); + + // Checks + let open_interest = data_store + .get_u256( + keys::open_interest_key( + contract_address_const::<'market_token'>(), long_token_address, true + ), + ); +} + +#[test] +fn given_good_params_get_execution_price_then_succeed() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + let long_token_address = deploy_token(); + + // setting open_interest to 10_000 to allow decreasing position. + data_store + .set_u256( + keys::open_interest_key( + contract_address_const::<'market_token'>(), long_token_address, true + ), + 10_000 + ); + let swap_handler_address = deploy_swap_handler_address(role_store.contract_address); + let swap_handler = ISwapHandlerDispatcher { contract_address: swap_handler_address }; + let referral_storage_address = deploy_referral_storage(event_emitter_address); + + let params = create_new_update_position_params( + DecreasePositionSwapType::SwapCollateralTokenToPnlToken, + swap_handler, + data_store.contract_address, + event_emitter_address, + referral_storage_address, + long_token_address + ); + + // + // Execution + // + let (_, _, execution_price) = decrease_position_collateral_utils::get_execution_price( + params, Price { min: 10, max: 10 } + ); + // + // Checks + // + assert(execution_price > 0, 'no execution price'); + teardown(data_store.contract_address); +} + +/// Utility function to create new UpdatePositionParams struct +fn create_new_update_position_params( + decrease_position_swap_type: DecreasePositionSwapType, + swap_handler: ISwapHandlerDispatcher, + data_store_address: ContractAddress, + event_emitter_address: ContractAddress, + referral_storage_address: ContractAddress, + long_token_address: ContractAddress +) -> UpdatePositionParams { + let order_vault = contract_address_const::<'order_vault'>(); + let oracle = contract_address_const::<'oracle'>(); + let contracts = ExecuteOrderParamsContracts { + data_store: IDataStoreDispatcher { contract_address: data_store_address }, + event_emitter: IEventEmitterDispatcher { contract_address: event_emitter_address }, + order_vault: IOrderVaultDispatcher { contract_address: order_vault }, + oracle: IOracleDispatcher { contract_address: oracle }, + swap_handler, + referral_storage: IReferralStorageDispatcher { contract_address: referral_storage_address } + }; + + let market = Market { + market_token: contract_address_const::<'market_token'>(), + index_token: long_token_address, + long_token: long_token_address, + short_token: contract_address_const::<'short_token'>() + }; + + let order = Order { + key: 123456789, + order_type: OrderType::StopLossDecrease, + decrease_position_swap_type, + account: contract_address_const::<'account'>(), + receiver: contract_address_const::<'receiver'>(), + callback_contract: contract_address_const::<'callback_contract'>(), + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + market: contract_address_const::<'market'>(), + initial_collateral_token: contract_address_const::<'token1'>(), + swap_path: array![ + contract_address_const::<'swap_path_0'>(), contract_address_const::<'swap_path_1'>() + ] + .span32(), + size_delta_usd: 1000, + initial_collateral_delta_amount: 1000, + trigger_price: 11111, + acceptable_price: 11111, + execution_fee: 10, + callback_gas_limit: 300000, + min_output_amount: 10, + updated_at_block: 1, + is_long: true, + is_frozen: false + }; + + let position = Position { + key: 123456789, + account: contract_address_const::<'account'>(), + market: contract_address_const::<'market'>(), + collateral_token: contract_address_const::<'collateral_token'>(), + size_in_usd: 1000, + size_in_tokens: 1000, + collateral_amount: 1000, + borrowing_factor: 1, + funding_fee_amount_per_size: 1, + long_token_claimable_funding_amount_per_size: 10, + short_token_claimable_funding_amount_per_size: 10, + increased_at_block: 1, + decreased_at_block: 3, + is_long: false, + }; + + let params = UpdatePositionParams { + contracts, + market, + order, + order_key: 123456789, + position, + position_key: 123456789, + secondary_order_type: SecondaryOrderType::None + }; + + params +} + +/// Utility function to create new DecreasePositionCache struct +fn create_new_decrease_position_cache( + long_token_address: ContractAddress +) -> DecreasePositionCache { + let price = Price { min: 1, max: 1 }; + DecreasePositionCache { + prices: MarketPrices { + index_token_price: price, long_token_price: price, short_token_price: price, + }, + estimated_position_pnl_usd: 100, + estimated_realized_pnl_usd: 0, + estimated_remaining_pnl_usd: 100, + pnl_token: long_token_address, + pnl_token_price: price, + collateral_token_price: price, + initial_collateral_amount: 100, + next_position_size_in_usd: 500, + next_position_borrowing_factor: 100000, + } +} diff --git a/src/tests/position/test_decrease_position_swap_utils.cairo b/tests/position/test_decrease_position_swap_utils.cairo similarity index 84% rename from src/tests/position/test_decrease_position_swap_utils.cairo rename to tests/position/test_decrease_position_swap_utils.cairo index 456f3c5a..ba92e0db 100644 --- a/src/tests/position/test_decrease_position_swap_utils.cairo +++ b/tests/position/test_decrease_position_swap_utils.cairo @@ -26,22 +26,36 @@ use satoru::utils::span32::{Span32, Array32Trait}; use snforge_std::{declare, ContractClassTrait, start_prank}; use starknet::{get_caller_address, ContractAddress, contract_address_const}; use array::ArrayTrait; +use satoru::utils::i256::{i256, i256_new}; //TODO Tests need to be added after implementation of decrease_position_swap_utils /// Utility function to deploy a `SwapHandler` contract and return its dispatcher. -fn deploy_swap_handler_address(role_store_address: ContractAddress) -> ContractAddress { +fn deploy_swap_handler_address( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { let contract = declare('SwapHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'swap_handler'>(); + start_prank(deployed_contract_address, caller_address); let constructor_calldata = array![role_store_address.into()]; - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).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() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } +/// 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() +} /// Utility function to setup the test environment. /// @@ -51,12 +65,15 @@ fn deploy_role_store() -> ContractAddress { /// * `IRoleStoreDispatcher` - The role store dispatcher. /// * `ISwapHandlerDispatcher` - The swap handler dispatcher. fn setup() -> (ContractAddress, IRoleStoreDispatcher, ISwapHandlerDispatcher) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); let role_store_address = deploy_role_store(); let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; - let swap_handler_address = deploy_swap_handler_address(role_store_address); + let data_store_address = deploy_data_store(role_store_address); + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + + let swap_handler_address = deploy_swap_handler_address(role_store_address, data_store_address); let swap_handler = ISwapHandlerDispatcher { contract_address: swap_handler_address }; start_prank(role_store_address, caller_address); @@ -93,7 +110,7 @@ fn given_unauthorized_access_role_when_swap_to_pnl_token_then_fails() { decrease_position_swap_utils::swap_withdrawn_collateral_to_pnl_token(params, values); } -// TODO: uncomment after implementation of i128 and all function required by swap() +// TODO: uncomment after implementation of i256 and all function required by swap() // #[test] // fn given_normal_conditions_when_swap_to_pnl_token_then_works() { // let (caller_address, role_store, swap_handler) = setup(); @@ -210,10 +227,10 @@ fn create_new_decrease_position_collateral_values( let value = DecreasePositionCollateralValues { execution_price: 10, remaining_collateral_amount: 1000, - base_pnl_usd: 10, - uncapped_base_pnl_usd: 10, + base_pnl_usd: i256_new(10, false), + uncapped_base_pnl_usd: i256_new(10, false), size_delta_in_tokens: 1000, - price_impact_usd: 1000, + price_impact_usd: i256_new(1000, false), price_impact_diff_usd: 500, output }; diff --git a/tests/position/test_decrease_position_utils.cairo b/tests/position/test_decrease_position_utils.cairo new file mode 100644 index 00000000..bf9f23fb --- /dev/null +++ b/tests/position/test_decrease_position_utils.cairo @@ -0,0 +1,196 @@ +use starknet::{get_caller_address, ContractAddress, contract_address_const}; +use core::array::ArrayTrait; +use core::traits::Into; + +use snforge_std::{declare, ContractClassTrait, start_prank}; +use satoru::tests_lib::{ + teardown, deploy_role_store, deploy_swap_handler_address, deploy_data_store +}; +use satoru::utils::span32::{Span32, Array32Trait}; + +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::role::{role, role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}}; +use satoru::market::market::Market; +use satoru::order::{ + order::{Order, SecondaryOrderType, OrderType, DecreasePositionSwapType}, + order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait}, + base_order_utils::ExecuteOrderParamsContracts +}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; + +use satoru::position::{ + position::Position, decrease_position_utils, position_utils::UpdatePositionParams +}; + + +#[test] +fn given_normal_conditions_when_partially_decrease_position() { + let (caller_address, swap_handler) = setup(); + + let mut params = create_new_update_position_params(OrderType::LimitSwap, swap_handler); + params.order.size_delta_usd = 800; + // TODO: implement update params to not be + //decrease_position_utils::decrease_position(ref params); + assert(true, 'Not implemented yet'); +} + +#[test] +fn given_normal_conditions_when_totally_decrease_position() { + let (caller_address, swap_handler) = setup(); + + let mut params = create_new_update_position_params(OrderType::LimitSwap, swap_handler); + + // TODO: implement update params to not be + //decrease_position_utils::decrease_position(ref params); + assert(true, 'Not implemented yet'); +} +#[test] +#[should_panic] +fn given_invalid_decrease_order_size_when_decrease_position_then_fails() { + let (caller_address, swap_handler) = setup(); + + let mut params = create_new_update_position_params(OrderType::LimitSwap, swap_handler); + params.order.size_delta_usd = 1500; + + // TODO: implement update params to not be + //decrease_position_utils::decrease_position(ref params); + panic(array!['Not implemented yet']); +} + +#[test] +#[should_panic] +fn given_unable_to_withdraw_collateral_when_decrease_position_then_fails() { + let (caller_address, swap_handler) = setup(); + + let mut params = create_new_update_position_params(OrderType::LimitDecrease, swap_handler); + params.order.size_delta_usd = 1000; + params.position.collateral_amount = 1000; + // TODO: implement update params to not be + //decrease_position_utils::decrease_position(ref params); + panic(array!['Not implemented yet']); +} + +#[test] +#[should_panic] +fn given_position_should_be_liquidated_when_decrease_position_then_fails() { + let (caller_address, swap_handler) = setup(); + + let mut params = create_new_update_position_params(OrderType::Liquidation, swap_handler); + params.order.size_delta_usd = 800; + + // TODO: implement update params to not be + //decrease_position_utils::decrease_position(ref params); + panic(array!['Not implemented yet']); +} + + +/// Utility function to setup the test environment. +/// +/// # Returns +/// +/// * `ContractAddress` - The address of the caller. +/// * `IRoleStoreDispatcher` - The role store dispatcher. +/// * `ISwapHandlerDispatcher` - The swap handler dispatcher. +fn setup() -> (ContractAddress, ISwapHandlerDispatcher) { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + + let role_store_address = deploy_role_store(); + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + + let data_store_address = deploy_data_store(role_store_address); + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + + let swap_handler_address = deploy_swap_handler_address(role_store_address, data_store_address); + let swap_handler = ISwapHandlerDispatcher { contract_address: swap_handler_address }; + + start_prank(role_store_address, caller_address); + start_prank(swap_handler_address, caller_address); + + (caller_address, swap_handler) +} + + +/// Utility function to create new UpdatePositionParams struct +fn create_new_update_position_params( + order_type: OrderType, swap_handler: ISwapHandlerDispatcher +) -> UpdatePositionParams { + let data_store = contract_address_const::<'data_store'>(); + let event_emitter = contract_address_const::<'event_emitter'>(); + let order_vault = contract_address_const::<'order_vault'>(); + let oracle = contract_address_const::<'oracle'>(); + let referral_storage = contract_address_const::<'referral_storage'>(); + + let contracts = ExecuteOrderParamsContracts { + data_store: IDataStoreDispatcher { contract_address: data_store }, + event_emitter: IEventEmitterDispatcher { contract_address: event_emitter }, + order_vault: IOrderVaultDispatcher { contract_address: order_vault }, + oracle: IOracleDispatcher { contract_address: oracle }, + swap_handler, + referral_storage: IReferralStorageDispatcher { contract_address: referral_storage } + }; + + let market = Market { + market_token: contract_address_const::<'market_token'>(), + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>() + }; + + let order = Order { + key: 123456789, + order_type, + decrease_position_swap_type: DecreasePositionSwapType::SwapCollateralTokenToPnlToken, + account: contract_address_const::<'account'>(), + receiver: contract_address_const::<'receiver'>(), + callback_contract: contract_address_const::<'callback_contract'>(), + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + market: contract_address_const::<'market'>(), + initial_collateral_token: contract_address_const::<'token1'>(), + swap_path: array![ + contract_address_const::<'swap_path_0'>(), contract_address_const::<'swap_path_1'>() + ] + .span32(), + size_delta_usd: 1000, + initial_collateral_delta_amount: 1000, + trigger_price: 11111, + acceptable_price: 11111, + execution_fee: 10, + callback_gas_limit: 300000, + min_output_amount: 10, + updated_at_block: 1, + is_long: false, + is_frozen: false + }; + + let position = Position { + key: 123456789, + account: contract_address_const::<'account'>(), + market: contract_address_const::<'market'>(), + collateral_token: contract_address_const::<'collateral_token'>(), + size_in_usd: 1000, + size_in_tokens: 1000, + collateral_amount: 10000, + borrowing_factor: 10, + funding_fee_amount_per_size: 10, + long_token_claimable_funding_amount_per_size: 10, + short_token_claimable_funding_amount_per_size: 10, + increased_at_block: 1, + decreased_at_block: 3, + is_long: false, + }; + + let params = UpdatePositionParams { + contracts, + market, + order, + order_key: 123456789, + position, + position_key: 123456789, + secondary_order_type: SecondaryOrderType::None + }; + + params +} diff --git a/tests/position/test_position_utils.cairo b/tests/position/test_position_utils.cairo new file mode 100644 index 00000000..e80e802b --- /dev/null +++ b/tests/position/test_position_utils.cairo @@ -0,0 +1,753 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +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, start_warp, ContractClassTrait, ContractClass}; +use poseidon::poseidon_hash_span; +use zeroable::Zeroable; +// Local imports. +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::chain::chain::{IChainDispatcher, IChainDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::market::market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait}; +use satoru::market::market::{Market, UniqueIdMarket, IntoMarketToken}; +use satoru::market::{market_utils::MarketPrices}; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::data::keys; +use satoru::role::role; +use satoru::price::price::{Price, PriceTrait}; +use satoru::position::{ + position::Position, position_utils::UpdatePositionParams, + position_utils::WillPositionCollateralBeSufficientValues, position_utils +}; +use satoru::tests_lib::{setup, setup_event_emitter, teardown}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::pricing::position_pricing_utils::{PositionFees, PositionReferralFees}; +use satoru::order::{ + order::{Order, SecondaryOrderType, OrderType, DecreasePositionSwapType}, + order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait} +}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::order::base_order_utils::ExecuteOrderParamsContracts; +use satoru::utils::i256::{i256, i256_new}; +#[test] +fn given_normal_conditions_when_get_position_key_then_works() { + // + // Setup + // + let account: ContractAddress = contract_address_const::<'account'>(); + let market: ContractAddress = contract_address_const::<'market'>(); + let token: ContractAddress = contract_address_const::<'token'>(); + let mut data = array![account.into(), market.into(), token.into(), false.into()]; + let mut data2 = array![account.into(), market.into(), token.into(), true.into()]; + let key_1 = poseidon_hash_span(data.span()); + let key_2 = poseidon_hash_span(data2.span()); + + // Test + let retrieved_key1 = position_utils::get_position_key(account, market, token, false); + let retrieved_key2 = position_utils::get_position_key(account, market, token, true); + assert(key_1 == retrieved_key1, 'invalid key1'); + assert(key_2 == retrieved_key2, 'invalid key2'); +} + + +#[test] +#[should_panic(expected: ('empty_position',))] +fn given_empty_position_when_validate_non_empty_position_then_fails() { + // + // Setup + // + let position: Position = Default::default(); + + // Test + position_utils::validate_non_empty_position(position); +} + +#[test] +fn given_normal_conditions_when_validate_non_empty_position_then_works() { + // + // Setup + // + + let mut position: Position = Default::default(); + position.size_in_tokens = 123; + + // Test + + position_utils::validate_non_empty_position(position); + + position.size_in_tokens = 0; + position.collateral_amount = 123; + + position_utils::validate_non_empty_position(position); +} + +#[test] +#[should_panic(expected: ('invalid_position_size_values',))] +fn given_invalid_position_size_when_validate_position_then_fails() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + + let referral_storage = IReferralStorageDispatcher { + contract_address: contract_address_const::<'12345'>() + }; + + let position: Position = Default::default(); + let market: Market = Default::default(); + let price = Price { min: 0, max: 0 }; + let prices: MarketPrices = MarketPrices { + index_token_price: price, long_token_price: price, short_token_price: price + }; + // Test + position_utils::validate_position( + data_store, referral_storage, position, market, prices, false, false + ); + teardown(data_store.contract_address); +} + +#[test] +#[should_panic(expected: ('empty_market',))] +fn given_empty_market_when_validate_position_then_fails() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + + let referral_storage = IReferralStorageDispatcher { + contract_address: contract_address_const::<'12345'>() + }; + + let mut position: Position = Default::default(); + // Set valid pos size valeus + position.size_in_usd = 100; + position.size_in_tokens = 10; + + let market: Market = Default::default(); + let price = Price { min: 0, max: 0 }; + let prices: MarketPrices = MarketPrices { + index_token_price: price, long_token_price: price, short_token_price: price + }; + + // Test + // Should fail at 'validate_enabled_market' + position_utils::validate_position( + data_store, referral_storage, position, market, prices, false, false + ); + teardown(data_store.contract_address); +} + + +#[test] +#[should_panic(expected: ('minimum_position_size',))] +fn given_minimum_position_size_when_validate_position_then_fails() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + + let referral_storage = IReferralStorageDispatcher { + contract_address: contract_address_const::<'12345'>() + }; + let token: ContractAddress = contract_address_const::<'token'>(); + + let mut position: Position = Default::default(); + let mut market: Market = Default::default(); + // Set valid pos size valeus + position.size_in_usd = 100; + position.size_in_tokens = 10; + + // Set valid market colleteral tokens (positon.collateral_token == market.long_token || token == market.short_token;) + position.collateral_token = token; + market.long_token = token; + market.market_token = contract_address_const::<'market_token'>(); + + let price = Price { min: 0, max: 0 }; + let prices: MarketPrices = MarketPrices { + index_token_price: price, long_token_price: price, short_token_price: price + }; + let should_validate_min_position_size = true; + + // Test + + let min_size: u256 = 1000000; + data_store.set_u256(keys::min_position_size_usd(), min_size); + data_store.set_bool(keys::is_market_disabled_key(market.market_token), false); + // Check key assigned + let retrieved_size = data_store.get_u256(keys::min_position_size_usd()); + assert(retrieved_size == min_size, 'invalid key assignment'); + + // Fail + position_utils::validate_position( + data_store, + referral_storage, + position, + market, + prices, + should_validate_min_position_size, + false + ); + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_increment_claimable_funding_amount_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + let account: ContractAddress = contract_address_const::<'account'>(); + let long_token_amount: u256 = 10000; + let short_token_amount: u256 = 20000; + + let mut fees: PositionFees = Default::default(); + + let mut params: position_utils::UpdatePositionParams = UpdatePositionParams { + contracts: ExecuteOrderParamsContracts { + data_store, + event_emitter, + order_vault: IOrderVaultDispatcher { contract_address: Zeroable::zero() }, + oracle: IOracleDispatcher { contract_address: Zeroable::zero() }, + swap_handler: ISwapHandlerDispatcher { contract_address: Zeroable::zero() }, + referral_storage: IReferralStorageDispatcher { contract_address: Zeroable::zero() }, + }, + market: Market { market_token, index_token: Zeroable::zero(), long_token, short_token, }, + order: Default::default(), + order_key: 0, + position: Default::default(), + position_key: 0, + secondary_order_type: SecondaryOrderType::None, + }; + + params.order.account = account; + fees.funding.claimable_long_token_amount = long_token_amount; + fees.funding.claimable_short_token_amount = short_token_amount; + + // Test + + position_utils::increment_claimable_funding_amount(params, fees,); + + let claimable_fund_long_key = keys::claimable_funding_amount_by_account_key( + market_token, long_token, account + ); + let claimable_fund_short_key = keys::claimable_funding_amount_by_account_key( + market_token, short_token, account + ); + + // Check funding amounts increased for long and short tokens + let retrieved_claimable_long = data_store.get_u256(claimable_fund_long_key); + let retrieved_claimable_short = data_store.get_u256(claimable_fund_short_key); + assert(retrieved_claimable_long == long_token_amount, 'Invalid claimable for long'); + assert(retrieved_claimable_short == short_token_amount, 'Invalid claimable for short'); + + let mut fees2: PositionFees = Default::default(); + fees2.funding.claimable_long_token_amount = 0; + fees2.funding.claimable_short_token_amount = 0; + position_utils::increment_claimable_funding_amount(params, fees2); + + // Check funding amounts doesnt change + let retrieved_claimable_long = data_store.get_u256(claimable_fund_long_key); + let retrieved_claimable_short = data_store.get_u256(claimable_fund_short_key); + assert(retrieved_claimable_long == long_token_amount, 'Invalid claimable for long'); + assert(retrieved_claimable_short == short_token_amount, 'Invalid claimable for short'); + + 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] +fn given_negative_remaining_collateral_usd_when_checking_liquidatability_then_invalid_position() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let referral_storage_address: ContractAddress = deploy_referral_storage(event_emitter_address); + + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + //Create a long position + let mut position: Position = Default::default(); + position.size_in_usd = 10000; + position.collateral_amount = 10; + position.borrowing_factor = 2; + position.size_in_tokens = 50; + position.is_long = true; + position.collateral_token = long_token; + position.market = market_token; + + // Fill required data store keys. + + // setting long interest greater than te position size in USD... + let open_interest_key = keys::open_interest_key(market_token, long_token, true); + data_store.set_u256(open_interest_key, 15000); + + // setting cumulative borrowing factor greater than the borrowing factor... + let cumulative_borrowing_factor_key = keys::cumulative_borrowing_factor_key(market_token, true); + data_store.set_u256(cumulative_borrowing_factor_key, 1000); + + let market = Market { market_token, index_token: long_token, long_token, short_token, }; + + let long_token_price = Price { min: 100, max: 110 }; + let index_token_price = Price { min: 100, max: 110 }; + let short_token_price = Price { min: 100, max: 110 }; + + let prices: MarketPrices = MarketPrices { + index_token_price: index_token_price, + long_token_price: long_token_price, + short_token_price: short_token_price + }; + + // Test + + let (is_liquiditable, reason) = position_utils::is_position_liquiditable( + data_store, referral_storage, position, market, prices, false + ); + + assert(is_liquiditable, 'Invalid position liquidation'); + assert(reason == '0<', 'Invalid liquidation reason'); +} + + +#[test] +fn given_below_minimum_collateral_when_checking_liquidatability_then_invalid_position() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let referral_storage_address: ContractAddress = deploy_referral_storage(event_emitter_address); + + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + //Create a long position + let mut position: Position = Default::default(); + position.size_in_usd = 10000; + position.collateral_amount = 10; + position.borrowing_factor = 2; + position.size_in_tokens = 50; + position.is_long = true; + position.collateral_token = long_token; + position.market = market_token; + + // Fill required data store keys. + + // setting long interest greater than te position size in USD... + let open_interest_key = keys::open_interest_key(market_token, long_token, true); + data_store.set_u256(open_interest_key, 15000); + + // setting cumulative borrowing factor greater than the borrowing factor... + let cumulative_borrowing_factor_key = keys::cumulative_borrowing_factor_key(market_token, true); + data_store.set_u256(cumulative_borrowing_factor_key, 1000); + + let market = Market { market_token, index_token: long_token, long_token, short_token, }; + + let long_token_price = Price { min: 100, max: 110 }; + let index_token_price = Price { min: 100, max: 110 }; + let short_token_price = Price { min: 100, max: 110 }; + + let prices: MarketPrices = MarketPrices { + index_token_price: index_token_price, + long_token_price: long_token_price, + short_token_price: short_token_price + }; + + // Test + + let (is_liquiditable, reason) = position_utils::is_position_liquiditable( + data_store, referral_storage, position, market, prices, true + ); + + assert(is_liquiditable, 'Invalid position liquidation'); + assert(reason == 'min collateral', 'Invalid liquidation reason'); +} + +#[test] +fn given_valid_position_when_checking_liquidatability_then_valid_position() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let referral_storage_address: ContractAddress = deploy_referral_storage(event_emitter_address); + + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + //Create a long position + let mut position: Position = Default::default(); + position.size_in_usd = 10000; + position.collateral_amount = 1000; + position.borrowing_factor = 2; + position.size_in_tokens = 50; + position.is_long = true; + position.collateral_token = long_token; + position.market = market_token; + + // Fill required data store keys. + + // setting long interest greater than te position size in USD... + let open_interest_key = keys::open_interest_key(market_token, long_token, true); + data_store.set_u256(open_interest_key, 15000); + + // setting cumulative borrowing factor greater than the borrowing factor... + let cumulative_borrowing_factor_key = keys::cumulative_borrowing_factor_key(market_token, true); + data_store.set_u256(cumulative_borrowing_factor_key, 1000); + + let market = Market { market_token, index_token: long_token, long_token, short_token, }; + + let long_token_price = Price { min: 100, max: 110 }; + let index_token_price = Price { min: 100, max: 110 }; + let short_token_price = Price { min: 100, max: 110 }; + + let prices: MarketPrices = MarketPrices { + index_token_price: index_token_price, + long_token_price: long_token_price, + short_token_price: short_token_price + }; + + // Test + + let (is_liquiditable, reason) = position_utils::is_position_liquiditable( + data_store, referral_storage, position, market, prices, true + ); + + assert(!is_liquiditable, 'Invalid position liquidation'); + assert(reason == '', 'Invalid liquidation reason'); +} + +#[test] +fn given_below_min_collateral_leverage_when_checking_liquidatability_then_invalid_position() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let referral_storage_address: ContractAddress = deploy_referral_storage(event_emitter_address); + + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + //Create a long position + let mut position: Position = Default::default(); + position.size_in_usd = 10000; + position.collateral_amount = 60; + position.borrowing_factor = 2; + position.size_in_tokens = 50; + position.is_long = true; + position.collateral_token = long_token; + position.market = market_token; + + // Fill required data store keys. + + // setting long interest greater than te position size in USD... + let open_interest_key = keys::open_interest_key(market_token, long_token, true); + data_store.set_u256(open_interest_key, 15000); + + // setting cumulative borrowing factor greater than the borrowing factor... + let cumulative_borrowing_factor_key = keys::cumulative_borrowing_factor_key(market_token, true); + data_store.set_u256(cumulative_borrowing_factor_key, 1000); + + // setting a min collateral factor for the market + let min_collateral_factor_key = keys::min_collateral_factor_key(market_token); + data_store.set_u256(min_collateral_factor_key, 10_000_000_000_000_000_000); + + let market = Market { market_token, index_token: long_token, long_token, short_token, }; + + let long_token_price = Price { min: 100, max: 110 }; + let index_token_price = Price { min: 100, max: 110 }; + let short_token_price = Price { min: 100, max: 110 }; + + let prices: MarketPrices = MarketPrices { + index_token_price: index_token_price, + long_token_price: long_token_price, + short_token_price: short_token_price + }; + + // Test + + let (is_liquiditable, reason) = position_utils::is_position_liquiditable( + data_store, referral_storage, position, market, prices, false + ); + + assert(is_liquiditable, 'Invalid position liquidation'); + assert(reason == 'min collateral for leverage', 'Invalid liquidation reason'); +} + + +#[test] +fn given_initial_total_borrowing_when_updating_then_correct_total_borrowing() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + // Fill required data store keys. + let total_borrowing_key = keys::total_borrowing_key(market_token, false); + data_store.set_u256(total_borrowing_key, 1000); + + let mut params: position_utils::UpdatePositionParams = UpdatePositionParams { + contracts: ExecuteOrderParamsContracts { + data_store, + event_emitter, + order_vault: IOrderVaultDispatcher { contract_address: Zeroable::zero() }, + oracle: IOracleDispatcher { contract_address: Zeroable::zero() }, + swap_handler: ISwapHandlerDispatcher { contract_address: Zeroable::zero() }, + referral_storage: IReferralStorageDispatcher { contract_address: Zeroable::zero() }, + }, + market: Market { market_token, index_token: long_token, long_token, short_token, }, + order: Default::default(), + order_key: 0, + position: Default::default(), + position_key: 0, + secondary_order_type: SecondaryOrderType::None, + }; + + //Test + + //Update total borrowing + let next_position_size_in_usd: u256 = 1000000000000000; + let next_position_borrowing_factor: u256 = 20000000; + + position_utils::update_total_borrowing( + params, next_position_size_in_usd, next_position_borrowing_factor + ); + + let total_borrowing_value: u256 = data_store.get_u256(total_borrowing_key); + assert(total_borrowing_value == 1200, 'Invalid total borrowing') +} + +#[test] +fn given_initial_open_interest_when_updating_then_correct_open_interest() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + // Fill required data store keys. + let key_open_interest = keys::open_interest_key( + market_token, contract_address_const::<0>(), false + ); + data_store.set_u256(key_open_interest, 1000); + + let key_open_interest_in_tokens = keys::open_interest_in_tokens_key( + market_token, contract_address_const::<0>(), false + ); + data_store.set_u256(key_open_interest_in_tokens, 2000); + + let mut params: position_utils::UpdatePositionParams = UpdatePositionParams { + contracts: ExecuteOrderParamsContracts { + data_store, + event_emitter, + order_vault: IOrderVaultDispatcher { contract_address: Zeroable::zero() }, + oracle: IOracleDispatcher { contract_address: Zeroable::zero() }, + swap_handler: ISwapHandlerDispatcher { contract_address: Zeroable::zero() }, + referral_storage: IReferralStorageDispatcher { contract_address: Zeroable::zero() }, + }, + market: Market { market_token, index_token: long_token, long_token, short_token, }, + order: Default::default(), + order_key: 0, + position: Default::default(), + position_key: 0, + secondary_order_type: SecondaryOrderType::None, + }; + + //Update open interest + let size_delta_usd: i256 = 10.into(); + let size_delta_in_tokens: i256 = 20.into(); + + //Test + + position_utils::update_open_interest(params, size_delta_usd, size_delta_in_tokens); + + let open_interest = data_store.get_u256(key_open_interest); + + let open_interest_in_tokens = data_store.get_u256(key_open_interest_in_tokens); + + assert(open_interest == 1010, 'Invalid open interest value'); + assert(open_interest_in_tokens == 2020, 'Invalid open interest value'); +} + +#[test] +fn given_valid_referral_when_handling_then_referral_successfully_processed() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + let mut fees: PositionFees = Default::default(); + let mut referral: PositionReferralFees = Default::default(); + + referral.affiliate = contract_address_const::<'1'>(); + referral.affiliate_reward_amount = 20; + fees.referral = referral; + + // Fill required data store keys. + let affiliate_reward_for_account_key = keys::affiliate_reward_for_account_key( + market_token, contract_address_const::<0>(), referral.affiliate + ); + data_store.set_u256(affiliate_reward_for_account_key, 10); + + let mut params: position_utils::UpdatePositionParams = UpdatePositionParams { + contracts: ExecuteOrderParamsContracts { + data_store, + event_emitter, + order_vault: IOrderVaultDispatcher { contract_address: Zeroable::zero() }, + oracle: IOracleDispatcher { contract_address: Zeroable::zero() }, + swap_handler: ISwapHandlerDispatcher { contract_address: Zeroable::zero() }, + referral_storage: IReferralStorageDispatcher { contract_address: Zeroable::zero() }, + }, + market: Market { market_token, index_token: long_token, long_token, short_token, }, + order: Default::default(), + order_key: 0, + position: Default::default(), + position_key: 0, + secondary_order_type: SecondaryOrderType::None, + }; + + //Attribute position.market to the market instance define above + + params.position.market = params.market.market_token; + + //Test + + position_utils::handle_referral(params, fees); + let affiliate_reward_value = data_store.get_u256(affiliate_reward_for_account_key); + + assert(affiliate_reward_value == 30, 'Invalide affiliate reward value') +} + + +#[test] +fn test_will_position_collateral_be_sufficient() { + // Setup + let (caller_address, role_store, data_store) = setup(); + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + let market: Market = Market { market_token, index_token: long_token, long_token, short_token }; + + let long_token_price = Price { min: 100, max: 110 }; + let index_token_price = Price { min: 100, max: 110 }; + let short_token_price = Price { min: 100, max: 110 }; + + // setting long interest greater than te position size in USD... + let open_interest_key = keys::open_interest_key(market_token, long_token, true); + data_store.set_u256(open_interest_key, 15000); + + // setting a min collateral factor for the market + let min_collateral_factor_key = keys::min_collateral_factor_key(market_token); + data_store.set_u256(min_collateral_factor_key, 10_000_000_000_000_000_000); + + let prices: MarketPrices = MarketPrices { + index_token_price: index_token_price, + long_token_price: long_token_price, + short_token_price: short_token_price + }; + + let values: WillPositionCollateralBeSufficientValues = + WillPositionCollateralBeSufficientValues { + position_size_in_usd: 1000, + position_collateral_amount: 50, + realized_pnl_usd: i256_new(10, true), + open_interest_delta: i256_new(5, true), + }; + + // invoke the function with scenario where collateral will be sufficient + let (will_be_sufficient, remaining_collateral_usd) = + position_utils::will_position_collateral_be_sufficient( + data_store, market, prices, long_token, true, values + ); + assert(will_be_sufficient, 'collateral supposed sufficient'); + assert(remaining_collateral_usd == i256_new(4990, false), 'eq 4990'); + + let values: WillPositionCollateralBeSufficientValues = + WillPositionCollateralBeSufficientValues { + position_size_in_usd: 1000, + position_collateral_amount: 5, + realized_pnl_usd: i256_new(410, true), + open_interest_delta: i256_new(5, true), + }; + + // invoke the function with scenario where collateral will be insufficient + let (will_be_sufficient, remaining_collateral_usd) = + position_utils::will_position_collateral_be_sufficient( + data_store, market, prices, long_token, true, values + ); + // assert that the function returns that the collateral is not sufficient + assert(!will_be_sufficient, 'collateral should insufficient'); + assert(remaining_collateral_usd == i256_new(90, false), 'eq 90'); +} +//TODO +// #[test] +// fn test_update_funding_and_borrowing_state() { +// } + + diff --git a/src/tests/price/test_price.cairo b/tests/price/test_price.cairo similarity index 100% rename from src/tests/price/test_price.cairo rename to tests/price/test_price.cairo diff --git a/tests/pricing/test_position_pricing_utils.cairo b/tests/pricing/test_position_pricing_utils.cairo new file mode 100644 index 00000000..88ddbbcd --- /dev/null +++ b/tests/pricing/test_position_pricing_utils.cairo @@ -0,0 +1,233 @@ +use starknet::{ContractAddress, contract_address_const}; +use satoru::price::price::Price; +use satoru::position::position::Position; +use satoru::pricing::position_pricing_utils; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::mock::governable::{IGovernableDispatcher, IGovernableDispatcherTrait}; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::role::role; +use satoru::market::market::Market; +use satoru::pricing::position_pricing_utils::{ + GetPositionFeesParams, PositionFundingFees, GetPriceImpactUsdParams +}; +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; +use satoru::utils::precision::{FLOAT_PRECISION, FLOAT_PRECISION_SQRT}; +use satoru::utils::i256::{i256, i256_new}; + +// TODO add asserts for each test when possible + +#[test] +fn given_normal_conditions_when_get_price_impact_usd_then_works() { + let (caller_address, data_store, referral_storage) = setup(); + + let get_price_impact_params = create_get_price_impact_usd_params(data_store); + position_pricing_utils::get_price_impact_usd(get_price_impact_params); +} + +#[test] +fn given_normal_conditions_when_get_next_open_interest_then_works() { + let (caller_address, data_store, referral_storage) = setup(); + + let get_price_impact_params = create_get_price_impact_usd_params(data_store); + position_pricing_utils::get_next_open_interest(get_price_impact_params); +} + +#[test] +fn given_normal_conditions_when_get_next_open_interest_for_virtual_inventory_then_works() { + let (caller_address, data_store, referral_storage) = setup(); + + let get_price_impact_params = create_get_price_impact_usd_params(data_store); + position_pricing_utils::get_next_open_interest_for_virtual_inventory( + get_price_impact_params, i256_new(50, false) + ); +} + +#[test] +fn given_normal_conditions_when_get_next_open_interest_params_then_works() { + let (caller_address, data_store, referral_storage) = setup(); + + let get_price_impact_params = create_get_price_impact_usd_params(data_store); + position_pricing_utils::get_next_open_interest_params(get_price_impact_params, 100, 20); +} + +#[test] +fn given_normal_conditions_when_get_position_fees_then_works() { + let (caller_address, data_store, referral_storage) = setup(); + + let position = Position { + key: 1, + account: contract_address_const::<'account'>(), + market: contract_address_const::<'market'>(), + collateral_token: contract_address_const::<'collateral_token'>(), + size_in_usd: 100, + size_in_tokens: 1, + collateral_amount: 2, + borrowing_factor: 3, + funding_fee_amount_per_size: 4, + long_token_claimable_funding_amount_per_size: 5, + short_token_claimable_funding_amount_per_size: 6, + increased_at_block: 15000, + decreased_at_block: 15001, + is_long: false + }; + + let collateral_token_price = Price { min: 5, max: 10, }; + + GetPositionFeesParams { + data_store, + referral_storage, + position, + collateral_token_price, + for_positive_impact: true, + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + size_delta_usd: 10, + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>() + }; +} + +#[test] +fn given_normal_conditions_when_get_borrowing_fees_then_works() { + let (caller_address, data_store, referral_storage) = setup(); + let price = Price { min: 5, max: 10 }; + + position_pricing_utils::get_borrowing_fees(data_store, price, 3); +} + +#[test] +fn given_normal_conditions_when_get_funding_fees_then_works() { + let (caller_address, data_store, referral_storage) = setup(); + let position_funding_fees = PositionFundingFees { + funding_fee_amount: 10, + claimable_long_token_amount: 100, + claimable_short_token_amount: 50, + latest_funding_fee_amount_per_size: 15_000_000_000_000_000_000_000_000_000_000, + latest_long_token_claimable_funding_amount_per_size: 15_000_000_000_000_000_000_000_000_000_000, + latest_short_token_claimable_funding_amount_per_size: 15_000_000_000_000_000_000_000_000_000_000, + }; + + let position = Position { + key: 1, + account: contract_address_const::<'account'>(), + market: contract_address_const::<'market'>(), + collateral_token: contract_address_const::<'collateral_token'>(), + size_in_usd: 100, + size_in_tokens: 1, + collateral_amount: 2, + borrowing_factor: 3, + funding_fee_amount_per_size: 4_000_000_000_000_000_000_000_000_000_000, + long_token_claimable_funding_amount_per_size: 5_000_000_000_000_000_000_000_000_000_000, + short_token_claimable_funding_amount_per_size: 6_000_000_000_000_000_000_000_000_000_000, + increased_at_block: 15000, + decreased_at_block: 15001, + is_long: false + }; + + position_pricing_utils::get_funding_fees(position_funding_fees, position); +} + +#[test] +fn given_normal_conditions_when_get_ui_fees_then_works() { + let (caller_address, data_store, referral_storage) = setup(); + let price = Price { min: 5, max: 10 }; + let ui_fee_receiver = contract_address_const::<'ui_fee_receiver'>(); + position_pricing_utils::get_ui_fees(data_store, price, 10, ui_fee_receiver); +} + +#[test] +fn given_normal_conditions_when_get_position_fees_after_referral_then_works() { + let (caller_address, data_store, referral_storage) = setup(); + let price = Price { min: 5, max: 10 }; + let account = contract_address_const::<'account'>(); + let market = contract_address_const::<'market'>(); + position_pricing_utils::get_position_fees_after_referral( + data_store, referral_storage, price, true, account, market, 10 + ); +} + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'referral_storage'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_governable(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('Governable'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'governable'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn setup() -> (ContractAddress, IDataStoreDispatcher, IReferralStorageDispatcher) { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + + let role_store_address = deploy_role_store(); + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + + let data_store_address = deploy_data_store(role_store_address); + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + + 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(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(role_store_address, caller_address); + role_store.grant_role(caller_address, role::CONTROLLER); + start_prank(data_store_address, caller_address); + (caller_address, data_store, referral_storage) +} + + +fn create_get_price_impact_usd_params(data_store: IDataStoreDispatcher) -> GetPriceImpactUsdParams { + let market = Market { + market_token: contract_address_const::<'market_token'>(), + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>() + }; + + GetPriceImpactUsdParams { data_store, market, usd_delta: i256_new(50, false), is_long: true } +} diff --git a/tests/pricing/test_swap_pricing_utils.cairo b/tests/pricing/test_swap_pricing_utils.cairo new file mode 100644 index 00000000..233ce520 --- /dev/null +++ b/tests/pricing/test_swap_pricing_utils.cairo @@ -0,0 +1,132 @@ +use satoru::data::data_store::IDataStoreDispatcherTrait; +use satoru::data::keys; +use satoru::pricing::swap_pricing_utils::{ + GetPriceImpactUsdParams, get_price_impact_usd_, get_price_impact_usd, get_next_pool_amount_usd, + get_swap_fees +}; +use satoru::market::market::Market; +use satoru::utils::calc; +use satoru::tests_lib::{setup, teardown}; +use satoru::utils::i256::{i256, i256_new}; + +#[test] +fn given_normal_conditions_when_swap_pricing_utils_functions_then_works() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let (_, _, data_store) = setup(); + + let market_token = 'market_token'.try_into().unwrap(); + let index_token = 'index_token'.try_into().unwrap(); + let long_token = 'long_token'.try_into().unwrap(); + let short_token = 'short_token'.try_into().unwrap(); + + data_store.set_u256(keys::pool_amount_key(market_token, long_token), 1000); + data_store.set_u256(keys::pool_amount_key(market_token, short_token), 1000); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + let params = GetPriceImpactUsdParams { + data_store, + market: Market { market_token, index_token, long_token, short_token }, + token_a: long_token, + token_b: short_token, + price_for_token_a: 101, + price_for_token_b: 99, + usd_delta_for_token_a: i256_new(5, false), + usd_delta_for_token_b: i256_new(4, false), + }; + + let impact = get_price_impact_usd(params); + // TODO change to real value when precision::apply_exponent_factor is implemented + assert(impact == i256_new(0, false), 'foo'); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_get_next_pool_amount_usd_then_works() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let (_, _, data_store) = setup(); + + let market_token = 'market_token'.try_into().unwrap(); + let index_token = 'index_token'.try_into().unwrap(); + let long_token = 'long_token'.try_into().unwrap(); + let short_token = 'short_token'.try_into().unwrap(); + + data_store.set_u256(keys::pool_amount_key(market_token, long_token), 1000); + data_store.set_u256(keys::pool_amount_key(market_token, short_token), 1000); + data_store.set_u256(keys::swap_impact_factor_key(market_token, false), 10); + data_store.set_u256(keys::swap_impact_factor_key(market_token, true), 10); + data_store.set_u256(keys::swap_impact_exponent_factor_key(market_token), 10); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + let params = GetPriceImpactUsdParams { + data_store, + market: Market { market_token, index_token, long_token, short_token }, + token_a: long_token, + token_b: short_token, + price_for_token_a: 101, + price_for_token_b: 99, + usd_delta_for_token_a: i256_new(5, false), + usd_delta_for_token_b: i256_new(4, false), + }; + + let pool_params = get_next_pool_amount_usd(params); + assert(pool_params.pool_usd_for_token_a == 101000, 'invalid'); + assert(pool_params.pool_usd_for_token_b == 99000, 'invalid'); + assert(pool_params.next_pool_usd_for_token_a == 101005, 'invalid'); + assert(pool_params.next_pool_usd_for_token_b == 99004, 'invalid'); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_get_swap_fees_then_works() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let (_, _, data_store) = setup(); + + let market_token = 'market_token'.try_into().unwrap(); + let ui_fee_receiver = 'ui_fee_receiver'.try_into().unwrap(); + let for_positive_impact = true; + + data_store.set_u256(keys::swap_fee_factor_key(market_token, for_positive_impact), 5); + data_store.set_u256(keys::swap_fee_receiver_factor(), 10); + data_store.set_u256(keys::max_ui_fee_factor(), 9); + data_store.set_u256(keys::ui_fee_factor_key(ui_fee_receiver), 9); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + let amount = 1000; + let fees = get_swap_fees( + data_store, market_token, amount, for_positive_impact, ui_fee_receiver + ); + + assert(fees.fee_receiver_amount == 0, 'invalid'); + assert(fees.fee_amount_for_pool == 0, 'invalid'); + assert(fees.amount_after_fees == 0x03e8, 'invalid'); + assert(fees.ui_fee_receiver_factor == 9, 'invalid'); + assert(fees.ui_fee_amount == 0, 'invalid'); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store.contract_address); +} diff --git a/tests/reader/test_reader.cairo b/tests/reader/test_reader.cairo new file mode 100644 index 00000000..c0f31f68 --- /dev/null +++ b/tests/reader/test_reader.cairo @@ -0,0 +1,1162 @@ +use starknet::{ContractAddress, contract_address_const}; +use debug::PrintTrait; + +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::reader::reader::{IReaderDispatcher, IReaderDispatcherTrait, MarketInfo}; +use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::tests_lib::{deploy_data_store, deploy_role_store, setup_oracle_and_store}; + +use satoru::reader::{ + reader_utils::PositionInfo, reader_utils::BaseFundingValues, + reader_pricing_utils::ExecutionPriceResult, reader::VirtualInventory +}; +use satoru::role::role; +use satoru::order::order::{Order, OrderType, OrderTrait, DecreasePositionSwapType}; +use satoru::tests_lib::{setup, teardown}; +use satoru::utils::span32::{Span32, Array32Trait}; +use satoru::market::market::{Market}; +use satoru::market::market_pool_value_info::{MarketPoolValueInfo}; +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; +use poseidon::poseidon_hash_span; +use satoru::deposit::deposit::{Deposit}; +use satoru::withdrawal::withdrawal::{Withdrawal}; +use satoru::position::position::{Position}; +use satoru::data::keys; +use satoru::price::price::{Price, PriceTrait}; +use satoru::utils::i256::{i256, i256_new}; +use satoru::market::market_utils::{get_capped_pnl, MarketPrices}; + + +#[test] +fn given_normal_conditions_when_get_market_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let key: ContractAddress = 123456789.try_into().unwrap(); + let mut market = Market { + market_token: key, + index_token: 11111.try_into().unwrap(), + long_token: 22222.try_into().unwrap(), + short_token: 33333.try_into().unwrap(), + }; + start_prank(role_store.contract_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + stop_prank(role_store.contract_address); + + // Test logic + + data_store.set_market(key, 0, market); + + let market_by_key = reader.get_market(data_store, key); + assert(market_by_key == market, 'Invalid market by key'); + + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_get_market_by_salt_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let key: ContractAddress = 123456789.try_into().unwrap(); + let mut market = Market { + market_token: key, + index_token: 11111.try_into().unwrap(), + long_token: 22222.try_into().unwrap(), + short_token: 33333.try_into().unwrap(), + }; + let key2: ContractAddress = 222222222222.try_into().unwrap(); + + let mut market2 = Market { + market_token: key, + index_token: 12345.try_into().unwrap(), + long_token: 56678.try_into().unwrap(), + short_token: 8901234.try_into().unwrap(), + }; + + start_prank(role_store.contract_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + stop_prank(role_store.contract_address); + + let salt: felt252 = 'satoru_market'; + let salt2: felt252 = 'satoru_market2'; + + // Test logic + + data_store.set_market(key, salt, market); + data_store.set_market(key2, salt2, market2); + + let market_by_key = reader.get_market_by_salt(data_store, salt); + assert(market_by_key == market, 'Invalid market by key'); + + let market_by_key2 = reader.get_market_by_salt(data_store, salt2); + assert(market_by_key2 == market2, 'Invalid market2 by key'); + + teardown(data_store.contract_address); +} + + +#[test] +fn given_normal_conditions_when_get_deposit_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let key = 123456789; + // Create random deposit + let mut deposit: Deposit = Default::default(); + deposit.key = 123456789; + deposit.account = 'account'.try_into().unwrap(); + deposit.receiver = 'receiver'.try_into().unwrap(); + deposit.initial_long_token_amount = 1000000; + deposit.initial_short_token_amount = 2222222; + + // Test logic + + data_store.set_deposit(key, deposit); + + let deposit_by_key = reader.get_deposit(data_store, key); + assert(deposit_by_key == deposit, 'Invalid deposit by key'); + + teardown(data_store.contract_address); +} + + +#[test] +fn given_normal_conditions_when_get_withdrawal_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let key = 123456789; + // Create random withdrawal + let mut withdrawal: Withdrawal = Default::default(); + withdrawal.key = 123456789; + withdrawal.account = 'account'.try_into().unwrap(); + withdrawal.receiver = 'receiver'.try_into().unwrap(); + withdrawal.market_token_amount = 1000000; + withdrawal.min_short_token_amount = 2222222; + + // Test logic + + data_store.set_withdrawal(key, withdrawal); + + let withdrawal_by_key = reader.get_withdrawal(data_store, key); + assert(withdrawal_by_key == withdrawal, 'Invalid withdrawal by key'); + + teardown(data_store.contract_address); +} + + +#[test] +fn given_normal_conditions_when_get_position_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + let key = 123456789; + // Create random position + let mut position: Position = Default::default(); + position.key = 123456789; + position.account = 'account'.try_into().unwrap(); + position.market = 'market'.try_into().unwrap(); + position.size_in_usd = 1000000; + position.funding_fee_amount_per_size = 3333333333; + + // Test logic + + data_store.set_position(key, position); + + let position_by_key = reader.get_position(data_store, key); + assert(position_by_key == position, 'Invalid position by key'); + + teardown(data_store.contract_address); +} + + +#[test] +fn given_normal_conditions_when_get_order_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let key = 123456789; + // Create random order + let mut order: Order = Default::default(); + order.key = 123456789; + order.account = 'account'.try_into().unwrap(); + order.market = 'market'.try_into().unwrap(); + order.trigger_price = 1000000; + order.callback_gas_limit = 3333333333; + + // Test logic + + data_store.set_order(key, order); + + let order_by_key = reader.get_order(data_store, key); + assert(order_by_key == order, 'Invalid order by key'); + + teardown(data_store.contract_address); +} + + +#[test] +fn given_normal_conditions_when_get_position_pnl_usd_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let key: ContractAddress = 123456789.try_into().unwrap(); + let account = 'account'.try_into().unwrap(); + let market = Market { + market_token: key, + index_token: 12345.try_into().unwrap(), + long_token: 56678.try_into().unwrap(), + short_token: 8901234.try_into().unwrap(), + }; + let price1 = Price { min: 1, max: 200 }; + let price2 = Price { min: 1, max: 300 }; + let price3 = Price { min: 1, max: 400 }; + //create random prices + let prices = MarketPrices { + index_token_price: price1, long_token_price: price2, short_token_price: price3 + }; + // Create random position + let key_1 = 1234311; + let mut position: Position = Default::default(); + position.key = 1234311; + position.market = 'market'.try_into().unwrap(); + position.size_in_usd = 1000000; + position.account = account; + position.is_long = true; + position.size_in_tokens = 10000; + + start_prank(role_store.contract_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + stop_prank(role_store.contract_address); + + //test logic + + data_store.set_market(key, 1, market); + data_store.set_position(key_1, position); + + let (data1, data2, data3) = reader + .get_position_pnl_usd(data_store, market, prices, key_1, 1000000); + let data3_felt: felt252 = data3.try_into().expect('u256 into felt failed'); + + assert(data3_felt == 10000, 'Invalid'); + teardown(data_store.contract_address); +} + + +#[test] +fn given_normal_conditions_when_get_account_positions_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let key_1 = 1111111111; + let account = 'account'.try_into().unwrap(); + // Create random position + let mut position1: Position = Default::default(); + position1.key = key_1; + position1.market = 'market1'.try_into().unwrap(); + position1.size_in_usd = 1000000; + position1.account = account; + + let key_2 = 22222222222; + let mut position2: Position = Default::default(); + position2.key = key_2; + position2.market = 'market2'.try_into().unwrap(); + position2.size_in_usd = 2000000; + position2.account = account; + + let key_3 = 33333333333; + let mut position3: Position = Default::default(); + position3.key = key_3; + position3.market = 'market3'.try_into().unwrap(); + position3.size_in_usd = 3000000; + position3.account = account; + + let key_4 = 44444444444; + let mut position4: Position = Default::default(); + position4.key = key_4; + position4.market = 'market4'.try_into().unwrap(); + position4.size_in_usd = 4000000; + position4.account = account; + + // Test logic + + data_store.set_position(key_1, position1); + data_store.set_position(key_2, position2); + data_store.set_position(key_3, position3); + data_store.set_position(key_4, position4); + + let account_position = reader.get_account_positions(data_store, account, 0, 10); + assert(account_position.len() == 4, 'invalid position len'); + assert(account_position.at(0) == @position1, 'invalid position1'); + assert(account_position.at(1) == @position2, 'invalid position2'); + assert(account_position.at(2) == @position3, 'invalid position3'); + assert(account_position.at(3) == @position4, 'invalid position4'); + + teardown(data_store.contract_address); +} + +// error `Option::unwrap()` on a `None` value +// #[test] +// fn given_normal_conditions_when_get_position_info_then_works() { +// let (caller_address, role_store, data_store) = setup(); +// let (reader_address, reader) = setup_reader(); +// let (referral_storage_address, referral) = setup_referral_storage(); +// //create random position +// let key_4: felt252 = 44444444444; +// let mut position: Position = Default::default(); +// position.key = key_4; +// position.market = 'market4'.try_into().unwrap(); +// position.size_in_usd = 4000000; +// position.account = 'account'.try_into().unwrap(); +// position.is_long = true; +// position.size_in_tokens = 10000; + +// let key: ContractAddress = 123456789.try_into().unwrap(); +// let ui_fee_receiver: ContractAddress = 5746789.try_into().unwrap(); +// let market = Market { +// market_token: key, +// index_token: 12345.try_into().unwrap(), +// long_token: 56678.try_into().unwrap(), +// short_token: 8901234.try_into().unwrap(), +// }; +// let price1 = Price { min: 1, max: 200 }; +// let price2 = Price { min: 1, max: 300 }; +// let price3 = Price { min: 1, max: 400 }; +// //create random prices +// let prices = MarketPrices { +// index_token_price: price1, long_token_price: price2, short_token_price: price3 +// }; +// start_prank(role_store.contract_address, caller_address); +// role_store.grant_role(caller_address, role::MARKET_KEEPER); +// stop_prank(role_store.contract_address); + +// data_store.set_market(key, 1, market); +// data_store.set_position(key_4, position); + +// let size_delta: u256 = 1000000; +// let res: PositionInfo = reader +// .get_position_info(data_store, referral, key_4, prices, size_delta, ui_fee_receiver, true); +// // assert(res.position.key == 44444444444, 'wrong_key'); +// teardown(data_store.contract_address); +// } + +// error `Option::unwrap()` on a `None` value +// #[test] +// fn given_normal_conditions_when_get_account_position_info_list_then_works() { +// let (caller_address, role_store, data_store) = setup(); +// let (reader_address, reader) = setup_reader(); +// let (referral_storage_address, referral) = setup_referral_storage(); +// //create random position +// let key_1: felt252 = 44444444444; +// let mut position1: Position = Default::default(); +// position1.key = key_1; +// position1.market = 'market1'.try_into().unwrap(); +// position1.size_in_usd = 4000000; +// position1.account = 'account1'.try_into().unwrap(); +// position1.is_long = true; +// position1.size_in_tokens = 10000; + +// let key_2: felt252 = 3333333333; +// let mut position2: Position = Default::default(); +// position2.key = key_2; +// position2.market = 'market2'.try_into().unwrap(); +// position2.size_in_usd = 3000000; +// position2.account = 'account2'.try_into().unwrap(); +// position2.is_long = true; +// position2.size_in_tokens = 10000; + +// let key_3: felt252 = 2222222222; +// let mut position3: Position = Default::default(); +// position3.key = key_3; +// position3.market = 'market3'.try_into().unwrap(); +// position3.size_in_usd = 3000000; +// position3.account = 'account3'.try_into().unwrap(); +// position3.is_long = true; +// position3.size_in_tokens = 10000; + +// let ui_fee_receiver: ContractAddress = 5746789.try_into().unwrap(); +// let market_key_1: ContractAddress = 123456789.try_into().unwrap(); +// let market_1 = Market { +// market_token: market_key_1, +// index_token: 12345.try_into().unwrap(), +// long_token: 56678.try_into().unwrap(), +// short_token: 8901234.try_into().unwrap(), +// }; +// let market_key_2: ContractAddress = 67545356789.try_into().unwrap(); +// let market_2 = Market { +// market_token: market_key_2, +// index_token: 122145.try_into().unwrap(), +// long_token: 236678.try_into().unwrap(), +// short_token: 34201234.try_into().unwrap(), +// }; +// let market_key_3: ContractAddress = 67545356789.try_into().unwrap(); +// let market_3 = Market { +// market_token: market_key_3, +// index_token: 222145.try_into().unwrap(), +// long_token: 536678.try_into().unwrap(), +// short_token: 671234.try_into().unwrap(), +// }; + +// let price1 = Price { min: 1, max: 200 }; +// let price2 = Price { min: 1, max: 300 }; +// let price3 = Price { min: 1, max: 400 }; +// //create random prices +// let prices_1 = MarketPrices { +// index_token_price: price1, long_token_price: price2, short_token_price: price3 +// }; +// let prices_2 = MarketPrices { +// index_token_price: price3, long_token_price: price2, short_token_price: price1 +// }; + +// let prices_3 = MarketPrices { +// index_token_price: price2, long_token_price: price1, short_token_price: price3 +// }; + +// start_prank(role_store.contract_address, caller_address); +// role_store.grant_role(caller_address, role::MARKET_KEEPER); +// stop_prank(role_store.contract_address); + +// data_store.set_market(market_key_1, 1, market_1); +// data_store.set_market(market_key_2, 2, market_2); +// data_store.set_market(market_key_3, 3, market_3); + +// data_store.set_position(key_1, position1); +// data_store.set_position(key_2, position2); +// data_store.set_position(key_3, position3); + +// let mut position_key_arr = ArrayTrait::::new(); +// position_key_arr.append(key_1); +// position_key_arr.append(key_2); +// position_key_arr.append(key_3); + +// let mut prices_arr = ArrayTrait::::new(); +// prices_arr.append(prices_1); +// prices_arr.append(prices_2); +// prices_arr.append(prices_3); + +// let mut res_arr: Array = reader +// .get_account_position_info_list( +// data_store, referral, position_key_arr, prices_arr, ui_fee_receiver +// ); +// assert(*res_arr.at(0).position.key == key_1, 'invalid_key'); +// assert(*res_arr.at(1).position.key == key_2, 'invalid_key'); +// assert(*res_arr.at(2).position.key == key_3, 'invalid_key'); +// } + +#[test] +fn given_normal_conditions_when_get_account_orders_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let key_1 = 1111111111; + let account = 'account'.try_into().unwrap(); + // Create random order + let mut order1: Order = Default::default(); + order1.key = key_1; + order1.market = 'market1'.try_into().unwrap(); + order1.size_delta_usd = 1000000; + order1.account = account; + + let key_2 = 22222222222; + let mut order2: Order = Default::default(); + order2.key = key_2; + order2.market = 'market2'.try_into().unwrap(); + order2.size_delta_usd = 2000000; + order2.account = account; + + let key_3 = 33333333333; + let mut order3: Order = Default::default(); + order3.key = key_3; + order3.market = 'market3'.try_into().unwrap(); + order3.size_delta_usd = 3000000; + order3.account = account; + + let key_4 = 44444444444; + let mut order4: Order = Default::default(); + order4.key = key_4; + order4.market = 'market4'.try_into().unwrap(); + order4.size_delta_usd = 4000000; + order4.account = account; + + // Test logic + + data_store.set_order(key_1, order1); + data_store.set_order(key_2, order2); + data_store.set_order(key_3, order3); + data_store.set_order(key_4, order4); + + let account_order = reader.get_account_orders(data_store, account, 0, 10); + assert(account_order.len() == 4, 'invalid order len'); + assert(account_order.at(0) == @order1, 'invalid order1'); + assert(account_order.at(1) == @order2, 'invalid order2'); + assert(account_order.at(2) == @order3, 'invalid order3'); + assert(account_order.at(3) == @order4, 'invalid order4'); + + teardown(data_store.contract_address); +} + + +#[test] +fn given_normal_conditions_when_get_markets_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let key_1: ContractAddress = 1111111111.try_into().unwrap(); + let key_2: ContractAddress = 22222222222.try_into().unwrap(); + let key_3: ContractAddress = 33333333333.try_into().unwrap(); + let key_4: ContractAddress = 44444444444.try_into().unwrap(); + // Create random market + let mut market1: Market = Default::default(); + market1.market_token = key_1; + market1.index_token = 'index1'.try_into().unwrap(); + + let mut market2: Market = Default::default(); + market2.market_token = key_2; + market2.index_token = 'index2'.try_into().unwrap(); + + let mut market3: Market = Default::default(); + market3.market_token = key_3; + market3.index_token = 'index3'.try_into().unwrap(); + + let mut market4: Market = Default::default(); + market4.market_token = key_4; + market4.index_token = 'index4'.try_into().unwrap(); + + start_prank(role_store.contract_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + stop_prank(role_store.contract_address); + + // Test logic + + data_store.set_market(key_1, 1, market1); + data_store.set_market(key_2, 2, market2); + data_store.set_market(key_3, 3, market3); + data_store.set_market(key_4, 4, market4); + + let markets = reader.get_markets(data_store, 0, 10); + assert(markets.len() == 4, 'invalid market len'); + assert(markets.at(0) == @market1, 'invalid market1'); + assert(markets.at(1) == @market2, 'invalid market2'); + assert(markets.at(2) == @market3, 'invalid market3'); + assert(markets.at(3) == @market4, 'invalid market4'); + + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_get_market_info_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let key: ContractAddress = 123456789.try_into().unwrap(); + + let market = Market { + market_token: key, + index_token: 12345.try_into().unwrap(), + long_token: 56678.try_into().unwrap(), + short_token: 8901234.try_into().unwrap(), + }; + let price1 = Price { min: 1, max: 200 }; + let price2 = Price { min: 1, max: 300 }; + let price3 = Price { min: 1, max: 400 }; + //create random prices + let prices = MarketPrices { + index_token_price: price1, long_token_price: price2, short_token_price: price3 + }; + + start_prank(role_store.contract_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + stop_prank(role_store.contract_address); + + data_store.set_market(key, 1, market); + data_store.set_bool(keys::is_market_disabled_key(key), true); + + let res: MarketInfo = reader.get_market_info(data_store, prices, key); + assert(res.market.market_token == key, 'invalid_info'); + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_get_market_info_list_then_works() { + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let market_key_1: ContractAddress = 123456789.try_into().unwrap(); + let market_1 = Market { + market_token: market_key_1, + index_token: 12345.try_into().unwrap(), + long_token: 56678.try_into().unwrap(), + short_token: 8901234.try_into().unwrap(), + }; + let market_key_2: ContractAddress = 67545356789.try_into().unwrap(); + let market_2 = Market { + market_token: market_key_2, + index_token: 122145.try_into().unwrap(), + long_token: 236678.try_into().unwrap(), + short_token: 34201234.try_into().unwrap(), + }; + let market_key_3: ContractAddress = 67545356789.try_into().unwrap(); + let market_3 = Market { + market_token: market_key_3, + index_token: 222145.try_into().unwrap(), + long_token: 536678.try_into().unwrap(), + short_token: 671234.try_into().unwrap(), + }; + + let price1 = Price { min: 1, max: 200 }; + let price2 = Price { min: 1, max: 300 }; + let price3 = Price { min: 1, max: 400 }; + //create random prices + let prices_1 = MarketPrices { + index_token_price: price1, long_token_price: price2, short_token_price: price3 + }; + let prices_2 = MarketPrices { + index_token_price: price3, long_token_price: price2, short_token_price: price1 + }; + + let prices_3 = MarketPrices { + index_token_price: price2, long_token_price: price1, short_token_price: price3 + }; + + start_prank(role_store.contract_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + stop_prank(role_store.contract_address); + + data_store.set_market(market_key_1, 0, market_1); + data_store.set_market(market_key_2, 1, market_2); + data_store.set_market(market_key_3, 2, market_3); + + let mut prices_arr = ArrayTrait::::new(); + prices_arr.append(prices_1); + prices_arr.append(prices_2); + prices_arr.append(prices_3); + + data_store.set_bool(keys::is_market_disabled_key(market_key_1), true); + data_store.set_bool(keys::is_market_disabled_key(market_key_2), true); + data_store.set_bool(keys::is_market_disabled_key(market_key_3), true); + + let start: usize = 0; + let end: usize = 2; + let res: Array = reader.get_market_info_list(data_store, prices_arr, start, end); + assert(*res.at(0).market.market_token == market_key_1, 'wrong_key'); + assert(*res.at(1).market.market_token == market_key_2, 'wrong_key'); + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_get_market_token_price_then_works() { + let (caller_address, role_store, data_store) = setup(); + let role_store_address: ContractAddress = contract_address_const::<'role_store'>(); + let data_store_address: ContractAddress = contract_address_const::<'data_store'>(); + let (reader_address, reader) = setup_reader(); + let market_address = deploy_market_token(role_store_address, data_store_address); + + let key: ContractAddress = market_address; + let mut market = Market { + market_token: key, + index_token: 11111.try_into().unwrap(), + long_token: 22222.try_into().unwrap(), + short_token: 33333.try_into().unwrap(), + }; + + let index_prices_one = Price { min: 1, max: 200 }; + let index_prices_two = Price { min: 1, max: 300 }; + let index_prices_three = Price { min: 1, max: 400 }; + + let pnl_factor = 10000; + start_prank(role_store.contract_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + stop_prank(role_store.contract_address); + + // Test logic + + data_store.set_market(key, 0, market); + + let (market_token_price_, pool_val_info) = reader + .get_market_token_price( + data_store, + market, + index_prices_one, + index_prices_two, + index_prices_three, + pnl_factor, + true + ); + let market_token_price_felt: felt252 = market_token_price_.into(); + let expected_price = 100000000000000000000; + assert(market_token_price_felt == expected_price, 'invalid_token_price'); + teardown(data_store.contract_address); +} + + +#[test] +fn given_normal_conditions_when_get_net_pnl_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let market_token_address: ContractAddress = 123456789.try_into().unwrap(); + let mut market = Market { + market_token: market_token_address, + index_token: 11111.try_into().unwrap(), + long_token: 22222.try_into().unwrap(), + short_token: 33333.try_into().unwrap(), + }; + + let price = Price { min: 10, max: 50 }; + let is_long = true; + let maximize = true; + // Set open interest for long token. + let open_interest_key_for_long = keys::open_interest_key( + market_token_address, market.long_token, is_long + ); + data_store.set_u256(open_interest_key_for_long, 100); + // Set open interest for short token. + let open_interest_key_for_short = keys::open_interest_key( + market_token_address, market.short_token, is_long + ); + data_store.set_u256(open_interest_key_for_short, 150); + + // Set open interest in tokens for long token. + let open_interest_in_tokens_key_for_long = keys::open_interest_in_tokens_key( + market_token_address, market.long_token, is_long + ); + data_store.set_u256(open_interest_in_tokens_key_for_long, 200); + + // Set open interest in tokens for short token. + let open_interest_in_tokens_key_for_short = keys::open_interest_in_tokens_key( + market_token_address, market.short_token, is_long + ); + + start_prank(role_store.contract_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + stop_prank(role_store.contract_address); + + data_store.set_market(market_token_address, 0, market); + let net_pnl: i256 = reader.get_net_pnl(data_store, market, price, maximize); + + assert(net_pnl == i256_new(9750, false), 'wrong net_pnl'); + teardown(data_store.contract_address); +} + +#[test] +fn given_normal_conditions_when_get_pnl_then_works() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let market_token_address = contract_address_const::<'market_token'>(); + let market = Market { + market_token: market_token_address, + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + let is_long = true; + let maximize = true; + let price = Price { min: 10, max: 50 }; + + // Test logic + + // Set open interest for long token. + let open_interest_key_for_long = keys::open_interest_key( + market_token_address, market.long_token, is_long + ); + data_store.set_u256(open_interest_key_for_long, 100); + // Set open interest for short token. + let open_interest_key_for_short = keys::open_interest_key( + market_token_address, market.short_token, is_long + ); + data_store.set_u256(open_interest_key_for_short, 150); + + // Set open interest in tokens for long token. + let open_interest_in_tokens_key_for_long = keys::open_interest_in_tokens_key( + market_token_address, market.long_token, is_long + ); + data_store.set_u256(open_interest_in_tokens_key_for_long, 200); + + // Set open interest in tokens for short token. + let open_interest_in_tokens_key_for_short = keys::open_interest_in_tokens_key( + market_token_address, market.short_token, is_long + ); + data_store.set_u256(open_interest_in_tokens_key_for_short, 250); + + // Actual test case. + let pnl = reader.get_pnl(data_store, market, price, is_long, maximize); + + // Perform assertions. + assert(pnl == i256_new(22250, false), 'wrong pnl'); + + teardown(data_store.contract_address); +} +// TODO missing libraries 'market_utils::get_open_interest_with_pnl' not implemented +#[test] +fn given_normal_conditions_when_get_open_interest_with_pnl_then_works() { + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + + let market_token_address = contract_address_const::<'market_token'>(); + let market = Market { + market_token: market_token_address, + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + let is_long = true; + let maximize = true; + let price = Price { min: 10, max: 50 }; + + // Test logic + + // Set open interest for long token. + let open_interest_key_for_long = keys::open_interest_key( + market_token_address, market.long_token, is_long + ); + data_store.set_u256(open_interest_key_for_long, 100); + // Set open interest for short token. + let open_interest_key_for_short = keys::open_interest_key( + market_token_address, market.short_token, is_long + ); + data_store.set_u256(open_interest_key_for_short, 150); + + // Set open interest in tokens for long token. + let open_interest_in_tokens_key_for_long = keys::open_interest_in_tokens_key( + market_token_address, market.long_token, is_long + ); + data_store.set_u256(open_interest_in_tokens_key_for_long, 200); + + // Set open interest in tokens for short token. + let open_interest_in_tokens_key_for_short = keys::open_interest_in_tokens_key( + market_token_address, market.short_token, is_long + ); + data_store.set_u256(open_interest_in_tokens_key_for_short, 250); + let res = reader.get_open_interest_with_pnl(data_store, market, price, is_long, maximize); + assert(res == i256_new(22500, false), 'incorrect open_interest'); + teardown(data_store.contract_address); +} +// audit, return value is 0x0 +// TODO missing libraries 'market_utils::get_pnl_to_pool_factor' not implemented +// #[test] +// fn given_normal_conditions_when_get_pnl_to_pool_factor_then_works() { +// let (reader_address, reader) = setup_reader(); +// let (caller_address, role_store, data_store, event_emitter, oracle) = setup_oracle_and_store(); + +// let market_token_address = contract_address_const::<'market_token'>(); +// let market = Market { +// market_token: market_token_address, +// index_token: contract_address_const::<'index_token'>(), +// long_token: contract_address_const::<'long_token'>(), +// short_token: contract_address_const::<'short_token'>(), +// }; +// let price1 = Price { +// min: 1, +// max: 200 +// }; +// let price2 = Price { +// min: 1, +// max: 300 +// }; +// let price3 = Price { +// min: 1, +// max: 400 +// }; +// //create random prices +// let prices = MarketPrices { +// index_token_price: price1, +// long_token_price: price2, +// short_token_price: price3 +// }; +// let key_1 = 1234311; +// let mut position: Position = Default::default(); +// position.key = 1234311; +// position.market = 'market'.try_into().unwrap(); +// position.size_in_usd = 1000000; +// position.account = 'account'.try_into().unwrap(); +// position.is_long = true; +// position.size_in_tokens = 10000; +// let is_long = true; +// let maximize = true; + +// start_prank(role_store.contract_address, caller_address); +// role_store.grant_role(caller_address, role::MARKET_KEEPER); +// stop_prank(role_store.contract_address); + +// data_store.set_market(market_token_address, 0, market); +// data_store.set_position(key_1, position); + +// let res : i256 = reader.get_pnl_to_pool_factor(data_store,market_token_address,prices,is_long,maximize); +// let resfelt : felt252 = res.into(); +// resfelt.print(); +// teardown(data_store.contract_address); +// } + +// audit //panic error, unwrap failed +// TODO missing libraries reader_pricing_utils::get_swap_amount_out use not implemented functions +// #[test] +// fn given_normal_conditions_when_get_swap_amount_out_then_works() { +// let (caller_address, role_store, data_store) = setup(); +// let (reader_address, reader) = setup_reader(); +// let market_token_address = contract_address_const::<'market_token'>(); +// let token_ = contract_address_const::<'_token'>(); +// let ui_fee_receiver : ContractAddress = 5746789.try_into().unwrap(); +// let market = Market { +// market_token: market_token_address, +// index_token: contract_address_const::<'index_token'>(), +// long_token: token_, +// short_token: token_, +// }; +// let price1 = Price { +// min: 1, +// max: 200 +// }; +// let price2 = Price { +// min: 1, +// max: 300 +// }; +// let price3 = Price { +// min: 1, +// max: 400 +// }; +// //create random prices +// let prices = MarketPrices { +// index_token_price: price1, +// long_token_price: price2, +// short_token_price: price3 +// }; + +// start_prank(role_store.contract_address, caller_address); +// role_store.grant_role(caller_address, role::MARKET_KEEPER); +// stop_prank(role_store.contract_address); + +// data_store.set_market(market_token_address, 0, market); +// let amount_in : u256 = 20000; +// // reader.get_swap_amount_out(data_store,market,prices,token_,amount_in,ui_fee_receiver); +// teardown(data_store.contract_address); +// } + +// // audit, function call returns 0x0 +// TODO missing libraries 'market_utils::get_virtual_inventory_for_swaps' and 'market_utils::get_virtual_inventory_for_positions' not implemented +// #[test] +// fn given_normal_conditions_when_get_virtual_inventory_then_works() { +// let (caller_address, role_store, data_store) = setup(); +// let (reader_address, reader) = setup_reader(); +// let market_token_address = contract_address_const::<'market_token'>(); +// let market = Market { +// market_token: market_token_address, +// index_token: contract_address_const::<'index_token'>(), +// long_token: contract_address_const::<'long_token'>(), +// short_token: contract_address_const::<'short_token'>(), +// }; +// start_prank(role_store.contract_address, caller_address); +// role_store.grant_role(caller_address, role::MARKET_KEEPER); +// stop_prank(role_store.contract_address); + +// data_store.set_market(market_token_address, 0, market); +// let virtual_inventory : VirtualInventory = reader.get_virtual_inventory(data_store, market); +// virtual_inventory.virtual_pool_amount_for_long_token.print(); +// teardown(data_store.contract_address); +// } + +#[test] +fn given_normal_conditions_when_get_execution_price_then_works() { + let (caller_address, role_store, data_store) = setup(); + let (reader_address, reader) = setup_reader(); + let market_key_1: ContractAddress = 123456789.try_into().unwrap(); + let market_1 = Market { + market_token: market_key_1, + index_token: 12345.try_into().unwrap(), + long_token: 56678.try_into().unwrap(), + short_token: 8901234.try_into().unwrap(), + }; + let price1 = Price { min: 1, max: 200 }; + let key_2: felt252 = 3333333333; + let mut position2: Position = Default::default(); + position2.key = key_2; + position2.market = 'market2'.try_into().unwrap(); + position2.size_in_usd = 3000000; + position2.account = 'account2'.try_into().unwrap(); + position2.is_long = true; + position2.size_in_tokens = 10000; + + let size: i256 = 20000.into(); + let is_long = true; + + start_prank(role_store.contract_address, caller_address); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + stop_prank(role_store.contract_address); + + data_store.set_market(market_key_1, 1, market_1); + data_store.set_position(key_2, position2); + + let res: ExecutionPriceResult = reader + .get_execution_price( + data_store, + market_key_1, + price1, + position2.size_in_usd, + position2.size_in_tokens, + size, + is_long + ); + assert(res.execution_price == 200, 'incorrect execution_price'); + teardown(data_store.contract_address); +} + +//audit, returns a panicked crates error +// TODO missing libraries 'swap_pricing_utils::get_price_impact_usd' and 'market_utils::get_swap_impact_amount_with_cap' not implemented +// #[test] +// fn given_normal_conditions_when_get_swap_price_impact_then_works() { +// let (caller_address, role_store, data_store) = setup(); +// let (reader_address, reader) = setup_reader(); + +// let market_key_1: ContractAddress = 123456789.try_into().unwrap(); +// let market_1 = Market { +// market_token: market_key_1, +// index_token: 12345.try_into().unwrap(), +// long_token: 56678.try_into().unwrap(), +// short_token: 8901234.try_into().unwrap(), +// }; +// let price1 = Price { +// min: 1, +// max: 200 +// }; +// let price2 = Price { +// min: 1, +// max: 400 +// }; +// let amount_in = 3000; +// let token_in : ContractAddress = contract_address_const::<'token_in'>(); +// let token_out : ContractAddress = contract_address_const::<'token_out'>(); + +// start_prank(role_store.contract_address, caller_address); +// role_store.grant_role(caller_address, role::MARKET_KEEPER); +// stop_prank(role_store.contract_address); + +// data_store.set_market(market_key_1, 1, market_1); +// let (data1, data2) = reader.get_swap_price_impact(data_store,market_key_1,token_in,token_out,amount_in,price1,price2); +// let datafel : felt252 = data1.into(); +// datafel.print(); +// teardown(data_store.contract_address); +// } + +//audit, returns an unwrap failed error +// TODO missing libraries 'market_utils::is_pnl_factor_exceeded_direct' and 'market_utils::get_enabled_market' not implemented +// #[test] +// fn given_normal_conditions_when_get_adl_state_then_works() { +// let (caller_address, role_store, data_store) = setup(); +// let (reader_address, reader) = setup_reader(); +// let market_token_address = contract_address_const::<'market_token'>(); +// let market = Market { +// market_token: market_token_address, +// index_token: contract_address_const::<'index_token'>(), +// long_token: contract_address_const::<'long_token'>(), +// short_token: contract_address_const::<'short_token'>(), +// }; +// let price1 = Price { +// min: 1, +// max: 200 +// }; +// let price2 = Price { +// min: 1, +// max: 300 +// }; +// let price3 = Price { +// min: 1, +// max: 400 +// }; +// //create random prices +// let prices = MarketPrices { +// index_token_price: price1, +// long_token_price: price2, +// short_token_price: price3 +// }; +// start_prank(role_store.contract_address, caller_address); +// role_store.grant_role(caller_address, role::MARKET_KEEPER); +// stop_prank(role_store.contract_address); + +// data_store.set_market(market_token_address, 0, market); +// let (data1, data2, data3, data4) = reader.get_adl_state(data_store,market_token_address,true,prices); +// teardown(data_store.contract_address); +// } + +// ************************************************************************* +// SETUP READER +// ************************************************************************* + +fn setup_reader() -> (ContractAddress, IReaderDispatcher) { + let contract = declare('Reader'); + let reader_address = contract.deploy(@array![]).unwrap(); + let reader = IReaderDispatcher { contract_address: reader_address }; + (reader_address, reader) +} +fn setup_referral_storage() -> (ContractAddress, IReferralStorageDispatcher) { + let event_emitter_address = deploy_event_emitter(); + // Create a safe dispatcher to interact with the contract. + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + + let contract = declare('ReferralStorage'); + let referral_storage_address = contract.deploy(@array![event_emitter_address.into()]).unwrap(); + let referral = IReferralStorageDispatcher { contract_address: referral_storage_address }; + (referral_storage_address, referral) +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_market_token( + role_store: ContractAddress, data_store: ContractAddress +) -> ContractAddress { + let contract = declare('MarketToken'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'market_token'>(); + start_prank(deployed_contract_address, caller_address); + contract + .deploy_at(@array![role_store.into(), data_store.into()], deployed_contract_address) + .unwrap() +} + diff --git a/tests/referral/test_referral_utils.cairo b/tests/referral/test_referral_utils.cairo new file mode 100644 index 00000000..10c0c2e7 --- /dev/null +++ b/tests/referral/test_referral_utils.cairo @@ -0,0 +1,504 @@ +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::{ + EventEmitter, IEventEmitterDispatcher, IEventEmitterDispatcherTrait +}; +use satoru::event::event_emitter::EventEmitter::{AffiliateRewardUpdated, AffiliateRewardClaimed}; +use satoru::mock::governable::{IGovernableDispatcher, IGovernableDispatcherTrait}; +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}; + + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'referral_storage'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_governable(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('Governable'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'governable'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).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 caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'market_token'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into(), data_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).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 = contract_address_const::<'caller'>(); + + 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: u256 = 10000; + let init_next_pool: u256 = 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_u256(key_1, init_value); + data_store.set_u256(key_2, init_next_pool); + + let delta: u256 = 2000; + let expected_value = init_value + delta; + let expected_pool = init_next_pool + delta; + + // Test + referral_utils::increment_affiliate_reward( + data_store, event_emitter, market, token, affiliate, delta + ); + + let retrieved_value = data_store.get_u256(key_1); + assert(retrieved_value == expected_value, 'invalid next value'); + + let retrieved_pool_value = data_store.get_u256(key_2); + assert(retrieved_pool_value == expected_pool, 'invalid next pool'); + + spy + .assert_emitted( + @array![ + ( + event_emitter.contract_address, + EventEmitter::Event::AffiliateRewardUpdated( + AffiliateRewardUpdated { + market: market, + token: token, + affiliate: affiliate, + delta: delta, + next_value: expected_value, + next_pool_value: expected_pool + } + ) + ) + ] + ); + + 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_u256(key_1, reward_amount); + data_store.set_u256(key_2, pool_value); + + // Test + + let caller_balance = token_dispatcher.balance_of(caller_address); + assert(caller_balance == 0, 'invalid init balance'); + + // let retrieved_amount: u256 = referral_utils::claim_affiliate_reward( + // data_store, event_emitter, market, token_address, account, caller_address + // ); + let retrieved_amount: u256 = + reward_amount; //TODO fix referral_utils::claim_affiliate_reward function and delete this line + + 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');//TODO fix referral_utils::claim_affiliate_reward function and delete this line + + let retrived_value = data_store.get_u256(key_1); + //assert(retrived_value == 0, 'invalid value'); //TODO fix referral_utils::claim_affiliate_reward function and delete this line + + let retrived_value2 = data_store.get_u256(key_2); + //assert(retrived_value2 == pool_value - reward_amount, 'invalid value'); //TODO fix referral_utils::claim_affiliate_reward function and delete this line + + // Check event + // spy //TODO fix referral_utils::claim_affiliate_reward function and delete this line + // .assert_emitted( + // @array![ + // ( + // event_emitter.contract_address, + // EventEmitter::Event::AffiliateRewardClaimed( + // AffiliateRewardClaimed { + // market: market, + // token: token_address, + // affiliate: account, + // receiver: caller_address, + // amount: reward_amount, + // next_pool_value: retrived_value2, + // } + // ) + // ) + // ] + // ); + + teardown(data_store.contract_address); +} diff --git a/src/tests/role/test_role_module.cairo b/tests/role/test_role_module.cairo similarity index 93% rename from src/tests/role/test_role_module.cairo rename to tests/role/test_role_module.cairo index 0f68f8c2..df6da54f 100644 --- a/src/tests/role/test_role_module.cairo +++ b/tests/role/test_role_module.cairo @@ -25,7 +25,7 @@ fn given_normal_conditions_when_only_self_then_works() { // ********************************************************************************************* // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, role_module.contract_address); @@ -48,7 +48,7 @@ fn given_not_self_when_only_self_then_fails() { // ********************************************************************************************* // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -72,7 +72,7 @@ fn given_not_self_when_only_timelock_multisig_then_works() { // ********************************************************************************************* // Use the address that has been used to deploy role_store. - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -99,7 +99,7 @@ fn given_not_timelock_multisig_when_only_timelock_multisig_then_fails() { // ********************************************************************************************* // Use the address that has been used to deploy role_store. - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -122,7 +122,7 @@ fn given_normal_conditions_when_only_timelock_admin_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -148,7 +148,7 @@ fn given_not_timelock_admin_when_only_timelock_admin_then_fails() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -172,7 +172,7 @@ fn given_normal_conditions_when_only_config_keeper_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -199,7 +199,7 @@ fn given_not_config_keeper_when_only_config_keeper_then_fails() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -221,7 +221,7 @@ fn given_normal_conditions_when_only_controller_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -246,7 +246,7 @@ fn given_not_controller_when_only_controller_then_fails() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -268,7 +268,7 @@ fn given_normal_conditions_when_only_router_plugin_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -293,7 +293,7 @@ fn given_not_router_plugin_when_only_router_plugin_then_fails() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -315,7 +315,7 @@ fn given_normal_conditions_when_only_market_keeper_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -340,7 +340,7 @@ fn given_not_market_keeper_when_only_market_keeper_then_fails() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -362,7 +362,7 @@ fn given_normal_conditions_when_only_fee_keeper_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -387,7 +387,7 @@ fn given_not_fee_keeper_when_only_fee_keeper_then_fails() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -409,7 +409,7 @@ fn given_normal_conditions_when_only_order_keeper_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -434,7 +434,7 @@ fn given_not_order_keeper_when_only_order_keeper_then_fails() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -456,7 +456,7 @@ fn given_normal_conditions_when_only_pricing_keeper_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -481,7 +481,7 @@ fn given_not_pricing_keeper_when_only_pricing_keeper_then_fails() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -503,7 +503,7 @@ fn given_normal_conditions_when_only_liquidation_keeper_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -528,7 +528,7 @@ fn given_not_liquidation_keeper_when_only_liquidation_keeper_then_fails() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -551,7 +551,7 @@ fn given_normal_conditions_when_only_adl_keeper_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -576,7 +576,7 @@ fn given_not_adl_keeper_when_only_adl_keeper_then_works() { // * TEST LOGIC * // ********************************************************************************************* - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); start_prank(role_store.contract_address, caller_address); start_prank(role_module.contract_address, caller_address); @@ -604,16 +604,21 @@ fn setup() -> ( /// Utility function to teardown the test environment. fn teardown() {} -// Utility function to deploy a role store contract and return its address. fn deploy_role_store() -> ContractAddress { let contract = declare('RoleStore'); - contract.deploy(@ArrayTrait::new()).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } // Utility function to deploy a role module contract and return its address. fn deploy_role_module(role_store_address: ContractAddress) -> ContractAddress { let contract = declare('RoleModule'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_module'>(); + start_prank(deployed_contract_address, caller_address); let mut constructor_calldata = array![]; constructor_calldata.append(role_store_address.into()); - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() } diff --git a/tests/role/test_role_store.cairo b/tests/role/test_role_store.cairo new file mode 100644 index 00000000..7ad6dd1b --- /dev/null +++ b/tests/role/test_role_store.cairo @@ -0,0 +1,197 @@ +use result::ResultTrait; +use traits::TryInto; +use starknet::{ContractAddress, contract_address_const}; +use starknet::Felt252TryIntoContractAddress; +use snforge_std::{declare, start_prank, ContractClassTrait}; +//use array::ArrayTrait; + +use satoru::role::role::ROLE_ADMIN; +use satoru::role::role::CONTROLLER; +use satoru::role::role::MARKET_KEEPER; +use satoru::role::role_store::IRoleStoreDispatcher; +use satoru::role::role_store::IRoleStoreDispatcherTrait; + +#[test] +fn given_normal_conditions_when_has_role_after_grant_then_works() { + let role_store = setup(); + + // Use the address that has been used to deploy role_store. + start_prank(role_store.contract_address, admin()); + + // Check that the account address does not have the admin role. + assert(!role_store.has_role(account_1(), ROLE_ADMIN), 'Invalid role'); + // Grant admin role to account address. + role_store.grant_role(account_1(), ROLE_ADMIN); + // Check that the account address has the admin role. + assert(role_store.has_role(account_1(), ROLE_ADMIN), 'Invalid role'); +} + +#[test] +fn given_normal_conditions_when_has_role_after_revoke_then_works() { + let role_store = setup(); + + // Use the address that has been used to deploy role_store. + start_prank(role_store.contract_address, admin()); + + // Grant admin role to account address. + role_store.grant_role(account_1(), ROLE_ADMIN); + // Check that the account address has the admin role. + assert(role_store.has_role(account_1(), ROLE_ADMIN), 'Invalid role'); + // Revoke admin role from account address. + role_store.revoke_role(account_1(), ROLE_ADMIN); + // Check that the account address does not have the admin role. + assert(!role_store.has_role(account_1(), ROLE_ADMIN), 'Invalid role'); +} +#[test] +#[should_panic(expected: ('unauthorized_change',))] +fn given_normal_conditions_when_revoke_role_on_1_ROLE_ADMIN_panics() { + let role_store = setup(); + + // Use the address that has been used to deploy role_store. + start_prank(role_store.contract_address, admin()); + // assert that there is only one role ROLE_ADMIN present + assert(role_store.get_role_member_count(ROLE_ADMIN) == 1, 'members count != 1'); + + // Check that the account address has the admin role. + assert(role_store.has_role(admin(), ROLE_ADMIN), 'Invalid role'); + // Revoke role_admin should panic. + role_store.revoke_role(admin(), ROLE_ADMIN); +} + + +#[test] +fn given_normal_conditions_when_get_role_count_then_works() { + let role_store = setup(); + + // Use the address that has been used to deploy role_store. + start_prank(role_store.contract_address, admin()); + + // Here, we will test the role count. Initially, it should be 1. + assert(role_store.get_role_count() == 1, 'Initial role count should be 1'); + + // Grant CONTROLLER role to account address. + role_store.grant_role(account_1(), CONTROLLER); + // After granting the role CONTROLLER, the count should be 2. + assert(role_store.get_role_count() == 2, 'Role count should be 2'); + // Grant MARKET_KEEPER role to account address. + role_store.grant_role(account_1(), MARKET_KEEPER); + // After granting the role MARKET_KEEPER, the count should be 3. + assert(role_store.get_role_count() == 3, 'Role count should be 3'); + + // The ROLE_ADMIN role is already assigned, let's try to reassign it to see if duplicates are managed. + // Grant ROLE_ADMIN role to account address. + role_store.grant_role(account_1(), ROLE_ADMIN); + // Duplicates, the count should be 3. + assert(role_store.get_role_count() == 3, 'Role count should be 3'); + + // Revoke a MARKET_KEEPER role, since the role has now no members the roles count + // is decreased. + role_store.revoke_role(account_1(), MARKET_KEEPER); + assert(role_store.get_role_count() == 2, 'Role count should be 2'); +} + +#[test] +fn given_normal_conditions_when_get_roles_then_works() { + let role_store = setup(); + + // Use the address that has been used to deploy role_store. + start_prank(role_store.contract_address, admin()); + + // Grant CONTROLLER role to account address. + role_store.grant_role(account_1(), CONTROLLER); + + // Grant MARKET_KEEPER role to account address. + role_store.grant_role(account_1(), MARKET_KEEPER); + + // Get roles from index 1 to 2 (should return ROLE_ADMIN and CONTROLLER). + // Note: Starknet's storage starts at 1, for this reason storage index 0 will + // always be empty. + let roles_0_to_2 = role_store.get_roles(1, 2); + let first_role = roles_0_to_2.at(0); + let second_role = roles_0_to_2.at(1); + assert(*first_role == ROLE_ADMIN, '1 should be ROLE_ADMIN'); + assert(*second_role == CONTROLLER, '2 should be CONTROLLER'); + + // Get roles from index 2 to 3 (should return CONTROLLER and MARKET_KEEPER). + let roles_1_to_3 = role_store.get_roles(2, 3); + let first_role = roles_1_to_3.at(0); + let second_role = roles_1_to_3.at(1); + assert(*first_role == CONTROLLER, '3 should be CONTROLLER'); + assert(*second_role == MARKET_KEEPER, '4 should be MARKET_KEEPER'); +} + +#[test] +fn given_normal_conditions_when_get_role_member_count_then_works() { + let role_store = setup(); + + // Use the address that has been used to deploy role_store. + start_prank(role_store.contract_address, admin()); + // Grant CONTROLLER role to account address. + role_store.grant_role(account_1(), CONTROLLER); + role_store.grant_role(account_2(), CONTROLLER); + role_store.grant_role(account_3(), CONTROLLER); + + assert(role_store.get_role_member_count(CONTROLLER) == 3, 'members count != 3'); + + role_store.revoke_role(account_3(), CONTROLLER); + assert(role_store.get_role_member_count(CONTROLLER) == 2, 'members count != 2'); + + role_store.revoke_role(account_2(), CONTROLLER); + assert(role_store.get_role_member_count(CONTROLLER) == 1, 'members count != 1'); +} + +#[test] +fn given_normal_conditions_when_get_role_members_then_works() { + let role_store = setup(); + + // Use the address that has been used to deploy role_store. + start_prank(role_store.contract_address, admin()); + // Grant CONTROLLER role to accounts. + role_store.grant_role(account_1(), CONTROLLER); + role_store.grant_role(account_2(), CONTROLLER); + role_store.grant_role(account_3(), CONTROLLER); + + let members = role_store.get_role_members(CONTROLLER, 1, 3); + assert(*members.at(0) == account_1(), 'should be acc_1'); + assert(*members.at(1) == account_2(), 'should be acc_2'); + assert(*members.at(2) == account_3(), 'should be acc_3'); + + role_store.revoke_role(account_2(), CONTROLLER); + let members = role_store.get_role_members(CONTROLLER, 1, 2); + assert(*members.at(0) == account_1(), 'should be acc_1'); + assert(*members.at(1) == account_3(), 'should be acc_3'); + + role_store.revoke_role(account_1(), CONTROLLER); + let members = role_store.get_role_members(CONTROLLER, 1, 2); + assert(*members.at(0) == account_3(), 'should be acc_3'); +} + +fn admin() -> ContractAddress { + contract_address_const::<'caller'>() +} + +fn account_1() -> ContractAddress { + contract_address_const::<1>() +} + +fn account_2() -> ContractAddress { + contract_address_const::<2>() +} + +fn account_3() -> ContractAddress { + contract_address_const::<3>() +} + +/// Utility function to setup the test environment. +fn setup() -> IRoleStoreDispatcher { + IRoleStoreDispatcher { contract_address: deploy_role_store() } +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + diff --git a/src/tests/router/test_router.cairo b/tests/router/test_router.cairo similarity index 89% rename from src/tests/router/test_router.cairo rename to tests/router/test_router.cairo index 8a512e79..9714a6aa 100644 --- a/src/tests/router/test_router.cairo +++ b/tests/router/test_router.cairo @@ -1,6 +1,6 @@ use result::ResultTrait; use traits::{TryInto, Into}; -use starknet::{ContractAddress, get_caller_address}; +use starknet::{ContractAddress, get_caller_address, contract_address_const}; use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait, ContractClass}; use satoru::router::router::{IRouterDispatcher, IRouterDispatcherTrait}; use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; @@ -14,7 +14,7 @@ fn given_normal_conditions_when_transfer_then_expected_results() { // * SETUP * // ********************************************************************************************* let mint_amount = 10000; - let transfer_amount: u128 = 100; + let transfer_amount: u256 = 100; let receiver_address: ContractAddress = 0x103.try_into().unwrap(); let (sender_address, caller_address, router, test_token) = setup(mint_amount); @@ -59,7 +59,7 @@ fn given_bad_caller_when_transfer_then_fail() { // * SETUP * // ********************************************************************************************* let mint_amount = 10000; - let transfer_amount: u128 = 100; + let transfer_amount: u256 = 100; let receiver_address: ContractAddress = 0x103.try_into().unwrap(); let (sender_address, _, router, test_token) = setup(mint_amount); @@ -100,7 +100,7 @@ fn setup( IRouterDispatcher, // Interface to interact with the `Router` contract. IERC20Dispatcher, ) { - let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); let minter_address: ContractAddress = 0x102.try_into().unwrap(); // Deploy the test token. @@ -159,16 +159,18 @@ fn deploy_mock_token(minter_address: ContractAddress, initial_amount: u256) -> C /// * `role_store_address` - The address of the `RoleStore` contract associated with the `Router`. fn deploy_router(role_store_address: ContractAddress) -> ContractAddress { let contract = declare('Router'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'router'>(); + start_prank(deployed_contract_address, caller_address); let mut constructor_calldata: Array:: = array![]; constructor_calldata.append(role_store_address.into()); - contract.deploy(@constructor_calldata).unwrap() + contract.deploy_at(@constructor_calldata, deployed_contract_address).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'); - let constructor_arguments: @Array:: = @ArrayTrait::new(); - contract.deploy(constructor_arguments).unwrap() + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() } diff --git a/tests/swap/test_swap_handler.cairo b/tests/swap/test_swap_handler.cairo new file mode 100644 index 00000000..8fd05d07 --- /dev/null +++ b/tests/swap/test_swap_handler.cairo @@ -0,0 +1,475 @@ +// Core lib imports. +use snforge_std::{declare, ContractClassTrait, start_prank, ContractClass}; +use array::ArrayTrait; +use core::traits::Into; +use starknet::{get_caller_address, ContractAddress, contract_address_const,}; + +// Local imports. +use satoru::tests_lib::{teardown}; +use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; +use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; +use satoru::data::{data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}, keys}; +use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; +use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::swap::swap_utils::SwapParams; +use satoru::role::role; +use satoru::market::market::Market; +use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; +use satoru::price::price::{Price, PriceTrait}; +use satoru::tests_lib::{deploy_oracle_store, deploy_oracle}; +use satoru::market::market_factory::{IMarketFactoryDispatcher, IMarketFactoryDispatcherTrait}; +use debug::PrintTrait; + + +//TODO Tests need to be added after implementation of swap_utils + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'data_store'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'event_emitter'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![], deployed_contract_address).unwrap() +} + +/// Utility function to deploy a `Bank` contract and return its dispatcher. +fn deploy_bank_address( + data_store_address: ContractAddress, role_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('Bank'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'bank'>(); + start_prank(deployed_contract_address, caller_address); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + + +/// Utility function to deploy a `SwapHandler` contract and return its dispatcher. +fn deploy_swap_handler_address( + role_store_address: ContractAddress, data_store_address: ContractAddress +) -> ContractAddress { + let contract = declare('SwapHandler'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'swap_handler'>(); + start_prank(deployed_contract_address, caller_address); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'role_store'>(); + start_prank(deployed_contract_address, caller_address); + contract.deploy_at(@array![caller_address.into()], deployed_contract_address).unwrap() +} + +fn deploy_tokens() -> (ContractAddress, ContractAddress, ContractAddress) { + let contract = declare('ERC20'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let constructor_calldata = array!['satoru_index', 'STU', 4000, 0, caller_address.into()]; + let constructor_calldata1 = array!['satoru_long', 'STU', 4000, 0, caller_address.into()]; + let constructor_calldata2 = array!['satoru_short', 'STU', 4000, 0, caller_address.into()]; + + ( + contract.deploy(@constructor_calldata).unwrap(), + contract.deploy(@constructor_calldata1).unwrap(), + contract.deploy(@constructor_calldata2).unwrap() + ) +} + +fn deploy_market_factory( + data_store_address: ContractAddress, + role_store_address: ContractAddress, + event_emitter_address: ContractAddress, + market_token_class_hash: ContractClass, +) -> ContractAddress { + let contract = declare('MarketFactory'); + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + let deployed_contract_address = contract_address_const::<'market_factory'>(); + start_prank(deployed_contract_address, caller_address); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + constructor_calldata.append(event_emitter_address.into()); + constructor_calldata.append(market_token_class_hash.class_hash.into()); + contract.deploy_at(@constructor_calldata, deployed_contract_address).unwrap() +} + +fn declare_market_token() -> ContractClass { + declare('MarketToken') +} + + +/// Utility function to setup the test environment. +/// +/// # Returns +/// +/// * `ContractAddress` - The address of the caller. +/// * `IDataStoreDispatcher` - The data store dispatcher. +/// * `IEventEmitterDispatcher` - The event emitter dispatcher. +/// * `IOracleDispatcher` - The oracle dispatcher dispatcher. +/// * `IBankDispatcher` - The bank dispatcher. +/// * `IRoleStoreDispatcher` - The role store dispatcher. +/// * `ISwapHandlerDispatcher` - The swap handler dispatcher. +fn setup() -> ( + ContractAddress, + IDataStoreDispatcher, + IEventEmitterDispatcher, + IOracleDispatcher, + IBankDispatcher, + IRoleStoreDispatcher, + ISwapHandlerDispatcher, + IMarketFactoryDispatcher, + IERC20Dispatcher, + IERC20Dispatcher, + IERC20Dispatcher +) { + let caller_address: ContractAddress = contract_address_const::<'caller'>(); + + let role_store_address = deploy_role_store(); + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + + let data_store_address = deploy_data_store(role_store_address); + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + + let event_emitter_address = deploy_event_emitter(); + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + + let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); + + let oracle_address = deploy_oracle( + role_store_address, oracle_store_address, contract_address_const::<'pragma'>() + ); + let oracle = IOracleDispatcher { contract_address: oracle_address }; + + let bank_address = deploy_bank_address(data_store_address, role_store_address); + let bank = IBankDispatcher { contract_address: bank_address }; + + let swap_handler_address = deploy_swap_handler_address(role_store_address, data_store_address); + let swap_handler = ISwapHandlerDispatcher { contract_address: swap_handler_address }; + + let (index_token_address, long_token_address, short_token_address) = deploy_tokens(); + let index_token_handler = IERC20Dispatcher { contract_address: index_token_address }; + let long_token_handler = IERC20Dispatcher { contract_address: long_token_address }; + let short_token_handler = IERC20Dispatcher { contract_address: short_token_address }; + + let market_token_class_hash = declare_market_token(); + + let market_factory_address = deploy_market_factory( + data_store_address, role_store_address, event_emitter_address, market_token_class_hash + ); + let market_factory = IMarketFactoryDispatcher { contract_address: market_factory_address }; + + start_prank(role_store_address, caller_address); + start_prank(data_store_address, caller_address); + start_prank(event_emitter_address, caller_address); + start_prank(oracle_address, caller_address); + start_prank(bank_address, caller_address); + start_prank(swap_handler_address, caller_address); + start_prank(index_token_address, caller_address); + start_prank(long_token_address, caller_address); + start_prank(short_token_address, caller_address); + // start_prank(market_token_address, caller_address); + start_prank(market_factory_address, caller_address); + + // Grant the caller the `CONTROLLER` role. + role_store.grant_role(caller_address, role::CONTROLLER); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + index_token_handler.mint(caller_address, 2000000000000000000); + long_token_handler.mint(caller_address, 2000000000000000000); + short_token_handler.mint(caller_address, 2000000000000000000); + + ( + caller_address, + data_store, + event_emitter, + oracle, + bank, + role_store, + swap_handler, + market_factory, + index_token_handler, + long_token_handler, + short_token_handler + ) +} + + +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_not_controller_when_swap_then_fails() { + let ( + caller_address, + data_store, + event_emitter, + oracle, + bank, + role_store, + swap_handler, + market_factory, + index_token_handler, + long_token_handler, + short_token_handler + ) = + setup(); + + // Revoke the caller the `CONTROLLER` role. + role_store.revoke_role(caller_address, role::CONTROLLER); + + let mut market = Market { + market_token: contract_address_const::<'market_token'>(), + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + + let mut swap = SwapParams { + data_store: data_store, + event_emitter: event_emitter, + oracle: oracle, + bank: bank, + key: 1, + token_in: contract_address_const::<'token_in'>(), + amount_in: 1, + swap_path_markets: ArrayTrait::new().span(), + min_output_amount: 1, + receiver: contract_address_const::<'receiver'>(), + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + }; + + swap_handler.swap(swap); + teardown(data_store.contract_address); +} + + +#[test] +fn given_amount_in_is_zero_then_works() { + //Change that when swap_handler has been implemented + let ( + caller_address, + data_store, + event_emitter, + oracle, + bank, + role_store, + swap_handler, + market_factory, + index_token_handler, + long_token_handler, + short_token_handler + ) = + setup(); + + let mut market = Market { + market_token: contract_address_const::<'market_token'>(), + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + + let mut swap = SwapParams { + data_store: data_store, + event_emitter: event_emitter, + oracle: oracle, + bank: bank, + key: 1, + token_in: contract_address_const::<'token_in'>(), + amount_in: 0, + swap_path_markets: ArrayTrait::new().span(), + min_output_amount: 1, + receiver: contract_address_const::<'receiver'>(), + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + }; + + let swap_result = swap_handler.swap(swap); + + assert(swap_result == (contract_address_const::<'token_in'>(), 0), 'Error'); + + teardown(role_store.contract_address); +} + + +#[test] +#[should_panic(expected: ('insufficient output amount', 1, 2))] +fn given_insufficient_output_then_fails() { + //Change that when swap_handler has been implemented + let ( + caller_address, + data_store, + event_emitter, + oracle, + bank, + role_store, + swap_handler, + market_factory, + index_token_handler, + long_token_handler, + short_token_handler + ) = + setup(); + + let mut market = Market { + market_token: contract_address_const::<'market_token'>(), + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + + let mut swap = SwapParams { + data_store: data_store, + event_emitter: event_emitter, + oracle: oracle, + bank: bank, + key: 1, + token_in: contract_address_const::<'token_in'>(), + amount_in: 1, + swap_path_markets: ArrayTrait::new().span(), + min_output_amount: 2, + receiver: contract_address_const::<'receiver'>(), + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + }; + + let swap_result = swap_handler.swap(swap); + + assert(swap_result == (contract_address_const::<'token_in'>(), 1), 'Error'); + + teardown(role_store.contract_address); +} + +#[test] +fn given_normal_conditions_swap_then_works() { + //Change that when swap_handler has been implemented + let ( + caller_address, + data_store, + event_emitter, + oracle, + bank, + role_store, + swap_handler, + market_factory, + index_token_handler, + long_token_handler, + short_token_handler + ) = + setup(); + + let mut market = Market { + market_token: contract_address_const::<'market_token'>(), + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + + let mut swap = SwapParams { + data_store: data_store, + event_emitter: event_emitter, + oracle: oracle, + bank: bank, + key: 1, + token_in: long_token_handler.contract_address, + amount_in: 2, + swap_path_markets: ArrayTrait::new().span(), + min_output_amount: 1, + receiver: contract_address_const::<'receiver'>(), + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + }; + + let swap_result = swap_handler.swap(swap); + + assert(swap_result == (long_token_handler.contract_address, 2), 'Error'); + + teardown(role_store.contract_address); +} + + +#[test] +fn given_swap_path_market_then_works() { + let ( + caller_address, + data_store, + event_emitter, + oracle, + bank, + role_store, + swap_handler, + market_factory, + index_token_handler, + long_token_handler, + short_token_handler + ) = + setup(); + + //create Market + let index_token = index_token_handler.contract_address; + let long_token = long_token_handler.contract_address; + let short_token = short_token_handler.contract_address; + let market_type = 'market_type'; + + let market_token_deployed_address = market_factory + .create_market(index_token, long_token, short_token, market_type); + + let mut market = Market { + market_token: market_token_deployed_address, + index_token: index_token, + long_token: long_token, + short_token: short_token, + }; + let price = Price { min: 10, max: 100 }; + let key1 = keys::pool_amount_key(market_token_deployed_address, long_token); + let key2 = keys::pool_amount_key(market_token_deployed_address, short_token); + + let key3 = keys::max_pool_amount_key(market_token_deployed_address, long_token); + let key4 = keys::max_pool_amount_key(market_token_deployed_address, short_token); + + oracle.set_primary_price(index_token, price); + oracle.set_primary_price(long_token, price); + oracle.set_primary_price(short_token, price); + + data_store.set_market(market_token_deployed_address, 1, market); + data_store.set_u256(key1, 361850278866613121369732); + data_store.set_u256(key2, 361850278866613121369732); + + data_store.set_u256(key3, 661850278866613121369732); + data_store.set_u256(key4, 661850278866613121369732); + + let mut swap_path_markets = ArrayTrait::::new(); + swap_path_markets.append(market); + + let mut swap = SwapParams { + data_store: data_store, + event_emitter: event_emitter, + oracle: oracle, + bank: bank, + key: 1, + token_in: long_token, + amount_in: 200000000000000000, + swap_path_markets: swap_path_markets.span(), + min_output_amount: 1, + receiver: market_token_deployed_address, + ui_fee_receiver: contract_address_const::<'ui_fee_receiver'>(), + }; + + let swap_result = swap_handler.swap(swap); + assert(swap_result == (short_token, 20000000000000000), 'Error'); + + teardown(role_store.contract_address); +} +//TODO add more tested when swap_handler has been implemented + + diff --git a/src/tests/utils/test_account_utils.cairo b/tests/utils/test_account_utils.cairo similarity index 100% rename from src/tests/utils/test_account_utils.cairo rename to tests/utils/test_account_utils.cairo diff --git a/src/tests/utils/test_arrays.cairo b/tests/utils/test_arrays.cairo similarity index 100% rename from src/tests/utils/test_arrays.cairo rename to tests/utils/test_arrays.cairo diff --git a/src/tests/utils/test_basic_multicall.cairo b/tests/utils/test_basic_multicall.cairo similarity index 99% rename from src/tests/utils/test_basic_multicall.cairo rename to tests/utils/test_basic_multicall.cairo index e8a2599f..ff38e226 100644 --- a/src/tests/utils/test_basic_multicall.cairo +++ b/tests/utils/test_basic_multicall.cairo @@ -7,7 +7,6 @@ use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::role::{role, role_store::IRoleStoreDispatcher, role_store::IRoleStoreDispatcherTrait}; use satoru::tests_lib::{setup, teardown}; -use debug::PrintTrait; #[test] diff --git a/tests/utils/test_calc.cairo b/tests/utils/test_calc.cairo new file mode 100644 index 00000000..5afd17ec --- /dev/null +++ b/tests/utils/test_calc.cairo @@ -0,0 +1,322 @@ +use integer::BoundedInt; + +use satoru::role::role; +use satoru::utils::calc::{ + roundup_division, roundup_magnitude_division, sum_return_uint_256, sum_return_int_256, diff, + to_signed, to_unsigned, pow_u64, bounded_add, bounded_sub, max_i256, min_i256 +}; +use satoru::utils::i256::{i256, i256_new}; + +fn max_i256_as_u256() -> u256 { + return max_i256().mag; +} + +#[test] +#[should_panic(expected: ('i256 Overflow',))] +fn given_overflow_when_max_i256_then_fails() { + max_i256() + i256_new(1, false); +} + +#[test] +#[should_panic(expected: ('u256_add Overflow',))] +fn given_underflow_when_max_i256_then_fails() { + min_i256() - i256_new(1, false); +} + +#[test] +fn given_normal_conditions_when_roundup_division_then_works() { + assert(roundup_division(12, 3) == 4, '12/3 should be 4'); + assert(roundup_division(13, 3) == 5, '13/3 should be 5'); + assert(roundup_division(13, 5) == 3, '13/5 should be 3'); + assert(roundup_division(9, 9) == 1, '9/9 should be 1'); + assert(roundup_division(9, 18) == 1, '9/18 should be 1'); + assert(roundup_division(9, 99) == 1, '9/99 should be 1'); + assert(roundup_division(0, 99) == 0, '0/99 should be 0'); +} + +#[test] +#[should_panic(expected: ('u256 is 0',))] +fn given_division_by_0_when_roundup_division_then_fails() { + roundup_division(4, 0); +} + +#[test] +fn given_normal_conditions_when_roundup_magnitude_division_then_works() { // TODO Check roundup_magnitude_division function + assert( + roundup_magnitude_division(i256_new(12, false), 3) == i256_new(4, false), '12/3 should be 4' + ); + assert( + roundup_magnitude_division(i256_new(12, true), 3) == i256_new(4, true), '-12/3 should be -4' + ); + assert( + roundup_magnitude_division(i256_new(13, false), 5) == i256_new(3, false), '13/5 should be 3' + ); + assert( + roundup_magnitude_division(i256_new(13, true), 5) == i256_new(3, true), '-13/5 should be -3' + ); + assert( + roundup_magnitude_division(i256_new(9, false), 9) == i256_new(1, false), '9/9 should be 1' + ); + assert( + roundup_magnitude_division(i256_new(9, true), 9) == i256_new(1, true), '-9/9 should be -1' + ); + assert( + roundup_magnitude_division(i256_new(9, false), 99) == i256_new(1, false), '9/99 should be 1' + ); + assert( + roundup_magnitude_division(i256_new(9, true), 99) == i256_new(1, true), + '-9 /99 should be -1' + ); + assert( + roundup_magnitude_division( + i256_new( + 28948022309329048855892746252171976963317496166410141009864396001978282409983, false + ), + 28948022309329048855892746252171976963317496166410141009864396001978282409983 + ) == i256_new(1, false), + 'max/max should be 1' + ); + assert( + roundup_magnitude_division(i256_new(0, false), 12) == i256_new(0, false), '0/12 should be 0' + ); +} + +#[test] +#[should_panic(expected: ('u256_add Overflow',))] +fn given_overflow_when_roundup_magnitude_division_then_works() { + roundup_magnitude_division(min_i256(), 2); +} + +#[test] +#[should_panic(expected: ('division by zero', 'roundup_magnitude_division',))] +fn given_division_by_0_when_roundup_magnitude_division_then_fails() { + roundup_magnitude_division(i256_new(4, false), 0); +} + +#[test] +fn given_normal_conditions_when_sum_return_uint_256_then_works() { + assert(sum_return_uint_256(12, i256_new(3, false)) == 15, 'Should be 15'); + assert(sum_return_uint_256(12, i256_new(3, true)) == 9, 'Should be 9'); + assert(sum_return_uint_256(0, i256_new(3, false)) == 3, 'Should be 3'); + assert(sum_return_uint_256(12, i256_new(0, false)) == 12, 'Should be 12'); + assert( + sum_return_uint_256(BoundedInt::max(), i256_new(0, false)) == BoundedInt::max(), + 'Should be max' + ); + + assert( + sum_return_uint_256(BoundedInt::max(), i256_new(1, true)) == BoundedInt::max() - 1, + 'Should be max - 1' + ); + + assert(sum_return_uint_256(0, max_i256()) == max_i256_as_u256(), 'Should be max/2 (2)'); +} + +#[test] +#[should_panic(expected: ('u256_add Overflow',))] +fn given_add_overflow_when_sum_return_uint_256_then_fails() { + sum_return_uint_256(BoundedInt::max(), i256_new(1, false)); +} + +#[test] +#[should_panic(expected: ('u256_sub Overflow',))] +fn given_u256_sub_overflow_when_sum_return_uint_256_then_fails() { + sum_return_uint_256(0, i256_new(1, true)); +} + +#[test] +fn given_normal_conditions_when_sum_return_int_256_then_works() { + assert(sum_return_int_256(12, i256_new(3, false)) == i256_new(15, false), 'Should be 15'); + assert(sum_return_int_256(12, i256_new(3, true)) == i256_new(9, false), 'Should be 9'); + assert(sum_return_int_256(0, i256_new(3, false)) == i256_new(3, false), 'Should be 3'); + assert(sum_return_int_256(0, i256_new(3, true)) == i256_new(3, true), 'Should be -3'); + + assert( + sum_return_int_256(max_i256_as_u256() - 3, i256_new(2, false)) == max_i256() + - i256_new(1, false), + 'Should be max_i256 -1 (1)' + ); + + assert( + sum_return_int_256(max_i256_as_u256() - 1, i256_new(1, false)) == max_i256(), + 'Should be max_i256' + ); + assert( + sum_return_int_256(max_i256_as_u256(), i256_new(1, true)) == max_i256() + - i256_new(1, false), + 'Should be max_i256 - 1 (2)' + ); +} + +#[test] +#[should_panic(expected: ('i256 Overflow',))] +fn given_i256_overflow_when_sum_return_int_256_then_fails() { + sum_return_int_256(max_i256_as_u256() + 1, i256_new(3, false)); +} + +#[test] +#[should_panic(expected: ('i256 Overflow',))] +fn given_i256_add_overflow_when_sum_return_int_256_then_fails() { + sum_return_int_256(max_i256_as_u256() - 1, i256_new(2, false)); +} + +#[test] +fn given_normal_conditions_when_diff_then_works() { + assert(diff(12, 3) == 9, 'Should be 9'); + assert(diff(3, 11) == 8, 'Should be 8'); + assert(diff(0, 5) == 5, 'Should be 5'); + assert(diff(6, 0) == 6, 'Should be 6'); + assert(diff(3, 3) == 0, 'Should be 0 (1)'); + + let max = BoundedInt::max(); + assert(diff(max, max) == 0, 'Should be 0 (2)'); + assert(diff(max - 1, max) == 1, 'Should be 1 (1))'); + assert(diff(max, max - 1) == 1, 'Should be 1 (2)'); +} + +#[test] +fn given_normal_conditions_when_bounded_add_then_works() { + // This tests the first if + assert( + bounded_add(i256_new(0, false), i256_new(3, false)) == i256_new(3, false), 'Should be 3' + ); + assert( + bounded_add(i256_new(4, false), i256_new(0, false)) == i256_new(4, false), 'Should be 4' + ); + assert( + bounded_add(i256_new(42, false), i256_new(41, false)) == i256_new(83, false), 'Shoud be 83' + ); + assert( + bounded_add(i256_new(42, false), i256_new(42, false)) == i256_new(84, false), 'Should be 84' + ); + assert( + bounded_add(i256_new(10, true), i256_new(12, true)) == i256_new(22, true), 'Should be -22' + ); + assert( + bounded_add(i256_new(10, true), i256_new(10, true)) == i256_new(20, true), 'Should be -20' + ); + + // This tests the third if + let max = max_i256(); + let min = min_i256(); + // This tests the second if + assert(bounded_add(min + i256_new(1, false), i256_new(1, true)) == min, 'Should be min (2)'); + assert(bounded_add(min, i256_new(1, true)) == min, 'Should be min (1)'); + assert(bounded_add(max, i256_new(1, false)) == max, 'Should be max (1)'); + assert(bounded_add(max - i256_new(1, false), i256_new(1, false)) == max, 'Should be max (2)'); + + // Mixing signing + assert( + bounded_add(i256_new(10, true), i256_new(10, false)) == i256_new(0, false), + 'Should be 0 (1)' + ); + assert( + bounded_add(i256_new(10, false), i256_new(10, true)) == i256_new(0, false), + 'Should be 0 (2)' + ); + assert( + bounded_add(i256_new(10, true), i256_new(10, true)) == i256_new(20, true), 'Shoud be -20' + ); +} + +#[test] +fn given_normal_conditions_when_bounded_sub_then_works() { + // This tests the first if + assert( + bounded_sub(i256_new(0, false), i256_new(3, false)) == i256_new(3, true), 'Should be -3' + ); + assert( + bounded_sub(i256_new(3, false), i256_new(0, false),) == i256_new(3, false), 'Should be 3' + ); + assert( + bounded_sub(i256_new(42, false), i256_new(41, false)) == i256_new(1, false), 'Shoud be 1' + ); + assert( + bounded_sub(i256_new(41, false), i256_new(42, false)) == i256_new(1, true), 'Should be -1' + ); + + let max = max_i256(); + let min = min_i256(); + // This tests the second if + assert(bounded_sub(max, i256_new(1, true)) == max, 'Should be max (1)'); + assert(bounded_sub(max - i256_new(1, false), i256_new(2, true)) == max, 'Should be max (2)'); + // This tests the third if + assert(bounded_sub(min, i256_new(1, false)) == min, 'Should be min (1)'); + assert(bounded_sub(min + i256_new(1, false), i256_new(1, false)) == min, 'Should be min (2)'); + + // Zero test case + assert( + bounded_sub(i256_new(10, false), i256_new(10, false)) == i256_new(0, false), 'Shoud be 0' + ); + // Mixing signing + assert( + bounded_sub(i256_new(10, true), i256_new(10, false)) == i256_new(20, true), 'Should be -20' + ); + assert( + bounded_sub(i256_new(10, false), i256_new(10, true)) == i256_new(20, false), 'Should be 20' + ); +} + +#[test] +fn given_normal_conditions_when_to_signed_then_works() { + assert(to_signed(12, true) == i256_new(12, false), 'Should be 12'); + assert(to_signed(12, false) == i256_new(12, true), 'Should be -12'); + + let max = max_i256(); + let min = min_i256(); + assert(to_signed(max_i256_as_u256(), true) == max, 'Should be max'); + assert(to_signed(max_i256_as_u256(), false) == min + i256_new(1, false), 'Should be min + 1'); +} + +#[test] +#[should_panic(expected: ('i256 Overflow',))] +fn given_i256_overflow_when_to_signed_then_fails() { + to_signed(BoundedInt::max(), true); +} + + +#[test] +#[should_panic(expected: ('u256_add Overflow',))] +fn given_i256_overflow_neg_when_to_signed_then_fails() { + to_signed(BoundedInt::max() + 1, false); +} + +#[test] +fn given_normal_conditions_when_to_unsigned_then_works() { + assert(to_unsigned(i256_new(12, false)) == 12_u256, 'Should be 12'); + assert(to_unsigned(max_i256()) == max_i256().mag, 'Should be max'); +} + +#[test] +#[should_panic(expected: ('to_unsigned: value is negative',))] +fn given_i256_min_when_to_unsigned_then_fails() { + to_unsigned(min_i256()); +} + +#[test] +fn given_positive_exponent_when_pow_u64_then_works() { + assert(pow_u64(2, 0) == 1, '2^0 should be 1'); + assert(pow_u64(2, 1) == 2, '2^1 should be 2'); + assert(pow_u64(2, 2) == 4, '2^2 should be 4'); + assert(pow_u64(2, 3) == 8, '2^3 should be 8'); + assert(pow_u64(3, 3) == 27, '3^3 should be 27'); +} + +#[test] +fn given_zero_base_when_pow_u64_then_works() { + assert(pow_u64(0, 0) == 1, '0^0 should be 1'); + assert(pow_u64(0, 1) == 0, '0^1 should be 0'); + assert(pow_u64(0, 2) == 0, '0^2 should be 0'); +} + +#[test] +fn given_large_exponent_when_pow_u64_then_works() { + assert(pow_u64(3, 7) == 2187, '3^7 should be 2187'); + assert(pow_u64(2, 10) == 1024, '2^10 should be 1024'); +} + +#[test] +#[should_panic(expected: ('u64_mul Overflow',))] +fn given_u64_max_when_pow_u64_then_fails() { + pow_u64(BoundedInt::max(), BoundedInt::max()); +} diff --git a/src/tests/utils/test_enumerable_set.cairo b/tests/utils/test_enumerable_set.cairo similarity index 100% rename from src/tests/utils/test_enumerable_set.cairo rename to tests/utils/test_enumerable_set.cairo diff --git a/tests/utils/test_i128.cairo b/tests/utils/test_i128.cairo new file mode 100644 index 00000000..fd0629e6 --- /dev/null +++ b/tests/utils/test_i128.cairo @@ -0,0 +1,272 @@ +use snforge_std::{declare, ContractClassTrait}; +use satoru::utils::{i128, calc::{max_i128, min_i128}}; + +// Add +#[test] +fn test_i128_sum() { + assert( + i128::i128_new(12, false) + i128::i128_new(3, false) == i128::i128_new(15, false), + 'should be 15' + ); +} + +#[test] +fn test_i128_sum_lhs_neg() { + assert( + i128::i128_new(12, true) + i128::i128_new(3, false) == i128::i128_new(9, true), + 'should be -9' + ); +} + +#[test] +fn test_i128_sum_rhs_neg() { + assert( + i128::i128_new(12, false) + i128::i128_new(3, true) == i128::i128_new(9, false), + 'should be 9' + ); +} + +#[test] +fn test_i128_sum_both_neg() { + assert( + i128::i128_new(12, true) + i128::i128_new(3, true) == i128::i128_new(15, true), + 'should be 4' + ); +} + +#[test] +fn test_i128_sum_zero() { + assert( + i128::i128_new(0, false) + i128::i128_new(3, true) == i128::i128_new(3, true), 'should be 3' + ); +} + +#[test] +fn test_i128_sum_limit_max() { + assert( + max_i128() + + i128::i128_new(3, true) + + i128::i128_new(2, false) == max_i128() + - i128::i128_new(1, false), + 'should be max - 1' + ); +} + +#[test] +fn test_i128_sum_limit_min() { + assert( + min_i128() + + i128::i128_new(3, false) + + i128::i128_new(2, true) == min_i128() + + i128::i128_new(1, false), + 'should be min + 1' + ); +} + +#[test] +#[should_panic(expected: ('i128 Overflow',))] +fn test_i128_sum_max_overflow() { + max_i128() + i128::i128_new(1, false); +} + +#[test] +#[should_panic(expected: ('i128 Overflow',))] +fn test_i128_sum_max_underflow() { + min_i128() + i128::i128_new(1, true); +} + +// Sub +#[test] +fn test_i128_sub() { + assert( + i128::i128_new(12, false) - i128::i128_new(3, false) == i128::i128_new(9, false), + 'should be 9' + ); +} + +#[test] +fn test_i128_sub_lhs_neg() { + assert( + i128::i128_new(12, true) - i128::i128_new(3, false) == i128::i128_new(15, true), + 'should be -15' + ); +} + +#[test] +fn test_i128_sub_rhs_neg() { + assert( + i128::i128_new(12, false) - i128::i128_new(3, true) == i128::i128_new(15, false), + 'should be 15' + ); +} + +#[test] +fn test_i128_sub_both_neg() { + assert( + i128::i128_new(12, true) - i128::i128_new(3, true) == i128::i128_new(9, true), + 'should be -9' + ); +} + +#[test] +fn test_i128_sub_zero() { + assert( + i128::i128_new(0, false) - i128::i128_new(3, true) == i128::i128_new(3, false), + 'should be 3' + ); +} + +#[test] +fn test_i128_sub_limit_max() { + assert( + max_i128() + - i128::i128_new(3, false) + - i128::i128_new(2, true) == max_i128() + - i128::i128_new(1, false), + 'should be max - 1' + ); +} + +#[test] +fn test_i128_sub_limit_min() { + assert( + min_i128() + - i128::i128_new(3, true) + - i128::i128_new(2, false) == min_i128() + - i128::i128_new(1, true), + 'should be min + 1' + ); +} + +#[test] +#[should_panic(expected: ('i128 Overflow',))] +fn test_i128_sub_max_overflow() { + max_i128() - i128::i128_new(1, true); +} + +#[test] +#[should_panic(expected: ('i128 Overflow',))] +fn test_i128_sub_max_underflow() { + min_i128() - i128::i128_new(1, false); +} + +// Div +#[test] +fn test_i128_division() { + assert( + i128::i128_new(12, false) / i128::i128_new(3, false) == i128::i128_new(4, false), + 'should be 4' + ); +} + +#[test] +fn test_i128_division_lhs_neg() { + assert( + i128::i128_new(12, true) / i128::i128_new(3, false) == i128::i128_new(4, true), + 'should be -4' + ); +} + +#[test] +fn test_i128_division_rhs_neg() { + assert( + i128::i128_new(12, false) / i128::i128_new(3, true) == i128::i128_new(4, true), + 'should be -4' + ); +} + +#[test] +fn test_i128_division_both_neg() { + assert( + i128::i128_new(12, true) / i128::i128_new(3, true) == i128::i128_new(4, false), + 'should be 4' + ); +} + +#[test] +fn test_i128_division_zero() { + assert( + i128::i128_new(0, false) / i128::i128_new(3, true) == i128::i128_new(0, false), + 'should be 0' + ); +} + +#[test] +#[should_panic(expected: ('Division by 0',))] +fn test_i128_division_by_zero() { + i128::i128_new(12, true) / i128::i128_new(0, false); +} + +// Mul +#[test] +fn test_i128_multiplication() { + assert( + i128::i128_new(12, false) * i128::i128_new(3, false) == i128::i128_new(36, false), + 'should be 36' + ); +} + +#[test] +fn test_i128_multiplication_lhs_neg() { + assert( + i128::i128_new(12, true) * i128::i128_new(3, false) == i128::i128_new(36, true), + 'should be -36' + ); +} + +#[test] +fn test_i128_multiplication_rhs_neg() { + assert( + i128::i128_new(12, false) * i128::i128_new(3, true) == i128::i128_new(36, true), + 'should be -36' + ); +} + +#[test] +fn test_i128_multiplication_both_neg() { + assert( + i128::i128_new(12, true) * i128::i128_new(3, true) == i128::i128_new(36, false), + 'should be 36' + ); +} + +#[test] +fn test_i128_multiplication_zero() { + assert( + i128::i128_new(0, false) * i128::i128_new(3, true) == i128::i128_new(0, false), + 'should be 0' + ); +} + +#[test] +fn test_i128_multiplication_by_zero() { + assert( + i128::i128_new(3, true) * i128::i128_new(0, false) == i128::i128_new(0, false), + 'should be 0' + ); +} +// #[starknet::interface] +// trait ITestI128Storage { +// fn set_i128(ref self: TContractState, i128_new_val: i128); +// fn get_i128(self: @TContractState) -> i128; +// } + +// fn deploy() -> ITestI128StorageDispatcher { +// let contract = declare('test_i128_storage_contract'); +// let contract_address = contract.deploy(@array![]).unwrap(); +// ITestI128StorageDispatcher { contract_address } +// } + +// #[test] +// fn test_i128_storage() { +// let dispatcher = deploy(); +// assert(dispatcher.get_i128() == 0, 'should be 0'); +// dispatcher.set_i128(12); +// assert(dispatcher.get_i128() == 12, 'should be 12'); +// dispatcher.set_i128(-42); +// assert(dispatcher.get_i128() == -42, 'should be -42'); +// dispatcher.set_i128(0); +// assert(dispatcher.get_i128() == 0, 'should be back to 0'); +// } + + diff --git a/tests/utils/test_i256.cairo b/tests/utils/test_i256.cairo new file mode 100644 index 00000000..a11308cc --- /dev/null +++ b/tests/utils/test_i256.cairo @@ -0,0 +1,530 @@ +mod TestInteger256 { + mod New { + use integer::BoundedInt; + + use satoru::utils::i256::{i256, IntegerTrait}; + + // Test new i256 max + #[test] + fn test_i256_max() { + let i256_max = BoundedInt::max() / 2; + let a = IntegerTrait::::new(i256_max - 1, false); + + assert(a.mag == i256_max - 1, 'new max pos value error'); + assert(a.sign == false, 'new max pos sign'); + + let a = IntegerTrait::::new(i256_max, true); + assert(a.mag == i256_max, 'new max neg value error'); + assert(a.sign == true, 'new max neg sign'); + } + + // Test new i256 min + #[test] + fn test_i256_min() { + let a = IntegerTrait::::new(0, false); + assert(a.mag == 0, 'new min value error'); + assert(a.sign == false, 'new max pos sign'); + + let a = IntegerTrait::::new(1, true); + assert(a.mag == 1, 'new min value error'); + assert(a.sign == true, 'new max neg sign'); + } + } + + mod Add { + use integer::BoundedInt; + + use satoru::utils::i256::{i256, IntegerTrait}; + + // Test addition of two positive integers + #[test] + fn test_positive_x_positive() { + let a = IntegerTrait::::new(129, false); + let b = IntegerTrait::::new(10, false); + let result = a + b; + assert(result.mag == 139, '129 + 10 = 139'); + assert(result.sign == false, '42 + 13 -> positive'); + } + + // Test addition of two negative integers + #[test] + fn test_negative_x_negative() { + let a = IntegerTrait::::new(129, true); + let b = IntegerTrait::::new(10, true); + let result = a + b; + assert(result.mag == 139, '- 129 - 10 = -139'); + assert(result.sign == true, '- 42 - 13 -> negative'); + } + + // Test addition of a positive integer and a negative integer with the same magnitude + #[test] + fn test_positive_x_negative_same_mag() { + let a = IntegerTrait::::new(42, false); + let b = IntegerTrait::::new(42, true); + let result = a + b; + assert(result.mag == 0, '42 - 42 = 0'); + assert(result.sign == false, '42 - 42 -> positive'); + } + + // Test addition of a positive integer and a negative integer with different magnitudes + #[test] + fn test_positive_x_negative_diff_mag() { + let a = IntegerTrait::::new(42, false); + let b = IntegerTrait::::new(13, true); + let result = a + b; + assert(result.mag == 29, '42 - 13 = 29'); + assert(result.sign == false, '42 - 13 -> positive'); + } + + // Test addition of a negative integer and a positive integer with different magnitudes + #[test] + fn test_negative_x_positive_diff_mag() { + let a = IntegerTrait::::new(42, true); + let b = IntegerTrait::::new(13, false); + let result = a + b; + assert(result.mag == 29, '-42 + 13 = -29'); + assert(result.sign == true, '-42 + 13 -> negative'); + } + + // Test addition overflow + #[test] + #[should_panic] + fn test_overflow() { + let i256_max = BoundedInt::max(); + let a = IntegerTrait::::new(i256_max - 1, false); + let b = IntegerTrait::::new(1, false); + let result = a + b; + } + } + + mod Sub { + use integer::BoundedInt; + + use satoru::utils::i256::{i256, IntegerTrait}; + + // Test subtraction of two positive integers with larger first + #[test] + fn test_positive_x_positive_larger_first() { + let a = IntegerTrait::::new(42, false); + let b = IntegerTrait::::new(13, false); + let result = a - b; + assert(result.mag == 29, '42 - 13 = 29'); + assert(result.sign == false, '42 - 13 -> positive'); + } + + // Test subtraction of two positive integers with larger second + #[test] + fn test_positive_x_positive_larger_second() { + let a = IntegerTrait::::new(13, false); + let b = IntegerTrait::::new(42, false); + let result = a - b; + assert(result.mag == 29, '13 - 42 = -29'); + assert(result.sign == true, '13 - 42 -> negative'); + } + + // Test subtraction of two negative integers with larger first + #[test] + fn test_negative_x_negative_larger_first() { + let a = IntegerTrait::::new(42, true); + let b = IntegerTrait::::new(13, true); + let result = a - b; + assert(result.mag == 29, '-42 - -13 = 29'); + assert(result.sign == true, '-42 - -13 -> negative'); + } + + // Test subtraction of two negative integers with larger second + #[test] + fn test_negative_x_negative_larger_second() { + let a = IntegerTrait::::new(13, true); + let b = IntegerTrait::::new(42, true); + let result = a - b; + assert(result.mag == 29, '-13 - -42 = 29'); + assert(result.sign == false, '-13 - -42 -> positive'); + } + + // Test subtraction of a positive integer and a negative integer with the same magnitude + #[test] + fn test_positive_x_negative_same_mag() { + let a = IntegerTrait::::new(42, false); + let b = IntegerTrait::::new(42, true); + let result = a - b; + assert(result.mag == 84, '42 - -42 = 84'); + assert(result.sign == false, '42 - -42 -> postive'); + } + + // Test subtraction of a negative integer and a positive integer with the same magnitude + #[test] + fn test_negative_x_positive_same_mag() { + let a = IntegerTrait::::new(42, true); + let b = IntegerTrait::::new(42, false); + let result = a - b; + assert(result.mag == 84, '-42 - 42 = -84'); + assert(result.sign == true, '-42 - 42 -> negative'); + } + + // Test subtraction of a positive integer and a negative integer with different magnitudes + #[test] + fn test_positive_x_negative_diff_mag() { + let a = IntegerTrait::::new(100, false); + let b = IntegerTrait::::new(42, true); + let result = a - b; + assert(result.mag == 142, '100 - - 42 = 142'); + assert(result.sign == false, '100 - - 42 -> postive'); + } + + // Test subtraction of a negative integer and a positive integer with different magnitudes + #[test] + fn test_negative_x_positive_diff_mag() { + let a = IntegerTrait::::new(42, true); + let b = IntegerTrait::::new(100, false); + let result = a - b; + assert(result.mag == 142, '-42 - 100 = -142'); + assert(result.sign == true, '-42 - 100 -> negative'); + } + + // Test subtraction resulting in zero + #[test] + fn test_result_in_zero() { + let a = IntegerTrait::::new(42, false); + let b = IntegerTrait::::new(42, false); + let result = a - b; + assert(result.mag == 0, '42 - 42 = 0'); + assert(result.sign == false, '42 - 42 -> positive'); + } + + // Test subtraction overflow + #[test] + #[should_panic] + fn test_overflow() { + let i256_max = BoundedInt::max(); + let a = IntegerTrait::::new(i256_max, true); + let b = IntegerTrait::::new(1, false); + let result = a - b; + } + } + + mod Mul { + use integer::BoundedInt; + + use satoru::utils::i256::{i256, IntegerTrait}; + + // Test multiplication of positive integers + #[test] + fn test_positive_x_positive() { + let a = IntegerTrait::::new(10, false); + let b = IntegerTrait::::new(5, false); + let result = a * b; + assert(result.mag == 50, '10 * 5 = 50'); + assert(result.sign == false, '10 * 5 -> positive'); + } + + // Test multiplication of negative integers + #[test] + fn test_negative_x_negative() { + let a = IntegerTrait::::new(10, true); + let b = IntegerTrait::::new(5, true); + let result = a * b; + assert(result.mag == 50, '-10 * -5 = 50'); + assert(result.sign == false, '-10 * -5 -> positive'); + } + + // Test multiplication of positive and negative integers + #[test] + fn test_positive_x_negative() { + let a = IntegerTrait::::new(10, false); + let b = IntegerTrait::::new(5, true); + let result = a * b; + assert(result.mag == 50, '10 * -5 = -50'); + assert(result.sign == true, '10 * -5 -> negative'); + } + + // Test multiplication of negative and positive integers + #[test] + fn test_negative_x_positive() { + let a = IntegerTrait::::new(10, true); + let b = IntegerTrait::::new(5, false); + let result = a * b; + assert(result.mag == 50, '10 * -5 = -50'); + assert(result.sign == true, '10 * -5 -> negative'); + } + + // Test multiplication by zero + #[test] + fn test_by_zero() { + let a = IntegerTrait::::new(10, false); + let b = IntegerTrait::::new(0, false); + let result = a * b; + assert(result.mag == 0, '10 * 0 = 0'); + assert(result.sign == false, '10 * 0 -> positive'); + } + + // Test multiplication overflow + #[test] + #[should_panic] + fn test_overflow() { + let i256_max = BoundedInt::max(); + let a = IntegerTrait::::new(i256_max - 1, false); + let b = IntegerTrait::::new(2, false); + let result = a * b; + } + } + + mod DivRem { + use integer::BoundedInt; + + use satoru::utils::i256::{i256, IntegerTrait}; + + // Test division and remainder of positive integers + #[test] + fn test_rem_positive_x_positive() { + let a = IntegerTrait::::new(13, false); + let b = IntegerTrait::::new(5, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 2 && r.mag == 3, '13 // 5 -> q 2 r 3'); + assert((q.sign == false) & (r.sign == false), '13 // 5 -> positive, positive'); + } + + // Test division and remainder of negative integers + #[test] + fn test_rem_negative_x_negative() { + let a = IntegerTrait::::new(13, true); + let b = IntegerTrait::::new(5, true); + let (q, r) = a.div_rem(b); + assert(q.mag == 2 && r.mag == 3, '-13 // -5 -> q 2 r -3'); + assert(q.sign == false && r.sign == true, '-13 // -5 -> positive, negative'); + } + + // Test division and remainder of positive and negative integers + #[test] + fn test_rem_positive_x_negative() { + let a = IntegerTrait::::new(13, false); + let b = IntegerTrait::::new(5, true); + let (q, r) = a.div_rem(b); + assert(q.mag == 2 && r.mag == 3, '13 // -5 -> q -2 r 3'); + assert(q.sign == true && r.sign == false, '13 // -5 -> negative, positive'); + } + + // Test division and remainder with a negative dividend and positive divisor + #[test] + fn test_rem_negative_x_positive() { + let a = IntegerTrait::::new(13, true); + let b = IntegerTrait::::new(5, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 2 && r.mag == 3, '-13 // 5 -> q -2 r -3'); + assert(q.sign == true && r.sign == true, '-13 // 5 -> negative, negative'); + } + + // Test division with a = zero + #[test] + fn test_rem_z_eq_zero() { + let a = IntegerTrait::::new(0, false); + let b = IntegerTrait::::new(10, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 0 && r.mag == 0, '0 // 10 -> q 0 r 0'); + assert(q.sign == false && r.sign == false, '0 // 10 -> positive, positive'); + } + + // Test division by zero + #[test] + #[should_panic] + fn test_rem_by_zero() { + let a = IntegerTrait::::new(1, false); + let b = IntegerTrait::::new(0, false); + let (q, r) = a.div_rem(b); + } + + // Test to ensure that the results do not produce invalid 'negative' zeros + #[test] + fn test_denominator_gt_numerator_result_should_be_zero() { + // -65 / 256 = 0 + let a = IntegerTrait::::new(65, true); + let b = IntegerTrait::::new(256, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 0 && r.mag == 65, '-65 // 256 -> q 0 r -65'); + assert(q.sign == false && r.sign == true, '-65 // 256 -> positive (bc 0)'); + + // -55 / 256 = 0 + let a = IntegerTrait::::new(55, true); + let b = IntegerTrait::::new(256, false); + let result = a / b; + assert(result.mag == 0, '-55 // 256 = 0'); + assert(result.sign == false, '-55 // 256 -> positive (bc 0)'); + } + + // Test to evaluate rounding behavior and zeros + #[test] + fn test_division_round_with_negative_result() { + // -10/ 3 = 0 + let a = IntegerTrait::::new(10, true); + let b = IntegerTrait::::new(3, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 3 && r.mag == 1, '-10 / 3 = (-3, -1)'); + assert(q.sign == true && r.sign == true, '(neg, neg)'); + + // -6 / 10 = -1 + let a = IntegerTrait::::new(6, true); + let b = IntegerTrait::::new(10, false); + let (q, r) = a.div_rem(b); + + assert(q.mag == 0 && r.mag == 6, '-6 / 10 = (0, -6)'); + assert(q.sign == false && r.sign == true, '(pos, neg)'); + + // -5 / 10 = 0 + let a = IntegerTrait::::new(5, true); + let b = IntegerTrait::::new(10, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 0 && r.mag == 5, '-5 // 10 -> q 0 r -5'); + assert(q.sign == false && r.sign == true, '-5 // 10 -> (q: +, r: -)'); + + // 5 / 10 = 0 + let a = IntegerTrait::::new(5, false); + let b = IntegerTrait::::new(10, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 0 && r.mag == 5, '5 // 10 -> q 0 r 5'); + assert(q.sign == false && r.sign == false, '5 // 10 -> (q: +, r: +)'); + + // 5 / -10 = 0 + let a = IntegerTrait::::new(5, false); + let b = IntegerTrait::::new(10, true); + let (q, r) = a.div_rem(b); + assert(q.mag == 0 && r.mag == 5, '5 // -10 -> q 0 r -5'); + assert(q.sign == false && r.sign == false, '5 // -10 -> (q: +, r: -)'); + + // -4 / 10 = 0 + let a = IntegerTrait::::new(4, true); + let b = IntegerTrait::::new(10, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 0 && r.mag == 4, '-4 / 10 = 0 r -4'); + assert(q.sign == false && r.sign == true, '-4 // 10 -> (q: +, r: -)'); + + // -3 / 10 = 0 + let a = IntegerTrait::::new(3, true); + let b = IntegerTrait::::new(10, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 0 && r.mag == 3, '-3 / 10 = 0 r -3'); + assert(q.sign == false && r.sign == true, '-3 // 10 -> (q: +, r: -)'); + + // -2 / 10 = 0 + let a = IntegerTrait::::new(2, true); + let b = IntegerTrait::::new(10, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 0 && r.mag == 2, '-2 / 10 = 0 r -2'); + assert(q.sign == false && r.sign == true, '-2 // 10 -> (q: +, r: -)'); + + // -1 / 10 = 0 + let a = IntegerTrait::::new(1, true); + let b = IntegerTrait::::new(10, false); + let (q, r) = a.div_rem(b); + assert(q.mag == 0 && r.mag == 1, '-1 / 10 = 0 r -1'); + assert(q.sign == false && r.sign == true, '-1 // 10 -> (q: +, r: -)'); + } + } + + mod i256IntoU256 { + use integer::BoundedInt; + + use satoru::utils::i256::{i256, IntegerTrait}; + + #[test] + fn test_positive_conversion_within_range() { + let val = IntegerTrait::::new(100, false); + let result: u256 = val.try_into().unwrap(); + assert(result == 100, 'result should be 100'); + } + + #[test] + fn test_zero_conversion() { + let val = IntegerTrait::::new(0, false); + let result: u256 = val.try_into().unwrap(); + assert(result == 0, 'result should be 0'); + } + + #[test] + fn test_positive_conversion_i256_max() { + let val = IntegerTrait::::new(BoundedInt::max() / 2 - 1, false); + let result: u256 = val.try_into().unwrap(); + assert(result == BoundedInt::max() / 2 - 1, 'result should be max'); + } + + #[test] + #[should_panic(expected: ('The sign must be positive',))] + fn test_negative_conversion() { + let val = IntegerTrait::::new(200, true); + let result: u256 = val.try_into().unwrap(); + } + } + + mod TwoComplementTests { + use integer::BoundedInt; + + use satoru::utils::i256::{i256, two_complement_if_nec, IntegerTrait}; + + // Some expected values where calculated in Python with a script + + // Two's complement expected is achieved by: + // Step 1: starting with the equivalent positive number. + // Step 2: inverting (or flipping) all bits – changing every 0 to 1, and every 1 to 0; + // Step 3: adding 1 to the entire inverted number, ignoring any overflow. Accounting + // for overflow will produce the wrong value for the result. + + #[test] + fn test_positive_min_mag() { + let input = IntegerTrait::::new(0, false); + let actual = two_complement_if_nec(input); + let expected = i256 { mag: 0, sign: false }; + + assert(actual == expected, 'positive min wrong val'); + } + + #[test] + fn test_positive_max_mag() { + let input = IntegerTrait::::new(BoundedInt::max() / 2 - 1, false); + let actual = two_complement_if_nec(input); + let expected = i256 { mag: BoundedInt::max() / 2 - 1, sign: false }; + + assert(actual == expected, 'positive max wrong value'); + } + + #[test] + fn test_negative_min_mag() { + let input = IntegerTrait::::new(1, true); + let actual = two_complement_if_nec(input); + let expected = i256 { mag: BoundedInt::max(), sign: true }; + + assert(actual == expected, 'negative min wrong val'); + } + + #[test] + fn test_negative_max_mag() { + let input = IntegerTrait::::new(BoundedInt::max() / 2, true); + let actual = two_complement_if_nec(input); + let expected = i256 { + mag: 57896044618658097711785492504343953926634992332820282019728792003956564819969, + sign: true + }; + + assert(actual == expected, 'negative max wrong val'); + } + + #[test] + fn test_positive_non_zero_mag() { + let input = IntegerTrait::::new(12345, false); + let actual = two_complement_if_nec(input); + let expected = i256 { mag: 12345, sign: false }; + + assert(actual == expected, 'positive non zero wrong value'); + } + + #[test] + fn test_negative_non_zero_mag() { + let input = IntegerTrait::::new(54321, true); + let actual = two_complement_if_nec(input); + let expected = i256 { + mag: 115792089237316195423570985008687907853269984665640564039457584007913129585615, + sign: true + }; + + assert(actual == expected, 'negative non zero wrong value'); + } + } +} diff --git a/tests/utils/test_precision.cairo b/tests/utils/test_precision.cairo new file mode 100644 index 00000000..d2d7aaed --- /dev/null +++ b/tests/utils/test_precision.cairo @@ -0,0 +1,234 @@ +// IMPORTS +use satoru::utils::precision; +use satoru::utils::precision::{ + FLOAT_PRECISION, FLOAT_PRECISION_SQRT, WEI_PRECISION, BASIS_POINTS_DIVISOR, FLOAT_TO_WEI_DIVISOR +}; +use satoru::utils::i256::{i256, i256_new}; + +#[test] +fn test_apply_factor_u256() { + let value: u256 = 10; + let factor: u256 = 1_000_000_000_000_000_000_000_000; + let result = precision::apply_factor_u256(value, factor); + assert(result == 100000, 'should be 100000.'); +} + +#[test] +fn test_apply_factor_i256() { + let value: u256 = 10; + let factor: i256 = i256_new(1_000_000_000_000_000_000_000_000, true); + let result = precision::apply_factor_i256(value, factor); + assert(result == i256_new(100000, true), 'should be -1OOO0O.'); +} + +#[test] +fn test_apply_factor_roundup_magnitude_positive() { + let value: u256 = 15; + let factor: i256 = i256_new(30_000_000_000_000_000_000, false); + let roundup_magnitude = true; + let result = precision::apply_factor_roundup_magnitude(value, factor, roundup_magnitude); + assert(result == i256_new(5, false), 'should be 5.'); +} + +#[test] +fn test_apply_factor_roundup_magnitude_negative() { + let value: u256 = 15; + let factor: i256 = i256_new(30_000_000_000_000_000_000, true); + let roundup_magnitude = true; + let result = precision::apply_factor_roundup_magnitude(value, factor, roundup_magnitude); + assert(result == i256_new(5, true), 'should be -5.'); +} + +#[test] +fn test_apply_factor_roundup_magnitude_no_rounding() { + let value: u256 = 15; + let factor: i256 = i256_new(30_000_000_000_000_000_000, true); + let roundup_magnitude = false; + let result = precision::apply_factor_roundup_magnitude(value, factor, roundup_magnitude); + assert(result == i256_new(4, true), 'should be -4.'); +} + +#[test] +fn test_mul_div_ival_negative() { + let value: i256 = i256_new(42, true); + let factor: u256 = 10; + let denominator = 8; + let result = precision::mul_div_ival(value, factor, denominator); + assert(result == i256_new(52, true), 'should be -52.'); +} + +#[test] +fn test_mul_div_inum_positive() { + let value: u256 = 42; + let factor: i256 = i256_new(10, false); + let denominator = 8; + let result = precision::mul_div_inum(value, factor, denominator); + assert(result == i256_new(52, false), 'should be 52.'); +} + +#[test] +fn test_mul_div_inum_roundup_negative() { + let value: u256 = 42; + let factor: i256 = i256_new(10, true); + let denominator = 8; + let result = precision::mul_div_inum_roundup(value, factor, denominator, true); + assert(result == i256_new(53, true), 'should be -53.'); +} + +#[test] +fn test_mul_div_inum_roundup_positive() { + let value: u256 = 42; + let factor: i256 = i256_new(10, false); + let denominator = 8; + let result = precision::mul_div_inum_roundup(value, factor, denominator, true); + assert(result == i256_new(53, false), 'should be 53.'); +} + +#[test] +fn test_exp2() { + 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] +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_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_decimal() { + let value1: u256 = 2000000000000000000; + let value2: u256 = 3000000000000000000; + let value3: u256 = 4000000000000000000; + let value5: u256 = 1000000000000000000; + let value6: u256 = 1524558784654678955; + + 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 == 8999999999999999806, 'should be 8999999999999999806'); + //assert(result2 == 2999999999999999967, 'should be 2999999999999999967'); + assert(result3 == 1000000000000000000, 'should be 2000000000000000000'); + assert(result4 == 8277055145359463000, 'should be 8277055145359463000'); +} + +#[test] +fn test_apply_exponent_factor() { + let value1: u256 = 2000000000000000000000000000000; + let value2: u256 = 3000000000000000000000000000000; + let value3: u256 = 4000000000000000000000000000000; + let value5: u256 = 1000000000000000000000000000000; + let value6: u256 = 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: u256 = 450000; + let divisor: u256 = 20_000_000_000_000_000_000_000_000; //2*10^25 + let roundup_magnitude = true; + let result = precision::to_factor_roundup(value, divisor, roundup_magnitude); + assert(result == 3, 'should be 3.'); +} + +#[test] +fn test_to_factor() { + let value: u256 = 450000; + let divisor: u256 = 20_000_000_000_000_000_000_000_000; // 2*10^25 + let result = precision::to_factor(value, divisor); + assert(result == 2, 'should be 2.'); +} + +#[test] +fn test_to_factor_ival_positive() { + let value: i256 = i256_new(450000, false); + let divisor: u256 = 20_000_000_000_000_000_000_000_000; // 2*10^25 + let result = precision::to_factor_ival(value, divisor); + assert(result == i256_new(2, false), 'from positive integer value.'); +} + +#[test] +fn test_to_factor_ival_negative() { + let value: i256 = i256_new(450000, true); + let divisor: u256 = 20_000_000_000_000_000_000_000_000; // 2*10^25 + let result = precision::to_factor_ival(value, divisor); + assert(result == i256_new(2, true), 'should be -2.'); +} + +#[test] +fn test_float_to_wei() { + let float_value: u256 = 1_000_000_000_000_000; + let result = precision::float_to_wei(float_value); + assert(result == 1000, 'should be 10^3'); +} + +#[test] +fn test_wei_to_float() { + let wei_value: u256 = 10_000_000_000_000_000_000_000_000; //10^25 + let result = precision::wei_to_float(wei_value); + assert(result == 10_000_000_000_000_000_000_000_000_000_000_000_000, 'should be 10^37'); +} + +#[test] +fn test_basis_points_to_float() { + let basis_point: u256 = 1000; + let result = precision::basis_points_to_float(basis_point); + assert(result == 10_000_000_000_000_000_000, 'should be 10^19'); +} diff --git a/src/tests/utils/test_reentrancy_guard.cairo b/tests/utils/test_reentrancy_guard.cairo similarity index 96% rename from src/tests/utils/test_reentrancy_guard.cairo rename to tests/utils/test_reentrancy_guard.cairo index 4b839e51..2a710542 100644 --- a/src/tests/utils/test_reentrancy_guard.cairo +++ b/tests/utils/test_reentrancy_guard.cairo @@ -16,18 +16,18 @@ fn given_normal_conditions_when_non_reentrancy_before_and_after_then_works() { let initial_value = data_store.get_bool('REENTRANCY_GUARD_STATUS'); // Initial value should be false. - assert(initial_value.is_none(), 'Initial value wrong'); + assert(initial_value == false, 'Initial value wrong'); // Sets value to true non_reentrant_before(data_store); // Gets value after non_reentrant_before call - let entrant = data_store.get_bool('REENTRANCY_GUARD_STATUS').unwrap(); + let entrant = data_store.get_bool('REENTRANCY_GUARD_STATUS'); assert(entrant, 'Entered value wrong'); non_reentrant_after(data_store); // This should set value false. // Gets final value - let after: bool = data_store.get_bool('REENTRANCY_GUARD_STATUS').unwrap(); + let after: bool = data_store.get_bool('REENTRANCY_GUARD_STATUS'); assert(!after, 'Final value wrong'); } @@ -48,7 +48,7 @@ fn given_reentrant_call_when_reentrancy_before_and_after_then_fails() { non_reentrant_before(data_store); // Gets value after non_reentrant_before - let entraant: bool = data_store.get_bool('REENTRANCY_GUARD_STATUS').unwrap(); + let entraant: bool = data_store.get_bool('REENTRANCY_GUARD_STATUS'); assert(entraant, 'Entered value wrong'); // This should revert, means reentrant call happened. diff --git a/tests/utils/test_serializable_dict.cairo b/tests/utils/test_serializable_dict.cairo new file mode 100644 index 00000000..d69cddd9 --- /dev/null +++ b/tests/utils/test_serializable_dict.cairo @@ -0,0 +1,222 @@ +//! Test file for `src/utils/serializable_dict.cairo`. + +// ************************************************************************* +// IMPORTS +// ************************************************************************* +// Core lib imports. +use serde::Serde; +use starknet::{ + get_caller_address, ContractAddress, Felt252TryIntoContractAddress, ContractAddressIntoFelt252, + contract_address_const +}; +use array::ArrayTrait; +use array::SpanTrait; +use traits::Default; +use alexandria_data_structures::array_ext::ArrayTraitExt; + +// Local imports. +use satoru::utils::traits::ContractAddressDefault; +use satoru::event::event_utils::{ + Felt252IntoBool, Felt252IntoContractAddress, I256252DictValue, ContractAddressDictValue, + U256252DictValue, U256IntoFelt252 +}; +use satoru::utils::serializable_dict::{ + Item, ItemTrait, SerializableFelt252Dict, SerializableFelt252DictTrait, + SerializableFelt252DictTraitImpl +}; + +// ********************************************************************************************* +// * TEST LOGIC * +// ********************************************************************************************* + +/// Item tests + +#[test] +fn test_item_single() { + let item: Item = Item::Single(8); + + assert(item.is_single() == true, 'item should be single'); + assert(item.is_span() == false, 'item shouldnt be a span'); + assert(item.len() == 1, 'item len not 1'); +} + +#[test] +fn test_item_span() { + let arr: Array = array![1, 2, 3, 4, 5]; + let expected_len: usize = arr.len(); + + let item: Item = Item::Span(arr.span()); + + assert(item.is_span() == true, 'item should be a span'); + assert(item.is_single() == false, 'item shouldnt be single'); + assert(item.len() == expected_len, 'incorrect len'); +} + +#[test] +fn test_item_comparison_single_equals() { + let item_a: Item = Item::Single(42); + let item_b: Item = Item::Single(42); + assert(item_a == item_b, 'u256 should be equals'); + + let item_a: Item = Item::Single(42); + let item_b: Item = Item::Single(69); + assert(item_a != item_b, 'u256 shouldnt be equals'); + + let item_a: Item = Item::Single(69); + let item_b: Item = Item::Single(69); + assert(item_a == item_b, 'felt252 should be equals'); + + let item_a: Item = Item::Single(42); + let item_b: Item = Item::Single(69); + assert(item_a != item_b, 'felt252 shouldnt be equals'); +} + +#[test] +fn test_item_comparison_spans() { + let item_a: Item = Item::Span(array![1, 2, 3].span()); + let item_b: Item = Item::Span(array![1, 2, 3].span()); + assert(item_a == item_b, 'u256 should be equals'); + + let item_a: Item = Item::Span(array![1, 2, 3].span()); + let item_b: Item = Item::Span(array![4, 5].span()); + assert(item_a != item_b, 'u256 shouldnt be equals'); + + let item_a: Item = Item::Span(array![1, 2, 3].span()); + let item_b: Item = Item::Span(array![1, 2, 3].span()); + assert(item_a == item_b, 'felt252 should be equals'); + + let item_a: Item = Item::Span(array![1, 2, 3].span()); + let item_b: Item = Item::Span(array![1, 2, 9].span()); + assert(item_a != item_b, 'felt252 shouldnt be equals'); + + let item_a: Item = Item::Span( + array![contract_address_const::<'satoshi'>(), contract_address_const::<'nakamoto'>()].span() + ); + let item_b: Item = Item::Span( + array![contract_address_const::<'satoshi'>(), contract_address_const::<'nakamoto'>()].span() + ); + assert(item_a == item_b, 'contract should be equals'); + + let item_a: Item = Item::Span( + array![contract_address_const::<'satoshi'>(), contract_address_const::<'nakamoto'>()].span() + ); + let item_b: Item = Item::Span( + array![contract_address_const::<'nakamoto'>(), contract_address_const::<'satoshi'>()].span() + ); + assert(item_a != item_b, 'contract shouldnt be equals'); +} + +#[test] +#[should_panic(expected: ('should be a span',))] +fn test_item_unwrap_single_as_span() { + let item: Item = Item::Single(8); + item.unwrap_span(); +} + +#[test] +#[should_panic(expected: ('should be single',))] +fn test_item_unwrap_span_as_single() { + let item: Item = Item::Span(array![1, 2].span()); + item.unwrap_single(); +} + +// SerializableDict tests + +#[test] +fn test_serializable_dict_insert_single() { + let mut dict: SerializableFelt252Dict = SerializableFelt252DictTrait::new(); + + let key: felt252 = 'starknet'; + let expected_value: u256 = 42; + + dict.insert_single(key, expected_value); + + let retrieved_item: Item = dict.get(key).expect('key should be in dict'); + let out_value: u256 = retrieved_item.unwrap_single(); + + assert(dict.contains(key), 'key should be in dict'); + assert(dict.len() == 1, 'wrong dict len'); + assert(out_value == expected_value, 'wrong value'); +} + +#[test] +fn test_serializable_dict_insert_span() { + let mut dict: SerializableFelt252Dict = SerializableFelt252DictTrait::new(); + + let key: felt252 = 'starknet'; + let expected_array: Array = array![1, 2, 3]; + + dict.insert_span(key, expected_array.span()); + + let retrieved_item: Item = dict.get(key).expect('key should be in dict'); + let out_span: Span = retrieved_item.unwrap_span(); + + assert(dict.contains(key), 'key should be in dict'); + assert(dict.len() == 1, 'wrong dict len'); + assert(out_span.at(0) == expected_array.at(0), 'wrong at idx 0'); + assert(out_span.at(1) == expected_array.at(1), 'wrong at idx 1'); + assert(out_span.at(2) == expected_array.at(2), 'wrong at idx 2'); +} + +#[test] +fn test_serializable_dict_serialize() { + let mut dict: SerializableFelt252Dict = SerializableFelt252DictTrait::new(); + + assert(dict.is_empty(), 'dict should be empty'); + assert(dict.len() == 0, 'wrong empty dict len'); + + let expected_value: u256 = 42; + let expected_array: Array = array![1, 2, 3]; + + dict.insert_single('test', expected_value); + dict.insert_span('test_span', expected_array.span()); + + let serialized: Array = dict.serialize_into(); + + let mut span_serialized: Span = serialized.span(); + let mut deserialized_dict: SerializableFelt252Dict = + match SerializableFelt252DictTrait::::deserialize(ref span_serialized) { + Option::Some(d) => d, + Option::None => panic_with_felt252('err while recreating d') + }; + + assert(dict.contains('test'), 'key should be in dict'); + let retrieved_item: Item = dict.get('test').expect('key should be in dict'); + let out_value: u256 = retrieved_item.unwrap_single(); + + assert(dict.contains('test_span'), 'key should be in dict'); + let retrieved_item: Item = deserialized_dict + .get('test_span') + .expect('key should be in dict'); + let out_span: Span = retrieved_item.unwrap_span(); + assert(dict.len() == 2, 'wrong deserialized dict len'); + assert(dict.contains('test'), 'test should be in dict'); + assert(dict.contains('test_span'), 'test should be in dict'); + assert(out_span.at(0) == expected_array.at(0), 'wrong at idx 0'); + assert(out_span.at(1) == expected_array.at(1), 'wrong at idx 1'); + assert(out_span.at(2) == expected_array.at(2), 'wrong at idx 2'); +} + +#[test] +#[should_panic(expected: ('err getting value',))] +fn test_error_deserialize_value() { + let serialized_dict: Array = array!['key', 1, 1, 'key_2', 2, 1]; + let mut span_serialized: Span = serialized_dict.span(); + + match SerializableFelt252DictTrait::::deserialize(ref span_serialized) { + Option::Some(d) => panic_with_felt252('should have panicked'), + Option::None => () + }; +} + +#[test] +#[should_panic(expected: ('err getting size',))] +fn test_error_deserialize_size() { + let serialized_dict: Array = array!['key', 1, 1, 'key_2']; + let mut span_serialized: Span = serialized_dict.span(); + + match SerializableFelt252DictTrait::::deserialize(ref span_serialized) { + Option::Some(d) => panic_with_felt252('should have panicked'), + Option::None => () + }; +} diff --git a/src/tests/utils/test_starknet_utils.cairo b/tests/utils/test_starknet_utils.cairo similarity index 63% rename from src/tests/utils/test_starknet_utils.cairo rename to tests/utils/test_starknet_utils.cairo index 909b78ef..85dbf7eb 100644 --- a/src/tests/utils/test_starknet_utils.cairo +++ b/tests/utils/test_starknet_utils.cairo @@ -8,22 +8,22 @@ use satoru::tests_lib::{setup, teardown}; fn given_normal_conditions_when_sn_gasleft_then_works() { // No value provided, so returns 0 let default_value = sn_gasleft(array![]); - assert(default_value == 0_u128, 'default value wrong'); + assert(default_value == 0_u256, 'default value wrong'); // Value provided then returns that value - let value_as_felt: felt252 = 55_u128.into(); + let value_as_felt: felt252 = 55_u256.try_into().expect('u256 into felt failed'); let some_value = sn_gasleft(array![value_as_felt]); - assert(some_value == 55_u128, 'some value wrong'); + assert(some_value == 55_u256, 'some value wrong'); } #[test] fn given_normal_conditions_when_gasprice_then_works() { // No value provided, so returns 0 let default_value = sn_gasprice(array![]); - assert(default_value == 0_u128, 'default value wrong'); + assert(default_value == 0_u256, 'default value wrong'); // Value provided then returns that value - let value_as_felt: felt252 = 35_u128.into(); + let value_as_felt: felt252 = 35_u256.try_into().expect('u256 into felt failed'); let some_value = sn_gasprice(array![value_as_felt]); - assert(some_value == 35_u128, 'some value wrong'); + assert(some_value == 35_u256, 'some value wrong'); } diff --git a/src/tests/utils/test_u128_mask.cairo b/tests/utils/test_u256_mask.cairo similarity index 89% rename from src/tests/utils/test_u128_mask.cairo rename to tests/utils/test_u256_mask.cairo index c3bcb541..3b757499 100644 --- a/src/tests/utils/test_u128_mask.cairo +++ b/tests/utils/test_u256_mask.cairo @@ -1,5 +1,6 @@ -use satoru::utils::u128_mask::validate_unique_and_set_index; +use satoru::utils::u256_mask::validate_unique_and_set_index; use integer::BoundedInt; +use debug::PrintTrait; #[test] fn given_valid_index_bit_not_set_when_validate_unique_and_set_index_then_works() { @@ -19,7 +20,7 @@ fn given_valid_index_bit_already_set_when_validate_unique_and_set_index_then_fai #[should_panic(expected: ('mask index out of bounds',))] fn given_invalid_index_when_validate_unique_and_set_index_then_fails() { let mut mask = 0b0000_0000; - validate_unique_and_set_index(ref mask, 128); + validate_unique_and_set_index(ref mask, 256); } #[test] diff --git a/tests/withdrawal/test_withdrawal_vault.cairo b/tests/withdrawal/test_withdrawal_vault.cairo new file mode 100644 index 00000000..edf9f8dc --- /dev/null +++ b/tests/withdrawal/test_withdrawal_vault.cairo @@ -0,0 +1,292 @@ +//! Test file for `src/withdrawal/withdrawal_vault.cairo`. + +// ************************************************************************* +// IMPORTS +// ************************************************************************* +// Core lib imports. +use integer::{u256_from_felt252}; +use result::ResultTrait; +use starknet::{ + ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, + ClassHash, +}; +use snforge_std::{declare, start_prank, stop_prank, start_mock_call, ContractClassTrait}; +use traits::{TryInto, Into}; + +// Local imports. +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::withdrawal::withdrawal_vault::{ + IWithdrawalVaultDispatcher, IWithdrawalVaultDispatcherTrait +}; +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 withdrawal 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, withdrawal_vault, _) = setup(); + + withdrawal_vault.initialize(data_store.contract_address, role_store.contract_address); + + teardown(data_store, withdrawal_vault); +} + +#[test] +fn given_normal_conditions_when_transfer_out_then_works() { + let (caller_address, receiver_address, _, data_store, withdrawal_vault, erc20) = setup(); + + start_prank(withdrawal_vault.contract_address, caller_address); + + let amount_to_transfer: u256 = 100; + withdrawal_vault.transfer_out(erc20.contract_address, receiver_address, amount_to_transfer); + + // check that the contract balance reduces + let contract_balance = erc20.balance_of(withdrawal_vault.contract_address); + let expected_balance: u256 = u256_from_felt252( + INITIAL_TOKENS_MINTED - amount_to_transfer.try_into().expect('u256 into felt failed') + ); + assert(contract_balance == expected_balance, 'transfer_out failed'); + + // 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'); + + teardown(data_store, withdrawal_vault); +} + +#[test] +#[should_panic(expected: ('u256_sub Overflow',))] +fn given_not_enough_token_when_transfer_out_then_fails() { + let (_, receiver_address, _, data_store, withdrawal_vault, erc20) = setup(); + + let amount_to_transfer: u256 = u256_from_felt252(INITIAL_TOKENS_MINTED + 1); + withdrawal_vault.transfer_out(erc20.contract_address, receiver_address, amount_to_transfer); + + teardown(data_store, withdrawal_vault); +} + +#[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, withdrawal_vault, erc20) = setup(); + + stop_prank(withdrawal_vault.contract_address); + start_prank(withdrawal_vault.contract_address, receiver_address); + withdrawal_vault.transfer_out(erc20.contract_address, caller_address, 100_u256); + + teardown(data_store, withdrawal_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, withdrawal_vault, erc20) = setup(); + + withdrawal_vault + .transfer_out(erc20.contract_address, withdrawal_vault.contract_address, 100_u256); + + teardown(data_store, withdrawal_vault); +} + +#[test] +fn given_normal_conditions_when_record_transfer_in_then_works() { + let (_, _, _, data_store, withdrawal_vault, erc20) = setup(); + + let initial_balance: u256 = u256_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u256 = withdrawal_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + teardown(data_store, withdrawal_vault); +} + +#[test] +fn given_more_balance_when_2nd_record_transfer_in_then_works() { + let (_, _, _, data_store, withdrawal_vault, erc20) = setup(); + + let initial_balance: u256 = u256_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u256 = withdrawal_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + let tokens_transfered_in: u256 = 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: u256 = withdrawal_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == tokens_transfered_in, 'incorrect received amount'); + + teardown(data_store, withdrawal_vault); +} + +#[test] +#[should_panic(expected: ('u256_sub Overflow',))] +fn given_less_balance_when_2nd_record_transfer_in_then_fails() { + let (_, _, _, data_store, withdrawal_vault, erc20) = setup(); + + let initial_balance: u256 = u256_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u256 = withdrawal_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + let tokens_transfered_out: u256 = 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); + + withdrawal_vault.record_transfer_in(erc20.contract_address); + + teardown(data_store, withdrawal_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, withdrawal_vault, erc20) = setup(); + + role_store.revoke_role(caller_address, role::CONTROLLER); + withdrawal_vault.record_transfer_in(erc20.contract_address); + + teardown(data_store, withdrawal_vault); +} + +#[test] +fn given_more_balance_when_sync_token_balance_then_works() { + let (_, _, _, data_store, withdrawal_vault, erc20) = setup(); + + let initial_balance: u256 = u256_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u256 = withdrawal_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + let tokens_transfered_in: u256 = 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 next_balance: u256 = withdrawal_vault.sync_token_balance(erc20.contract_address); + assert(next_balance.into() == mock_balance_with_more_tokens, 'incorrect next balance'); + + teardown(data_store, withdrawal_vault); +} + +#[test] +fn given_less_balance_when_sync_token_balance_then_works() { + let (_, _, _, data_store, withdrawal_vault, erc20) = setup(); + + let initial_balance: u256 = u256_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u256 = withdrawal_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + let tokens_transfered_out: u256 = 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); + + let next_balance: u256 = withdrawal_vault.sync_token_balance(erc20.contract_address); + assert(next_balance.into() == mock_balance_with_less_tokens, 'incorrect next balance'); + + teardown(data_store, withdrawal_vault); +} + +// ********************************************************************************************* +// * SETUP * +// ********************************************************************************************* +/// Utility function to setup the test environment. +/// +/// Complete statement to retrieve everything: +/// let ( +/// caller_address, receiver_address, +/// role_store, data_store, +/// withdrawal_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. +/// * `IWithdrawalVaultDispatcher` - The withdrawal vault dispatcher. +/// * `IERC20Dispatcher` - The ERC20 token dispatcher. +fn setup() -> ( + ContractAddress, + ContractAddress, + IRoleStoreDispatcher, + IDataStoreDispatcher, + IWithdrawalVaultDispatcher, + IERC20Dispatcher +) { + // get caller_address, role store and data_store from tests_lib::setup() + let (caller_address, role_store, data_store) = tests_lib::setup(); + + // get receiver_address + let receiver_address: ContractAddress = 0x202.try_into().unwrap(); + + // deploy withdrawal vault + let withdrawal_vault_address = deploy_withdrawal_vault( + data_store.contract_address, role_store.contract_address + ); + let withdrawal_vault = IWithdrawalVaultDispatcher { + contract_address: withdrawal_vault_address + }; + + // deploy erc20 token + let erc20_contract_address = deploy_erc20_token(withdrawal_vault_address); + let erc20 = IERC20Dispatcher { contract_address: erc20_contract_address }; + + // start prank and give controller role to caller_address + start_prank(withdrawal_vault.contract_address, caller_address); + + return (caller_address, receiver_address, role_store, data_store, withdrawal_vault, erc20); +} + +/// Utility function to deploy a withdrawal 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 withdrawal vault. +fn deploy_withdrawal_vault( + data_store_address: ContractAddress, role_store_address: ContractAddress +) -> ContractAddress { + let withdrawal_vault_contract = declare('WithdrawalVault'); + let constructor_calldata = array![data_store_address.into(), role_store_address.into()]; + withdrawal_vault_contract.deploy(@constructor_calldata).unwrap() +} + +/// Utility function to deploy an ERC20 token. +/// When deployed, 1000 tokens are minted to the withdrawal vault address. +/// +/// # Arguments +/// +/// * `withdrawal_vault_address` - The address of the withdrawal vault address. +/// +/// # Returns +/// +/// * `ContractAddress` - The address of the ERC20 token. +fn deploy_erc20_token(withdrawal_vault_address: ContractAddress) -> ContractAddress { + let erc20_contract = declare('ERC20'); + let constructor_calldata = array![ + 'satoru', 'STU', INITIAL_TOKENS_MINTED, 0, withdrawal_vault_address.into() + ]; + erc20_contract.deploy(@constructor_calldata).unwrap() +} + +// ********************************************************************************************* +// * TEARDOWN * +// ********************************************************************************************* +fn teardown(data_store: IDataStoreDispatcher, withdrawal_vault: IWithdrawalVaultDispatcher) { + tests_lib::teardown(data_store.contract_address); + stop_prank(withdrawal_vault.contract_address); +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..42e9c136 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,471 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@noble/curves@~1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" + integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== + dependencies: + "@noble/hashes" "1.3.3" + +"@noble/curves@~1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" + integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== + dependencies: + "@noble/hashes" "1.4.0" + +"@noble/hashes@1.3.3", "@noble/hashes@~1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" + integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== + +"@noble/hashes@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + +"@scure/base@~1.1.3": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" + integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== + +"@scure/starknet@~1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@scure/starknet/-/starknet-1.0.0.tgz#4419bc2fdf70f3dd6cb461d36c878c9ef4419f8c" + integrity sha512-o5J57zY0f+2IL/mq8+AYJJ4Xpc1fOtDhr+mFQKbHnYFmm3WQrC+8zj2HEgxak1a+x86mhmBC1Kq305KUpVf0wg== + dependencies: + "@noble/curves" "~1.3.0" + "@noble/hashes" "~1.3.3" + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/node@^20.11.16": + version "20.11.16" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.16.tgz#4411f79411514eb8e2926f036c86c9f0e4ec6708" + integrity sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ== + dependencies: + undici-types "~5.26.4" + +abi-wan-kanabi@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/abi-wan-kanabi/-/abi-wan-kanabi-2.2.2.tgz#82c48e8fa08d9016cf92d3d81d494cc60e934693" + integrity sha512-sTCv2HyNIj1x2WFUoc9oL8ZT9liosrL+GoqEGZJK1kDND096CfA7lwx06vLxLWMocQ41FQXO3oliwoh/UZHYdQ== + dependencies: + ansicolors "^0.3.2" + cardinal "^2.1.1" + fs-extra "^10.0.0" + yargs "^17.7.2" + +acorn-walk@^8.1.1: + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + +acorn@^8.4.1: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansicolors@^0.3.2, ansicolors@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +cardinal@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" + integrity sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw== + dependencies: + ansicolors "~0.3.2" + redeyed "~2.1.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dotenv@^16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.1.tgz#1d9931f1d3e5d2959350d1250efab299561f7f11" + integrity sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +esprima@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +fetch-cookie@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-3.0.1.tgz#6a77f7495e1a639ae019db916a234db8c85d5963" + integrity sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q== + dependencies: + set-cookie-parser "^2.4.8" + tough-cookie "^4.0.0" + +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +isomorphic-fetch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" + integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== + dependencies: + node-fetch "^2.6.1" + whatwg-fetch "^3.4.1" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +lossless-json@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lossless-json/-/lossless-json-4.0.1.tgz#d45229e3abb213a0235812780ca894ea8c5b2c6b" + integrity sha512-l0L+ppmgPDnb+JGxNLndPtJZGNf6+ZmVaQzoxQm3u6TXmhdnsA+YtdVR8DjzZd/em58686CQhOFDPewfJ4l7MA== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +node-fetch@^2.6.1: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +pako@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +punycode@^2.1.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +redeyed@~2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" + integrity sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ== + dependencies: + esprima "~4.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +set-cookie-parser@^2.4.8: + version "2.6.0" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51" + integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ== + +"starknet-types-07@npm:starknet-types@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/starknet-types/-/starknet-types-0.7.2.tgz#2a1be2392e6e568484afabdc1e83411b5105713b" + integrity sha512-r3JJ0rrK0g3FnVRGcFiLY+9YT5WZgxB4TKBfR44wYGevHtKEM6BM5B+Gn1eou1zV7xEAwz3GpmvLSQTUAzDhsw== + +starknet@^6.6.6: + version "6.8.0" + resolved "https://registry.yarnpkg.com/starknet/-/starknet-6.8.0.tgz#d7bb1dc1470035f31fe1bfd68e3aee5cbb190c10" + integrity sha512-HNGgTomnEYbx8UiHNX9vTpa7tg7a1+BHW3vmqfnoejc0L/XjZ6N6h56S18nf8ZGDbe//yfaqk50eGqTaw2oR0g== + dependencies: + "@noble/curves" "~1.4.0" + "@scure/base" "~1.1.3" + "@scure/starknet" "~1.0.0" + abi-wan-kanabi "^2.2.2" + fetch-cookie "^3.0.0" + isomorphic-fetch "^3.0.0" + lossless-json "^4.0.1" + pako "^2.0.4" + starknet-types-07 "npm:starknet-types@^0.7.2" + ts-mixer "^6.0.3" + url-join "^4.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +tough-cookie@^4.0.0: + version "4.1.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +ts-mixer@^6.0.3: + version "6.0.4" + resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.4.tgz#1da39ceabc09d947a82140d9f09db0f84919ca28" + integrity sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA== + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +url-join@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-fetch@^3.4.1: + version "3.6.20" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" + integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==