diff --git a/.all-contributorsrc b/.all-contributorsrc index a1152b48..f6901995 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -261,6 +261,51 @@ "contributions": [ "code" ] + }, + { + "login": "Tbelleng", + "name": "Tbelleng", + "avatar_url": "https://avatars.githubusercontent.com/u/117627242?v=4", + "profile": "https://github.com/Tbelleng", + "contributions": [ + "code" + ] + }, + { + "login": "dic0de", + "name": "dic0de", + "avatar_url": "https://avatars.githubusercontent.com/u/37063500?v=4", + "profile": "https://github.com/dic0de", + "contributions": [ + "code" + ] + }, + { + "login": "akhercha", + "name": "akhercha", + "avatar_url": "https://avatars.githubusercontent.com/u/22559023?v=4", + "profile": "https://github.com/akhercha", + "contributions": [ + "code" + ] + }, + { + "login": "VictorONN", + "name": "VictorONN", + "avatar_url": "https://avatars.githubusercontent.com/u/73134512?v=4", + "profile": "https://github.com/VictorONN", + "contributions": [ + "code" + ] + }, + { + "login": "kasteph", + "name": "kasteph", + "avatar_url": "https://avatars.githubusercontent.com/u/3408478?v=4", + "profile": "https://github.com/kasteph", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 344f5672..84f4b4a2 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,13 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d ftupas
ftupas

💻 lambda-0x
lambda-0x

💻 + + Tbelleng
Tbelleng

💻 + dic0de
dic0de

💻 + akhercha
akhercha

💻 + VictorONN
VictorONN

💻 + kasteph
kasteph

💻 + diff --git a/book/src/README.md b/book/src/README.md index bb8dfdfe..aed820de 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -4,6 +4,6 @@ Satoru is a cutting-edge synthetics platform for Starknet, taking inspiration fr ![Satoru Whats Up](./assets/satoru-whats-up.gif) -Like it's namesake, Satoru is **powerful** and **badass**. It's also a bit of a mystery. We don't know what it's capable of yet, but we're excited to find out. +Like its namesake, Satoru is **powerful** and **badass**. It's also a bit of a mystery. We don't know what it's capable of yet, but we're excited to find out. -Combine the power of **Starknet** with it's huge computation capacity to the great design of **GMX v2**, and you get Satoru, a synthetics platform that is fast, cheap and scalable. +Combine the power of **Starknet** with its huge computation capacity with the great design of **GMX v2**, and you get Satoru, a synthetics platform that is fast, cheap and scalable. diff --git a/book/src/continuous-integration/README.md b/book/src/continuous-integration/README.md index 26aaf1aa..0a120922 100644 --- a/book/src/continuous-integration/README.md +++ b/book/src/continuous-integration/README.md @@ -4,4 +4,4 @@ A workflow is a configurable automated process that will run one or more jobs. Workflows are defined by a YAML file checked in to your repository and will run when triggered by an event in your repository, or they can be triggered manually, or at a defined schedule. -In this section we will run through every workflows and give a detailed explanation of their functionalities. \ No newline at end of file +In this section we will run through every workflow and give a detailed explanation of their functionalities. diff --git a/book/src/smart-contracts-architecture/data-module.md b/book/src/smart-contracts-architecture/data-module.md index 7bf834cb..5346f1e1 100644 --- a/book/src/smart-contracts-architecture/data-module.md +++ b/book/src/smart-contracts-architecture/data-module.md @@ -5,7 +5,7 @@ The Data Module serves as the backbone for storing and managing the protocol's d ### Smart Contracts #### [data_store.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/data/data_store.cairo) -The `DataStore` is the central smart contract of the module, holding the responsibility of maintaining the protocol's data. It manage different entities, including orders, positions, withdrawals, and deposits. +The `DataStore` is the central smart contract of the module, holding the responsibility of maintaining the protocol's data. It manages different entities, including orders, positions, withdrawals, and deposits. ##### Key Features & Responsibilities: - **Order Management:** Enables the creation, reading, updating, and deletion of orders, each linked to a specific user account. Orders can be retrieved using their unique keys or can be listed per user account. @@ -28,4 +28,4 @@ The constructor initializes the contract with a `role_store` address, establishi ### Cairo Library Files #### [keys.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/data/keys.cairo) -This Cairo library file plays a crucial role in generating the keys for the protocol's entries in the data store. The keys serve as unique identifiers, enabling the protocol to accurately access and manage the stored data. \ No newline at end of file +This Cairo library file plays a crucial role in generating the keys for the protocol's entries in the data store. The keys serve as unique identifiers, enabling the protocol to accurately access and manage the stored data. diff --git a/scripts/bank/deploy_StrictBank_contract.sh b/scripts/bank/deploy_StrictBank_contract.sh index 8a64a1b6..3057d467 100644 --- a/scripts/bank/deploy_StrictBank_contract.sh +++ b/scripts/bank/deploy_StrictBank_contract.sh @@ -3,13 +3,10 @@ # Deployment script for strict_bank.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_StrictBank.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_StrictBank.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 \ No newline at end of file +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/bank/deploy_bank_contract.sh b/scripts/bank/deploy_bank_contract.sh index 28bbea24..21796eca 100644 --- a/scripts/bank/deploy_bank_contract.sh +++ b/scripts/bank/deploy_bank_contract.sh @@ -3,13 +3,10 @@ # Deployment script for bank.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_Bank.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_Bank.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 \ No newline at end of file +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/config/deploy_config_contract.sh b/scripts/config/deploy_config_contract.sh index 4bc2fe48..e8bcf550 100644 --- a/scripts/config/deploy_config_contract.sh +++ b/scripts/config/deploy_config_contract.sh @@ -3,13 +3,10 @@ # Deployment script for config.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_Config.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_Config.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 $3 \ No newline at end of file +starkli deploy $class_hash $3 $4 $5 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/config/deploy_timelock_contract.sh b/scripts/config/deploy_timelock_contract.sh index f05fcd0c..936275ce 100644 --- a/scripts/config/deploy_timelock_contract.sh +++ b/scripts/config/deploy_timelock_contract.sh @@ -3,13 +3,10 @@ # Deployment script for timelock.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_Timelock.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_Timelock.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash \ No newline at end of file +starkli deploy $class_hash --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/data/deploy_data_store_contract.sh b/scripts/data/deploy_data_store_contract.sh index a714087d..b730278e 100755 --- a/scripts/data/deploy_data_store_contract.sh +++ b/scripts/data/deploy_data_store_contract.sh @@ -3,13 +3,10 @@ # Deployment script for data_store.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_DataStore.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_DataStore.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 \ No newline at end of file +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/deposit/deploy_deposit_vault_contract.sh b/scripts/deposit/deploy_deposit_vault_contract.sh index 8cd4107c..55fb5b41 100755 --- a/scripts/deposit/deploy_deposit_vault_contract.sh +++ b/scripts/deposit/deploy_deposit_vault_contract.sh @@ -3,13 +3,10 @@ # Deployment script for deposit_vault.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_DepositVault.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_DepositVault.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 \ No newline at end of file +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/event/deploy_event_emitter_contract.sh b/scripts/event/deploy_event_emitter_contract.sh index 562c722f..c63f6396 100755 --- a/scripts/event/deploy_event_emitter_contract.sh +++ b/scripts/event/deploy_event_emitter_contract.sh @@ -3,13 +3,10 @@ # Deployment script for event_emitter.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_EventEmitter.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_EventEmitter.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash \ No newline at end of file +starkli deploy $class_hash --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_adl_handler_contract.sh b/scripts/exchange/deploy_adl_handler_contract.sh index bcf9a0c0..8747b6d2 100644 --- a/scripts/exchange/deploy_adl_handler_contract.sh +++ b/scripts/exchange/deploy_adl_handler_contract.sh @@ -3,13 +3,10 @@ # Deployment script for adl_handler.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_AdlHandler.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_AdlHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 $3 $4 $5 $6 $7 $8 \ No newline at end of file +starkli deploy $class_hash $3 $4 $5 $6 $7 $8 $9 $10 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_base_order_handler_contract.sh b/scripts/exchange/deploy_base_order_handler_contract.sh index f5d22da6..0d046e06 100644 --- a/scripts/exchange/deploy_base_order_handler_contract.sh +++ b/scripts/exchange/deploy_base_order_handler_contract.sh @@ -3,13 +3,10 @@ # Deployment script for base_order_handler.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_BaseOrderHandler.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_BaseOrderHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 $3 $4 $5 $6 $7 \ No newline at end of file +starkli deploy $class_hash $3 $4 $5 $6 $7 $8 $9 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_deposit_handler_contract.sh b/scripts/exchange/deploy_deposit_handler_contract.sh index 82c0dd4c..442b31bd 100644 --- a/scripts/exchange/deploy_deposit_handler_contract.sh +++ b/scripts/exchange/deploy_deposit_handler_contract.sh @@ -3,13 +3,10 @@ # Deployment script for deposit_handler.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_DepositHandler.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_DepositHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 $3 $4 $5 \ No newline at end of file +starkli deploy $class_hash $3 $4 $5 $6 $7 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_liquidation_handler_contract.sh b/scripts/exchange/deploy_liquidation_handler_contract.sh index 8692b898..cff1a5fe 100644 --- a/scripts/exchange/deploy_liquidation_handler_contract.sh +++ b/scripts/exchange/deploy_liquidation_handler_contract.sh @@ -3,13 +3,10 @@ # Deployment script for liquidation_handler.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_LiquidationHandler.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_LiquidationHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 $3 $4 $5 $6 $7 \ No newline at end of file +starkli deploy $class_hash $3 $4 $5 $6 $7 $8 $9 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_order_handler_contract.sh b/scripts/exchange/deploy_order_handler_contract.sh index a887e207..72c5ab5d 100644 --- a/scripts/exchange/deploy_order_handler_contract.sh +++ b/scripts/exchange/deploy_order_handler_contract.sh @@ -3,13 +3,10 @@ # Deployment script for order_handler.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_OrderHandler.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_OrderHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 $3 $4 $5 $6 $7 \ No newline at end of file +starkli deploy $class_hash $3 $4 $5 $6 $7 $8 $9 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/exchange/deploy_withdrawal_handler_contract.sh b/scripts/exchange/deploy_withdrawal_handler_contract.sh index f56d3281..5e1a9327 100644 --- a/scripts/exchange/deploy_withdrawal_handler_contract.sh +++ b/scripts/exchange/deploy_withdrawal_handler_contract.sh @@ -3,13 +3,10 @@ # Deployment script for withdrawal_handler.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_WithdrawalHandler.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_WithdrawalHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 $3 $4 $5 \ No newline at end of file +starkli deploy $class_hash $3 $4 $5 $6 $7 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/fee/deploy_fee_handler_contract.sh b/scripts/fee/deploy_fee_handler_contract.sh index 074f4f21..58958bdd 100755 --- a/scripts/fee/deploy_fee_handler_contract.sh +++ b/scripts/fee/deploy_fee_handler_contract.sh @@ -3,13 +3,10 @@ # Deployment script for fee_handler.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_FeeHandler.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_FeeHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 $3 \ No newline at end of file +starkli deploy $class_hash $3 $4 $5 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/market/deploy_market_factory_contract.sh b/scripts/market/deploy_market_factory_contract.sh index 12ff7bf7..d43db089 100644 --- a/scripts/market/deploy_market_factory_contract.sh +++ b/scripts/market/deploy_market_factory_contract.sh @@ -3,13 +3,10 @@ # Deployment script for market_factory.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_MarketFactory.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_MarketFactory.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 $3 $4 \ No newline at end of file +starkli deploy $class_hash $3 $4 $5 $6 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/market/deploy_market_token_contract.sh b/scripts/market/deploy_market_token_contract.sh index c04de48e..08563d75 100644 --- a/scripts/market/deploy_market_token_contract.sh +++ b/scripts/market/deploy_market_token_contract.sh @@ -3,13 +3,10 @@ # Deployment script for market_token.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_MarketToken.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_MarketToken.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 \ No newline at end of file +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/mock/deploy_governable_contract.sh b/scripts/mock/deploy_governable_contract.sh index eeb8f338..a7f802ee 100644 --- a/scripts/mock/deploy_governable_contract.sh +++ b/scripts/mock/deploy_governable_contract.sh @@ -3,13 +3,10 @@ # Deployment script for governable.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_Governable.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_Governable.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 \ No newline at end of file +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/mock/deploy_referral_storage.sh b/scripts/mock/deploy_referral_storage.sh index 1d4840b7..89976652 100644 --- a/scripts/mock/deploy_referral_storage.sh +++ b/scripts/mock/deploy_referral_storage.sh @@ -3,13 +3,10 @@ # Deployment script for referral_storage.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_ReferralStorage.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_ReferralStorage.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 \ No newline at end of file +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/oracle/deploy_oracle_contract.sh b/scripts/oracle/deploy_oracle_contract.sh index 9904757a..f156cde6 100755 --- a/scripts/oracle/deploy_oracle_contract.sh +++ b/scripts/oracle/deploy_oracle_contract.sh @@ -3,13 +3,10 @@ # Deployment script for data_store.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_Oracle.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_Oracle.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 \ No newline at end of file +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/oracle/deploy_oracle_store_contract.sh b/scripts/oracle/deploy_oracle_store_contract.sh index 9fa1e33c..947f165d 100755 --- a/scripts/oracle/deploy_oracle_store_contract.sh +++ b/scripts/oracle/deploy_oracle_store_contract.sh @@ -3,13 +3,10 @@ # Deployment script for oracle_store.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_OracleStore.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_OracleStore.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 \ No newline at end of file +starkli deploy $class_hash $3 $4 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/oracle/deploy_price_feed_contract.sh b/scripts/oracle/deploy_price_feed_contract.sh index 75b4f0be..0764fcb1 100644 --- a/scripts/oracle/deploy_price_feed_contract.sh +++ b/scripts/oracle/deploy_price_feed_contract.sh @@ -3,13 +3,10 @@ # Deployment script for price_feed.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_PriceFeed.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_PriceFeed.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash \ No newline at end of file +starkli deploy $class_hash --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/order/deploy_order_vault_contract.sh b/scripts/order/deploy_order_vault_contract.sh index 04c9a70f..84374869 100755 --- a/scripts/order/deploy_order_vault_contract.sh +++ b/scripts/order/deploy_order_vault_contract.sh @@ -3,13 +3,10 @@ # Deployment script for order_vault.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_OrderVault.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_OrderVault.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 \ No newline at end of file +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/reader/deploy_reader_contract.sh b/scripts/reader/deploy_reader_contract.sh index fd4f8146..36e7b56d 100644 --- a/scripts/reader/deploy_reader_contract.sh +++ b/scripts/reader/deploy_reader_contract.sh @@ -3,13 +3,10 @@ # Deployment script for reader.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_Reader.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_Reader.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash \ No newline at end of file +starkli deploy $class_hash --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/role/deploy_role_module_contract.sh b/scripts/role/deploy_role_module_contract.sh index 3954f2ef..9f614190 100644 --- a/scripts/role/deploy_role_module_contract.sh +++ b/scripts/role/deploy_role_module_contract.sh @@ -3,13 +3,10 @@ # Deployment script for role_module.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_RoleModule.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_RoleModule.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 \ No newline at end of file +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/role/deploy_role_store_contract.sh b/scripts/role/deploy_role_store_contract.sh old mode 100644 new mode 100755 index d9567d00..8639bb29 --- a/scripts/role/deploy_role_store_contract.sh +++ b/scripts/role/deploy_role_store_contract.sh @@ -1,15 +1,10 @@ #!/bin/bash # Deployment script for role_store.cairo +command_output=$(starkli declare ../../target/dev/satoru_RoleStore.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_RoleStore.sierra.json) - -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash \ No newline at end of file +starkli deploy $class_hash --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/router/deploy_exchange_router_contract.sh b/scripts/router/deploy_exchange_router_contract.sh index a8384b5c..63109871 100644 --- a/scripts/router/deploy_exchange_router_contract.sh +++ b/scripts/router/deploy_exchange_router_contract.sh @@ -3,13 +3,10 @@ # Deployment script for exchange_router.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_ExchangeRouter.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_ExchangeRouter.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 $2 $3 $4 $5 $6 $7 \ No newline at end of file +starkli deploy $class_hash $3 $4 $5 $6 $7 $8 $9 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/router/deploy_router_contract.sh b/scripts/router/deploy_router_contract.sh index 5684905d..861f5a60 100644 --- a/scripts/router/deploy_router_contract.sh +++ b/scripts/router/deploy_router_contract.sh @@ -3,13 +3,10 @@ # Deployment script for router.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_Router.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_Router.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 \ No newline at end of file +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/swap/deploy_swap_handler_contract.sh b/scripts/swap/deploy_swap_handler_contract.sh index 8810d73b..84438844 100644 --- a/scripts/swap/deploy_swap_handler_contract.sh +++ b/scripts/swap/deploy_swap_handler_contract.sh @@ -3,13 +3,10 @@ # Deployment script for swap_handler.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_SwapHandler.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_SwapHandler.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 \ No newline at end of file +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/scripts/withdrawal/deploy_withdrawal_vault_contract.sh b/scripts/withdrawal/deploy_withdrawal_vault_contract.sh index bff07513..6e33a342 100644 --- a/scripts/withdrawal/deploy_withdrawal_vault_contract.sh +++ b/scripts/withdrawal/deploy_withdrawal_vault_contract.sh @@ -3,13 +3,10 @@ # Deployment script for withdrawal_vault.cairo # Declare the contract and capture the command output -command_output=$(starkli declare ../target/dev/satoru_WithdrawalVault.sierra.json) +command_output=$(starkli declare ../../target/dev/satoru_WithdrawalVault.sierra.json --network=goerli-1 --compiler-version=2.1.0 --account $1 --keystore $2) -# Define the character to split the command output -from_char=":" - -# Extract the class hash from the command output -class_hash=$(echo "$command_output" | sed 's/.*'$from_char'//') +from_string="Class hash declared:" +class_hash="${command_output#*$from_string}" # Deploy the contract using the extracted class hash -starkli deploy $class_hash $1 \ No newline at end of file +starkli deploy $class_hash $3 --network=goerli-1 --account $1 --keystore $2 \ No newline at end of file diff --git a/src/data/data_store.cairo b/src/data/data_store.cairo index fb04e850..94c952e0 100644 --- a/src/data/data_store.cairo +++ b/src/data/data_store.cairo @@ -481,7 +481,7 @@ mod DataStore { // Core lib imports. use core::option::OptionTrait; use core::traits::TryInto; - use starknet::{get_caller_address, ContractAddress, contract_address_const,}; + use starknet::{get_caller_address, ContractAddress, contract_address_const}; use nullable::NullableTrait; use zeroable::Zeroable; use alexandria_storage::list::{ListTrait, List}; diff --git a/src/deposit/deposit_vault.cairo b/src/deposit/deposit_vault.cairo index 54c26857..79828e98 100644 --- a/src/deposit/deposit_vault.cairo +++ b/src/deposit/deposit_vault.cairo @@ -114,8 +114,8 @@ mod DepositVault { } fn record_transfer_in(ref self: ContractState, token: ContractAddress) -> u128 { - // TODO - 0 + let mut state: StrictBank::ContractState = StrictBank::unsafe_new_contract_state(); + IStrictBank::record_transfer_in(ref state, token) } } } diff --git a/src/deposit/error.cairo b/src/deposit/error.cairo index 3a936f54..cc92949c 100644 --- a/src/deposit/error.cairo +++ b/src/deposit/error.cairo @@ -4,4 +4,14 @@ mod DepositError { const CANT_BE_ZERO: felt252 = 'deposit account cant be 0'; const EMPTY_DEPOSIT_AMOUNTS: felt252 = 'empty_deposit_amounts'; const EMPTY_DEPOSIT: felt252 = 'empty_deposit'; + const EMPTY_DEPOSIT_AMOUNTS_AFTER_SWAP: felt252 = 'empty deposit amount after swap'; + const INVALID_POOL_VALUE_FOR_DEPOSIT: felt252 = 'invalid pool value for deposit'; + + + fn MIN_MARKET_TOKENS(received: u128, expected: u128) { + let mut data = array!['invalid swap output token']; + data.append(received.into()); + data.append(expected.into()); + panic(data) + } } diff --git a/src/deposit/execute_deposit_utils.cairo b/src/deposit/execute_deposit_utils.cairo index 519fb7ef..742a450f 100644 --- a/src/deposit/execute_deposit_utils.cairo +++ b/src/deposit/execute_deposit_utils.cairo @@ -10,14 +10,36 @@ use result::ResultTrait; use debug::PrintTrait; // Local imports. -use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; +use satoru::callback::callback_utils::after_deposit_execution; +use satoru::data::{ + keys::{deposit_fee_type, ui_deposit_fee_type, max_pnl_factor_for_deposits}, + data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait} +}; +use satoru::deposit::{ + deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}, error::DepositError +}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; -use satoru::deposit::deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}; +use satoru::event::event_utils::{LogData, set_item_uint_items, UintItems}; +use satoru::fee::fee_utils; +use satoru::gas::gas_utils::pay_execution_fee_deposit; +use satoru::market::{ + market::Market, market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}, + market_utils +}; use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; -use satoru::price::price::Price; -use satoru::market::market::Market; -use satoru::utils::span32::Span32; +use satoru::oracle::{oracle::{IOracleDispatcher, IOracleDispatcherTrait}, oracle_utils}; +use satoru::price::price::{Price, PriceTrait}; +use satoru::pricing::swap_pricing_utils::{ + get_swap_fees, get_price_impact_usd, GetPriceImpactUsdParams +}; +use satoru::swap::swap_utils; +use satoru::swap::error::SwapError; +use satoru::utils::{ + calc::{to_unsigned, to_signed}, i128::{I128Default}, precision, span32::Span32, + starknet_utils::{sn_gasleft, sn_gasprice} +}; + /// Struct used in executeDeposit to avoid stack too deep errors #[derive(Drop, Serde)] @@ -67,6 +89,7 @@ struct _ExecuteDepositParams { price_impact_usd: u128 } +#[derive(Drop, Default)] struct ExecuteDepositCache { long_token_amount: u128, short_token_amount: u128, @@ -80,29 +103,395 @@ struct ExecuteDepositCache { /// # Arguments /// * `params` - ExecuteDepositParams. #[inline(always)] -fn execute_deposit(params: ExecuteDepositParams) { //TODO +fn execute_deposit(params: ExecuteDepositParams) { + // 63/64 gas is forwarded to external calls, reduce the startingGas to account for this + let starting_gas = params.starting_gas - sn_gasleft(array![]) / 63; + + let deposit = params.data_store.get_deposit(params.key).unwrap(); + params.data_store.remove_deposit(params.key, deposit.account); + + let mut cache: ExecuteDepositCache = Default::default(); + + assert(deposit.account.is_non_zero(), DepositError::EMPTY_DEPOSIT); + + oracle_utils::validate_block_number_within_range( + params.min_oracle_block_numbers.span(), + params.max_oracle_block_numbers.span(), + deposit.updated_at_block, + ); + + let market = market_utils::get_enabled_market(params.data_store, deposit.market); + let prices = market_utils::get_market_prices(params.oracle, market); + + // deposits should improve the pool state but it should be checked if + // the max pnl factor for deposits is exceeded as this would lead to the + // price of the market token decreasing below a target minimum percentage + // due to pnl + // note that this is just a validation for deposits, there is no actual + // minimum price for a market token + market_utils::validate_max_pnl( + params.data_store, + market, + prices, + max_pnl_factor_for_deposits(), + max_pnl_factor_for_deposits(), + ); + + cache + .long_token_amount = + swap( + @params, + deposit.long_token_swap_path, + deposit.initial_long_token, + deposit.initial_long_token_amount, + market.market_token, + market.long_token, + deposit.ui_fee_receiver, + ); + + cache + .short_token_amount = + swap( + @params, + deposit.short_token_swap_path, + deposit.initial_short_token, + deposit.initial_short_token_amount, + market.market_token, + market.short_token, + deposit.ui_fee_receiver, + ); + + assert( + cache.long_token_amount == 0 && cache.short_token_amount == 0, + DepositError::EMPTY_DEPOSIT_AMOUNTS_AFTER_SWAP + ); + + cache.long_token_usd = cache.long_token_amount * prices.long_token_price.mid_price(); + cache.short_token_usd = cache.short_token_amount * prices.short_token_price.mid_price(); + + cache + .price_impact_usd = + get_price_impact_usd( + GetPriceImpactUsdParams { + data_store: params.data_store, + market: market, + token_a: market.long_token, + token_b: market.short_token, + price_for_token_a: prices.long_token_price.mid_price(), + price_for_token_b: prices.short_token_price.mid_price(), + usd_delta_for_token_a: to_signed(cache.long_token_usd, true), + usd_delta_for_token_b: to_signed(cache.short_token_usd, false), + } + ); + + if cache.long_token_amount > 0 { + let _params = _ExecuteDepositParams { + market: market, + account: deposit.account, + receiver: deposit.receiver, + ui_fee_receiver: deposit.ui_fee_receiver, + token_in: market.long_token, + token_out: market.short_token, + token_in_price: prices.long_token_price, + token_out_price: prices.short_token_price, + amount: cache.long_token_amount, + price_impact_usd: precision::mul_div( + to_unsigned(cache.price_impact_usd), + cache.long_token_usd, + cache.long_token_usd + cache.short_token_usd + ) + }; + + cache.received_market_tokens += execute_deposit_helper(@params, @_params); + } else if cache.short_token_amount > 0 { + let _params = _ExecuteDepositParams { + market: market, + account: deposit.account, + receiver: deposit.receiver, + ui_fee_receiver: deposit.ui_fee_receiver, + token_in: market.short_token, + token_out: market.long_token, + token_in_price: prices.short_token_price, + token_out_price: prices.long_token_price, + amount: cache.short_token_amount, + price_impact_usd: precision::mul_div( + to_unsigned(cache.price_impact_usd), + cache.short_token_usd, + cache.long_token_usd + cache.short_token_usd + ) + }; + + cache.received_market_tokens += execute_deposit_helper(@params, @_params); + } + + if cache.received_market_tokens < deposit.min_market_tokens { + DepositError::MIN_MARKET_TOKENS(cache.received_market_tokens, deposit.min_market_tokens); + } + + market_utils::validate_market_token_balance_with_address( + params.data_store, market.market_token + ); + + (params.event_emitter) + .emit_deposit_executed( + params.key, + cache.long_token_amount, + cache.short_token_amount, + cache.received_market_tokens, + ); + + let event_data: LogData = Default::default(); + let mut uint_items: UintItems = Default::default(); + set_item_uint_items(uint_items, 0, 'received_market_tokens', cache.received_market_tokens); + after_deposit_execution(params.key, deposit, event_data); + + pay_execution_fee_deposit( + params.data_store, + params.event_emitter, + params.deposit_vault, + deposit.execution_fee, + params.starting_gas, + params.keeper, + deposit.account, + ); } /// Executes a deposit. /// # Arguments -/// * `params` - ExecuteDepositParams. -/// * `_params` - _ExecuteDepositParams. +/// * `params` - @ExecuteDepositParams. +/// * `_params` - @_ExecuteDepositParams. #[inline(always)] -fn _execute_deposit(params: ExecuteDepositParams, _params: _ExecuteDepositParams) -> u128 { - //TODO - 0 +fn execute_deposit_helper(params: @ExecuteDepositParams, _params: @_ExecuteDepositParams) -> u128 { + // for markets where longToken == shortToken, the price impact factor should be set to zero + // in which case, the priceImpactUsd would always equal zero + let fees = get_swap_fees( + *params.data_store, + *_params.market.market_token, + *_params.amount, + *_params.price_impact_usd > 0, + *_params.ui_fee_receiver, + ); + + fee_utils::increment_claimable_fee_amount( + *params.data_store, + *params.event_emitter, + *_params.market.market_token, + *_params.token_in, + fees.fee_receiver_amount, + deposit_fee_type(), + ); + + fee_utils::increment_claimable_ui_fee_amount( + *params.data_store, + *params.event_emitter, + *_params.ui_fee_receiver, + *_params.market.market_token, + *_params.token_in, + fees.ui_fee_amount, + ui_deposit_fee_type(), + ); + + (*params.event_emitter) + .emit_swap_fees_collected( + *_params.market.market_token, + *_params.token_in, + *_params.token_in_price.min, + 'deposit', + fees.clone(), + ); + + let market_pool_value_info = market_utils::get_pool_value_info( + *params.data_store, + *_params.market, + (*params.oracle).get_primary_price(*_params.market.index_token), + if *_params.token_in == *_params.market.long_token { + *_params.token_in_price + } else { + *_params.token_out_price + }, + if *_params.token_in == *_params.market.short_token { + *_params.token_in_price + } else { + *_params.token_out_price + }, + max_pnl_factor_for_deposits(), + true, + ); + + assert(market_pool_value_info.pool_value < 0, DepositError::INVALID_POOL_VALUE_FOR_DEPOSIT); + + let mut mint_amount = 0; + let pool_value = market_pool_value_info.pool_value; + let market_tokens_supply = market_utils::get_market_token_supply( + IMarketTokenDispatcher { contract_address: *_params.market.market_token } + ); + + assert( + pool_value == 0 && market_tokens_supply > 0, DepositError::INVALID_POOL_VALUE_FOR_DEPOSIT + ); + + (*params.event_emitter) + .emit_market_pool_value_info( + *_params.market.market_token, market_pool_value_info, market_tokens_supply, + ); + + // the pool_value and market_tokens_supply is cached for the mint_amount calculation below + // so the effect of any positive price impact on the pool_value and market_tokens_supply + // would not be accounted for + // + // for most cases, this should not be an issue, since the pool_value and market_tokens_supply + // should have been proportionately increased + // + // e.g. if the pool_value is $100 and market_tokens_supply is 100, and there is a positive price impact + // of $10, the pool_value should have increased by $10 and the market_tokens_supply should have been increased by 10 + // + // there is a case where this may be an issue which is when all tokens are withdrawn from an existing market + // and the market_tokens_supply is reset to zero, but the pool_value is not entirely zero + // the case where this happens should be very rare and during withdrawal the pool_value should be close to zero + // + // however, in case this occurs, the usdToMarketTokenAmount will mint an additional number of market tokens + // proportional to the existing pool_value + // + // since the pool_value and market_tokens_supply is cached, this could occur once during positive price impact + // and again when calculating the mint_amount + // + // to avoid this, set the price_impact_usd to be zero for this case + let mut price_impact_usd = *_params.price_impact_usd; + + if price_impact_usd > 0 && market_tokens_supply == 0 { + price_impact_usd = 0; + } + + let mut amount_after_fees = fees.amount_after_fees; + + if price_impact_usd > 0 { + // when there is a positive price impact factor, + // tokens from the swap impact pool are used to mint additional market tokens for the user + // for example, if 50,000 USDC is deposited and there is a positive price impact + // an additional 0.005 ETH may be used to mint market tokens + // the swap impact pool is decreased by the used amount + // + // price_impact_usd is calculated based on pricing assuming only depositAmount of tokenIn + // was added to the pool + // since impactAmount of tokenOut is added to the pool here, the calculation of + // the price impact would not be entirely accurate + // + // it is possible that the addition of the positive impact amount of tokens into the pool + // could increase the imbalance of the pool, for most cases this should not be a significant + // change compared to the improvement of balance from the actual deposit + let positive_impact_amount = to_unsigned( + market_utils::apply_swap_impact_with_cap( + *params.data_store, + *params.event_emitter, + *_params.market.market_token, + *_params.token_out, + *_params.token_out_price, + to_signed(price_impact_usd, true), + ) + ); + + // calculate the usd amount using positiveImpactAmount since it may + // be capped by the max available amount in the impact pool + // use tokenOutPrice.max to get the USD value since the positiveImpactAmount + // was calculated using a USD value divided by tokenOutPrice.max + // + // for the initial deposit, the pool value and token supply would be zero + // so the market token price is treated as 1 USD + // + // it is possible for the pool value to be more than zero and the token supply + // to be zero, in that case, the market token price is also treated as 1 USD + mint_amount = + market_utils::usd_to_market_token_amount( + positive_impact_amount * *_params.token_out_price.max, + to_unsigned(pool_value), + market_tokens_supply, + ); + + market_utils::apply_delta_to_pool_amount( + *params.data_store, + *params.event_emitter, + *_params.market, + *_params.token_out, + to_signed(positive_impact_amount, false), + ); + + market_utils::validate_pool_amount(params.data_store, _params.market, *_params.token_out,); + + if (price_impact_usd < 0) { + // when there is a negative price impact factor, + // less of the deposit amount is used to mint market tokens + // for example, if 10 ETH is deposited and there is a negative price impact + // only 9.995 ETH may be used to mint market tokens + // the remaining 0.005 ETH will be stored in the swap impact pool + let negative_impact_amount = market_utils::apply_swap_impact_with_cap( + *params.data_store, + *params.event_emitter, + *_params.market.market_token, + *_params.token_out, + *_params.token_out_price, + to_signed(price_impact_usd, false), + ); + + amount_after_fees -= to_unsigned((-negative_impact_amount)); + } + } + + mint_amount += + market_utils::usd_to_market_token_amount( + amount_after_fees * *_params.token_in_price.min, + to_unsigned(pool_value), + market_tokens_supply, + ); + + market_utils::apply_delta_to_pool_amount( + *params.data_store, + *params.event_emitter, + *_params.market, + *_params.token_out, + to_signed(amount_after_fees + fees.fee_amount_for_pool, false), + ); + + market_utils::validate_pool_amount(params.data_store, _params.market, *_params.token_in); + + IMarketTokenDispatcher { contract_address: *_params.market.market_token } + .mint(*_params.receiver, mint_amount); + + mint_amount } #[inline(always)] fn swap( - params: ExecuteDepositParams, + params: @ExecuteDepositParams, swap_path: Span32, initial_token: ContractAddress, - intput_amount: u128, + input_amount: u128, market: ContractAddress, expected_output_token: ContractAddress, ui_fee_receiver: ContractAddress ) -> u128 { - //TODO - 0 + let swap_path_markets = market_utils::get_swap_path_markets(*params.data_store, swap_path,); + + let (output_token, output_amount) = swap_utils::swap( + @swap_utils::SwapParams { + data_store: *params.data_store, + event_emitter: *params.event_emitter, + oracle: *params.oracle, + bank: IBankDispatcher { contract_address: market }, + key: *params.key, + token_in: initial_token, + amount_in: input_amount, + swap_path_markets: swap_path_markets.span(), + min_output_amount: 0, + receiver: market, + ui_fee_receiver: ui_fee_receiver, + } + ); + + if output_token != expected_output_token { + SwapError::INVALID_SWAP_OUTPUT_TOKEN(output_token, expected_output_token) + } + + market_utils::validate_markets_token_balance(*params.data_store, swap_path_markets.span(),); + + output_amount } diff --git a/src/event/event_emitter.cairo b/src/event/event_emitter.cairo index 4b232baa..73ca5035 100755 --- a/src/event/event_emitter.cairo +++ b/src/event/event_emitter.cairo @@ -1992,7 +1992,7 @@ mod EventEmitter { /// * `market` - The market where fees were collected. /// * `collateral_token` - The collateral token. /// * `trade_size_usd` - The trade size in usd. - /// * `is_increase` - Wether it is an increase. + /// * `is_increase` - Whether it is an increase. /// * `fees` - The struct storing position fees. fn emit_position_fees_collected( ref self: ContractState, @@ -2063,7 +2063,7 @@ mod EventEmitter { /// * `market` - The market where fees were collected. /// * `collateral_token` - The collateral token. /// * `trade_size_usd` - The trade size in usd. - /// * `is_increase` - Wether it is an increase. + /// * `is_increase` - Whether it is an increase. /// * `fees` - The struct storing position fees. fn emit_position_fees_info( ref self: ContractState, @@ -2225,7 +2225,7 @@ mod EventEmitter { /// # Arguments // * `market`- Address of the market for the ADL state update // * `is_long`- Indicates the ADL state update is for the long or short side of the market - // * `pnl_to_pool_factor`- The the ratio of PnL to pool value + // * `pnl_to_pool_factor`- The ratio of PnL to pool value // * `max_pnl_factor`- The max PnL factor // * `should_enable_adl`- Whether ADL was enabled or disabled fn emit_adl_state_updated( diff --git a/src/event/event_utils.cairo b/src/event/event_utils.cairo index f61794de..4b4cc7d1 100644 --- a/src/event/event_utils.cairo +++ b/src/event/event_utils.cairo @@ -5,8 +5,7 @@ use traits::Default; use satoru::utils::traits::ContractAddressDefault; //TODO Switch the append with a set in the functions when its available - -#[derive(Default, Drop, Serde)] +#[derive(Drop, Serde)] struct EventLogData { cant_be_empty: u128, // remove // TODO diff --git a/src/exchange/adl_handler.cairo b/src/exchange/adl_handler.cairo index 87e1eae5..21bfc43b 100644 --- a/src/exchange/adl_handler.cairo +++ b/src/exchange/adl_handler.cairo @@ -21,7 +21,7 @@ trait IAdlHandler { /// Checks the ADL state to update the isAdlEnabled flag. /// # Arguments /// * `market` - The market to check. - /// * `is_long` - Wether to check long or short side. + /// * `is_long` - Whether to check long or short side. /// * `oracle_params` - The oracle set price parameters used to set price /// before performing checks fn update_adl_state( @@ -40,7 +40,7 @@ trait IAdlHandler { /// position profit, this is not implemented within the contracts at the moment. /// # Arguments /// * `market` - The market to check. - /// * `is_long` - Wether to check long or short side. + /// * `is_long` - Whether to check long or short side. /// * `oracle_params` - The oracle set price parameters used to set price /// before performing adl. fn execute_adl( @@ -110,7 +110,7 @@ mod AdlHandler { max_oracle_block_numbers: Array, /// The key of the adl to execute. key: felt252, - /// Wether adl should be allowed, depending on pnl state. + /// Whether adl should be allowed, depending on pnl state. should_allow_adl: bool, /// The maximum pnl factor to allow adl. max_pnl_factor_for_adl: u128, diff --git a/src/exchange/base_order_handler.cairo b/src/exchange/base_order_handler.cairo index b182fc1c..988f61e0 100644 --- a/src/exchange/base_order_handler.cairo +++ b/src/exchange/base_order_handler.cairo @@ -175,7 +175,7 @@ mod BaseOrderHandler { impl InternalImpl of InternalTrait { /// Get the BaseOrderUtils.ExecuteOrderParams to execute an order. /// # Arguments - /// * `key` - The the key of the order to execute. + /// * `key` - The key of the order to execute. /// * `oracle_params` - The set price parameters for oracle. /// * `keeper` - The keeper executing the order. /// * `starting_gas` - The starting gas. diff --git a/src/order/base_order_utils.cairo b/src/order/base_order_utils.cairo index 859aae89..99ec45f5 100644 --- a/src/order/base_order_utils.cairo +++ b/src/order/base_order_utils.cairo @@ -256,7 +256,7 @@ fn validate_order_trigger_price( }; if !ok { - panic_invalid_prices(primary_price, trigger_price, order_type); + OrderError::INVALID_ORDER_PRICE(primary_price, trigger_price, order_type); } return; @@ -276,7 +276,7 @@ fn validate_order_trigger_price( }; if !ok { - panic_invalid_prices(primary_price, trigger_price, order_type); + OrderError::INVALID_ORDER_PRICE(primary_price, trigger_price, order_type); } return; @@ -296,7 +296,7 @@ fn validate_order_trigger_price( }; if !ok { - panic_invalid_prices(primary_price, trigger_price, order_type); + OrderError::INVALID_ORDER_PRICE(primary_price, trigger_price, order_type); } return; @@ -340,7 +340,7 @@ fn get_execution_price_for_increase( // it may also be possible for users to prevent the execution of orders from other users // by manipulating the price impact, though this should be costly - panic_unfulfillable(execution_price, acceptable_price); + OrderError::ORDER_NOT_FULFILLABLE_AT_ACCEPTABLE_PRICE(execution_price, acceptable_price); 0 // doesn't compile otherwise } @@ -416,18 +416,11 @@ fn get_execution_price_for_decrease( if adjusted_price_impact_usd < 0 && calc::to_unsigned(-adjusted_price_impact_usd) > size_delta_usd { - panic( - array![ - OrderError::PRICE_IMPACT_LARGER_THAN_ORDER_SIZE, - adjusted_price_impact_usd.into(), - size_delta_usd.into() - ] + OrderError::PRICE_IMPACT_LARGER_THAN_ORDER_SIZE( + adjusted_price_impact_usd, size_delta_usd ); } - // error: Trait has no implementation in context: core::traits::Div:: - // TODO: uncomment this when i128 division available - // let adjustment = precision::mul_div_inum(position_size_in_usd, adjusted_price_impact_usd, position_size_in_tokens) / size_delta_usd.try_into().unwrap(); let numerator = precision::mul_div_inum( position_size_in_usd, adjusted_price_impact_usd, position_size_in_tokens ); @@ -436,15 +429,12 @@ fn get_execution_price_for_decrease( let _execution_price: i128 = calc::to_signed(price, true) + adjustment; if _execution_price < 0 { - panic( - array![ - OrderError::NEGATIVE_EXECUTION_PRICE, - _execution_price.into(), - price.into(), - position_size_in_usd.into(), - adjusted_price_impact_usd.into(), - size_delta_usd.into() - ] + OrderError::NEGATIVE_EXECUTION_PRICE( + _execution_price, + price, + position_size_in_usd, + adjusted_price_impact_usd, + size_delta_usd ); } @@ -482,7 +472,7 @@ fn get_execution_price_for_decrease( // // it may also be possible for users to prevent the execution of orders from other users // by manipulating the price impact, though this should be costly - panic_unfulfillable(execution_price, acceptable_price); + OrderError::ORDER_NOT_FULFILLABLE_AT_ACCEPTABLE_PRICE(execution_price, acceptable_price); 0 } @@ -497,27 +487,3 @@ fn validate_non_empty_order(order: @Order) { OrderError::EMPTY_ORDER ); } - -#[inline(always)] -fn panic_invalid_prices(primary_price: Price, trigger_price: u128, order_type: OrderType) { - panic( - array![ - OrderError::INVALID_ORDER_PRICES, - primary_price.min.into(), - primary_price.max.into(), - trigger_price.into(), - order_type.into(), - ] - ); -} - -#[inline(always)] -fn panic_unfulfillable(execution_price: u128, acceptable_price: u128) { - panic( - array![ - OrderError::ORDER_NOT_FULFILLABLE_AT_ACCEPTABLE_PRICE, - execution_price.into(), - acceptable_price.into() - ] - ); -} diff --git a/src/order/error.cairo b/src/order/error.cairo index 25dd29df..c8b06797 100644 --- a/src/order/error.cairo +++ b/src/order/error.cairo @@ -1,16 +1,57 @@ mod OrderError { + use satoru::order::order::OrderType; + use satoru::price::price::Price; + const EMPTY_ORDER: felt252 = 'empty_order'; + const INVALID_ORDER_PRICES: felt252 = 'invalid_order_prices'; const INVALID_KEEPER_FOR_FROZEN_ORDER: felt252 = 'invalid_keeper_for_frozen_order'; const UNSUPPORTED_ORDER_TYPE: felt252 = 'unsupported_order_type'; - const INVALID_ORDER_PRICES: felt252 = 'invalid_order_prices'; const INVALID_FROZEN_ORDER_KEEPER: felt252 = 'invalid_frozen_order_keeper'; const ORDER_NOT_FOUND: felt252 = 'order_not_found'; const ORDER_INDEX_NOT_FOUND: felt252 = 'order_index_not_found'; const CANT_BE_ZERO: felt252 = 'order account cant be 0'; - const ORDER_NOT_FULFILLABLE_AT_ACCEPTABLE_PRICE: felt252 = - 'order_unfulfillable_at_price'; // TODO: unshorten value - const NEGATIVE_EXECUTION_PRICE: felt252 = 'negative_execution_price'; - const PRICE_IMPACT_LARGER_THAN_ORDER_SIZE: felt252 = - 'price_impact_too_large'; // TODO: unshorten value const EMPTY_SIZE_DELTA_IN_TOKENS: felt252 = 'empty_size_delta_in_tokens'; + + fn INVALID_ORDER_PRICE(primary_price: Price, trigger_price: u128, order_type: OrderType) { + let mut data: Array = array![]; + data.append('invalid_order_price'); + data.append(primary_price.min.into()); + data.append(primary_price.max.into()); + data.append(trigger_price.into()); + data.append(order_type.into()); + panic(data); + } + + fn ORDER_NOT_FULFILLABLE_AT_ACCEPTABLE_PRICE(execution_price: u128, acceptable_price: u128) { + let mut data: Array = array![]; + data.append('order_unfulfillable_at_price'); + data.append(execution_price.into()); + data.append(acceptable_price.into()); + panic(data); + } + + fn PRICE_IMPACT_LARGER_THAN_ORDER_SIZE(price_impact_usd: i128, size_delta_usd: u128) { + let mut data: Array = array![]; + data.append('price_impact_too_large'); + data.append(price_impact_usd.into()); + data.append(size_delta_usd.into()); + panic(data); + } + + fn NEGATIVE_EXECUTION_PRICE( + execution_price: i128, + price: u128, + position_size_in_usd: u128, + adjusted_price_impact_usd: i128, + size_delta_usd: u128 + ) { + let mut data: Array = array![]; + data.append('negative_execution_price'); + data.append(execution_price.into()); + data.append(price.into()); + data.append(position_size_in_usd.into()); + data.append(adjusted_price_impact_usd.into()); + data.append(size_delta_usd.into()); + panic(data); + } } diff --git a/src/order/increase_order_utils.cairo b/src/order/increase_order_utils.cairo index a509ebce..5107cc1d 100644 --- a/src/order/increase_order_utils.cairo +++ b/src/order/increase_order_utils.cairo @@ -24,8 +24,8 @@ use alexandria_data_structures::array_ext::SpanTraitExt; /// This function should return an EventLogData cause the callback_utils /// needs it. We need to find a solution for that case. #[inline(always)] -fn process_order(params: ExecuteOrderParams) -> event_utils::EventLogData { - market_utils::validate_position_market_check(params.contracts.data_store, params.market); +fn process_order(params: ExecuteOrderParams) -> event_utils::LogData { + market_utils::validate_position_market(params.contracts.data_store, params.market.market_token); let (collateral_token, collateral_increment_amount) = swap_utils::swap( @swap_utils::SwapParams { @@ -88,7 +88,8 @@ fn process_order(params: ExecuteOrderParams) -> event_utils::EventLogData { collateral_increment_amount ); - event_utils::EventLogData { cant_be_empty: 'todo' } // TODO switch to LogData + let log: event_utils::LogData = Default::default(); // TODO + log } /// Validate the oracle block numbers used for the prices in the oracle. diff --git a/src/pricing/swap_pricing_utils.cairo b/src/pricing/swap_pricing_utils.cairo index 670c2d09..806aa032 100644 --- a/src/pricing/swap_pricing_utils.cairo +++ b/src/pricing/swap_pricing_utils.cairo @@ -53,7 +53,7 @@ struct PoolParams { } /// Struct to contain swap fee values. -#[derive(Drop, Clone, starknet::Store, Serde)] +#[derive(Copy, Drop, starknet::Store, Serde)] struct SwapFees { /// The fee amount for the fee receiver. fee_receiver_amount: u128, diff --git a/src/reader/reader_pricing_utils.cairo b/src/reader/reader_pricing_utils.cairo index 27b62c9d..2a2ea11e 100644 --- a/src/reader/reader_pricing_utils.cairo +++ b/src/reader/reader_pricing_utils.cairo @@ -121,7 +121,7 @@ fn get_swap_amount_out( // an additional 100 USDC may be sent to the user // the swap impact pool is decreased by the used amount - cache.amount_in = fees.clone().amount_after_fees; + cache.amount_in = fees.amount_after_fees; //round amount_out down error_utils::check_division_by_zero(cache.token_out_price.max, 'token_out_price.max'); cache.amount_out = cache.amount_in * cache.token_in_price.min / cache.token_out_price.max; diff --git a/src/router/error.cairo b/src/router/error.cairo index 61bfe0a3..e86cb69d 100644 --- a/src/router/error.cairo +++ b/src/router/error.cairo @@ -1,3 +1,36 @@ mod RouterError { + use starknet::ContractAddress; + const ALREADY_INITIALIZED: felt252 = 'already_initialized'; + const DEPOSIT_NOT_VALID: felt252 = 'deposit_not_valid'; + const WITHDRAWAL_NOT_VALID: felt252 = 'withdrawal_not_valid'; + const ORDER_NOT_VALID: felt252 = 'order_not_valid'; + const EMPTY_DEPOSIT: felt252 = 'empty_deposit'; + const EMPTY_ORDER: felt252 = 'empty_order'; + + fn UNAUTHORIZED(sender: ContractAddress, message: felt252) { + let mut data = array![message.into()]; + data.append(sender.into()); + panic(data) + } + + fn INVALID_CLAIM_FUNDING_FEES_INPUT(markets_len: u32, tokens_len: u32) { + let mut data = array![markets_len.into(), tokens_len.into()]; + panic(data) + } + + fn INVALID_CLAIM_COLLATERAL_INPUT(markets_len: u32, tokens_len: u32, time_keys_len: u32) { + let mut data = array![markets_len.into(), tokens_len.into(), time_keys_len.into()]; + panic(data) + } + + fn INVALID_CLAIM_AFFILIATE_REWARDS_INPUT(markets_len: u32, tokens_len: u32) { + let mut data = array![markets_len.into(), tokens_len.into()]; + panic(data) + } + + fn INVALID_CLAIM_UI_FEES_INPUT(markets_len: u32, tokens_len: u32) { + let mut data = array![markets_len.into(), tokens_len.into()]; + panic(data) + } } diff --git a/src/router/exchange_router.cairo b/src/router/exchange_router.cairo index 1d655509..521cfbb9 100644 --- a/src/router/exchange_router.cairo +++ b/src/router/exchange_router.cairo @@ -176,7 +176,9 @@ mod ExchangeRouter { // ************************************************************************* // Core lib imports. - use starknet::ContractAddress; + use starknet::{ + get_caller_address, ContractAddress, contract_address_const, get_contract_address + }; use core::zeroable::Zeroable; use debug::PrintTrait; @@ -194,9 +196,20 @@ mod ExchangeRouter { use super::IExchangeRouter; use satoru::deposit::deposit_utils::CreateDepositParams; - use satoru::withdrawal::withdrawal_utils::CreateWithdrawalParams; + use satoru::withdrawal::{withdrawal::Withdrawal, withdrawal_utils::CreateWithdrawalParams}; use satoru::order::base_order_utils::CreateOrderParams; use satoru::oracle::oracle_utils::SimulatePricesParams; + use satoru::utils::account_utils; + use satoru::utils::global_reentrancy_guard; + use satoru::router::error::RouterError; + use satoru::deposit::deposit::Deposit; + use satoru::order::order::Order; + use satoru::callback::callback_utils; + use satoru::feature::feature_utils; + use satoru::market::market_utils; + use satoru::data::keys; + use satoru::referral::referral_utils; + use satoru::fee::fee_utils; // ************************************************************************* // STORAGE @@ -267,48 +280,153 @@ mod ExchangeRouter { impl ExchangeRouterImpl of super::IExchangeRouter { fn send_tokens( ref self: ContractState, token: ContractAddress, receiver: ContractAddress, amount: u128 - ) { //TODO + ) { + account_utils::validate_receiver(receiver); + let account = get_caller_address(); + self.router.read().plugin_transfer(token, account, receiver, amount); } fn create_deposit(ref self: ContractState, params: CreateDepositParams) -> felt252 { - //TODO - 0 + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let account = get_caller_address(); + + let key = self.deposit_handler.read().create_deposit(account, params); + + global_reentrancy_guard::non_reentrant_after(data_store); + + key } - fn cancel_deposit(ref self: ContractState, key: felt252) { //TODO + fn cancel_deposit(ref self: ContractState, key: felt252) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let deposit_result = data_store.get_deposit(key); + let mut deposit: Deposit = Default::default(); + + // Check if the deposit is valid + match deposit_result { + Option::Some(dep) => { + deposit = dep; + }, + Option::None => { + panic_with_felt252(RouterError::DEPOSIT_NOT_VALID) + } + }; + if (deposit.account == contract_address_const::<0>()) { + panic_with_felt252(RouterError::EMPTY_DEPOSIT) + } + + if (deposit.account != get_caller_address()) { + RouterError::UNAUTHORIZED(get_caller_address(), 'account for cancel_deposit') + } + + self.deposit_handler.read().cancel_deposit(key); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn create_withdrawal(ref self: ContractState, params: CreateWithdrawalParams) -> felt252 { - //TODO - 0 + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let account = get_caller_address(); + + let key = self.withdrawal_handler.read().create_withdrawal(account, params); + + global_reentrancy_guard::non_reentrant_after(data_store); + + key } - fn cancel_withdrawal(ref self: ContractState, key: felt252) { //TODO + fn cancel_withdrawal(ref self: ContractState, key: felt252) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let withdrawal_result = data_store.get_withdrawal(key); + let mut withdrawal: Withdrawal = Default::default(); + + // Check if the withdrawal is valid + match withdrawal_result { + Option::Some(withd) => { + withdrawal = withd; + }, + Option::None => { + panic_with_felt252(RouterError::WITHDRAWAL_NOT_VALID) + } + }; + + if (withdrawal.account != get_caller_address()) { + RouterError::UNAUTHORIZED(get_caller_address(), 'account for cancel_withdrawal') + } + + self.withdrawal_handler.read().cancel_withdrawal(key); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn create_order(ref self: ContractState, params: CreateOrderParams) -> felt252 { - //TODO - 0 + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let account = get_caller_address(); + + let key = self.order_handler.read().create_order(account, params); + + global_reentrancy_guard::non_reentrant_after(data_store); + + key } fn set_saved_callback_contract( ref self: ContractState, market: ContractAddress, callback_contract: ContractAddress - ) { //TODO + ) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + callback_utils::set_saved_callback_contract( + data_store, get_caller_address(), market, callback_contract + ); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn simulate_execute_deposit( ref self: ContractState, key: felt252, simulated_oracle_params: SimulatePricesParams - ) { //TODO + ) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + self.deposit_handler.read().simulate_execute_deposit(key, simulated_oracle_params); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn simulate_execute_withdrawal( ref self: ContractState, key: felt252, simulated_oracle_params: SimulatePricesParams - ) { //TODO + ) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + self + .withdrawal_handler + .read() + .simulate_execute_withdrawal(key, simulated_oracle_params); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn simulate_execute_order( ref self: ContractState, key: felt252, simulated_oracle_params: SimulatePricesParams - ) { //TODO + ) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + self.order_handler.read().simulate_execute_order(key, simulated_oracle_params); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn update_order( @@ -318,10 +436,62 @@ mod ExchangeRouter { acceptable_price: u128, trigger_price: u128, min_output_amout: u128 - ) { //TODO + ) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let order_result = data_store.get_order(key); + let mut order: Order = Default::default(); + + // Check if the order is valid + match order_result { + Option::Some(ord) => { + order = ord; + }, + Option::None => { + panic_with_felt252(RouterError::ORDER_NOT_VALID) + } + }; + + if (order.account != get_caller_address()) { + RouterError::UNAUTHORIZED(get_caller_address(), 'account for update_order') + } + self + .order_handler + .read() + .update_order( + key, size_delta_usd, acceptable_price, trigger_price, min_output_amout, order + ); + + global_reentrancy_guard::non_reentrant_after(data_store); } - fn cancel_order(ref self: ContractState, key: felt252) { //TODO + fn cancel_order(ref self: ContractState, key: felt252) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let order_result = data_store.get_order(key); + let mut order: Order = Default::default(); + + // Check if the order is valid + match order_result { + Option::Some(ord) => { + order = ord; + }, + Option::None => { + panic_with_felt252(RouterError::ORDER_NOT_VALID); + } + }; + if (order.account != contract_address_const::<0>()) { + panic_with_felt252(RouterError::EMPTY_ORDER) + } + + if (order.account != get_caller_address()) { + RouterError::UNAUTHORIZED(get_caller_address(), 'account for cancel_order') + } + self.order_handler.read().cancel_order(key); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn claim_funding_fees( @@ -330,8 +500,45 @@ mod ExchangeRouter { tokens: Array, receiver: ContractAddress ) -> Array { - //TODO - ArrayTrait::new() + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + if (markets.len() != tokens.len()) { + RouterError::INVALID_CLAIM_FUNDING_FEES_INPUT(markets.len(), tokens.len()) + } + + feature_utils::validate_feature( + data_store, keys::claim_funding_fees_feature_disabled_key(get_contract_address()) + ); + + account_utils::validate_receiver(receiver); + + let account = get_caller_address(); + + let mut claimed_amounts: Array = ArrayTrait::new(); + + let mut i = 0; + loop { + if i == markets.len() { + break; + } + claimed_amounts + .append( + market_utils::claim_funding_fees( + data_store, + self.event_emitter.read(), + *markets[i], + *tokens[i], + account, + receiver + ) + ); + i += 1; + }; + + global_reentrancy_guard::non_reentrant_after(data_store); + + claimed_amounts } fn claim_collateral( @@ -341,8 +548,48 @@ mod ExchangeRouter { time_keys: Array, receiver: ContractAddress ) -> Array { - //TODO - ArrayTrait::new() + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + if (markets.len() != tokens.len() || tokens.len() != time_keys.len()) { + RouterError::INVALID_CLAIM_COLLATERAL_INPUT( + markets.len(), tokens.len(), time_keys.len() + ) + } + + feature_utils::validate_feature( + data_store, keys::claim_collateral_feature_disabled_key(get_contract_address()) + ); + + account_utils::validate_receiver(receiver); + + let account = get_caller_address(); + + let mut claimed_amounts: Array = ArrayTrait::new(); + + let mut i = 0; + loop { + if i == markets.len() { + break; + } + claimed_amounts + .append( + market_utils::claim_collateral( + data_store, + self.event_emitter.read(), + *markets[i], + *tokens[i], + *time_keys[i], + account, + receiver + ) + ); + i += 1; + }; + + global_reentrancy_guard::non_reentrant_after(data_store); + + claimed_amounts } fn claim_affiliate_rewards( @@ -351,11 +598,56 @@ mod ExchangeRouter { tokens: Array, receiver: ContractAddress ) -> Array { - //TODO - ArrayTrait::new() + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + if (markets.len() != tokens.len()) { + RouterError::INVALID_CLAIM_AFFILIATE_REWARDS_INPUT(markets.len(), tokens.len()) + } + + feature_utils::validate_feature( + data_store, + keys::claim_affiliate_rewards_feature_disabled_key(get_contract_address()) + ); + + let account = get_caller_address(); + + let mut claimed_amounts: Array = ArrayTrait::new(); + + let mut i = 0; + loop { + if i == markets.len() { + break; + } + claimed_amounts + .append( + referral_utils::claim_affiliate_reward( + data_store, + self.event_emitter.read(), + *markets[i], + *tokens[i], + account, + receiver + ) + ); + i = i + 1; + }; + + global_reentrancy_guard::non_reentrant_after(data_store); + + claimed_amounts } - fn set_ui_fee_factor(ref self: ContractState, ui_fee_factor: u128) { //TODO + fn set_ui_fee_factor(ref self: ContractState, ui_fee_factor: u128) { + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + let account = get_caller_address(); + market_utils::set_ui_fee_factor( + data_store, self.event_emitter.read(), account, ui_fee_factor + ); + + global_reentrancy_guard::non_reentrant_after(data_store); } fn claim_ui_fees( @@ -364,8 +656,43 @@ mod ExchangeRouter { tokens: Array, receiver: ContractAddress ) -> Array { - //TODO - ArrayTrait::new() + let data_store = self.data_store.read(); + global_reentrancy_guard::non_reentrant_before(data_store); + + if (markets.len() != tokens.len()) { + RouterError::INVALID_CLAIM_UI_FEES_INPUT(markets.len(), tokens.len()) + } + + feature_utils::validate_feature( + data_store, keys::claim_ui_fees_feature_disabled_key(get_contract_address()) + ); + + let ui_fee_receiver = get_caller_address(); + + let mut claimed_amounts: Array = ArrayTrait::new(); + + let mut i = 0; + loop { + if i == markets.len() { + break; + } + claimed_amounts + .append( + fee_utils::claim_ui_fees( + data_store, + self.event_emitter.read(), + ui_fee_receiver, + *markets[i], + *tokens[i], + receiver + ) + ); + i += 1; + }; + + global_reentrancy_guard::non_reentrant_after(data_store); + + claimed_amounts } } } diff --git a/src/swap/error.cairo b/src/swap/error.cairo index b756f4c8..33c572b6 100644 --- a/src/swap/error.cairo +++ b/src/swap/error.cairo @@ -29,4 +29,13 @@ mod SwapError { data.append(market.into()); panic(data) } + + fn INVALID_SWAP_OUTPUT_TOKEN( + output_token: ContractAddress, expected_output_token: ContractAddress + ) { + let mut data = array!['invalid swap output token']; + data.append(output_token.into()); + data.append(expected_output_token.into()); + panic(data) + } } diff --git a/tests/bank/test_strict_bank.cairo b/tests/bank/test_strict_bank.cairo new file mode 100644 index 00000000..1c09ee80 --- /dev/null +++ b/tests/bank/test_strict_bank.cairo @@ -0,0 +1,321 @@ +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Core lib imports. + +use result::ResultTrait; +use traits::{TryInto, Into}; +use starknet::{ContractAddress, get_caller_address, contract_address_const, ClassHash,}; +use integer::u256_from_felt252; +use debug::PrintTrait; +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait, ContractClass}; + +// Local imports. +use satoru::bank::bank::{IBankDispatcherTrait, IBankDispatcher}; +use satoru::bank::strict_bank::{IStrictBankDispatcher, IStrictBankDispatcherTrait}; +use satoru::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::role::role; + +/// Setup required contracts. +fn setup_contracts() -> ( + // This caller address will be used with `start_prank` cheatcode to mock the caller address., + ContractAddress, + // This receiver address will be used with `start_prank` cheatcode to mock the receiver address., + ContractAddress, + // Interface to interact with the `RoleStore` contract. + IRoleStoreDispatcher, + // Interface to interact with the `DataStore` contract. + IDataStoreDispatcher, + // Interface to interact with the `Bank` contract. + IBankDispatcher, + // Interface to interact with the `StrictBank` contract. + IStrictBankDispatcher +) { + // Deploy the role store contract. + let role_store_address = deploy_role_store(); + + // Create a role store dispatcher. + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + + // Deploy the contract. + let data_store_address = deploy_data_store(role_store_address); + + // Create a safe dispatcher to interact with the contract. + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + + // Deploy the bank contract + let bank_address = deploy_bank(data_store_address, role_store_address); + + //Create a safe dispatcher to interact with the Bank contract. + let bank = IBankDispatcher { contract_address: bank_address }; + + // Deploy the strict bank contract + let strict_bank_address = deploy_strict_bank(data_store_address, role_store_address); + + //Create a safe dispatcher to interact with the StrictBank contract. + let strict_bank = IStrictBankDispatcher { contract_address: strict_bank_address }; + + // start prank and give controller role to caller_address + let caller_address: ContractAddress = 0x101.try_into().unwrap(); + let receiver_address: ContractAddress = 0x202.try_into().unwrap(); + start_prank(role_store_address, caller_address); + role_store.grant_role(caller_address, role::CONTROLLER); + start_prank(data_store_address, caller_address); + start_prank(strict_bank_address, caller_address); + + (caller_address, receiver_address, role_store, data_store, bank, strict_bank) +} + +// /// Utility function to deploy a bank contract and return its address. +fn deploy_bank( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let contract = declare('Bank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + contract.deploy(@constructor_calldata).unwrap() +} + +/// Utility function to deploy a strict bank contract and return its address. +fn deploy_strict_bank( + data_store_address: ContractAddress, role_store_address: ContractAddress, +) -> ContractAddress { + let contract = declare('StrictBank'); + let mut constructor_calldata = array![]; + constructor_calldata.append(data_store_address.into()); + constructor_calldata.append(role_store_address.into()); + contract.deploy(@constructor_calldata).unwrap() +} + +/// Utility function to deploy a data store contract and return its address. +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let mut constructor_calldata = array![]; + constructor_calldata.append(role_store_address.into()); + contract.deploy(@constructor_calldata).unwrap() +} + +/// Utility function to deploy a data store contract and return its address. +/// Copied from `tests/role/test_role_store.rs`. +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + let constructor_arguments: @Array:: = @ArrayTrait::new(); + contract.deploy(constructor_arguments).unwrap() +} + +// ********************************************************************************************* +// * TEARDOWN * +// ********************************************************************************************* +fn teardown(data_store: IDataStoreDispatcher, strict_bank: IStrictBankDispatcher) { + stop_prank(data_store.contract_address); + stop_prank(strict_bank.contract_address); +} + + +#[test] +#[should_panic(expected: ('already_initialized',))] +fn test_initialize() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // try initializing after previously initializing in setup + strict_bank.initialize(data_store.contract_address, role_store.contract_address); + teardown(data_store, strict_bank); +} + +#[test] +fn given_normal_conditions_when_transfer_out_then_works() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array![ + 'satoru', 'STU', 1000, 0, strict_bank.contract_address.into() + ]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + // call the transfer_out function + strict_bank.transfer_out(erc20_contract_address, receiver_address, 100_u128); + // check that the contract balance reduces + let contract_balance = erc20_dispatcher.balance_of(strict_bank.contract_address); + assert(contract_balance == u256_from_felt252(900), 'transfer_out failed'); + // check that the balance of the receiver increases + let receiver_balance = erc20_dispatcher.balance_of(receiver_address); + assert(receiver_balance == u256_from_felt252(100), 'transfer_out failed'); + // teardown + teardown(data_store, strict_bank); +} + +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_has_no_controller_role_when_transfer_out_then_fails() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array![ + 'satoru', 'STU', 1000, 0, strict_bank.contract_address.into() + ]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + // stop prank as caller_address and start prank as receiver_address who has no controller role + stop_prank(strict_bank.contract_address); + start_prank(strict_bank.contract_address, receiver_address); + // call the transfer_out function + strict_bank.transfer_out(erc20_contract_address, caller_address, 100); + // teardown + teardown(data_store, strict_bank); +} + +#[test] +#[should_panic(expected: ('self_transfer_not_supported',))] +fn given_receiver_is_contract_when_transfer_out_then_fails() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // deploy erc20 token. Mint to strict_bank + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array![ + 'satoru', 'STU', 1000, 0, strict_bank.contract_address.into() + ]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + // transfer out with our strict_bank address as the receiver address + strict_bank.transfer_out(erc20_contract_address, strict_bank.contract_address, 100_u128); + + //teardown + teardown(data_store, strict_bank); +} + +#[test] +fn given_normal_conditions_when_record_transfer_in_works() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array!['satoru', 'STU', 1000, 0, caller_address.into()]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + start_prank(erc20_contract_address, caller_address); + + // send tokens into strict bank + erc20_dispatcher.transfer(strict_bank.contract_address, u256_from_felt252(50)); + + let new_balance: u128 = erc20_dispatcher + .balance_of(strict_bank.contract_address) + .try_into() + .unwrap(); + + assert( + strict_bank.record_transfer_in(erc20_contract_address) == new_balance, + 'unsuccessful transfer in' + ); + + // teardown + teardown(data_store, strict_bank); +} + +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_has_no_controller_role_when_record_transfer_in_then_fails() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array![ + 'satoru', 'STU', 1000, 0, strict_bank.contract_address.into() + ]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + // stop prank as caller_address and start prank as receiver_address who has no controller role + stop_prank(strict_bank.contract_address); + start_prank(strict_bank.contract_address, receiver_address); + // call the transfer_out function with receiver address + strict_bank.record_transfer_in(erc20_contract_address); + // teardown + teardown(data_store, strict_bank); +} + +#[test] +fn given_normal_conditions_when_sync_token_balance_passes() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array!['satoru', 'STU', 1000, 0, caller_address.into()]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + start_prank(erc20_contract_address, caller_address); + + // send tokens into strict bank + erc20_dispatcher.transfer(strict_bank.contract_address, u256_from_felt252(50)); + + //update the new balance by calling sync_token_balance + strict_bank.sync_token_balance(erc20_contract_address); + + // teardown + teardown(data_store, strict_bank); +} + +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_has_no_controller_role_when_sync_token_balance_then_fails() { + let (caller_address, receiver_address, role_store, data_store, bank, strict_bank) = + setup_contracts(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // deploy erc20 token + let erc20_contract = declare('ERC20'); + let constructor_calldata3 = array!['satoru', 'STU', 1000, 0, caller_address.into()]; + let erc20_contract_address = erc20_contract.deploy(@constructor_calldata3).unwrap(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_contract_address }; + + start_prank(erc20_contract_address, caller_address); + + // send tokens into strict bank + erc20_dispatcher.transfer(strict_bank.contract_address, u256_from_felt252(50)); + + // stop prank as caller_address and start prank as receiver_address who has no controller role + stop_prank(strict_bank.contract_address); + start_prank(strict_bank.contract_address, receiver_address); + + // call the sync_token_balance function with receiver address + strict_bank.sync_token_balance(erc20_contract_address); + // teardown + teardown(data_store, strict_bank); +} + diff --git a/tests/deposit/test_deposit_vault.cairo b/tests/deposit/test_deposit_vault.cairo index 96667d72..6808e2d7 100644 --- a/tests/deposit/test_deposit_vault.cairo +++ b/tests/deposit/test_deposit_vault.cairo @@ -10,7 +10,7 @@ use starknet::{ ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, ClassHash, }; -use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; +use snforge_std::{declare, start_prank, stop_prank, start_mock_call, ContractClassTrait}; use traits::{TryInto, Into}; // Local imports. @@ -89,11 +89,62 @@ fn given_receiver_is_contract_when_transfer_out_then_fails() { teardown(data_store, deposit_vault); } -/// TODO: implement the tests when record_transfer_in is implemented #[test] -#[should_panic(expected: ('NOT IMPLEMENTED YET',))] fn given_normal_conditions_when_record_transfer_in_then_works() { - assert(true == false, 'NOT IMPLEMENTED YET') + let (_, _, _, data_store, deposit_vault, erc20) = setup(); + + let initial_balance: u128 = u128_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u128 = deposit_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + teardown(data_store, deposit_vault); +} + +#[test] +fn given_more_balance_when_2nd_record_transfer_in_then_works() { + let (_, _, _, data_store, deposit_vault, erc20) = setup(); + + let initial_balance: u128 = u128_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u128 = deposit_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + let tokens_transfered_in: u128 = 250; + let mock_balance_with_more_tokens: u256 = (initial_balance + tokens_transfered_in).into(); + start_mock_call(erc20.contract_address, 'balance_of', mock_balance_with_more_tokens); + + let tokens_received: u128 = deposit_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == tokens_transfered_in, 'incorrect received amount'); + + teardown(data_store, deposit_vault); +} + +#[test] +#[should_panic(expected: ('u128_sub Overflow',))] +fn given_less_balance_when_2nd_record_transfer_in_then_fails() { + let (_, _, _, data_store, deposit_vault, erc20) = setup(); + + let initial_balance: u128 = u128_from_felt252(INITIAL_TOKENS_MINTED); + let tokens_received: u128 = deposit_vault.record_transfer_in(erc20.contract_address); + assert(tokens_received == initial_balance, 'should be initial balance'); + + let tokens_transfered_out: u128 = 250; + let mock_balance_with_less_tokens: u256 = (initial_balance - tokens_transfered_out).into(); + start_mock_call(erc20.contract_address, 'balance_of', mock_balance_with_less_tokens); + + deposit_vault.record_transfer_in(erc20.contract_address); + + teardown(data_store, deposit_vault); +} + +#[test] +#[should_panic(expected: ('unauthorized_access',))] +fn given_caller_is_not_controller_when_record_transfer_in_then_fails() { + let (caller_address, _, role_store, data_store, deposit_vault, erc20) = setup(); + + role_store.revoke_role(caller_address, role::CONTROLLER); + deposit_vault.record_transfer_in(erc20.contract_address); + + teardown(data_store, deposit_vault); } // ********************************************************************************************* diff --git a/tests/lib.cairo b/tests/lib.cairo index 98b2f2a9..0e77cf5b 100644 --- a/tests/lib.cairo +++ b/tests/lib.cairo @@ -3,6 +3,7 @@ mod adl { } mod bank { mod test_bank; + mod test_strict_bank; } mod callback { mod test_callback_utils; diff --git a/tests/mock/test_governable.cairo b/tests/mock/test_governable.cairo index 2b4ce316..cdad3de1 100644 --- a/tests/mock/test_governable.cairo +++ b/tests/mock/test_governable.cairo @@ -118,6 +118,9 @@ fn setup_with_other_address() -> ( //TODO add more tests +// This test checks the 'only_gov' function under normal conditions. +// It sets up the environment with the correct initial governance, then calls `only_gov`. +// The test expects the call to succeed without any errors. #[test] fn given_normal_conditions_when_only_gov_then_works() { let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = @@ -126,6 +129,9 @@ fn given_normal_conditions_when_only_gov_then_works() { teardown(data_store.contract_address); } +// This test checks the `only_gov` function when the governance condition is not met. +// It sets up the environment with a different governance, then calls `only_gov`. +// The test expects the call to panic with the error 'Unauthorized gov caller'. #[test] #[should_panic(expected: ('Unauthorized gov caller',))] fn given_forbidden_when_only_gov_then_fails() { @@ -135,6 +141,10 @@ fn given_forbidden_when_only_gov_then_fails() { teardown(data_store.contract_address); } +// This test checks the `transfer_ownership` function under normal conditions. +// It sets up the environment with the correct initial governance, then calls `transfer_ownership` +// with a new governance address. +// The test expects the call to succeed and the ownership to be transferred without any errors. #[test] fn given_normal_conditions_when_transfer_ownership_then_works() { let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = @@ -143,3 +153,79 @@ fn given_normal_conditions_when_transfer_ownership_then_works() { governable.transfer_ownership(new_caller_address); teardown(data_store.contract_address); } + +/// This test case verifies the `transfer_ownership` function behavior when called by an unauthorized address. +/// The expected outcome is a panic with the error message "Unauthorized gov caller" which corresponds +/// to the `UNAUTHORIZED_GOV` error in the `MockError` module. +#[test] +#[should_panic(expected: ('Unauthorized gov caller',))] +fn given_unauthorized_caller_when_transfer_ownership_then_fails() { + // Setup the environment with a different caller address. + let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = + setup_with_other_address(); + + // Try to transfer ownership to a new address. + let new_uncaller_address: ContractAddress = 0x102.try_into().unwrap(); + governable.transfer_ownership(new_uncaller_address); + teardown(data_store.contract_address); +} + +/// This test checks the `accept_ownership` function under normal conditions. +/// It sets up the environment with the correct initial governance, then calls `transfer_ownership` +/// to a new governance address, followed by `accept_ownership` from the new governance address. +/// The test expects the call to succeed and the ownership to be accepted without any errors. +#[test] +fn given_normal_conditions_when_accept_ownership_then_works() { + let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = + setup(); + let new_caller_address: ContractAddress = 0x102.try_into().unwrap(); + + // Transfer the ownership to the new address. + governable.transfer_ownership(new_caller_address); + + // Update the prank context to the new governance address, to simulate the new governor accepting the ownership. + start_prank(governable.contract_address, new_caller_address); + + // Now call accept_ownership from the new governance address. + governable.accept_ownership(); + teardown(data_store.contract_address); +} + +/// This test checks the `accept_ownership` function under abnormal conditions. +/// It sets up the environment with the correct initial governance, then calls `transfer_ownership` +/// to a new governance address. However, `accept_ownership` is then called from an unauthorized address. +/// The test expects the call to panic with the error 'Unauthorized pending_gov caller'. +#[test] +#[should_panic(expected: ('Unauthorized pending_gov caller',))] +fn given_abnormal_conditions_when_accept_ownership_then_fails() { + let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = + setup(); + let new_caller_address: ContractAddress = 0x102.try_into().unwrap(); + let unauthorized_address: ContractAddress = 0x103.try_into().unwrap(); + + // Transfer the ownership to the new address. + governable.transfer_ownership(new_caller_address); + + // Update the prank context to an unauthorized address, to simulate an unauthorized attempt to accept the ownership. + start_prank(governable.contract_address, unauthorized_address); + + // Now call accept_ownership from the unauthorized address. + governable.accept_ownership(); + teardown(data_store.contract_address); +} + +#[test] +#[should_panic(expected: ('already_initialized',))] +fn given_already_initialized_when_initialize_then_fails() { + // Setup the environment. + let (caller_address, role_store, data_store, event_emitter, referral_storage, governable) = + setup(); + + // Assume that the contract has been initialized during setup. + // Try to initialize it again with the same event emitter address. + let event_emitter_address = event_emitter.contract_address; + + // This call should panic with the error 'already_initialized'. + governable.initialize(event_emitter_address); + teardown(data_store.contract_address); +} diff --git a/tests/order/test_base_order_utils.cairo b/tests/order/test_base_order_utils.cairo index 5f2a23ae..5a6eebca 100644 --- a/tests/order/test_base_order_utils.cairo +++ b/tests/order/test_base_order_utils.cairo @@ -1,13 +1,7 @@ -use starknet::ContractAddress; -use snforge_std::{start_mock_call, stop_mock_call}; - -use satoru::data::data_store::IDataStoreDispatcherTrait; -use satoru::nonce::nonce_utils::{get_current_nonce, increment_nonce, compute_key}; -use satoru::tests_lib::{setup, teardown}; -use satoru::oracle::oracle::{IOracleSafeDispatcher, IOracleDispatcher, IOracleDispatcherTrait}; -use satoru::order::{ - error::OrderError, order::{Order, SecondaryOrderType, OrderType, DecreasePositionSwapType}, -}; +use starknet::{ContractAddress, contract_address_const}; +use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; + +use satoru::order::{order::{OrderType, Order},}; use satoru::price::price::{Price, PriceTrait}; use satoru::order::base_order_utils::{ is_market_order, is_limit_order, is_swap_order, is_position_order, is_increase_order, @@ -15,37 +9,221 @@ use satoru::order::base_order_utils::{ get_execution_price_for_increase, get_execution_price_for_decrease, validate_non_empty_order }; +use satoru::data::data_store::{DataStore, IDataStoreDispatcher, IDataStoreDispatcherTrait}; +use satoru::event::event_emitter::{EventEmitter, IEventEmitterDispatcher}; +use satoru::oracle::oracle::{Oracle, IOracleDispatcher, IOracleDispatcherTrait, SetPricesParams}; +use satoru::oracle::oracle_store::{IOracleStoreDispatcher, IOracleStoreDispatcherTrait}; +use satoru::oracle::price_feed::PriceFeed; +use satoru::role::role_store::{IRoleStoreDispatcher, IRoleStoreDispatcherTrait}; +use satoru::role::role; + +#[test] +fn given_normal_conditions_when_is_market_order_then_works() { + // Test market orders + assert(is_market_order(OrderType::MarketSwap), 'invalid market swap res'); + assert(is_market_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(is_market_order(OrderType::MarketDecrease), 'invalid market dec. res'); + + // Test other orders + assert(!is_market_order(OrderType::LimitSwap), 'invalid limit swap res'); + assert(!is_market_order(OrderType::LimitIncrease), 'invalid limit inc. res'); + assert(!is_market_order(OrderType::StopLossDecrease), 'invalid stop loss res '); +} + +#[test] +fn given_normal_conditions_when_is_limit_order_then_works() { + // Test limit orders + assert(is_limit_order(OrderType::LimitSwap), 'invalid limit swap res'); + assert(is_limit_order(OrderType::LimitIncrease), 'invalid limit inc. res'); + assert(is_limit_order(OrderType::LimitDecrease), 'invalid limit dec. res'); + + // Test other orders + assert(!is_limit_order(OrderType::MarketSwap), 'invalid market swap res'); + assert(!is_limit_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(!is_limit_order(OrderType::StopLossDecrease), 'invalid stop loss res '); +} + +#[test] +fn given_normal_conditions_when_is_swap_order_then_works() { + // Test swap orders + assert(is_swap_order(OrderType::MarketSwap), 'invalid market swap res'); + assert(is_swap_order(OrderType::LimitSwap), 'invalid limit swap res'); + + // Test other orders + assert(!is_swap_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(!is_swap_order(OrderType::LimitIncrease), 'invalid limit inc. res '); +} + + #[test] fn given_normal_conditions_when_is_position_order_then_works() { - assert(!is_position_order(OrderType::MarketSwap), 'Should not be position'); - assert(is_position_order(OrderType::MarketIncrease), 'Should be position'); + // Test position orders + assert(is_position_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(is_position_order(OrderType::LimitIncrease), 'invalid limit inc. res'); + assert(is_position_order(OrderType::StopLossDecrease), 'invalid stop loss res'); + assert(is_position_order(OrderType::Liquidation), 'invalid liquidation res'); + + // Test other orders + assert(!is_position_order(OrderType::LimitSwap), 'invalid limit swap res'); + assert(!is_position_order(OrderType::MarketSwap), 'invalid market swap res '); +} + + +#[test] +fn given_normal_conditions_when_is_increase_order_then_works() { + // Test position orders + assert(is_increase_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(is_increase_order(OrderType::LimitIncrease), 'invalid limit inc. res'); + + // Test other orders + assert(!is_increase_order(OrderType::MarketDecrease), 'invalid market dec. res'); + assert(!is_increase_order(OrderType::MarketSwap), 'invalid market swap res '); +} + +#[test] +fn given_normal_conditions_when_is_decrease_order_then_works() { + // Test position orders + assert(is_decrease_order(OrderType::MarketDecrease), 'invalid market dec. res'); + assert(is_decrease_order(OrderType::LimitDecrease), 'invalid limit dec. res'); + assert(is_decrease_order(OrderType::StopLossDecrease), 'invalid stop loss res'); + assert(is_decrease_order(OrderType::Liquidation), 'invalid liquidation res'); + + // Test other orders + assert(!is_decrease_order(OrderType::MarketIncrease), 'invalid market inc. res'); + assert(!is_decrease_order(OrderType::LimitIncrease), 'invalid limit inc. res'); +} + +#[test] +fn given_normal_conditions_when_is_liquidation_order_then_works() { + // Test position orders + assert(is_liquidation_order(OrderType::Liquidation), 'invalid liquidation inc. res'); + // Test other orders + assert(!is_liquidation_order(OrderType::MarketDecrease), 'invalid market dec. res'); + assert(!is_liquidation_order(OrderType::MarketSwap), 'invalid market swap res '); } + #[test] fn given_normal_conditions_when_validate_order_trigger_price_then_works() { - // TODO when oracle - // let oracle_address: ContractAddress = 'oracle'.try_into().unwrap(); - // start_mock_call(oracle_address, 'get_primary_price', Price { min: 9, max: 11 }); - // let oracle = IOracleSafeDispatcher { contract_address: oracle_address }; - // validate_order_trigger_price( - // oracle, - // index_token: 'token'.try_into().unwrap(), - // order_type: OrderType::LimitIncrease, - // trigger_price: 10, - // is_long: true, - // ); - // stop_mock_call(oracle_address, 'get_primary_price'); - assert(true, 'Tautology'); + // Setup + let (_, _, _, oracle) = setup(); + let index_token = contract_address_const::<'ETH'>(); + let price = Price { min: 100000, max: 200000 }; + oracle.set_primary_price(index_token, price); + + // Test + + // Test swap orders validates + validate_order_trigger_price(oracle, index_token, OrderType::MarketSwap, 100, true); + + // Test limit increase orders + validate_order_trigger_price( + oracle, index_token, OrderType::LimitIncrease, price.max + 1, true + ); + + validate_order_trigger_price( + oracle, index_token, OrderType::LimitIncrease, price.min - 1, false + ); + + // Test limit decrease orders + validate_order_trigger_price( + oracle, index_token, OrderType::LimitDecrease, price.min - 1, true + ); + + validate_order_trigger_price( + oracle, index_token, OrderType::LimitDecrease, price.max + 1, false + ); + + // Test stop loss orders + validate_order_trigger_price( + oracle, index_token, OrderType::StopLossDecrease, price.min + 1, true + ); + + validate_order_trigger_price( + oracle, index_token, OrderType::StopLossDecrease, price.max - 1, false + ); + + assert(true, 'e'); +} + +#[test] +#[should_panic( + expected: ('invalid_order_price', 100000, 200000, 199999, 6053968548023263173723725853541) +)] +fn given_limit_increase_price_higher_than_trigger_when_validate_order_trigger_price_then_fails() { + // Setup + let (_, _, _, oracle) = setup(); + let index_token = contract_address_const::<'ETH'>(); + let price = Price { min: 100000, max: 200000 }; + oracle.set_primary_price(index_token, price); + + // Test + validate_order_trigger_price( + oracle, index_token, OrderType::LimitIncrease, price.max - 1, true + ); +} + + +#[test] +#[should_panic( + expected: ('invalid_order_price', 100000, 200000, 199999, 6053968548022900352478745817957) +)] +fn given_limit_decrease_price_lower_than_trigger_when_validate_order_trigger_price_then_fails() { + // Setup + let (_, _, _, oracle) = setup(); + let index_token = contract_address_const::<'ETH'>(); + let price = Price { min: 100000, max: 200000 }; + oracle.set_primary_price(index_token, price); + + // Test + validate_order_trigger_price( + oracle, index_token, OrderType::LimitDecrease, price.max - 1, false + ); } +#[test] +#[should_panic( + expected: ( + 'invalid_order_price', 100000, 200000, 99999, 110930490330413861099797394456752255845 + ) +)] +fn given_stop_loss_price_lower_than_trigger_when_validate_order_trigger_price_then_fails() { + // Setup + let (_, _, _, oracle) = setup(); + let index_token = contract_address_const::<'ETH'>(); + let price = Price { min: 100000, max: 200000 }; + oracle.set_primary_price(index_token, price); + + // Test + validate_order_trigger_price( + oracle, index_token, OrderType::StopLossDecrease, price.min - 1, true + ); +} + + #[test] fn given_normal_conditions_when_get_execution_price_for_increase_then_works() { let price = get_execution_price_for_increase( size_delta_usd: 200, size_delta_in_tokens: 20, acceptable_price: 10, is_long: true, ); - assert(price == 10, 'Should be 10'); + assert(price == 10, 'invalid price'); + + let price = get_execution_price_for_increase( + size_delta_usd: 400, size_delta_in_tokens: 10, acceptable_price: 20, is_long: false, + ); + assert(price == 40, 'invalid price2'); } + +#[test] +#[should_panic(expected: ('order_unfulfillable_at_price', 500, 10))] +fn given_order_not_fullfillable_when_get_execution_price_for_increase_then_fails() { + let price = get_execution_price_for_increase( + size_delta_usd: 5000, size_delta_in_tokens: 10, acceptable_price: 10, is_long: true, + ); +} + + #[test] fn given_normal_conditions_when_get_execution_price_for_decrease_then_works() { let price = get_execution_price_for_decrease( @@ -57,9 +235,91 @@ fn given_normal_conditions_when_get_execution_price_for_decrease_then_works() { acceptable_price: 8, is_long: true, ); - assert(price == 9, 'Should be 9'); + assert(price == 9, 'invalid price1'); + + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 1000, max: 1100 }, + position_size_in_usd: 200000000, + position_size_in_tokens: 30000, + size_delta_usd: 50000, + price_impact_usd: 15, + acceptable_price: 1001, + is_long: true, + ); + assert(price == 1002, 'invalid price2'); + + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 1000, max: 1100 }, + position_size_in_usd: 200000000, + position_size_in_tokens: 30000, + size_delta_usd: 50000, + price_impact_usd: 15, + acceptable_price: 1100, + is_long: false, + ); + assert(price == 1098, 'invalid price'); +} + + +#[test] +#[should_panic( + expected: ( + 'price_impact_too_large', + 3618502788666131213697322783095070105623107215331596699973092056135872020466, + 1 + ) +)] +fn given_price_impact_larger_than_order_when_get_execution_price_for_decrease_then_fails() { + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 1000, max: 1100 }, + position_size_in_usd: 200000000, + position_size_in_tokens: 30000, + size_delta_usd: 1, + price_impact_usd: 15, + acceptable_price: 1100, + is_long: false, + ); } +#[test] +#[should_panic( + expected: ( + 'negative_execution_price', + 3618502788666131213697322783095070105623107215331596699973092056135872020480, + 1, + 200000000, + 3618502788666131213697322783095070105623107215331596699973092056135872020466, + 50000 + ) +)] +fn given_negative_execution_price_than_order_when_get_execution_price_for_decrease_then_fails() { + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 1, max: 1 }, + position_size_in_usd: 200000000, + position_size_in_tokens: 30000, + size_delta_usd: 50000, + price_impact_usd: 15, + acceptable_price: 1100, + is_long: false, + ); +} + + +#[test] +#[should_panic(expected: ('order_unfulfillable_at_price', 1002, 10000,))] +fn given_not_acceptable_price_when_get_execution_price_for_decrease_then_fails() { + let price = get_execution_price_for_decrease( + index_token_price: Price { min: 1000, max: 1100 }, + position_size_in_usd: 200000000, + position_size_in_tokens: 30000, + size_delta_usd: 50000, + price_impact_usd: 15, + acceptable_price: 10000, + is_long: true, + ); +} + + #[test] fn given_normal_conditions_when_validate_non_empty_order_then_works() { let mut order: Order = Default::default(); @@ -75,3 +335,73 @@ fn given_empty_order_when_validate_non_empty_order_then_fails() { let order: Order = Default::default(); validate_non_empty_order(@order); } + + +// ********************************************************************************************* +// * SETUP * +// ********************************************************************************************* + +fn setup() -> (ContractAddress, IDataStoreDispatcher, IEventEmitterDispatcher, IOracleDispatcher) { + let caller_address = contract_address_const::<0x101>(); + let order_keeper = contract_address_const::<0x2233>(); + let role_store_address = deploy_role_store(); + let role_store = IRoleStoreDispatcher { contract_address: role_store_address }; + let data_store_address = deploy_data_store(role_store_address); + let data_store = IDataStoreDispatcher { contract_address: data_store_address }; + let event_emitter_address = deploy_event_emitter(); + let event_emitter = IEventEmitterDispatcher { contract_address: event_emitter_address }; + let oracle_store_address = deploy_oracle_store(role_store_address, event_emitter_address); + let oracle_store = IOracleStoreDispatcher { contract_address: oracle_store_address }; + let pragma_address = deploy_price_feed(); + let oracle_address = deploy_oracle(oracle_store_address, role_store_address, pragma_address); + let oracle = IOracleDispatcher { contract_address: oracle_address }; + start_prank(role_store_address, caller_address); + role_store.grant_role(caller_address, role::CONTROLLER); + role_store.grant_role(order_keeper, role::ORDER_KEEPER); + oracle_store.add_signer(contract_address_const::<'signer'>()); + start_prank(data_store_address, caller_address); + start_prank(oracle_address, caller_address); + + (caller_address, data_store, event_emitter, oracle) +} + +fn deploy_price_feed() -> ContractAddress { + let contract = declare('PriceFeed'); + contract.deploy(@array![]).unwrap() +} + +fn deploy_oracle( + oracle_store_address: ContractAddress, + role_store_address: ContractAddress, + pragma_address: ContractAddress +) -> ContractAddress { + let contract = declare('Oracle'); + let constructor_calldata = array![ + role_store_address.into(), oracle_store_address.into(), pragma_address.into() + ]; + contract.deploy(@constructor_calldata).unwrap() +} + +fn deploy_oracle_store( + role_store_address: ContractAddress, event_emitter_address: ContractAddress +) -> ContractAddress { + let contract = declare('OracleStore'); + let constructor_calldata = array![role_store_address.into(), event_emitter_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + +fn deploy_data_store(role_store_address: ContractAddress) -> ContractAddress { + let contract = declare('DataStore'); + let constructor_calldata = array![role_store_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + +fn deploy_role_store() -> ContractAddress { + let contract = declare('RoleStore'); + contract.deploy(@array![]).unwrap() +} + +fn deploy_event_emitter() -> ContractAddress { + let contract = declare('EventEmitter'); + contract.deploy(@array![]).unwrap() +}