diff --git a/README.md b/README.md index 787bfe9..9d07d4b 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,24 @@ The AMM is currently built on Cairo version 2.3.0. However while OpenZeppelin wa - src/tokens/lptoken.cairo - src/tokens/option_token.cairo +## Tests +Unit tests are located in files containing relevant code, while integration tests are located in `tests` folder. Currently there are integration tests for: +- Depositing liquidity +- Withdrawing liquidity +- Opening trade +- Closing trade +- Settling trade +- View functions +- Sandwich guard + +Most of the tests also contain a couple of different scenarios. + +Folder `src/testing` contains various utilities for writing tests: + - `setup.cairo` - Contains function that will fully deploy the AMM (along with tokens representing ETH, USDC), set it up, add liquidity etc. The function is called `deploy_setup` and it returns a tuple of two structs, where the first one is `Ctx` (context) which contains information needed for tests - addresses (AMM, tokens, LP tokens, OP tokens, admin...) strike, expiry etc. The second struct, `Dispatchers`, provides dispatcher for every contract that is deployed in the tests (it's more convenient than having to create them in the test manually). + - `test_utils.cairo` provides utilities for fetching all the information about current AMM state (plus some user balances etc.). To fetch the `Stats` struct, call the `new` method on it with `Ctx` and `Dispatchers` as args. `Stats` also implement `PrintTrait` for more convenient debugging. + +You can look into `tests/test_dummy.cairo` to see an example of how to write a new test - how to deploy the setup, prank the AMM, mock the Pragma price, fetch the AMM state etc. + ## Deployment - Zero to Hero Deployment described using `starkli`, version `0.1.20 (e4d2307)`. diff --git a/tests/test_dummy.cairo b/tests/test_dummy.cairo index cb2fd6f..a656307 100644 --- a/tests/test_dummy.cairo +++ b/tests/test_dummy.cairo @@ -1,29 +1,42 @@ +// Example test + use starknet::ContractAddress; -use carmine_protocol::amm_core::peripheries::view::View; -use carmine_protocol::testing::setup::{deploy_setup, _add_expired_option}; -use array::ArrayTrait; -use debug::PrintTrait; -use carmine_protocol::amm_interface::{IAMMDispatcher, IAMMDispatcherTrait}; -use carmine_protocol::types::option_::{Option_Trait, Option_}; -use cubit::f128::types::{Fixed, FixedTrait}; -use snforge_std::{start_prank, stop_prank, start_warp, stop_warp, start_mock_call, stop_mock_call}; use traits::{TryInto, Into}; use option::OptionTrait; +use debug::PrintTrait; +use snforge_std::{start_prank, stop_prank, start_warp, stop_warp, start_mock_call, stop_mock_call}; +use cubit::f128::types::{Fixed, FixedTrait}; + +use carmine_protocol::testing::test_utils::{Stats, StatsTrait}; +use carmine_protocol::testing::setup::deploy_setup; + +use carmine_protocol::amm_interface::IAMMDispatcherTrait; use carmine_protocol::amm_core::oracles::pragma::Pragma::PRAGMA_ORACLE_ADDRESS; use carmine_protocol::amm_core::oracles::pragma::PragmaUtils::{ PragmaPricesResponse, Checkpoint, AggregationMode }; + #[test] fn test_dummy() { + // Deploying setup + // It returns the context with all the needed information (addresses, strike, expiry etc) + // and Dispatchers (dispatchers for all the contracts deployed in the tests) let (ctx, dsps) = deploy_setup(); + + // Some token amounts for trading etc let five_tokens: u256 = 5000000000000000000; // with 18 decimals let five_k_tokens: u256 = 5000000000; // with 6 decimals let one_int = 1000000000000000000; // 1*10**18 + // Warping start_warp(ctx.amm_address, 1000000000); + + // Pranking the AMM to call it from user address start_prank(ctx.amm_address, ctx.admin_address); + + // Mocking Pragma to return ETH price of 1.4k start_mock_call( PRAGMA_ORACLE_ADDRESS.try_into().unwrap(), 'get_data', @@ -36,6 +49,8 @@ fn test_dummy() { } ); + // Opening LONG CALL trade using the Dispatchers struct + // NOTE: You still have to import IAMMDispatcherTrait for it to work let long_call_premia = dsps .amm .trade_open( @@ -50,21 +65,48 @@ fn test_dummy() { 99999999999 // Disable this check ); - let long_put_premia = dsps + // Opening SHORT CALL trade using the Dispatchers struct + let long_call_premia = dsps .amm .trade_open( - 1, // Put + 0, // Call ctx.strike_price, ctx.expiry, - 0, // Long + 1, // Short one_int, ctx.usdc_address, ctx.eth_address, - FixedTrait::from_unscaled_felt(100_000), // Disable this check + FixedTrait::from_felt(1), // Disable this check 99999999999 // Disable this check ); + // You can fetch some information about the directly: + + // Fetching AMM long call option position + let call_pos_long = dsps + .amm + .get_option_position(ctx.call_lpt_address, 0, ctx.expiry, ctx.strike_price); + + // Fetching AMM short call option position + let call_pos_short = dsps + .amm + .get_option_position(ctx.call_lpt_address, 1, ctx.expiry, ctx.strike_price); + + // But you can also fetch all the AMM state at once with Stats + let stats_1 = StatsTrait::new(ctx, dsps); + + // Printing the Stats struct: + // stats_1.print(); + + // It already contains the information about the AMM position in all the options: + assert(call_pos_long == stats_1.opt_pos_lc, 'This should not fail'); + assert(call_pos_short == stats_1.opt_pos_sc, 'This should not fail'); + + // Now let's warp past the expiry and try to settle an option start_warp(ctx.amm_address, 1000000000 + 60 * 60 * 24 + 1); + + // AMM calls get_last_checkpoint_before function in Pragma oracle, so we + // need to mock it ie at price of 1.4k per ETH start_mock_call( PRAGMA_ORACLE_ADDRESS.try_into().unwrap(), 'get_last_checkpoint_before', @@ -78,31 +120,52 @@ fn test_dummy() { 1 ) ); - start_mock_call(PRAGMA_ORACLE_ADDRESS.try_into().unwrap(), 'get_decimals', 8); - // let call_pos = dsps.amm.get_option_position( - // ctx.call_lpt_address, - // 1, - // ctx.expiry, - // ctx.strike_price - // ); + // Same goes for get_decimals function + start_mock_call(PRAGMA_ORACLE_ADDRESS.try_into().unwrap(), 'get_decimals', 8); - // let put_pos = dsps.amm.get_option_position( - // ctx.put_lpt_address, - // 1, - // ctx.expiry, - // ctx.strike_price - // ); + // Expire Call pool + dsps + .amm + .expire_option_token_for_pool( + ctx.call_lpt_address, 0, // Long + ctx.strike_price, ctx.expiry + ); + dsps + .amm + .expire_option_token_for_pool( + ctx.call_lpt_address, 1, // Short + ctx.strike_price, ctx.expiry + ); - // let call_val = dsps.amm.get_value_of_pool_position( - // ctx.call_lpt_address - // ); + // Settle trades on user side + dsps + .amm + .trade_settle( + 0, // Call + ctx.strike_price, + ctx.expiry, + 0, // Long + one_int, + ctx.usdc_address, + ctx.eth_address, + ); - // let put_val = dsps.amm.get_value_of_pool_position( - // ctx.put_lpt_address - // ); + dsps + .amm + .trade_settle( + 0, // Call + ctx.strike_price, + ctx.expiry, + 1, // Short + one_int, + ctx.usdc_address, + ctx.eth_address, + ); - let call_val = dsps.amm.get_value_of_pool_expired_position(ctx.call_lpt_address); + // Fetch the AMM state again + let stats_2 = StatsTrait::new(ctx, dsps); - let put_val = dsps.amm.get_value_of_pool_expired_position(ctx.put_lpt_address); + // Assert some stuff + assert(stats_2.locked_capital_c == 0, 'Call pool should have no locked'); }