├── .env.example ├── .github └── workflows │ ├── README.md │ ├── katana.yaml │ ├── katana │ └── katana.env │ ├── linters.yaml │ ├── security.yaml │ └── tests.yaml ├── .gitignore ├── .prettierignore ├── .tool-versions ├── CODEOWNERS ├── LICENSE ├── Makefile ├── README.md ├── Scarb.toml ├── crates ├── yas_core │ ├── .gitignore │ ├── Scarb.toml │ └── src │ │ ├── contracts │ │ ├── yas_erc20.cairo │ │ ├── yas_factory.cairo │ │ ├── yas_pool.cairo │ │ └── yas_router.cairo │ │ ├── interfaces │ │ ├── interface_ERC20.cairo │ │ ├── interface_yas_mint_callback.cairo │ │ └── interface_yas_swap_callback.cairo │ │ ├── lib.cairo │ │ ├── libraries │ │ ├── bit_math.cairo │ │ ├── liquidity_math.cairo │ │ ├── position.cairo │ │ ├── sqrt_price_math.cairo │ │ ├── swap_math.cairo │ │ ├── tick.cairo │ │ ├── tick_bitmap.cairo │ │ └── tick_math.cairo │ │ ├── numbers │ │ ├── fixed_point.cairo │ │ ├── fixed_point │ │ │ ├── core.cairo │ │ │ ├── implementations.cairo │ │ │ ├── implementations │ │ │ │ └── impl_64x96.cairo │ │ │ ├── math.cairo │ │ │ └── math │ │ │ │ └── math_64x96.cairo │ │ ├── signed_integer.cairo │ │ └── signed_integer │ │ │ ├── i128.cairo │ │ │ ├── i16.cairo │ │ │ ├── i256.cairo │ │ │ ├── i32.cairo │ │ │ ├── i64.cairo │ │ │ ├── i8.cairo │ │ │ └── integer_trait.cairo │ │ ├── tests │ │ ├── test_contracts │ │ │ ├── test_yas_factory.cairo │ │ │ └── test_yas_pool.cairo │ │ ├── test_libraries │ │ │ ├── test_bit_math.cairo │ │ │ ├── test_liquidity_math.cairo │ │ │ ├── test_position.cairo │ │ │ ├── test_sqrt_price_math.cairo │ │ │ ├── test_swap_math.cairo │ │ │ ├── test_tick.cairo │ │ │ ├── test_tick_bitmap.cairo │ │ │ └── test_tick_math.cairo │ │ ├── test_numbers │ │ │ ├── test_fixed_point.cairo │ │ │ ├── test_fixed_point │ │ │ │ └── test_fp64x96.cairo │ │ │ ├── test_signed_integer.cairo │ │ │ └── test_signed_integer │ │ │ │ ├── test_i256.cairo │ │ │ │ ├── test_i32.cairo │ │ │ │ └── test_signed_integer.cairo │ │ ├── test_utils │ │ │ ├── test_math_utils.cairo │ │ │ └── test_utils.cairo │ │ └── utils │ │ │ ├── constants.cairo │ │ │ ├── pool_1.cairo │ │ │ └── swap_cases.cairo │ │ └── utils │ │ ├── math_utils.cairo │ │ └── utils.cairo ├── yas_faucet │ ├── .gitignore │ ├── Scarb.toml │ └── src │ │ ├── lib.cairo │ │ ├── tests │ │ └── test_yas_faucet.cairo │ │ └── yas_faucet.cairo └── yas_periphery │ ├── .gitignore │ ├── Scarb.toml │ └── src │ ├── lib.cairo │ ├── tests │ └── test_yas_nft_position_manager.cairo │ └── yas_nft_position_manager.cairo ├── deprecated_scripts ├── Cargo.lock ├── Cargo.toml ├── rust-toolchain ├── rustfmt.toml └── scripts │ ├── deploy.rs │ └── local.rs ├── scripts ├── deploy.sh ├── run_local_demo.sh ├── setup_katana_account.sh └── utility │ ├── README.md │ ├── convert_to_64x96.py │ └── parse_to_cairo_struct.py └── yas.png /.env.example: -------------------------------------------------------------------------------- 1 | # Copy this file to .env and fill in the values 2 | 3 | # Katana Prefunded Account 4 | ACCOUNT_ADDRESS=0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 5 | ACCOUNT_PRIVATE_KEY=0x1800000000300000180000000000030000000000003006001800006600 6 | ACCOUNT_SRC=~/.starkli-wallets/account_katana.json 7 | RPC_URL=http://0.0.0.0:5050 8 | -------------------------------------------------------------------------------- /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # Testing GitHub workflows locally 2 | 3 | We are using [act](`https://github.com/nektos/act`) to test GitHub actions locally 4 | 5 | ### Installation 6 | 7 | #### For Linux users: 8 | 9 | 1. Download `act` tool 10 | 11 | ```bash 12 | curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash 13 | ``` 14 | 15 | 2. Move it to the appropiate /bin folder: 16 | 17 | ```bash 18 | mv ./bin/act /bin/act 19 | ``` 20 | 21 | 3. Verify it is correctly installed: 22 | 23 | ```bash 24 | act --version 25 | ``` 26 | 27 | #### For MacOS users: 28 | 29 | 1. simply run: 30 | 31 | ```bash 32 | brew install act 33 | ``` 34 | 35 | 2. Verify it is correctly installed: 36 | 37 | ```bash 38 | act --version 39 | ``` 40 | 41 | ### How to use act 42 | 43 | #### Linux 44 | 45 | To run all the 'workflows': 46 | 47 | ```bash 48 | act -a .github/workflows/ 49 | ``` 50 | 51 | To run only one of the github events, specify it with the `-j` flag 52 | 53 | ```bash 54 | act -a .github/workflows/ -j katana 55 | ``` 56 | 57 | #### For MacOS users with M1 processor or newer versions: 58 | 59 | To run all the 'workflows': 60 | 61 | ```bash 62 | act -a .github/workflows/ --container-architecture linux/amd64 63 | ``` 64 | 65 | To run only one of the github events, specify it with the `-j` flag: 66 | 67 | ```bash 68 | act -a .github/workflows/ -j katana --container-architecture linux/amd64 69 | ``` 70 | -------------------------------------------------------------------------------- /.github/workflows/katana.yaml: -------------------------------------------------------------------------------- 1 | name: Run Katana 2 | on: 3 | merge_group: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: ["*"] 8 | jobs: 9 | katana: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Install scarb 15 | uses: software-mansion/setup-scarb@v1 16 | with: 17 | scarb-version: "0.7.0" 18 | 19 | - name: Install starkliup 20 | run: curl https://get.starkli.sh | sh 21 | 22 | - name: Install Starkli 23 | run: |- 24 | /home/runner/.config/.starkli/bin/starkliup --version 0.1.20 25 | sudo mv /home/runner/.config/.starkli/bin/starkli /usr/local/bin/ 26 | 27 | - name: Setup Katana .env 28 | run: | 29 | cp .github/workflows/katana/katana.env .env 30 | 31 | - name: Download Katana 32 | run: | 33 | wget https://github.com/dojoengine/dojo/releases/download/v0.3.1/dojo_v0.3.1_linux_amd64.tar.gz 34 | tar -xzvf dojo_v0.3.1_linux_amd64.tar.gz 35 | rm sozo torii dojo-language-server 36 | 37 | - name: Run Tests Demo 38 | run: | 39 | ./katana & 40 | make demo-local 41 | 42 | - name: Run Tests Deploy 43 | run: | 44 | ./katana & 45 | make deploy 46 | -------------------------------------------------------------------------------- /.github/workflows/katana/katana.env: -------------------------------------------------------------------------------- 1 | #Only for running test in CI 2 | # Katana Prefunded Account 3 | ACCOUNT_ADDRESS=0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 4 | ACCOUNT_PRIVATE_KEY=0x1800000000300000180000000000030000000000003006001800006600 5 | ACCOUNT_SRC=/home/runner/.config/.starkli/account_katana.json 6 | RPC_URL=http://0.0.0.0:5050 7 | -------------------------------------------------------------------------------- /.github/workflows/linters.yaml: -------------------------------------------------------------------------------- 1 | name: Task - Linters 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | jobs: 8 | prettier: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Run prettier 13 | run: |- 14 | npx prettier --check . 15 | -------------------------------------------------------------------------------- /.github/workflows/security.yaml: -------------------------------------------------------------------------------- 1 | name: Security 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | jobs: 8 | check: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: software-mansion/setup-scarb@v1 13 | with: 14 | scarb-version: "0.7.0" 15 | 16 | - name: Install Semgrep 17 | run: | 18 | pip install semgrep==1.45.0 19 | - name: Run Semgrep 20 | run: semgrep --config https://github.com/avnu-labs/semgrep-cairo-rules/releases/download/v0.0.1/cairo-rules.yaml ./crates > semgrep-output.txt 21 | - name: Save Semgrep Output as an Artifact 22 | uses: actions/upload-artifact@v3 23 | with: 24 | name: semgrep-cairo 25 | path: semgrep-output.txt 26 | 27 | - name: Build cairo programs 28 | run: scarb build 29 | 30 | - name: Cache Cargo dependencies 31 | uses: actions/cache@v2 32 | with: 33 | path: | 34 | ~/.cargo 35 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 36 | 37 | - name: Check if Caracal is installed 38 | id: check-caracal 39 | run: | 40 | if ! command -v caracal &> /dev/null; then 41 | echo "Caracal is not installed. Installing..." 42 | cargo install --git https://github.com/crytic/caracal --profile release --force 43 | else 44 | echo "Caracal is already installed." 45 | fi 46 | - name: Run Caracal 47 | run: caracal detect . > caracal-output.txt 48 | - name: Save Caracal Output as an Artifact 49 | uses: actions/upload-artifact@v3 50 | with: 51 | name: caracal-cairo 52 | path: caracal-output.txt 53 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | merge_group: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: ["*"] 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: software-mansion/setup-scarb@v1 14 | with: 15 | scarb-version: "0.7.0" 16 | - run: scarb fmt --check 17 | - run: scarb test 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .starkli-wallets/** 2 | target 3 | .env 4 | .idea -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .starkli-wallets/** 2 | target 3 | .env 4 | .idea 5 | 6 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | scarb 0.7.0 2 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @rcatalan98 @dpinones @dubzn -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | deps: install-dojo 2 | curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 0.7.0 \ 3 | 4 | fmt: 5 | scarb fmt 6 | npx prettier -w . 7 | 8 | install-dojo: 9 | @echo "Installing Dojo..." 10 | @if [ ! -d "${HOME}/.dojo" ]; then mkdir -p ${HOME}/.dojo; fi 11 | @cd ${HOME}/.dojo && \ 12 | if [ ! -d "dojo" ]; then git clone https://github.com/dojoengine/dojo; fi && \ 13 | cd dojo && \ 14 | cargo install --path ./crates/katana --locked --force 15 | @echo "Dojo installation complete." 16 | 17 | start-katana: 18 | katana 19 | 20 | clean: 21 | scarb clean 22 | 23 | build: clean 24 | scarb build 25 | 26 | deploy: build 27 | @./scripts/deploy.sh 28 | 29 | demo-local: build 30 | @./scripts/run_local_demo.sh 31 | 32 | Command := $(firstword $(MAKECMDGOALS)) 33 | FILTER := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) 34 | test: 35 | ifneq ($(FILTER),) 36 | scarb test -f $(FILTER) 37 | else 38 | scarb test 39 | endif 40 | %:: 41 | @true 42 | 43 | setup-katana-account: 44 | @./scripts/setup_katana_account.sh 45 | -------------------------------------------------------------------------------- /Scarb.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*"] 3 | 4 | [workspace.package] 5 | description = "yas" 6 | version = "0.1.0" 7 | cairo-version = "2.2.0" 8 | readme = "README.md" 9 | repository = "https://github.com/lambdaclass/yet-another-swap/" 10 | 11 | [workspace.dependencies] 12 | starknet = ">=2.2.0" 13 | openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" } 14 | 15 | [[workspace.tool.starknet-contract]] 16 | -------------------------------------------------------------------------------- /crates/yas_core/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /crates/yas_core/Scarb.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas_core" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | starknet.workspace = true 7 | 8 | [lib] 9 | 10 | [[target.starknet-contract]] 11 | sierra = true 12 | 13 | [cairo] 14 | sierra-replace-ids = true 15 | -------------------------------------------------------------------------------- /crates/yas_core/src/contracts/yas_erc20.cairo: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts for Cairo v0.7.0 (token/erc20/erc20.cairo) 3 | use starknet::ContractAddress; 4 | 5 | #[starknet::interface] 6 | trait IERC20 { 7 | fn name(self: @TState) -> felt252; 8 | fn symbol(self: @TState) -> felt252; 9 | fn decimals(self: @TState) -> u8; 10 | fn totalSupply(self: @TState) -> u256; 11 | fn balanceOf(self: @TState, account: ContractAddress) -> u256; 12 | fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; 13 | fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; 14 | fn transferFrom( 15 | ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 16 | ) -> bool; 17 | fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; 18 | } 19 | 20 | #[starknet::contract] 21 | mod ERC20 { 22 | use super::IERC20; 23 | 24 | use integer::BoundedInt; 25 | use starknet::ContractAddress; 26 | use starknet::get_caller_address; 27 | 28 | #[storage] 29 | struct Storage { 30 | ERC20_name: felt252, 31 | ERC20_symbol: felt252, 32 | ERC20_total_supply: u256, 33 | ERC20_balances: LegacyMap, 34 | ERC20_allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, 35 | } 36 | 37 | #[event] 38 | #[derive(Drop, starknet::Event)] 39 | enum Event { 40 | Transfer: Transfer, 41 | Approval: Approval, 42 | } 43 | 44 | #[derive(Drop, starknet::Event)] 45 | struct Transfer { 46 | from: ContractAddress, 47 | to: ContractAddress, 48 | value: u256 49 | } 50 | 51 | #[derive(Drop, starknet::Event)] 52 | struct Approval { 53 | owner: ContractAddress, 54 | spender: ContractAddress, 55 | value: u256 56 | } 57 | 58 | mod Errors { 59 | const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; 60 | const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; 61 | const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; 62 | const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; 63 | const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0'; 64 | const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; 65 | } 66 | 67 | #[constructor] 68 | fn constructor( 69 | ref self: ContractState, 70 | name: felt252, 71 | symbol: felt252, 72 | initial_supply: u256, 73 | recipient: ContractAddress 74 | ) { 75 | self.initializer(name, symbol); 76 | self._mint(recipient, initial_supply); 77 | } 78 | 79 | // 80 | // External 81 | // 82 | 83 | #[external(v0)] 84 | impl ERC20Impl of IERC20 { 85 | fn name(self: @ContractState) -> felt252 { 86 | self.ERC20_name.read() 87 | } 88 | 89 | fn symbol(self: @ContractState) -> felt252 { 90 | self.ERC20_symbol.read() 91 | } 92 | 93 | fn decimals(self: @ContractState) -> u8 { 94 | 18 95 | } 96 | 97 | fn totalSupply(self: @ContractState) -> u256 { 98 | self.ERC20_total_supply.read() 99 | } 100 | 101 | fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { 102 | self.ERC20_balances.read(account) 103 | } 104 | 105 | fn allowance( 106 | self: @ContractState, owner: ContractAddress, spender: ContractAddress 107 | ) -> u256 { 108 | self.ERC20_allowances.read((owner, spender)) 109 | } 110 | 111 | fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { 112 | let sender = get_caller_address(); 113 | self._transfer(sender, recipient, amount); 114 | true 115 | } 116 | 117 | fn transferFrom( 118 | ref self: ContractState, 119 | sender: ContractAddress, 120 | recipient: ContractAddress, 121 | amount: u256 122 | ) -> bool { 123 | let caller = get_caller_address(); 124 | self._spend_allowance(sender, caller, amount); 125 | self._transfer(sender, recipient, amount); 126 | true 127 | } 128 | 129 | fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { 130 | let caller = get_caller_address(); 131 | self._approve(caller, spender, amount); 132 | true 133 | } 134 | } 135 | 136 | #[external(v0)] 137 | fn increase_allowance( 138 | ref self: ContractState, spender: ContractAddress, added_value: u256 139 | ) -> bool { 140 | self._increase_allowance(spender, added_value) 141 | } 142 | 143 | #[external(v0)] 144 | fn increaseAllowance( 145 | ref self: ContractState, spender: ContractAddress, addedValue: u256 146 | ) -> bool { 147 | increase_allowance(ref self, spender, addedValue) 148 | } 149 | 150 | #[external(v0)] 151 | fn decrease_allowance( 152 | ref self: ContractState, spender: ContractAddress, subtracted_value: u256 153 | ) -> bool { 154 | self._decrease_allowance(spender, subtracted_value) 155 | } 156 | 157 | #[external(v0)] 158 | fn decreaseAllowance( 159 | ref self: ContractState, spender: ContractAddress, subtractedValue: u256 160 | ) -> bool { 161 | decrease_allowance(ref self, spender, subtractedValue) 162 | } 163 | 164 | // 165 | // Internal 166 | // 167 | 168 | #[generate_trait] 169 | impl InternalImpl of InternalTrait { 170 | fn initializer(ref self: ContractState, name: felt252, symbol: felt252) { 171 | self.ERC20_name.write(name); 172 | self.ERC20_symbol.write(symbol); 173 | } 174 | 175 | fn _increase_allowance( 176 | ref self: ContractState, spender: ContractAddress, added_value: u256 177 | ) -> bool { 178 | let caller = get_caller_address(); 179 | self 180 | ._approve( 181 | caller, spender, self.ERC20_allowances.read((caller, spender)) + added_value 182 | ); 183 | true 184 | } 185 | 186 | fn _decrease_allowance( 187 | ref self: ContractState, spender: ContractAddress, subtracted_value: u256 188 | ) -> bool { 189 | let caller = get_caller_address(); 190 | self 191 | ._approve( 192 | caller, 193 | spender, 194 | self.ERC20_allowances.read((caller, spender)) - subtracted_value 195 | ); 196 | true 197 | } 198 | 199 | fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { 200 | assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); 201 | self.ERC20_total_supply.write(self.ERC20_total_supply.read() + amount); 202 | self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount); 203 | self.emit(Transfer { from: Zeroable::zero(), to: recipient, value: amount }); 204 | } 205 | 206 | fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { 207 | assert(!account.is_zero(), Errors::BURN_FROM_ZERO); 208 | self.ERC20_total_supply.write(self.ERC20_total_supply.read() - amount); 209 | self.ERC20_balances.write(account, self.ERC20_balances.read(account) - amount); 210 | self.emit(Transfer { from: account, to: Zeroable::zero(), value: amount }); 211 | } 212 | 213 | fn _approve( 214 | ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 215 | ) { 216 | assert(!owner.is_zero(), Errors::APPROVE_FROM_ZERO); 217 | assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO); 218 | self.ERC20_allowances.write((owner, spender), amount); 219 | self.emit(Approval { owner, spender, value: amount }); 220 | } 221 | 222 | fn _transfer( 223 | ref self: ContractState, 224 | sender: ContractAddress, 225 | recipient: ContractAddress, 226 | amount: u256 227 | ) { 228 | assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); 229 | assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); 230 | self.ERC20_balances.write(sender, self.ERC20_balances.read(sender) - amount); 231 | self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount); 232 | self.emit(Transfer { from: sender, to: recipient, value: amount }); 233 | } 234 | 235 | fn _spend_allowance( 236 | ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 237 | ) { 238 | let current_allowance = self.ERC20_allowances.read((owner, spender)); 239 | if current_allowance != BoundedInt::max() { 240 | self._approve(owner, spender, current_allowance - amount); 241 | } 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /crates/yas_core/src/contracts/yas_factory.cairo: -------------------------------------------------------------------------------- 1 | use starknet::ContractAddress; 2 | use yas_core::numbers::signed_integer::i32::i32; 3 | 4 | /// @title The interface for the YAS Factory 5 | /// @notice The YAS Factory facilitates creation of YAS pools and control over the protocol fees 6 | #[starknet::interface] 7 | trait IYASFactory { 8 | /// @notice Returns the current owner of the factory 9 | /// @dev Can be changed by the current owner via set_owner 10 | /// @return The address of the factory owner 11 | fn owner(self: @TContractState) -> ContractAddress; 12 | 13 | /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled 14 | /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context 15 | /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee 16 | /// @return The tick spacing 17 | fn fee_amount_tick_spacing(self: @TContractState, fee: u32) -> i32; 18 | 19 | /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist 20 | /// @dev token_a and token_b may be passed in either token_0/token_1 or token_1/token_0 order 21 | /// @param token_a The contract address of either token0 or token1 22 | /// @param token_b The contract address of the other token 23 | /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip 24 | /// @return pool The pool address 25 | fn pool( 26 | self: @TContractState, token_a: ContractAddress, token_b: ContractAddress, fee: u32 27 | ) -> ContractAddress; 28 | 29 | /// @notice Creates a pool for the given two tokens and fee 30 | /// @param token_a One of the two tokens in the desired pool 31 | /// @param token_b The other of the two tokens in the desired pool 32 | /// @param fee The desired fee for the pool 33 | /// @dev token_a and token_b may be passed in either order: token_0/token_1 or token_1/token_0. tick_spacing is retrieved 34 | /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments 35 | /// are invalid. 36 | /// @return pool The address of the newly created pool 37 | fn create_pool( 38 | ref self: TContractState, token_a: ContractAddress, token_b: ContractAddress, fee: u32 39 | ) -> ContractAddress; 40 | 41 | /// @notice Updates the owner of the factory 42 | /// @dev Must be called by the current owner 43 | /// @param new_owner The new owner of the factory 44 | fn set_owner(ref self: TContractState, new_owner: ContractAddress); 45 | 46 | /// @notice Enables a fee amount with the given tick_spacing 47 | /// @dev Fee amounts may never be removed once enabled 48 | /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6) 49 | /// @param tick_spacing The spacing between ticks to be enforced for all pools created with the given fee amount 50 | fn enable_fee_amount(ref self: TContractState, fee: u32, tick_spacing: i32); 51 | } 52 | 53 | 54 | #[starknet::contract] 55 | mod YASFactory { 56 | use super::IYASFactory; 57 | 58 | use poseidon::poseidon_hash_span; 59 | use starknet::SyscallResultTrait; 60 | use starknet::syscalls::deploy_syscall; 61 | use starknet::{ContractAddress, ClassHash, get_caller_address, get_contract_address}; 62 | 63 | use yas_core::contracts::yas_pool::{ 64 | YASPool, IYASPool, IYASPoolDispatcher, IYASPoolDispatcherTrait 65 | }; 66 | use yas_core::numbers::signed_integer::{i32::i32, integer_trait::IntegerTrait}; 67 | use yas_core::utils::utils::ContractAddressPartialOrd; 68 | 69 | #[event] 70 | #[derive(Drop, starknet::Event)] 71 | enum Event { 72 | OwnerChanged: OwnerChanged, 73 | PoolCreated: PoolCreated, 74 | FeeAmountEnabled: FeeAmountEnabled 75 | } 76 | 77 | /// @notice Emitted when the owner of the factory is changed 78 | /// @param old_owner The owner before the owner was changed 79 | /// @param new_owner The owner after the owner was changed 80 | #[derive(Drop, starknet::Event)] 81 | struct OwnerChanged { 82 | old_owner: ContractAddress, 83 | new_owner: ContractAddress 84 | } 85 | 86 | /// @notice Emitted when a pool is created 87 | /// @param token_0 The first token of the pool by address sort order 88 | /// @param token_1 The second token of the pool by address sort order 89 | /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip 90 | /// @param tick_spacing The minimum number of ticks between initialized ticks 91 | /// @param pool The address of the created pool 92 | #[derive(Drop, starknet::Event)] 93 | struct PoolCreated { 94 | token_0: ContractAddress, 95 | token_1: ContractAddress, 96 | fee: u32, 97 | tick_spacing: i32, 98 | pool: ContractAddress 99 | } 100 | 101 | /// @notice Emitted when a new fee amount is enabled for pool creation via the factory 102 | /// @param fee The enabled fee, denominated in hundredths of a bip 103 | /// @param tick_spacing The minimum number of ticks between initialized ticks for pools created with the given fee 104 | #[derive(Drop, starknet::Event)] 105 | struct FeeAmountEnabled { 106 | fee: u32, 107 | tick_spacing: i32 108 | } 109 | 110 | #[storage] 111 | struct Storage { 112 | owner: ContractAddress, 113 | fee_amount_tick_spacing: LegacyMap::, 114 | pool: LegacyMap<(ContractAddress, ContractAddress, u32), ContractAddress>, 115 | pool_class_hash: ClassHash 116 | } 117 | 118 | #[constructor] 119 | fn constructor(ref self: ContractState, owner: ContractAddress, pool_class_hash: ClassHash) { 120 | self.owner.write(owner); 121 | self.emit(OwnerChanged { old_owner: Zeroable::zero(), new_owner: owner }); 122 | 123 | assert(pool_class_hash.is_non_zero(), 'pool class hash can not be zero'); 124 | self.pool_class_hash.write(pool_class_hash); 125 | 126 | // value derived from a simple proportion. 127 | // fee %0.01 -> tick_spacing 2 128 | self.fee_amount_tick_spacing.write(100, IntegerTrait::::new(2, false)); 129 | self.emit(FeeAmountEnabled { fee: 100, tick_spacing: IntegerTrait::::new(2, false) }); 130 | 131 | // fee %0.05 -> tick_spacing 10 132 | self.fee_amount_tick_spacing.write(500, IntegerTrait::::new(10, false)); 133 | self.emit(FeeAmountEnabled { fee: 500, tick_spacing: IntegerTrait::::new(10, false) }); 134 | 135 | // fee %0.3 -> tick_spacing 60 136 | self.fee_amount_tick_spacing.write(3000, IntegerTrait::::new(60, false)); 137 | self 138 | .emit( 139 | FeeAmountEnabled { fee: 3000, tick_spacing: IntegerTrait::::new(60, false) } 140 | ); 141 | 142 | // fee %1 -> tick_spacing 200 143 | self.fee_amount_tick_spacing.write(10000, IntegerTrait::::new(200, false)); 144 | self 145 | .emit( 146 | FeeAmountEnabled { fee: 10000, tick_spacing: IntegerTrait::::new(200, false) } 147 | ); 148 | } 149 | 150 | #[external(v0)] 151 | impl YASFactoryImpl of IYASFactory { 152 | fn owner(self: @ContractState) -> ContractAddress { 153 | self.owner.read() 154 | } 155 | 156 | fn fee_amount_tick_spacing(self: @ContractState, fee: u32) -> i32 { 157 | self.fee_amount_tick_spacing.read(fee) 158 | } 159 | 160 | fn pool( 161 | self: @ContractState, token_a: ContractAddress, token_b: ContractAddress, fee: u32 162 | ) -> ContractAddress { 163 | self.pool.read((token_a, token_b, fee)) 164 | } 165 | 166 | fn create_pool( 167 | ref self: ContractState, token_a: ContractAddress, token_b: ContractAddress, fee: u32 168 | ) -> ContractAddress { 169 | assert(token_a != token_b, 'tokens must be different'); 170 | assert( 171 | token_a.is_non_zero() && token_b.is_non_zero(), 'tokens addresses cannot be zero' 172 | ); 173 | 174 | let (token_0, token_1) = if token_a < token_b { 175 | (token_a, token_b) 176 | } else { 177 | (token_b, token_a) 178 | }; 179 | 180 | let tick_spacing = self.fee_amount_tick_spacing(fee); 181 | assert(tick_spacing.is_non_zero(), 'tick spacing not initialized'); 182 | 183 | assert(self.pool(token_0, token_1, fee).is_zero(), 'token pair already created'); 184 | 185 | let contract_address_salt = generate_salt(@token_0, @token_1); 186 | let calldata = serialize_calldata(@token_0, @token_1, fee, @tick_spacing); 187 | 188 | let (pool, _) = deploy_syscall( 189 | self.pool_class_hash.read(), contract_address_salt, calldata.span(), false 190 | ) 191 | .unwrap_syscall(); 192 | 193 | self.pool.write((token_0, token_1, fee), pool); 194 | // populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses 195 | self.pool.write((token_1, token_0, fee), pool); 196 | 197 | self.emit(PoolCreated { token_0, token_1, fee, tick_spacing, pool }); 198 | 199 | pool 200 | } 201 | 202 | fn enable_fee_amount(ref self: ContractState, fee: u32, tick_spacing: i32) { 203 | self.assert_only_owner(); 204 | assert(fee < 1000000, 'fee cannot be gt 1000000'); 205 | 206 | // tick spacing is capped at 16384 to prevent the situation where tick_spacing is so large that 207 | // TickBitmap#nextInitializedTickWithinOneWord overflows int24 container from a valid tick 208 | // 16384 ticks represents a >5x price change with ticks of 1 bips 209 | assert( 210 | tick_spacing > Zeroable::zero() 211 | && tick_spacing < IntegerTrait::::new(16384, false), 212 | 'wrong tick_spacing (0 Array { 237 | let mut calldata = array![]; 238 | calldata.append(get_contract_address().into()); 239 | Serde::serialize(token_0, ref calldata); 240 | Serde::serialize(token_1, ref calldata); 241 | calldata.append(fee.into()); 242 | Serde::serialize(tick_spacing, ref calldata); 243 | 244 | calldata 245 | } 246 | 247 | fn generate_salt(token_0: @ContractAddress, token_1: @ContractAddress) -> felt252 { 248 | let mut data = array![]; 249 | Serde::serialize(token_0, ref data); 250 | Serde::serialize(token_1, ref data); 251 | poseidon_hash_span(data.span()) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /crates/yas_core/src/contracts/yas_router.cairo: -------------------------------------------------------------------------------- 1 | use starknet::ContractAddress; 2 | 3 | use yas_core::numbers::signed_integer::{i32::i32, i256::i256}; 4 | use yas_core::numbers::fixed_point::implementations::impl_64x96::FixedType; 5 | 6 | #[starknet::interface] 7 | trait IYASRouter { 8 | fn mint( 9 | self: @TContractState, 10 | pool: ContractAddress, 11 | recipient: ContractAddress, 12 | tick_lower: i32, 13 | tick_upper: i32, 14 | amount: u128 15 | ); 16 | fn yas_mint_callback( 17 | ref self: TContractState, amount_0_owed: u256, amount_1_owed: u256, data: Array 18 | ); 19 | fn swap( 20 | self: @TContractState, 21 | pool: ContractAddress, 22 | recipient: ContractAddress, 23 | zero_for_one: bool, 24 | amount_specified: i256, 25 | sqrt_price_limit_X96: FixedType 26 | ) -> (i256, i256); 27 | fn yas_swap_callback( 28 | ref self: TContractState, amount_0_delta: i256, amount_1_delta: i256, data: Array 29 | ); 30 | fn swap_exact_0_for_1( 31 | self: @TContractState, 32 | pool: ContractAddress, 33 | amount_in: u256, 34 | recipient: ContractAddress, 35 | sqrt_price_limit_X96: FixedType 36 | ) -> (i256, i256); 37 | fn swap_exact_1_for_0( 38 | self: @TContractState, 39 | pool: ContractAddress, 40 | amount_in: u256, 41 | recipient: ContractAddress, 42 | sqrt_price_limit_X96: FixedType 43 | ) -> (i256, i256); 44 | } 45 | 46 | #[starknet::contract] 47 | mod YASRouter { 48 | use super::IYASRouter; 49 | 50 | use starknet::{ContractAddress, get_caller_address, get_contract_address}; 51 | 52 | use yas_core::contracts::yas_pool::{IYASPoolDispatcher, IYASPoolDispatcherTrait}; 53 | use yas_core::interfaces::interface_ERC20::{IERC20Dispatcher, IERC20DispatcherTrait}; 54 | use yas_core::numbers::fixed_point::implementations::impl_64x96::FixedType; 55 | use yas_core::numbers::signed_integer::{i32::i32, i256::i256, integer_trait::IntegerTrait}; 56 | 57 | #[event] 58 | #[derive(Drop, starknet::Event)] 59 | enum Event { 60 | MintCallback: MintCallback, 61 | SwapCallback: SwapCallback 62 | } 63 | 64 | #[derive(Drop, starknet::Event)] 65 | struct MintCallback { 66 | amount_0_owed: u256, 67 | amount_1_owed: u256 68 | } 69 | 70 | #[derive(Drop, starknet::Event)] 71 | struct SwapCallback { 72 | amount_0_delta: i256, 73 | amount_1_delta: i256 74 | } 75 | 76 | #[storage] 77 | struct Storage {} 78 | 79 | #[external(v0)] 80 | impl YASRouterCallbackImpl of IYASRouter { 81 | fn mint( 82 | self: @ContractState, 83 | pool: ContractAddress, 84 | recipient: ContractAddress, 85 | tick_lower: i32, 86 | tick_upper: i32, 87 | amount: u128 88 | ) { 89 | IYASPoolDispatcher { contract_address: pool } 90 | .mint( 91 | recipient, tick_lower, tick_upper, amount, array![get_caller_address().into()] 92 | ); 93 | } 94 | 95 | fn yas_mint_callback( 96 | ref self: ContractState, amount_0_owed: u256, amount_1_owed: u256, data: Array 97 | ) { 98 | let msg_sender = get_caller_address(); 99 | 100 | // TODO: we need verify if data has a valid ContractAddress 101 | let mut sender: ContractAddress = Zeroable::zero(); 102 | if !data.is_empty() { 103 | sender = (*data[0]).try_into().unwrap(); 104 | } 105 | 106 | self.emit(MintCallback { amount_0_owed, amount_1_owed }); 107 | 108 | if amount_0_owed > 0 { 109 | let token_0 = IYASPoolDispatcher { contract_address: msg_sender }.token_0(); 110 | IERC20Dispatcher { contract_address: token_0 } 111 | .transferFrom(sender, msg_sender, amount_0_owed); 112 | } 113 | if amount_1_owed > 0 { 114 | let token_1 = IYASPoolDispatcher { contract_address: msg_sender }.token_1(); 115 | IERC20Dispatcher { contract_address: token_1 } 116 | .transferFrom(sender, msg_sender, amount_1_owed); 117 | } 118 | } 119 | 120 | fn swap( 121 | self: @ContractState, 122 | pool: ContractAddress, 123 | recipient: ContractAddress, 124 | zero_for_one: bool, 125 | amount_specified: i256, 126 | sqrt_price_limit_X96: FixedType 127 | ) -> (i256, i256) { 128 | IYASPoolDispatcher { contract_address: pool } 129 | .swap( 130 | recipient, 131 | zero_for_one, 132 | amount_specified, 133 | sqrt_price_limit_X96, 134 | array![get_caller_address().into()] 135 | ) 136 | } 137 | 138 | fn yas_swap_callback( 139 | ref self: ContractState, 140 | amount_0_delta: i256, 141 | amount_1_delta: i256, 142 | data: Array 143 | ) { 144 | let msg_sender = get_caller_address(); 145 | 146 | // TODO: we need verify if data has a valid ContractAddress 147 | let mut sender: ContractAddress = Zeroable::zero(); 148 | if !data.is_empty() { 149 | sender = (*data[0]).try_into().unwrap(); 150 | } 151 | 152 | self.emit(SwapCallback { amount_0_delta, amount_1_delta }); 153 | 154 | if amount_0_delta > Zeroable::zero() { 155 | let token_0 = IYASPoolDispatcher { contract_address: msg_sender }.token_0(); 156 | IERC20Dispatcher { contract_address: token_0 } 157 | .transferFrom(sender, msg_sender, amount_0_delta.try_into().unwrap()); 158 | } else if amount_1_delta > Zeroable::zero() { 159 | let token_1 = IYASPoolDispatcher { contract_address: msg_sender }.token_1(); 160 | IERC20Dispatcher { contract_address: token_1 } 161 | .transferFrom(sender, msg_sender, amount_1_delta.try_into().unwrap()); 162 | } else { 163 | // if both are not gt 0, both must be 0. 164 | assert( 165 | amount_0_delta == Zeroable::zero() && amount_1_delta == Zeroable::zero(), 166 | 'both amount deltas are negative' 167 | ); 168 | } 169 | } 170 | fn swap_exact_0_for_1( 171 | self: @ContractState, 172 | pool: ContractAddress, 173 | amount_in: u256, 174 | recipient: ContractAddress, 175 | sqrt_price_limit_X96: FixedType 176 | ) -> (i256, i256) { 177 | IYASPoolDispatcher { contract_address: pool } 178 | .swap( 179 | recipient, 180 | true, 181 | IntegerTrait::::new(amount_in, false), 182 | sqrt_price_limit_X96, 183 | array![get_caller_address().into()] 184 | ) 185 | } 186 | 187 | fn swap_exact_1_for_0( 188 | self: @ContractState, 189 | pool: ContractAddress, 190 | amount_in: u256, 191 | recipient: ContractAddress, 192 | sqrt_price_limit_X96: FixedType 193 | ) -> (i256, i256) { 194 | IYASPoolDispatcher { contract_address: pool } 195 | .swap( 196 | recipient, 197 | true, 198 | IntegerTrait::::new(amount_in, true), 199 | sqrt_price_limit_X96, 200 | array![get_caller_address().into()] 201 | ) 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /crates/yas_core/src/interfaces/interface_ERC20.cairo: -------------------------------------------------------------------------------- 1 | use starknet::ContractAddress; 2 | 3 | #[starknet::interface] 4 | trait IERC20 { 5 | fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; 6 | fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; 7 | fn transferFrom( 8 | ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 9 | ) -> bool; 10 | } 11 | -------------------------------------------------------------------------------- /crates/yas_core/src/interfaces/interface_yas_mint_callback.cairo: -------------------------------------------------------------------------------- 1 | #[starknet::interface] 2 | trait IYASMintCallback { 3 | fn yas_mint_callback( 4 | self: @TContractState, amount_0_owed: u256, amount_1_owed: u256, data: Array 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /crates/yas_core/src/interfaces/interface_yas_swap_callback.cairo: -------------------------------------------------------------------------------- 1 | use yas_core::numbers::signed_integer::i256::i256; 2 | 3 | // In order to do a swap the contract trying to perform this action will have to implement this function in their code. 4 | #[starknet::interface] 5 | trait IYASSwapCallback { 6 | fn yas_swap_callback( 7 | self: @TContractState, amount_0_delta: i256, amount_1_delta: i256, data: Array 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /crates/yas_core/src/lib.cairo: -------------------------------------------------------------------------------- 1 | mod contracts { 2 | mod yas_erc20; 3 | mod yas_factory; 4 | mod yas_pool; 5 | mod yas_router; 6 | } 7 | 8 | mod interfaces { 9 | mod interface_ERC20; 10 | mod interface_yas_mint_callback; 11 | mod interface_yas_swap_callback; 12 | } 13 | 14 | mod libraries { 15 | mod bit_math; 16 | mod liquidity_math; 17 | mod position; 18 | mod tick_math; 19 | mod swap_math; 20 | mod sqrt_price_math; 21 | mod tick; 22 | mod tick_bitmap; 23 | } 24 | 25 | mod numbers { 26 | mod fixed_point; 27 | mod signed_integer; 28 | } 29 | 30 | mod utils { 31 | mod math_utils; 32 | mod utils; 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | mod test_contracts { 38 | mod test_yas_factory; 39 | mod test_yas_pool; 40 | } 41 | 42 | mod test_libraries { 43 | mod test_bit_math; 44 | mod test_liquidity_math; 45 | mod test_tick_math; 46 | mod test_position; 47 | mod test_sqrt_price_math; 48 | mod test_swap_math; 49 | mod test_tick; 50 | mod test_tick_bitmap; 51 | } 52 | 53 | mod test_numbers { 54 | mod test_fixed_point; 55 | mod test_signed_integer; 56 | } 57 | 58 | mod test_utils { 59 | mod test_math_utils; 60 | mod test_utils; 61 | } 62 | 63 | mod utils { 64 | mod constants; 65 | mod swap_cases; 66 | mod pool_1; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /crates/yas_core/src/libraries/bit_math.cairo: -------------------------------------------------------------------------------- 1 | mod BitMath { 2 | use integer::{U256BitAnd, Felt252IntoU256}; 3 | use traits::DivEq; 4 | 5 | /// @notice Returns the index of the most significant bit of the number, 6 | /// where the least significant bit is at index 0 and the most significant bit is at index 255 7 | /// @dev The function satisfies the property: 8 | /// x >= 2**mostSignificantBit(x) and x < 2**(mostSignificantBit(x)+1) 9 | /// @param x the value for which to compute the most significant bit, must be greater than 0 10 | /// @return r the index of the most significant bit 11 | fn most_significant_bit(x: u256) -> u8 { 12 | match check_gt_zero(x) { 13 | Result::Ok(()) => {}, 14 | Result::Err(err) => { 15 | panic_with_felt252(err) 16 | } 17 | } 18 | let mut x: u256 = x; 19 | let mut r: u8 = 0; 20 | 21 | if x >= 0x100000000000000000000000000000000 { 22 | x /= 0x100000000000000000000000000000000; 23 | r += 128; 24 | } 25 | if x >= 0x10000000000000000 { 26 | x /= 0x10000000000000000; 27 | r += 64; 28 | } 29 | if x >= 0x100000000 { 30 | x /= 0x100000000; 31 | r += 32; 32 | } 33 | if x >= 0x10000 { 34 | x /= 0x10000; 35 | r += 16; 36 | } 37 | if x >= 0x100 { 38 | x /= 0x100; 39 | r += 8; 40 | } 41 | if x >= 0x10 { 42 | x /= 0x10; 43 | r += 4; 44 | } 45 | if x >= 0x4 { 46 | x /= 0x4; 47 | r += 2; 48 | } 49 | if x >= 0x2 { 50 | r += 1; 51 | } 52 | r 53 | } 54 | 55 | /// @notice Returns the index of the least significant bit of the number, 56 | /// where the least significant bit is at index 0 and the most significant bit is at index 255 57 | /// @dev The function satisfies the property: 58 | /// (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0) 59 | /// @param x the value for which to compute the least significant bit, must be greater than 0 60 | /// @return r the index of the least significant bit 61 | fn least_significant_bit(x: u256) -> u8 { 62 | match check_gt_zero(x) { 63 | Result::Ok(()) => {}, 64 | Result::Err(err) => { 65 | panic_with_felt252(err) 66 | } 67 | } 68 | let mut x = x; 69 | let mut r: u8 = 255; 70 | 71 | if (x & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) > 0 { 72 | r -= 128; 73 | } else { 74 | x /= 0x100000000000000000000000000000000; 75 | } 76 | if (x & 0xFFFFFFFFFFFFFFFF) > 0 { 77 | r -= 64; 78 | } else { 79 | x /= 0x10000000000000000; 80 | } 81 | if (x & 0xFFFFFFFF) > 0 { 82 | r -= 32; 83 | } else { 84 | x /= 0x100000000; 85 | } 86 | if (x & 0xFFFF) > 0 { 87 | r -= 16; 88 | } else { 89 | x /= 0x10000; 90 | } 91 | if (x & 0xFF) > 0 { 92 | r -= 8; 93 | } else { 94 | x /= 0x100; 95 | } 96 | if (x & 0xF) > 0 { 97 | r -= 4; 98 | } else { 99 | x /= 0x10; 100 | } 101 | if (x & 0x3) > 0 { 102 | r -= 2; 103 | } else { 104 | x /= 0x4; 105 | } 106 | if (x & 0x1) > 0 { 107 | r -= 1; 108 | } 109 | r 110 | } 111 | 112 | fn check_gt_zero(x: u256) -> Result<(), felt252> { 113 | if x > 0 { 114 | Result::Ok(()) 115 | } else { 116 | Result::Err('x must be greater than 0') 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /crates/yas_core/src/libraries/liquidity_math.cairo: -------------------------------------------------------------------------------- 1 | /// Math library for liquidity 2 | mod LiquidityMath { 3 | use core::traits::PartialOrd; 4 | use core::integer::u128_overflowing_add; 5 | use yas_core::numbers::signed_integer::{i128::i128, integer_trait::IntegerTrait}; 6 | 7 | /// Add a signed liquidity delta to liquidity and revert if it overflows or underflows. 8 | /// Parameters: 9 | /// - x: The liquidity before change. 10 | /// - y: The delta by which liquidity should be changed. 11 | fn add_delta(x: u128, y: i128) -> u128 { 12 | let zero = IntegerTrait::::new(0, true); 13 | if (y < zero) { 14 | // require((z = x - uint128(-y)) < x, 'LS'); 15 | let y_abs_i128: i128 = y.abs(); 16 | let y_felt252: felt252 = y_abs_i128.into(); 17 | let y_u128: u128 = y_felt252.try_into().unwrap(); 18 | match check_liquidity_sub(x, y_u128) { 19 | Result::Ok(()) => {}, 20 | Result::Err(err) => { 21 | panic_with_felt252(err) 22 | } 23 | }; 24 | x - y_u128 25 | } else { 26 | // require((z = x + uint128(y)) >= x, 'LA'); 27 | let y_felt252: felt252 = y.into(); 28 | let y_u128: u128 = y_felt252.try_into().unwrap(); 29 | match check_liquidity_add(x, y_u128) { 30 | Result::Ok(()) => {}, 31 | Result::Err(err) => { 32 | panic_with_felt252(err) 33 | } 34 | }; 35 | x + y_u128 36 | } 37 | } 38 | 39 | fn check_liquidity_sub(x: u128, y: u128) -> Result<(), felt252> { 40 | if x >= y { 41 | Result::Ok(()) 42 | } else { 43 | Result::Err('LS') 44 | } 45 | } 46 | fn check_liquidity_add(x: u128, y: u128) -> Result<(), felt252> { 47 | if u128_overflowing_add(x, y).is_ok() { 48 | Result::Ok(()) 49 | } else { 50 | Result::Err('LA') 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/yas_core/src/libraries/position.cairo: -------------------------------------------------------------------------------- 1 | use starknet::ContractAddress; 2 | use yas_core::numbers::signed_integer::{i32::i32, i128::i128, integer_trait::IntegerTrait}; 3 | 4 | 5 | // info stored for each user's position 6 | #[derive(Copy, Drop, Serde, starknet::Store)] 7 | struct Info { 8 | // the amount of liquidity owned by this position 9 | liquidity: u128, 10 | // fee growth per unit of liquidity as of the last update to liquidity or fees owned 11 | fee_growth_inside_0_last_X128: u256, 12 | fee_growth_inside_1_last_X128: u256, 13 | // the fee owed to the position owner in token0/token1 14 | tokens_owed_0: u128, 15 | tokens_owed_1: u128, 16 | } 17 | 18 | #[derive(Copy, Drop, Hash, Serde)] 19 | struct PositionKey { 20 | owner: ContractAddress, 21 | tick_lower: i32, 22 | tick_upper: i32, 23 | } 24 | 25 | #[starknet::interface] 26 | trait IPosition { 27 | /// @notice Returns the Info struct of a position, given an owner and position boundaries 28 | /// @param self The mapping containing all user positions 29 | /// @param owner The ContractAddress of the position owner 30 | /// @param position_key conformed by the owner and the tick boundaries 31 | fn get(self: @TContractState, position_key: PositionKey) -> Info; 32 | 33 | /// @notice Credits accumulated fees to a user's position 34 | /// @param self The individual position to update 35 | /// @param position_key conformed by the owner and the tick boundaries to retrieve the position to be updated 36 | /// @param liquidityDelta The change in pool liquidity as a result of the position update 37 | /// @param feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries 38 | /// @param feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries 39 | fn update( 40 | ref self: TContractState, 41 | position_key: PositionKey, 42 | liquidity_delta: i128, 43 | fee_growth_inside_0_X128: u256, 44 | fee_growth_inside_1_X128: u256 45 | ); 46 | 47 | fn update_tokens_owed( 48 | ref self: TContractState, 49 | position_key: PositionKey, 50 | tokens_owed_0: u128, 51 | tokens_owed_1: u128 52 | ); 53 | } 54 | 55 | #[starknet::contract] 56 | mod Position { 57 | use super::{Info, PositionKey, IPosition}; 58 | 59 | use integer::BoundedInt; 60 | use hash::{HashStateTrait, HashStateExTrait}; 61 | use poseidon::PoseidonTrait; 62 | 63 | use yas_core::libraries::liquidity_math::LiquidityMath::add_delta; 64 | use yas_core::numbers::signed_integer::{i128::i128, integer_trait::IntegerTrait}; 65 | use yas_core::utils::math_utils::FullMath::mul_div; 66 | 67 | #[storage] 68 | struct Storage { 69 | positions: LegacyMap::, 70 | } 71 | 72 | #[external(v0)] 73 | impl PositionImpl of IPosition { 74 | fn get(self: @ContractState, position_key: PositionKey) -> Info { 75 | let hashed_key = PoseidonTrait::new().update_with(position_key).finalize(); 76 | self.positions.read(hashed_key) 77 | } 78 | 79 | fn update( 80 | ref self: ContractState, 81 | position_key: PositionKey, 82 | liquidity_delta: i128, 83 | fee_growth_inside_0_X128: u256, 84 | fee_growth_inside_1_X128: u256 85 | ) { 86 | // get the position info 87 | let hashed_key = PoseidonTrait::new().update_with(position_key).finalize(); 88 | let mut position = self.positions.read(hashed_key); 89 | 90 | let liquidity_next: u128 = if liquidity_delta == IntegerTrait::::new(0, false) { 91 | // disallows pokes for 0 liquidity positions 92 | assert(position.liquidity > 0, 'NP'); 93 | position.liquidity 94 | } else { 95 | add_delta(position.liquidity, liquidity_delta) 96 | }; 97 | 98 | // calculate accumulated fees 99 | let max_u128: u128 = BoundedInt::max(); 100 | let tokens_owed_0: u128 = mul_div( 101 | fee_growth_inside_0_X128 - position.fee_growth_inside_0_last_X128, 102 | position.liquidity.into(), 103 | max_u128.into() 104 | ) 105 | .try_into() 106 | .unwrap(); 107 | let tokens_owed_1: u128 = mul_div( 108 | fee_growth_inside_1_X128 - position.fee_growth_inside_1_last_X128, 109 | position.liquidity.into(), 110 | max_u128.into() 111 | ) 112 | .try_into() 113 | .unwrap(); 114 | 115 | // update the position 116 | if liquidity_delta != IntegerTrait::::new(0, false) { 117 | position.liquidity = liquidity_next; 118 | } 119 | 120 | position.fee_growth_inside_0_last_X128 = fee_growth_inside_0_X128; 121 | position.fee_growth_inside_1_last_X128 = fee_growth_inside_1_X128; 122 | 123 | if tokens_owed_0 > 0 || tokens_owed_1 > 0 { 124 | // overflow is acceptable, have to withdraw before you hit type(uint128).max fees 125 | position.tokens_owed_0 += tokens_owed_0; 126 | position.tokens_owed_1 += tokens_owed_1; 127 | } 128 | self.positions.write(hashed_key, position); 129 | } 130 | 131 | fn update_tokens_owed( 132 | ref self: ContractState, 133 | position_key: PositionKey, 134 | tokens_owed_0: u128, 135 | tokens_owed_1: u128 136 | ) { 137 | let hashed_key = PoseidonTrait::new().update_with(position_key).finalize(); 138 | let mut position = self.positions.read(hashed_key); 139 | position.tokens_owed_0 = tokens_owed_0; 140 | position.tokens_owed_1 = tokens_owed_1; 141 | self.positions.write(hashed_key, position); 142 | } 143 | } 144 | 145 | #[generate_trait] 146 | impl InternalImpl of InternalTrait { 147 | fn set_position(ref self: ContractState, position_key: PositionKey, info: Info) { 148 | let hashed_key = PoseidonTrait::new().update_with(position_key).finalize(); 149 | self.positions.write(hashed_key, info); 150 | } 151 | } 152 | } 153 | 154 | -------------------------------------------------------------------------------- /crates/yas_core/src/libraries/sqrt_price_math.cairo: -------------------------------------------------------------------------------- 1 | // Functions based on Q64.96 sqrt price and liquidity 2 | // Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas 3 | mod SqrtPriceMath { 4 | use integer::{u256_overflowing_add, u256_overflow_mul}; 5 | 6 | use yas_core::numbers::fixed_point::implementations::impl_64x96::{ 7 | FP64x96Impl, FixedType, FixedTrait, FP64x96Add, FP64x96Sub, FP64x96Mul, FP64x96Div, 8 | FP64x96PartialEq, FP64x96PartialOrd, Q96_RESOLUTION, ONE, MAX 9 | }; 10 | use yas_core::numbers::signed_integer::i256::i256; 11 | use yas_core::numbers::signed_integer::{i128::i128, integer_trait::IntegerTrait}; 12 | use yas_core::utils::math_utils::{ 13 | FullMath::{div_rounding_up, mul_div, mul_div_rounding_up}, pow 14 | }; 15 | 16 | /// Returns the next square root price given a token0 delta. 17 | /// @param sqrtPX96 The initial price (prior to considering the token0 delta). 18 | /// @param liquidity The quantity of available liquidity. 19 | /// @param amount The quantity of token0 to be added or removed from virtual reserves. 20 | /// @param add Indicates whether to add or subtract the token0 amount. 21 | /// @return The resulting price after adding or removing the amount, based on the 'add' parameter. 22 | fn get_next_sqrt_price_from_amount0_rounding_up( 23 | sqrtPX96: FixedType, liquidity: u128, amount: u256, add: bool 24 | ) -> FixedType { 25 | if amount == 0 { 26 | return sqrtPX96; 27 | } 28 | match check_sqrtPX96_sign(sqrtPX96.sign) { 29 | Result::Ok(()) => {}, 30 | Result::Err(err) => { 31 | panic_with_felt252(err) 32 | } 33 | }; 34 | let numerator = liquidity.into() * pow(2, Q96_RESOLUTION.into()); 35 | let (product, product_has_overflow) = u256_overflow_mul(amount, sqrtPX96.mag); 36 | 37 | if add { 38 | if !product_has_overflow && product / amount == sqrtPX96.mag { 39 | let (denominator, denominator_has_overflow) = u256_overflowing_add( 40 | numerator, product 41 | ); 42 | if !denominator_has_overflow && denominator >= numerator { 43 | return FP64x96Impl::new( 44 | mul_div_rounding_up(numerator, sqrtPX96.mag, denominator), false 45 | ); 46 | } 47 | } 48 | FP64x96Impl::new(div_rounding_up(numerator, (numerator / sqrtPX96.mag) + amount), false) 49 | } else { 50 | // if the product overflows, we know the denominator underflows 51 | // in addition, we must check that the denominator does not underflow 52 | match check_product_overflow(product, amount, false, sqrtPX96) { 53 | Result::Ok(()) => {}, 54 | Result::Err(err) => { 55 | panic_with_felt252(err) 56 | } 57 | }; 58 | match check_denominator_underflow(numerator, product) { 59 | Result::Ok(()) => {}, 60 | Result::Err(err) => { 61 | panic_with_felt252(err) 62 | } 63 | }; 64 | let denominator = numerator - product; 65 | FP64x96Impl::new(mul_div_rounding_up(numerator, sqrtPX96.mag, denominator), false) 66 | } 67 | } 68 | 69 | /// Returns the next square root price given a token1 delta. 70 | /// @param sqrtPX96 The initial price (prior to considering the token1 delta). 71 | /// @param liquidity The quantity of available liquidity. 72 | /// @param amount The quantity of token1 to be added or removed from virtual reserves. 73 | /// @param add Indicates whether to add or subtract the token1 amount. 74 | /// @return The resulting price after adding or removing the `amount`, based on the 'add' parameter. 75 | fn get_next_sqrt_price_from_amount1_rounding_down( 76 | sqrtPX96: FixedType, liquidity: u128, amount: u256, add: bool 77 | ) -> FixedType { 78 | match check_sqrtPX96_sign(sqrtPX96.sign) { 79 | Result::Ok(()) => {}, 80 | Result::Err(err) => { 81 | panic_with_felt252(err) 82 | } 83 | }; 84 | // if we're adding (subtracting), rounding down requires rounding the quotient down (up) 85 | // in both cases, avoid a mulDiv for most inputs 86 | if add { 87 | let mut quotient = if amount <= MAX { 88 | amount * pow(2, Q96_RESOLUTION.into()) / liquidity.into() 89 | } else { 90 | mul_div(amount, ONE, liquidity.into()) 91 | }; 92 | (sqrtPX96 + FP64x96Impl::new(quotient, false)) 93 | } else { 94 | let mut quotient = if amount <= MAX { 95 | div_rounding_up(amount * pow(2, Q96_RESOLUTION.into()), liquidity.into()) 96 | } else { 97 | mul_div_rounding_up(amount, ONE, liquidity.into()) 98 | }; 99 | match check_sqrtPX96_and_quotient(sqrtPX96, FP64x96Impl::new(quotient, false)) { 100 | Result::Ok(()) => {}, 101 | Result::Err(err) => { 102 | panic_with_felt252(err) 103 | } 104 | }; 105 | (sqrtPX96 - FP64x96Impl::new(quotient, false)) 106 | } 107 | } 108 | 109 | /// Returns the next square root price given an input amount of token0 or token1. 110 | /// @dev Throws if the price or liquidity is 0, or if the next price is out of bounds. 111 | /// @param sqrtPX96 The starting price, i.e., before accounting for the input amount. 112 | /// @param liquidity The amount of usable liquidity. 113 | /// @param amountIn How much of token0 or token1 is being swapped in. 114 | /// @param zeroForOne Indicates whether the amount in is token0 or token1. 115 | /// @return sqrtQX96 The price after adding the input amount to token0 or token1. 116 | fn get_next_sqrt_price_from_input( 117 | sqrtPX96: FixedType, liquidity: u128, amount_in: u256, zero_for_one: bool 118 | ) -> FixedType { 119 | match check_sqrtPX96_sign_and_liquidity(sqrtPX96.sign, liquidity) { 120 | Result::Ok(()) => {}, 121 | Result::Err(err) => { 122 | panic_with_felt252(err) 123 | } 124 | }; 125 | 126 | if zero_for_one { 127 | get_next_sqrt_price_from_amount0_rounding_up(sqrtPX96, liquidity, amount_in, true) 128 | } else { 129 | get_next_sqrt_price_from_amount1_rounding_down(sqrtPX96, liquidity, amount_in, true) 130 | } 131 | } 132 | 133 | /// Returns the next square root price given an output amount of token0 or token1. 134 | /// @dev Throws if the price or liquidity is 0, or if the next price is out of bounds. 135 | /// @param sqrtPX96 The starting price before accounting for the output amount. 136 | /// @param liquidity The amount of usable liquidity. 137 | /// @param amountOut How much of token0 or token1 is being swapped out. 138 | /// @param zeroForOne Indicates whether the amount out is token0 or token1. 139 | /// @return sqrtQX96 The price after removing the output amount of token0 or token1. 140 | fn get_next_sqrt_price_from_output( 141 | sqrtPX96: FixedType, liquidity: u128, amount_out: u256, zero_for_one: bool 142 | ) -> FixedType { 143 | match check_sqrtPX96_sign_and_liquidity(sqrtPX96.sign, liquidity) { 144 | Result::Ok(()) => {}, 145 | Result::Err(err) => { 146 | panic_with_felt252(err) 147 | } 148 | }; 149 | if zero_for_one { 150 | get_next_sqrt_price_from_amount1_rounding_down(sqrtPX96, liquidity, amount_out, false) 151 | } else { 152 | get_next_sqrt_price_from_amount0_rounding_up(sqrtPX96, liquidity, amount_out, false) 153 | } 154 | } 155 | 156 | /// Returns the amount0 delta between two prices. 157 | /// @dev Calculates `liquidity / sqrt(lower) - liquidity / sqrt(upper)`, 158 | /// i.e., `liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower))`. 159 | /// @param sqrtRatioAX96 A sqrt price. 160 | /// @param sqrtRatioBX96 Another sqrt price. 161 | /// @param liquidity The amount of usable liquidity. 162 | /// @param roundUp Indicates whether to round the amount up or down. 163 | /// @return amount0 Amount of token0 required to cover a position of size liquidity between the two passed prices. 164 | fn get_amount_0_delta( 165 | sqrt_ratio_AX96: FixedType, sqrt_ratio_BX96: FixedType, liquidity: u128, round_up: bool 166 | ) -> u256 { 167 | let (sqrt_ratio_AX96_1, sqrt_ratio_BX96_1) = if sqrt_ratio_AX96 > sqrt_ratio_BX96 { 168 | (sqrt_ratio_BX96, sqrt_ratio_AX96) 169 | } else { 170 | (sqrt_ratio_AX96, sqrt_ratio_BX96) 171 | }; 172 | 173 | let numerator1 = liquidity.into() * pow(2, Q96_RESOLUTION.into()); 174 | let numerator2 = sqrt_ratio_BX96_1 - sqrt_ratio_AX96_1; 175 | 176 | match check_sqrtPX96_sign(sqrt_ratio_AX96_1.sign) { 177 | Result::Ok(()) => {}, 178 | Result::Err(err) => { 179 | panic_with_felt252(err) 180 | } 181 | }; 182 | 183 | if round_up { 184 | div_rounding_up( 185 | mul_div_rounding_up(numerator1, numerator2.mag, sqrt_ratio_BX96_1.mag), 186 | sqrt_ratio_AX96_1.mag 187 | ) 188 | } else { 189 | mul_div(numerator1, numerator2.mag, sqrt_ratio_BX96_1.mag) / sqrt_ratio_AX96_1.mag 190 | } 191 | } 192 | 193 | /// Returns the amount1 delta between two prices. 194 | /// @dev Calculates `liquidity * (sqrt(upper) - sqrt(lower))`. 195 | /// @param sqrtRatioAX96 A sqrt price. 196 | /// @param sqrtRatioBX96 Another sqrt price. 197 | /// @param liquidity The amount of usable liquidity. 198 | /// @param roundUp Indicates whether to round the amount up or down. 199 | /// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices. 200 | fn get_amount_1_delta( 201 | sqrt_ratio_AX96: FixedType, sqrt_ratio_BX96: FixedType, liquidity: u128, round_up: bool 202 | ) -> u256 { 203 | let (sqrt_ratio_AX96_1, sqrt_ratio_BX96_1) = if sqrt_ratio_AX96 > sqrt_ratio_BX96 { 204 | (sqrt_ratio_BX96, sqrt_ratio_AX96) 205 | } else { 206 | (sqrt_ratio_AX96, sqrt_ratio_BX96) 207 | }; 208 | 209 | if round_up { 210 | mul_div_rounding_up(liquidity.into(), (sqrt_ratio_BX96_1 - sqrt_ratio_AX96_1).mag, ONE) 211 | } else { 212 | mul_div(liquidity.into(), (sqrt_ratio_BX96_1 - sqrt_ratio_AX96_1).mag, ONE) 213 | } 214 | } 215 | 216 | fn get_amount_0_delta_signed_token( 217 | sqrt_ratio_AX96: FixedType, sqrt_ratio_BX96: FixedType, liquidity: i128 218 | ) -> i256 { 219 | if liquidity < IntegerTrait::::new(0, false) { 220 | IntegerTrait::::new( 221 | get_amount_0_delta(sqrt_ratio_AX96, sqrt_ratio_BX96, liquidity.abs().mag, false), 222 | true 223 | ) 224 | } else { 225 | IntegerTrait::::new( 226 | get_amount_0_delta(sqrt_ratio_AX96, sqrt_ratio_BX96, liquidity.mag, true), false 227 | ) 228 | } 229 | } 230 | 231 | fn get_amount_1_delta_signed_token( 232 | sqrt_ratio_AX96: FixedType, sqrt_ratio_BX96: FixedType, liquidity: i128 233 | ) -> i256 { 234 | if liquidity < IntegerTrait::::new(0, false) { 235 | IntegerTrait::::new( 236 | get_amount_1_delta(sqrt_ratio_AX96, sqrt_ratio_BX96, liquidity.abs().mag, false), 237 | true 238 | ) 239 | } else { 240 | IntegerTrait::::new( 241 | get_amount_1_delta(sqrt_ratio_AX96, sqrt_ratio_BX96, liquidity.mag, true), false 242 | ) 243 | } 244 | } 245 | 246 | fn check_sqrtPX96_sign(sign: bool) -> Result<(), felt252> { 247 | if sign { 248 | Result::Err('sqrt_ratio_AX96 cannot be neg') 249 | } else { 250 | Result::Ok(()) 251 | } 252 | } 253 | fn check_sqrtPX96_sign_and_liquidity(sign: bool, liquidity: u128) -> Result<(), felt252> { 254 | if sign == false && liquidity > 0 { 255 | Result::Ok(()) 256 | } else { 257 | Result::Err('sqrt_ratio_AX96 cannot be neg') 258 | } 259 | } 260 | 261 | fn check_sqrtPX96_and_quotient( 262 | sqrtPX96: FixedType, quotient: FixedType 263 | ) -> Result<(), felt252> { 264 | if sqrtPX96 > quotient { 265 | Result::Ok(()) 266 | } else { 267 | Result::Err('sqrtPX96_fp < quotient') 268 | } 269 | } 270 | 271 | fn check_product_overflow( 272 | product: u256, amount: u256, sign: bool, value_2: FixedType 273 | ) -> Result<(), felt252> { 274 | if FP64x96Impl::new(product / amount, sign) == value_2 { 275 | Result::Ok(()) 276 | } else { 277 | Result::Err('product overflow') 278 | } 279 | } 280 | 281 | fn check_denominator_underflow(numerator: u256, product: u256) -> Result<(), felt252> { 282 | if numerator > product { 283 | Result::Ok(()) 284 | } else { 285 | Result::Err('denominator underflow') 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /crates/yas_core/src/libraries/swap_math.cairo: -------------------------------------------------------------------------------- 1 | /// Computes the result of a swap within ticks 2 | /// Contains methods for computing the result of a swap within a single tick price range, i.e., a single tick. 3 | mod SwapMath { 4 | use yas_core::utils::math_utils::FullMath::{div_rounding_up, mul_div, mul_div_rounding_up}; 5 | use yas_core::numbers::fixed_point::implementations::impl_64x96::{ 6 | FP64x96Impl, FixedType, FixedTrait, FP64x96Add, FP64x96Sub, FP64x96Mul, FP64x96Div, 7 | FP64x96PartialEq, FP64x96PartialOrd, Q96_RESOLUTION, ONE, MAX 8 | }; 9 | use yas_core::numbers::signed_integer::i256::i256; 10 | use yas_core::libraries::sqrt_price_math::SqrtPriceMath::{ 11 | get_amount_0_delta, get_amount_1_delta, get_next_sqrt_price_from_input, 12 | get_next_sqrt_price_from_output 13 | }; 14 | use integer::{u256_overflowing_add, u256_overflow_mul}; 15 | use yas_core::numbers::signed_integer::integer_trait::IntegerTrait; 16 | 17 | const _1e6: u256 = 1000000; // 10 ** 6 18 | 19 | /// Computes the result of swapping some amount in, or amount out, given the parameters of the swap 20 | /// @param sqrt_ratio_currentX96 The current sqrt price of the pool 21 | /// @param sqrt_ratio_targetX96 The price that cannot be exceeded, from which the direction of the swap is inferred 22 | /// @param liquidity The usable liquidity 23 | /// @param amount_remaining How much input or output amount is remaining to be swapped in/out 24 | /// @param fee_pips The fee taken from the input amount, expressed in hundredths of a bip 25 | /// @return sqrt_ratio_nextX96 The price after swapping the amount in/out, not to exceed the price target 26 | /// @return amount_in The amount to be swapped in, of either token0 or token1, based on the direction of the swap 27 | /// @return amount_out The amount to be received, of either token0 or token1, based on the direction of the swap 28 | /// @return fee_amount The amount of input that will be taken as a fee 29 | fn compute_swap_step( 30 | sqrt_ratio_currentX96: FixedType, 31 | sqrt_ratio_targetX96: FixedType, 32 | liquidity: u128, 33 | amount_remaining: i256, 34 | fee_pips: u32 35 | ) -> (FixedType, u256, u256, u256) { 36 | let zero_for_one = sqrt_ratio_currentX96 >= sqrt_ratio_targetX96; 37 | let exact_in = amount_remaining >= IntegerTrait::::new(0, false); 38 | let mut sqrt_ratio_nextX96 = FP64x96Impl::new(1, false); 39 | let mut amount_in = 0; 40 | let mut amount_out = 0; 41 | 42 | if exact_in { 43 | // at this point, amount_remaining is positive bc exact_in == true 44 | let amount_remaining_less_fee = mul_div( 45 | amount_remaining.mag, _1e6 - fee_pips.into(), _1e6 46 | ); 47 | amount_in = 48 | if zero_for_one { 49 | get_amount_0_delta(sqrt_ratio_targetX96, sqrt_ratio_currentX96, liquidity, true) 50 | } else { 51 | get_amount_1_delta(sqrt_ratio_currentX96, sqrt_ratio_targetX96, liquidity, true) 52 | }; 53 | 54 | sqrt_ratio_nextX96 = 55 | if amount_remaining_less_fee >= amount_in { 56 | sqrt_ratio_targetX96 57 | } else { 58 | get_next_sqrt_price_from_input( 59 | sqrt_ratio_currentX96, liquidity, amount_remaining_less_fee, zero_for_one 60 | ) 61 | }; 62 | } else { 63 | amount_out = 64 | if zero_for_one { 65 | get_amount_1_delta( 66 | sqrt_ratio_targetX96, sqrt_ratio_currentX96, liquidity, false 67 | ) 68 | } else { 69 | get_amount_0_delta( 70 | sqrt_ratio_currentX96, sqrt_ratio_targetX96, liquidity, false 71 | ) 72 | }; 73 | 74 | sqrt_ratio_nextX96 = 75 | if amount_remaining.mag >= amount_out { 76 | sqrt_ratio_targetX96 77 | } else { 78 | get_next_sqrt_price_from_output( 79 | sqrt_ratio_currentX96, liquidity, amount_remaining.mag, zero_for_one 80 | ) 81 | }; 82 | } 83 | 84 | let max = sqrt_ratio_targetX96 == sqrt_ratio_nextX96; 85 | 86 | if zero_for_one { 87 | amount_in = 88 | if max && exact_in { 89 | amount_in 90 | } else { 91 | get_amount_0_delta(sqrt_ratio_nextX96, sqrt_ratio_currentX96, liquidity, true) 92 | }; 93 | 94 | amount_out = 95 | if max && !exact_in { 96 | amount_out 97 | } else { 98 | get_amount_1_delta(sqrt_ratio_nextX96, sqrt_ratio_currentX96, liquidity, false) 99 | }; 100 | } else { 101 | amount_in = 102 | if max && exact_in { 103 | amount_in 104 | } else { 105 | get_amount_1_delta(sqrt_ratio_currentX96, sqrt_ratio_nextX96, liquidity, true) 106 | }; 107 | 108 | amount_out = 109 | if max && !exact_in { 110 | amount_out 111 | } else { 112 | get_amount_0_delta(sqrt_ratio_currentX96, sqrt_ratio_nextX96, liquidity, false) 113 | }; 114 | } 115 | 116 | if !exact_in && amount_out > amount_remaining.mag { 117 | amount_out = amount_remaining.mag; 118 | } 119 | 120 | let fee_amount = if exact_in && sqrt_ratio_nextX96 != sqrt_ratio_targetX96 { 121 | amount_remaining.mag - amount_in 122 | } else { 123 | mul_div_rounding_up(amount_in, fee_pips.into(), _1e6 - fee_pips.into()) 124 | }; 125 | 126 | (sqrt_ratio_nextX96, amount_in, amount_out, fee_amount) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /crates/yas_core/src/libraries/tick_bitmap.cairo: -------------------------------------------------------------------------------- 1 | use yas_core::numbers::signed_integer::i32::i32; 2 | 3 | #[starknet::interface] 4 | trait ITickBitmap { 5 | fn flip_tick(ref self: TContractState, tick: i32, tick_spacing: i32); 6 | fn next_initialized_tick_within_one_word( 7 | self: @TContractState, tick: i32, tick_spacing: i32, lte: bool 8 | ) -> (i32, bool); 9 | } 10 | 11 | #[starknet::contract] 12 | mod TickBitmap { 13 | use super::ITickBitmap; 14 | 15 | use integer::BoundedInt; 16 | use hash::{HashStateTrait, HashStateExTrait}; 17 | use poseidon::PoseidonTrait; 18 | 19 | use yas_core::libraries::bit_math::BitMath; 20 | use yas_core::numbers::signed_integer::{ 21 | i16::i16, i32::{i32, u8Intoi32, i32TryIntoi16, i32TryIntou8, mod_i32}, 22 | integer_trait::IntegerTrait 23 | }; 24 | use yas_core::utils::math_utils::{BitShift::BitShiftTrait, pow}; 25 | 26 | #[storage] 27 | struct Storage { 28 | bitmap: LegacyMap, 29 | } 30 | 31 | #[external(v0)] 32 | impl TickBitmapImpl of ITickBitmap { 33 | /// @notice Flips the initialized state for a given tick from false to true, or vice versa 34 | /// @param self The ContractState 35 | /// @param tick The tick to flip 36 | /// @param tick_spacing The spacing between usable ticks 37 | fn flip_tick(ref self: ContractState, tick: i32, tick_spacing: i32) { 38 | assert( 39 | tick % tick_spacing == IntegerTrait::::new(0, false), 40 | 'ensure that the tick is spaced' 41 | ); 42 | 43 | let (word_pos, bit_pos) = position(tick / tick_spacing); 44 | let mask: u256 = 1_u256.shl(bit_pos.into()); 45 | let hashed_word_pos = PoseidonTrait::new().update_with(word_pos).finalize(); 46 | let word = self.bitmap.read(hashed_word_pos); 47 | self.bitmap.write(hashed_word_pos, word ^ mask); 48 | } 49 | 50 | /// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either 51 | /// to the left (less than or equal to) or right (greater than) of the given tick 52 | /// @param self The @ContractState 53 | /// @param tick The starting tick 54 | /// @param tick_spacing The spacing between usable ticks 55 | /// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick) 56 | /// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick 57 | /// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks 58 | fn next_initialized_tick_within_one_word( 59 | self: @ContractState, tick: i32, tick_spacing: i32, lte: bool 60 | ) -> (i32, bool) { 61 | let mut compressed: i32 = tick / tick_spacing; 62 | if (tick < IntegerTrait::::new(0, false) 63 | && tick % tick_spacing != IntegerTrait::::new(0, false)) { 64 | compressed -= IntegerTrait::::new(1, false); // round towards negative infinity 65 | }; 66 | 67 | if lte { 68 | let (word_pos, bit_pos) = position(compressed); 69 | let word: u256 = self 70 | .bitmap 71 | .read(PoseidonTrait::new().update_with(word_pos).finalize()); 72 | // all the 1s at or to the right of the current bitPos 73 | let mask: u256 = 1_u256.shl(bit_pos.into()) - 1 + 1_u256.shl(bit_pos.into()); 74 | let masked: u256 = word & mask; 75 | 76 | // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word 77 | let initialized = masked != 0; 78 | // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick 79 | let next = if initialized { 80 | // (compressed - int24(bit_pos - BitMath.most_significant_bit(masked))) * tick_spacing 81 | (compressed - (bit_pos - BitMath::most_significant_bit(masked)).into()) 82 | * tick_spacing 83 | } else { 84 | // (compressed - int24(bit_pos)) * tick_spacing 85 | (compressed - bit_pos.into()) * tick_spacing 86 | }; 87 | (next, initialized) 88 | } else { 89 | // start from the word of the next tick, since the current tick state doesn't matter 90 | let (word_pos, bit_pos) = position(compressed + IntegerTrait::::new(1, false)); 91 | let word = self.bitmap.read(PoseidonTrait::new().update_with(word_pos).finalize()); 92 | // all the 1s at or to the left of the bitPos 93 | let mask: u256 = ~(1_u256.shl(bit_pos.into()) - 1); 94 | let masked: u256 = word & mask; 95 | 96 | // if there are no initialized ticks to the left of the current tick, return leftmost in the word 97 | let initialized = masked != 0; 98 | // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick 99 | let next = if initialized { 100 | // (compressed + 1 + int24(BitMath::least_significant_bit(masked) - bit_pos)) * tick_spacing 101 | (compressed 102 | + IntegerTrait::::new(1, false) 103 | + (BitMath::least_significant_bit(masked) - bit_pos).into()) 104 | * tick_spacing 105 | } else { 106 | // (compressed + 1 + int24(type(uint8).max - bit_pos)) * tick_spacing 107 | let max_u8: u8 = BoundedInt::max(); 108 | (compressed + IntegerTrait::::new(1, false) + (max_u8 - bit_pos).into()) 109 | * tick_spacing 110 | }; 111 | (next, initialized) 112 | } 113 | } 114 | } 115 | 116 | #[generate_trait] 117 | impl InternalImpl of InternalTrait { 118 | // returns whether the given tick is initialized 119 | fn is_initialized(self: @ContractState, tick: i32) -> bool { 120 | let (next, initialized) = self 121 | .next_initialized_tick_within_one_word( 122 | tick, IntegerTrait::::new(1, false), true 123 | ); 124 | if next == tick { 125 | initialized 126 | } else { 127 | false 128 | } 129 | } 130 | } 131 | 132 | /// Calculates the word value based on a tick input. 133 | /// - For ticks between 0 and 255 inclusive, it returns 0. 134 | /// - For ticks greater than 255, it divides the tick by 256. 135 | /// - For ticks less than 0 but greater than or equal to -256, it returns -1. 136 | /// - For other negative ticks, it divides the tick by 256 and subtracts 1. 137 | /// 138 | /// Parameters: 139 | /// - `tick`: An i32 input representing the tick value. 140 | /// 141 | /// Returns: An i16 value representing the calculated word. 142 | fn calculate_word(tick: i32) -> i16 { 143 | let zero = IntegerTrait::::new(0, false); 144 | let one_negative = IntegerTrait::::new(1, true); 145 | let upper_bound = IntegerTrait::::new(255, false); 146 | let divisor = IntegerTrait::::new(256, false); 147 | let negative_lower_bound = IntegerTrait::::new(256, true); 148 | 149 | let result = if tick >= zero && tick <= upper_bound { 150 | zero 151 | } else if tick > upper_bound { 152 | tick / divisor 153 | } else if tick >= negative_lower_bound { 154 | one_negative 155 | } else { 156 | tick / divisor + one_negative 157 | }; 158 | result.try_into().expect('calculate_word') 159 | } 160 | 161 | /// Calculates the bit value based on a given tick input. 162 | /// 163 | /// Parameters: 164 | /// - `tick`: An i32 input representing the tick value. 165 | /// Returns: A u8 value representing the calculated bit. 166 | fn calculate_bit(tick: i32) -> u8 { 167 | // Using this util function because Orion returns negative reminder numbers 168 | let bit = mod_i32(tick, IntegerTrait::::new(256, false)); 169 | bit.try_into().expect('calculate_bit') 170 | } 171 | 172 | /// @notice Computes the position in the mapping where the initialized bit for a tick lives 173 | /// @param tick The tick for which to compute the position 174 | /// @return word_pos The key in the mapping containing the word in which the bit is stored 175 | /// @return bit_pos The bit position in the word where the flag is stored 176 | fn position(tick: i32) -> (i16, u8) { 177 | let word_pos: i16 = calculate_word(tick); 178 | let bit_pos: u8 = calculate_bit(tick); 179 | (word_pos, bit_pos) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /crates/yas_core/src/numbers/fixed_point.cairo: -------------------------------------------------------------------------------- 1 | //! Fixed-Point implemented from https://github.com/gizatechxyz/orion/ 2 | mod core; 3 | mod implementations; 4 | mod math; 5 | -------------------------------------------------------------------------------- /crates/yas_core/src/numbers/fixed_point/core.cairo: -------------------------------------------------------------------------------- 1 | /// A struct representing a fixed point number. 2 | #[derive(Serde, Copy, Drop, starknet::Store)] 3 | struct FixedType { 4 | mag: u256, 5 | sign: bool 6 | } 7 | 8 | /// A struct listing fixed point implementations. 9 | #[derive(Serde, Copy, Drop)] 10 | enum FixedImpl { 11 | FP64x96: (), 12 | } 13 | 14 | /// Trait 15 | /// 16 | /// new - Constructs a new fixed point instance. 17 | /// new_unscaled - Creates a new fixed point instance with the specified unscaled magnitude and sign. 18 | /// from_felt - Creates a new fixed point instance from a `felt252` value. 19 | /// from_unscaled_felt - Creates a new fixed point instance from an unscaled `felt252` value. 20 | /// abs - Returns the absolute value of the fixed point number. 21 | /// ceil - Returns the smallest integer greater than or equal to the fixed point number. 22 | /// floor - Returns the largest integer less than or equal to the fixed point number. 23 | /// exp - Returns the value of e raised to the power of the fixed point number. 24 | /// exp2 - Returns the value of 2 raised to the power of the fixed point number. 25 | /// pow - Returns the result of raising the fixed point number to the power of another fixed point number 26 | /// round - Rounds the fixed point number to the nearest whole number. 27 | /// sqrt - Returns the square root of the fixed point number. 28 | trait FixedTrait { 29 | /// # FixedTrait::new 30 | /// 31 | /// ```rust 32 | /// fn new(mag: u256, sign: bool) -> FixedType; 33 | /// ``` 34 | /// 35 | /// Constructs a new fixed point instance. 36 | /// 37 | /// ## Args 38 | /// 39 | /// * `mag`(`u256`) - The magnitude of the fixed point. 40 | /// * `sign`(`bool`) - The sign of the fixed point, where `true` represents a negative number. 41 | /// 42 | /// ## Returns 43 | /// 44 | /// A new fixed point instance. 45 | /// 46 | /// ## Examples 47 | /// 48 | /// ```rust 49 | /// fn new_fp_example() -> FixedType { 50 | /// // We can call `new` function as follows. 51 | /// FixedTrait::new(67108864, false) 52 | /// } 53 | /// >>> {mag: 67108864, sign: false} // = 1 54 | /// ``` 55 | /// 56 | fn new(mag: u256, sign: bool) -> FixedType; 57 | /// # FixedTrait::new\_unscaled 58 | /// 59 | /// ```rust 60 | /// fn new_unscaled(mag: u256, sign: bool) -> FixedType; 61 | /// ``` 62 | /// 63 | /// Creates a new fixed point instance with the specified unscaled magnitude and sign. 64 | /// This function is only useful when you want a number with only an integer part. 65 | /// 66 | /// ## Args 67 | /// 68 | /// `mag`(`u256`) - The unscaled magnitude of the fixed point. 69 | /// `sign`(`bool`) - The sign of the fixed point, where `true` represents a negative number. 70 | /// 71 | /// ## Returns 72 | /// 73 | /// A new fixed point instance. 74 | /// 75 | /// ## Examples 76 | /// 77 | /// ```rust 78 | /// fn new_unscaled_example() -> FixedType { 79 | /// // We can call `new_unscaled` function as follows. 80 | /// FixedTrait::new_unscaled(1); 81 | /// } 82 | /// >>> {mag: 67108864, sign: false} 83 | /// ``` 84 | /// 85 | fn new_unscaled(mag: u256, sign: bool) -> FixedType; 86 | /// # FixedTrait::from_felt 87 | /// 88 | /// 89 | /// ```rust 90 | /// fn from_felt(val: felt252) -> FixedType; 91 | /// ``` 92 | /// 93 | /// Creates a new fixed point instance from a felt252 value. 94 | /// This function is only useful when you want a number with only an integer part. 95 | /// 96 | /// ## Args 97 | /// 98 | /// * `val`(`felt252`) - `felt252` value to convert in FixedType 99 | /// 100 | /// ## Returns 101 | /// 102 | /// A new fixed point instance. 103 | /// 104 | /// ## Examples 105 | /// 106 | /// ```rust 107 | /// fn from_felt_example() -> FixedType { 108 | /// // We can call `from_felt` function as follows . 109 | /// FixedTrait::from_felt(194615706); 110 | /// } 111 | /// >>> {mag: 194615706, sign: false} // = 2.9 112 | /// ``` 113 | /// 114 | fn from_felt(val: felt252) -> FixedType; 115 | ///# FixedTrait::from\_unscaled\_felt 116 | /// 117 | ///```rust 118 | ///fn from_unscaled_felt(val: felt252) -> FixedType; 119 | ///``` 120 | /// 121 | ///Creates a new fixed point instance from an unscaled felt252 value. 122 | /// This function is only useful when you want a number with only an integer part. 123 | /// 124 | /// ## Args 125 | /// 126 | /// `val`(`felt252`) - `felt252` value to convert in FixedType 127 | /// 128 | /// ## Returns - A new fixed point instance. 129 | /// 130 | /// ## Examples 131 | /// 132 | ///```rust 133 | ///fn from_unscaled_felt_example() -> FixedType { 134 | /// // We can call `from_unscaled_felt` function as follows . 135 | /// FixedTrait::from_unscaled_felt(1); 136 | ///} 137 | ///>>> {mag: 67108864, sign: false} 138 | ///``` 139 | /// 140 | fn from_unscaled_felt(val: felt252) -> FixedType; 141 | /// # fp.abs 142 | /// 143 | /// ```rust 144 | /// fn abs(self: FixedType) -> FixedType; 145 | /// ``` 146 | /// 147 | /// Returns the absolute value of the fixed point number. 148 | /// 149 | /// ## Args 150 | /// 151 | /// * `self`(`FixedType`) - The input fixed point 152 | /// 153 | /// ## Returns 154 | /// 155 | /// The absolute value of the input fixed point number. 156 | /// 157 | /// ## Examples 158 | /// 159 | /// ```rust 160 | /// fn abs_fp_example() -> FixedType { 161 | /// // We instantiate fixed point here. 162 | /// let fp = FixedTrait::from_unscaled_felt(-1); 163 | /// 164 | /// // We can call `abs` function as follows. 165 | /// fp.abs() 166 | /// } 167 | /// >>> {mag: 67108864, sign: false} // = 1 168 | /// ``` 169 | /// 170 | fn abs(self: FixedType) -> FixedType; 171 | /// # fp.ceil 172 | /// 173 | /// ```rust 174 | /// fn ceil(self: FixedType) -> FixedType; 175 | /// ``` 176 | /// 177 | /// Returns the smallest integer greater than or equal to the fixed point number. 178 | /// 179 | /// ## Args 180 | /// 181 | /// *`self`(`FixedType`) - The input fixed point 182 | /// 183 | /// ## Returns 184 | /// 185 | /// The smallest integer greater than or equal to the input fixed point number. 186 | /// 187 | /// ## Examples 188 | /// 189 | /// ```rust 190 | /// fn ceil_fp_example() -> FixedType { 191 | /// // We instantiate fixed point here. 192 | /// let fp = FixedTrait::from_felt(194615506); // 2.9 193 | /// 194 | /// // We can call `ceil` function as follows. 195 | /// fp.ceil() 196 | /// } 197 | /// >>> {mag: 201326592, sign: false} // = 3 198 | /// ``` 199 | /// 200 | fn ceil(self: FixedType) -> FixedType; 201 | /// # fp.floor 202 | /// 203 | /// ```rust 204 | /// fn floor(self: FixedType) -> FixedType; 205 | /// ``` 206 | /// 207 | /// Returns the largest integer less than or equal to the fixed point number. 208 | /// 209 | /// ## Args 210 | /// 211 | /// * `self`(`FixedType`) - The input fixed point 212 | /// 213 | /// ## Returns 214 | /// 215 | /// Returns the largest integer less than or equal to the input fixed point number. 216 | /// 217 | /// ## Examples 218 | /// 219 | /// ```rust 220 | /// fn floor_fp_example() -> FixedType { 221 | /// // We instantiate fixed point here. 222 | /// let fp = FixedTrait::from_felt(194615506); // 2.9 223 | /// 224 | /// // We can call `floor` function as follows. 225 | /// fp.floor() 226 | /// } 227 | /// >>> {mag: 134217728, sign: false} // = 2 228 | /// ``` 229 | /// 230 | fn floor(self: FixedType) -> FixedType; 231 | /// # fp.round 232 | /// 233 | /// ```rust 234 | /// fn round(self: FixedType) -> FixedType; 235 | /// ``` 236 | /// 237 | /// Rounds the fixed point number to the nearest whole number. 238 | /// 239 | /// ## Args 240 | /// 241 | /// * `self`(`FixedType`) - The input fixed point 242 | /// 243 | /// ## Returns 244 | /// 245 | /// A fixed point number representing the rounded value. 246 | /// 247 | /// ## Examples 248 | /// 249 | /// 250 | /// ```rust 251 | /// fn round_fp_example() -> FixedType { 252 | /// // We instantiate FixedTrait points here. 253 | /// let a = FixedTrait::from_felt(194615506); // 2.9 254 | /// 255 | /// // We can call `round` function as follows. 256 | /// a.round(b) 257 | /// } 258 | /// >>> {mag: 201326592, sign: false} // = 3 259 | /// ``` 260 | /// 261 | fn round(self: FixedType) -> FixedType; 262 | /// # fp.sqrt 263 | /// 264 | /// ```rust 265 | /// fn sqrt(self: FixedType) -> FixedType; 266 | /// ``` 267 | /// 268 | /// Returns the square root of the fixed point number. 269 | /// 270 | /// ## Args 271 | /// 272 | /// `self`(`FixedType`) - The input fixed point 273 | /// 274 | /// ## Panics 275 | /// 276 | /// * Panics if the input is negative. 277 | /// 278 | /// ## Returns 279 | /// 280 | /// A fixed point number representing the square root of the input value. 281 | /// 282 | /// ## Examples 283 | /// 284 | /// ```rust 285 | /// fn sqrt_fp_example() -> FixedType { 286 | /// // We instantiate FixedTrait points here. 287 | /// let a = FixedTrait::from_unscaled_felt(25); 288 | /// 289 | /// // We can call `round` function as follows. 290 | /// a.sqrt() 291 | /// } 292 | /// >>> {mag: 1677721600, sign: false} // = 5 293 | /// ``` 294 | /// 295 | fn sqrt(self: FixedType) -> FixedType; 296 | } 297 | -------------------------------------------------------------------------------- /crates/yas_core/src/numbers/fixed_point/implementations.cairo: -------------------------------------------------------------------------------- 1 | mod impl_64x96; 2 | -------------------------------------------------------------------------------- /crates/yas_core/src/numbers/fixed_point/implementations/impl_64x96.cairo: -------------------------------------------------------------------------------- 1 | use debug::PrintTrait; 2 | 3 | use yas_core::numbers::fixed_point::core::{FixedTrait, FixedType}; 4 | use yas_core::numbers::fixed_point::math::math_64x96; 5 | use yas_core::numbers::signed_integer::{i8::i8, i32::i32}; 6 | 7 | const PRIME: felt252 = 3618502788666131213697322783095070105623107215331596699973092056135872020480; 8 | const HALF_PRIME: felt252 = 9 | 1809251394333065606848661391547535052811553607665798349986546028067936010240; 10 | const ONE: u256 = 79228162514264337593543950336; // 2 ** 96 11 | const ONE_u128: u128 = 79228162514264337593543950336; // 2 ** 96 12 | const HALF: u128 = 39614081257132168796771975168; // 2 ** 95 13 | const MAX: u256 = 1461501637330902918203684832716283019655932542975; // (2 ** 160) - 1 14 | const Q96_RESOLUTION: u128 = 96; 15 | 16 | /// IMPLS 17 | 18 | impl FP64x96Impl of FixedTrait { 19 | fn new(mag: u256, sign: bool) -> FixedType { 20 | assert(mag <= MAX, 'fp overflow'); 21 | return FixedType { mag: mag, sign: sign }; 22 | } 23 | 24 | fn new_unscaled(mag: u256, sign: bool) -> FixedType { 25 | return FixedTrait::new(mag * ONE, sign); 26 | } 27 | 28 | fn from_felt(val: felt252) -> FixedType { 29 | let mag = _felt_abs(val).into(); 30 | return FixedTrait::new(mag, _felt_sign(val)); 31 | } 32 | 33 | fn from_unscaled_felt(val: felt252) -> FixedType { 34 | return FixedTrait::from_felt(val * ONE_u128.into()); 35 | } 36 | 37 | fn abs(self: FixedType) -> FixedType { 38 | return math_64x96::abs(self); 39 | } 40 | 41 | fn ceil(self: FixedType) -> FixedType { 42 | return math_64x96::ceil(self); 43 | } 44 | 45 | 46 | fn floor(self: FixedType) -> FixedType { 47 | return math_64x96::floor(self); 48 | } 49 | 50 | fn round(self: FixedType) -> FixedType { 51 | return math_64x96::round(self); 52 | } 53 | 54 | fn sqrt(self: FixedType) -> FixedType { 55 | return math_64x96::sqrt(self); 56 | } 57 | } 58 | 59 | impl FP64x96Print of PrintTrait { 60 | fn print(self: FixedType) { 61 | self.sign.print(); 62 | self.mag.print(); 63 | } 64 | } 65 | 66 | impl FP64x96Into of Into { 67 | fn into(self: FixedType) -> felt252 { 68 | let mag_felt = self.mag.try_into().unwrap(); 69 | 70 | if (self.sign == true) { 71 | return mag_felt * -1; 72 | } else { 73 | return mag_felt; 74 | } 75 | } 76 | } 77 | 78 | impl FP64x96PartialEq of PartialEq { 79 | #[inline(always)] 80 | fn eq(lhs: @FixedType, rhs: @FixedType) -> bool { 81 | return math_64x96::eq(*lhs, *rhs); 82 | } 83 | 84 | #[inline(always)] 85 | fn ne(lhs: @FixedType, rhs: @FixedType) -> bool { 86 | return math_64x96::ne(*lhs, *rhs); 87 | } 88 | } 89 | 90 | impl FP64x96Add of Add { 91 | fn add(lhs: FixedType, rhs: FixedType) -> FixedType { 92 | return math_64x96::add(lhs, rhs); 93 | } 94 | } 95 | 96 | impl FP64x96AddEq of AddEq { 97 | #[inline(always)] 98 | fn add_eq(ref self: FixedType, other: FixedType) { 99 | self = Add::add(self, other); 100 | } 101 | } 102 | 103 | impl FP64x96Sub of Sub { 104 | fn sub(lhs: FixedType, rhs: FixedType) -> FixedType { 105 | return math_64x96::sub(lhs, rhs); 106 | } 107 | } 108 | 109 | impl FP64x96SubEq of SubEq { 110 | #[inline(always)] 111 | fn sub_eq(ref self: FixedType, other: FixedType) { 112 | self = Sub::sub(self, other); 113 | } 114 | } 115 | 116 | impl FP64x96Mul of Mul { 117 | fn mul(lhs: FixedType, rhs: FixedType) -> FixedType { 118 | return math_64x96::mul(lhs, rhs); 119 | } 120 | } 121 | 122 | impl FP64x96MulEq of MulEq { 123 | #[inline(always)] 124 | fn mul_eq(ref self: FixedType, other: FixedType) { 125 | self = Mul::mul(self, other); 126 | } 127 | } 128 | 129 | impl FP64x96Div of Div { 130 | fn div(lhs: FixedType, rhs: FixedType) -> FixedType { 131 | return math_64x96::div(lhs, rhs); 132 | } 133 | } 134 | 135 | impl FP64x96DivEq of DivEq { 136 | #[inline(always)] 137 | fn div_eq(ref self: FixedType, other: FixedType) { 138 | self = Div::div(self, other); 139 | } 140 | } 141 | 142 | impl FP64x96PartialOrd of PartialOrd { 143 | #[inline(always)] 144 | fn ge(lhs: FixedType, rhs: FixedType) -> bool { 145 | return math_64x96::ge(lhs, rhs); 146 | } 147 | 148 | #[inline(always)] 149 | fn gt(lhs: FixedType, rhs: FixedType) -> bool { 150 | return math_64x96::gt(lhs, rhs); 151 | } 152 | 153 | #[inline(always)] 154 | fn le(lhs: FixedType, rhs: FixedType) -> bool { 155 | return math_64x96::le(lhs, rhs); 156 | } 157 | 158 | #[inline(always)] 159 | fn lt(lhs: FixedType, rhs: FixedType) -> bool { 160 | return math_64x96::lt(lhs, rhs); 161 | } 162 | } 163 | 164 | impl FP64x96Neg of Neg { 165 | #[inline(always)] 166 | fn neg(a: FixedType) -> FixedType { 167 | return math_64x96::neg(a); 168 | } 169 | } 170 | 171 | impl FP64x96TryIntoI32 of TryInto { 172 | fn try_into(self: FixedType) -> Option { 173 | _i32_try_from_fp(self) 174 | } 175 | } 176 | 177 | impl FP64x96TryIntoI8 of TryInto { 178 | fn try_into(self: FixedType) -> Option { 179 | _i8_try_from_fp(self) 180 | } 181 | } 182 | 183 | impl FP64x96TryIntoU32 of TryInto { 184 | fn try_into(self: FixedType) -> Option { 185 | _u32_try_from_fp(self) 186 | } 187 | } 188 | 189 | impl FP64x96Zeroable of Zeroable { 190 | fn zero() -> FixedType { 191 | FP64x96Impl::new(0, false) 192 | } 193 | #[inline(always)] 194 | fn is_zero(self: FixedType) -> bool { 195 | self.mag == Zeroable::zero() 196 | } 197 | #[inline(always)] 198 | fn is_non_zero(self: FixedType) -> bool { 199 | !self.is_zero() 200 | } 201 | } 202 | 203 | /// INTERNAL 204 | 205 | fn _felt_sign(a: felt252) -> bool { 206 | return integer::u256_from_felt252(a) > integer::u256_from_felt252(HALF_PRIME); 207 | } 208 | 209 | fn _felt_abs(a: felt252) -> felt252 { 210 | let a_sign = _felt_sign(a); 211 | 212 | if (a_sign == true) { 213 | return a * -1; 214 | } else { 215 | return a; 216 | } 217 | } 218 | 219 | fn _i32_try_from_fp(x: FixedType) -> Option { 220 | let unscaled_mag: Option = (x.mag / ONE).try_into(); 221 | 222 | match unscaled_mag { 223 | Option::Some(val) => Option::Some(i32 { mag: unscaled_mag.unwrap(), sign: x.sign }), 224 | Option::None(_) => Option::None(()) 225 | } 226 | } 227 | 228 | fn _u32_try_from_fp(x: FixedType) -> Option { 229 | let unscaled: Option = (x.mag / ONE).try_into(); 230 | 231 | match unscaled { 232 | Option::Some(val) => Option::Some(unscaled.unwrap()), 233 | Option::None(_) => Option::None(()) 234 | } 235 | } 236 | 237 | fn _i8_try_from_fp(x: FixedType) -> Option { 238 | let unscaled_mag: Option = (x.mag / ONE).try_into(); 239 | 240 | match unscaled_mag { 241 | Option::Some(val) => Option::Some(i8 { mag: unscaled_mag.unwrap(), sign: x.sign }), 242 | Option::None(_) => Option::None(()) 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /crates/yas_core/src/numbers/fixed_point/math.cairo: -------------------------------------------------------------------------------- 1 | mod math_64x96; 2 | -------------------------------------------------------------------------------- /crates/yas_core/src/numbers/fixed_point/math/math_64x96.cairo: -------------------------------------------------------------------------------- 1 | use yas_core::numbers::fixed_point::core::{FixedTrait, FixedType}; 2 | use yas_core::numbers::fixed_point::implementations::impl_64x96::{ONE, ONE_u128, MAX, HALF}; 3 | use yas_core::numbers::fixed_point::implementations::impl_64x96::{ 4 | FP64x96Impl, FP64x96Add, FP64x96AddEq, FP64x96Into, FP64x96Print, FP64x96PartialEq, FP64x96Sub, 5 | FP64x96SubEq, FP64x96Mul, FP64x96MulEq, FP64x96Div, FP64x96DivEq, FP64x96PartialOrd, FP64x96Neg 6 | }; 7 | 8 | /// Cf: FixedTrait::abs docstring 9 | fn abs(a: FixedType) -> FixedType { 10 | return FixedTrait::new(a.mag, false); 11 | } 12 | 13 | /// Adds two fixed point numbers and returns the result. 14 | /// 15 | /// # Arguments 16 | /// 17 | /// * `a` - The first fixed point number to add. 18 | /// * `b` - The second fixed point number to add. 19 | /// 20 | /// # Returns 21 | /// 22 | /// * The sum of the input fixed point numbers. 23 | fn add(a: FixedType, b: FixedType) -> FixedType { 24 | return FixedTrait::from_felt(a.into() + b.into()); 25 | } 26 | 27 | /// Cf: FixedTrait::ceil docstring 28 | fn ceil(a: FixedType) -> FixedType { 29 | let (div_u128, rem_u128) = _split_unsigned(a); 30 | 31 | if (rem_u128 == 0_u128) { 32 | return a; 33 | } else if (a.sign == false) { 34 | return FixedTrait::new_unscaled((div_u128 + 1_u128).into(), false); 35 | } else { 36 | return FixedTrait::from_unscaled_felt(div_u128.into() * -1); 37 | } 38 | } 39 | 40 | /// Divides the first fixed point number by the second fixed point number and returns the result. 41 | /// 42 | /// # Arguments 43 | /// 44 | /// * `a` - The dividend fixed point number. 45 | /// * `b` - The divisor fixed point number. 46 | /// 47 | /// # Returns 48 | /// 49 | /// * The result of the division of the input fixed point numbers. 50 | fn div(a: FixedType, b: FixedType) -> FixedType { 51 | let res_sign = a.sign ^ b.sign; 52 | 53 | // Invert b to preserve precision as much as possible 54 | // TODO: replace if / when there is a felt div_rem supported 55 | let mul_res = integer::u256_wide_mul(a.mag, ONE); 56 | let b_inv = MAX / b.mag; 57 | let res = u256 { high: mul_res.limb1, low: mul_res.limb0 } / b.mag 58 | + u256 { high: mul_res.limb3, low: mul_res.limb2 } * b_inv; 59 | 60 | // Re-apply sign 61 | return FixedType { mag: res, sign: res_sign }; 62 | } 63 | 64 | /// Checks whether two fixed point numbers are equal. 65 | /// 66 | /// # Arguments 67 | /// 68 | /// * `a` - The first fixed point number to compare. 69 | /// * `b` - The second fixed point number to compare. 70 | /// 71 | /// # Returns 72 | /// 73 | /// * A boolean value that indicates whether the input fixed point numbers are equal. 74 | fn eq(a: FixedType, b: FixedType) -> bool { 75 | return a.mag == b.mag && a.sign == b.sign; 76 | } 77 | 78 | /// Cf: FixedTrait::floor docstring 79 | fn floor(a: FixedType) -> FixedType { 80 | let (div_u128, rem_u128) = _split_unsigned(a); 81 | 82 | if (rem_u128 == 0_u128) { 83 | return a; 84 | } else if (a.sign == false) { 85 | return FixedTrait::new_unscaled(div_u128.into(), false); 86 | } else { 87 | return FixedTrait::from_unscaled_felt(-1 * div_u128.into() - 1); 88 | } 89 | } 90 | 91 | /// Checks whether the first fixed point number is greater than or equal to the second fixed point number. 92 | /// 93 | /// # Arguments 94 | /// 95 | /// * `a` - The first fixed point number to compare. 96 | /// * `b` - The second fixed point number to compare. 97 | /// 98 | /// # Returns 99 | /// 100 | /// * A boolean value that indicates whether the first fixed point number is greater than or equal to the second fixed point number. 101 | fn ge(a: FixedType, b: FixedType) -> bool { 102 | if (a.sign != b.sign) { 103 | return !a.sign; 104 | } else { 105 | return a.mag == b.mag || (a.mag > b.mag) ^ a.sign; 106 | } 107 | } 108 | 109 | /// Checks whether the first fixed point number is greater than the second fixed point number. 110 | /// 111 | /// # Arguments 112 | /// 113 | /// * `a` - The first fixed point number to compare. 114 | /// * `b` - The second fixed point number to compare. 115 | /// 116 | /// # Returns 117 | /// 118 | /// * A boolean value that indicates whether the first fixed point number is greater than the second fixed point number. 119 | fn gt(a: FixedType, b: FixedType) -> bool { 120 | if (a.sign != b.sign) { 121 | return !a.sign; 122 | } else { 123 | return a.mag != b.mag && (a.mag > b.mag) ^ a.sign; 124 | } 125 | } 126 | 127 | /// Checks whether the first fixed point number is less than or equal to the second fixed point number. 128 | /// 129 | /// # Arguments 130 | /// 131 | /// * `a` - The first fixed point number to compare. 132 | /// * `b` - The second fixed point number to compare. 133 | /// 134 | /// # Returns 135 | /// 136 | /// * A boolean value that indicates whether the first fixed point number is less than or equal to the second fixed point number. 137 | fn le(a: FixedType, b: FixedType) -> bool { 138 | if (a.sign != b.sign) { 139 | return a.sign; 140 | } else { 141 | return a.mag == b.mag || (a.mag < b.mag) ^ a.sign; 142 | } 143 | } 144 | 145 | /// Checks whether the first fixed point number is less than the second fixed point number. 146 | /// 147 | /// # Arguments 148 | /// 149 | /// * `a` - The first fixed point number to compare. 150 | /// * `b` - The second fixed point number to compare. 151 | /// 152 | /// # Returns 153 | /// 154 | /// * A boolean value that indicates whether the first fixed point number is less than the second fixed point number. 155 | fn lt(a: FixedType, b: FixedType) -> bool { 156 | if (a.sign != b.sign) { 157 | return a.sign; 158 | } else { 159 | return a.mag != b.mag && (a.mag < b.mag) ^ a.sign; 160 | } 161 | } 162 | 163 | /// Multiplies two fixed point numbers. 164 | /// 165 | /// # Arguments 166 | /// 167 | /// * `a` - The first fixed point number. 168 | /// * `b` - The second fixed point number. 169 | /// 170 | /// # Returns 171 | /// 172 | /// * A FixedType value representing the product of the two input numbers. 173 | fn mul(a: FixedType, b: FixedType) -> FixedType { 174 | let res_sign = a.sign ^ b.sign; 175 | 176 | // Use u128 to multiply and shift back down 177 | // TODO: replace if / when there is a felt div_rem supported 178 | let mul_res = integer::u256_wide_mul(a.mag, b.mag); 179 | let res_u256 = u256 { high: mul_res.limb3, low: mul_res.limb2 } 180 | + (u256 { high: mul_res.limb1, low: mul_res.limb0 } / ONE); 181 | 182 | // Re-apply sign 183 | return FixedType { mag: res_u256, sign: res_sign }; 184 | } 185 | 186 | /// Checks whether the first fixed point number is not equal to the second fixed point number. 187 | /// 188 | /// # Arguments 189 | /// 190 | /// * `a` - The first fixed point number to compare. 191 | /// * `b` - The second fixed point number to compare. 192 | /// 193 | /// # Returns 194 | /// 195 | /// * A boolean value that indicates whether the first fixed point number is not equal to the second fixed point number. 196 | fn ne(a: FixedType, b: FixedType) -> bool { 197 | return a.mag != b.mag || a.sign != b.sign; 198 | } 199 | 200 | /// Negates a fixed point number. 201 | /// 202 | /// # Arguments 203 | /// 204 | /// * `a` - The fixed point number to negate. 205 | /// 206 | /// # Returns 207 | /// 208 | /// * A FixedType value representing the negation of the input number. 209 | fn neg(a: FixedType) -> FixedType { 210 | if (a.sign == false) { 211 | return FixedTrait::new(a.mag, true); 212 | } else { 213 | return FixedTrait::new(a.mag, false); 214 | } 215 | } 216 | 217 | /// Cf: FixedTrait::round docstring 218 | fn round(a: FixedType) -> FixedType { 219 | let (div_u128, rem_u128) = _split_unsigned(a); 220 | 221 | if (HALF <= rem_u128) { 222 | return FixedTrait::new(ONE * (div_u128 + 1_u128).into(), a.sign); 223 | } else { 224 | return FixedTrait::new(ONE * div_u128.into(), a.sign); 225 | } 226 | } 227 | 228 | /// Important! It is advised that the current function 229 | /// has a precision error of at least 3 points 230 | fn sqrt(a: FixedType) -> FixedType { 231 | assert(a.sign == false, 'must be positive'); 232 | let root = integer::u256_sqrt(a.mag); 233 | let scale_root = integer::u256_sqrt(ONE); 234 | let res_u256 = root.into() * ONE / scale_root.into(); 235 | return FixedTrait::new(res_u256, false); 236 | } 237 | 238 | /// Subtracts one fixed point number from another. 239 | /// 240 | /// # Arguments 241 | /// 242 | /// * `a` - The minuend fixed point number. 243 | /// * `b` - The subtrahend fixed point number. 244 | /// 245 | /// # Returns 246 | /// 247 | /// * A fixed point number representing the result of the subtraction. 248 | fn sub(a: FixedType, b: FixedType) -> FixedType { 249 | return FixedTrait::from_felt(a.into() - b.into()); 250 | } 251 | 252 | /// INTERNAL 253 | 254 | /// Ignores the sign and always returns false. 255 | /// 256 | /// # Arguments 257 | /// 258 | /// * `a` - The input fixed point number. 259 | /// 260 | /// # Returns 261 | /// 262 | /// * A tuple of two u128 numbers representing the division and remainder of the input number divided by `ONE`. 263 | fn _split_unsigned(a: FixedType) -> (u128, u128) { 264 | let div: u256 = a.mag / ONE; 265 | let rem: u256 = a.mag % ONE; 266 | return (div.try_into().unwrap(), rem.try_into().unwrap()); 267 | } 268 | -------------------------------------------------------------------------------- /crates/yas_core/src/numbers/signed_integer.cairo: -------------------------------------------------------------------------------- 1 | mod integer_trait; 2 | mod i8; 3 | mod i16; 4 | mod i32; 5 | mod i64; 6 | mod i128; 7 | mod i256; 8 | 9 | -------------------------------------------------------------------------------- /crates/yas_core/src/numbers/signed_integer/integer_trait.cairo: -------------------------------------------------------------------------------- 1 | /// Trait 2 | /// 3 | /// new - Constructs a new `signed_integer 4 | /// div_rem - Computes `signed_integer` division and modulus simultaneously 5 | /// abs - Computes the absolute value of the given `signed_integer` 6 | /// max - Returns the maximum between two `signed_integer` 7 | /// min - Returns the minimum between two `signed_integer` 8 | trait IntegerTrait { 9 | /// # IntegerTrait::new 10 | /// 11 | /// ```rust 12 | /// fn new(mag: U, sign: bool) -> T; 13 | /// ``` 14 | /// 15 | /// Returns a new signed integer. 16 | /// 17 | /// ## Args 18 | /// 19 | /// * `mag`(`U`) - The magnitude of the integer. 20 | /// * `sign`(`bool`) - The sign of the integer, where `true` represents a negative number. 21 | /// 22 | /// > _`` generic type depends on the uint type (u8, u16, u32, u64, u128)._ 23 | /// 24 | /// ## Panics 25 | /// 26 | /// Panics if `mag` is out of range. 27 | /// 28 | /// ## Returns 29 | /// 30 | /// A new signed integer. 31 | /// 32 | /// ## Examples 33 | /// 34 | /// ```rust 35 | /// fn new_i8_example() -> i8 { 36 | /// IntegerTrait::::new(42_u8, true) 37 | /// } 38 | /// >>> {mag: 42, sign: true} // = -42 39 | /// ``` 40 | /// 41 | /// ```rust 42 | /// fn panic_i8_example() -> i8 { 43 | /// IntegerTrait::::new(129_u8, true) 44 | /// } 45 | /// >>> panics with "int: out of range" 46 | /// ``` 47 | /// 48 | fn new(mag: U, sign: bool) -> T; 49 | /// # int.div_rem 50 | /// 51 | /// ```rust 52 | /// fn div_rem(self: T, other: T) -> (T, T); 53 | /// ``` 54 | /// 55 | /// Computes signed\_integer division and modulus simultaneously 56 | /// 57 | /// ## Args 58 | /// 59 | /// * `self`(`T`) - The dividend 60 | /// * `other`(`T`) - The divisor 61 | /// 62 | /// ## Panics 63 | /// 64 | /// Panics if the divisor is zero. 65 | /// 66 | /// ## Returns 67 | /// 68 | /// A tuple of signed integer ``, containing the quotient and the remainder of the division. 69 | /// 70 | /// ## Examples 71 | /// 72 | /// ```rust 73 | /// fn div_rem_example() -> (i32, i32) { 74 | /// // We instantiate signed integers here. 75 | /// let a = IntegerTrait::::new(13, false); 76 | /// let b = IntegerTrait::::new(5, false); 77 | /// 78 | /// // We can call `div_rem` function as follows. 79 | /// a.div_rem(b) 80 | /// } 81 | /// >>> ({mag: 2, sign: false}, {mag: 3, sign: false}) // = (2, 3) 82 | /// ``` 83 | /// 84 | fn div_rem(self: T, other: T) -> (T, T); 85 | /// # int.abs 86 | /// 87 | /// ```rust 88 | /// fn abs(self: T) -> T; 89 | /// ``` 90 | /// 91 | /// Computes the absolute value of a signed\_integer. 92 | /// 93 | /// ## Args 94 | /// 95 | /// `self`(`T`) - The signed integer to which the absolute value is applied 96 | /// 97 | /// ## Returns 98 | /// 99 | /// A signed integer ``, representing the absolute value of `self` . 100 | /// 101 | /// ## Examples 102 | /// 103 | /// ```rust 104 | /// fn abs_example() -> i32 { 105 | /// // We instantiate signed integers here. 106 | /// let int = IntegerTrait::::new(42, true); 107 | /// 108 | /// // We can call `abs` function as follows. 109 | /// a.abs() 110 | /// } 111 | /// >>> {mag: 42, sign: false} // = 42 112 | /// ``` 113 | /// 114 | fn abs(self: T) -> T; 115 | /// # int.max 116 | /// 117 | /// ```rust 118 | /// fn max(self: T, other: T) -> T; 119 | /// ``` 120 | /// 121 | /// Returns the maximum between two signed\_integer. 122 | /// 123 | /// ## Args 124 | /// 125 | /// *`self`(`T`) - The first signed integer to compare. 126 | /// * `other`(`T`) - The second signed integer to compare. 127 | /// 128 | /// ## Returns 129 | /// 130 | /// A signed integer ``, The maximum between `self` and `other`. 131 | /// 132 | /// ## Examples 133 | /// 134 | /// ```rust 135 | /// fn max_example() -> i32 { 136 | /// // We instantiate signed integer here. 137 | /// let a = IntegerTrait::::new(42, true); 138 | /// let b = IntegerTrait::::new(13, false); 139 | /// 140 | /// // We can call `max` function as follows. 141 | /// a.max(b) 142 | /// } 143 | /// >>> {mag: 13, sign: false} // as 13 > -42 144 | /// ``` 145 | /// 146 | fn max(self: T, other: T) -> T; 147 | /// # int.min 148 | /// 149 | /// ```rust 150 | /// fn min(self: T, other: T) -> T; 151 | /// ``` 152 | /// 153 | /// Returns the minimum between two signed\_integer. 154 | /// 155 | /// ## Args 156 | /// 157 | /// `self`(`T`) - The first signed integer to compare. 158 | /// `other`(`T`) - The second signed integer to compare. 159 | /// 160 | /// ## Returns 161 | /// 162 | /// A signed integer ``, The minimum between `self` and `other`. 163 | /// 164 | /// ## Examples 165 | /// 166 | /// 167 | /// ```rust 168 | /// fn min_example() -> i32 { 169 | /// // We instantiate signed integer here. 170 | /// let a = IntegerTrait::::new(42, true); 171 | /// let b = IntegerTrait::::new(13, false); 172 | /// 173 | /// // We can call `max` function as follows. 174 | /// a.min(b) 175 | /// } 176 | /// >>> {mag: 42, sign: true} // as -42 < 13 177 | /// ``` 178 | /// 179 | fn min(self: T, other: T) -> T; 180 | } 181 | 182 | -------------------------------------------------------------------------------- /crates/yas_core/src/tests/test_libraries/test_bit_math.cairo: -------------------------------------------------------------------------------- 1 | mod BitMathTests { 2 | mod MostSignificantBit { 3 | use integer::BoundedInt; 4 | use yas_core::libraries::bit_math::BitMath::most_significant_bit; 5 | 6 | #[test] 7 | fn msb_happy_path() { 8 | // 1 9 | assert(most_significant_bit(1) == 0, 'msb should be 0'); 10 | } 11 | 12 | #[test] 13 | fn msb_larger_number() { 14 | // 10000000 15 | assert(most_significant_bit(128) == 7, 'msb should be 7'); 16 | } 17 | 18 | #[test] 19 | fn msb_bigger_number() { 20 | // 11110100001001000000 21 | assert(most_significant_bit(1000000) == 19, 'msb should be 19'); 22 | } 23 | 24 | #[test] 25 | fn msb_maximum_256() { 26 | assert(most_significant_bit(BoundedInt::max()) == 255, 'msb should be 255'); 27 | } 28 | 29 | #[test] 30 | fn msb_random_number() { 31 | // 11000000111001 32 | assert(most_significant_bit(12345) == 13, 'msb should be 13'); 33 | } 34 | 35 | #[test] 36 | #[should_panic] 37 | fn msb_number_zero() { 38 | let ret = most_significant_bit(0); 39 | } 40 | } 41 | 42 | mod LeastSignificantBit { 43 | use integer::BoundedInt; 44 | use yas_core::libraries::bit_math::BitMath::{least_significant_bit, check_gt_zero}; 45 | #[test] 46 | fn lsb_happy_path() { 47 | // 1 48 | assert(least_significant_bit(1) == 0, 'lsb should be 0'); 49 | } 50 | #[test] 51 | #[should_panic(expected: ('x must be greater than 0',))] 52 | fn test_check_gt_zero() { 53 | match check_gt_zero(0) { 54 | Result::Ok(()) => {}, 55 | Result::Err(err) => { 56 | panic_with_felt252(err) 57 | }, 58 | } 59 | } 60 | #[test] 61 | fn lsb_larger_number() { 62 | // 10000000 63 | assert(least_significant_bit(128) == 7, 'lsb should be 7'); 64 | } 65 | 66 | #[test] 67 | fn lsb_bigger_number() { 68 | // 11110100001001000000 69 | assert(least_significant_bit(1000000) == 6, 'lsb should be 6'); 70 | } 71 | 72 | #[test] 73 | fn lsb_maximum_256() { 74 | // 75 | assert(least_significant_bit(BoundedInt::max()) == 0, 'lsb should be 0'); 76 | } 77 | 78 | #[test] 79 | fn lsb_random_number() { 80 | // 11000000111001 81 | let ret = least_significant_bit(12345); 82 | assert(ret == 0, 'lsb should be 0'); 83 | } 84 | 85 | #[test] 86 | #[should_panic] 87 | fn lsb_number_zero() { 88 | let ret = least_significant_bit(0); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /crates/yas_core/src/tests/test_libraries/test_liquidity_math.cairo: -------------------------------------------------------------------------------- 1 | use yas_core::libraries::liquidity_math::LiquidityMath::add_delta; 2 | use yas_core::numbers::signed_integer::{i128::i128, integer_trait::IntegerTrait}; 3 | 4 | #[test] 5 | #[available_gas(2000000)] 6 | fn test_add_delta_5_10() { 7 | let y = IntegerTrait::::new(10, false); 8 | let z = add_delta(5, y); 9 | assert(z == 15, 'z == 15'); 10 | } 11 | 12 | #[test] 13 | #[available_gas(2000000)] 14 | fn test_add_delta_1_0() { 15 | let y = IntegerTrait::::new(0, false); 16 | let z = add_delta(1, y); 17 | assert(z == 1, 'z == 1'); 18 | } 19 | #[test] 20 | #[available_gas(2000000)] 21 | fn test_add_delta_1_minus1() { 22 | let y = IntegerTrait::::new(1, true); 23 | let z = add_delta(1, y); 24 | assert(z == 0, 'z == 0'); 25 | } 26 | #[test] 27 | #[available_gas(2000000)] 28 | fn test_add_delta_1_1() { 29 | let y = IntegerTrait::::new(1, false); 30 | let z = add_delta(1, y); 31 | assert(z == 2, 'z == 2'); 32 | } 33 | #[test] 34 | #[available_gas(2000000)] 35 | #[should_panic(expected: ('LA',))] 36 | // Should panic with 'LA'. 37 | fn test_add_delta_overflows() { 38 | let x: u128 = 340282366920938463463374607431768211455; // 2 ** 128 - 1 39 | let x = x - 14; 40 | let y = IntegerTrait::::new(15, false); 41 | add_delta(x, y); 42 | } 43 | #[test] 44 | #[available_gas(2000000)] 45 | #[should_panic(expected: ('LS',))] 46 | // Should panic with 'LS'. 47 | fn test_add_delta_0_minus1_underflows() { 48 | let y = IntegerTrait::::new(1, true); 49 | add_delta(0, y); 50 | } 51 | 52 | #[test] 53 | #[available_gas(2000000)] 54 | #[should_panic(expected: ('LS',))] 55 | // Should panic with 'LS'. 56 | fn test_add_delta_3_minus4_underflows() { 57 | let y = IntegerTrait::::new(4, true); 58 | add_delta(3, y); 59 | } 60 | -------------------------------------------------------------------------------- /crates/yas_core/src/tests/test_libraries/test_position.cairo: -------------------------------------------------------------------------------- 1 | mod PositionTests { 2 | use yas_core::libraries::position::Position; 3 | use starknet::{contract_address_const, ContractAddress}; 4 | 5 | fn STATE() -> Position::ContractState { 6 | Position::contract_state_for_testing() 7 | } 8 | 9 | fn OWNER() -> ContractAddress { 10 | contract_address_const::<'OWNER'>() 11 | } 12 | 13 | mod Get { 14 | use super::{STATE, OWNER}; 15 | 16 | use yas_core::libraries::position::{ 17 | Info, PositionKey, Position::{PositionImpl, InternalImpl} 18 | }; 19 | 20 | use yas_core::numbers::signed_integer::{i32::i32, integer_trait::IntegerTrait}; 21 | 22 | 23 | #[test] 24 | #[available_gas(30000000)] 25 | fn test_get_state() { 26 | let mut state = STATE(); 27 | 28 | let position_key = PositionKey { 29 | owner: OWNER(), 30 | tick_lower: IntegerTrait::::new(0, false), 31 | tick_upper: IntegerTrait::::new(10, false), 32 | }; 33 | 34 | let info = Info { 35 | liquidity: 100, 36 | fee_growth_inside_0_last_X128: 20, 37 | fee_growth_inside_1_last_X128: 20, 38 | tokens_owed_0: 10, 39 | tokens_owed_1: 10, 40 | }; 41 | 42 | InternalImpl::set_position(ref state, position_key, info); 43 | 44 | let position = PositionImpl::get(@state, position_key); 45 | 46 | assert(position.liquidity == 100, 'liquidity'); 47 | assert(position.fee_growth_inside_0_last_X128 == 20, 'fee_growth_inside_0_last_X128'); 48 | assert(position.fee_growth_inside_1_last_X128 == 20, 'fee_growth_inside_1_last_X128'); 49 | assert(position.tokens_owed_0 == 10, 'tokens_owed_0'); 50 | assert(position.tokens_owed_1 == 10, 'tokens_owed_1'); 51 | } 52 | #[test] 53 | #[available_gas(30000000)] 54 | fn test_get_not_init_position() { 55 | let mut state = STATE(); 56 | 57 | let position_key = PositionKey { 58 | owner: OWNER(), 59 | tick_lower: IntegerTrait::::new(0, false), 60 | tick_upper: IntegerTrait::::new(10, false), 61 | }; 62 | 63 | let position: Info = PositionImpl::get(@state, position_key); 64 | 65 | assert(position.liquidity == 0, 'liquidity'); 66 | assert(position.fee_growth_inside_0_last_X128 == 0, 'fee_growth_inside_0_last_X128'); 67 | assert(position.fee_growth_inside_1_last_X128 == 0, 'fee_growth_inside_1_last_X128'); 68 | assert(position.tokens_owed_0 == 0, 'tokens_owed_0'); 69 | assert(position.tokens_owed_1 == 0, 'tokens_owed_1'); 70 | } 71 | } 72 | 73 | mod Update { 74 | use super::{STATE, OWNER}; 75 | 76 | use yas_core::libraries::position::{ 77 | Info, PositionKey, Position::{PositionImpl, InternalImpl} 78 | }; 79 | 80 | use yas_core::numbers::signed_integer::{i32::i32, i128::i128, integer_trait::IntegerTrait}; 81 | 82 | #[test] 83 | #[available_gas(30000000)] 84 | #[should_panic(expected: ('NP',))] 85 | fn test_liquidity_delta_eq_zero_with_position_not_init() { 86 | let mut state = STATE(); 87 | 88 | let position_key = PositionKey { 89 | owner: OWNER(), 90 | tick_lower: IntegerTrait::::new(0, false), 91 | tick_upper: IntegerTrait::::new(10, false), 92 | }; 93 | 94 | // should be zero or negative to make it panic 95 | let liquidity_delta = IntegerTrait::::new(0, false); 96 | 97 | let fee_growth_inside_0_X128 = 0; 98 | let fee_growth_inside_1_X128 = 0; 99 | 100 | let position = PositionImpl::update( 101 | ref state, 102 | position_key, 103 | liquidity_delta, 104 | fee_growth_inside_0_X128, 105 | fee_growth_inside_1_X128 106 | ); 107 | } 108 | 109 | #[test] 110 | #[available_gas(30000000)] 111 | fn test_liquidity_delta_eq_zero_with_position_init() { 112 | let mut state = STATE(); 113 | 114 | let position_key = PositionKey { 115 | owner: OWNER(), 116 | tick_lower: IntegerTrait::::new(0, false), 117 | tick_upper: IntegerTrait::::new(10, false), 118 | }; 119 | 120 | let info = Info { 121 | liquidity: 100, 122 | fee_growth_inside_0_last_X128: 0, 123 | fee_growth_inside_1_last_X128: 0, 124 | tokens_owed_0: 10, 125 | tokens_owed_1: 10, 126 | }; 127 | 128 | InternalImpl::set_position(ref state, position_key, info); 129 | 130 | let liquidity_delta = IntegerTrait::::new(0, false); 131 | 132 | let fee_growth_inside_0_X128 = 0; 133 | let fee_growth_inside_1_X128 = 0; 134 | 135 | PositionImpl::update( 136 | ref state, 137 | position_key, 138 | liquidity_delta, 139 | fee_growth_inside_0_X128, 140 | fee_growth_inside_1_X128 141 | ); 142 | 143 | let position = PositionImpl::get(@state, position_key); 144 | 145 | assert(position.liquidity == 100, 'liquidity'); 146 | assert(position.fee_growth_inside_0_last_X128 == 0, 'fee_growth_inside_0_last_X128'); 147 | assert(position.fee_growth_inside_1_last_X128 == 0, 'fee_growth_inside_1_last_X128'); 148 | assert(position.tokens_owed_0 == 10, 'tokens_owed_0'); 149 | assert(position.tokens_owed_1 == 10, 'tokens_owed_1'); 150 | } 151 | 152 | #[test] 153 | #[available_gas(30000000)] 154 | fn test_not_init_position() { 155 | let mut state = STATE(); 156 | 157 | let position_key = PositionKey { 158 | owner: OWNER(), 159 | tick_lower: IntegerTrait::::new(0, false), 160 | tick_upper: IntegerTrait::::new(10, false), 161 | }; 162 | 163 | let liquidity_delta = IntegerTrait::::new(100, false); 164 | 165 | let fee_growth_inside_0_X128 = 0; 166 | let fee_growth_inside_1_X128 = 0; 167 | 168 | PositionImpl::update( 169 | ref state, 170 | position_key, 171 | liquidity_delta, 172 | fee_growth_inside_0_X128, 173 | fee_growth_inside_1_X128 174 | ); 175 | 176 | let position = PositionImpl::get(@state, position_key); 177 | 178 | assert(position.liquidity == 100, 'liquidity'); 179 | assert(position.fee_growth_inside_0_last_X128 == 0, 'fee_growth_inside_0_last_X128'); 180 | assert(position.fee_growth_inside_1_last_X128 == 0, 'fee_growth_inside_1_last_X128'); 181 | assert(position.tokens_owed_0 == 0, 'tokens_owed_0'); 182 | assert(position.tokens_owed_1 == 0, 'tokens_owed_1'); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /crates/yas_core/src/tests/test_numbers/test_fixed_point.cairo: -------------------------------------------------------------------------------- 1 | mod test_fp64x96; 2 | -------------------------------------------------------------------------------- /crates/yas_core/src/tests/test_numbers/test_signed_integer.cairo: -------------------------------------------------------------------------------- 1 | mod test_signed_integer; 2 | mod test_i32; 3 | mod test_i256; 4 | 5 | -------------------------------------------------------------------------------- /crates/yas_core/src/tests/test_numbers/test_signed_integer/test_signed_integer.cairo: -------------------------------------------------------------------------------- 1 | mod SignedInteger { 2 | use yas_core::numbers::signed_integer::{integer_trait::IntegerTrait, i32::i32}; 3 | 4 | #[test] 5 | fn test_into() { 6 | let x = IntegerTrait::::new(42, false); 7 | assert(x.into() == 42, 'should convert to felt252'); 8 | let x = IntegerTrait::::new(42, true); 9 | assert(x.into() == -42, 'should convert to felt252'); 10 | } 11 | 12 | #[test] 13 | fn test_add() { 14 | // Test addition of two positive integers 15 | let a = IntegerTrait::::new(42, false); 16 | let b = IntegerTrait::::new(13, false); 17 | let result = a + b; 18 | assert(result.mag == 55, '42 + 13 = 55'); 19 | assert(result.sign == false, '42 + 13 -> positive'); 20 | 21 | // Test addition of two negative integers 22 | let a = IntegerTrait::::new(42, true); 23 | let b = IntegerTrait::::new(13, true); 24 | let result = a + b; 25 | assert(result.mag == 55, '-42 - 13 = -55'); 26 | assert(result.sign == true, '-42 - 13 -> negative'); 27 | 28 | // Test addition of a positive integer and a negative integer with the same magnitude 29 | 30 | let a = IntegerTrait::::new(42, false); 31 | let b = IntegerTrait::::new(42, true); 32 | let result = a + b; 33 | assert(result.mag == 0, '42 - 42 = 0'); 34 | assert(result.sign == false, '42 - 42 -> positive'); 35 | 36 | // Test addition of a positive integer and a negative integer with different magnitudes 37 | let a = IntegerTrait::::new(42, false); 38 | let b = IntegerTrait::::new(13, true); 39 | let result = a + b; 40 | assert(result.mag == 29, '42 - 13 = 29'); 41 | assert(result.sign == false, '42 - 13 -> positive'); 42 | 43 | // Test addition of a negative integer and a positive integer with different magnitudes 44 | let a = IntegerTrait::::new(42, true); 45 | let b = IntegerTrait::::new(13, false); 46 | let result = a + b; 47 | assert(result.mag == 29, '-42 + 13 = -29'); 48 | assert(result.sign == true, '-42 + 13 -> negative'); 49 | } 50 | 51 | #[test] 52 | fn test_sub() { 53 | // Test subtraction of two positive integers with larger first 54 | let a = IntegerTrait::::new(42, false); 55 | let b = IntegerTrait::::new(13, false); 56 | let result = a - b; 57 | assert(result.mag == 29, '42 - 13 = 29'); 58 | assert(result.sign == false, '42 - 13 -> positive'); 59 | 60 | // Test subtraction of two positive integers with larger second 61 | let a = IntegerTrait::::new(13, false); 62 | let b = IntegerTrait::::new(42, false); 63 | let result = a - b; 64 | assert(result.mag == 29, '13 - 42 = -29'); 65 | assert(result.sign == true, '13 - 42 -> negative'); 66 | 67 | // Test subtraction of two negative integers with larger first 68 | let a = IntegerTrait::::new(42, true); 69 | let b = IntegerTrait::::new(13, true); 70 | let result = a - b; 71 | assert(result.mag == 29, '-42 - -13 = 29'); 72 | assert(result.sign == true, '-42 - -13 -> negative'); 73 | 74 | // Test subtraction of two negative integers with larger second 75 | let a = IntegerTrait::::new(13, true); 76 | let b = IntegerTrait::::new(42, true); 77 | let result = a - b; 78 | assert(result.mag == 29, '-13 - -42 = 29'); 79 | assert(result.sign == false, '-13 - -42 -> positive'); 80 | 81 | // Test subtraction of a positive integer and a negative integer with the same magnitude 82 | let a = IntegerTrait::::new(42, false); 83 | let b = IntegerTrait::::new(42, true); 84 | let result = a - b; 85 | assert(result.mag == 84, '42 - -42 = 84'); 86 | assert(result.sign == false, '42 - -42 -> postive'); 87 | 88 | // Test subtraction of a negative integer and a positive integer with the same magnitude 89 | let a = IntegerTrait::::new(42, true); 90 | let b = IntegerTrait::::new(42, false); 91 | let result = a - b; 92 | assert(result.mag == 84, '-42 - 42 = -84'); 93 | assert(result.sign == true, '-42 - 42 -> negative'); 94 | 95 | // Test subtraction of a positive integer and a negative integer with different magnitudes 96 | let a = IntegerTrait::::new(100, false); 97 | let b = IntegerTrait::::new(42, true); 98 | let result = a - b; 99 | assert(result.mag == 142, '100 - - 42 = 142'); 100 | assert(result.sign == false, '100 - - 42 -> postive'); 101 | 102 | // Test subtraction of a negative integer and a positive integer with different magnitudes 103 | let a = IntegerTrait::::new(42, true); 104 | let b = IntegerTrait::::new(100, false); 105 | let result = a - b; 106 | assert(result.mag == 142, '-42 - 100 = -142'); 107 | assert(result.sign == true, '-42 - 100 -> negative'); 108 | 109 | // Test subtraction resulting in zero 110 | let a = IntegerTrait::::new(42, false); 111 | let b = IntegerTrait::::new(42, false); 112 | let result = a - b; 113 | assert(result.mag == 0, '42 - 42 = 0'); 114 | assert(result.sign == false, '42 - 42 -> positive'); 115 | } 116 | 117 | #[test] 118 | fn test_mul() { 119 | // Test multiplication of positive integers 120 | let a = IntegerTrait::::new(10, false); 121 | let b = IntegerTrait::::new(5, false); 122 | let result = a * b; 123 | assert(result.mag == 50, '10 * 5 = 50'); 124 | assert(result.sign == false, '10 * 5 -> positive'); 125 | 126 | // Test multiplication of negative integers 127 | let a = IntegerTrait::::new(10, true); 128 | let b = IntegerTrait::::new(5, true); 129 | let result = a * b; 130 | assert(result.mag == 50, '-10 * -5 = 50'); 131 | assert(result.sign == false, '-10 * -5 -> positive'); 132 | 133 | // Test multiplication of positive and negative integers 134 | let a = IntegerTrait::::new(10, false); 135 | let b = IntegerTrait::::new(5, true); 136 | let result = a * b; 137 | assert(result.mag == 50, '10 * -5 = -50'); 138 | assert(result.sign == true, '10 * -5 -> negative'); 139 | 140 | // Test multiplication by zero 141 | let a = IntegerTrait::::new(10, false); 142 | let b = IntegerTrait::::new(0, false); 143 | let result = a * b; 144 | assert(result.mag == 0, '10 * 0 = 0'); 145 | assert(result.sign == false, '10 * 0 -> positive'); 146 | } 147 | 148 | #[test] 149 | fn test_div_no_rem() { 150 | // Test division of positive integers 151 | let a = IntegerTrait::::new(10, false); 152 | let b = IntegerTrait::::new(5, false); 153 | let result = a / b; 154 | assert(result.mag == 2, '10 // 5 = 2'); 155 | assert(result.sign == false, '10 // 5 -> positive'); 156 | 157 | // Test division of negative integers 158 | let a = IntegerTrait::::new(10, true); 159 | let b = IntegerTrait::::new(5, true); 160 | let result = a / b; 161 | assert(result.mag == 2, '-10 // -5 = 2'); 162 | assert(result.sign == false, '-10 // -5 -> positive'); 163 | 164 | // Test division of positive and negative integers 165 | let a = IntegerTrait::::new(10, false); 166 | let b = IntegerTrait::::new(5, true); 167 | let result = a / b; 168 | assert(result.mag == 2, '10 // -5 = -2'); 169 | assert(result.sign == true, '10 // -5 -> negative'); 170 | 171 | // Test division with a = zero 172 | let a = IntegerTrait::::new(0, false); 173 | let b = IntegerTrait::::new(10, false); 174 | let result = a / b; 175 | assert(result.mag == 0, '0 // 10 = 0'); 176 | assert(result.sign == false, '0 // 10 -> positive'); 177 | 178 | // Test division with a = zero 179 | let a = IntegerTrait::::new(0, false); 180 | let b = IntegerTrait::::new(10, false); 181 | let result = a / b; 182 | assert(result.mag == 0, '0 // 10 = 0'); 183 | assert(result.sign == false, '0 // 10 -> positive'); 184 | } 185 | 186 | #[test] 187 | #[available_gas(20000000)] 188 | fn test_div_rem() { 189 | // Test division and remainder of positive integers 190 | let a = IntegerTrait::::new(13, false); 191 | let b = IntegerTrait::::new(5, false); 192 | let (q, r) = a.div_rem(b); 193 | assert(q.mag == 2 && r.mag == 3, '13 // 5 = 2 r 3'); 194 | assert((q.sign == false) & (r.sign == false), '13 // 5 -> positive'); 195 | 196 | // Test division and remainder of negative integers 197 | let a = IntegerTrait::::new(13, true); 198 | let b = IntegerTrait::::new(5, true); 199 | let (q, r) = a.div_rem(b); 200 | assert(q.mag == 2 && r.mag == 3, '-13 // -5 = 2 r -3'); 201 | assert(q.sign == false && r.sign == true, '-13 // -5 -> positive'); 202 | 203 | // Test division and remainder of positive and negative integers 204 | let a = IntegerTrait::::new(13, false); 205 | let b = IntegerTrait::::new(5, true); 206 | let (q, r) = a.div_rem(b); 207 | assert(q.mag == 3 && r.mag == 2, '13 // -5 = -3 r -2'); 208 | assert(q.sign == true && r.sign == true, '13 // -5 -> negative'); 209 | 210 | // Test division with a = zero 211 | let a = IntegerTrait::::new(0, false); 212 | let b = IntegerTrait::::new(10, false); 213 | let (q, r) = a.div_rem(b); 214 | assert(q.mag == 0 && r.mag == 0, '0 // 10 = 0 r 0'); 215 | assert(q.sign == false && r.sign == false, '0 // 10 -> positive'); 216 | 217 | // Test division and remainder with a negative dividend and positive divisor 218 | let a = IntegerTrait::::new(13, true); 219 | let b = IntegerTrait::::new(5, false); 220 | let (q, r) = a.div_rem(b); 221 | assert(q.mag == 3 && r.mag == 2, '-13 // 5 = -3 r 2'); 222 | assert(q.sign == true && r.sign == false, '-13 // 5 -> negative'); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /crates/yas_core/src/tests/test_utils/test_utils.cairo: -------------------------------------------------------------------------------- 1 | mod ContractAddressPartialOrdTest { 2 | use starknet::{ContractAddress, contract_address_const}; 3 | use yas_core::utils::utils::ContractAddressPartialOrd; 4 | 5 | #[test] 6 | #[available_gas(2000000)] 7 | fn test_contract_address_gt() { 8 | let a = contract_address_const::<1>(); 9 | let b = contract_address_const::<0>(); 10 | assert(a > b, 'a should be > b'); 11 | } 12 | 13 | #[test] 14 | #[available_gas(2000000)] 15 | fn test_contract_address_lt() { 16 | let a = contract_address_const::<0>(); 17 | let b = contract_address_const::<1>(); 18 | assert(a < b, 'a should be < b'); 19 | } 20 | 21 | #[test] 22 | #[available_gas(2000000)] 23 | fn test_contract_address_le() { 24 | let a = contract_address_const::<5>(); 25 | let b = contract_address_const::<5>(); 26 | assert(a <= b, 'a should be <= b'); 27 | assert(!(a < b), 'a should not be < b'); 28 | 29 | let b = contract_address_const::<6>(); 30 | assert(a <= b, 'a should be <= b'); 31 | assert(a < b, 'a should be < b'); 32 | } 33 | 34 | #[test] 35 | #[available_gas(2000000)] 36 | fn test_contract_address_ge() { 37 | let a = contract_address_const::<5>(); 38 | let b = contract_address_const::<5>(); 39 | assert(a >= b, 'a should be >= b'); 40 | assert(!(a > b), 'a should not be > b'); 41 | 42 | let a = contract_address_const::<6>(); 43 | assert(a >= b, 'a should be >= b'); 44 | assert(a > b, 'a should be > b'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/yas_core/src/tests/utils/constants.cairo: -------------------------------------------------------------------------------- 1 | mod FactoryConstants { 2 | use starknet::{ContractAddress, ClassHash, contract_address_const, class_hash_const}; 3 | use yas_core::contracts::yas_pool::{ 4 | YASPool, IYASPool, IYASPoolDispatcher, IYASPoolDispatcherTrait 5 | }; 6 | 7 | fn OWNER() -> ContractAddress { 8 | contract_address_const::<'OWNER'>() 9 | } 10 | 11 | fn OTHER() -> ContractAddress { 12 | contract_address_const::<'CALLER'>() 13 | } 14 | 15 | fn ZERO() -> ContractAddress { 16 | Zeroable::zero() 17 | } 18 | 19 | fn POOL_CLASS_HASH() -> ClassHash { 20 | YASPool::TEST_CLASS_HASH.try_into().unwrap() 21 | } 22 | 23 | fn TOKEN_A() -> ContractAddress { 24 | contract_address_const::<'A'>() 25 | } 26 | 27 | fn TOKEN_B() -> ContractAddress { 28 | contract_address_const::<'B'>() 29 | } 30 | 31 | enum FeeAmount { 32 | CUSTOM: (), 33 | LOW: (), 34 | MEDIUM: (), 35 | HIGH: () 36 | } 37 | 38 | fn fee_amount(fee_type: FeeAmount) -> u32 { 39 | match fee_type { 40 | FeeAmount::CUSTOM => 100, 41 | FeeAmount::LOW => 500, 42 | FeeAmount::MEDIUM => 3000, 43 | FeeAmount::HIGH => 10000, 44 | } 45 | } 46 | 47 | fn tick_spacing(fee_type: FeeAmount) -> u32 { 48 | match fee_type { 49 | FeeAmount::CUSTOM => 2, 50 | FeeAmount::LOW => 10, 51 | FeeAmount::MEDIUM => 60, 52 | FeeAmount::HIGH => 200, 53 | } 54 | } 55 | } 56 | 57 | mod PoolConstants { 58 | use starknet::{ContractAddress, contract_address_const}; 59 | 60 | use yas_core::contracts::yas_pool::{ 61 | YASPool, IYASPool, IYASPoolDispatcher, IYASPoolDispatcherTrait 62 | }; 63 | use yas_core::numbers::signed_integer::{ 64 | i32::i32, i32::i32_div_no_round, integer_trait::IntegerTrait 65 | }; 66 | use yas_core::numbers::fixed_point::implementations::impl_64x96::{ 67 | FP64x96Impl, FixedType, FixedTrait 68 | }; 69 | 70 | fn OWNER() -> ContractAddress { 71 | contract_address_const::<'OWNER'>() 72 | } 73 | 74 | fn WALLET() -> ContractAddress { 75 | contract_address_const::<'WALLET'>() 76 | } 77 | 78 | fn OTHER() -> ContractAddress { 79 | contract_address_const::<'OTHER'>() 80 | } 81 | 82 | fn POOL_ADDRESS() -> ContractAddress { 83 | contract_address_const::<'POOL_ADDRESS'>() 84 | } 85 | 86 | fn FACTORY_ADDRESS() -> ContractAddress { 87 | contract_address_const::<'FACTORY'>() 88 | } 89 | 90 | fn TOKEN_A() -> ContractAddress { 91 | contract_address_const::<'TOKEN_A'>() 92 | } 93 | 94 | fn TOKEN_B() -> ContractAddress { 95 | contract_address_const::<'TOKEN_B'>() 96 | } 97 | 98 | fn STATE() -> YASPool::ContractState { 99 | YASPool::contract_state_for_testing() 100 | } 101 | 102 | fn max_tick(tick_spacing: i32) -> i32 { 103 | let MAX_TICK = IntegerTrait::::new(887272, false); 104 | i32_div_no_round(MAX_TICK, tick_spacing) * tick_spacing 105 | } 106 | 107 | fn min_tick(tick_spacing: i32) -> i32 { 108 | let MIN_TICK = IntegerTrait::::new(887272, true); 109 | i32_div_no_round(MIN_TICK, tick_spacing) * tick_spacing 110 | } 111 | 112 | // Due to issues with the calculations, the implementation of encode_sqrt_price(a, b) 113 | // was removed in favor of using constant values. What we are interested in testing are the 114 | // methods of the SqrtPriceMath library. 115 | 116 | // returns result of encode_price_sqrt(1, 1) on v3-core typescript impl. 117 | fn encode_price_sqrt_1_1() -> FixedType { 118 | FP64x96Impl::new(79228162514264337593543950336, false) 119 | } 120 | 121 | // sqrt_price_X96 is the result of encode_price_sqrt(1, 2) on v3-core typescript impl. 122 | fn encode_price_sqrt_1_2() -> FixedType { 123 | FP64x96Impl::new(56022770974786139918731938227, false) 124 | } 125 | 126 | // returns result of encode_price_sqrt(1, 10) on v3-core typescript impl. 127 | fn encode_price_sqrt_1_10() -> FixedType { 128 | FP64x96Impl::new(25054144837504793118641380156, false) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /crates/yas_core/src/tests/utils/pool_1.cairo: -------------------------------------------------------------------------------- 1 | use yas_core::numbers::signed_integer::{ 2 | i32::i32, i32::i32_div_no_round, i256::i256, integer_trait::IntegerTrait 3 | }; 4 | use yas_core::tests::utils::swap_cases::SwapTestHelper::{ 5 | SwapExpectedResults, SwapTestCase, PoolTestCase, obtain_swap_cases, POOL_CASES 6 | }; 7 | 8 | fn SWAP_CASES_POOL_1() -> (Array, Array) { 9 | obtain_swap_cases(array![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) 10 | } 11 | 12 | fn SWAP_EXPECTED_RESULTS_POOL_1() -> Array { 13 | array![ 14 | SwapExpectedResults { 15 | amount_0_before: 2000000000000000000, 16 | amount_0_delta: IntegerTrait::::new(1000000000000000000, false), 17 | amount_1_before: 2000000000000000000, 18 | amount_1_delta: IntegerTrait::::new(665331998665331998, true), 19 | execution_price: 66533, 20 | fee_growth_global_0_X128_delta: 510423550381407695195061911147652317, 21 | fee_growth_global_1_X128_delta: 0, 22 | pool_price_after: 44533, 23 | pool_price_before: 100000, 24 | tick_after: IntegerTrait::::new(8090, true), 25 | tick_before: IntegerTrait::::new(0, false), 26 | }, 27 | SwapExpectedResults { 28 | amount_0_before: 2000000000000000000, 29 | amount_0_delta: IntegerTrait::::new(665331998665331998, true), 30 | amount_1_before: 2000000000000000000, 31 | amount_1_delta: IntegerTrait::::new(1000000000000000000, false), 32 | execution_price: 150300, 33 | fee_growth_global_0_X128_delta: 0, 34 | fee_growth_global_1_X128_delta: 510423550381407695195061911147652317, 35 | pool_price_after: 224550, 36 | pool_price_before: 100000, 37 | tick_after: IntegerTrait::::new(8089, false), 38 | tick_before: IntegerTrait::::new(0, false), 39 | }, 40 | SwapExpectedResults { 41 | amount_0_before: 2000000000000000000, 42 | amount_0_delta: IntegerTrait::::new(2006018054162487463, false), 43 | amount_1_before: 2000000000000000000, 44 | amount_1_delta: IntegerTrait::::new(1000000000000000000, true), 45 | execution_price: 49850, 46 | fee_growth_global_0_X128_delta: 1023918857334819954209013958517557896, 47 | fee_growth_global_1_X128_delta: 0, 48 | pool_price_after: 25000, 49 | pool_price_before: 100000, 50 | tick_after: IntegerTrait::::new(13864, true), 51 | tick_before: IntegerTrait::::new(0, false), 52 | }, 53 | SwapExpectedResults { 54 | amount_0_before: 2000000000000000000, 55 | amount_0_delta: IntegerTrait::::new(1000000000000000000, true), 56 | amount_1_before: 2000000000000000000, 57 | amount_1_delta: IntegerTrait::::new(2006018054162487463, false), 58 | execution_price: 200600, 59 | fee_growth_global_0_X128_delta: 0, 60 | fee_growth_global_1_X128_delta: 1023918857334819954209013958517557896, 61 | pool_price_after: 400000, 62 | pool_price_before: 100000, 63 | tick_after: IntegerTrait::::new(13863, false), 64 | tick_before: IntegerTrait::::new(0, false), 65 | }, 66 | SwapExpectedResults { 67 | amount_0_before: 2000000000000000000, 68 | amount_0_delta: IntegerTrait::::new(830919884399388263, false), 69 | amount_1_before: 2000000000000000000, 70 | amount_1_delta: IntegerTrait::::new(585786437626904951, true), 71 | execution_price: 70499, 72 | fee_growth_global_0_X128_delta: 424121077477644648929101317621422688, 73 | fee_growth_global_1_X128_delta: 0, 74 | pool_price_after: 50000, 75 | pool_price_before: 100000, 76 | tick_after: IntegerTrait::::new(6932, true), 77 | tick_before: IntegerTrait::::new(0, false), 78 | }, 79 | SwapExpectedResults { 80 | amount_0_before: 2000000000000000000, 81 | amount_0_delta: IntegerTrait::::new(585786437626904951, true), 82 | amount_1_before: 2000000000000000000, 83 | amount_1_delta: IntegerTrait::::new(830919884399388263, false), 84 | execution_price: 141850, 85 | fee_growth_global_0_X128_delta: 0, 86 | fee_growth_global_1_X128_delta: 424121077477644648929101317621422688, 87 | pool_price_after: 200000, 88 | pool_price_before: 100000, 89 | tick_after: IntegerTrait::::new(6931, false), 90 | tick_before: IntegerTrait::::new(0, false), 91 | }, 92 | SwapExpectedResults { 93 | amount_0_before: 2000000000000000000, 94 | amount_0_delta: IntegerTrait::::new(830919884399388263, false), 95 | amount_1_before: 2000000000000000000, 96 | amount_1_delta: IntegerTrait::::new(585786437626904951, true), 97 | execution_price: 70499, 98 | fee_growth_global_0_X128_delta: 424121077477644648929101317621422688, 99 | fee_growth_global_1_X128_delta: 0, 100 | pool_price_after: 50000, 101 | pool_price_before: 100000, 102 | tick_after: IntegerTrait::::new(6932, true), 103 | tick_before: IntegerTrait::::new(0, false), 104 | }, 105 | SwapExpectedResults { 106 | amount_0_before: 2000000000000000000, 107 | amount_0_delta: IntegerTrait::::new(585786437626904951, true), 108 | amount_1_before: 2000000000000000000, 109 | amount_1_delta: IntegerTrait::::new(830919884399388263, false), 110 | execution_price: 141850, 111 | fee_growth_global_0_X128_delta: 0, 112 | fee_growth_global_1_X128_delta: 424121077477644648929101317621422688, 113 | pool_price_after: 200000, 114 | pool_price_before: 100000, 115 | tick_after: IntegerTrait::::new(6931, false), 116 | tick_before: IntegerTrait::::new(0, false), 117 | }, 118 | SwapExpectedResults { 119 | amount_0_before: 2000000000000000000, 120 | amount_0_delta: IntegerTrait::::new(1000, false), 121 | amount_1_before: 2000000000000000000, 122 | amount_1_delta: IntegerTrait::::new(996, true), 123 | execution_price: 99600, 124 | fee_growth_global_0_X128_delta: 510423550381407695195, 125 | fee_growth_global_1_X128_delta: 0, 126 | pool_price_after: 100000, 127 | pool_price_before: 100000, 128 | tick_after: IntegerTrait::::new(1, true), 129 | tick_before: IntegerTrait::::new(0, false), 130 | }, 131 | SwapExpectedResults { 132 | amount_0_before: 2000000000000000000, 133 | amount_0_delta: IntegerTrait::::new(996, true), 134 | amount_1_before: 2000000000000000000, 135 | amount_1_delta: IntegerTrait::::new(1000, false), 136 | execution_price: 100400, 137 | fee_growth_global_0_X128_delta: 0, 138 | fee_growth_global_1_X128_delta: 510423550381407695195, 139 | pool_price_after: 100000, 140 | pool_price_before: 100000, 141 | tick_after: IntegerTrait::::new(0, false), 142 | tick_before: IntegerTrait::::new(0, false), 143 | }, 144 | SwapExpectedResults { 145 | amount_0_before: 2000000000000000000, 146 | amount_0_delta: IntegerTrait::::new(1005, false), 147 | amount_1_before: 2000000000000000000, 148 | amount_1_delta: IntegerTrait::::new(1000, true), 149 | execution_price: 99502, 150 | fee_growth_global_0_X128_delta: 680564733841876926926, 151 | fee_growth_global_1_X128_delta: 0, 152 | pool_price_after: 100000, 153 | pool_price_before: 100000, 154 | tick_after: IntegerTrait::::new(1, true), 155 | tick_before: IntegerTrait::::new(0, false), 156 | }, 157 | SwapExpectedResults { 158 | amount_0_before: 2000000000000000000, 159 | amount_0_delta: IntegerTrait::::new(1000, true), 160 | amount_1_before: 2000000000000000000, 161 | amount_1_delta: IntegerTrait::::new(1005, false), 162 | execution_price: 100500, 163 | fee_growth_global_0_X128_delta: 0, 164 | fee_growth_global_1_X128_delta: 680564733841876926926, 165 | pool_price_after: 100000, 166 | pool_price_before: 100000, 167 | tick_after: IntegerTrait::::new(0, false), 168 | tick_before: IntegerTrait::::new(0, false), 169 | }, 170 | SwapExpectedResults { 171 | amount_0_before: 2000000000000000000, 172 | amount_0_delta: IntegerTrait::::new(735088935932648267, true), 173 | amount_1_before: 2000000000000000000, 174 | amount_1_delta: IntegerTrait::::new(1165774985123750584, false), 175 | execution_price: 158590, 176 | fee_growth_global_0_X128_delta: 0, 177 | fee_growth_global_1_X128_delta: 595039006852697554786973994761078087, 178 | pool_price_after: 250000, 179 | pool_price_before: 100000, 180 | tick_after: IntegerTrait::::new(9163, false), 181 | tick_before: IntegerTrait::::new(0, false), 182 | }, 183 | SwapExpectedResults { 184 | amount_0_before: 2000000000000000000, 185 | amount_0_delta: IntegerTrait::::new(1165774985123750584, false), 186 | amount_1_before: 2000000000000000000, 187 | amount_1_delta: IntegerTrait::::new(735088935932648267, true), 188 | execution_price: 63056, 189 | fee_growth_global_0_X128_delta: 595039006852697554786973994761078087, 190 | fee_growth_global_1_X128_delta: 0, 191 | pool_price_after: 40000, 192 | pool_price_before: 100000, 193 | tick_after: IntegerTrait::::new(9164, true), 194 | tick_before: IntegerTrait::::new(0, false), 195 | } 196 | ] 197 | } 198 | -------------------------------------------------------------------------------- /crates/yas_core/src/utils/math_utils.cairo: -------------------------------------------------------------------------------- 1 | use integer::BoundedInt; 2 | 3 | mod Constants { 4 | const Q128: u256 = 0x100000000000000000000000000000000; 5 | } 6 | 7 | mod FullMath { 8 | use integer::{ 9 | BoundedInt, u256_wide_mul, u256_safe_divmod, u512_safe_div_rem_by_u256, u256_try_as_non_zero 10 | }; 11 | 12 | // Multiplies two u256 numbers and divides the result by a third. 13 | // Credits to sphinx-protocol 14 | // https://github.com/sphinx-protocol/types252/blob/c5d209fe2b4c2cb2a21f9ad463de13d2c5dffa46/src/math/math.cairo#L37 15 | // # Arguments 16 | // * `a` - The multiplicand 17 | // * `b` - The multiplier 18 | // * `denominator` - The divisor. 19 | // 20 | // # Returns 21 | // * `result` - The 256-bit result 22 | fn mul_div(a: u256, b: u256, denominator: u256) -> u256 { 23 | let product = u256_wide_mul(a, b); 24 | let (q, _) = u512_safe_div_rem_by_u256( 25 | product, u256_try_as_non_zero(denominator).expect('mul_div by zero') 26 | ); 27 | assert(q.limb2 == 0 && q.limb3 == 0, 'mul_div u256 overflow'); 28 | u256 { low: q.limb0, high: q.limb1 } 29 | } 30 | 31 | // Calculates ceil(a×b÷denominator). Throws if result overflows a uint256 or denominator == 0 32 | // # Arguments 33 | // * `a` - The multiplicand 34 | // * `b` - The multiplier 35 | // * `denominator` - The divisor. 36 | // 37 | // # Returns 38 | // * `result` - The 256-bit result 39 | fn mul_div_rounding_up(a: u256, b: u256, denominator: u256) -> u256 { 40 | let result: u256 = mul_div(a, b, denominator); 41 | let max_u256: u256 = BoundedInt::max(); 42 | if (mul_mod_n(a, b, denominator) > 0) { 43 | assert(result < max_u256, 'mul_div_rounding_up overflow'); 44 | result + 1 45 | } else { 46 | result 47 | } 48 | } 49 | 50 | fn mul_mod_n(a: u256, b: u256, n: u256) -> u256 { 51 | let (_, r) = u512_safe_div_rem_by_u256( 52 | u256_wide_mul(a, b), u256_try_as_non_zero(n).expect('mul_mod_n by zero') 53 | ); 54 | r 55 | } 56 | 57 | fn div_rounding_up(a: u256, denominator: u256) -> u256 { 58 | let (quotient, remainder, _) = u256_safe_divmod( 59 | a, u256_try_as_non_zero(denominator).expect('div_rounding_up by zero') 60 | ); 61 | if remainder != 0 { 62 | quotient + 1 63 | } else { 64 | quotient 65 | } 66 | } 67 | } 68 | 69 | mod BitShift { 70 | use super::pow; 71 | use integer::BoundedInt; 72 | 73 | use yas_core::numbers::signed_integer::{ 74 | i32::{i32, ensure_non_negative_zero, i32_check_sign_zero}, i256::i256, 75 | integer_trait::IntegerTrait 76 | }; 77 | 78 | trait BitShiftTrait { 79 | fn shl(self: @T, n: T) -> T; 80 | fn shr(self: @T, n: T) -> T; 81 | } 82 | 83 | impl U256BitShift of BitShiftTrait { 84 | #[inline(always)] 85 | fn shl(self: @u256, n: u256) -> u256 { 86 | *self * pow(2, n) 87 | } 88 | 89 | #[inline(always)] 90 | fn shr(self: @u256, n: u256) -> u256 { 91 | *self / pow(2, n) 92 | } 93 | } 94 | 95 | impl I256BitShift of BitShiftTrait { 96 | #[inline(always)] 97 | fn shl(self: @i256, n: i256) -> i256 { 98 | let mut new_mag = self.mag.shl(n.mag); 99 | if *self.sign && n.mag == 128 { 100 | new_mag += 1_u256; 101 | }; 102 | // Left shift operation: mag << n 103 | if *self.sign { 104 | new_mag = new_mag & BoundedInt::::max() / 2; 105 | } else { 106 | new_mag = new_mag & ((BoundedInt::::max() / 2) - 1); 107 | }; 108 | 109 | IntegerTrait::::new(new_mag, *self.sign) 110 | } 111 | 112 | #[inline(always)] 113 | fn shr(self: @i256, n: i256) -> i256 { 114 | let mut new_mag = self.mag.shr(n.mag); 115 | let mut new_sign = *self.sign; 116 | if *self.sign && n.mag == 128 { 117 | new_mag += 1_u256; 118 | }; 119 | if new_mag == 0 { 120 | if *self.sign { 121 | new_sign = true; 122 | new_mag = 1; 123 | } else { 124 | new_sign == false; 125 | }; 126 | }; 127 | // Right shift operation: mag >> n 128 | IntegerTrait::::new(new_mag, new_sign) 129 | } 130 | } 131 | 132 | impl U32BitShift of BitShiftTrait { 133 | #[inline(always)] 134 | fn shl(self: @u32, n: u32) -> u32 { 135 | *self * pow(2, n.into()).try_into().unwrap() 136 | } 137 | 138 | #[inline(always)] 139 | fn shr(self: @u32, n: u32) -> u32 { 140 | *self / pow(2, n.into()).try_into().unwrap() 141 | } 142 | } 143 | 144 | impl U8BitShift of BitShiftTrait { 145 | #[inline(always)] 146 | fn shl(self: @u8, n: u8) -> u8 { 147 | *self * pow(2, n.into()).try_into().unwrap() 148 | } 149 | 150 | #[inline(always)] 151 | fn shr(self: @u8, n: u8) -> u8 { 152 | *self / pow(2, n.into()).try_into().unwrap() 153 | } 154 | } 155 | } 156 | 157 | /// Raise a number to a power. 158 | /// * `base` - The number to raise. 159 | /// * `exp` - The exponent. 160 | /// # Returns 161 | /// * `u256` - The result of base raised to the power of exp. 162 | fn pow(base: u256, exp: u256) -> u256 { 163 | if exp == 0 { 164 | 1 165 | } else if exp == 1 { 166 | base 167 | } else if (exp & 1) == 1 { 168 | base * pow(base * base, exp / 2) 169 | } else { 170 | pow(base * base, exp / 2) 171 | } 172 | } 173 | 174 | /// @notice Performs modular subtraction of two unsigned 256-bit integers, a and b. 175 | /// @param a The first operand for subtraction. 176 | /// @param b The second operand for subtraction. 177 | /// @return The result of (a - b) modulo 2^256. 178 | fn mod_subtraction(a: u256, b: u256) -> u256 { 179 | if b > a { 180 | (BoundedInt::max() - b) + a + 1 181 | } else { 182 | a - b 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /crates/yas_core/src/utils/utils.cairo: -------------------------------------------------------------------------------- 1 | use starknet::{ContractAddress, contract_address_to_felt252}; 2 | use yas_core::contracts::yas_pool::YASPool::Slot0; 3 | use yas_core::numbers::fixed_point::implementations::impl_64x96::{FP64x96PartialEq}; 4 | 5 | impl ContractAddressPartialOrd of PartialOrd { 6 | #[inline(always)] 7 | fn le(lhs: ContractAddress, rhs: ContractAddress) -> bool { 8 | let lhs_u256: u256 = lhs.into(); 9 | let rhs_u256: u256 = rhs.into(); 10 | lhs_u256 <= rhs_u256 11 | } 12 | #[inline(always)] 13 | fn ge(lhs: ContractAddress, rhs: ContractAddress) -> bool { 14 | let lhs_u256: u256 = lhs.into(); 15 | let rhs_u256: u256 = rhs.into(); 16 | rhs_u256 <= lhs_u256 17 | } 18 | #[inline(always)] 19 | fn lt(lhs: ContractAddress, rhs: ContractAddress) -> bool { 20 | let lhs_u256: u256 = lhs.into(); 21 | let rhs_u256: u256 = rhs.into(); 22 | lhs_u256 < rhs_u256 23 | } 24 | #[inline(always)] 25 | fn gt(lhs: ContractAddress, rhs: ContractAddress) -> bool { 26 | let lhs_u256: u256 = lhs.into(); 27 | let rhs_u256: u256 = rhs.into(); 28 | rhs_u256 < lhs_u256 29 | } 30 | } 31 | 32 | impl ContractAddressIntoU256 of Into { 33 | fn into(self: ContractAddress) -> u256 { 34 | contract_address_to_felt252(self).into() 35 | } 36 | } 37 | 38 | impl Slot0PartialEq of PartialEq { 39 | #[inline(always)] 40 | fn eq(lhs: @Slot0, rhs: @Slot0) -> bool { 41 | (*lhs.sqrt_price_X96) == (*rhs.sqrt_price_X96) 42 | && (*lhs.tick) == (*rhs.tick) 43 | && lhs.fee_protocol == rhs.fee_protocol 44 | } 45 | #[inline(always)] 46 | fn ne(lhs: @Slot0, rhs: @Slot0) -> bool { 47 | !(lhs == rhs) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/yas_faucet/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /crates/yas_faucet/Scarb.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas_faucet" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | starknet.workspace = true 7 | openzeppelin.workspace = true 8 | yas_core = { path = "../yas_core" } 9 | 10 | [[target.starknet-contract]] 11 | sierra = true 12 | 13 | [cairo] 14 | sierra-replace-ids = true 15 | -------------------------------------------------------------------------------- /crates/yas_faucet/src/lib.cairo: -------------------------------------------------------------------------------- 1 | mod yas_faucet; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | mod test_yas_faucet; 6 | } 7 | -------------------------------------------------------------------------------- /crates/yas_faucet/src/tests/test_yas_faucet.cairo: -------------------------------------------------------------------------------- 1 | mod YASFaucet { 2 | use starknet::{ContractAddress, ClassHash, SyscallResultTrait, contract_address_const}; 3 | use starknet::syscalls::deploy_syscall; 4 | use starknet::testing::{set_contract_address, set_caller_address, set_block_timestamp}; 5 | 6 | use yas_core::contracts::yas_erc20::{ERC20, IERC20Dispatcher, IERC20DispatcherTrait}; 7 | use yas_faucet::yas_faucet::{YASFaucet, IYASFaucetDispatcher, IYASFaucetDispatcherTrait}; 8 | use yas_core::tests::utils::constants::PoolConstants::{OTHER, OWNER, WALLET}; 9 | 10 | fn setup() -> (IYASFaucetDispatcher, IERC20Dispatcher) { 11 | let yas_token = deploy_erc20('YAS', '$YAS', 4000000000000000000, OWNER()); 12 | let yas_faucet = deploy_yas_faucet(OWNER(), yas_token.contract_address, 1000, 86400); 13 | set_contract_address(OWNER()); 14 | yas_token.transfer(yas_faucet.contract_address, 4000000000000000000); 15 | (yas_faucet, yas_token) 16 | } 17 | 18 | fn deploy_yas_faucet( 19 | owner: ContractAddress, 20 | token_address: ContractAddress, 21 | withdrawal_amount: u256, 22 | wait_time: u64 23 | ) -> IYASFaucetDispatcher { 24 | let mut calldata: Array = ArrayTrait::new(); 25 | calldata.append(owner.into()); 26 | calldata.append(token_address.into()); 27 | Serde::serialize(@withdrawal_amount, ref calldata); 28 | calldata.append(wait_time.into()); 29 | 30 | let (address, _) = deploy_syscall( 31 | YASFaucet::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), true 32 | ) 33 | .unwrap_syscall(); 34 | 35 | return IYASFaucetDispatcher { contract_address: address }; 36 | } 37 | 38 | fn deploy_erc20( 39 | name: felt252, symbol: felt252, initial_supply: u256, recipent: ContractAddress 40 | ) -> IERC20Dispatcher { 41 | let mut calldata = array![name, symbol]; 42 | Serde::serialize(@initial_supply, ref calldata); 43 | calldata.append(recipent.into()); 44 | 45 | let (address, _) = deploy_syscall( 46 | ERC20::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), true 47 | ) 48 | .unwrap_syscall(); 49 | 50 | return IERC20Dispatcher { contract_address: address }; 51 | } 52 | 53 | #[test] 54 | #[available_gas(200000000)] 55 | fn test_happy_path() { 56 | let (yas_faucet, yas_erc_20) = setup(); 57 | 58 | assert(yas_erc_20.balanceOf(WALLET()) == 0, 'wrong balance'); 59 | 60 | set_contract_address(WALLET()); 61 | yas_faucet.faucet_mint(); 62 | 63 | assert(yas_erc_20.balanceOf(WALLET()) == 1000, 'wrong balance'); 64 | } 65 | 66 | #[test] 67 | #[available_gas(200000000)] 68 | fn test_double_faucet_mint() { 69 | let (yas_faucet, yas_erc_20) = setup(); 70 | 71 | assert(yas_erc_20.balanceOf(WALLET()) == 0, 'wrong balance'); 72 | 73 | set_contract_address(WALLET()); 74 | yas_faucet.faucet_mint(); 75 | assert(yas_erc_20.balanceOf(WALLET()) == 1000, 'wrong balance'); 76 | 77 | set_block_timestamp(86400 + 1); 78 | 79 | yas_faucet.faucet_mint(); 80 | assert(yas_erc_20.balanceOf(WALLET()) == 2000, 'wrong balance'); 81 | } 82 | 83 | #[test] 84 | #[available_gas(200000000)] 85 | fn test_withdraw_all_balance() { 86 | let (yas_faucet, yas_erc_20) = setup(); 87 | 88 | assert(yas_erc_20.balanceOf(OTHER()) == 0, 'wrong balance'); 89 | assert( 90 | yas_erc_20.balanceOf(yas_faucet.contract_address) == 4000000000000000000, 91 | 'wrong balance' 92 | ); 93 | 94 | set_contract_address(OWNER()); 95 | yas_faucet.withdraw_all_balance(OTHER()); 96 | 97 | assert(yas_erc_20.balanceOf(OTHER()) == 4000000000000000000, 'wrong balance'); 98 | assert(yas_erc_20.balanceOf(yas_faucet.contract_address) == 0, 'wrong balance'); 99 | } 100 | 101 | #[test] 102 | #[available_gas(200000000)] 103 | fn test_faucet_constructor() { 104 | let (yas_faucet, yas_erc_20) = setup(); 105 | assert(yas_faucet.get_amount_faucet() == 4000000000000000000, 'wrong amount_faucet'); 106 | assert( 107 | yas_faucet.get_token_address() == yas_erc_20.contract_address, 'wrong token_address' 108 | ); 109 | assert(yas_faucet.get_withdrawal_amount() == 1000, 'wrong withdrawal_amount'); 110 | assert(yas_faucet.get_wait_time() == 86400, 'wrong wait_time'); 111 | } 112 | 113 | #[test] 114 | #[available_gas(200000000)] 115 | #[should_panic(expected: ('Not allowed to withdraw', 'ENTRYPOINT_FAILED'))] 116 | fn test_withdrawal_not_allowed_panic() { 117 | let (yas_faucet, yas_erc_20) = setup(); 118 | 119 | assert(yas_erc_20.balanceOf(WALLET()) == 0, 'wrong balance'); 120 | 121 | set_contract_address(WALLET()); 122 | yas_faucet.faucet_mint(); 123 | assert(yas_erc_20.balanceOf(WALLET()) == 1000, 'wrong balance'); 124 | 125 | set_block_timestamp(86400); 126 | 127 | yas_faucet.faucet_mint(); 128 | assert(yas_erc_20.balanceOf(WALLET()) == 2000, 'wrong balance'); 129 | } 130 | 131 | #[test] 132 | #[available_gas(200000000)] 133 | #[should_panic(expected: ('There is not enough balance', 'ENTRYPOINT_FAILED'))] 134 | fn test_insufficient_balance_panic() { 135 | let yas_token = deploy_erc20('YAS', '$YAS', 4000000000000000000, OWNER()); 136 | let yas_faucet = deploy_yas_faucet(OWNER(), yas_token.contract_address, 1000, 86400); 137 | set_contract_address(WALLET()); 138 | yas_faucet.faucet_mint(); 139 | } 140 | 141 | #[test] 142 | #[available_gas(200000000)] 143 | #[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] 144 | fn test_owner_withdrawal_panic() { 145 | let (yas_faucet, yas_erc_20) = setup(); 146 | 147 | assert(yas_erc_20.balanceOf(OTHER()) == 0, 'wrong balance'); 148 | assert( 149 | yas_erc_20.balanceOf(yas_faucet.contract_address) == 4000000000000000000, 150 | 'wrong balance' 151 | ); 152 | 153 | set_contract_address(WALLET()); 154 | yas_faucet.withdraw_all_balance(OTHER()); 155 | 156 | assert(yas_erc_20.balanceOf(OTHER()) == 4000000000000000000, 'wrong balance'); 157 | assert(yas_erc_20.balanceOf(yas_faucet.contract_address) == 0, 'wrong balance'); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /crates/yas_faucet/src/yas_faucet.cairo: -------------------------------------------------------------------------------- 1 | use starknet::ContractAddress; 2 | 3 | #[starknet::interface] 4 | trait IYASFaucet { 5 | fn faucet_mint(ref self: TContractState); 6 | fn withdraw_all_balance(ref self: TContractState, user: ContractAddress); 7 | fn get_user_unlock_time(self: @TContractState, user: ContractAddress) -> u64; 8 | fn get_amount_faucet(self: @TContractState) -> u256; 9 | fn get_token_address(self: @TContractState) -> ContractAddress; 10 | fn get_withdrawal_amount(self: @TContractState) -> u256; 11 | fn get_wait_time(self: @TContractState) -> u64; 12 | fn set_withdrawal_amount(ref self: TContractState, amount: u256); 13 | fn set_wait_time(ref self: TContractState, wait_time: u64); 14 | } 15 | 16 | #[starknet::contract] 17 | mod YASFaucet { 18 | use super::IYASFaucet; 19 | 20 | use starknet::{ContractAddress, get_block_timestamp, get_caller_address, get_contract_address}; 21 | use openzeppelin::access::ownable::Ownable; 22 | use yas_core::interfaces::interface_ERC20::{IERC20DispatcherTrait, IERC20Dispatcher}; 23 | 24 | #[storage] 25 | struct Storage { 26 | user_unlock_time: LegacyMap, 27 | token_address: ContractAddress, 28 | withdrawal_amount: u256, 29 | wait_time: u64, 30 | } 31 | 32 | #[constructor] 33 | fn constructor( 34 | ref self: ContractState, 35 | owner: ContractAddress, 36 | token_address: ContractAddress, 37 | withdrawal_amount: u256, 38 | wait_time: u64 39 | ) { 40 | let mut unsafe_state = Ownable::unsafe_new_contract_state(); 41 | Ownable::InternalImpl::initializer(ref unsafe_state, owner); 42 | self.token_address.write(token_address); 43 | self.withdrawal_amount.write(withdrawal_amount); 44 | self.wait_time.write(wait_time); 45 | } 46 | 47 | #[external(v0)] 48 | impl YASFaucetImpl of IYASFaucet { 49 | fn faucet_mint(ref self: ContractState) { 50 | let withdrawal_amount = self.withdrawal_amount.read(); 51 | assert(self.get_amount_faucet() > withdrawal_amount, 'There is not enough balance'); 52 | let caller_address = get_caller_address(); 53 | assert(self.allowed_to_withdraw(caller_address), 'Not allowed to withdraw'); 54 | self 55 | .user_unlock_time 56 | .write(caller_address, get_block_timestamp() + self.wait_time.read()); 57 | IERC20Dispatcher { contract_address: self.token_address.read() } 58 | .transfer(caller_address, withdrawal_amount); 59 | } 60 | 61 | fn withdraw_all_balance(ref self: ContractState, user: ContractAddress) { 62 | let unsafe_state = Ownable::unsafe_new_contract_state(); 63 | Ownable::InternalImpl::assert_only_owner(@unsafe_state); 64 | let balance = self.get_amount_faucet(); 65 | IERC20Dispatcher { contract_address: self.token_address.read() } 66 | .transfer(user, balance); 67 | } 68 | 69 | fn get_amount_faucet(self: @ContractState) -> u256 { 70 | IERC20Dispatcher { contract_address: self.token_address.read() } 71 | .balanceOf(get_contract_address()) 72 | } 73 | 74 | fn get_user_unlock_time(self: @ContractState, user: ContractAddress) -> u64 { 75 | self.user_unlock_time.read(user) 76 | } 77 | 78 | fn get_token_address(self: @ContractState) -> ContractAddress { 79 | self.token_address.read() 80 | } 81 | 82 | fn get_withdrawal_amount(self: @ContractState) -> u256 { 83 | self.withdrawal_amount.read() 84 | } 85 | 86 | fn get_wait_time(self: @ContractState) -> u64 { 87 | self.wait_time.read() 88 | } 89 | 90 | fn set_withdrawal_amount(ref self: ContractState, amount: u256) { 91 | let unsafe_state = Ownable::unsafe_new_contract_state(); 92 | Ownable::InternalImpl::assert_only_owner(@unsafe_state); 93 | self.withdrawal_amount.write(amount); 94 | } 95 | 96 | fn set_wait_time(ref self: ContractState, wait_time: u64) { 97 | let unsafe_state = Ownable::unsafe_new_contract_state(); 98 | Ownable::InternalImpl::assert_only_owner(@unsafe_state); 99 | self.wait_time.write(wait_time); 100 | } 101 | } 102 | 103 | #[generate_trait] 104 | impl InternalImpl of InternalTrait { 105 | fn allowed_to_withdraw(self: @ContractState, user: ContractAddress) -> bool { 106 | let unlock_time = self.user_unlock_time.read(user); 107 | if unlock_time == 0 { 108 | return true; 109 | } 110 | let timestamp = get_block_timestamp(); 111 | if unlock_time < timestamp { 112 | true 113 | } else { 114 | false 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /crates/yas_periphery/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /crates/yas_periphery/Scarb.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas_periphery" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | starknet.workspace = true 7 | openzeppelin.workspace = true 8 | yas_core = { path = "../yas_core" } 9 | 10 | [[target.starknet-contract]] 11 | sierra = true 12 | 13 | [cairo] 14 | sierra-replace-ids = true 15 | -------------------------------------------------------------------------------- /crates/yas_periphery/src/lib.cairo: -------------------------------------------------------------------------------- 1 | mod yas_nft_position_manager; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | mod test_yas_nft_position_manager; 6 | } 7 | -------------------------------------------------------------------------------- /crates/yas_periphery/src/tests/test_yas_nft_position_manager.cairo: -------------------------------------------------------------------------------- 1 | mod YASNFTPositionManagerTests { 2 | use starknet::{ClassHash, ContractAddress, SyscallResultTrait}; 3 | use starknet::syscalls::deploy_syscall; 4 | use starknet::testing::{set_contract_address}; 5 | use integer::BoundedInt; 6 | 7 | use yas_core::contracts::yas_erc20::{ 8 | ERC20, ERC20::ERC20Impl, IERC20Dispatcher, IERC20DispatcherTrait 9 | }; 10 | use yas_core::contracts::yas_factory::{ 11 | YASFactory, IYASFactory, IYASFactoryDispatcher, IYASFactoryDispatcherTrait 12 | }; 13 | use yas_core::libraries::tick_math::{TickMath::MIN_TICK, TickMath::MAX_TICK}; 14 | use yas_core::numbers::fixed_point::implementations::impl_64x96::{ 15 | FP64x96Impl, FixedType, FixedTrait 16 | }; 17 | use yas_core::numbers::signed_integer::{ 18 | i32::i32, i32::i32_div_no_round, integer_trait::IntegerTrait 19 | }; 20 | use yas_core::tests::utils::constants::FactoryConstants::{ 21 | POOL_CLASS_HASH, FeeAmount, fee_amount, tick_spacing, OWNER 22 | }; 23 | use yas_core::tests::utils::constants::PoolConstants::{ 24 | TOKEN_A, TOKEN_B, POOL_ADDRESS, WALLET, encode_price_sqrt_1_1 25 | }; 26 | 27 | use yas_periphery::yas_nft_position_manager::{ 28 | YASNFTPositionManager, IYASNFTPositionManager, IYASNFTPositionManagerDispatcher, 29 | IYASNFTPositionManagerDispatcherTrait, MintParams 30 | }; 31 | 32 | fn setup() -> (IYASNFTPositionManagerDispatcher, IERC20Dispatcher, IERC20Dispatcher) { 33 | let yas_factory = deploy_factory(OWNER(), POOL_CLASS_HASH()); // 0x1 34 | let nft_position_manager = deploy_nft_position_manager(yas_factory.contract_address); // 0x2 35 | 36 | // Deploy ERC20 tokens with factory address 37 | let token_0 = deploy_erc20('YAS0', '$YAS0', BoundedInt::max(), OWNER()); // 0x3 38 | let token_1 = deploy_erc20('YAS1', '$YAS1', BoundedInt::max(), OWNER()); // 0x4 39 | 40 | set_contract_address(OWNER()); 41 | token_0.transfer(WALLET(), BoundedInt::max()); 42 | token_1.transfer(WALLET(), BoundedInt::max()); 43 | 44 | // Give permissions to expend WALLET() tokens 45 | set_contract_address(WALLET()); 46 | token_1.approve(nft_position_manager.contract_address, BoundedInt::max()); 47 | token_0.approve(nft_position_manager.contract_address, BoundedInt::max()); 48 | 49 | (nft_position_manager, token_0, token_1) 50 | } 51 | 52 | fn deploy_factory( 53 | deployer: ContractAddress, pool_class_hash: ClassHash 54 | ) -> IYASFactoryDispatcher { 55 | let (address, _) = deploy_syscall( 56 | YASFactory::TEST_CLASS_HASH.try_into().unwrap(), 57 | 0, 58 | array![deployer.into(), pool_class_hash.into()].span(), 59 | true 60 | ) 61 | .unwrap_syscall(); 62 | 63 | return IYASFactoryDispatcher { contract_address: address }; 64 | } 65 | 66 | fn deploy_erc20( 67 | name: felt252, symbol: felt252, initial_supply: u256, recipent: ContractAddress 68 | ) -> IERC20Dispatcher { 69 | let mut calldata = array![name, symbol]; 70 | Serde::serialize(@initial_supply, ref calldata); 71 | calldata.append(recipent.into()); 72 | 73 | let (address, _) = deploy_syscall( 74 | ERC20::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), true 75 | ) 76 | .unwrap_syscall(); 77 | 78 | return IERC20Dispatcher { contract_address: address }; 79 | } 80 | 81 | fn deploy_nft_position_manager(factory: ContractAddress) -> IYASNFTPositionManagerDispatcher { 82 | let (address, _) = deploy_syscall( 83 | YASNFTPositionManager::TEST_CLASS_HASH.try_into().unwrap(), 84 | 0, 85 | array![factory.into()].span(), 86 | true 87 | ) 88 | .unwrap_syscall(); 89 | 90 | return IYASNFTPositionManagerDispatcher { contract_address: address }; 91 | } 92 | 93 | fn get_min_tick_and_max_tick() -> (i32, i32) { 94 | let tick_spacing = IntegerTrait::::new(tick_spacing(FeeAmount::MEDIUM), false); 95 | let min_tick = i32_div_no_round(MIN_TICK(), tick_spacing) * tick_spacing; 96 | let max_tick = i32_div_no_round(MAX_TICK(), tick_spacing) * tick_spacing; 97 | (min_tick, max_tick) 98 | } 99 | 100 | mod Mint { 101 | use super::{setup, get_min_tick_and_max_tick}; 102 | use starknet::testing::{set_contract_address}; 103 | 104 | use yas_core::contracts::yas_erc20::{ 105 | ERC20, ERC20::ERC20Impl, IERC20Dispatcher, IERC20DispatcherTrait 106 | }; 107 | use yas_core::numbers::fixed_point::implementations::impl_64x96::{ 108 | FP64x96Impl, FixedType, FixedTrait 109 | }; 110 | use yas_core::tests::utils::constants::FactoryConstants::{ 111 | POOL_CLASS_HASH, FeeAmount, fee_amount, tick_spacing 112 | }; 113 | use yas_core::tests::utils::constants::PoolConstants::{ 114 | TOKEN_A, TOKEN_B, WALLET, OTHER, encode_price_sqrt_1_1 115 | }; 116 | 117 | use yas_periphery::yas_nft_position_manager::{ 118 | YASNFTPositionManager, IYASNFTPositionManager, IYASNFTPositionManagerDispatcher, 119 | IYASNFTPositionManagerDispatcherTrait, MintParams, Position, PoolKey 120 | }; 121 | 122 | #[test] 123 | #[available_gas(200000000)] 124 | #[should_panic(expected: ('CONTRACT_NOT_DEPLOYED', 'ENTRYPOINT_FAILED'))] 125 | fn test_fails_if_pool_does_not_exist() { 126 | let (yas_nft_position_manager, token_0, token_1) = setup(); 127 | 128 | let (min_tick, max_tick) = get_min_tick_and_max_tick(); 129 | 130 | yas_nft_position_manager 131 | .mint( 132 | MintParams { 133 | token_0: token_0.contract_address, 134 | token_1: token_1.contract_address, 135 | fee: fee_amount(FeeAmount::MEDIUM), 136 | recipient: WALLET(), 137 | tick_lower: min_tick, 138 | tick_upper: max_tick, 139 | amount_0_desired: 100, 140 | amount_1_desired: 100, 141 | amount_0_min: 0, 142 | amount_1_min: 0, 143 | deadline: 1 144 | } 145 | ); 146 | } 147 | 148 | #[test] 149 | #[available_gas(200000000)] 150 | #[should_panic( 151 | expected: ( 152 | 'u256_sub Overflow', 153 | 'ENTRYPOINT_FAILED', 154 | 'ENTRYPOINT_FAILED', 155 | 'ENTRYPOINT_FAILED', 156 | 'ENTRYPOINT_FAILED' 157 | ) 158 | )] 159 | fn test_fails_if_cannot_transfer() { 160 | let (yas_nft_position_manager, token_0, token_1) = setup(); 161 | 162 | let sqrt_price_X96 = encode_price_sqrt_1_1(); 163 | 164 | yas_nft_position_manager 165 | .create_and_initialize_pool_if_necessary( 166 | token_0.contract_address, 167 | token_1.contract_address, 168 | fee_amount(FeeAmount::MEDIUM), 169 | sqrt_price_X96 170 | ); 171 | 172 | set_contract_address(WALLET()); 173 | token_0.approve(yas_nft_position_manager.contract_address, 0); 174 | 175 | let (min_tick, max_tick) = get_min_tick_and_max_tick(); 176 | yas_nft_position_manager 177 | .mint( 178 | MintParams { 179 | token_0: token_0.contract_address, 180 | token_1: token_1.contract_address, 181 | fee: fee_amount(FeeAmount::MEDIUM), 182 | recipient: WALLET(), 183 | tick_lower: min_tick, 184 | tick_upper: max_tick, 185 | amount_0_desired: 100, 186 | amount_1_desired: 100, 187 | amount_0_min: 0, 188 | amount_1_min: 0, 189 | deadline: 1 190 | } 191 | ); 192 | } 193 | 194 | #[test] 195 | #[available_gas(200000000)] 196 | fn test_creates_a_token() { 197 | let (yas_nft_position_manager, token_0, token_1) = setup(); 198 | 199 | let sqrt_price_X96 = encode_price_sqrt_1_1(); 200 | 201 | yas_nft_position_manager 202 | .create_and_initialize_pool_if_necessary( 203 | token_0.contract_address, 204 | token_1.contract_address, 205 | fee_amount(FeeAmount::MEDIUM), 206 | sqrt_price_X96 207 | ); 208 | 209 | let (min_tick, max_tick) = get_min_tick_and_max_tick(); 210 | yas_nft_position_manager 211 | .mint( 212 | MintParams { 213 | token_0: token_0.contract_address, 214 | token_1: token_1.contract_address, 215 | fee: fee_amount(FeeAmount::MEDIUM), 216 | recipient: OTHER(), 217 | tick_lower: min_tick, 218 | tick_upper: max_tick, 219 | amount_0_desired: 15, 220 | amount_1_desired: 15, 221 | amount_0_min: 0, 222 | amount_1_min: 0, 223 | deadline: 10 224 | } 225 | ); 226 | 227 | assert(yas_nft_position_manager.balance_of(OTHER()) == 1, 'wrong balance_of OTHER'); 228 | // assert(yas_nft_position_manager.token_of_owner_by_index(other.address, 0) == 1, ''); // TODO: ERC721Enumerable 229 | 230 | let (position, pool_key) = yas_nft_position_manager.positions(1); 231 | assert(pool_key.token_0 == token_0.contract_address, 'wrong token_0'); 232 | assert(pool_key.token_1 == token_1.contract_address, 'wrong token_1'); 233 | assert(pool_key.fee == fee_amount(FeeAmount::MEDIUM), 'wrong fee'); 234 | assert(position.tick_lower == min_tick, 'wrong tick_lower'); 235 | assert(position.tick_upper == max_tick, 'wrong tick_upper'); 236 | assert(position.liquidity == 15, 'wrong liquidity'); 237 | assert(position.tokens_owed_0 == 0, 'wrong tokens_owed_0'); 238 | assert(position.tokens_owed_1 == 0, 'wrong tokens_owed_1'); 239 | assert(position.fee_growth_inside_0_last_X128 == 0, 'wrong fee_growth_inside_0'); 240 | assert(position.fee_growth_inside_1_last_X128 == 0, 'wrong fee_growth_inside_1'); 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /deprecated_scripts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas" 3 | version = "0.1.0" 4 | edition = "2021" 5 | readme = "README.md" 6 | repository = "https://github.com/lambdaclass/yet-another-swap/" 7 | resolver = "2" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | eyre = "0.6.8" 13 | dotenv = "0.15.0" 14 | rpassword = "7.2.0" 15 | serde_json = "1.0.74" 16 | starknet = "0.6.0" 17 | tokio = { version = "1.21.2", features = ["full"]} 18 | url = "2.2.2" 19 | 20 | [[bin]] 21 | name = "deploy" 22 | path = "scripts/deploy.rs" 23 | 24 | [[bin]] 25 | name = "local" 26 | path = "scripts/local.rs" -------------------------------------------------------------------------------- /deprecated_scripts/rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2023-08-23 2 | -------------------------------------------------------------------------------- /deprecated_scripts/rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | newline_style = "Unix" 3 | use_field_init_shorthand = true 4 | use_small_heuristics = "Max" 5 | use_try_shorthand = true 6 | max_width = 120 7 | 8 | # Unstable features below 9 | unstable_features = true 10 | version = "Two" 11 | comment_width = 100 12 | format_code_in_doc_comments = true 13 | format_macro_bodies = true 14 | format_macro_matchers = true 15 | format_strings = true 16 | imports_granularity = "Module" 17 | group_imports = "StdExternalCrate" 18 | normalize_comments = true 19 | normalize_doc_attributes = true 20 | wrap_comments = true 21 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source .env 4 | set -e 5 | 6 | export STARKNET_ACCOUNT=$ACCOUNT_SRC 7 | export STARKNET_RPC=$RPC_URL 8 | 9 | # ANSI format 10 | GREEN='\e[32m' 11 | RESET='\e[0m' 12 | 13 | # Declare all contracts 14 | 15 | echo -e "$GREEN\n==> Declaring Router$RESET" 16 | ROUTER_CLASS_HASH=$(starkli declare --watch --private-key $ACCOUNT_PRIVATE_KEY ./target/dev/yas_core_YASRouter.sierra.json) 17 | echo -e $GREEN$ROUTER_CLASS_HASH 18 | 19 | echo -e "$GREEN\n==> Declaring Factory$RESET" 20 | FACTORY_CLASS_HASH=$(starkli declare --watch --private-key $ACCOUNT_PRIVATE_KEY ./target/dev/yas_core_YASFactory.sierra.json) 21 | echo -e $GREEN$FACTORY_CLASS_HASH$RESET 22 | 23 | echo -e "$GREEN\n==> Declaring Pool$RESET" 24 | POOL_CLASS_HASH=$(starkli declare --watch --private-key $ACCOUNT_PRIVATE_KEY ./target/dev/yas_core_YASPool.sierra.json) 25 | echo -e $GREEN$POOL_CLASS_HASH$RESET 26 | 27 | echo -e "$GREEN\n==> Declaring YASNFTPositionManager$RESET" 28 | NFT_POSITION_MANAGER_CLASS_HASH=$(starkli declare --watch --private-key $ACCOUNT_PRIVATE_KEY ./target/dev/yas_periphery_YASNFTPositionManager.sierra.json) 29 | echo -e $GREEN$POOL_CLASS_HASH$RESET 30 | 31 | echo -e "$GREEN\n==> Deploying Factory$RESET" 32 | FACTORY_ADDRESS=$(starkli deploy --watch $FACTORY_CLASS_HASH --private-key $ACCOUNT_PRIVATE_KEY \ 33 | $ACCOUNT_ADDRESS \ 34 | $POOL_CLASS_HASH) 35 | echo -e $GREEN$FACTORY_ADDRESS$RESET 36 | 37 | echo -e "$GREEN\n==> Deploying Router$RESET" 38 | ROUTER_ADDRESS=$(starkli deploy --watch $ROUTER_CLASS_HASH --private-key $ACCOUNT_PRIVATE_KEY) 39 | echo -e $GREEN$ROUTER_ADDRESS$RESET 40 | 41 | echo -e "$GREEN\n==> Deploying YASNFTPositionManager$RESET" 42 | NFT_POSITION_MANAGER_ADDRESS=$(starkli deploy --watch $NFT_POSITION_MANAGER_CLASS_HASH $FACTORY_ADDRESS --private-key $ACCOUNT_PRIVATE_KEY) 43 | echo -e $GREEN$NFT_POSITION_MANAGER_ADDRESS$RESET 44 | -------------------------------------------------------------------------------- /scripts/run_local_demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source .env 4 | set -e 5 | 6 | # This values are from Katana, just for testing 7 | KATANA_PRIVATE_KEY=0x1800000000300000180000000000030000000000003006001800006600 8 | KATANA_ACCOUNT_ADDRESS=0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 9 | KATANA_URL=http://0.0.0.0:5050 10 | 11 | U128_MAX=340282366920938463463374607431768211455 12 | 13 | KATANA_ACCOUNT_SRC=$ACCOUNT_SRC 14 | 15 | export STARKNET_ACCOUNT=$KATANA_ACCOUNT_SRC 16 | export STARKNET_RPC=$KATANA_URL 17 | 18 | # ANSI format 19 | GREEN='\e[32m' 20 | RESET='\e[0m' 21 | 22 | # Check if the JSON file exists 23 | if [ ! -f "$KATANA_ACCOUNT_SRC" ]; then 24 | $(starkli account fetch --output $KATANA_ACCOUNT_SRC $KATANA_ACCOUNT_ADDRESS) 25 | echo -e "$GREEN\n==> Katana JSON account file created at: $KATANA_ACCOUNT_SRC$RESET" 26 | else 27 | echo -e "$GREEN\n==> Katana JSON account file already exists at: $KATANA_ACCOUNT_SRC$RESET" 28 | fi 29 | 30 | # Declare all contracts 31 | echo -e "$GREEN\n==> Declaring ERC20$RESET" 32 | ERC20_CLASS_HASH=$(starkli declare --watch --private-key $KATANA_PRIVATE_KEY ./target/dev/yas_core_ERC20.sierra.json) 33 | echo -e $GREEN$ERC20_CLASS_HASH$RESET 34 | 35 | echo -e "$GREEN\n==> Declaring Router$RESET" 36 | ROUTER_CLASS_HASH=$(starkli declare --watch --private-key $KATANA_PRIVATE_KEY ./target/dev/yas_core_YASRouter.sierra.json) 37 | echo -e $GREEN$ROUTER_CLASS_HASH 38 | 39 | echo -e "$GREEN\n==> Declaring Factory$RESET" 40 | FACTORY_CLASS_HASH=$(starkli declare --watch --private-key $KATANA_PRIVATE_KEY ./target/dev/yas_core_YASFactory.sierra.json) 41 | echo -e $GREEN$FACTORY_CLASS_HASH$RESET 42 | 43 | echo -e "$GREEN\n==> Declaring Pool$RESET" 44 | POOL_CLASS_HASH=$(starkli declare --watch --private-key $KATANA_PRIVATE_KEY ./target/dev/yas_core_YASPool.sierra.json) 45 | echo -e $GREEN$POOL_CLASS_HASH$RESET 46 | 47 | echo -e "$GREEN\n==> Deploying TYAS0 token$RESET" 48 | # name: TYAS0 49 | # symbol: $YAS0 50 | # supply: 4000000000000000000 51 | # recipent: Katana account 52 | TOKEN_0_ADDRESS=$(starkli deploy --watch $ERC20_CLASS_HASH --private-key $KATANA_PRIVATE_KEY \ 53 | 362274706224 \ 54 | 156116276016 \ 55 | u256:4000000000000000000 \ 56 | $KATANA_ACCOUNT_ADDRESS) 57 | echo -e $GREEN$TOKEN_0_ADDRESS$RESET 58 | 59 | echo -e "$GREEN\n==> Deploying TYAS1 token$RESET" 60 | # name: TYAS1 61 | # symbol: $YAS1 62 | # supply: 4000000000000000000 63 | # recipent: Katana account 64 | TOKEN_1_ADDRESS=$(starkli deploy --watch $ERC20_CLASS_HASH --private-key $KATANA_PRIVATE_KEY \ 65 | 362274706225 \ 66 | 156116276017 \ 67 | u256:4000000000000000000 \ 68 | $KATANA_ACCOUNT_ADDRESS) 69 | echo -e $GREEN$TOKEN_1_ADDRESS$RESET 70 | 71 | echo -e "$GREEN\n==> Deploying Factory$RESET" 72 | FACTORY_ADDRESS=$(starkli deploy --watch $FACTORY_CLASS_HASH --private-key $KATANA_PRIVATE_KEY \ 73 | $KATANA_ACCOUNT_ADDRESS \ 74 | $POOL_CLASS_HASH) 75 | echo -e $GREEN$FACTORY_ADDRESS$RESET 76 | 77 | echo -e "$GREEN\n==> Deploying Router$RESET" 78 | ROUTER_ADDRESS=$(starkli deploy --watch $ROUTER_CLASS_HASH --private-key $KATANA_PRIVATE_KEY) 79 | echo -e $GREEN$ROUTER_ADDRESS$RESET 80 | 81 | echo -e "$GREEN\n==> Deploying Pool$RESET" 82 | POOL_ADDRESS=$(starkli deploy --watch $POOL_CLASS_HASH --private-key $KATANA_PRIVATE_KEY \ 83 | $FACTORY_ADDRESS \ 84 | $TOKEN_0_ADDRESS \ 85 | $TOKEN_1_ADDRESS \ 86 | 3000 \ 87 | 60 0 ) 88 | echo -e $GREEN$POOL_ADDRESS$RESET 89 | 90 | echo -e "$GREEN\n==> Initialize Pool$RESET" 91 | starkli invoke --watch --private-key $KATANA_PRIVATE_KEY $POOL_ADDRESS initialize \ 92 | u256:79228162514264337593543950336 \ 93 | 0; 94 | 95 | echo -e "$GREEN\n==> TYAS0 Approve$RESET" 96 | starkli invoke --watch --private-key $KATANA_PRIVATE_KEY $TOKEN_0_ADDRESS approve \ 97 | $ROUTER_ADDRESS \ 98 | $U128_MAX \ 99 | $U128_MAX 100 | 101 | echo -e "$GREEN\n==> TYAS1 Approve$RESET" 102 | starkli invoke --watch --private-key $KATANA_PRIVATE_KEY $TOKEN_1_ADDRESS approve \ 103 | $ROUTER_ADDRESS \ 104 | $U128_MAX \ 105 | $U128_MAX 106 | 107 | OWNER_T0_BALANCE_BF_MINT=$(starkli call $TOKEN_0_ADDRESS balanceOf $KATANA_ACCOUNT_ADDRESS) 108 | OWNER_T1_BALANCE_BF_MINT=$(starkli call $TOKEN_1_ADDRESS balanceOf $KATANA_ACCOUNT_ADDRESS) 109 | 110 | echo -e "$GREEN\n==> Balance before mint$RESET" 111 | echo -e "$GREEN TYAS0: $OWNER_T0_BALANCE_BF_MINT\n TYAS1: $OWNER_T1_BALANCE_BF_MINT$RESET" 112 | 113 | echo -e "$GREEN\n==> Mint$RESET" 114 | starkli invoke --watch --private-key $KATANA_PRIVATE_KEY $ROUTER_ADDRESS mint \ 115 | $POOL_ADDRESS \ 116 | $KATANA_ACCOUNT_ADDRESS \ 117 | 887220 1 \ 118 | 887220 0 \ 119 | 2000000000000000000 120 | 121 | OWNER_T0_BALANCE_AF_MINT=$(starkli call $TOKEN_0_ADDRESS balanceOf $KATANA_ACCOUNT_ADDRESS) 122 | OWNER_T1_BALANCE_AF_MINT=$(starkli call $TOKEN_1_ADDRESS balanceOf $KATANA_ACCOUNT_ADDRESS) 123 | 124 | echo -e "$GREEN\n==> Balance after mint$RESET" 125 | echo -e "$GREEN TYAS0: $OWNER_T0_BALANCE_AF_MINT\n TYAS1: $OWNER_T1_BALANCE_AF_MINT$RESET" 126 | 127 | echo -e "$GREEN\n==> Swap" 128 | starkli invoke --watch --private-key $KATANA_PRIVATE_KEY $ROUTER_ADDRESS swap \ 129 | $POOL_ADDRESS \ 130 | $KATANA_ACCOUNT_ADDRESS \ 131 | 1 \ 132 | 500000000000000000 0 \ 133 | 1 \ 134 | 4295128740 0 0 \ 135 | 136 | OWNER_T0_BALANCE_AF_SWAP=$(starkli call $TOKEN_0_ADDRESS balanceOf $KATANA_ACCOUNT_ADDRESS) 137 | OWNER_T1_BALANCE_AF_SWAP=$(starkli call $TOKEN_1_ADDRESS balanceOf $KATANA_ACCOUNT_ADDRESS) 138 | 139 | echo -e "$GREEN\n==> Balance after swap$RESET" 140 | echo -e "$GREEN TYAS0: $OWNER_T0_BALANCE_AF_SWAP\n TYAS1: $OWNER_T1_BALANCE_AF_SWAP$RESET" 141 | -------------------------------------------------------------------------------- /scripts/setup_katana_account.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source .env 4 | set -e 5 | 6 | export STARKNET_ACCOUNT=$ACCOUNT_SRC 7 | export STARKNET_RPC=$RPC_URL 8 | 9 | # ANSI format 10 | GREEN='\e[32m' 11 | RESET='\e[0m' 12 | 13 | # Check if the JSON file exists 14 | if [ ! -f "$ACCOUNT_SRC" ]; then 15 | $(starkli account fetch --output $ACCOUNT_SRC $ACCOUNT_ADDRESS) 16 | echo -e "$GREEN\n==> Katana JSON account file created at: $ACCOUNT_SRC$RESET" 17 | else 18 | echo -e "$GREEN\n==> Katana JSON account file already exists at: $ACCOUNT_SRC$RESET" 19 | fi 20 | -------------------------------------------------------------------------------- /scripts/utility/README.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | This Readme explains the structure and process of passing the pool test cases from the original Uniswap Protocol to a compatible and more readable cairo structure. 4 | 5 | To test the swap() functionality, Uniswap does the following: 6 | It creates an array of Pools 7 | It creates an array of Swaps configurations 8 | It does every swap with every pool, resulting in around 240 tests. 9 | 10 | To verify the resulting values, Uniswap has a snapshot file with an element for each of these 240 test, an object with many expected values before and after each swap: pool price before, pool price after, tokens transferred, etc. 11 | 12 | To use these values, we have made a python script ´parse_to_cairo_struct.py´ that parses the original snapshot of a pool and converts the syntax to a cairo object. This way, we can use the same testing technique. 13 | 14 | After this, there is a process of manually reordering the outputted expected cases so that they pair with the swap cases of each pool case. 15 | -------------------------------------------------------------------------------- /scripts/utility/convert_to_64x96.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from decimal import Decimal, getcontext 3 | 4 | def parse_price(formatted_price): 5 | getcontext().prec = 50 6 | price = Decimal(formatted_price) 7 | original_price = (price.sqrt()) * (2 ** 96) 8 | return str(original_price) 9 | 10 | def main(): 11 | if len(sys.argv) != 2: 12 | print("Usage: python script.py ") 13 | sys.exit(1) 14 | formatted_price = sys.argv[1] 15 | result = parse_price(formatted_price) 16 | print(f"RESULT: {result}") 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /scripts/utility/parse_to_cairo_struct.py: -------------------------------------------------------------------------------- 1 | # Input of the script 2 | # Object { 3 | # "amount0Before": "2000000000000000000", 4 | # "amount0Delta": "1000", 5 | # "amount1Before": "2000000000000000000", 6 | # "amount1Delta": "-998", 7 | # "executionPrice": "0.99800", 8 | # "feeGrowthGlobal0X128Delta": "170141183460469231731", 9 | # "feeGrowthGlobal1X128Delta": "0", 10 | # "poolPriceAfter": "1.0000", 11 | # "poolPriceBefore": "1.0000", 12 | # "tickAfter": -1, 13 | # "tickBefore": 0, 14 | # } 15 | # `; 16 | 17 | # Output of the script 18 | # let swap_expected_result = SwapExpectedResults { 19 | # amount_0_before: 2000000000000000000, 20 | # amount_0_delta: IntegerTrait::::new(1000, false), 21 | # amount_1_before: 2000000000000000000, 22 | # amount_1_delta: IntegerTrait::::new(998, true), 23 | # execution_price: 99800, // original executionPrice * 10**5 24 | # fee_growth_global_0_X128_delta: 170141183460469231731, 25 | # fee_growth_global_1_X128_delta: 0, 26 | # pool_price_after: 79228162514264337593543950336, // after applying parse_price from test_to_cairo 27 | # pool_price_before: 79228162514264337593543950336, // after applying parse_price from test_to_cairo 28 | # tick_after: IntegerTrait::::new(1, true), 29 | # tick_before: IntegerTrait::::new(0, false), 30 | # } 31 | 32 | # pass "True" to "true" 33 | def sign_to_text(sign): 34 | if sign : 35 | return "true" 36 | else: 37 | return "false" 38 | 39 | def format_amount_delta(amount_delta): 40 | # convert to integer 41 | amount_delta = int(amount_delta) 42 | # if the amount_delta is negative, then sign is true 43 | sign = amount_delta < 0 44 | # convert to positive 45 | amount_delta = abs(amount_delta) 46 | # return the formatted amount_delta and if its 0 then sign is false 47 | return f'IntegerTrait::::new({amount_delta}, {sign_to_text(sign)})' 48 | 49 | def format_execution_price(execution_price): 50 | # remove the quotes 51 | execution_price = execution_price.replace('"', '') 52 | # remove the comma 53 | execution_price = execution_price.replace(',', '') 54 | # convert to float 55 | execution_price = float(execution_price) 56 | # multiply by 10**5 57 | execution_price = execution_price * 10**5 58 | # convert to integer 59 | execution_price = int(round(execution_price)) 60 | # return the formatted execution_price 61 | return f'{execution_price}' 62 | 63 | # end format is * 10**5 64 | def format_pool_price(pool_price): 65 | # remove the quotes 66 | pool_price = pool_price.replace('"', '') 67 | # remove the comma 68 | pool_price = pool_price.replace(',', '') 69 | # convert to float 70 | pool_price = float(pool_price) 71 | # dislpace comma 72 | pool_price = pool_price * 10**5 73 | rounded = '%s' % float('%.5g' % pool_price) 74 | pool_price = float(rounded) 75 | ## take the square root 76 | #pool_price = pool_price ** (1/2) 77 | ## multiply by 2**96 78 | #pool_price = pool_price * 2**96 79 | # convert to integer 80 | pool_price = int(pool_price) 81 | # return the formatted pool_price 82 | return f'{pool_price}' 83 | 84 | def format_tick(tick): 85 | # remove the quotes 86 | tick = tick.replace('"', '') 87 | # remove the comma 88 | tick = tick.replace(',', '') 89 | # convert to integer 90 | tick = int(tick) 91 | # return the formatted tick 92 | return f'IntegerTrait::::new({abs(tick)}, {sign_to_text(tick < 0)})' 93 | 94 | def parse_object(object): 95 | # first save in a dictionary the key-value pairs 96 | # each key is a string and each value is a string 97 | # the value of a key is separated by a "," from the next key 98 | values = {} 99 | keys_in_cairo ={ 100 | "amount0Before": "amount_0_before", 101 | "amount0Delta": "amount_0_delta", 102 | "amount1Before": "amount_1_before", 103 | "amount1Delta": "amount_1_delta", 104 | "executionPrice": "execution_price", 105 | "feeGrowthGlobal0X128Delta": "fee_growth_global_0_X128_delta", 106 | "feeGrowthGlobal1X128Delta": "fee_growth_global_1_X128_delta", 107 | "poolPriceAfter": "pool_price_after", 108 | "poolPriceBefore": "pool_price_before", 109 | "tickAfter": "tick_after", 110 | "tickBefore": "tick_before", 111 | "poolBalance0": "pool_balance_0", 112 | "poolBalance1": "pool_balance_1", 113 | "poolPriceBefore": "pool_price_before", 114 | "swapError": "swap_error", 115 | "tickBefore": "tick_before", 116 | } 117 | # key is the original key object, and the value is a lambda function that will apply the necessary changes 118 | # for every key it's the identity function 119 | # except for: 120 | # amount_0_delta -> IntegerTrait::::new(value, false) 121 | functions_to_apply = { 122 | "amount_0_delta": format_amount_delta, 123 | "amount_1_delta": format_amount_delta, 124 | "execution_price": format_execution_price, 125 | "pool_price_after": format_pool_price, 126 | "pool_price_before": format_pool_price, 127 | "tick_after": format_tick, 128 | "tick_before": format_tick, 129 | } 130 | 131 | # remove the spaces and the new lines 132 | object = object.replace(" ", "") 133 | object = object.replace("\n", "") 134 | 135 | # remove the '"' from the keys and the values 136 | object = object.replace('"', "") 137 | # split the object by "," 138 | object = object.split(",") 139 | # remove the last element, which is empty 140 | object.pop() 141 | # for each key-value pair 142 | for key_value in object: 143 | # split by ":" 144 | key_value = key_value.split(":") 145 | # the key is the first element 146 | key = key_value[0] 147 | # the value is the second element 148 | value = key_value[1] 149 | # save the key-value pair in the dictionary 150 | values[keys_in_cairo[key]] = value 151 | 152 | # for each key-value in values apply the corresponding function 153 | for key, value in values.items(): 154 | # if the key is in functions_to_apply 155 | if key in functions_to_apply: 156 | # apply the function 157 | values[key] = functions_to_apply[key](value) 158 | return values 159 | 160 | #main 161 | if __name__ == "__main__": 162 | # read from 'pool2_swap1.txt' 163 | file = open('./pool2_swaps_torober.txt', 'r') 164 | objects = file.read() 165 | # each object is separated by a ;\n from the next object 166 | objects = objects.split(';\n') 167 | 168 | # for each object save only the object that is the content that is between { and } 169 | parsed_objects = [] 170 | for object in objects: 171 | # find the first { 172 | start = object.find('{') 173 | # find the last } 174 | end = object.rfind('}') 175 | # save the object 176 | object = object[start+1:end] 177 | # parse the object 178 | parsed_objects.append(parse_object(object)) 179 | 180 | to_print = "" 181 | for i, object in enumerate(parsed_objects): 182 | # swap_variable_string = "let swap_expected_result = SwapExpectedResults {" 183 | swap_variable_string = "\t\t\tSwapExpectedResults {" 184 | 185 | for key, value in object.items(): 186 | swap_variable_string += f'\n\t\t\t\t{key}: {value},' 187 | swap_variable_string += "\n\t\t\t}," 188 | 189 | if i != len(parsed_objects) - 1: 190 | swap_variable_string += "\n" 191 | to_print += swap_variable_string 192 | 193 | # save the result in 'swap_expected_result.txt' 194 | file = open('swap_expected_result.txt', 'w') 195 | file.write(to_print) 196 | 197 | 198 | -------------------------------------------------------------------------------- /yas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/yet-another-swap/f3ee03a3564a37698e9589f564ac63aa59dab283/yas.png --------------------------------------------------------------------------------