diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b1e0a42..061c6c61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,9 +5,12 @@ on: paths: - '**.cairo' - '.github/' + - 'Scarb.lock' + - 'Scarb.toml' env: - SCARB_VERSION: 2.3.1 + SCARB_VERSION: 2.4.0 + FOUNDRY_VERSION: 0.14.0 jobs: build: @@ -17,7 +20,11 @@ jobs: uses: actions/checkout@v3 - name: Install Scarb run: curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v $SCARB_VERSION + - name: Install SnFoundryUp + run: curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh + - name: Install SnFoundry + run: snfoundryup -v $FOUNDRY_VERSION - name: Check formatting run: scarb fmt --check - - name: Build with scarb - run: scarb build + - name: Run tests with snforge + run: snforge test diff --git a/.gitignore b/.gitignore index 6ce8bb1a..98cbdd5d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,6 @@ Cargo.lock .env vendor/ -.DS_Store \ No newline at end of file +.DS_Store +.snfoundry_cache/ +account* \ No newline at end of file diff --git a/Scarb.lock b/Scarb.lock index 14bb4a9d..d8df5b64 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -1,6 +1,20 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "cubit" +version = "1.3.0" +source = "git+https://github.com/akhercha/cubit.git?branch=chore%2Fcairo_upgrade#d3869a3f0c47e5ed229bbbfe2fce3fc0510cbc8a" + [[package]] name = "governance" version = "0.2.0" +dependencies = [ + "cubit", + "snforge_std", +] + +[[package]] +name = "snforge_std" +version = "0.14.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git#a1caebbfffd00e612c09ed89da006f6c48a92fd9" diff --git a/Scarb.toml b/Scarb.toml index d5acd4bc..65a4a76d 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -2,11 +2,23 @@ name = "governance" description = "A flexible monolithic governance contract, developed for use with Carmine Options AMM" version = "0.2.0" -cairo-version = "2.1.1" +cairo-version = "2.4.0" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest [dependencies] -starknet = ">=1.1.0" +cubit = { git = "https://github.com/akhercha/cubit.git", branch = "chore/cairo_upgrade" } +starknet = ">=1.3.0" +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", commit = "a1caebbfffd00e612c09ed89da006f6c48a92fd9"} [[target.starknet-contract]] + +[[tool.snforge.fork]] +name = "MAINNET" +url = "http://34.22.208.73:6060/" +block_id.tag = "Latest" + +[[tool.snforge.fork]] +name = "GOERLI" +url = "https://limited-rpc.nethermind.io/goerli-juno" +block_id.tag = "Latest" \ No newline at end of file diff --git a/src/amm_types/basic.cairo b/src/amm_types/basic.cairo new file mode 100644 index 00000000..ad315278 --- /dev/null +++ b/src/amm_types/basic.cairo @@ -0,0 +1,16 @@ +use cubit::f128::types::fixed::{Fixed, FixedTrait}; +use starknet::ContractAddress; +use core::option::OptionTrait; + +type LPTAddress = ContractAddress; +type OptionSide = u8; +type OptionType = u8; +type Timestamp = u64; // In seconds, Block timestamps are also u64 + +type Int = u128; + +type Maturity = felt252; + +type Volatility = Fixed; +type Strike = Fixed; + diff --git a/src/constants.cairo b/src/constants.cairo index 3d0ced4a..123dff00 100644 --- a/src/constants.cairo +++ b/src/constants.cairo @@ -1,6 +1,5 @@ const NEW_PROPOSAL_QUORUM: u128 = 200; // 1/200 of totalSupply required to propose an upgrade. Quorums don't take into account investors. at all, they don't count into total eligible voters, but do vote -const PROPOSAL_VOTING_TIME_BLOCKS: u64 = 2500; const QUORUM: u128 = 10; // 1/10 of totalSupply required to participate to pass const MINUS_ONE: felt252 = 0x800000000000011000000000000000000000000000000000000000000000000; const TEAM_TOKEN_BALANCE: u128 = 1000000000000000000; @@ -9,3 +8,19 @@ const OPTION_PUT: felt252 = 1; const TRADE_SIDE_LONG: felt252 = 0; const TRADE_SIDE_SHORT: felt252 = 1; const PROPOSAL_VOTING_SECONDS: u64 = consteval_int!(60 * 60 * 24 * 7); + + +// ADDRESSES + +const USDC_ADDRESS: felt252 = 0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8; +const ETH_ADDRESS: felt252 = 0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7; +const BTC_ADDRESS: felt252 = 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac; + +// CLASS HASHES + +// corresponds to commit 7b7db57419fdb25b93621fbea6a845005f7725d0 in protocol-cairo1 repo, branch audit-fixes +const LP_TOKEN_CLASS_HASH: felt252 = + 0x06d15bc862ce48375ec98fea84d76ca67b7ac5978d80c848fa5496108783fbc2; +const AMM_CLASS_HASH: felt252 = 0x045fb686c8875f31966e7308d71c03e9ae78f9566a61870a2b616dc225dd3313; +const OPTION_TOKEN_CLASS_HASH: felt252 = + 0x07fc0b6ecc96a698cdac8c4ae447816d73bffdd9603faacffc0a8047149d02ed; diff --git a/src/contract.cairo b/src/contract.cairo index f47fd004..da95874c 100644 --- a/src/contract.cairo +++ b/src/contract.cairo @@ -21,14 +21,11 @@ trait IGovernance { fn get_governance_token_address(self: @TContractState) -> ContractAddress; fn get_amm_address(self: @TContractState) -> ContractAddress; fn apply_passed_proposal(ref self: TContractState, prop_id: felt252); +// AIRDROPS - // AIRDROPS +// in component - // in component - - // OPTIONS - - fn add_0911_1611_options(ref self: TContractState); +// OPTIONS / ONE-OFF } @@ -40,7 +37,6 @@ mod Governance { use governance::types::ContractType; use governance::types::PropDetails; use governance::upgrades::Upgrades; - use governance::options::Options; use governance::airdrop::airdrop as airdrop_component; use starknet::ContractAddress; @@ -143,9 +139,5 @@ mod Governance { fn apply_passed_proposal(ref self: ContractState, prop_id: felt252) { Upgrades::apply_passed_proposal(prop_id) } - - fn add_0911_1611_options(ref self: ContractState) { - Options::run_add_0911_1611_options() - } } } diff --git a/src/lib.cairo b/src/lib.cairo index 21cb846e..3df95c88 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,4 +1,7 @@ mod airdrop; +mod amm_types { + mod basic; +} mod constants; mod contract; mod merkle_tree; diff --git a/src/options.cairo b/src/options.cairo index 0e7fe3fc..0a0c53ec 100644 --- a/src/options.cairo +++ b/src/options.cairo @@ -18,74 +18,28 @@ mod Options { use starknet::syscalls::deploy_syscall; use starknet::info::get_contract_address; + use cubit::f128::types::{Fixed, FixedTrait}; + use governance::contract::Governance::{amm_address, proposal_initializer_run}; - use governance::constants::{OPTION_CALL, OPTION_PUT, TRADE_SIDE_LONG, TRADE_SIDE_SHORT}; + use governance::constants::{ + OPTION_CALL, OPTION_PUT, TRADE_SIDE_LONG, TRADE_SIDE_SHORT, OPTION_TOKEN_CLASS_HASH + }; use governance::traits::{ IAMMDispatcher, IAMMDispatcherTrait, IOptionTokenDispatcher, IOptionTokenDispatcherTrait }; use governance::types::OptionSide; use governance::contract::Governance; use governance::types::OptionType; - use governance::traits::Math64x61_; use governance::contract::Governance::proposal_initializer_runContractMemberStateTrait; - // 2**61 = 2305843009213693952 - const VOLATILITY_28: Math64x61_ = consteval_int!(28 * 2305843009213693952); - const VOLATILITY_30: Math64x61_ = consteval_int!(30 * 2305843009213693952); - const VOLATILITY_30_5: Math64x61_ = 70328211781017665536; - const VOLATILITY_31_5: Math64x61_ = 72634054790231359488; - const VOLATILITY_32: Math64x61_ = consteval_int!(32 * 2305843009213693952); - const VOLATILITY_32_5: Math64x61_ = 74939897799445053440; - const VOLATILITY_33: Math64x61_ = consteval_int!(33 * 2305843009213693952); - const VOLATILITY_34: Math64x61_ = consteval_int!(34 * 2305843009213693952); - const VOLATILITY_35: Math64x61_ = consteval_int!(35 * 2305843009213693952); - const VOLATILITY_35_5: Math64x61_ = 81857426827086135296; - const VOLATILITY_36: Math64x61_ = consteval_int!(36 * 2305843009213693952); - const VOLATILITY_37: Math64x61_ = consteval_int!(37 * 2305843009213693952); - const VOLATILITY_38: Math64x61_ = 87622034350120370176; - const VOLATILITY_38_5: Math64x61_ = 88774955854727217152; - const VOLATILITY_39: Math64x61_ = consteval_int!(39 * 2305843009213693952); - const VOLATILITY_40: Math64x61_ = 92233720368547758080; - const VOLATILITY_41: Math64x61_ = 94539563377761452032; - const VOLATILITY_41_5: Math64x61_ = 95692484882368299008; - const VOLATILITY_42: Math64x61_ = consteval_int!(42 * 2305843009213693952); - const VOLATILITY_43_5: Math64x61_ = 100304170900795686912; - const VOLATILITY_44: Math64x61_ = 101457092405402533888; - const VOLATILITY_45: Math64x61_ = 124515522497539473408; - const VOLATILITY_46: Math64x61_ = 106068778423829921792; - const VOLATILITY_46_5: Math64x61_ = 107221699928436768768; - const VOLATILITY_47: Math64x61_ = consteval_int!(47 * 2305843009213693952); - const VOLATILITY_48_5: Math64x61_ = 111833385946864156672; - const VOLATILITY_51: Math64x61_ = consteval_int!(51 * 2305843009213693952); - const VOLATILITY_52: Math64x61_ = consteval_int!(52 * 2305843009213693952); - const VOLATILITY_53: Math64x61_ = consteval_int!(53 * 2305843009213693952); - const VOLATILITY_55: Math64x61_ = 126821365506753167360; - const VOLATILITY_59: Math64x61_ = consteval_int!(59 * 2305843009213693952); - const VOLATILITY_60: Math64x61_ = 138350580552821637120; - - const STRIKE_PRICE_1400: Math64x61_ = consteval_int!(1400 * 2305843009213693952); - const STRIKE_PRICE_1500: Math64x61_ = consteval_int!(1500 * 2305843009213693952); - const STRIKE_PRICE_1600: Math64x61_ = consteval_int!(1600 * 2305843009213693952); - const STRIKE_PRICE_1700: Math64x61_ = consteval_int!(1700 * 2305843009213693952); - const STRIKE_PRICE_1800: Math64x61_ = consteval_int!(1800 * 2305843009213693952); - const STRIKE_PRICE_1900: Math64x61_ = consteval_int!(1900 * 2305843009213693952); - const STRIKE_PRICE_2000: Math64x61_ = consteval_int!(2000 * 2305843009213693952); - - fn add_options(salt: felt252, mut options: Span) { + fn add_options(mut options: Span) { // TODO use block hash from block_hash syscall as salt // actually doable with the new syscall let governance_address = get_contract_address(); let state = Governance::unsafe_new_contract_state(); let amm_address = state.get_amm_address(); - let proxy_class: felt252 = - 0x00eafb0413e759430def79539db681f8a4eb98cf4196fe457077d694c6aeeb82; - let opt_class: felt252 = 0x5ce3a80daeb5b7a766df9b41ca8d9e52b6b0a045a0d2ced72f43d4dd2f93b10; loop { match options.pop_front() { - Option::Some(option) => { - add_option( - proxy_class, opt_class, governance_address, amm_address, salt, option - ); - }, + Option::Some(option) => { add_option(governance_address, amm_address, option); }, Option::None(()) => { break (); }, }; } @@ -118,164 +72,73 @@ mod Options { name_long: felt252, name_short: felt252, maturity: felt252, - strike_price: Math64x61_, + strike_price: Fixed, option_type: OptionType, lptoken_address: ContractAddress, - initial_volatility: Math64x61_ + btc: bool, + initial_volatility: Fixed } fn add_option( - proxy_class: felt252, - opt_class: felt252, - governance_address: ContractAddress, - amm_address: ContractAddress, - salt: felt252, - option: @FutureOption + governance_address: ContractAddress, amm_address: ContractAddress, option: @FutureOption ) { - // mainnet - let USDC_addr: felt252 = 0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8; - let ETH_addr: felt252 = 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7; - let quote_token_address = USDC_addr.try_into().unwrap(); - let base_token_address = ETH_addr.try_into().unwrap(); let o = *option; - // Yes, this 'overflows', but it's expected and wanted. - let custom_salt: felt252 = salt - + o.strike_price - + o.maturity - + o.option_type - + o.lptoken_address.into() - + o.initial_volatility; - - let proxy_class_hash: ClassHash = proxy_class.try_into().unwrap(); - let opt_class_hash: ClassHash = opt_class.try_into().unwrap(); - let optoken_long_addr: ContractAddress = deploy_via_proxy( - proxy_class_hash, opt_class_hash, custom_salt - ); - - IOptionTokenDispatcher { contract_address: optoken_long_addr } - .initializer( - o.name_long, - 'C-OPT', - governance_address, - amm_address, - quote_token_address, - base_token_address, - o.option_type, - o.strike_price, - o.maturity, - TRADE_SIDE_LONG - ); - - IAMMDispatcher { contract_address: amm_address } - .add_option( - TRADE_SIDE_LONG, - o.maturity, - o.strike_price, - quote_token_address, - base_token_address, - o.option_type, - o.lptoken_address, - optoken_long_addr, - o.initial_volatility - ); - - let optoken_short_addr: ContractAddress = deploy_via_proxy( - proxy_class_hash, opt_class_hash, custom_salt + 1 - ); - - IOptionTokenDispatcher { contract_address: optoken_short_addr } - .initializer( - o.name_short, - 'C-OPT', - governance_address, - amm_address, - quote_token_address, - base_token_address, - o.option_type, - o.strike_price, - o.maturity, - TRADE_SIDE_SHORT - ); - IAMMDispatcher { contract_address: amm_address } - .add_option( - TRADE_SIDE_SHORT, - o.maturity, - o.strike_price, - quote_token_address, - base_token_address, - o.option_type, - o.lptoken_address, - optoken_short_addr, - o.initial_volatility - ); - } - - fn add_option_both_sides( - proxy_class: felt252, - opt_class: felt252, - governance_address: ContractAddress, - amm_address: ContractAddress, - salt: felt252, - option: @FutureOption - ) { // mainnet let USDC_addr: felt252 = 0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8; let ETH_addr: felt252 = 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7; - let quote_token_address = USDC_addr.try_into().unwrap(); - let base_token_address = ETH_addr.try_into().unwrap(); - let o = *option; + let BTC_addr: felt252 = 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac; + let quote_token_address = USDC_addr; + let base_token_address = if (o.btc) { + BTC_addr + } else { + ETH_addr + }; + // Yes, this 'overflows', but it's expected and wanted. - let custom_salt: felt252 = salt - + o.strike_price + let custom_salt: felt252 = 42 + + o.strike_price.mag.into() + o.maturity + o.option_type - + o.lptoken_address.into() - + o.initial_volatility; - - let proxy_class_hash: ClassHash = proxy_class.try_into().unwrap(); - let opt_class_hash: ClassHash = opt_class.try_into().unwrap(); - let optoken_long_addr: ContractAddress = deploy_via_proxy( - proxy_class_hash, opt_class_hash, custom_salt + + o.lptoken_address.into(); + + let opt_class_hash: ClassHash = OPTION_TOKEN_CLASS_HASH.try_into().unwrap(); + let mut optoken_long_calldata = array![]; + optoken_long_calldata.append(o.name_long); + optoken_long_calldata.append('C-OPT'); + optoken_long_calldata.append(amm_address.into()); + optoken_long_calldata.append(quote_token_address); + optoken_long_calldata.append(base_token_address); + optoken_long_calldata.append(o.option_type); + optoken_long_calldata.append(o.strike_price.mag.into()); + optoken_long_calldata.append(o.maturity); + optoken_long_calldata.append(TRADE_SIDE_LONG); + let deploy_retval = deploy_syscall( + opt_class_hash, custom_salt + 1, optoken_long_calldata.span(), false ); - let optoken_short_addr: ContractAddress = deploy_via_proxy( - proxy_class_hash, opt_class_hash, custom_salt + 1 + let (optoken_long_addr, _) = deploy_retval.unwrap_syscall(); + + let mut optoken_short_calldata = array![]; + optoken_short_calldata.append(o.name_short); + optoken_short_calldata.append('C-OPT'); + optoken_short_calldata.append(amm_address.into()); + optoken_short_calldata.append(quote_token_address); + optoken_short_calldata.append(base_token_address); + optoken_short_calldata.append(o.option_type); + optoken_short_calldata.append(o.strike_price.mag.into()); + optoken_short_calldata.append(o.maturity); + optoken_short_calldata.append(TRADE_SIDE_SHORT); + let deploy_retval = deploy_syscall( + opt_class_hash, custom_salt + 2, optoken_short_calldata.span(), false ); - - IOptionTokenDispatcher { contract_address: optoken_long_addr } - .initializer( - o.name_long, - 'C-OPT', - governance_address, - amm_address, - quote_token_address, - base_token_address, - o.option_type, - o.strike_price, - o.maturity, - TRADE_SIDE_LONG - ); - - IOptionTokenDispatcher { contract_address: optoken_short_addr } - .initializer( - o.name_short, - 'C-OPT', - governance_address, - amm_address, - quote_token_address, - base_token_address, - o.option_type, - o.strike_price, - o.maturity, - TRADE_SIDE_SHORT - ); + let (optoken_short_addr, _) = deploy_retval.unwrap_syscall(); IAMMDispatcher { contract_address: amm_address } .add_option_both_sides( - o.maturity, + o.maturity.try_into().unwrap(), o.strike_price, - quote_token_address, - base_token_address, + quote_token_address.try_into().unwrap(), + base_token_address.try_into().unwrap(), o.option_type, o.lptoken_address, optoken_long_addr, @@ -284,166 +147,138 @@ mod Options { ); } - fn deploy_via_proxy( - proxy_class: ClassHash, impl_class: ClassHash, salt: felt252 - ) -> ContractAddress { - let curr_salt = salt + impl_class.into(); - let mut calldata = array![impl_class.into(), 0, 0]; - let syscall_res = deploy_syscall(proxy_class, curr_salt, calldata.span(), false); - let (res, _) = syscall_res.unwrap_syscall(); - res - } - - fn run_add_0911_1611_options() { - let mut state = Governance::unsafe_new_contract_state(); - assert(!state.proposal_initializer_run.read(36), 'prop already initialized'); - - state.proposal_initializer_run.write(36, true); - - add_0911_options(); - add_1611_options(); - } - - fn add_0911_options() { - let MATURITY: felt252 = 1699574399; + fn add_1201_options( + eth_lpt_addr: ContractAddress, + eth_usdc_lpt_addr: ContractAddress, + btc_lpt_addr: ContractAddress, + btc_usdc_lpt_addr: ContractAddress + ) { + let MATURITY: felt252 = 1705017599; - let eth_lpt_addr: ContractAddress = - 0x7aba50fdb4e024c1ba63e2c60565d0fd32566ff4b18aa5818fc80c30e749024 - .try_into() - .unwrap(); - let usdc_lpt_addr: ContractAddress = - 0x18a6abca394bd5f822cfa5f88783c01b13e593d1603e7b41b00d31d2ea4827a - .try_into() - .unwrap(); + let point_five = FixedTrait::ONE() / FixedTrait::from_unscaled_felt(2); let mut to_add = ArrayTrait::::new(); to_add .append( FutureOption { - name_long: 'ETHUSDC-09NOV23-1800-LONG-CALL', - name_short: 'ETHUSDC-09NOV23-1800-SHORT-CALL', + name_long: 'ETHUSDC-12JAN24-2300-LONG-CALL', + name_short: 'ETHUSDC-12JAN24-2300-SHORT-CALL', maturity: MATURITY, - strike_price: STRIKE_PRICE_1800, + strike_price: FixedTrait::from_unscaled_felt(2300), option_type: OPTION_CALL, lptoken_address: eth_lpt_addr, - initial_volatility: VOLATILITY_41 + btc: false, + initial_volatility: FixedTrait::from_unscaled_felt(62) } ); to_add .append( FutureOption { - name_long: 'ETHUSDC-09NOV23-1900-LONG-CALL', - name_short: 'ETHUSDC-09NOV23-1900-SHORT-CALL', + name_long: 'ETHUSDC-12JAN24-2400-LONG-CALL', + name_short: 'ETHUSDC-12JAN24-2400-SHORT-CALL', maturity: MATURITY, - strike_price: STRIKE_PRICE_1900, + strike_price: FixedTrait::from_unscaled_felt(2400), option_type: OPTION_CALL, lptoken_address: eth_lpt_addr, - initial_volatility: VOLATILITY_45 + btc: false, + initial_volatility: FixedTrait::from_unscaled_felt(62) + point_five } ); to_add .append( FutureOption { - name_long: 'ETHUSDC-09NOV23-2000-LONG-CALL', - name_short: 'ETHUSDC-09NOV23-2000-SHORT-CALL', + name_long: 'ETHUSDC-12JAN24-2500-LONG-CALL', + name_short: 'ETHUSDC-12JAN24-2500-SHORT-CALL', maturity: MATURITY, - strike_price: STRIKE_PRICE_2000, + strike_price: FixedTrait::from_unscaled_felt(2500), option_type: OPTION_CALL, lptoken_address: eth_lpt_addr, - initial_volatility: VOLATILITY_53 + btc: false, + initial_volatility: FixedTrait::from_unscaled_felt(64) } ); to_add .append( FutureOption { - name_long: 'ETHUSDC-09NOV23-1700-LONG-PUT', - name_short: 'ETHUSDC-09NOV23-1700-SHORT-PUT', + name_long: 'ETHUSDC-12JAN24-2300-LONG-PUT', + name_short: 'ETHUSDC-12JAN24-2300-SHORT-PUT', maturity: MATURITY, - strike_price: STRIKE_PRICE_1700, + strike_price: FixedTrait::from_unscaled_felt(2300), option_type: OPTION_PUT, - lptoken_address: usdc_lpt_addr, - initial_volatility: VOLATILITY_42 + lptoken_address: eth_usdc_lpt_addr, + btc: false, + initial_volatility: FixedTrait::from_unscaled_felt(62) } ); to_add .append( FutureOption { - name_long: 'ETHUSDC-09NOV23-1600-LONG-PUT', - name_short: 'ETHUSDC-09NOV23-1600-SHORT-PUT', + name_long: 'ETHUSDC-12JAN24-2200-LONG-PUT', + name_short: 'ETHUSDC-12JAN24-2200-SHORT-PUT', maturity: MATURITY, - strike_price: STRIKE_PRICE_1600, + strike_price: FixedTrait::from_unscaled_felt(2200), option_type: OPTION_PUT, - lptoken_address: usdc_lpt_addr, - initial_volatility: VOLATILITY_52 + lptoken_address: eth_usdc_lpt_addr, + btc: false, + initial_volatility: FixedTrait::from_unscaled_felt(62) } ); - add_options(1011238812, to_add.span()) - } - - - fn add_1611_options() { - let MATURITY: felt252 = 1700179199; - - let eth_lpt_addr: ContractAddress = - 0x7aba50fdb4e024c1ba63e2c60565d0fd32566ff4b18aa5818fc80c30e749024 - .try_into() - .unwrap(); - let usdc_lpt_addr: ContractAddress = - 0x18a6abca394bd5f822cfa5f88783c01b13e593d1603e7b41b00d31d2ea4827a - .try_into() - .unwrap(); + // BITCOIN - let mut to_add = ArrayTrait::::new(); to_add .append( FutureOption { - name_long: 'ETHUSDC-16NOV23-1800-LONG-CALL', - name_short: 'ETHUSDC-16NOV23-1800-SHORT-CALL', + name_long: 'BTCUSD-12JAN24-44000-LONG-CALL', + name_short: 'BTCUSD-12JAN24-44000-SHORT-CALL', maturity: MATURITY, - strike_price: STRIKE_PRICE_1800, + strike_price: FixedTrait::from_unscaled_felt(44000), option_type: OPTION_CALL, - lptoken_address: eth_lpt_addr, - initial_volatility: VOLATILITY_44 + lptoken_address: btc_lpt_addr, + btc: true, + initial_volatility: FixedTrait::from_unscaled_felt(62) + point_five } ); - to_add // purposefully only two call strikes to be conservative about future volatility spikes + to_add .append( FutureOption { - name_long: 'ETHUSDC-16NOV23-1900-LONG-CALL', - name_short: 'ETHUSDC-16NOV23-1900-SHORT-CALL', + name_long: 'BTCUSD-12JAN24-45000-LONG-CALL', + name_short: 'BTCUSD-12JAN24-45000-SHORT-CALL', maturity: MATURITY, - strike_price: STRIKE_PRICE_1900, + strike_price: FixedTrait::from_unscaled_felt(45000), option_type: OPTION_CALL, - lptoken_address: eth_lpt_addr, - initial_volatility: VOLATILITY_46 + lptoken_address: btc_lpt_addr, + btc: true, + initial_volatility: FixedTrait::from_unscaled_felt(63) + point_five } ); to_add .append( FutureOption { - name_long: 'ETHUSDC-16NOV23-1700-LONG-PUT', - name_short: 'ETHUSDC-16NOV23-1700-SHORT-PUT', + name_long: 'BTCUSD-12JAN24-43000-LONG-PUT', + name_short: 'BTCUSD-12JAN24-43000-SHORT-PUT', maturity: MATURITY, - strike_price: STRIKE_PRICE_1700, + strike_price: FixedTrait::from_unscaled_felt(43000), option_type: OPTION_PUT, - lptoken_address: usdc_lpt_addr, - initial_volatility: VOLATILITY_43_5 + lptoken_address: btc_usdc_lpt_addr, + btc: true, + initial_volatility: FixedTrait::from_unscaled_felt(62) } ); to_add .append( FutureOption { - name_long: 'ETHUSDC-16NOV23-1600-LONG-PUT', - name_short: 'ETHUSDC-16NOV23-1600-SHORT-PUT', + name_long: 'BTCUSD-12JAN24-42000-LONG-PUT', + name_short: 'BTCUSD-12JAN24-42000-SHORT-PUT', maturity: MATURITY, - strike_price: STRIKE_PRICE_1600, + strike_price: FixedTrait::from_unscaled_felt(42000), option_type: OPTION_PUT, - lptoken_address: usdc_lpt_addr, - initial_volatility: VOLATILITY_48_5 + lptoken_address: btc_usdc_lpt_addr, + btc: true, + initial_volatility: FixedTrait::from_unscaled_felt(62) } ); - add_options(16112383681242, to_add.span()) + add_options(to_add.span()) } } diff --git a/src/traits.cairo b/src/traits.cairo index af4e3ac8..e66750a0 100644 --- a/src/traits.cairo +++ b/src/traits.cairo @@ -1,9 +1,10 @@ -use starknet::ContractAddress; +use starknet::{ClassHash, ContractAddress}; use governance::types::OptionSide; use governance::types::OptionType; -type Math64x61_ = felt252; // legacy, for AMM trait definition +use core::starknet::SyscallResultTrait; +use cubit::f128::types::{Fixed, FixedTrait}; #[starknet::interface] trait IERC20 { @@ -25,34 +26,34 @@ trait IAMM { fn trade_open( ref self: TContractState, option_type: OptionType, - strike_price: Math64x61_, - maturity: felt252, + strike_price: Fixed, + maturity: u64, option_side: OptionSide, - option_size: felt252, + option_size: u128, quote_token_address: ContractAddress, base_token_address: ContractAddress, - limit_total_premia: Math64x61_, - tx_deadline: felt252, - ) -> Math64x61_; + limit_total_premia: Fixed, + tx_deadline: u64, + ) -> Fixed; fn trade_close( ref self: TContractState, option_type: OptionType, - strike_price: Math64x61_, - maturity: felt252, + strike_price: Fixed, + maturity: u64, option_side: OptionSide, - option_size: felt252, + option_size: u128, quote_token_address: ContractAddress, base_token_address: ContractAddress, - limit_total_premia: Math64x61_, - tx_deadline: felt252, - ) -> Math64x61_; + limit_total_premia: Fixed, + tx_deadline: u64, + ) -> Fixed; fn trade_settle( ref self: TContractState, option_type: OptionType, - strike_price: Math64x61_, - maturity: felt252, + strike_price: Fixed, + maturity: u64, option_side: OptionSide, - option_size: felt252, + option_size: u128, quote_token_address: ContractAddress, base_token_address: ContractAddress, ); @@ -60,32 +61,35 @@ trait IAMM { self: @TContractState, lptoken_address: ContractAddress, option_side: OptionSide, - strike_price: Math64x61_, - maturity: felt252, - ) -> felt252; - fn set_trading_halt(ref self: TContractState, new_status: felt252); - fn get_trading_halt(self: @TContractState) -> felt252; + strike_price: Fixed, + maturity: u64, + ) -> bool; + fn set_trading_halt(ref self: TContractState, new_status: bool); + fn get_trading_halt(self: @TContractState) -> bool; + fn set_trading_halt_permission( + ref self: TContractState, address: ContractAddress, permission: bool + ); + fn get_trading_halt_permission(self: @TContractState, address: ContractAddress) -> bool; fn add_lptoken( ref self: TContractState, quote_token_address: ContractAddress, base_token_address: ContractAddress, option_type: OptionType, lptoken_address: ContractAddress, - pooled_token_addr: ContractAddress, - volatility_adjustment_speed: Math64x61_, + volatility_adjustment_speed: Fixed, max_lpool_bal: u256, ); - fn add_option( + fn add_option_both_sides( ref self: TContractState, - option_side: OptionSide, - maturity: felt252, - strike_price: Math64x61_, + maturity: u64, + strike_price: Fixed, quote_token_address: ContractAddress, base_token_address: ContractAddress, option_type: OptionType, lptoken_address: ContractAddress, - option_token_address_: ContractAddress, - initial_volatility: Math64x61_, + option_token_address_long: ContractAddress, + option_token_address_short: ContractAddress, + initial_volatility: Fixed ); fn add_option_both_sides( ref self: TContractState, @@ -103,38 +107,43 @@ trait IAMM { self: @TContractState, lptoken_address: ContractAddress, option_side: OptionSide, - maturity: felt252, - strike_price: Math64x61_, + maturity: u64, + strike_price: Fixed, ) -> ContractAddress; fn get_lptokens_for_underlying( - ref self: TContractState, pooled_token_addr: ContractAddress, underlying_amt: u256, + self: @TContractState, pooled_token_addr: ContractAddress, underlying_amt: u256, ) -> u256; fn get_underlying_for_lptokens( - self: @TContractState, pooled_token_addr: ContractAddress, lpt_amt: u256 + self: @TContractState, lptoken_addr: ContractAddress, lpt_amt: u256 ) -> u256; fn get_available_lptoken_addresses(self: @TContractState, order_i: felt252) -> ContractAddress; - fn get_all_options(self: @TContractState, lptoken_address: ContractAddress) -> Array; - fn get_all_non_expired_options_with_premia( - self: @TContractState, lptoken_address: ContractAddress - ) -> Array; - fn get_option_with_position_of_user( - self: @TContractState, user_address: ContractAddress - ) -> Array; + // fn get_all_options(self: @TContractState, lptoken_address: ContractAddress) -> Array; + // fn get_all_non_expired_options_with_premia( + // self: @TContractState, lptoken_address: ContractAddress + // ) -> Array; + // fn get_option_with_position_of_user( + // self: @TContractState, user_address: ContractAddress + // ) -> Array; fn get_all_lptoken_addresses(self: @TContractState,) -> Array; - fn get_value_of_pool_position( + fn get_value_of_pool_position(self: @TContractState, lptoken_address: ContractAddress) -> Fixed; + + fn get_value_of_pool_expired_position( self: @TContractState, lptoken_address: ContractAddress - ) -> Math64x61_; + ) -> Fixed; + fn get_value_of_pool_non_expired_position( + self: @TContractState, lptoken_address: ContractAddress + ) -> Fixed; + + // fn get_value_of_position( - // option: Option, - // position_size: Math64x61_, + // self: @TContractState, + // option: Option_, + // position_size: u128, // option_type: OptionType, - // current_volatility: Math64x61_, - // ) -> Math64x61_; - // fn get_all_poolinfo() -> Array; - // fn get_option_info_from_addresses( - // lptoken_address: ContractAddress, option_token_address: ContractAddress, - // ) -> Option; - // fn get_user_pool_infos(user: ContractAddress) -> Array; + // current_volatility: Fixed, + // ) -> Fixed; + // fn get_all_poolinfo(self: @TContractState) -> Array; + // fn get_user_pool_infos(self: @TContractState, user: ContractAddress) -> Array; fn deposit_liquidity( ref self: TContractState, pooled_token_addr: ContractAddress, @@ -156,38 +165,35 @@ trait IAMM { ref self: TContractState, lptoken_address: ContractAddress, option_side: OptionSide, - strike_price: Math64x61_, - maturity: felt252, + strike_price: Fixed, + maturity: u64, ); - fn getAdmin(self: @TContractState); fn set_max_option_size_percent_of_voladjspd( - ref self: TContractState, max_opt_size_as_perc_of_vol_adjspd: felt252 + ref self: TContractState, max_opt_size_as_perc_of_vol_adjspd: u128 ); - fn get_max_option_size_percent_of_voladjspd(self: @TContractState) -> felt252; + fn get_max_option_size_percent_of_voladjspd(self: @TContractState) -> u128; fn get_lpool_balance(self: @TContractState, lptoken_address: ContractAddress) -> u256; - fn get_max_lpool_balance(self: @TContractState, pooled_token_addr: ContractAddress) -> u256; + fn get_max_lpool_balance(self: @TContractState, lpt_addr: ContractAddress) -> u256; fn set_max_lpool_balance( - ref self: TContractState, pooled_token_addr: ContractAddress, max_lpool_bal: u256 + ref self: TContractState, lpt_addr: ContractAddress, max_lpool_bal: u256 ); fn get_pool_locked_capital(self: @TContractState, lptoken_address: ContractAddress) -> u256; - // fn get_available_options(lptoken_address: ContractAddress, order_i: felt252) -> Option; - fn get_available_options_usable_index( - self: @TContractState, lptoken_address: ContractAddress, starting_index: felt252 - ) -> felt252; + // fn get_available_options( + // self: @TContractState, lptoken_address: ContractAddress, order_i: u32 + // ) -> Option_; + fn get_lptoken_address_for_given_option( self: @TContractState, quote_token_address: ContractAddress, base_token_address: ContractAddress, option_type: OptionType, ) -> ContractAddress; - //fn get_pool_definition_from_lptoken_address(lptoken_addres: ContractAddress) -> Pool; - fn get_option_type(self: @TContractState, lptoken_address: ContractAddress) -> OptionType; - fn get_pool_volatility_separate( - self: @TContractState, - lptoken_address: ContractAddress, - maturity: felt252, - strike_price: Math64x61_, - ) -> Math64x61_; + // fn get_pool_definition_from_lptoken_address( + // self: @TContractState, lptoken_addres: ContractAddress + // ) -> Pool; + fn get_option_volatility( + self: @TContractState, lptoken_address: ContractAddress, maturity: u64, strike_price: Fixed, + ) -> Fixed; fn get_underlying_token_address( self: @TContractState, lptoken_address: ContractAddress ) -> ContractAddress; @@ -196,44 +202,45 @@ trait IAMM { ) -> felt252; fn get_pool_volatility_adjustment_speed( self: @TContractState, lptoken_address: ContractAddress - ) -> Math64x61_; - fn set_pool_volatility_adjustment_speed_external( - ref self: TContractState, lptoken_address: ContractAddress, new_speed: Math64x61_, + ) -> Fixed; + fn set_pool_volatility_adjustment_speed( + ref self: TContractState, lptoken_address: ContractAddress, new_speed: Fixed ); - fn get_pool_volatility( - self: @TContractState, lptoken_address: ContractAddress, maturity: felt252 - ) -> Math64x61_; - fn get_pool_volatility_auto( - self: @TContractState, - lptoken_address: ContractAddress, - maturity: felt252, - strike_price: Math64x61_, - ) -> Math64x61_; fn get_option_position( self: @TContractState, lptoken_address: ContractAddress, option_side: OptionSide, - maturity: felt252, - strike_price: Math64x61_ - ) -> felt252; - // need to return two values, unclear rn - //fn get_total_premia( - // option: Option, lptoken_address: ContractAddress, position_size: u256, is_closing: Bool, - //) -> (total_premia_before_fees : Math64x61_, total_premia_including_fees : Math64x61_); + maturity: u64, + strike_price: Fixed + ) -> u128; + // fn get_total_premia( + // self: @TContractState, option: Option_, position_size: u256, is_closing: bool + // ) -> (Fixed, Fixed); + fn black_scholes( self: @TContractState, - sigma: felt252, - time_till_maturity_annualized: felt252, - strike_price: felt252, - underlying_price: felt252, - risk_free_rate_annualized: felt252, - is_for_trade: felt252, // bool - ) -> (felt252, felt252); - fn empiric_median_price(self: @TContractState, key: felt252) -> Math64x61_; - fn initializer(ref self: TContractState, proxy_admin: ContractAddress); - fn upgrade(ref self: TContractState, new_implementation: felt252); - fn setAdmin(ref self: TContractState, address: felt252); - fn getImplementationHash(self: @TContractState,) -> felt252; + sigma: Fixed, + time_till_maturity_annualized: Fixed, + strike_price: Fixed, + underlying_price: Fixed, + risk_free_rate_annualized: Fixed, + is_for_trade: bool, // bool + ) -> (Fixed, Fixed, bool); + fn get_current_price( + self: @TContractState, + quote_token_address: ContractAddress, + base_token_address: ContractAddress + ) -> Fixed; + fn get_terminal_price( + self: @TContractState, + quote_token_address: ContractAddress, + base_token_address: ContractAddress, + maturity: u64 + ) -> Fixed; + + fn set_pragma_checkpoint(ref self: TContractState, key: felt252); + fn set_pragma_required_checkpoints(ref self: TContractState); + fn upgrade(ref self: TContractState, new_implementation: ClassHash); } #[starknet::interface] @@ -263,42 +270,56 @@ trait IGovernanceToken { } #[starknet::interface] -trait IOptionToken { - fn initializer( - ref self: TContractState, - name: felt252, - symbol: felt252, - proxy_admin: ContractAddress, - owner: ContractAddress, - quote_token_address: ContractAddress, - base_token_address: ContractAddress, - option_type: OptionType, - strike_price: Math64x61_, - maturity: felt252, - side: OptionSide - ); - fn _set_owner_admin(ref self: TContractState, owner: ContractAddress); - fn upgrade(ref self: TContractState, new_implementation: felt252); - fn name(self: @TContractState) -> felt252; - fn symbol(self: @TContractState) -> felt252; - fn decimals(self: @TContractState) -> felt252; - fn totalSupply(self: @TContractState) -> u256; - fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; - fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn owner(self: @TContractState) -> ContractAddress; - fn quote_token_address(self: @TContractState) -> ContractAddress; - fn base_token_address(self: @TContractState) -> ContractAddress; - fn option_type(self: @TContractState) -> OptionType; - fn strike_price(self: @TContractState) -> Math64x61_; - fn maturity(self: @TContractState) -> felt252; - fn side(self: @TContractState) -> OptionSide; - fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> felt252; +trait IOptionToken { + // IERC20 + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + + // IERC20Metadata + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + + // IERC20SafeAllowance + fn increase_allowance(ref self: TState, spender: ContractAddress, added_value: u256) -> bool; + fn decrease_allowance( + ref self: TState, spender: ContractAddress, subtracted_value: u256 + ) -> bool; + + // IERC20CamelOnly + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; fn transferFrom( - ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> felt252; - fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> felt252; - fn mint(ref self: TContractState, to: ContractAddress, amount: u256); - fn burn(ref self: TContractState, account: ContractAddress, amount: u256); - fn transferOwnership(ref self: TContractState, newOwner: ContractAddress); - fn renounceOwnership(ref self: TContractState); + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + + // IERC20CamelSafeAllowance + fn increaseAllowance(ref self: TState, spender: ContractAddress, addedValue: u256) -> bool; + fn decreaseAllowance(ref self: TState, spender: ContractAddress, subtractedValue: u256) -> bool; + + // Custom Functions + fn mint(ref self: TState, recipient: ContractAddress, amount: u256); + fn burn(ref self: TState, account: ContractAddress, amount: u256); + fn upgrade(ref self: TState, new_class_hash: ClassHash); + + // Ownable Functions + fn transferOwnership(ref self: TState, newOwner: ContractAddress); + fn renounceOwnership(ref self: TState); + fn owner(self: @TState) -> ContractAddress; + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); + + // Option data + fn quote_token_address(self: @TState) -> ContractAddress; + fn base_token_address(self: @TState) -> ContractAddress; + fn option_type(self: @TState) -> u8; + fn strike_price(self: @TState) -> Fixed; + fn maturity(self: @TState) -> u64; + fn side(self: @TState) -> u8; } diff --git a/src/upgrades.cairo b/src/upgrades.cairo index cccaa211..552ab88b 100644 --- a/src/upgrades.cairo +++ b/src/upgrades.cairo @@ -52,7 +52,8 @@ mod Upgrades { match contract_type { 0 => { let amm_addr: ContractAddress = state.get_amm_address(); - IAMMDispatcher { contract_address: amm_addr }.upgrade(impl_hash); + IAMMDispatcher { contract_address: amm_addr } + .upgrade(impl_hash.try_into().unwrap()); }, _ => { if (contract_type == 1) { diff --git a/src/vesting.cairo b/src/vesting.cairo new file mode 100644 index 00000000..e0aecfe3 --- /dev/null +++ b/src/vesting.cairo @@ -0,0 +1,128 @@ +use starknet::ContractAddress; + +// To add new vesting schedules, this Component should be part of a Custom proposal which calls add_linear_vesting_schedule or a vesting milestone. +// This will execute the code from this component in the context of the contract. Only vest() should be exported externally from the contract. + +#[starknet::interface] +trait IVesting { + fn vest(ref self: TContractState, grantee: ContractAddress, vested_timestamp: u64); + + fn add_vesting_milestone( + ref self: TContractState, vesting_timestamp: u64, grantee: ContractAddress, amount: u128 + ); + + fn add_linear_vesting_schedule( + ref self: TContractState, + first_vest: u64, + period: u64, + increments_count: u64, + total_amount: u128 + ); +// MAYBE – streaming? +// MAYBE – options on the govtoken? +} + +#[starknet::component] +mod vesting { + use starknet::syscalls::get_block_timestamp; + + use governance::traits::IGovernanceTokenDispatcher; + use governance::traits::IGovernanceTokenDispatcherTrait; + + #[storage] + struct Storage { + milestone: LegacyMap::<(u64, ContractAddress), u128> + } + + #[derive(starknet::Event, Drop)] + #[event] + enum Event { + VestingMilestoneAdded: VestingMilestoneAdded, + Vested: Vested + } + + #[derive(starknet::Event, Drop)] + struct VestingMilestoneAdded { + grantee: ContractAddress, + timestamp: u64, + amount: u128 + } + + #[derive(starknet::Event, Drop)] + struct Vested { + grantee: ContractAddress, + timestamp: u64, + amount: u128 + } + + #[embeddable_as(VestingImpl)] + impl Vesting< + TContractState, +HasComponent + > of super::IVesting> { + fn vest( + ref self: ComponentState, + grantee: ContractAddress, + vested_timestamp: u64 + ) { + let amt_to_vest = self.milestone.read((vested_timestamp, grantee)); + assert(amt_to_vest != 0, 'no vesting milestone found, or already vested'); + assert(get_block_timestamp() > vested_timestamp, 'not yet eligible'); + IGovernanceTokenDispatcher { contract_address: govtoken_addr } + .mint(claimee, u256 { high: 0, low: amt_to_vest }); + self.milestone.write((vested_timestamp, grantee), 0); + self + .emit( + Vested { grantee: grantee, timestamp: vested_timestamp, amount: amt_to_vest } + ); + } + + fn add_vesting_milestone( + ref self: ComponentState, + vesting_timestamp: u64, + grantee: ContractAddress, + amount: u128 + ) { + self.milestone.write((vested_timestamp, grantee), amount); + self + .emit( + VestingMilestoneAdded { + grantee: grantee, timestamp: vesting_timestamp, amount: u128 + } + ) + } + + fn add_linear_vesting_schedule( + ref self: TContractState, + first_vest: u64, + period: u64, + increments_count: u16, + total_amount: u128, + grantee: ContractAddress + ) { + let mut i: u16 = 0; + let mut curr_timestamp = first_vest; + assert(increments_count > 1, 'schedule must have more than one milestone'); + assert(get_block_timestamp() < first_vest, 'first vest cannot be in the past'); + assert() + let per_vest_amount = total_amount / increments_count; + let mut total_scheduled = 0; + loop { + if i == increments_count { + break; + } + total_scheduled += per_vest_amount; + if i + 1 == increments_count { + let left_to_get_to_total = total_amount - total_scheduled; + self + .add_vesting_milestone( + curr_timestamp, grantee, per_vest_amount + left_to_get_to_total + ); + } else { + self.add_vesting_milestone(curr_timestamp, grantee, per_vest_amount); + } + curr_timestamp = curr_timestamp + period; + i += 1; + } + } + } +} diff --git a/tests/add_options.cairo b/tests/add_options.cairo new file mode 100644 index 00000000..1400378d --- /dev/null +++ b/tests/add_options.cairo @@ -0,0 +1,73 @@ +use tests::basic::submit_44_signal_proposals; + +use governance::traits::IAMM; +use governance::contract::IGovernanceDispatcher; +use governance::contract::IGovernanceDispatcherTrait; +use governance::traits::{ + IAMMDispatcher, IAMMDispatcherTrait, IERC20Dispatcher, IERC20DispatcherTrait +}; + +use starknet::{ContractAddress, get_block_timestamp}; + +use snforge_std::{declare, ContractClassTrait, ContractClass, start_prank, start_warp, CheatTarget}; +use cubit::f128::types::{Fixed, FixedTrait}; + +use debug::PrintTrait; + +// #[test] +// #[fork("MAINNET")] +// fn test_add_options() { +// submit_44_signal_proposals(); +// let gov_contract_addr: ContractAddress = +// 0x001405ab78ab6ec90fba09e6116f373cda53b0ba557789a4578d8c1ec374ba0f +// .try_into() +// .unwrap(); +// let dispatcher = IGovernanceDispatcher { contract_address: gov_contract_addr }; +// let marek_address: ContractAddress = +// 0x0011d341c6e841426448ff39aa443a6dbb428914e05ba2259463c18308b86233 +// .try_into() +// .unwrap(); +// let new_contract: ContractClass = declare('Governance'); +// start_prank(CheatTarget::One(gov_contract_addr), marek_address); +// let ret = dispatcher.submit_proposal(new_contract.class_hash.into(), 1); +// dispatcher.vote(ret, 1); +// let curr_timestamp = get_block_timestamp(); +// let warped_timestamp = curr_timestamp + consteval_int!(60 * 60 * 24 * 7) + 420; +// start_warp(CheatTarget::One(gov_contract_addr), warped_timestamp); +// let status = dispatcher.get_proposal_status(ret); +// dispatcher.apply_passed_proposal(ret); +// dispatcher.add_0501_options(); +// let amm_addr = 0x076dbabc4293db346b0a56b29b6ea9fe18e93742c73f12348c8747ecfc1050aa +// .try_into() +// .unwrap(); +// trade_option(1704412799, marek_address, amm_addr, FixedTrait::from_unscaled_felt(2200)); +// } + +// buys 0.01 long eth/usdc call +fn trade_option( + maturity: u64, trader: ContractAddress, amm_addr: ContractAddress, strike_price: Fixed +) { + let amm = IAMMDispatcher { contract_address: amm_addr }; + start_prank(CheatTarget::One(amm_addr), trader); + let amt = 184467440737095520; + let USDC_addr: felt252 = 0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8; + let ETH_addr: felt252 = 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7; + let quote_token_address = USDC_addr.try_into().unwrap(); + let base_token_address = ETH_addr.try_into().unwrap(); + let curr_timestamp = get_block_timestamp(); + let eth = IERC20Dispatcher { contract_address: base_token_address }; + start_prank(CheatTarget::One(base_token_address), trader); + eth.approve(amm_addr, amt + amt); + amm + .trade_open( + 0, + strike_price, + maturity.into(), + 0, + amt.low.into(), + quote_token_address, + base_token_address, + FixedTrait::ONE(), + (curr_timestamp + 420).into() + ); +} diff --git a/tests/basic.cairo b/tests/basic.cairo new file mode 100644 index 00000000..12457ebb --- /dev/null +++ b/tests/basic.cairo @@ -0,0 +1,147 @@ +use array::ArrayTrait; +use core::traits::TryInto; +use debug::PrintTrait; +use starknet::ContractAddress; +use snforge_std::{BlockId, declare, ContractClassTrait, ContractClass, start_prank, CheatTarget}; + +use governance::contract::IGovernanceDispatcher; +use governance::contract::IGovernanceDispatcherTrait; + +//#[test] +//#[fork(url: "https://rpc.starknet-testnet.lava.build", block_id: BlockId::Number(904597))] +fn test_submit_proposal() { + let gov_contract_addr: ContractAddress = + 0x7ba1d4836a1142c09dde23cb39b2885fe350912591461b5764454a255bdbac6 + .try_into() + .unwrap(); + let dispatcher = IGovernanceDispatcher { contract_address: gov_contract_addr }; + // corresponding govtoken: 0x05151bfdd47826df3669033ea7fb977d3b2d45c4f4d1c439a9edf4062bf34bfa + // has one holder, with 31 CARM: 0x0583a9d956d65628f806386ab5b12dccd74236a3c6b930ded9cf3c54efc722a1 + let admin_addr: ContractAddress = + 0x0583a9d956d65628f806386ab5b12dccd74236a3c6b930ded9cf3c54efc722a1 + .try_into() + .unwrap(); + //start_prank(gov_contract_addr, admin_addr); + dispatcher.submit_proposal(0x00, 1); +} + +#[test] +#[fork("GOERLI")] +fn test_forking_functionality() { + let gov_contract_addr: ContractAddress = + 0x7ba1d4836a1142c09dde23cb39b2885fe350912591461b5764454a255bdbac6 + .try_into() + .unwrap(); + let dispatcher = IGovernanceDispatcher { contract_address: gov_contract_addr }; + let propdetails = dispatcher.get_proposal_details(1); + assert( + propdetails.payload == 0x78b4ccacdc1c902281f6f13d94b6d17b1f4c44ff811c01dea504d43a264f611, + 'payload not match' + ); +} + + +// Raises the prop_id to 44, fixes prop_id now 0 +fn submit_44_signal_proposals() { + let gov_contract_addr: ContractAddress = + 0x001405ab78ab6ec90fba09e6116f373cda53b0ba557789a4578d8c1ec374ba0f + .try_into() + .unwrap(); + let dispatcher = IGovernanceDispatcher { contract_address: gov_contract_addr }; + let scaling_address: ContractAddress = + 0x052df7acdfd3174241fa6bd5e1b7192cd133f8fc30a2a6ed99b0ddbfb5b22dcd + .try_into() + .unwrap(); + + let mut curr_prop: u8 = 0; + + loop { + if curr_prop == 44 { + break; + } + start_prank(CheatTarget::One(gov_contract_addr), scaling_address); + dispatcher.submit_proposal(42, 4); // for signal vote with payload 42 + curr_prop += 1; + } +} + + +// This proposes upgrading the current mainnet Carmine governance to the one in master, votes on it with multiple wallets and passes the upgrade turbo. +#[test] +#[fork("MAINNET")] +fn test_upgrade_mainnet_to_master() { + let gov_contract_addr: ContractAddress = + 0x001405ab78ab6ec90fba09e6116f373cda53b0ba557789a4578d8c1ec374ba0f + .try_into() + .unwrap(); + let dispatcher = IGovernanceDispatcher { contract_address: gov_contract_addr }; + + let mut top_carm_holders = ArrayTrait::new(); + let marek_address: ContractAddress = + 0x0011d341c6e841426448ff39aa443a6dbb428914e05ba2259463c18308b86233 + .try_into() + .unwrap(); + top_carm_holders.append(marek_address); + let scaling_address: ContractAddress = + 0x052df7acdfd3174241fa6bd5e1b7192cd133f8fc30a2a6ed99b0ddbfb5b22dcd + .try_into() + .unwrap(); + top_carm_holders.append(scaling_address); + let ondrej_address: ContractAddress = + 0x0583a9d956d65628f806386ab5b12dccd74236a3c6b930ded9cf3c54efc722a1 + .try_into() + .unwrap(); + top_carm_holders.append(ondrej_address); + let carlote_address: ContractAddress = + 0x021b2b25dd73bc60b0549683653081f8963562cbe5cba2d123ec0cbcbf0913e4 + .try_into() + .unwrap(); + top_carm_holders.append(carlote_address); + let fifth_address: ContractAddress = + 0x02af7135154dc27d9311b79c57ccc7b3a6ed74efd0c2b81116e8eb49dbf6aaf8 + .try_into() + .unwrap(); + top_carm_holders.append(fifth_address); + let sixth_address: ContractAddress = + 0x07824efd915baa421d93909bd7f24e36c022b5cfbc5af6687328848a6490ada7 + .try_into() + .unwrap(); + top_carm_holders.append(sixth_address); + let madman_address: ContractAddress = + 0x06717eaf502baac2b6b2c6ee3ac39b34a52e726a73905ed586e757158270a0af + .try_into() + .unwrap(); + top_carm_holders.append(madman_address); + let eighth_address: ContractAddress = + 0x03d1525605db970fa1724693404f5f64cba8af82ec4aab514e6ebd3dec4838ad + .try_into() + .unwrap(); + top_carm_holders.append(eighth_address); + + // declare current and submit proposal + let new_contract: ContractClass = declare('Governance'); + start_prank(CheatTarget::One(gov_contract_addr), scaling_address); + let new_prop_id = dispatcher.submit_proposal(new_contract.class_hash.into(), 1); + loop { + match top_carm_holders.pop_front() { + Option::Some(holder) => { + start_prank(CheatTarget::One(gov_contract_addr), holder); + dispatcher.vote(new_prop_id, 1); + }, + Option::None(()) => { break (); }, + } + }; + assert(dispatcher.get_proposal_status(new_prop_id) == 1, 'proposal not passed!'); + + dispatcher.apply_passed_proposal(new_prop_id); + assert(check_if_healthy(gov_contract_addr), 'new gov not healthy'); +} + + +fn check_if_healthy(gov_contract_addr: ContractAddress) -> bool { + // TODO + let dispatcher = IGovernanceDispatcher { contract_address: gov_contract_addr }; + dispatcher.get_proposal_status(0); + let prop_details = dispatcher.get_proposal_details(0); + (prop_details.payload + prop_details.to_upgrade) != 0 +}