diff --git a/.gitignore b/.gitignore index 1c87b9ee..abc99a6f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ out/ .DS_Store .env -scripts/simulationData/simulationOutput/simulationResults*.json \ No newline at end of file +scripts/simulationData/simulationOutput/simulationResults*.json +scripts/simulationData/simulationSheet*.json \ No newline at end of file diff --git a/scripts/abi/optionRound.ts b/scripts/abi/optionRound.ts index 6a63efd9..124d34c3 100644 --- a/scripts/abi/optionRound.ts +++ b/scripts/abi/optionRound.ts @@ -80,18 +80,36 @@ export const ABI = [ } ] }, + { + "type": "enum", + "name": "pitch_lake_starknet::types::OptionRoundState", + "variants": [ + { + "name": "Open", + "type": "()" + }, + { + "name": "Auctioning", + "type": "()" + }, + { + "name": "Running", + "type": "()" + }, + { + "name": "Settled", + "type": "()" + } + ] + }, { "type": "struct", "name": "pitch_lake_starknet::types::Bid", "members": [ { - "name": "id", + "name": "bid_id", "type": "core::felt252" }, - { - "name": "nonce", - "type": "core::integer::u64" - }, { "name": "owner", "type": "core::starknet::contract_address::ContractAddress" @@ -103,28 +121,10 @@ export const ABI = [ { "name": "price", "type": "core::integer::u256" - } - ] - }, - { - "type": "enum", - "name": "pitch_lake_starknet::types::OptionRoundState", - "variants": [ - { - "name": "Open", - "type": "()" - }, - { - "name": "Auctioning", - "type": "()" - }, - { - "name": "Running", - "type": "()" }, { - "name": "Settled", - "type": "()" + "name": "tree_nonce", + "type": "core::integer::u64" } ] }, @@ -134,73 +134,73 @@ export const ABI = [ "items": [ { "type": "function", - "name": "get_auction_start_date", + "name": "get_vault_address", "inputs": [], "outputs": [ { - "type": "core::integer::u64" + "type": "core::starknet::contract_address::ContractAddress" } ], "state_mutability": "view" }, { "type": "function", - "name": "get_auction_end_date", + "name": "get_round_id", "inputs": [], "outputs": [ { - "type": "core::integer::u64" + "type": "core::integer::u256" } ], "state_mutability": "view" }, { "type": "function", - "name": "get_option_settlement_date", + "name": "get_state", "inputs": [], "outputs": [ { - "type": "core::integer::u64" + "type": "pitch_lake_starknet::types::OptionRoundState" } ], "state_mutability": "view" }, { "type": "function", - "name": "starting_liquidity", + "name": "get_auction_start_date", "inputs": [], "outputs": [ { - "type": "core::integer::u256" + "type": "core::integer::u64" } ], "state_mutability": "view" }, { "type": "function", - "name": "unsold_liquidity", + "name": "get_auction_end_date", "inputs": [], "outputs": [ { - "type": "core::integer::u256" + "type": "core::integer::u64" } ], "state_mutability": "view" }, { "type": "function", - "name": "total_premiums", + "name": "get_option_settlement_date", "inputs": [], "outputs": [ { - "type": "core::integer::u256" + "type": "core::integer::u64" } ], "state_mutability": "view" }, { "type": "function", - "name": "total_payout", + "name": "get_starting_liquidity", "inputs": [], "outputs": [ { @@ -211,7 +211,7 @@ export const ABI = [ }, { "type": "function", - "name": "clearing_price", + "name": "get_unsold_liquidity", "inputs": [], "outputs": [ { @@ -222,7 +222,7 @@ export const ABI = [ }, { "type": "function", - "name": "total_options_sold", + "name": "get_reserve_price", "inputs": [], "outputs": [ { @@ -233,61 +233,41 @@ export const ABI = [ }, { "type": "function", - "name": "get_bid_details", - "inputs": [ - { - "name": "bid_id", - "type": "core::felt252" - } - ], + "name": "get_strike_price", + "inputs": [], "outputs": [ { - "type": "pitch_lake_starknet::types::Bid" + "type": "core::integer::u256" } ], "state_mutability": "view" }, { "type": "function", - "name": "get_bidding_nonce_for", - "inputs": [ - { - "name": "option_buyer", - "type": "core::starknet::contract_address::ContractAddress" - } - ], + "name": "get_cap_level", + "inputs": [], "outputs": [ { - "type": "core::integer::u32" + "type": "core::integer::u128" } ], "state_mutability": "view" }, { "type": "function", - "name": "get_bids_for", - "inputs": [ - { - "name": "option_buyer", - "type": "core::starknet::contract_address::ContractAddress" - } - ], + "name": "get_options_available", + "inputs": [], "outputs": [ { - "type": "core::array::Array::" + "type": "core::integer::u256" } ], "state_mutability": "view" }, { "type": "function", - "name": "get_refundable_balance_for", - "inputs": [ - { - "name": "option_buyer", - "type": "core::starknet::contract_address::ContractAddress" - } - ], + "name": "get_options_sold", + "inputs": [], "outputs": [ { "type": "core::integer::u256" @@ -297,13 +277,19 @@ export const ABI = [ }, { "type": "function", - "name": "get_total_options_balance_for", - "inputs": [ + "name": "get_clearing_price", + "inputs": [], + "outputs": [ { - "name": "option_buyer", - "type": "core::starknet::contract_address::ContractAddress" + "type": "core::integer::u256" } ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_total_premium", + "inputs": [], "outputs": [ { "type": "core::integer::u256" @@ -313,13 +299,19 @@ export const ABI = [ }, { "type": "function", - "name": "get_payout_balance_for", - "inputs": [ + "name": "get_settlement_price", + "inputs": [], + "outputs": [ { - "name": "option_buyer", - "type": "core::starknet::contract_address::ContractAddress" + "type": "core::integer::u256" } ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_total_payout", + "inputs": [], "outputs": [ { "type": "core::integer::u256" @@ -329,68 +321,72 @@ export const ABI = [ }, { "type": "function", - "name": "get_mintable_options_for", + "name": "get_account_bid_nonce", "inputs": [ { - "name": "option_buyer", + "name": "account", "type": "core::starknet::contract_address::ContractAddress" } ], "outputs": [ { - "type": "core::integer::u256" + "type": "core::integer::u64" } ], "state_mutability": "view" }, { "type": "function", - "name": "vault_address", + "name": "get_bid_tree_nonce", "inputs": [], "outputs": [ { - "type": "core::starknet::contract_address::ContractAddress" + "type": "core::integer::u64" } ], "state_mutability": "view" }, { "type": "function", - "name": "get_state", - "inputs": [], - "outputs": [ + "name": "get_bid_details", + "inputs": [ { - "type": "pitch_lake_starknet::types::OptionRoundState" + "name": "bid_id", + "type": "core::felt252" } ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "get_strike_price", - "inputs": [], "outputs": [ { - "type": "core::integer::u256" + "type": "pitch_lake_starknet::types::Bid" } ], "state_mutability": "view" }, { "type": "function", - "name": "get_cap_level", - "inputs": [], + "name": "get_account_bids", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], "outputs": [ { - "type": "core::integer::u128" + "type": "core::array::Array::" } ], "state_mutability": "view" }, { "type": "function", - "name": "get_reserve_price", - "inputs": [], + "name": "get_account_refundable_balance", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], "outputs": [ { "type": "core::integer::u256" @@ -400,8 +396,13 @@ export const ABI = [ }, { "type": "function", - "name": "total_options_available", - "inputs": [], + "name": "get_account_mintable_options", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], "outputs": [ { "type": "core::integer::u256" @@ -411,8 +412,13 @@ export const ABI = [ }, { "type": "function", - "name": "get_round_id", - "inputs": [], + "name": "get_account_total_options", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], "outputs": [ { "type": "core::integer::u256" @@ -422,23 +428,19 @@ export const ABI = [ }, { "type": "function", - "name": "update_round_params", + "name": "get_account_payout_balance", "inputs": [ { - "name": "reserve_price", - "type": "core::integer::u256" - }, - { - "name": "cap_level", - "type": "core::integer::u128" - }, + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ { - "name": "strike_price", "type": "core::integer::u256" } ], - "outputs": [], - "state_mutability": "external" + "state_mutability": "view" }, { "type": "function", @@ -469,7 +471,7 @@ export const ABI = [ }, { "type": "function", - "name": "settle_option_round", + "name": "settle_round", "inputs": [ { "name": "settlement_price", @@ -478,9 +480,29 @@ export const ABI = [ ], "outputs": [ { - "type": "(core::integer::u256, core::integer::u256)" + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "update_round_params", + "inputs": [ + { + "name": "reserve_price", + "type": "core::integer::u256" + }, + { + "name": "cap_level", + "type": "core::integer::u128" + }, + { + "name": "strike_price", + "type": "core::integer::u256" } ], + "outputs": [], "state_mutability": "external" }, { @@ -512,11 +534,7 @@ export const ABI = [ "type": "core::felt252" }, { - "name": "new_amount", - "type": "core::integer::u256" - }, - { - "name": "new_price", + "name": "price_increase", "type": "core::integer::u256" } ], @@ -532,7 +550,7 @@ export const ABI = [ "name": "refund_unused_bids", "inputs": [ { - "name": "option_bidder", + "name": "account", "type": "core::starknet::contract_address::ContractAddress" } ], @@ -809,7 +827,12 @@ export const ABI = [ "kind": "struct", "members": [ { - "name": "total_options_available", + "name": "starting_liquidity", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "options_available", "type": "core::integer::u256", "kind": "data" } @@ -817,7 +840,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::option_round::contract::OptionRound::BidAccepted", + "name": "pitch_lake_starknet::option_round::contract::OptionRound::BidPlaced", "kind": "struct", "members": [ { @@ -826,8 +849,8 @@ export const ABI = [ "kind": "key" }, { - "name": "nonce", - "type": "core::integer::u32", + "name": "bid_id", + "type": "core::felt252", "kind": "data" }, { @@ -839,6 +862,11 @@ export const ABI = [ "name": "price", "type": "core::integer::u256", "kind": "data" + }, + { + "name": "bid_tree_nonce_now", + "type": "core::integer::u64", + "kind": "data" } ] }, @@ -853,28 +881,18 @@ export const ABI = [ "kind": "key" }, { - "name": "id", + "name": "bid_id", "type": "core::felt252", "kind": "data" }, { - "name": "old_amount", + "name": "price_increase", "type": "core::integer::u256", "kind": "data" }, { - "name": "old_price", - "type": "core::integer::u256", - "kind": "data" - }, - { - "name": "new_amount", - "type": "core::integer::u256", - "kind": "data" - }, - { - "name": "new_price", - "type": "core::integer::u256", + "name": "bid_tree_nonce_now", + "type": "core::integer::u64", "kind": "data" } ] @@ -884,13 +902,18 @@ export const ABI = [ "name": "pitch_lake_starknet::option_round::contract::OptionRound::AuctionEnded", "kind": "struct", "members": [ + { + "name": "options_sold", + "type": "core::integer::u256", + "kind": "data" + }, { "name": "clearing_price", "type": "core::integer::u256", "kind": "data" }, { - "name": "total_options_sold", + "name": "unsold_liquidity", "type": "core::integer::u256", "kind": "data" } @@ -902,7 +925,7 @@ export const ABI = [ "kind": "struct", "members": [ { - "name": "total_payout", + "name": "settlement_price", "type": "core::integer::u256", "kind": "data" }, @@ -910,11 +933,6 @@ export const ABI = [ "name": "payout_per_option", "type": "core::integer::u256", "kind": "data" - }, - { - "name": "settlement_price", - "type": "core::integer::u256", - "kind": "data" } ] }, @@ -929,12 +947,12 @@ export const ABI = [ "kind": "key" }, { - "name": "num_options", + "name": "number_of_options", "type": "core::integer::u256", "kind": "data" }, { - "name": "amount", + "name": "exercised_amount", "type": "core::integer::u256", "kind": "data" } @@ -951,7 +969,7 @@ export const ABI = [ "kind": "key" }, { - "name": "amount", + "name": "refunded_amount", "type": "core::integer::u256", "kind": "data" } @@ -1018,7 +1036,7 @@ export const ABI = [ "kind": "key" }, { - "name": "amount", + "name": "minted_amount", "type": "core::integer::u256", "kind": "data" } @@ -1096,8 +1114,8 @@ export const ABI = [ "kind": "nested" }, { - "name": "BidAccepted", - "type": "pitch_lake_starknet::option_round::contract::OptionRound::BidAccepted", + "name": "BidPlaced", + "type": "pitch_lake_starknet::option_round::contract::OptionRound::BidPlaced", "kind": "nested" }, { diff --git a/scripts/abi/vault.ts b/scripts/abi/vault.ts index c1aa369e..b2a9f5e2 100644 --- a/scripts/abi/vault.ts +++ b/scripts/abi/vault.ts @@ -42,18 +42,7 @@ export const ABI = [ "items": [ { "type": "function", - "name": "vault_manager", - "inputs": [], - "outputs": [ - { - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "vault_type", + "name": "get_vault_type", "inputs": [], "outputs": [ { @@ -64,7 +53,7 @@ export const ABI = [ }, { "type": "function", - "name": "get_market_aggregator", + "name": "get_market_aggregator_address", "inputs": [], "outputs": [ { @@ -75,7 +64,7 @@ export const ABI = [ }, { "type": "function", - "name": "eth_address", + "name": "get_eth_address", "inputs": [], "outputs": [ { @@ -119,7 +108,7 @@ export const ABI = [ }, { "type": "function", - "name": "current_round_id", + "name": "get_current_round_id", "inputs": [], "outputs": [ { @@ -146,13 +135,8 @@ export const ABI = [ }, { "type": "function", - "name": "get_lp_starting_balance", - "inputs": [ - { - "name": "liquidity_provider", - "type": "core::starknet::contract_address::ContractAddress" - } - ], + "name": "get_vault_total_balance", + "inputs": [], "outputs": [ { "type": "core::integer::u256" @@ -162,13 +146,8 @@ export const ABI = [ }, { "type": "function", - "name": "get_lp_locked_balance", - "inputs": [ - { - "name": "liquidity_provider", - "type": "core::starknet::contract_address::ContractAddress" - } - ], + "name": "get_vault_locked_balance", + "inputs": [], "outputs": [ { "type": "core::integer::u256" @@ -178,13 +157,8 @@ export const ABI = [ }, { "type": "function", - "name": "get_lp_unlocked_balance", - "inputs": [ - { - "name": "liquidity_provider", - "type": "core::starknet::contract_address::ContractAddress" - } - ], + "name": "get_vault_unlocked_balance", + "inputs": [], "outputs": [ { "type": "core::integer::u256" @@ -194,17 +168,8 @@ export const ABI = [ }, { "type": "function", - "name": "get_lp_queued_balance", - "inputs": [ - { - "name": "liquidity_provider", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "round_id", - "type": "core::integer::u256" - } - ], + "name": "get_vault_stashed_balance", + "inputs": [], "outputs": [ { "type": "core::integer::u256" @@ -214,26 +179,21 @@ export const ABI = [ }, { "type": "function", - "name": "get_lp_stashed_balance", - "inputs": [ - { - "name": "liquidity_provider", - "type": "core::starknet::contract_address::ContractAddress" - } - ], + "name": "get_vault_queued_bps", + "inputs": [], "outputs": [ { - "type": "core::integer::u256" + "type": "core::integer::u16" } ], "state_mutability": "view" }, { "type": "function", - "name": "get_lp_total_balance", + "name": "get_account_total_balance", "inputs": [ { - "name": "liquidity_provider", + "name": "account", "type": "core::starknet::contract_address::ContractAddress" } ], @@ -246,19 +206,13 @@ export const ABI = [ }, { "type": "function", - "name": "get_total_locked_balance", - "inputs": [], - "outputs": [ + "name": "get_account_locked_balance", + "inputs": [ { - "type": "core::integer::u256" + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" } ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "get_total_unlocked_balance", - "inputs": [], "outputs": [ { "type": "core::integer::u256" @@ -268,11 +222,11 @@ export const ABI = [ }, { "type": "function", - "name": "get_total_queued_balance", + "name": "get_account_unlocked_balance", "inputs": [ { - "name": "round_id", - "type": "core::integer::u256" + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" } ], "outputs": [ @@ -284,19 +238,13 @@ export const ABI = [ }, { "type": "function", - "name": "get_total_stashed_balance", - "inputs": [], - "outputs": [ + "name": "get_account_stashed_balance", + "inputs": [ { - "type": "core::integer::u256" + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" } ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "get_total_balance", - "inputs": [], "outputs": [ { "type": "core::integer::u256" @@ -306,74 +254,30 @@ export const ABI = [ }, { "type": "function", - "name": "get_premiums_collected", + "name": "get_account_queued_bps", "inputs": [ { - "name": "liquidity_provider", + "name": "account", "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "round_id", - "type": "core::integer::u256" } ], "outputs": [ { - "type": "core::integer::u256" + "type": "core::integer::u16" } ], "state_mutability": "view" }, { "type": "function", - "name": "update_round_params", - "inputs": [], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "start_auction", - "inputs": [], - "outputs": [ - { - "type": "core::integer::u256" - } - ], - "state_mutability": "external" - }, - { - "type": "function", - "name": "end_auction", - "inputs": [], - "outputs": [ - { - "type": "(core::integer::u256, core::integer::u256)" - } - ], - "state_mutability": "external" - }, - { - "type": "function", - "name": "settle_option_round", - "inputs": [], - "outputs": [ - { - "type": "(core::integer::u256, core::integer::u256)" - } - ], - "state_mutability": "external" - }, - { - "type": "function", - "name": "deposit_liquidity", + "name": "deposit", "inputs": [ { "name": "amount", "type": "core::integer::u256" }, { - "name": "liquidity_provider", + "name": "account", "type": "core::starknet::contract_address::ContractAddress" } ], @@ -386,7 +290,7 @@ export const ABI = [ }, { "type": "function", - "name": "withdraw_liquidity", + "name": "withdraw", "inputs": [ { "name": "amount", @@ -405,8 +309,8 @@ export const ABI = [ "name": "queue_withdrawal", "inputs": [ { - "name": "amount", - "type": "core::integer::u256" + "name": "bps", + "type": "core::integer::u16" } ], "outputs": [], @@ -414,10 +318,10 @@ export const ABI = [ }, { "type": "function", - "name": "claim_queued_liquidity", + "name": "withdraw_stash", "inputs": [ { - "name": "liquidity_provider", + "name": "account", "type": "core::starknet::contract_address::ContractAddress" } ], @@ -430,49 +334,37 @@ export const ABI = [ }, { "type": "function", - "name": "convert_position_to_lp_tokens", - "inputs": [ - { - "name": "amount", - "type": "core::integer::u256" - } - ], + "name": "update_round_params", + "inputs": [], "outputs": [], "state_mutability": "external" }, { "type": "function", - "name": "convert_lp_tokens_to_position", - "inputs": [ + "name": "start_auction", + "inputs": [], + "outputs": [ { - "name": "source_round", - "type": "core::integer::u256" - }, - { - "name": "amount", "type": "core::integer::u256" } ], - "outputs": [], "state_mutability": "external" }, { "type": "function", - "name": "convert_lp_tokens_to_newer_lp_tokens", - "inputs": [ - { - "name": "source_round", - "type": "core::integer::u256" - }, - { - "name": "target_round", - "type": "core::integer::u256" - }, + "name": "end_auction", + "inputs": [], + "outputs": [ { - "name": "amount", - "type": "core::integer::u256" + "type": "(core::integer::u256, core::integer::u256)" } ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "settle_round", + "inputs": [], "outputs": [ { "type": "core::integer::u256" @@ -502,16 +394,12 @@ export const ABI = [ "name": "eth_address", "type": "core::starknet::contract_address::ContractAddress" }, - { - "name": "vault_manager", - "type": "core::starknet::contract_address::ContractAddress" - }, { "name": "vault_type", "type": "pitch_lake_starknet::types::VaultType" }, { - "name": "market_aggregator", + "name": "market_aggregator_address", "type": "core::starknet::contract_address::ContractAddress" }, { @@ -531,12 +419,17 @@ export const ABI = [ "kind": "key" }, { - "name": "position_balance_before", + "name": "amount", "type": "core::integer::u256", "kind": "data" }, { - "name": "position_balance_after", + "name": "account_unlocked_balance_now", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "vault_unlocked_balance_now", "type": "core::integer::u256", "kind": "data" } @@ -553,12 +446,17 @@ export const ABI = [ "kind": "key" }, { - "name": "position_balance_before", + "name": "amount", "type": "core::integer::u256", "kind": "data" }, { - "name": "position_balance_after", + "name": "account_unlocked_balance_now", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "vault_unlocked_balance_now", "type": "core::integer::u256", "kind": "data" } @@ -575,17 +473,17 @@ export const ABI = [ "kind": "key" }, { - "name": "round_id", - "type": "core::integer::u256", + "name": "bps", + "type": "core::integer::u16", "kind": "data" }, { - "name": "previous_amount_queued", + "name": "account_queued_liquidity_now", "type": "core::integer::u256", "kind": "data" }, { - "name": "new_amount_queued", + "name": "vault_queued_liquidity_now", "type": "core::integer::u256", "kind": "data" } @@ -593,7 +491,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::vault::contract::Vault::QueuedLiquidityCollected", + "name": "pitch_lake_starknet::vault::contract::Vault::StashWithdrawn", "kind": "struct", "members": [ { @@ -602,7 +500,12 @@ export const ABI = [ "kind": "key" }, { - "name": "stashed_amount", + "name": "amount", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "vault_stashed_balance_now", "type": "core::integer::u256", "kind": "data" } @@ -622,6 +525,36 @@ export const ABI = [ "name": "address", "type": "core::starknet::contract_address::ContractAddress", "kind": "data" + }, + { + "name": "reserve_price", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "strike_price", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "cap_level", + "type": "core::integer::u128", + "kind": "data" + }, + { + "name": "auction_start_date", + "type": "core::integer::u64", + "kind": "data" + }, + { + "name": "auction_end_date", + "type": "core::integer::u64", + "kind": "data" + }, + { + "name": "option_settlement_date", + "type": "core::integer::u64", + "kind": "data" } ] }, @@ -646,8 +579,8 @@ export const ABI = [ "kind": "nested" }, { - "name": "QueuedLiquidityCollected", - "type": "pitch_lake_starknet::vault::contract::Vault::QueuedLiquidityCollected", + "name": "StashWithdrawn", + "type": "pitch_lake_starknet::vault::contract::Vault::StashWithdrawn", "kind": "nested" }, { diff --git a/scripts/simulation.ts b/scripts/simulation.ts index b444acd1..cc8bbaad 100644 --- a/scripts/simulation.ts +++ b/scripts/simulation.ts @@ -5,6 +5,8 @@ import { TestRunner } from "./utils/facades/TestRunner"; import { Constants, MarketData } from "./utils/facades/types"; import { simulationTesting } from "./simulationTests"; import { SimulationParameters } from "./utils/facades/RoundSimulator"; +import { Account, CallData, hash, Provider } from "starknet"; +import { ERC20Facade } from "./utils/facades/erc20Facade"; async function main(environment: string, port?: string) { const provider = getProvider(environment, port); const devAccount = getAccount(environment, provider); @@ -33,15 +35,28 @@ async function main(environment: string, port?: string) { constants ); + const feeTokenFacade = new ERC20Facade( + "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + provider + ); + feeTokenFacade.erc20Contract.connect(devAccount); + await feeTokenFacade.erc20Contract.transfer( + "0x0134f47366096198eb8f86e3ae6b075d399ca7abd918a56e01bb3b24963c2f75", + BigInt(1000000000000000000) + ); + await feeTokenFacade.erc20Contract.transfer( + "0x01577908d02E0a3A6B243A149Eb91BB4514f3aAb948CFE63b2f8bb52397618D4", + BigInt(1000000000000000000) + ); await testRunner.ethFacade.supplyERC20( devAccount, provider, ethAddress, vaultAddress ); + await simulationTesting(testRunner); } main(process.argv[2], process.argv[3]); - diff --git a/scripts/simulationTests/index.ts b/scripts/simulationTests/index.ts index f44af61a..61840738 100644 --- a/scripts/simulationTests/index.ts +++ b/scripts/simulationTests/index.ts @@ -35,7 +35,6 @@ async function simulationTesting(testRunner: TestRunner) { const roundData = await simulator.simulateRound(roundParams); data.results.push(roundData); } - console.log("DATA", data); const stringified = JSON.stringify(data); fs.writeFile( `./simulationData/simulationOutput/simulationResults-${Math.floor( @@ -53,13 +52,13 @@ const initial = { liquidityProviders: [1, 2], depositAmounts: ["50000000000000", "50000000000000"], optionBidders: [1, 3], - bidAmounts: [5000, 7000], + }; const repeating = { liquidityProviders: [], depositAmounts: [], optionBidders: [1, 3], - bidAmounts: [5000, 7000], + }; export const generateSheet = () => { @@ -82,10 +81,16 @@ export const generateSheet = () => { return marketData.reservePrice; }), marketData, + bidAmounts:[Math.random(),Math.random()], + withdrawals:[1,2], + withdrawalAmounts:[Math.random()/2,Math.random()/2], } as SimulationSheet; } else return { ...repeating, + bidAmounts:[Math.random(),Math.random()], + withdrawals:[1,2], + withdrawalAmounts:[Math.random()/2,Math.random()/2], bidPrices: initial.optionBidders.map((bidder) => { return marketData.reservePrice; }), diff --git a/scripts/smokeTests.ts b/scripts/smokeTests.ts index e3f66708..07c7cf8c 100644 --- a/scripts/smokeTests.ts +++ b/scripts/smokeTests.ts @@ -17,6 +17,7 @@ async function main(environment: string, port?: string) { volatility:10, capLevel:5000, } + console.log("HASHES",hashes) let { ethAddress, vaultAddress } = await deployContracts( environment, devAccount, diff --git a/scripts/utils/constants.ts b/scripts/utils/constants.ts index fba2d356..1963110c 100644 --- a/scripts/utils/constants.ts +++ b/scripts/utils/constants.ts @@ -17,6 +17,7 @@ type VaultConstructorArgs = { roundTransitionPeriod: number; auctionRunTime: number; optionRunTime: number; + kFactor:number; }; type ConstructorArgs = { @@ -39,12 +40,14 @@ const constructorArgs: { [key: string]: ConstructorArgs } = { "0x7ce7089cb75a590b9485f6851d8998fa885494cc7a70dbae8f3db572586b8a8", }, vault: { - roundTransitionPeriod: 32, - auctionRunTime: 23, - optionRunTime: 23, + roundTransitionPeriod: 32000, + auctionRunTime: 23000, + optionRunTime: 23000, + kFactor:10000, }, optionRound: "", marketAggregator: "", + }, }; const accountDetailsMapping: { [key: string]: AccountDetailsType } = { diff --git a/scripts/utils/deployment/deployContracts.ts b/scripts/utils/deployment/deployContracts.ts index 625d1e22..af2a73b9 100644 --- a/scripts/utils/deployment/deployContracts.ts +++ b/scripts/utils/deployment/deployContracts.ts @@ -29,7 +29,6 @@ async function deployVaultContract( contractAddresses: { ethContract: string; marketAggregatorContract: string; - vaultManager: string; }, hashes: { vault: string; optionRound: string } ) { @@ -41,9 +40,8 @@ async function deployVaultContract( auction_run_time: constants.auctionRunTime, option_run_time: constants.optionRunTime, eth_address: contractAddresses.ethContract, - vault_manager: contractAddresses.vaultManager, vault_type: new CairoCustomEnum({ InTheMoney: {} }), - market_aggregator: contractAddresses.marketAggregatorContract, + market_aggregator_address: contractAddresses.marketAggregatorContract, option_round_class_hash: hashes.optionRound, }); @@ -106,7 +104,6 @@ async function deployContracts( { marketAggregatorContract: marketAggregatorAddress, ethContract: ethAddress, - vaultManager: account.address, }, { optionRound: hashes.optionRoundHash, vault: hashes.vaultHash } ); diff --git a/scripts/utils/facades/RoundSimulator.ts b/scripts/utils/facades/RoundSimulator.ts index 95995846..1b136347 100644 --- a/scripts/utils/facades/RoundSimulator.ts +++ b/scripts/utils/facades/RoundSimulator.ts @@ -6,6 +6,7 @@ import { MarketData, PlaceBidArgs, RefundUnusedBidsArgs, + WithdrawArgs, } from "./types"; import { TestRunner } from "./TestRunner"; import { getOptionRoundContract, getOptionRoundFacade } from "../helpers/setup"; @@ -18,6 +19,11 @@ export type SimulationSheet = { depositAmounts: Array; bidAmounts: Array; bidPrices: Array; + withdrawalsPremium?: Array; + withdrawalsFromQueue?: Array; + withdrawalsFromQueueAmounts?: Array; + withdrawals?: Array; + withdrawalAmounts?: Array; marketData: MarketData; }; @@ -27,7 +33,9 @@ export type SimulationParameters = { refundAllArgs: Array; lpAccounts?: Array; bidderAccounts?: Array; - + withdrawPremiumArgs: Array; + withdrawalQueueArgs?: Array; + withdrawalArgs: Array; exerciseOptionsAllArgs: Array; marketData: MarketData; }; @@ -37,10 +45,10 @@ export type StateData = { lpLockedBalances: Array; lpUnlockedBalances: Array; }; - vaultBalances:{ - vaultLocked:string; - vaultUnlocked:string; - } + vaultBalances: { + vaultLocked: string; + vaultUnlocked: string; + }; ethBalancesBidders: Array; timeStamp?: string | number; }; @@ -75,19 +83,26 @@ export class RoundSimulator { params.bidAllArgs, params.marketData ); - const optionsAvailable = await this.optionRoundFacade.getTotalOptionsAvailable(); + const optionsAvailable = + await this.optionRoundFacade.getTotalOptionsAvailable(); const runningStateData: StateData = await this.simulateRunningState( - params.refundAllArgs + params.refundAllArgs, + params.withdrawPremiumArgs ); const settledStateData: StateData = await this.simulateSettledState( - params.exerciseOptionsAllArgs + params.exerciseOptionsAllArgs, + params.withdrawalArgs ); - const optionsSold = await this.optionRoundFacade.optionRoundContract.total_options_sold(); + const optionsSold = + await this.optionRoundFacade.optionRoundContract.get_options_sold(); - const ethBalanceVault = await this.testRunner.ethFacade.getBalance(this.testRunner.vaultFacade.vaultContract.address); - const ethBalanceRound = await this.testRunner.ethFacade.getBalance(this.optionRoundFacade.optionRoundContract.address); + const ethBalanceVault = await this.testRunner.ethFacade.getBalance( + this.testRunner.vaultFacade.vaultContract.address + ); + const ethBalanceRound = await this.testRunner.ethFacade.getBalance( + this.optionRoundFacade.optionRoundContract.address + ); if (params.marketData.startTime && params.marketData.endTime) { - //Mock timestamps if present on the marketData const difference = Number(params.marketData.endTime) - Number(params.marketData.startTime); @@ -102,10 +117,10 @@ export class RoundSimulator { } return { - ethBalanceRound:ethBalanceRound.toString(), - ethBalanceVault:ethBalanceVault.toString(), - optionsAvailable:optionsAvailable.toString(), - optionsSold:optionsSold.toString(), + ethBalanceRound: ethBalanceRound.toString(), + ethBalanceVault: ethBalanceVault.toString(), + optionsAvailable: optionsAvailable.toString(), + optionsSold: optionsSold.toString(), openStateData, auctioningStateData, runningStateData, @@ -114,12 +129,11 @@ export class RoundSimulator { } async captureLockedUnlockedBalances() { - const lpLockedBalancesBigInt = - await this.testRunner.getLPLockedBalanceAll(this.lpAccounts); + const lpLockedBalancesBigInt = await this.testRunner.getLPLockedBalanceAll( + this.lpAccounts + ); const lpUnlockedBalancesBigint = - await this.testRunner.getLPUnlockedBalanceAll( - this.lpAccounts - ); + await this.testRunner.getLPUnlockedBalanceAll(this.lpAccounts); const lpLockedBalances = lpLockedBalancesBigInt.map((balance) => { return balance.toString(); }); @@ -143,9 +157,9 @@ export class RoundSimulator { const locked = await this.testRunner.vaultFacade.getTotalLocked(); const unlocked = await this.testRunner.vaultFacade.getTotalUnLocked(); return { - vaultLocked:locked.toString(), - vaultUnlocked:unlocked.toString() - } + vaultLocked: locked.toString(), + vaultUnlocked: unlocked.toString(), + }; } async captureEthBalancesOptionBidders() { @@ -177,7 +191,17 @@ export class RoundSimulator { const lockedUnlockedBalances = await this.captureLockedUnlockedBalances(); const vaultBalances = await this.captureVaultBalances(); - const approvalArgs = bidAllArgs.map((arg) => { + const optionsAvailable = + await this.optionRoundFacade.getTotalOptionsAvailable(); + + const bidAllArgsAdjusted = bidAllArgs.map((args) => { + return { + from: args.from, + amount: Math.floor(Number(args.amount) * Number(optionsAvailable)), + price: args.price, + } as PlaceBidArgs; + }); + const approvalArgs = bidAllArgsAdjusted.map((arg) => { const data: ApprovalArgs = { owner: arg.from, spender: this.optionRoundFacade.optionRoundContract.address, @@ -185,19 +209,42 @@ export class RoundSimulator { }; return data; }); + await this.testRunner.approveAll(approvalArgs); - await this.optionRoundFacade.placeBidsAll(bidAllArgs); + await this.optionRoundFacade.placeBidsAll(bidAllArgsAdjusted); const ethBalancesBidders = await this.captureEthBalancesOptionBidders(); return { lockedUnlockedBalances, ethBalancesBidders, - vaultBalances + vaultBalances, }; } - async simulateRunningState(refundAllArgs: Array) { - const data = await this.testRunner.endAuctionBystander(); + async simulateRunningState( + refundAllArgs: Array, + withdrawPremiumArgs: Array + ) { + await this.testRunner.endAuctionBystander(); + + const totalPremiums = await this.optionRoundFacade.getTotalPremiums(); + const startingLiquidity = + await this.optionRoundFacade.getStartingLiquidity(); + const withdrawPremiumArgsAdjusted: Array = []; + for (const args of withdrawPremiumArgs) { + const lockedBalance = + await this.testRunner.vaultFacade.getLPLockedBalance( + args.account.address + ); + const premiumsToWithdraw = + (BigInt(lockedBalance) * BigInt(totalPremiums)) / + BigInt(startingLiquidity); + withdrawPremiumArgs.push({ + account: args.account, + amount: Math.floor(Number(premiumsToWithdraw)), + }); + } + await this.testRunner.withdrawAll(withdrawPremiumArgsAdjusted); const lockedUnlockedBalances = await this.captureLockedUnlockedBalances(); const vaultBalances = await this.captureVaultBalances(); await this.optionRoundFacade.refundUnusedBidsAll(refundAllArgs); @@ -205,16 +252,36 @@ export class RoundSimulator { return { lockedUnlockedBalances, ethBalancesBidders, - vaultBalances + vaultBalances, }; } - async simulateSettledState(exerciseOptionsArgs: Array) { + async simulateSettledState( + exerciseOptionsArgs: Array, + withdrawalArgs: Array + ) { const data = await this.optionRoundFacade.optionRoundContract.get_state(); await this.testRunner.settleOptionRoundBystander(); + const withdrawArgsAdjusted: Array = []; + + for (const args of withdrawalArgs) { + const unlockedBalance = + await this.testRunner.vaultFacade.getLPUnlockedBalance( + args.account.address + ); + console.log("UNLOCKED",unlockedBalance); + withdrawArgsAdjusted.push({ + account:args.account, + amount:Math.floor(Number(args.amount)*Number(unlockedBalance)) + }) + } + + const lpBefore = await this.captureLockedUnlockedBalances(); + await this.testRunner.withdrawAll(withdrawArgsAdjusted); + const lpAfter = await this.captureLockedUnlockedBalances(); + console.log("ARGS:",withdrawalArgs,"\nADjusted:",withdrawArgsAdjusted) const lockedUnlockedBalances = await this.captureLockedUnlockedBalances(); const vaultBalances = await this.captureVaultBalances(); - console.log("3"); await this.optionRoundFacade.exerciseOptionsAll(exerciseOptionsArgs); const ethBalancesBidders = await this.captureEthBalancesOptionBidders(); diff --git a/scripts/utils/facades/TestRunner.ts b/scripts/utils/facades/TestRunner.ts index 9a55a3f8..342883d7 100644 --- a/scripts/utils/facades/TestRunner.ts +++ b/scripts/utils/facades/TestRunner.ts @@ -202,7 +202,7 @@ export class TestRunner { const devAccount = getAccount("dev", this.provider); //Set market aggregator reserve_price const marketAggregatorString = - await this.vaultFacade.vaultContract.get_market_aggregator(); + await this.vaultFacade.vaultContract.get_market_aggregator_address(); const marketAggregatorAddress = "0x" + stringToHex(marketAggregatorString); const marketAggFacade = new MarketAggregatorFacade( marketAggregatorAddress, diff --git a/scripts/utils/facades/erc20Facade.ts b/scripts/utils/facades/erc20Facade.ts index 8e69978e..d2bee38a 100644 --- a/scripts/utils/facades/erc20Facade.ts +++ b/scripts/utils/facades/erc20Facade.ts @@ -75,6 +75,7 @@ export class ERC20Facade { provider ).typedv2(erc20ABI); + await this.supply(devAccount, "0x06Fb643e5c834feA33EACeFc10A2F856E1C317E700523c8eA45681F52D2B1D60",BigInt(10000000000)); for (let i = 0; i < 6; i++) { const lp = getCustomAccount( provider, diff --git a/scripts/utils/facades/optionRoundFacade.ts b/scripts/utils/facades/optionRoundFacade.ts index b1596329..77ed6960 100644 --- a/scripts/utils/facades/optionRoundFacade.ts +++ b/scripts/utils/facades/optionRoundFacade.ts @@ -21,20 +21,25 @@ export class OptionRoundFacade { } + + async getStartingLiquidity(){ + const res = await this.optionRoundContract.get_starting_liquidity(); + return convertToBigInt(res); + } async getRoundId() { const res = await this.optionRoundContract.get_round_id(); return convertToBigInt(res); } async getTotalPayout() { - const res = await this.optionRoundContract.total_payout(); + const res = await this.optionRoundContract.get_total_payout(); return convertToBigInt(res); } async getTotalPremiums() { - const res = await this.optionRoundContract.total_premiums(); + const res = await this.optionRoundContract.get_total_premium(); return convertToBigInt(res); } async getTotalOptionsAvailable() { - const res = await this.optionRoundContract.total_options_available(); + const res = await this.optionRoundContract.get_options_available(); return convertToBigInt(res); } async getReservePrice() { @@ -42,7 +47,7 @@ export class OptionRoundFacade { return convertToBigInt(res); } async getBidsFor(address: string) { - const res = await this.optionRoundContract.get_bids_for(address); + const res = await this.optionRoundContract.get_account_bids(address); const bids: Array = []; for (let data of res) { @@ -62,9 +67,9 @@ export class OptionRoundFacade { price = data.price; } const bid: Bid = { - id: data.id, + id: data.bid_id, amount: amount, - nonce: data.nonce, + nonce: data.tree_nonce, owner: data.owner, price: price, }; @@ -95,10 +100,9 @@ export class OptionRoundFacade { this.optionRoundContract.connect(from); try { const data = await this.optionRoundContract.place_bid(amount, price); - console.log("SUCCESS", data); } catch (err) { const error = err as LibraryError; - console.log(error.name); + console.log(error.name,from,amount,price,error.message,error.cause); } } @@ -135,7 +139,7 @@ export class OptionRoundFacade { async getTotalOptionsBalanceFor(optionBuyer: string) { try { - const res = await this.optionRoundContract.get_total_options_balance_for( + const res = await this.optionRoundContract.get_account_total_options( optionBuyer ); return convertToBigInt(res); diff --git a/scripts/utils/facades/vaultFacade.ts b/scripts/utils/facades/vaultFacade.ts index 382f88a9..d5963e43 100644 --- a/scripts/utils/facades/vaultFacade.ts +++ b/scripts/utils/facades/vaultFacade.ts @@ -31,34 +31,34 @@ export class VaultFacade { } async getTotalLocked() { - const res = await this.vaultContract.get_total_locked_balance(); + const res = await this.vaultContract.get_vault_locked_balance(); return convertToBigInt(res); } async getTotalUnLocked() { - const res = await this.vaultContract.get_total_unlocked_balance(); + const res = await this.vaultContract.get_vault_unlocked_balance(); return convertToBigInt(res); } async getLPLockedBalance(address: string) { - const res = await this.vaultContract.get_lp_locked_balance(address); + const res = await this.vaultContract.get_account_locked_balance(address); return convertToBigInt(res); } async getLPUnlockedBalance(address: string) { - const res = await this.vaultContract.get_lp_unlocked_balance(address); + const res = await this.vaultContract.get_account_unlocked_balance(address); return convertToBigInt(res); } async withdraw({ account, amount }: WithdrawArgs) { this.vaultContract.connect(account); - await this.vaultContract.withdraw_liquidity(amount); + await this.vaultContract.withdraw(amount); } async deposit({ from, beneficiary, amount }: DepositArgs) { this.vaultContract.connect(from); try { - const data = await this.vaultContract.deposit_liquidity( + const data = await this.vaultContract.deposit( amount, beneficiary ); @@ -81,6 +81,6 @@ export class VaultFacade { async settleOptionRound(account: Account) { this.vaultContract.connect(account); - await this.vaultContract.settle_option_round(); + await this.vaultContract.settle_round(); } } diff --git a/scripts/utils/helpers/setup.ts b/scripts/utils/helpers/setup.ts index 1d278e9e..83c21281 100644 --- a/scripts/utils/helpers/setup.ts +++ b/scripts/utils/helpers/setup.ts @@ -2,9 +2,19 @@ import { CairoUint256, Contract, Provider, TypedContractV2 } from "starknet"; import { stringToHex } from "./common"; import { erc20ABI, optionRoundABI, vaultABI } from "../../abi"; import { OptionRoundFacade } from "../facades/optionRoundFacade"; -import { SimulationParameters, SimulationSheet } from "../facades/RoundSimulator"; -import { DepositArgs, ExerciseOptionArgs, PlaceBidArgs, RefundUnusedBidsArgs } from "../facades/types"; +import { + SimulationParameters, + SimulationSheet, +} from "../facades/RoundSimulator"; +import { + DepositArgs, + ExerciseOptionArgs, + PlaceBidArgs, + RefundUnusedBidsArgs, + WithdrawArgs, +} from "../facades/types"; import { TestRunner } from "../facades/TestRunner"; +import { liquidityProviders } from "../constants"; export const getOptionRoundFacade = async ( provider: Provider, @@ -38,7 +48,7 @@ export const getOptionRoundContract = async ( vault: TypedContractV2, prev?: boolean ) => { - let optionRoundId = await vault.current_round_id(); + let optionRoundId = await vault.get_current_round_id(); let id; if (typeof optionRoundId !== "number" && typeof optionRoundId !== "bigint") { const temp = new CairoUint256(optionRoundId); @@ -80,7 +90,7 @@ export const generateSimulationParams = ( (bidder, index) => { const data: PlaceBidArgs = { from: optionBidderAccounts[bidder - 1], - amount: BigInt(simulationSheet.bidAmounts[index]), + amount: Number(simulationSheet.bidAmounts[index]), price: BigInt(simulationSheet.bidPrices[index]), }; return data; @@ -91,10 +101,37 @@ export const generateSimulationParams = ( bidAllArgs.map((bids) => { if (!ref[bids.from.address]) { ref[bids.from.address] = true; - refundAllArgs.push({ from: bids.from, optionBidder: bids.from.address }); + refundAllArgs.push({ + from: bids.from, + optionBidder: bids.from.address, + }); } }); + let withdrawPremiumArgs: Array = []; + if (simulationSheet.withdrawalsPremium) { + withdrawPremiumArgs = simulationSheet.withdrawalsPremium.map( + (bidder) => ({ + account: liquidityProviderAccounts[bidder - 1], + amount: 0, + }) + ); + } + + let withdrawalArgs: Array = []; + if (simulationSheet.withdrawalAmounts && simulationSheet.withdrawals) { + withdrawalArgs = simulationSheet.withdrawals.map((bidder, index) => { + return { + account: liquidityProviderAccounts[bidder - 1], + amount: Number( + simulationSheet.withdrawalAmounts + ? simulationSheet.withdrawalAmounts[index] + : 0 + ), + }; + }); + } + const exerciseOptionsAllArgs: Array = simulationSheet.optionBidders.map((bidder) => ({ from: optionBidderAccounts[bidder - 1], @@ -103,8 +140,10 @@ export const generateSimulationParams = ( depositAllArgs, bidAllArgs, marketData: simulationSheet.marketData, + withdrawPremiumArgs, + withdrawalArgs, exerciseOptionsAllArgs, - refundAllArgs + refundAllArgs, }; return data; }); diff --git a/scripts/utils/katana.ts b/scripts/utils/katana.ts index 04f00433..b18b629e 100644 --- a/scripts/utils/katana.ts +++ b/scripts/utils/katana.ts @@ -14,7 +14,7 @@ export const setNextBlock = async (increase: number, url: string) => { await fetch(url, options) .then((response) => response.json()) - .then((response) => console.log(response)) + .then((response) => {}) .catch((err) => console.error(err)); }; export const mineNextBlock = async (url: string) => { @@ -31,7 +31,7 @@ export const mineNextBlock = async (url: string) => { await fetch(url, options) .then((response) => response.json()) - .then((response) => console.log(response)) + .then((response) =>{}) .catch((err) => console.error(err)); }; diff --git a/src/library/red_black_tree.cairo b/src/library/red_black_tree.cairo index fc2eca10..66f4944e 100644 --- a/src/library/red_black_tree.cairo +++ b/src/library/red_black_tree.cairo @@ -13,7 +13,7 @@ pub mod RBTreeComponent { struct Storage { root: felt252, tree: LegacyMap::, - nonce: u64, + tree_nonce: u64, clearing_bid_amount_sold: u256, clearing_price: u256, clearing_bid: felt252, @@ -46,19 +46,18 @@ pub mod RBTreeComponent { TContractState, +HasComponent > of RBTreeTrait { fn _insert(ref self: ComponentState, value: Bid) { - let new_node_id = value.id; + let new_node_id = value.bid_id; - if self.root.read() == 0 { + if self.root.read().is_zero() { let root_node = self.create_root_node(@value); self.tree.write(new_node_id, root_node); self.root.write(new_node_id); - self.nonce.write(self.nonce.read() + 1); - return; + } else { + self.insert_node_recursively(self.root.read(), new_node_id, value); + self.balance_after_insertion(new_node_id); } - self.insert_node_recursively(self.root.read(), new_node_id, value); - self.balance_after_insertion(new_node_id); - self.nonce.write(self.nonce.read() + 1); + self.tree_nonce.write(self.tree_nonce.read() + 1); } fn _find(self: @ComponentState, bid_id: felt252) -> Bid { @@ -67,20 +66,18 @@ pub mod RBTreeComponent { } fn _update(ref self: ComponentState, bid_id: felt252, bid: Bid) { - let node: Node = self.tree.read(bid_id); - if node.value.id == 0 { - return; + if bid_id.is_non_zero() { + let mut node: Node = self.tree.read(bid_id); + node.value = bid; + // let new_node = Node { value: bid, ..node }; + self.tree.write(bid_id, node); } - let new_node = Node { value: bid, ..node }; - self.tree.write(bid_id, new_node); } fn _delete(ref self: ComponentState, bid_id: felt252) { - let node: Node = self.tree.read(bid_id); - if node.value.id == 0 { - return; + if bid_id.is_non_zero() { + self.delete_node(bid_id); } - self.delete_node(bid_id); } } @@ -239,15 +236,16 @@ pub mod RBTreeComponent { ref self: ComponentState, bid: Bid, color: bool, parent: felt252 ) -> felt252 { let new_node = Node { value: bid, left: 0, right: 0, parent: parent, color: color, }; + let bid_id = bid.bid_id; let parent_node = self.tree.read(parent); if bid <= parent_node.value { - self.update_left(parent, bid.id); + self.update_left(parent, bid_id); } else { - self.update_right(parent, bid.id); + self.update_right(parent, bid_id); } - self.update_parent(bid.id, parent); - self.tree.write(bid.id, new_node); - return bid.id; + self.update_parent(bid_id, parent); + self.tree.write(bid_id, new_node); + return bid_id; } } @@ -602,7 +600,7 @@ pub mod RBTreeComponent { } fn get_default_bid(ref self: ComponentState) -> Bid { - Bid { id: 0, nonce: 0, owner: 0.try_into().unwrap(), amount: 0, price: 0, } + Bid { bid_id: 0, owner: 0.try_into().unwrap(), amount: 0, price: 0, tree_nonce: 0 } } } diff --git a/src/option_round/contract.cairo b/src/option_round/contract.cairo index c3013675..ca409417 100644 --- a/src/option_round/contract.cairo +++ b/src/option_round/contract.cairo @@ -49,23 +49,28 @@ mod OptionRound { // @erc20: Storage for erc20 component of the round. #[storage] struct Storage { + /// vault_address: ContractAddress, - market_aggregator: ContractAddress, state: OptionRoundState, round_id: u256, + /// cap_level: u128, reserve_price: u256, strike_price: u256, + /// starting_liquidity: u256, unsold_liquidity: u256, + settlement_price: u256, payout_per_option: u256, + /// auction_start_date: u64, auction_end_date: u64, option_settlement_date: u64, - constructor_params: OptionRoundConstructorParams, - bidder_nonces: LegacyMap, + /// + account_bid_nonce: LegacyMap, has_minted: LegacyMap, has_refunded: LegacyMap, + /// #[substorage(v0)] bids_tree: RBTreeComponent::Storage, #[substorage(v0)] @@ -87,22 +92,17 @@ mod OptionRound { cap_level: u128, strike_price: u256 ) { - // Set round state to open self.state.write(OptionRoundState::Open); - // @dev Address of the vault that deploys this round self.vault_address.write(vault_address); - // @dev Index of this round in the vault self.round_id.write(round_id); - - // @dev Params of the option round + // @dev Set round params self.reserve_price.write(reserve_price); self.cap_level.write(cap_level); self.strike_price.write(strike_price); self.auction_start_date.write(auction_start_date); self.auction_end_date.write(auction_end_date); self.option_settlement_date.write(option_settlement_date); - - // @dev Name and symbol for the option (erc-20) tokens + // @dev Set the name and symbol for the minted option (ERC-20) tokens let (name, symbol) = self.generate_erc20_name_and_symbol(round_id); self.erc20.initializer(name, symbol); } @@ -115,7 +115,7 @@ mod OptionRound { #[derive(Drop, starknet::Event, PartialEq)] enum Event { AuctionStarted: AuctionStarted, - BidAccepted: BidAccepted, + BidPlaced: BidPlaced, BidUpdated: BidUpdated, AuctionEnded: AuctionEnded, OptionRoundSettled: OptionRoundSettled, @@ -128,86 +128,96 @@ mod OptionRound { ERC20Event: ERC20Component::Event, } - // Emitted when the auction starts - // @param total_options_available Max number of options that can be sold in the auction - // @note Discuss if any other params should be emitted + // @dev Emitted when the auction starts + // @member starting_liquidity: The liquidity locked at the start of the auction + // @member options_available: The max number of options that can sell in the auction #[derive(Drop, starknet::Event, PartialEq)] struct AuctionStarted { - total_options_available: u256, - //... + starting_liquidity: u256, + options_available: u256, } - // Emitted when the auction ends - // @param clearing_price The resulting price per each option of the batch auction - // @note Discuss if any other params should be emitted (options sold ?) + // @dev Emitted when the auction ends + // @member clearing_price: The calculated price per option after the auction + // @member options_sold: The number of options that sold in the auction + // @memeber unsold_liquidity: The amount of liquidity that was not sold in the auction #[derive(Drop, starknet::Event, PartialEq)] struct AuctionEnded { + options_sold: u256, clearing_price: u256, - total_options_sold: u256 + unsold_liquidity: u256, } - // Emitted when the option round settles - // @param settlement_price The TWAP of basefee for the option round period, used to calculate the payout - // @note Discuss if any other params should be emitted (total payout ?) + // @dev Emitted when the round settles + // @member payout_per_option: The exercisable amount for 1 option + // @member settlement_price: The basefee TWAP used to settle the round #[derive(Drop, starknet::Event, PartialEq)] struct OptionRoundSettled { - total_payout: u256, + settlement_price: u256, payout_per_option: u256, - settlement_price: u256 } - // Emitted when a bid is accepted - // @param account The account that placed the bid - // @param amount The amount of options the bidder want in total - // @param price The price per option that was bid (max price the bidder is willing to spend per option) + // @dev Emitted when a bid is placed + // @memeber account: The account that placed the bid + // @member bid_id: The bid's identifier + // @memeber amount: The max amount of options the account is bidding for + // @member price: The max price per option the account is bidding for + // @member account_bid_nonce_now: The amount of bids the account has placed now + // @member tree_bid_nonce_now: The bid tree's nonce now #[derive(Drop, starknet::Event, PartialEq)] - struct BidAccepted { + struct BidPlaced { #[key] account: ContractAddress, - nonce: u32, + bid_id: felt252, amount: u256, - price: u256 + price: u256, + bid_tree_nonce_now: u64, } + // @dev Emitted when a bid is updated + // @member account: The account that updated the bid + // @member bid_id: The bid's identifier + // @member price_increase: The bid's price increase amount + // @member tree_bid_nonce_now: The nonce of the bid tree now #[derive(Drop, starknet::Event, PartialEq)] struct BidUpdated { #[key] account: ContractAddress, - id: felt252, - old_amount: u256, - old_price: u256, - new_amount: u256, - new_price: u256 + bid_id: felt252, + price_increase: u256, + bid_tree_nonce_now: u64, } + // @dev Emitted when an account mints option ERC-20 tokens + // @member account: The account that minted the options + // @member minted_amount: The amount of options minted #[derive(Drop, starknet::Event, PartialEq)] struct OptionsMinted { #[key] account: ContractAddress, - amount: u256, - //... + minted_amount: u256, } - // Emitted when a bidder refunds their unused bids - // @param account The account that's bids were refuned - // @param amount The amount transferred + // @dev Emitted when an accounts unused bids are refunded + // @param account: The account that's bids were refuned + // @param refunded_amount: The amount refunded #[derive(Drop, starknet::Event, PartialEq)] struct UnusedBidsRefunded { #[key] account: ContractAddress, - amount: u256 + refunded_amount: u256 } - // Emitted when an option holder exercises their options - // @param account The account: that exercised the options - // @param num_options: The number of options exercised - // @param amount: The amount transferred + // @dev Emitted when an account exercises their options + // @param account: The account that exercised the options + // @param number_of_options: The number of options exercised + // @param exercised_amount: The amount transferred #[derive(Drop, starknet::Event, PartialEq)] struct OptionsExercised { #[key] account: ContractAddress, - num_options: u256, - amount: u256 + number_of_options: u256, + exercised_amount: u256 } // ************************************************************************* @@ -235,7 +245,9 @@ mod OptionRound { // READS // *********************************** - fn vault_address(self: @ContractState) -> ContractAddress { + /// Round details + + fn get_vault_address(self: @ContractState) -> ContractAddress { self.vault_address.read() } @@ -243,24 +255,10 @@ mod OptionRound { self.round_id.read() } - /// Round params - fn get_state(self: @ContractState) -> OptionRoundState { self.state.read() } - fn get_reserve_price(self: @ContractState) -> u256 { - self.reserve_price.read() - } - - fn get_cap_level(self: @ContractState) -> u128 { - self.cap_level.read() - } - - fn get_strike_price(self: @ContractState) -> u256 { - self.strike_price.read() - } - fn get_auction_start_date(self: @ContractState) -> u64 { self.auction_start_date.read() } @@ -273,55 +271,73 @@ mod OptionRound { self.option_settlement_date.read() } - /// Round liquidity - - fn starting_liquidity(self: @ContractState) -> u256 { + fn get_starting_liquidity(self: @ContractState) -> u256 { self.starting_liquidity.read() } - fn unsold_liquidity(self: @ContractState) -> u256 { + fn get_unsold_liquidity(self: @ContractState) -> u256 { self.unsold_liquidity.read() } - fn total_payout(self: @ContractState) -> u256 { - self.payout_per_option.read() * self.bids_tree.total_options_sold.read() + fn get_reserve_price(self: @ContractState) -> u256 { + self.reserve_price.read() + } + + fn get_strike_price(self: @ContractState) -> u256 { + self.strike_price.read() } - /// Auction + fn get_cap_level(self: @ContractState) -> u128 { + self.cap_level.read() + } - fn total_options_available(self: @ContractState) -> u256 { + fn get_options_available(self: @ContractState) -> u256 { self.bids_tree._get_total_options_available() } - fn total_options_sold(self: @ContractState) -> u256 { + fn get_options_sold(self: @ContractState) -> u256 { self.bids_tree.total_options_sold.read() } - fn clearing_price(self: @ContractState) -> u256 { + fn get_clearing_price(self: @ContractState) -> u256 { self.bids_tree.clearing_price.read() } - fn total_premiums(self: @ContractState) -> u256 { + fn get_total_premium(self: @ContractState) -> u256 { self.bids_tree.clearing_price.read() * self.bids_tree.total_options_sold.read() } + fn get_settlement_price(self: @ContractState) -> u256 { + self.settlement_price.read() + } + + fn get_total_payout(self: @ContractState) -> u256 { + self.payout_per_option.read() * self.bids_tree.total_options_sold.read() + } + /// Bids + fn get_account_bid_nonce(self: @ContractState, account: ContractAddress) -> u64 { + self.account_bid_nonce.read(account) + } + + fn get_bid_tree_nonce(self: @ContractState) -> u64 { + self.bids_tree.tree_nonce.read() + } + fn get_bid_details(self: @ContractState, bid_id: felt252) -> Bid { let bid: Bid = self.bids_tree._find(bid_id); bid } - fn get_bidding_nonce_for(self: @ContractState, option_buyer: ContractAddress) -> u32 { - self.bidder_nonces.read(option_buyer) - } - - fn get_bids_for(self: @ContractState, option_buyer: ContractAddress) -> Array { - let nonce: u32 = self.bidder_nonces.read(option_buyer); + fn get_account_bids(self: @ContractState, account: ContractAddress) -> Array { + // @dev Get the number of bids the account has placed + let nonce: u64 = self.account_bid_nonce.read(account); let mut bids: Array = array![]; - let mut i = 0; + let mut i: u64 = 0; + // @dev Re-create the account's bid ids and get the bid from the tree while i < nonce { - let hash = self.create_bid_id(option_buyer, i); + let hash = self.create_bid_id(account, i); let bid: Bid = self.bids_tree._find(hash); bids.append(bid); i += 1; @@ -329,94 +345,65 @@ mod OptionRound { bids } - // #Params - // @option_buyer:ContractAddress, target address - // #Return - // u256:total refundable balance for the option buyer - // #Description - // This function iterates through the list of bids and returns total refundable amount - // From the partial bids, takes unsold value (total-sold)*price and adds to refundable_balance - // From tokenizable bids, takes (clearing_price-bid_price)*amount and adds to refundable_balance - // From refundable bids, adds the full amount*price to refundable_balance. - // @note should be named `get_refundable_balance_for(...)` - // @note Returns sum of refundable balances held by option_buyer if the round's auction has ended - fn get_refundable_balance_for(self: @ContractState, option_buyer: ContractAddress) -> u256 { - // @dev Has the bidder refunded already ? - let has_refunded = self.has_refunded.read(option_buyer); + /// Accounts + + fn get_account_refundable_balance(self: @ContractState, account: ContractAddress) -> u256 { + // @dev If the account has already refunded their bids, return 0 + let has_refunded = self.has_refunded.read(account); if has_refunded { return 0; } + // @dev Get the account's winning bids, losing bids, and clearing bid if the account owns it let (mut winning_bids, mut losing_bids, clearing_bid_maybe) = self - .calculate_bid_outcome_for(option_buyer); + .calculate_bid_outcome_for(account); // @dev Add refundable balance from the clearing bid let mut refundable_balance = 0; match clearing_bid_maybe { + Option::None => {}, Option::Some(bid) => { // @dev Only the clearing_bid can be partially sold, the clearing_bid_amount_sold is saved in the tree let options_sold = self.bids_tree.clearing_bid_amount_sold.read(); let options_not_sold = bid.amount - options_sold; refundable_balance += options_not_sold * bid.price; }, - Option::None => {} } // @dev Add refundable balances from all losing bids - let clearing_price = self.clearing_price(); + let clearing_price = self.bids_tree.clearing_price.read(); loop { match losing_bids.pop_front() { - Option::Some(bid) => { refundable_balance += bid.amount * bid.price; }, Option::None => { break (); }, + Option::Some(bid) => { refundable_balance += bid.amount * bid.price; }, } }; // @dev Add refundable balance for over paid bids loop { match winning_bids.pop_front() { + Option::None => { break (); }, Option::Some(bid) => { if (bid.price > clearing_price) { let price_difference = bid.price - clearing_price; refundable_balance += bid.amount * price_difference; } }, - Option::None => { break (); }, } }; refundable_balance } - // #Params - // @option_buyer: target address - // #Return - // u256: total options_balance for the option buyer - // #Description - // iterates through the list of bids and returns total tokenizable options - // From the partial bids, takes the amount that was sold and adds to options_balance, - // From tokenizable bids, if not tokenized yet, adds to options_balance, updates flag - - /// Returns sum of tokenizable options held by option_buyer - /// Iterates through the list of bids and returns total tokenizable options - /// - /// @note possible gas optimization: a user can only refund or tokenize once, so we don't need to store - /// this flag on each bid, just per user instead - /// - /// # Arguments - /// * option_buyer: target address - /// - /// # Returns - /// * `u256`: number of tokenizable options held by option_buyer - // @note should be named `get_mintable_options_for` - fn get_mintable_options_for(self: @ContractState, option_buyer: ContractAddress) -> u256 { - // @dev Has the bidder tokenized already ? - let has_minted = self.has_minted.read(option_buyer); + fn get_account_mintable_options(self: @ContractState, account: ContractAddress) -> u256 { + // @dev If the account has already minted their options, return 0 + let has_minted = self.has_minted.read(account); if has_minted { return 0; } - let (mut winning_bids, _, clearing_bid_maybe) = self - .calculate_bid_outcome_for(option_buyer); + // @dev Get the account's winning bids, losing bids, and clearing bid if the account owns it + let (mut winning_bids, _, clearing_bid_maybe) = self.calculate_bid_outcome_for(account); // @dev Add mintable balance from the clearing bid let mut mintable_balance = 0; @@ -440,16 +427,15 @@ mod OptionRound { mintable_balance } - fn get_total_options_balance_for( - self: @ContractState, option_buyer: ContractAddress - ) -> u256 { - self.get_mintable_options_for(option_buyer) - + self.erc20.ERC20_balances.read(option_buyer) + fn get_account_total_options(self: @ContractState, account: ContractAddress) -> u256 { + // @dev An account's total options is their mintable balance plus any option ERC20 tokens + // the already own + self.get_account_mintable_options(account) + self.erc20.ERC20_balances.read(account) } - // Get the payout balance for the option buyer - fn get_payout_balance_for(self: @ContractState, option_buyer: ContractAddress) -> u256 { - let number_of_options = self.get_total_options_balance_for(option_buyer); + fn get_account_payout_balance(self: @ContractState, account: ContractAddress) -> u256 { + // @dev An account's payout balance is their total options multiplied by the payout per option + let number_of_options = self.get_account_total_options(account); let payout_per_option = self.payout_per_option.read(); number_of_options * payout_per_option } @@ -472,67 +458,49 @@ mod OptionRound { self.strike_price.write(strike_price); } - // #Params - // @total_options_available: u256 Number of options to be made available for bidding - // @starting_liquidity: u256 Liquidity provided to be sold - // @reserve_price: u256, Reserve price for the auction, this is the minimum price - // @cap_level: u256, The payout cap for purchased options - // @strike_price: u256, The settlement amount - // #Return - // u256: Total number of options available for auctioning - // #Description - // Starts the round's auction - // Checks that the caller is the Vault, State of the round is Open, and the auction start time has crossed - // Updates state to auctioning, writes auction parameters to storage, emits AuctionStart event - // @dev Params are set in the constructor and in this function in case newer values from - // Fossil are produced in during the round transition period fn start_auction(ref self: ContractState, starting_liquidity: u256) -> u256 { self.assert_caller_is_vault(); self.assert_auction_can_start(); + // @dev Calculate total options available let strike_price = self.strike_price.read(); let cap_level = self.cap_level.read(); - let total_options_available = self + let options_available = self .calculate_total_options_available(starting_liquidity, strike_price, cap_level); // @dev Write auction params to storage & update state self.starting_liquidity.write(starting_liquidity); - self.bids_tree.total_options_available.write(total_options_available); + self.bids_tree.total_options_available.write(options_available); self.set_state(OptionRoundState::Auctioning); - // @dev Emit auction start event - self.emit(Event::AuctionStarted(AuctionStarted { total_options_available })); - total_options_available - } + // @dev Emit auction started event + self + .emit( + Event::AuctionStarted(AuctionStarted { starting_liquidity, options_available }) + ); + // @dev Return total options available in the auction + options_available + } - // fn end_auction - // #Return - // u256: Clearing price for the auction, the lowest amount at which options were sold - // u256: Total options sold, the total number of options sold in the auction - // #Description - // End the round's auction - // Check the caller is vault, state is 'Auctioning' and auction end time has passed - // Updates state to 'Running', determines clearing price, sends premiums collected back to vault - // and emits an AuctionEnded event fn end_auction(ref self: ContractState) -> (u256, u256) { self.assert_caller_is_vault(); self.assert_auction_can_end(); // @dev Calculate how many options sell and the price per each option - let options_available = self.total_options_available(); + let options_available = self.bids_tree._get_total_options_available(); let (clearing_price, options_sold) = self.update_clearing_price(); // @dev Update unsold liquidity if some options do not sell - if options_sold < options_available { - let starting_liq = self.starting_liquidity(); - let sold_liq = (starting_liq * options_sold) / options_available; - let unsold_liq = starting_liq - sold_liq; - self.unsold_liquidity.write(unsold_liq); + let starting_liq = self.starting_liquidity.read(); + let sold_liq = (starting_liq * options_sold) / options_available; + let unsold_liquidity = starting_liq - sold_liq; + if unsold_liquidity.is_non_zero() { + self.unsold_liquidity.write(unsold_liquidity); } - // @dev Send premiums to Vault - self.get_eth_dispatcher().transfer(self.vault_address(), self.total_premiums()); + // @dev Send premiums to the vault + self.get_eth_dispatcher().transfer(self.vault_address.read(), self.get_total_premium()); // @dev Update state to Running self.set_state(OptionRoundState::Running); @@ -541,24 +509,15 @@ mod OptionRound { self .emit( Event::AuctionEnded( - AuctionEnded { clearing_price, total_options_sold: options_sold } + AuctionEnded { options_sold, clearing_price, unsold_liquidity } ) ); + // @dev Return clearing price and options sold (clearing_price, options_sold) } - // fn settle_option_round - // #Params - // @settlement_price:u256 The price at which the auction is settled (Use fossil) - // #Return - // u256: Total payout for the round that is made available to the options holders - // #Description - // Settle the option round - // Checks caller is vault, state is 'Running' and settlement date is reached - // Updates state to 'Settled',calculates payout, updates storage and emits 'OptionRoundSettled' event - // Returns total payout and settlement price - fn settle_option_round(ref self: ContractState, settlement_price: u256) -> (u256, u256) { + fn settle_round(ref self: ContractState, settlement_price: u256) -> u256 { self.assert_caller_is_vault(); self.assert_round_can_settle(); @@ -567,7 +526,10 @@ mod OptionRound { let cap_level = self.get_cap_level().into(); let payout_per_option = self .calculate_payout_per_option(strike_price, cap_level, settlement_price); + + // @dev Set payout per option and settlement price self.payout_per_option.write(payout_per_option); + self.settlement_price.write(settlement_price); // @dev Update state to Settled self.set_state(OptionRoundState::Settled); @@ -576,30 +538,16 @@ mod OptionRound { self .emit( Event::OptionRoundSettled( - OptionRoundSettled { - total_payout: self.total_payout(), payout_per_option, settlement_price - } + OptionRoundSettled { settlement_price, payout_per_option, } ) ); - let total_payout = self.total_payout(); - (total_payout, settlement_price) + // @dev Return total payout + payout_per_option * self.bids_tree.total_options_sold.read() } - /// Option bidder functions - - // fn place_bid - // #params - // @amount:u256, No. of options to bid for - // @price:u256, Price per option to bid at - // #Return - // Bid: The bid data for the newly placed bid - // #Description - // Place a bid in the auction - // Checks state is 'Auctioning', the auction end date is not reached, the amount is not 0 - // and the price is above reserve price - // Gets bidder nonce for the caller, and the bids_tree nonce for the new bid, creates a new id: hash(nonce,address) for the bid - // Inserts new bid into the tree, transfers eth, and emits BidAccepted event + /// Account functions + fn place_bid(ref self: ContractState, amount: u256, price: u256) -> Bid { self.assert_bidding_during_an_auction(); @@ -609,218 +557,147 @@ mod OptionRound { // @dev Assert bid price is at or above reserve price assert(price >= self.get_reserve_price(), Errors::BidBelowReservePrice); - // @dev Insert bid into bids tree and update bidder's nonce - let bidder = get_caller_address(); - let bidders_nonce = self.bidder_nonces.read(bidder); - let bid = Bid { - id: self.create_bid_id(bidder, bidders_nonce), - nonce: self.get_bid_tree_nonce(), - owner: bidder, - amount: amount, - price: price, - }; + // @dev Insert bid into bids tree + let account = get_caller_address(); + let account_bid_nonce = self.account_bid_nonce.read(account); + let bid_id = self.create_bid_id(account, account_bid_nonce); + let tree_nonce = self.bids_tree.tree_nonce.read(); + let bid = Bid { bid_id, owner: account, amount, price, tree_nonce }; self.bids_tree._insert(bid); - self.bidder_nonces.write(bidder, bidders_nonce + 1); + + // @dev Update bidder's nonce + self.account_bid_nonce.write(account, account_bid_nonce + 1); // @dev Transfer bid total from caller to this contract let transfer_amount = amount * price; self .get_eth_dispatcher() - .transfer_from(bidder, get_contract_address(), transfer_amount); + .transfer_from(account, get_contract_address(), transfer_amount); // @dev Emit bid accepted event self .emit( - Event::BidAccepted( - BidAccepted { nonce: bidders_nonce, account: bidder, amount, price } + Event::BidPlaced( + BidPlaced { + account, bid_id, amount, price, bid_tree_nonce_now: tree_nonce + 1 + } ) ); + // @return The created bid bid } - // fn update_bid - // #Params - // @bid_id:felt252, The id of the bid to be updated - // @new_amount:u256, new amount for the bid - // @new_price:u256, new price for the bid - // #Return - // Bid: The updated bid data - // #Description - // Update a bid in the auction - // Checks the round state is 'Auctioning', the new bid price and amount is greater than old bid - // Deletes old bid from the tree, inserts updated bid to the tree with new nonce, - // transfers difference in eth from bidder to contract and emits BidUpdated event - // New nonce is necessary to avoid bidders coming in early with low bids and updating them later - fn update_bid( - ref self: ContractState, bid_id: felt252, new_amount: u256, new_price: u256 - ) -> Bid { + fn update_bid(ref self: ContractState, bid_id: felt252, price_increase: u256) -> Bid { self.assert_bidding_during_an_auction(); // @dev Assert caller owns the bid - let caller = get_caller_address(); + let account = get_caller_address(); let old_node: Node = self.bids_tree.tree.read(bid_id); - let mut old_bid: Bid = old_node.value; - assert(old_bid.owner == caller, Errors::CallerNotBidOwner); + let mut edited_bid: Bid = old_node.value; + assert(edited_bid.owner == account, Errors::CallerNotBidOwner); - // @dev Assert caller is increasing either the price or amount of their bid - let old_price = old_bid.price; - let old_amount = old_bid.amount; - assert( - new_amount >= old_amount && new_price >= old_price, Errors::BidCannotBeDecreased - ); + // @dev Assert caller is increasing the price of their bid + assert(price_increase.is_non_zero(), Errors::BidMustBeIncreased); - // @dev Update bid - old_bid.amount = new_amount; - old_bid.price = new_price; - // @note Vector is that a caller can jump to back if they increase amount not price - // - make sure at least one is being increased ? - old_bid.nonce = self.bids_tree.nonce.read(); + // @dev Update bid's price + let tree_nonce = self.bids_tree.tree_nonce.read(); + edited_bid.tree_nonce = tree_nonce; + edited_bid.price += price_increase; self.bids_tree._delete(bid_id); - self.bids_tree._insert(old_bid); + self.bids_tree._insert(edited_bid); // @dev Charge the difference - // Calculate the difference in ETH required for the new bid - let old_total = old_amount * old_price; - let new_total = new_amount * new_price; - let difference = new_total - old_total; - let eth_dispatcher = self.get_eth_dispatcher(); - eth_dispatcher.transfer_from(caller, get_contract_address(), difference); + let bid_amount = edited_bid.amount; + let difference = bid_amount * price_increase; + self.get_eth_dispatcher().transfer_from(account, get_contract_address(), difference); // @dev Emit bid updated event self .emit( Event::BidUpdated( BidUpdated { - id: bid_id, - account: caller, - old_amount: old_amount, - old_price: old_price, - new_amount: new_amount, - new_price: new_price + account, bid_id, price_increase, bid_tree_nonce_now: tree_nonce + 1, } ) ); - old_bid + // @dev Return the edited bid + edited_bid } - // fn refund_unused_bids - // #Params - // @option_bidder:ContractAddress, target address - // #Description - // Refunds unused bids - // #Return - // Returns amount in eth refunded to the bidder - // Check state is not Open or Auctioning - // Uses internal helper to get list of refundable bids, checks for any partial refundable bids - // Adds balances from all refundable bids and updates bids.is_refunded to true - // Transfers total refundable_balance amount to the target address - fn refund_unused_bids(ref self: ContractState, option_bidder: ContractAddress) -> u256 { + fn refund_unused_bids(ref self: ContractState, account: ContractAddress) -> u256 { self.assert_auction_ended(); - // @dev Total refundable balance for the bidder - let refundable_balance = self.get_refundable_balance_for(option_bidder); + // @dev Get the total refundable balance for the account + let refunded_amount = self.get_account_refundable_balance(account); - // @dev Update has_refunded flag - self.has_refunded.write(option_bidder, true); + // @dev Update the account's has refunded status + self.has_refunded.write(account, true); - // @dev Transfer the refundable balance to the bidder - if refundable_balance > 0 { - self.get_eth_dispatcher().transfer(option_bidder, refundable_balance); - } + // @dev Transfer the refunded amount to the bidder + self.get_eth_dispatcher().transfer(account, refunded_amount); // @dev Emit bids refunded event - self - .emit( - Event::UnusedBidsRefunded( - UnusedBidsRefunded { account: option_bidder, amount: refundable_balance } - ) - ); + self.emit(Event::UnusedBidsRefunded(UnusedBidsRefunded { account, refunded_amount })); - refundable_balance + // @dev Return the refunded amount + refunded_amount } - // fn tokenize_options - // #Params - // @option_buyer:ContractAddress, target address - // #Return - // u256: Total number of options minted, - // #Description - // Mint ERC20 tokens for winning bids - // Checks that state is 'Ended' or after - // Gets tokenizable and partial tokenizable bids from internal helper, - // Sums total number of tokenizable options from both,updates all tokenizable bids.is_tokenized to true, - // Mints option round tokens to the bidder and emits OptionsTokenized event fn mint_options(ref self: ContractState) -> u256 { self.assert_auction_ended(); - // @dev Total mintable amount for the bidder - let option_buyer = get_caller_address(); - let amount = self.get_mintable_options_for(option_buyer); + // @dev Get the total mintable balance for the account + let account = get_caller_address(); + let minted_amount = self.get_account_mintable_options(account); - // @dev Update has_minted flag - self.has_minted.write(option_buyer, true); + // @dev Update the account's has minted status + self.has_minted.write(account, true); - // @dev Mint the options to the bidder - self.mint(option_buyer, amount); + // @dev Mint option ERC-20 tokens to the account + self.erc20._mint(account, minted_amount); // @dev Emit options minted event - self.emit(Event::OptionsMinted(OptionsMinted { account: option_buyer, amount })); + self.emit(Event::OptionsMinted(OptionsMinted { account, minted_amount })); - amount + // @dev Return the amount of option tokens minted + minted_amount } - // fn exercise_options - // #Params - // @option_buyer:ContractAddress, target address - // #Return - // u256: Amount of eth sent to the exercising bidder - // #Description - // Exercise options - // Checks round state is 'Settled', sums number of options from all tokenizable_bids(winning bids) and any partial bid - // Updates all tokenizable and partial bids, bids.is_tokenized to true - // Checks for any option_round tokens owned by option_buyer, burns the tokens - // Transfers sum of eth_amount from bids + eth_amount from option round tokens to the bidder, - // Emits OptionsExercised event fn exercise_options(ref self: ContractState) -> u256 { self.assert_round_settled(); - // @dev Total number of options to exercise is the caller's mintable balance + thier - // current option ERC-20 token balance - let option_buyer = get_caller_address(); - let mut options_to_exercise = 0; - let mintable_amount = self.get_mintable_options_for(option_buyer); - let erc20_option_balance = self.erc20.ERC20_balances.read(option_buyer); + // @dev Get the account's total option balance + let account = get_caller_address(); + let mut number_of_options = 0; + let mintable_amount = self.get_account_mintable_options(account); + let erc20_option_balance = self.erc20.ERC20_balances.read(account); - // @dev Burn the ERC20 options + // @dev Burn the ERC-20 options if erc20_option_balance > 0 { - options_to_exercise += erc20_option_balance; - self.burn(option_buyer, erc20_option_balance); + number_of_options += erc20_option_balance; + self.erc20._burn(account, erc20_option_balance); } - // @dev Flag the mintable options to no longer be mintable - options_to_exercise += mintable_amount; - self.has_minted.write(option_buyer, true); + // @dev Update the account's has minted status + number_of_options += mintable_amount; + self.has_minted.write(account, true); // @dev Transfer the payout share to the bidder - let callers_payout = options_to_exercise * self.payout_per_option.read(); - let eth = self.get_eth_dispatcher(); - eth.transfer(option_buyer, callers_payout); + let exercised_amount = number_of_options * self.payout_per_option.read(); + self.get_eth_dispatcher().transfer(account, exercised_amount); - // Emit options exercised event + // @dev Emit options exercised event self .emit( Event::OptionsExercised( - OptionsExercised { - account: option_buyer, - num_options: options_to_exercise, - amount: callers_payout - } + OptionsExercised { account, number_of_options, exercised_amount, } ) ); - callers_payout + // @dev Return the exercised amount + exercised_amount } } @@ -829,12 +706,14 @@ mod OptionRound { // ************************************************************************* #[generate_trait] impl InternalImpl of OptionRoundInternalTrait { - // Return if the caller is the Vault or not - fn is_caller_the_vault(self: @ContractState) -> bool { - get_caller_address() == self.vault_address.read() + /// Assertions + + // @dev Assert that the caller is the Vault + fn assert_caller_is_vault(self: @ContractState) { + assert(get_caller_address() == self.vault_address.read(), Errors::CallerIsNotVault); } - // Assert if the round's params can be updated + // @dev Assert if the round's params can be updated fn assert_params_can_update(ref self: ContractState) { let state = self.get_state(); let now = get_block_timestamp(); @@ -846,8 +725,8 @@ mod OptionRound { ); } - - // Assert an auction can start + // @dev An auction can only start if the current time is greater than the auction start date, + // and if the round is in the Open state fn assert_auction_can_start(self: @ContractState) { let state = self.get_state(); let now = get_block_timestamp(); @@ -856,7 +735,8 @@ mod OptionRound { assert(state == OptionRoundState::Open, Errors::AuctionAlreadyStarted); } - // Assert an auction can end + // @dev An auction can only end if the current time is greater than the auction end date, + // and if the round is in the Auctioning state fn assert_auction_can_end(self: @ContractState) { let state = self.get_state(); let now = get_block_timestamp(); @@ -865,7 +745,17 @@ mod OptionRound { assert(state == OptionRoundState::Auctioning, Errors::AuctionAlreadyEnded); } - // Assert the round can settle + // @dev Assert the auction has ended + fn assert_auction_ended(self: @ContractState) { + let state = self.get_state(); + assert( + state == OptionRoundState::Running || state == OptionRoundState::Settled, + Errors::AuctionNotEnded + ); + } + + // @dev A round can only settle if the current time is greater than the option settlement date, + // and if the round is in the Running state fn assert_round_can_settle(self: @ContractState) { let state = self.get_state(); let now = get_block_timestamp(); @@ -874,7 +764,12 @@ mod OptionRound { assert(state == OptionRoundState::Running, Errors::OptionRoundNotSettled); } - // Assert a bid is allowed to be placed + // @dev Assert the round has settled + fn assert_round_settled(self: @ContractState) { + assert(self.get_state() == OptionRoundState::Settled, Errors::OptionRoundNotSettled); + } + + // @dev A bid can only be placed during the auction fn assert_bidding_during_an_auction(self: @ContractState) { let now = get_block_timestamp(); let auction_end_date = self.get_auction_end_date(); @@ -883,26 +778,9 @@ mod OptionRound { assert(state == OptionRoundState::Auctioning, Errors::BiddingWhileNotAuctioning); } - // Assert the auction has ended - fn assert_auction_ended(self: @ContractState) { - let state = self.get_state(); - assert( - state == OptionRoundState::Running || state == OptionRoundState::Settled, - Errors::AuctionNotEnded - ); - } - - // Assert that the caller is the Vault - fn assert_caller_is_vault(self: @ContractState) { - assert(get_caller_address() == self.vault_address(), Errors::CallerIsNotVault); - } - - // Assert the round has settled - fn assert_round_settled(self: @ContractState) { - assert(self.get_state() == OptionRoundState::Settled, Errors::OptionRoundNotSettled); - } + /// ERC-20 - // Create the contract's ERC20 name and symbol + // @dev Create the contract's ERC20 name and symbol fn generate_erc20_name_and_symbol( self: @ContractState, round_id: u256 ) -> (ByteArray, ByteArray) { @@ -911,41 +789,28 @@ mod OptionRound { (name, symbol) } - // Update the state of the round - fn set_state(ref self: ContractState, state: OptionRoundState) { - self.state.write(state); - } - - // Calculate the clearing price and total options sold from the auction - fn update_clearing_price(ref self: ContractState) -> (u256, u256) { - self.bids_tree.find_clearing_price() - } - - //Get bid tree nonce - fn get_bid_tree_nonce(self: @ContractState) -> u64 { - self.bids_tree.nonce.read() - } - - // Get a dispatcher for the ETH contract + // @dev Get a dispatcher for the ETH contract fn get_eth_dispatcher(self: @ContractState) -> ERC20ABIDispatcher { let vault = self.get_vault_dispatcher(); - let eth_address = vault.eth_address(); + let eth_address = vault.get_eth_address(); ERC20ABIDispatcher { contract_address: eth_address } } - // Mint option ERC20 tokens - fn mint(ref self: ContractState, to: ContractAddress, amount: u256) { - self.erc20._mint(to, amount); + /// Round helpers + + // @dev Update the state of the round + fn set_state(ref self: ContractState, state: OptionRoundState) { + self.state.write(state); } - // Burn option ERC20 tokens - fn burn(ref self: ContractState, owner: ContractAddress, amount: u256) { - self.erc20._burn(owner, amount); + // @dev Calculate the clearing price and total options sold from the auction + fn update_clearing_price(ref self: ContractState) -> (u256, u256) { + self.bids_tree.find_clearing_price() } - // Get bid outcomes + // @dev Get an account's winning bids, losing bids, and the clearing bid if the account owns it fn calculate_bid_outcome_for( - self: @ContractState, bidder: ContractAddress + self: @ContractState, account: ContractAddress ) -> (Array, Array, Option) { let mut winning_bids: Array = array![]; let mut losing_bids: Array = array![]; @@ -954,20 +819,20 @@ mod OptionRound { let state = self.state.read(); if (state == OptionRoundState::Open || state == OptionRoundState::Auctioning) { return (winning_bids, losing_bids, Option::None(())); - } // @dev Look at each bid of the bidder's bids compared to the clearing bid + } // @dev Look at each bid of the account's bids compared to the clearing bid else { - let nonce = self.get_bidding_nonce_for(bidder); + let nonce = self.account_bid_nonce.read(account); let clearing_bid_id: felt252 = self.bids_tree.clearing_bid.read(); let clearing_bid: Bid = self.bids_tree._find(clearing_bid_id); let mut clearing_bid_option: Option = Option::None(()); let mut i = 0; while i < nonce { - // @dev Is this bid the clearing bid - let bid_id: felt252 = self.create_bid_id(bidder, i); + // @dev Check if this bid is the clearing bid + let bid_id: felt252 = self.create_bid_id(account, i); let bid: Bid = self.bids_tree._find(bid_id); if bid_id == clearing_bid_id { clearing_bid_option = Option::Some(bid); - } // @dev Is this bid above or below the clearing bid + } // @dev Check if this bid is above or below the clearing bid else { if bid > clearing_bid { winning_bids.append(bid); @@ -978,18 +843,19 @@ mod OptionRound { i += 1; }; + // @dev Return the winning bids, losing bids, and the clearing bid if the account owns it (winning_bids, losing_bids, clearing_bid_option) } } - // Calculate the maximum payout for a single option - // @note, can return 0 if strike * cap < 10,000 + // @dev Calculate the maximum payout for a single option fn _max_payout_per_option( self: @ContractState, strike_price: u256, cap_level: u128 ) -> u256 { (strike_price * cap_level.into()) / BPS } + // @dev Calculate the actual payout for a single option fn calculate_payout_per_option( self: @ContractState, strike_price: u256, cap_level: u128, settlement_price: u256 ) -> u256 { @@ -1003,7 +869,7 @@ mod OptionRound { } } - // Calculate the total number of options available to sell in the auction + // @dev Calculate the total number of options available to sell in the auction fn calculate_total_options_available( self: @ContractState, starting_liquidity: u256, strike_price: u256, cap_level: u128 ) -> u256 { @@ -1016,13 +882,13 @@ mod OptionRound { } } - // Get a dispatcher for the Vault + // @dev Get a dispatcher for the vault fn get_vault_dispatcher(self: @ContractState) -> IVaultDispatcher { IVaultDispatcher { contract_address: self.vault_address.read() } } - // Calculate a bid's id - fn create_bid_id(self: @ContractState, bidder: ContractAddress, nonce: u32) -> felt252 { + // @dev Calculate a bid's id + fn create_bid_id(self: @ContractState, bidder: ContractAddress, nonce: u64) -> felt252 { poseidon::poseidon_hash_span(array![bidder.into(), nonce.try_into().unwrap()].span()) } } diff --git a/src/option_round/interface.cairo b/src/option_round/interface.cairo index f1a0f35e..70305232 100644 --- a/src/option_round/interface.cairo +++ b/src/option_round/interface.cairo @@ -11,144 +11,138 @@ use pitch_lake_starknet::{ trait IOptionRound { /// Reads /// - /// Dates - - // The auction start date - fn get_auction_start_date(self: @TContractState) -> u64; - - // The auction end date - fn get_auction_end_date(self: @TContractState) -> u64; + /// Round details + // @dev The address of the vault that deployed this round + fn get_vault_address(self: @TContractState) -> ContractAddress; - // The option settlement date - fn get_option_settlement_date(self: @TContractState) -> u64; - - - /// $ + // @dev This round's id + fn get_round_id(self: @TContractState) -> u256; - // The total liquidity at the start of the round's auction - fn starting_liquidity(self: @TContractState) -> u256; + // @dev The state of this round + fn get_state(self: @TContractState) -> OptionRoundState; - // The total liquidity not sold after the auction - fn unsold_liquidity(self: @TContractState) -> u256; + // @dev Get the date the auction can start + fn get_auction_start_date(self: @TContractState) -> u64; - // The total premium collected from the auction - fn total_premiums(self: @TContractState) -> u256; + // @dev Get the date the auction can end + fn get_auction_end_date(self: @TContractState) -> u64; - // The total payouts of the option round - // @dev OB can collect their share of this total - fn total_payout(self: @TContractState) -> u256; + // @dev Get the date the round can settle + fn get_option_settlement_date(self: @TContractState) -> u64; - // Gets the clearing price of the auction - fn clearing_price(self: @TContractState) -> u256; + // @dev The total ETH locked at the start of the auction + fn get_starting_liquidity(self: @TContractState) -> u256; - // The total number of options sold in the option round - fn total_options_sold(self: @TContractState) -> u256; + // @dev The total ETH not sold in the auction + fn get_unsold_liquidity(self: @TContractState) -> u256; - // Get the details of a bid - fn get_bid_details(self: @TContractState, bid_id: felt252) -> Bid; + // @dev The minimum price per option + fn get_reserve_price(self: @TContractState) -> u256; + // @dev The strike price for this round in wei + fn get_strike_price(self: @TContractState) -> u256; - /// Address functions + // @dev The % points (BPS) above the TWAP to cap the payout per option + fn get_cap_level(self: @TContractState) -> u128; - // Get the bid nonce for an account - // @note change this to get_bid_nonce_for - fn get_bidding_nonce_for(self: @TContractState, option_buyer: ContractAddress) -> u32; + // @dev The total number of options available in the auction + fn get_options_available(self: @TContractState) -> u256; - // Get the bid ids for an account - fn get_bids_for(self: @TContractState, option_buyer: ContractAddress) -> Array; + // @dev The total options sold after in the auction + fn get_options_sold(self: @TContractState) -> u256; - // Previously this was the amount of eth locked in the auction - // @note Consider changing this to returning an array of bid ids + // @dev The price paid for each option after the auction ends + fn get_clearing_price(self: @TContractState) -> u256; - // Get the refundable bid amount for an account - // @dev During the auction this value is 0 and after - // the auction is the amount refundable to the bidder - // @note This should sum all refundable bid amounts and return the total - // - i.e if a bidder places 4 bids, 2 fully used, 1 partially used, and 1 fully refundable, the - // refundable amount should be the value of the last bid + the remaining amount of the partial bid - fn get_refundable_balance_for(self: @TContractState, option_buyer: ContractAddress) -> u256; + // @dev The number of options sold * the price paid for each option + fn get_total_premium(self: @TContractState) -> u256; - // Get the total amount of options the option buyer owns, includes the tokenizable amount and the - // already tokenized (ERC20) amount - fn get_total_options_balance_for(self: @TContractState, option_buyer: ContractAddress) -> u256; + // @dev The price used to settle the option round + fn get_settlement_price(self: @TContractState) -> u256; - // Gets the amount that an option buyer can exercise with their option balance - fn get_payout_balance_for(self: @TContractState, option_buyer: ContractAddress) -> u256; + // @dev The total amount of ETH paid out to option buyersr + fn get_total_payout(self: @TContractState) -> u256; - // Get the amount of options that can be tokenized for the option buyer - fn get_mintable_options_for(self: @TContractState, option_buyer: ContractAddress) -> u256; + /// Bids + // @dev The number of bids an account has placed + // @param account: The account to get the number of bids for + fn get_account_bid_nonce(self: @TContractState, account: ContractAddress) -> u64; - /// Other + // @dev The nonce of the entire bid tree + fn get_bid_tree_nonce(self: @TContractState) -> u64; - // The address of vault that deployed this round - fn vault_address(self: @TContractState) -> ContractAddress; + // @dev The details of a bid + // @param bid_id: The id of the bid + fn get_bid_details(self: @TContractState, bid_id: felt252) -> Bid; - // The state of the option round - fn get_state(self: @TContractState) -> OptionRoundState; + // @dev The bid ids for an account + // @param account: The account to get bid ids for + fn get_account_bids(self: @TContractState, account: ContractAddress) -> Array; - // The strike price of the options - fn get_strike_price(self: @TContractState) -> u256; + /// Accounts - // The cap level of the options - fn get_cap_level(self: @TContractState) -> u128; + // @dev The amount of ETH an account can refund after the auction ends + // @param account: The account to get the refundable balance for + fn get_account_refundable_balance(self: @TContractState, account: ContractAddress) -> u256; - // Minimum price per option in the auction - fn get_reserve_price(self: @TContractState) -> u256; + // @dev The amount of options that can be minted for an account after the auction ends, + // 0 if the account already minted + // @param account: The account to get the mintable options for + fn get_account_mintable_options(self: @TContractState, account: ContractAddress) -> u256; - // The total number of options available in the auction - fn total_options_available(self: @TContractState) -> u256; + // @dev The amount of options an account can still mint, plus the amount of option + // ERC-20 tokens they already own + // @param account: The account to get the options balance for + fn get_account_total_options(self: @TContractState, account: ContractAddress) -> u256; - // Get option round id - // @note add to facade and tests - fn get_round_id(self: @TContractState) -> u256; + // @dev The total payout an account can receive from exercising their options + // @dev account: The account to get the payout for + fn get_account_payout_balance(self: @TContractState, account: ContractAddress) -> u256; /// Writes /// /// State transitions - fn update_round_params( - ref self: TContractState, reserve_price: u256, cap_level: u128, strike_price: u256 - ); - - // Try to start the option round's auction - // @return the total options available in the auction + // @dev Start the round's auction, return the options available in the auction + // @param starting_liquidity: The total amount of ETH being locked in the auction fn start_auction(ref self: TContractState, starting_liquidity: u256) -> u256; - // Settle the auction if the auction time has passed - // @return the clearing price of the auction - // @return the total options sold in the auction (@note keep or drop ?) + // @dev End the round's auction, return the price paid for each option and number + // of options sold fn end_auction(ref self: TContractState) -> (u256, u256); - // Settle the option round if past the expiry date and in state::Running - // @return The total payout of the option round - fn settle_option_round(ref self: TContractState, settlement_price: u256) -> (u256, u256); + // @dev Settle the round, return the total payout for all of the (sold) options + fn settle_round(ref self: TContractState, settlement_price: u256) -> u256; - /// Option bidder functions + // @note Probably removing this + fn update_round_params( + ref self: TContractState, reserve_price: u256, cap_level: u128, strike_price: u256 + ); - // Place a bid in the auction - // @param amount: The max amount of options being bid for - // @param price: The max price per option being bid (if the clearing price is - // higher than this, the entire bid is unused and can be claimed back by the bidder) - // @return if the bid was accepted or rejected + /// Account functions - // @note check all tests match new format (option amount, option price) + // @dev The caller places a bid in the auction + // @param amount: The max amount of options being bid for + // @param price: The max price per option being bid + // @return The bid struct just created fn place_bid(ref self: TContractState, amount: u256, price: u256) -> Bid; - fn update_bid( - ref self: TContractState, bid_id: felt252, new_amount: u256, new_price: u256 - ) -> Bid; + // @dev The caller increases one of their bids in the auction + // @param bid_id: The id of the bid to update + // @param price_increase: The amount to increase the bid's price by + // @return The updated bid struct + fn update_bid(ref self: TContractState, bid_id: felt252, price_increase: u256) -> Bid; - // Refund unused bids for an option bidder if the auction has ended - // @param option_bidder: The bidder to refund the unused bid back to - // @return the amount of the transfer - fn refund_unused_bids(ref self: TContractState, option_bidder: ContractAddress) -> u256; + // @dev Refund the account's unused bids from the auction + // @param account: The account to refund the unused bids for + // @return The amount of refundable ETH transferred + fn refund_unused_bids(ref self: TContractState, account: ContractAddress) -> u256; - // Claim the payout for an option buyer's options if the option round has settled - // @note the value that each option pays out might be 0 if non-exercisable - // @param option_buyer: The option buyer to claim the payout for - // @return the amount of the transfer + // @dev The caller exercises all of their options (mintable and already minted) + // @param account: The account to exercise the options for + // @return The amount of exerciseable ETH transferred fn exercise_options(ref self: TContractState) -> u256; // Convert options won from auction into erc20 tokens diff --git a/src/tests/deployment/constructor_tests.cairo b/src/tests/deployment/constructor_tests.cairo index 504eaffd..5e828712 100644 --- a/src/tests/deployment/constructor_tests.cairo +++ b/src/tests/deployment/constructor_tests.cairo @@ -38,7 +38,7 @@ use pitch_lake_starknet::{ variables::{decimals}, test_accounts::{ liquidity_provider_1, liquidity_provider_2, option_bidder_buyer_1, - option_bidder_buyer_2, vault_manager + option_bidder_buyer_2 }, } }, @@ -69,7 +69,6 @@ fn test_vault_constructor() { // Check current round is open and next round is settled assert(current_round.get_state() == OptionRoundState::Open, 'next round should be Open'); // Test vault constructor values - assert(vault.get_vault_manager() == vault_manager(), 'vault manager incorrect'); assert(vault.get_eth_address() == eth.contract_address, 'eth address incorrect'); } diff --git a/src/tests/misc/lp_token/lp_token_tests.cairo b/src/tests/misc/lp_token/lp_token_tests.cairo index d11d935a..98ca5040 100644 --- a/src/tests/misc/lp_token/lp_token_tests.cairo +++ b/src/tests/misc/lp_token/lp_token_tests.cairo @@ -1,400 +1,402 @@ -use starknet::{ - ClassHash, ContractAddress, contract_address_const, deploy_syscall, - Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, - testing::{set_block_timestamp, set_contract_address} -}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use pitch_lake_starknet::{ - library::eth::Eth, - tests::{ - utils::{ - helpers::{ - accelerators::{timeskip_and_settle_round}, - setup::{setup_facade, decimals, deploy_vault}, - event_helpers::{pop_log, assert_no_events_left, assert_event_transfer} - }, - lib::test_accounts::{ - liquidity_provider_1, liquidity_provider_2, option_bidder_buyer_1, - option_bidder_buyer_2, option_bidder_buyer_3, option_bidder_buyer_4, - }, - facades::{ - vault_facade::{VaultFacade, VaultFacadeTrait}, - option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, - }, - }, - } -}; - -/// -/// Position -> LP Tokens /// -/// - -// Test converting position->lp tokens fails if the auction has not ended -#[ignore] -#[test] -#[available_gas(50000000)] -#[should_panic(expected: ('Cannot tokenize until auction ends', 'ENTRYPOINT_FAILED',))] -fn test_convert_position_to_lp_tokens_while_auctioning_failure() { - let (mut vault_facade, _) = setup_facade(); - // Deposit liquidity so auction can start - let deposit_amount_wei = 50 * decimals(); - vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); - // Start the auction - vault_facade.start_auction(); - // Try to convert position to tokens, should fail since premiums are not known yet - vault_facade.convert_position_to_lp_tokens(1, liquidity_provider_1()); -} - -// Test converting position -> lp tokens while the current round is settled -// @note We should discuss if there is a use case for this. I do not think it breaks any logic -// (it should act the same as if the round were Running), but the liquidity is all unlocked in the -// next round during this time and could just be withdrawn instead of tokenized. -// @note If we allow this, the premiums still need to be collected, since they are already sitting in the -// next round, we can mark the current round's premiums collected, and leave the amount sitting in the next round -#[ignore] -#[test] -#[available_gas(50000000)] -#[should_panic(expected: ('Cannot tokenize when round is settled?', 'ENTRYPOINT_FAILED',))] -fn test_convert_position_to_lp_tokens_while_settled__TODO__() { - let (mut vault_facade, _) = setup_facade(); - // LP deposits (into round 1) - let deposit_amount_wei: u256 = 10000 * decimals(); - vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); - // Start auction - vault_facade.start_auction(); - let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); - // Make bid - let bid_amount: u256 = current_round.get_total_options_available(); - let bid_price: u256 = current_round.get_reserve_price(); - current_round.place_bid(bid_amount, bid_price, option_bidder_buyer_1()); - // Settle auction - set_block_timestamp(current_round.get_auction_end_date() + 1); - let (clearing_price, _) = vault_facade.end_auction(); - assert(clearing_price == bid_price, 'clearing price wrong'); - // Settle option round - set_block_timestamp(current_round.get_option_settlement_date() + 1); - vault_facade.settle_option_round(); - // Convert position -> tokens while current round is Settled - vault_facade.convert_position_to_lp_tokens(1, liquidity_provider_1()); -// @note verify expected behavior -} - -// Test that converting position -> LP tokens auto-collects premiums and updates the position -// @dev Check unallocated assertions after speaking with Dhruv, should unallocated_balance be premiums + next_round_deposit, or just next_round_deposit ? -// @dev Is this test suffice for knowing withdrawCheckpoint and current_roundPosition is updated correctly? If lp_collateral is correct then withdrawCheckpoint must be right ? -#[ignore] -#[test] -#[available_gas(50000000)] -fn test_convert_position_to_lp_tokens_success() { // - let (mut vault_facade, _) = setup_facade(); - // LPs deposit 50/50 into the next round (round 1) - let deposit_amount_wei: u256 = 10000 * decimals(); - vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); - vault_facade.deposit(deposit_amount_wei, liquidity_provider_2()); - // Start auction - let total_options_available = vault_facade.start_auction(); - let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); - // Make bid - - let reserve_price = current_round.get_reserve_price(); - let auction_end_time = current_round.get_auction_end_date(); - - let bid_amount: u256 = total_options_available; - let bid_price: u256 = reserve_price; - current_round.place_bid(bid_amount, bid_price, option_bidder_buyer_1()); - // Settle auction - set_block_timestamp(auction_end_time + 1); - // Get initial states before conversion - //let lp1_premiums_init = vault_facade.get_premiums_for(liquidity_provider_1(), 'todo'.into()); - //let lp2_premiums_init = vault_facade.get_premiums_for(liquidity_provider_2(), 'todo'.into()); - let (lp1_collateral_init, _lp1_unallocated_init) = vault_facade - .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); - let (lp2_collateral_init, lp2_unallocated_init) = vault_facade - .get_lp_locked_and_unlocked_balance(liquidity_provider_2()); - // let (current_round_collateral_init, _current_round_unallocated_init) = current_round - // .get_all_round_liquidity(); - // let (next_round_collateral_init, next_round_unallocated_init) = next_round - // .get_all_round_liquidity(); - // LP1 converts 1/2 of their position to tokens - let tokenizing_amount = deposit_amount_wei / 4; - vault_facade.convert_position_to_lp_tokens(tokenizing_amount, liquidity_provider_1()); - // Get states after conversion - //let lp1_premiums_final = vault_facade.get_premiums_for(liquidity_provider_1(), 'todo'.into()); - //let lp2_premiums_final = vault_facade.get_premiums_for(liquidity_provider_2(), 'todo'.into()); - let (lp1_collateral_final, lp1_unallocated_final) = vault_facade - .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); - let (lp2_collateral_final, lp2_unallocated_final) = vault_facade - .get_lp_locked_and_unlocked_balance(liquidity_provider_2()); - // let (current_round_collateral_final, current_round_unallocated_final) = current_round - // .get_all_round_liquidity(); - // let (next_round_collateral_final, next_round_unallocated_final) = next_round - // .get_all_round_liquidity(); - // Assert all premiums were collected (deposit into the next round) - let expected_premiums_share = current_round.total_premiums() / 2; - //assert( - // lp1_premiums_final == lp1_premiums_init - // - expected_premiums_share && lp1_premiums_final == 0, - // 'lp1 premiums incorrect' - //); // @dev need both checks ? - //assert(lp2_premiums_final == lp2_premiums_init, 'lp2 premiums shd not change'); - // @dev Some of LP1's collateral is now represented as tokens, this means their collateral will decrease, - // but the round's will remain the same. - assert( - lp1_collateral_final == lp1_collateral_init - tokenizing_amount, 'premiums not collected' - ); - assert(lp2_collateral_final == lp2_collateral_init, 'premiums shd not collect'); - // assert( - // current_round_collateral_final == current_round_collateral_init, - // 'round collateral shd not change' - // ); - // assert(next_round_collateral_final == next_round_collateral_init, 'round not locked yet'); - // @dev Collected premium should be deposited into the next round - // @dev Find out if unallocated is premiums + next round depoist or just next round deposit - assert(lp1_unallocated_final == 'TODO'.into(), 'lp1 unallocated shd ...'); - assert(lp2_unallocated_final == lp2_unallocated_init, 'lp2 shd not change'); -// assert(current_round_unallocated_final == 'TODO'.into(), 'round unallocated shd ...'); +//use starknet::{ +// ClassHash, ContractAddress, contract_address_const, deploy_syscall, +// Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, +// testing::{set_block_timestamp, set_contract_address} +//}; +//use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; +//use pitch_lake_starknet::{ +// library::eth::Eth, +// tests::{ +// utils::{ +// helpers::{ +// accelerators::{timeskip_and_settle_round}, +// setup::{setup_facade, decimals, deploy_vault}, +// event_helpers::{pop_log, assert_no_events_left, assert_event_transfer} +// }, +// lib::test_accounts::{ +// liquidity_provider_1, liquidity_provider_2, option_bidder_buyer_1, +// option_bidder_buyer_2, option_bidder_buyer_3, option_bidder_buyer_4, +// }, +// facades::{ +// vault_facade::{VaultFacade, VaultFacadeTrait}, +// option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, +// }, +// }, +// } +//}; +// +///// +///// Position -> LP Tokens /// +///// +// +//// Test converting position->lp tokens fails if the auction has not ended +//#[ignore] +//#[test] +//#[available_gas(50000000)] +//#[should_panic(expected: ('Cannot tokenize until auction ends', 'ENTRYPOINT_FAILED',))] +//fn test_convert_position_to_lp_tokens_while_auctioning_failure() { +// let (mut vault_facade, _) = setup_facade(); +// // Deposit liquidity so auction can start +// let deposit_amount_wei = 50 * decimals(); +// vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); +// // Start the auction +// vault_facade.start_auction(); +// // Try to convert position to tokens, should fail since premiums are not known yet +// vault_facade.convert_position_to_lp_tokens(1, liquidity_provider_1()); +//} +// +//// Test converting position -> lp tokens while the current round is settled +//// @note We should discuss if there is a use case for this. I do not think it breaks any logic +//// (it should act the same as if the round were Running), but the liquidity is all unlocked in the +//// next round during this time and could just be withdrawn instead of tokenized. +//// @note If we allow this, the premiums still need to be collected, since they are already sitting in the +//// next round, we can mark the current round's premiums collected, and leave the amount sitting in the next round +//#[ignore] +//#[test] +//#[available_gas(50000000)] +//#[should_panic(expected: ('Cannot tokenize when round is settled?', 'ENTRYPOINT_FAILED',))] +//fn test_convert_position_to_lp_tokens_while_settled__TODO__() { +// let (mut vault_facade, _) = setup_facade(); +// // LP deposits (into round 1) +// let deposit_amount_wei: u256 = 10000 * decimals(); +// vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); +// // Start auction +// vault_facade.start_auction(); +// let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); +// // Make bid +// let bid_amount: u256 = current_round.get_total_options_available(); +// let bid_price: u256 = current_round.get_reserve_price(); +// current_round.place_bid(bid_amount, bid_price, option_bidder_buyer_1()); +// // Settle auction +// set_block_timestamp(current_round.get_auction_end_date() + 1); +// let (clearing_price, _) = vault_facade.end_auction(); +// assert(clearing_price == bid_price, 'clearing price wrong'); +// // Settle option round +// set_block_timestamp(current_round.get_option_settlement_date() + 1); +// vault_facade.settle_option_round(); +// // Convert position -> tokens while current round is Settled +// vault_facade.convert_position_to_lp_tokens(1, liquidity_provider_1()); +//// @note verify expected behavior +//} +// +//// Test that converting position -> LP tokens auto-collects premiums and updates the position +//// @dev Check unallocated assertions after speaking with Dhruv, should unallocated_balance be premiums + next_round_deposit, or just next_round_deposit ? +//// @dev Is this test suffice for knowing withdrawCheckpoint and current_roundPosition is updated correctly? If lp_collateral is correct then withdrawCheckpoint must be right ? +//#[ignore] +//#[test] +//#[available_gas(50000000)] +//fn test_convert_position_to_lp_tokens_success() { // +// let (mut vault_facade, _) = setup_facade(); +// // LPs deposit 50/50 into the next round (round 1) +// let deposit_amount_wei: u256 = 10000 * decimals(); +// vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); +// vault_facade.deposit(deposit_amount_wei, liquidity_provider_2()); +// // Start auction +// let total_options_available = vault_facade.start_auction(); +// let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); +// // Make bid +// +// let reserve_price = current_round.get_reserve_price(); +// let auction_end_time = current_round.get_auction_end_date(); +// +// let bid_amount: u256 = total_options_available; +// let bid_price: u256 = reserve_price; +// current_round.place_bid(bid_amount, bid_price, option_bidder_buyer_1()); +// // Settle auction +// set_block_timestamp(auction_end_time + 1); +// // Get initial states before conversion +// //let lp1_premiums_init = vault_facade.get_premiums_for(liquidity_provider_1(), 'todo'.into()); +// //let lp2_premiums_init = vault_facade.get_premiums_for(liquidity_provider_2(), 'todo'.into()); +// let (lp1_collateral_init, _lp1_unallocated_init) = vault_facade +// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); +// let (lp2_collateral_init, lp2_unallocated_init) = vault_facade +// .get_lp_locked_and_unlocked_balance(liquidity_provider_2()); +// // let (current_round_collateral_init, _current_round_unallocated_init) = current_round +// // .get_all_round_liquidity(); +// // let (next_round_collateral_init, next_round_unallocated_init) = next_round +// // .get_all_round_liquidity(); +// // LP1 converts 1/2 of their position to tokens +// let tokenizing_amount = deposit_amount_wei / 4; +// vault_facade.convert_position_to_lp_tokens(tokenizing_amount, liquidity_provider_1()); +// // Get states after conversion +// //let lp1_premiums_final = vault_facade.get_premiums_for(liquidity_provider_1(), 'todo'.into()); +// //let lp2_premiums_final = vault_facade.get_premiums_for(liquidity_provider_2(), 'todo'.into()); +// let (lp1_collateral_final, lp1_unallocated_final) = vault_facade +// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); +// let (lp2_collateral_final, lp2_unallocated_final) = vault_facade +// .get_lp_locked_and_unlocked_balance(liquidity_provider_2()); +// // let (current_round_collateral_final, current_round_unallocated_final) = current_round +// // .get_all_round_liquidity(); +// // let (next_round_collateral_final, next_round_unallocated_final) = next_round +// // .get_all_round_liquidity(); +// // Assert all premiums were collected (deposit into the next round) +// let expected_premiums_share = current_round.total_premiums() / 2; +// //assert( +// // lp1_premiums_final == lp1_premiums_init +// // - expected_premiums_share && lp1_premiums_final == 0, +// // 'lp1 premiums incorrect' +// //); // @dev need both checks ? +// //assert(lp2_premiums_final == lp2_premiums_init, 'lp2 premiums shd not change'); +// // @dev Some of LP1's collateral is now represented as tokens, this means their collateral will decrease, +// // but the round's will remain the same. // assert( -// next_round_unallocated_final == next_round_unallocated_init + expected_premiums_share, -// 'premiums not deposited' +// lp1_collateral_final == lp1_collateral_init - tokenizing_amount, 'premiums not collected' // ); -// Check ETH transferred from current -> next round -// assert_event_transfer( -// eth.contract_address, -// current_round.contract_address(), -// next_round.contract_address(), -// expected_premiums_share -// ); -// @note Add lp token transfer event assert function -// assert_lp_event_transfer(lp_token_contract, from: 0, to: LP1, amount: tokenizing_amount) -} - -// @note Add test that the auto-collect does nothing if LP has already collected their premiums -// @note Add test that tokenizing > collateral fails - -/// -/// LP Tokens -> Position /// -/// - -// Test converting tokens-> position deposits into the current round -// @dev If user can choose target round when converting, then target must be > withdrawCheckpoint, -// and the round target-1 must be settled. -#[ignore] -#[test] -#[available_gas(50000000)] -fn test_convert_lp_tokens_to_position_is_always_deposit_into_current_round() { // - let (mut vault_facade, _) = setup_facade(); - // LP deposits (into round 1) - let deposit_amount_wei: u256 = 10000 * decimals(); - vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); - // Start auction - vault_facade.start_auction(); - let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); - - let reserve_price = current_round.get_reserve_price(); - let total_options_available = current_round.get_total_options_available(); - let auction_end_time = current_round.get_auction_end_date(); - - // Make bid - let bid_amount: u256 = total_options_available; - let bid_price: u256 = reserve_price; - current_round.place_bid(bid_amount, bid_price, option_bidder_buyer_1()); - // Settle auction - set_block_timestamp(auction_end_time + 1); - vault_facade.end_auction(); - - // Convert position -> tokens (while current is Running) - vault_facade.convert_position_to_lp_tokens(deposit_amount_wei, liquidity_provider_1()); - // Initial state - let (lp_collateral_init, lp_unallocated_init) = vault_facade - .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); - // let (current_round_collateral_init, current_round_unallocated_init) = current_round - // .get_all_round_liquidity(); - // let (next_round_collateral_init, next_round_unallocated_init) = next_round_facade - // .get_all_round_liquidity(); - // - // Convert some tokens to a position while current is Running - vault_facade.convert_lp_tokens_to_position(1, deposit_amount_wei / 4, liquidity_provider_1()); - // Get states after conversion1 - let (lp_collateral1, lp_unallocated1) = vault_facade - .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); - // let (current_round_collateral1, current_round_unallocated1) = current_round - // .get_all_round_liquidity(); - // let (next_round_collateral1, next_round_unallocated1) = next_round_facade - // .get_all_round_liquidity(); - // - // Settle option round - // @dev Do we need to mock the mkagg to say there is no payout for these tests ? - timeskip_and_settle_round(ref vault_facade); - - // Convert some tokens to a position while current is Settled - vault_facade.convert_lp_tokens_to_position(1, deposit_amount_wei / 4, liquidity_provider_1()); - // Get states after conversion2 - let (lp_collateral2, lp_unallocated2) = vault_facade - .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); - // let (current_round_collateral2, current_round_unallocated2) = current_round - // .get_all_round_liquidity(); - // let (next_round_collateral2, next_round_unallocated2) = next_round_facade - // .get_all_round_liquidity(); - // - /// Start next round's auction - // @dev Need to jump to time += RTP - vault_facade.start_auction(); - let mut next_next_round = vault_facade.get_current_round(); - - // Convert some tokens to a position while current is Auctioning - vault_facade.convert_lp_tokens_to_position(1, deposit_amount_wei / 4, liquidity_provider_1()); - // Get states after conversion3 - let (lp_collateral3, lp_unallocated3) = vault_facade - .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); - // let (current_round_collateral3, current_round_unallocated3) = current_round - // .get_all_round_liquidity(); - // let (next_round_collateral3, next_round_unallocated3) = next_round_facade - // .get_all_round_liquidity(); - // let (next_next_round_collateral3, next_next_round_unallocated3) = next_next_round - // .get_all_round_liquidity(); - - // Assert initial state after converting position -> tokens - assert(lp_collateral_init == 0, 'lp collat.init shd be in tokens'); - assert( - lp_unallocated_init == 0, 'lp unalloc.init shd be 0' - ); // premiums already collected when position->tokens - // assert(current_round_collateral_init == deposit_amount_wei, 'current r collat.init wrong'); - //assert( - // current_round_unallocated_init == 0, 'current r unalloc.init shd be 0' - // ); // @dev should this be total premiums ? or stay 0 since all were collected - // assert(next_round_collateral_init == 0, 'next round collat.init shd be 0'); - // assert(next_round_unallocated_init == 0, 'next round unalloc.init shd b 0'); - - // Assert converting tokens -> position while current is Running deposits into the next round - assert(lp_collateral1 == deposit_amount_wei / 4, 'conversion amount shd be collat'); - assert(lp_unallocated1 == 0, 'lp unalloc1 shd be 0'); - // assert(current_round_collateral1 == deposit_amount_wei, 'current r collat1 wrong'); - // assert(current_round_unallocated1 == 0, 'current r unalloc1 shd be 0'); - // assert(next_round_collateral1 == 0, 'next round collat1 shd be 0'); - // assert(next_round_unallocated1 == 0, 'next round unalloc1 shd be 0'); - - // Assert converting tokens -> position while current is Settled deposits into the next round - assert(lp_collateral2 == 0, 'liq is unalloc in next round'); - assert(lp_unallocated2 == deposit_amount_wei / 2, 'prev collat shd rollover'); -// assert(current_round_collateral2 == 0, 'current r collat2 shd b 0'); -// assert(current_round_unallocated2 == 0, 'current r unalloc2 shd b 0'); -// assert(next_round_collateral2 == 0, 'next round collat2 shd b 0'); -// assert(next_round_unallocated2 == deposit_amount_wei / 2, 'all liq shd be unalloc in next'); - -// Assert converting tokens -> position while current is Auctioning deposits into the next next round -// assert(lp_collateral3 == 3 * deposit_amount_wei / 4, 'lp collat shd rollover + amount'); -// assert(lp_unallocated3 == 0, 'lp unalloc shd be 0'); -// assert(current_round_collateral3 == 0, 'current r collat shd be 0'); -// assert(current_round_unallocated3 == 0, 'current r unalloc shd be 0'); -// assert(next_round_collateral3 == 3 * deposit_amount_wei / 4, 'round collat shd rollover'); -// assert(next_round_unallocated3 == 0, 'round unalloc shd be 0'); -// assert(next_next_round_collateral3 == 0, 'next next round collat shd be 0'); -// assert(next_next_round_unallocated3 == 0, 'next next round unalloc shd b 0'); -} - -// Test converting round lp tokens into a position backwards fails (only if user can choose target round) -//fn test_convert_lp_tokens_to_position_backwards_fails() { } - -// Test converting lp tokens into a position does not count the premiums earned in the source round -#[test] -#[available_gas(50000000)] -#[ignore] -fn test_convert_lp_tokens_to_position_does_not_count_source_round_premiums() { // -// Deploy vault - -// LP1 and LP2 deposits into round 1 (50/50) - -// Start/end round 1's auction - -// LP1 converts entire position to lp tokens, then sends them to LP3 - -// Accelerate to current round 3 - -// LP3 converts all their r1 lp tokens -> r3 position - -// Assert LP3's r3 position is < LP2's r3 position (LP3's position does not include the premiums from r1 rolling over, but LP2's does) -// assert(vault::position[LP3, 3] < vault::position[LP2, 3]) -} - - -// Test converting lp tokens into a position in same round (r1 tokens to r1 position) sets premiumsCollected to true -// - The minter of the rx tokens already collected their premiums, this ensures that if the buyer of the tokens converts the rx tokens -// into an rx position, they are not allowed to double-collect these premiums -fn test_rx_tokens_to_rx_position_sets_rx_premiums_collected_to_true() { // -// Deploy vault - -// LP1 deposits 20 ETH into round 1 - -// Start/end round 1's auction - -// Convert 10 ETH to r1 LP tokens -// vault.convert_position_to_lp_tokens(amount: 10 ETH) - -// Split the tokens so that LP1 has 1/2, and LP2 has the other half -// lp_token_dispatcher.transfer_from(LP1, LP2, 5 ETH r1 lp tokens) - -// LP1 and LP2 both have 5 ETH r1 lp tokens - -// Both convert their tokens to positions in round 1 -// vault.convert_lp_tokens_to_position(lp_token_id: 1, target_round_for_position: 1, amount: 5 ETH LP tokens, caller: LP1) -// vault.convert_lp_tokens_to_position(lp_token_id: 1, target_round_for_position: 1, amount: 5 ETH LP tokens, caller: LP2) - -// Assert positions are expected -// assert(position[1, LP1] == 15 ETH) -// assert(position[1, LP2] == 5 ETH) - -// Assert premiums are expected -// @note Both LP's should not have any premiums to claim, when LP1 converted position -> token, they already collected the premiums for the tokens -// assert(vault.premiums_balance_of(LP1) == 0) -// assert(vault.premiums_balance_of(LP2) == 0) - -// What are premiumsCollected for both LP1 and LP2 ? -// - - -// Convert all r1 LP tokens to an r1 position -// vault.convert_lp_tokens_to_position(lp_token_id: 1, target_round_for_position: 1, amount: 5 ETH LP tokens) - -// Assert 5 ETH LP tokens were burned - -// Assert position[1] == 10ETH - -// premiumsCollected needs to be set, so that LP cannot double claim the r1 premium - -} - -// Test converting lp tokens into a position in the same round (r1 tokens to r1 position) handles exisiting premiums -// @dev When rX tokens -> rX position, we set premiumsCollected to true. If the user has an active position with -// collectable premiums, we do not want them to get lost, so we collect them during this step. -fn test_rx_tokens_to_rx_position_handles_exisiting_premiums() { // -// lp 1 and 2 deposit, auction starts/ends -// lp 1 tokenizes and sells to lp2 -// lp 2 converts the rX tokens into an rX position, -// - test that lp2's already exisiting premiums get collected -// - test that lp2's premiumsCollected is true -} -// @note Add test that converting tokens->position is always a position into the current round -// @dev If we want to add that a user can choose the position round (rx tokens -> ry position), -// then y must be > user.withdrawCheckpoint and we should check that other logic does not break. -// @dev The 2 tests below are not needed if the conversion is always into the current round, only -// if they user can specify the round when converting tokens->position - -/// -/// LP Tokens (rA) -> LP tokens (rB) -/// - -// - Cannot go backwards (B must be > A) -// - rB's auction must be over -// - @dev LP tokens represent a position net premiums, so we do not count the premiums earned from an lp token's source round. -// This is an issue because if a user converts tokenAs -> tokenBs, in the future we will ingore the premiums earned from roundB. To make sure the user -// still gets their premiums from this round upon tokenA->tokenB conversion, we need to collect the premiums from roundB for the user -// (we collect the amount of premiums the tokens earn in rB, if the user already has a storage position in rB with collectable premiums we ignore them). -// - @dev When we collect rB's premiums, we do not touch premiumsCollected or collectable premiums in rB, they can stay as they are in storage -// - @dev When we collect rB's premiums, we deposit them into roundB+1 as a position. -// - We cannot collect the premiums as ETH since rB might be a historical (not the current) round and that ETH may be locked in the current round. If B is the current round, then -// this position in rB+1 is immediately withdrawable. - -// @dev Begs the question, should collect always be a deposit into the next round, to then be withdrawable if choosen to (if user wants to claim premiums as eth, can just do a multi-call, collect, then withdraw) -// - `collect()` should not always set premiumsCollected to true. If a user converts tokens A-B, we collect the rB premiums for the tokens. If the user has a position in storage with collectable premiums, we are -// not touching them, so premiumsCollected does not need to be set. So maybe the collect entry point does set premiums collected to true, but collecting during a tokenA-tokenB conversion does not +// assert(lp2_collateral_final == lp2_collateral_init, 'premiums shd not collect'); +// // assert( +// // current_round_collateral_final == current_round_collateral_init, +// // 'round collateral shd not change' +// // ); +// // assert(next_round_collateral_final == next_round_collateral_init, 'round not locked yet'); +// // @dev Collected premium should be deposited into the next round +// // @dev Find out if unallocated is premiums + next round depoist or just next round deposit +// assert(lp1_unallocated_final == 'TODO'.into(), 'lp1 unallocated shd ...'); +// assert(lp2_unallocated_final == lp2_unallocated_init, 'lp2 shd not change'); +//// assert(current_round_unallocated_final == 'TODO'.into(), 'round unallocated shd ...'); +//// assert( +//// next_round_unallocated_final == next_round_unallocated_init + expected_premiums_share, +//// 'premiums not deposited' +//// ); +//// Check ETH transferred from current -> next round +//// assert_event_transfer( +//// eth.contract_address, +//// current_round.contract_address(), +//// next_round.contract_address(), +//// expected_premiums_share +//// ); +//// @note Add lp token transfer event assert function +//// assert_lp_event_transfer(lp_token_contract, from: 0, to: LP1, amount: tokenizing_amount) +//} +// +//// @note Add test that the auto-collect does nothing if LP has already collected their premiums +//// @note Add test that tokenizing > collateral fails +// +///// +///// LP Tokens -> Position /// +///// +// +//// Test converting tokens-> position deposits into the current round +//// @dev If user can choose target round when converting, then target must be > withdrawCheckpoint, +//// and the round target-1 must be settled. +//#[ignore] +//#[test] +//#[available_gas(50000000)] +//fn test_convert_lp_tokens_to_position_is_always_deposit_into_current_round() { // +// let (mut vault_facade, _) = setup_facade(); +// // LP deposits (into round 1) +// let deposit_amount_wei: u256 = 10000 * decimals(); +// vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); +// // Start auction +// vault_facade.start_auction(); +// let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); +// +// let reserve_price = current_round.get_reserve_price(); +// let total_options_available = current_round.get_total_options_available(); +// let auction_end_time = current_round.get_auction_end_date(); +// +// // Make bid +// let bid_amount: u256 = total_options_available; +// let bid_price: u256 = reserve_price; +// current_round.place_bid(bid_amount, bid_price, option_bidder_buyer_1()); +// // Settle auction +// set_block_timestamp(auction_end_time + 1); +// vault_facade.end_auction(); +// +// // Convert position -> tokens (while current is Running) +// vault_facade.convert_position_to_lp_tokens(deposit_amount_wei, liquidity_provider_1()); +// // Initial state +// let (lp_collateral_init, lp_unallocated_init) = vault_facade +// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); +// // let (current_round_collateral_init, current_round_unallocated_init) = current_round +// // .get_all_round_liquidity(); +// // let (next_round_collateral_init, next_round_unallocated_init) = next_round_facade +// // .get_all_round_liquidity(); +// // +// // Convert some tokens to a position while current is Running +// vault_facade.convert_lp_tokens_to_position(1, deposit_amount_wei / 4, liquidity_provider_1()); +// // Get states after conversion1 +// let (lp_collateral1, lp_unallocated1) = vault_facade +// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); +// // let (current_round_collateral1, current_round_unallocated1) = current_round +// // .get_all_round_liquidity(); +// // let (next_round_collateral1, next_round_unallocated1) = next_round_facade +// // .get_all_round_liquidity(); +// // +// // Settle option round +// // @dev Do we need to mock the mkagg to say there is no payout for these tests ? +// timeskip_and_settle_round(ref vault_facade); +// +// // Convert some tokens to a position while current is Settled +// vault_facade.convert_lp_tokens_to_position(1, deposit_amount_wei / 4, liquidity_provider_1()); +// // Get states after conversion2 +// let (lp_collateral2, lp_unallocated2) = vault_facade +// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); +// // let (current_round_collateral2, current_round_unallocated2) = current_round +// // .get_all_round_liquidity(); +// // let (next_round_collateral2, next_round_unallocated2) = next_round_facade +// // .get_all_round_liquidity(); +// // +// /// Start next round's auction +// // @dev Need to jump to time += RTP +// vault_facade.start_auction(); +// let mut next_next_round = vault_facade.get_current_round(); +// +// // Convert some tokens to a position while current is Auctioning +// vault_facade.convert_lp_tokens_to_position(1, deposit_amount_wei / 4, liquidity_provider_1()); +// // Get states after conversion3 +// let (lp_collateral3, lp_unallocated3) = vault_facade +// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); +// // let (current_round_collateral3, current_round_unallocated3) = current_round +// // .get_all_round_liquidity(); +// // let (next_round_collateral3, next_round_unallocated3) = next_round_facade +// // .get_all_round_liquidity(); +// // let (next_next_round_collateral3, next_next_round_unallocated3) = next_next_round +// // .get_all_round_liquidity(); +// +// // Assert initial state after converting position -> tokens +// assert(lp_collateral_init == 0, 'lp collat.init shd be in tokens'); +// assert( +// lp_unallocated_init == 0, 'lp unalloc.init shd be 0' +// ); // premiums already collected when position->tokens +// // assert(current_round_collateral_init == deposit_amount_wei, 'current r collat.init wrong'); +// //assert( +// // current_round_unallocated_init == 0, 'current r unalloc.init shd be 0' +// // ); // @dev should this be total premiums ? or stay 0 since all were collected +// // assert(next_round_collateral_init == 0, 'next round collat.init shd be 0'); +// // assert(next_round_unallocated_init == 0, 'next round unalloc.init shd b 0'); +// +// // Assert converting tokens -> position while current is Running deposits into the next round +// assert(lp_collateral1 == deposit_amount_wei / 4, 'conversion amount shd be collat'); +// assert(lp_unallocated1 == 0, 'lp unalloc1 shd be 0'); +// // assert(current_round_collateral1 == deposit_amount_wei, 'current r collat1 wrong'); +// // assert(current_round_unallocated1 == 0, 'current r unalloc1 shd be 0'); +// // assert(next_round_collateral1 == 0, 'next round collat1 shd be 0'); +// // assert(next_round_unallocated1 == 0, 'next round unalloc1 shd be 0'); +// +// // Assert converting tokens -> position while current is Settled deposits into the next round +// assert(lp_collateral2 == 0, 'liq is unalloc in next round'); +// assert(lp_unallocated2 == deposit_amount_wei / 2, 'prev collat shd rollover'); +//// assert(current_round_collateral2 == 0, 'current r collat2 shd b 0'); +//// assert(current_round_unallocated2 == 0, 'current r unalloc2 shd b 0'); +//// assert(next_round_collateral2 == 0, 'next round collat2 shd b 0'); +//// assert(next_round_unallocated2 == deposit_amount_wei / 2, 'all liq shd be unalloc in next'); +// +//// Assert converting tokens -> position while current is Auctioning deposits into the next next round +//// assert(lp_collateral3 == 3 * deposit_amount_wei / 4, 'lp collat shd rollover + amount'); +//// assert(lp_unallocated3 == 0, 'lp unalloc shd be 0'); +//// assert(current_round_collateral3 == 0, 'current r collat shd be 0'); +//// assert(current_round_unallocated3 == 0, 'current r unalloc shd be 0'); +//// assert(next_round_collateral3 == 3 * deposit_amount_wei / 4, 'round collat shd rollover'); +//// assert(next_round_unallocated3 == 0, 'round unalloc shd be 0'); +//// assert(next_next_round_collateral3 == 0, 'next next round collat shd be 0'); +//// assert(next_next_round_unallocated3 == 0, 'next next round unalloc shd b 0'); +//} +// +//// Test converting round lp tokens into a position backwards fails (only if user can choose target round) +////fn test_convert_lp_tokens_to_position_backwards_fails() { } +// +//// Test converting lp tokens into a position does not count the premiums earned in the source round +//#[test] +//#[available_gas(50000000)] +//#[ignore] +//fn test_convert_lp_tokens_to_position_does_not_count_source_round_premiums() { // +//// Deploy vault +// +//// LP1 and LP2 deposits into round 1 (50/50) +// +//// Start/end round 1's auction +// +//// LP1 converts entire position to lp tokens, then sends them to LP3 +// +//// Accelerate to current round 3 +// +//// LP3 converts all their r1 lp tokens -> r3 position +// +//// Assert LP3's r3 position is < LP2's r3 position (LP3's position does not include the premiums from r1 rolling over, but LP2's does) +//// assert(vault::position[LP3, 3] < vault::position[LP2, 3]) +//} +// +// +//// Test converting lp tokens into a position in same round (r1 tokens to r1 position) sets premiumsCollected to true +//// - The minter of the rx tokens already collected their premiums, this ensures that if the buyer of the tokens converts the rx tokens +//// into an rx position, they are not allowed to double-collect these premiums +//fn test_rx_tokens_to_rx_position_sets_rx_premiums_collected_to_true() { // +//// Deploy vault +// +//// LP1 deposits 20 ETH into round 1 +// +//// Start/end round 1's auction +// +//// Convert 10 ETH to r1 LP tokens +//// vault.convert_position_to_lp_tokens(amount: 10 ETH) +// +//// Split the tokens so that LP1 has 1/2, and LP2 has the other half +//// lp_token_dispatcher.transfer_from(LP1, LP2, 5 ETH r1 lp tokens) +// +//// LP1 and LP2 both have 5 ETH r1 lp tokens +// +//// Both convert their tokens to positions in round 1 +//// vault.convert_lp_tokens_to_position(lp_token_id: 1, target_round_for_position: 1, amount: 5 ETH LP tokens, caller: LP1) +//// vault.convert_lp_tokens_to_position(lp_token_id: 1, target_round_for_position: 1, amount: 5 ETH LP tokens, caller: LP2) +// +//// Assert positions are expected +//// assert(position[1, LP1] == 15 ETH) +//// assert(position[1, LP2] == 5 ETH) +// +//// Assert premiums are expected +//// @note Both LP's should not have any premiums to claim, when LP1 converted position -> token, they already collected the premiums for the tokens +//// assert(vault.premiums_balance_of(LP1) == 0) +//// assert(vault.premiums_balance_of(LP2) == 0) +// +//// What are premiumsCollected for both LP1 and LP2 ? +//// - +// +//// Convert all r1 LP tokens to an r1 position +//// vault.convert_lp_tokens_to_position(lp_token_id: 1, target_round_for_position: 1, amount: 5 ETH LP tokens) +// +//// Assert 5 ETH LP tokens were burned +// +//// Assert position[1] == 10ETH +// +//// premiumsCollected needs to be set, so that LP cannot double claim the r1 premium +// +//} +// +//// Test converting lp tokens into a position in the same round (r1 tokens to r1 position) handles exisiting premiums +//// @dev When rX tokens -> rX position, we set premiumsCollected to true. If the user has an active position with +//// collectable premiums, we do not want them to get lost, so we collect them during this step. +//fn test_rx_tokens_to_rx_position_handles_exisiting_premiums() { // +//// lp 1 and 2 deposit, auction starts/ends +//// lp 1 tokenizes and sells to lp2 +//// lp 2 converts the rX tokens into an rX position, +//// - test that lp2's already exisiting premiums get collected +//// - test that lp2's premiumsCollected is true +//} +//// @note Add test that converting tokens->position is always a position into the current round +//// @dev If we want to add that a user can choose the position round (rx tokens -> ry position), +//// then y must be > user.withdrawCheckpoint and we should check that other logic does not break. +//// @dev The 2 tests below are not needed if the conversion is always into the current round, only +//// if they user can specify the round when converting tokens->position +// +///// +///// LP Tokens (rA) -> LP tokens (rB) +///// +// +//// - Cannot go backwards (B must be > A) +//// - rB's auction must be over +//// - @dev LP tokens represent a position net premiums, so we do not count the premiums earned from an lp token's source round. +//// This is an issue because if a user converts tokenAs -> tokenBs, in the future we will ingore the premiums earned from roundB. To make sure the user +//// still gets their premiums from this round upon tokenA->tokenB conversion, we need to collect the premiums from roundB for the user +//// (we collect the amount of premiums the tokens earn in rB, if the user already has a storage position in rB with collectable premiums we ignore them). +//// - @dev When we collect rB's premiums, we do not touch premiumsCollected or collectable premiums in rB, they can stay as they are in storage +//// - @dev When we collect rB's premiums, we deposit them into roundB+1 as a position. +//// - We cannot collect the premiums as ETH since rB might be a historical (not the current) round and that ETH may be locked in the current round. If B is the current round, then +//// this position in rB+1 is immediately withdrawable. +// +//// @dev Begs the question, should collect always be a deposit into the next round, to then be withdrawable if choosen to (if user wants to claim premiums as eth, can just do a multi-call, collect, then withdraw) +//// - `collect()` should not always set premiumsCollected to true. If a user converts tokens A-B, we collect the rB premiums for the tokens. If the user has a position in storage with collectable premiums, we are +//// not touching them, so premiumsCollected does not need to be set. So maybe the collect entry point does set premiums collected to true, but collecting during a tokenA-tokenB conversion does not +// +// diff --git a/src/tests/misc/pitch_lake_test.cairo b/src/tests/misc/pitch_lake_test.cairo index 70101724..e2856837 100644 --- a/src/tests/misc/pitch_lake_test.cairo +++ b/src/tests/misc/pitch_lake_test.cairo @@ -92,7 +92,7 @@ fn test_vault_type() { let itm_vault: IVaultDispatcher = pitch_lake_dispatcher.in_the_money_vault(); let otm_vault: IVaultDispatcher = pitch_lake_dispatcher.out_the_money_vault(); let atm_vault: IVaultDispatcher = pitch_lake_dispatcher.at_the_money_vault(); - assert(itm_vault.vault_type() == VaultType::InTheMoney, 'ITM vault wrong'); - assert(otm_vault.vault_type() == VaultType::OutOfMoney, 'OTM vault wrong'); - assert(atm_vault.vault_type() == VaultType::AtTheMoney, 'ATM vault wrong'); + assert(itm_vault.get_vault_type() == VaultType::InTheMoney, 'ITM vault wrong'); + assert(otm_vault.get_vault_type() == VaultType::OutOfMoney, 'OTM vault wrong'); + assert(atm_vault.get_vault_type() == VaultType::AtTheMoney, 'ATM vault wrong'); } diff --git a/src/tests/option_round/option_buyers/bidding_tests.cairo b/src/tests/option_round/option_buyers/bidding_tests.cairo index 509acdfd..77da6764 100644 --- a/src/tests/option_round/option_buyers/bidding_tests.cairo +++ b/src/tests/option_round/option_buyers/bidding_tests.cairo @@ -30,7 +30,7 @@ use pitch_lake_starknet::{ accelerate_to_settled, timeskip_past_auction_end_date, }, event_helpers::{ - assert_event_transfer, assert_event_auction_bid_accepted, pop_log, + assert_event_transfer, assert_event_auction_bid_placed, pop_log, assert_no_events_left, } }, @@ -215,20 +215,24 @@ fn test_bid_accepted_events() { let mut bid_amounts = create_array_linear(options_available, 3).span(); let mut bid_prices = create_array_gradient(reserve_price, reserve_price, 3).span(); clear_event_logs(array![current_round.contract_address()]); - current_round.place_bids(bid_amounts, bid_prices, option_bidders); + let mut bids = current_round.place_bids(bid_amounts, bid_prices, option_bidders).span(); // Check bid accepted events loop { match option_bidders.pop_front() { Option::Some(ob) => { + let bid = bids.pop_front().unwrap(); + let bid_id = bid.bid_id; + let tree_nonce = bid.tree_nonce; let bid_amount = bid_amounts.pop_front().unwrap(); let bid_price = bid_prices.pop_front().unwrap(); - assert_event_auction_bid_accepted( + assert_event_auction_bid_placed( current_round.contract_address(), *ob, + *bid_id, *bid_amount, *bid_price, - 0 //The 0 is nonce, nonce for each of the bidders should be 0 as it's there first bid + *tree_nonce + 1 ); }, Option::None => { break; } @@ -446,7 +450,7 @@ fn test_place_bid_id() { array![bidder.into(), bid_nonce.into()].span() ); let bid = current_round.place_bid(bid_amount, bid_price, bidder); - assert(bid.id == expected_hash, 'Bid Id Incorrect'); + assert(bid.bid_id == expected_hash, 'Bid Id Incorrect'); i -= 1; }; } diff --git a/src/tests/option_round/option_buyers/update_bids_tests.cairo b/src/tests/option_round/option_buyers/update_bids_tests.cairo index 71ca9757..343b4ca0 100644 --- a/src/tests/option_round/option_buyers/update_bids_tests.cairo +++ b/src/tests/option_round/option_buyers/update_bids_tests.cairo @@ -1,6 +1,6 @@ use core::traits::Into; use pitch_lake_starknet::{ - types::Errors, + types::{Errors, BidDisplay}, tests::{ utils::{ helpers::{ @@ -19,31 +19,10 @@ use pitch_lake_starknet::{ }, } }; -#[test] -#[available_gas(50000000)] -fn test_update_bids_amount_cannot_be_decreased() { - let (mut vault_facade, _) = setup_facade(); - let options_available = accelerate_to_auctioning(ref vault_facade); - let mut current_round = vault_facade.get_current_round(); - let reserve_price = current_round.get_reserve_price(); - - // Place bids - let bidder = option_bidder_buyer_1(); - let bid_price = reserve_price; - let mut bid_amount = options_available; - let bid = current_round.place_bid(bid_amount, bid_price, bidder); - - // Update bid to lower price - current_round - .update_bid_expect_error( - bid.id, bid_amount - 1, bid_price, bidder, Errors::BidCannotBeDecreased - ); -} - #[test] #[available_gas(50000000)] -fn test_update_bids_price_cannot_be_decreased() { +fn test_update_bids_price_increase_cannot_be_0() { let (mut vault_facade, _) = setup_facade(); let options_available = accelerate_to_auctioning(ref vault_facade); let mut current_round = vault_facade.get_current_round(); @@ -56,10 +35,7 @@ fn test_update_bids_price_cannot_be_decreased() { let bid = current_round.place_bid(bid_amount, bid_price, bidder); // Update bid to lower price - current_round - .update_bid_expect_error( - bid.id, bid_amount, bid_price - 1, bidder, Errors::BidCannotBeDecreased - ); + current_round.update_bid_expect_error(bid.bid_id, 0, bidder, Errors::BidMustBeIncreased); } #[test] @@ -77,66 +53,46 @@ fn test_update_bid_event() { let bid = current_round.place_bid(bid_amount, bid_price, option_buyer); clear_event_logs(array![current_round.contract_address()]); - let updated_bid = current_round.update_bid(bid.id, bid_amount + 1, bid_price + 5); + let updated_bid = current_round.update_bid(bid.bid_id, 5); assert_event_auction_bid_updated( current_round.contract_address(), option_buyer, - bid_amount, //Old amount - bid_price, //Old price - bid_amount + 1, //Updated amount - bid_price + 5, //Updated price - bid.id + bid.bid_id, + 5, //Updated amount + current_round.get_bid_tree_nonce(), ); - assert(updated_bid.amount == bid_amount + 1, 'Updated amount incorrect'); + assert(updated_bid.amount == bid_amount, 'Amount shd not change'); assert(updated_bid.price == bid_price + 5, 'Updated price incorrect'); } -// These 2 tests deal with the case where the the bidder is editing their bid to cost them more ETH, but lower -// either their amount or price -// - @note They are purposefully using default errors so we do not forget to discuss this point - -#[test] -#[available_gas(50000000)] -fn test_update_bids_amount_cannot_be_decreased_event_if_price_is_increased() { - let (mut vault_facade, _) = setup_facade(); - let options_available = accelerate_to_auctioning(ref vault_facade); - let mut current_round = vault_facade.get_current_round(); - let reserve_price = current_round.get_reserve_price(); - - // Place bids - let bidder = option_bidder_buyer_1(); - let bid_price = reserve_price; - let mut bid_amount = options_available; - let bid = current_round.place_bid(bid_amount, bid_price, bidder); - - // Update bid to lower price and much higer amount (bid total $ > prev total) - current_round - .update_bid_expect_error( - bid.id, bid_amount - 1, 10 * bid_price, bidder, Errors::BidCannotBeDecreased - ); -} - #[test] -#[available_gas(50000000)] -fn test_update_bids_price_cannot_be_decreased_event_if_amount_is_increased() { +#[available_gas(80000000)] +fn test_update_bid_nonces() { let (mut vault_facade, _) = setup_facade(); let mut current_round = vault_facade.get_current_round(); let options_available = accelerate_to_auctioning(ref vault_facade); let reserve_price = current_round.get_reserve_price(); // Place bids - let bidder = option_bidder_buyer_1(); + let option_buyer = option_bidder_buyer_1(); let bid_price = reserve_price; - let mut bid_amount = options_available; - let bid = current_round.place_bid(bid_amount, bid_price, bidder); + let mut bid_amount = options_available / 2; + let bid1 = current_round.place_bid(bid_amount, bid_price, option_buyer); + current_round.place_bid(bid_amount, bid_price, option_buyer); + current_round.place_bid(bid_amount, bid_price, option_buyer); + clear_event_logs(array![current_round.contract_address()]); - // Update bid to lower price and >> amount - current_round - .update_bid_expect_error( - bid.id, 10 * bid_amount, bid_price - 1, bidder, Errors::BidCannotBeDecreased - ); -} + let bidder_nonce_before = current_round.get_bidding_nonce_for(option_buyer); + let tree_nonce_before = current_round.get_bid_tree_nonce(); + current_round.update_bid(bid1.bid_id, 10000000000000); + let bidder_nonce_after = current_round.get_bidding_nonce_for(option_buyer); + let tree_nonce_after = current_round.get_bid_tree_nonce(); + assert(bidder_nonce_before == bidder_nonce_after, 'Bidder nonce should not change'); + assert(tree_nonce_before + 1 == tree_nonce_after, 'Tree nonce should change'); + assert_eq!(bidder_nonce_after, 3); + assert_eq!(tree_nonce_after, 4); +} // Test that bid cannot be updated by a non owner #[test] #[available_gas(50000000)] @@ -150,19 +106,16 @@ fn test_update_bids_must_be_called_by_bid_owner() { let bidder = option_bidder_buyer_1(); let non_bidder = option_bidder_buyer_2(); let bid_price = reserve_price; - let mut bid_amount = options_available; + let bid_amount = options_available; let bid = current_round.place_bid(bid_amount, bid_price, bidder); // Update bid as non bidder - current_round - .update_bid_expect_error( - bid.id, 10 * bid_amount, bid_price - 1, non_bidder, Errors::CallerNotBidOwner - ); + current_round.update_bid_expect_error(bid.bid_id, 1, non_bidder, Errors::CallerNotBidOwner); } // Test that updating bids keeps get_bids_for working as expected #[test] -#[available_gas(70000000)] +#[available_gas(100000000)] fn test_updating_bids_and_get_bids_for() { let (mut vault, _) = setup_facade(); let mut current_round = vault.get_current_round(); @@ -177,17 +130,22 @@ fn test_updating_bids_and_get_bids_for() { let bid2 = current_round.place_bid(bid_amount + 1, bid_price + 1, bidder); let bid3 = current_round.place_bid(bid_amount + 2, bid_price + 2, bidder); - let bid4 = current_round.update_bid(bid3.id, bid_amount + 3, bid_price + 3); + // Edit bid 3, should not change order or amount of bids + let bid4 = current_round.update_bid(bid3.bid_id, 3); - let exepcted_bids = array![bid1, bid2, bid4]; + let expected_bids = array![bid1, bid2, bid4]; let actual_bids = current_round.get_bids_for(bidder); - assert(actual_bids == exepcted_bids, 'Bids do not match'); + assert(actual_bids == expected_bids, 'bids do not match'); } + +// Test later bid higher price wins +// Test later bid same price higher amount loses + #[test] #[available_gas(70000000)] -fn test_updating_bids_follows_tree_nonce_sorting() { +fn test_updating_bids_higher_price_wins() { let (mut vault, _) = setup_facade(); let mut current_round = vault.get_current_round(); let options_available = accelerate_to_auctioning(ref vault); @@ -197,15 +155,15 @@ fn test_updating_bids_follows_tree_nonce_sorting() { let bidder = option_bidder_buyer_1(); let bidder2 = option_bidder_buyer_2(); - let bid_price = reserve_price; + let bid_price = 2 * reserve_price; let bid_amount = (3 * options_available / 4); - // Bid 1 < Bid 2 - let bid1 = current_round.place_bid(bid_amount, bid_price, bidder); - let _bid2 = current_round.place_bid(bid_amount, bid_price + 10, bidder2); + // Bid 1 == Bid 2 + let _bid1 = current_round.place_bid(bid_amount, bid_price - 1, bidder); + let bid2 = current_round.place_bid(bid_amount, bid_price - 1, bidder2); - // Update Bid 1 to be higher than Bid 2, but because of tree nonce, will still be ranked lower - let _bid1 = current_round.update_bid(bid1.id, bid_amount + 10000000, bid_price + 10); + // Update Bid 2 to be > than Bid 1 + let _bid2 = current_round.update_bid(bid2.bid_id, 1); timeskip_and_end_auction(ref vault); @@ -216,3 +174,31 @@ fn test_updating_bids_follows_tree_nonce_sorting() { assert_eq!(options_for2, 3 * options_available / 4, "Bidder 1 options wrong"); } +#[test] +#[available_gas(70000000)] +fn test_updating_bids_lower_tree_index_loses() { + let (mut vault, _) = setup_facade(); + let mut current_round = vault.get_current_round(); + let options_available = accelerate_to_auctioning(ref vault); + let reserve_price = current_round.get_reserve_price(); + + // Place bids + let bidder = option_bidder_buyer_1(); + let bidder2 = option_bidder_buyer_2(); + let amount = (3 * options_available) / 4; + + // Bid 1 amount > Bid 2 amount, Bid 1 price < Bid 2 price, Bid 2 is ranked higher because of price + let bid1 = current_round.place_bid(amount, reserve_price, bidder); + let _bid2 = current_round.place_bid(amount / 2, reserve_price + 1, bidder2); + + // Update Bid 1 to be same price as Bid 2. Bid 2 ranked higher because of tree nonce still + current_round.update_bid(bid1.bid_id, 1); + + timeskip_and_end_auction(ref vault); + + let options_for1 = current_round.get_mintable_options_for(bidder); + let options_for2 = current_round.get_mintable_options_for(bidder2); + + assert_eq!(options_for1, options_available - (amount / 2), "Bidder 1 options wrong"); + assert_eq!(options_for2, amount / 2, "Bidder 2 options wrong"); +} diff --git a/src/tests/option_round/rb_tree/rb_tree_stress_tests.cairo b/src/tests/option_round/rb_tree/rb_tree_stress_tests.cairo index 9dd47ae2..35dcbe67 100644 --- a/src/tests/option_round/rb_tree/rb_tree_stress_tests.cairo +++ b/src/tests/option_round/rb_tree/rb_tree_stress_tests.cairo @@ -103,9 +103,9 @@ fn testing_random_insertion_and_deletion() { rb_tree.insert(new_bid); - inserted_node_ids.append(new_bid.id); + inserted_node_ids.append(new_bid.bid_id); - let bid = rb_tree.find(new_bid.id); + let bid = rb_tree.find(new_bid.bid_id); println!("Inserting price {}", bid.price); assert(bid.price == price.try_into().unwrap(), 'Insertion error'); @@ -127,7 +127,7 @@ fn testing_random_insertion_and_deletion() { let found_bid = rb_tree.find(*bid_id); - assert(found_bid.id == 0, 'Bid delete error'); + assert(found_bid.bid_id == 0, 'Bid delete error'); let is_tree_valid = rb_tree.is_tree_valid(); assert(is_tree_valid, 'Tree is not valid'); diff --git a/src/tests/option_round/rb_tree/rb_tree_tests.cairo b/src/tests/option_round/rb_tree/rb_tree_tests.cairo index 1eea03cd..a06741c5 100644 --- a/src/tests/option_round/rb_tree/rb_tree_tests.cairo +++ b/src/tests/option_round/rb_tree/rb_tree_tests.cairo @@ -96,7 +96,7 @@ fn test_recoloring_only() { let mut new_bid = create_bid(31, 1); rb_tree.insert(new_bid); - let node_31 = new_bid.id; + let node_31 = new_bid.bid_id; new_bid = create_bid(11, 2); let node_11 = rb_tree.add_node(new_bid, RED, node_31); @@ -148,7 +148,7 @@ fn test_recoloring_two() { let mut new_bid = create_bid(31, 1); rb_tree.insert(new_bid); - let node_31 = new_bid.id; + let node_31 = new_bid.bid_id; let new_bid = create_bid(11, 2); let node_11 = rb_tree.add_node(new_bid, RED, node_31); @@ -200,7 +200,7 @@ fn test_right_rotation() { let mut new_bid = create_bid(21, 1); rb_tree.insert(new_bid); - let node_21 = new_bid.id; + let node_21 = new_bid.bid_id; let new_bid = create_bid(1, 2); let node_1 = rb_tree.add_node(new_bid, BLACK, node_21); @@ -239,7 +239,7 @@ fn test_left_rotation_no_sibling() { let mut new_bid = create_bid(10, 1); rb_tree.insert(new_bid); - let node_10 = new_bid.id; + let node_10 = new_bid.bid_id; let new_bid = create_bid(7, 2); let node_7 = rb_tree.add_node(new_bid, BLACK, node_10); @@ -275,7 +275,7 @@ fn test_right_rotation_no_sibling_left_subtree() { let mut new_bid = create_bid(23, 1); rb_tree.insert(new_bid); - let node_23 = new_bid.id; + let node_23 = new_bid.bid_id; let new_bid = create_bid(3, 2); let node_3 = rb_tree.add_node(new_bid, BLACK, node_23); @@ -313,7 +313,7 @@ fn test_left_right_rotation_no_sibling() { let mut new_bid = create_bid(21, 1); rb_tree.insert(new_bid); - let node_21 = new_bid.id; + let node_21 = new_bid.bid_id; let new_bid = create_bid(1, 2); let node_1 = rb_tree.add_node(new_bid, BLACK, node_21); @@ -351,7 +351,7 @@ fn test_right_left_rotation_no_sibling() { let mut new_bid = create_bid(21, 1); rb_tree.insert(new_bid); - let node_21 = new_bid.id; + let node_21 = new_bid.bid_id; let new_bid = create_bid(1, 2); let node_1 = rb_tree.add_node(new_bid, BLACK, node_21); @@ -390,7 +390,7 @@ fn test_recolor_lr() { let mut new_bid = create_bid(31, 1); rb_tree.insert(new_bid); - let node_31 = new_bid.id; + let node_31 = new_bid.bid_id; let new_bid = create_bid(11, 2); let node_11 = rb_tree.add_node(new_bid, RED, node_31); @@ -533,7 +533,7 @@ fn test_right_left_rotation_after_recolor() { let mut new_bid = create_bid(10, 1); rb_tree.insert(new_bid); - let node_10 = new_bid.id; + let node_10 = new_bid.bid_id; let new_bid = create_bid(5, 2); rb_tree.add_node(new_bid, BLACK, node_10); @@ -579,7 +579,7 @@ fn test_right_rotation_after_recolor() { let mut new_bid = create_bid(33, 1); rb_tree.insert(new_bid); - let node_33 = new_bid.id; + let node_33 = new_bid.bid_id; let new_bid = create_bid(13, 2); let node_13 = rb_tree.add_node(new_bid, RED, node_33); @@ -701,7 +701,7 @@ fn test_delete_single_deep_child() { let mut new_bid = create_bid(20, 1); rb_tree.insert(new_bid); - let node_20 = new_bid.id; + let node_20 = new_bid.bid_id; let new_bid = create_bid(10, 2); let node_10 = rb_tree.add_node(new_bid, BLACK, node_20); @@ -758,7 +758,7 @@ fn test_deletion_red_node_red_successor_no_children() { let mut new_bid = create_bid(16, 1); rb_tree.insert(new_bid); - let node_16 = new_bid.id; + let node_16 = new_bid.bid_id; let new_bid = create_bid(11, 2); let node_11 = rb_tree.add_node(new_bid, RED, node_16); @@ -805,7 +805,7 @@ fn test_mirror_deletion_red_node_red_successor_no_children() { let mut new_bid = create_bid(16, 1); rb_tree.insert(new_bid); - let node_16 = new_bid.id; + let node_16 = new_bid.bid_id; let new_bid = create_bid(11, 2); let node_11 = rb_tree.add_node(new_bid, RED, node_16); @@ -856,7 +856,7 @@ fn test_deletion_black_node_black_successor_right_red_child() { let mut new_bid = create_bid(16, 1); rb_tree.insert(new_bid); - let node_16 = new_bid.id; + let node_16 = new_bid.bid_id; let new_bid = create_bid(11, 2); let node_11 = rb_tree.add_node(new_bid, BLACK, node_16); @@ -911,7 +911,7 @@ fn test_deletion_black_node_black_successor_no_child() { let mut new_bid = create_bid(21, 1); rb_tree.insert(new_bid); - let node_21 = new_bid.id; + let node_21 = new_bid.bid_id; let new_bid = create_bid(1, 2); rb_tree.add_node(new_bid, BLACK, node_21); @@ -947,7 +947,7 @@ fn test_deletion_black_node_no_successor() { let mut new_bid = create_bid(21, 1); rb_tree.insert(new_bid); - let node_21 = new_bid.id; + let node_21 = new_bid.bid_id; let new_bid = create_bid(1, 2); let node_1 = rb_tree.add_node(new_bid, BLACK, node_21); @@ -983,7 +983,7 @@ fn test_mirror_deletion_black_node_no_successor() { let mut new_bid = create_bid(10, 1); rb_tree.insert(new_bid); - let node_10 = new_bid.id; + let node_10 = new_bid.bid_id; let new_bid = create_bid(5, 2); let node_5 = rb_tree.add_node(new_bid, BLACK, node_10); @@ -1040,7 +1040,7 @@ fn test_deletion_black_node_no_successor_3() { let mut new_bid = create_bid(10, 1); rb_tree.insert(new_bid); - let node_10 = new_bid.id; + let node_10 = new_bid.bid_id; let new_bid = create_bid(7, 2); let node_7 = rb_tree.add_node(new_bid, BLACK, node_10); @@ -1093,7 +1093,7 @@ fn test_deletion_black_node_successor() { let mut new_bid = create_bid(10, 1); rb_tree.insert(new_bid); - let node_10 = new_bid.id; + let node_10 = new_bid.bid_id; let new_bid = create_bid(5, 2); let node_5 = rb_tree.add_node(new_bid, BLACK, node_10); @@ -1145,7 +1145,7 @@ fn test_mirror_deletion_black_node_successor() { let mut new_bid = create_bid(20, 1); rb_tree.insert(new_bid); - let node_20 = new_bid.id; + let node_20 = new_bid.bid_id; let new_bid = create_bid(10, 2); let node_10 = rb_tree.add_node(new_bid, BLACK, node_20); @@ -1227,17 +1227,21 @@ fn test_delete_tree_one_by_one() { // Test Utilities -fn create_bid(price: u256, nonce: u64) -> Bid { - let bidder = mock_address(MOCK_ADDRESS); - let id = poseidon::poseidon_hash_span(array![bidder.into(), nonce.try_into().unwrap()].span()); - Bid { id: id, nonce: nonce, owner: bidder, amount: 0, price: price, } +fn create_bid(price: u256, tree_nonce: u64) -> Bid { + let owner = mock_address(MOCK_ADDRESS); + let bid_id = poseidon::poseidon_hash_span( + array![owner.into(), tree_nonce.try_into().unwrap()].span() + ); + Bid { bid_id, owner, amount: 0, price, tree_nonce } } -fn insert(rb_tree: IRBTreeMockContractDispatcher, price: u256, nonce: u64) -> felt252 { - let bidder = mock_address(MOCK_ADDRESS); - let id = poseidon::poseidon_hash_span(array![bidder.into(), nonce.try_into().unwrap()].span()); - rb_tree.insert(Bid { id: id, nonce: nonce, owner: bidder, amount: 0, price: price, }); - return id; +fn insert(rb_tree: IRBTreeMockContractDispatcher, price: u256, tree_nonce: u64) -> felt252 { + let owner = mock_address(MOCK_ADDRESS); + let bid_id = poseidon::poseidon_hash_span( + array![owner.into(), tree_nonce.try_into().unwrap()].span() + ); + rb_tree.insert(Bid { bid_id, owner, amount: 0, price, tree_nonce }); + return bid_id; } fn is_tree_valid(rb_tree: IRBTreeMockContractDispatcher) -> bool { diff --git a/src/tests/option_round/state_transition/auction_end/premium_earned_tests.cairo b/src/tests/option_round/state_transition/auction_end/premium_earned_tests.cairo index 2258dcba..517ad6eb 100644 --- a/src/tests/option_round/state_transition/auction_end/premium_earned_tests.cairo +++ b/src/tests/option_round/state_transition/auction_end/premium_earned_tests.cairo @@ -92,7 +92,7 @@ fn test_premium_amount_for_liquidity_providers_3() { // Test the portion of premiums an LP can collect in a round is correct (more LPs) #[test] -#[available_gas(50000000)] +#[available_gas(80000000)] fn test_premium_amount_for_liquidity_providers_4() { let (mut vault_facade, _) = setup_facade(); // LPs diff --git a/src/tests/utils/facades/option_round_facade.cairo b/src/tests/utils/facades/option_round_facade.cairo index 03299703..ec6c51e9 100644 --- a/src/tests/utils/facades/option_round_facade.cairo +++ b/src/tests/utils/facades/option_round_facade.cairo @@ -115,12 +115,12 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { // Settle the current option round fn settle_option_round(ref self: OptionRoundFacade, settlement_price: u256) -> u256 { - let (total_payout, _) = self.option_round_dispatcher.settle_option_round(settlement_price); + let total_payout = self.option_round_dispatcher.settle_round(settlement_price); // Set ETH approvals for next round let vault_dispatcher = IVaultDispatcher { contract_address: self.vault_address() }; let next_round_address = vault_dispatcher.get_round_address(self.get_round_id() + 1); - eth_supply_and_approve_all_bidders(next_round_address, vault_dispatcher.eth_address()); + eth_supply_and_approve_all_bidders(next_round_address, vault_dispatcher.get_eth_address()); sanity_checks::settle_option_round(ref self, total_payout) } @@ -145,7 +145,7 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { ref self: OptionRoundFacade, settlement_price: u256, error: felt252, ) { let safe_option_round = self.get_safe_dispatcher(); - safe_option_round.settle_option_round(settlement_price).expect_err(error); + safe_option_round.settle_round(settlement_price).expect_err(error); } @@ -264,11 +264,11 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { // Update a bid for an option bidder // @return: The updated bid - fn update_bid(ref self: OptionRoundFacade, id: felt252, amount: u256, price: u256) -> Bid { + fn update_bid(ref self: OptionRoundFacade, id: felt252, price_increase: u256) -> Bid { let old_bid = self.get_bid_details(id); let bidder = old_bid.owner; set_contract_address(bidder); - let new_bid = self.option_round_dispatcher.update_bid(id, amount, price); + let new_bid = self.option_round_dispatcher.update_bid(id, price_increase); sanity_checks::update_bid(ref self, old_bid, new_bid) } @@ -277,13 +277,13 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { fn update_bid_expect_error( ref self: OptionRoundFacade, id: felt252, - amount: u256, - price: u256, + price_increase: u256, bidder: ContractAddress, error: felt252, ) { let safe_option_round = self.get_safe_dispatcher(); - safe_option_round.update_bid(id, amount, price).expect_err(error); + set_contract_address(bidder); + safe_option_round.update_bid(id, price_increase).expect_err(error); } // Refunds all unused bids of an option bidder @@ -393,29 +393,29 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { /// $ fn starting_liquidity(ref self: OptionRoundFacade) -> u256 { - self.option_round_dispatcher.starting_liquidity() + self.option_round_dispatcher.get_starting_liquidity() } fn unsold_liquidity(ref self: OptionRoundFacade) -> u256 { // @note Temp fix, can move this function to round facade - self.option_round_dispatcher.unsold_liquidity() + self.option_round_dispatcher.get_unsold_liquidity() } fn total_premiums(ref self: OptionRoundFacade) -> u256 { - self.option_round_dispatcher.total_premiums() + self.option_round_dispatcher.get_total_premium() } fn total_payout(ref self: OptionRoundFacade) -> u256 { - self.option_round_dispatcher.total_payout() + self.option_round_dispatcher.get_total_payout() } fn get_auction_clearing_price(ref self: OptionRoundFacade) -> u256 { - self.option_round_dispatcher.clearing_price() + self.option_round_dispatcher.get_clearing_price() } fn total_options_sold(ref self: OptionRoundFacade) -> u256 { - self.option_round_dispatcher.total_options_sold() + self.option_round_dispatcher.get_options_sold() } fn get_bid_details(ref self: OptionRoundFacade, bid_id: felt252) -> Bid { @@ -424,33 +424,37 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { fn get_bidding_nonce_for( ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress - ) -> u32 { - self.option_round_dispatcher.get_bidding_nonce_for(option_bidder_buyer) + ) -> u64 { + self.option_round_dispatcher.get_account_bid_nonce(option_bidder_buyer) + } + + fn get_bid_tree_nonce(ref self: OptionRoundFacade) -> u64 { + self.option_round_dispatcher.get_bid_tree_nonce() } fn get_bids_for( ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress ) -> Array { - self.option_round_dispatcher.get_bids_for(option_bidder_buyer) + self.option_round_dispatcher.get_account_bids(option_bidder_buyer) } fn get_refundable_balance_for( ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress ) -> u256 { - self.option_round_dispatcher.get_refundable_balance_for(option_bidder_buyer) + self.option_round_dispatcher.get_account_refundable_balance(option_bidder_buyer) } fn get_payout_balance_for( ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress ) -> u256 { - self.option_round_dispatcher.get_payout_balance_for(option_bidder_buyer) + self.option_round_dispatcher.get_account_payout_balance(option_bidder_buyer) } fn get_mintable_options_for( ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress ) -> u256 { - self.option_round_dispatcher.get_mintable_options_for(option_bidder_buyer) + self.option_round_dispatcher.get_account_mintable_options(option_bidder_buyer) } /// Other @@ -459,7 +463,7 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { } fn vault_address(ref self: OptionRoundFacade) -> ContractAddress { - self.option_round_dispatcher.vault_address() + self.option_round_dispatcher.get_vault_address() } fn contract_address(ref self: OptionRoundFacade) -> ContractAddress { @@ -485,7 +489,7 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { } fn get_total_options_available(ref self: OptionRoundFacade) -> u256 { - self.option_round_dispatcher.total_options_available() + self.option_round_dispatcher.get_options_available() } /// ERC20 functions diff --git a/src/tests/utils/facades/sanity_checks.cairo b/src/tests/utils/facades/sanity_checks.cairo index a5d4eb6d..918d00f1 100644 --- a/src/tests/utils/facades/sanity_checks.cairo +++ b/src/tests/utils/facades/sanity_checks.cairo @@ -63,12 +63,12 @@ fn exercise_options( fn place_bid(ref self: OptionRoundFacade, bid: Bid) -> Bid { let nonce: felt252 = (self.get_bidding_nonce_for(bid.owner) - 1).into(); let expected_id = poseidon::poseidon_hash_span(array![bid.owner.into(), nonce].span()); - assert(bid.id == expected_id, 'Invalid hash generated'); + assert(bid.bid_id == expected_id, 'Invalid hash generated'); bid } fn update_bid(ref option_round: OptionRoundFacade, old_bid: Bid, new_bid: Bid) -> Bid { - let storage_bid = option_round.get_bid_details(old_bid.id); + let storage_bid = option_round.get_bid_details(old_bid.bid_id); assert(new_bid == storage_bid, 'Bid Mismatch'); new_bid } diff --git a/src/tests/utils/facades/vault_facade.cairo b/src/tests/utils/facades/vault_facade.cairo index de01d0c6..eb6f7e7f 100644 --- a/src/tests/utils/facades/vault_facade.cairo +++ b/src/tests/utils/facades/vault_facade.cairo @@ -52,9 +52,7 @@ impl VaultFacadeImpl of VaultFacadeTrait { fn deposit(ref self: VaultFacade, amount: u256, liquidity_provider: ContractAddress) -> u256 { // @note Previously, we were setting the contract address to bystander set_contract_address(liquidity_provider); - let updated_unlocked_position = self - .vault_dispatcher - .deposit_liquidity(amount, liquidity_provider); + let updated_unlocked_position = self.vault_dispatcher.deposit(amount, liquidity_provider); sanity_checks::deposit(ref self, liquidity_provider, updated_unlocked_position) } @@ -83,12 +81,12 @@ impl VaultFacadeImpl of VaultFacadeTrait { ) { set_contract_address(liquidity_provider); let safe_vault = self.get_safe_dispatcher(); - safe_vault.withdraw_liquidity(amount).expect_err(error); + safe_vault.withdraw(amount).expect_err(error); } fn withdraw(ref self: VaultFacade, amount: u256, liquidity_provider: ContractAddress) -> u256 { set_contract_address(liquidity_provider); - let updated_unlocked_position = self.vault_dispatcher.withdraw_liquidity(amount); + let updated_unlocked_position = self.vault_dispatcher.withdraw(amount); sanity_checks::withdraw(ref self, liquidity_provider, updated_unlocked_position) } @@ -142,11 +140,9 @@ impl VaultFacadeImpl of VaultFacadeTrait { }; } - fn claim_queued_liquidity(ref self: VaultFacade, liquidity_provider: ContractAddress) -> u256 { - let expected_stashed_amount = self.get_lp_stashed_balance(liquidity_provider); - let actual_stashed_amount = self - .vault_dispatcher - .claim_queued_liquidity(liquidity_provider); + fn claim_queued_liquidity(ref self: VaultFacade, account: ContractAddress) -> u256 { + let expected_stashed_amount = self.get_lp_stashed_balance(account); + let actual_stashed_amount = self.vault_dispatcher.withdraw_stash(account); sanity_checks::claim_queued_liquidity( ref self, expected_stashed_amount, actual_stashed_amount ) @@ -197,7 +193,7 @@ impl VaultFacadeImpl of VaultFacadeTrait { let mut current_round = self.get_current_round(); // Settle the current round - let (total_payout, _) = self.vault_dispatcher.settle_option_round(); + let total_payout = self.vault_dispatcher.settle_round(); sanity_checks::settle_option_round(ref current_round, total_payout); @@ -210,7 +206,7 @@ impl VaultFacadeImpl of VaultFacadeTrait { fn settle_option_round_expect_error(ref self: VaultFacade, error: felt252) { set_contract_address(bystander()); let safe_vault = self.get_safe_dispatcher(); - safe_vault.settle_option_round().expect_err(error); + safe_vault.settle_round().expect_err(error); } /// Fossil @@ -219,53 +215,12 @@ impl VaultFacadeImpl of VaultFacadeTrait { } - // // Set the mock market aggregator data for the period of the current round - // fn set_market_aggregator_value(ref self: VaultFacade, avg_base_fee: u256) { - // set_contract_address(bystander()); - // let mut current_round = self.get_current_round(); - // let start_date = current_round.get_auction_start_date(); - // let end_date = current_round.get_option_settlement_date(); - // let market_aggregator = IMarketAggregatorSetterDispatcher { - // contract_address: self.get_market_aggregator(), - // }; - // let _ = market_aggregator.set_value_without_proof(start_date, end_date, avg_base_fee); - // } - - /// LP token related - - fn convert_position_to_lp_tokens( - ref self: VaultFacade, amount: u256, liquidity_provider: ContractAddress - ) { - set_contract_address(liquidity_provider); - self.vault_dispatcher.convert_position_to_lp_tokens(amount); - } - - fn convert_lp_tokens_to_position( - ref self: VaultFacade, source_round: u256, amount: u256, liquidity_provider: ContractAddress - ) { - set_contract_address(liquidity_provider); - self.vault_dispatcher.convert_lp_tokens_to_position(source_round, amount); - } - - fn convert_lp_tokens_to_newer_lp_tokens( - ref self: VaultFacade, - source_round: u256, - target_round: u256, - amount: u256, - liquidity_provider: ContractAddress - ) -> u256 { - set_contract_address(liquidity_provider); - self - .vault_dispatcher - .convert_lp_tokens_to_newer_lp_tokens(source_round, target_round, amount) - } - /// Reads /// /// Rounds fn get_current_round_id(ref self: VaultFacade) -> u256 { - self.vault_dispatcher.current_round_id() + self.vault_dispatcher.get_current_round_id() } fn get_option_round_address(ref self: VaultFacade, id: u256) -> ContractAddress { @@ -284,7 +239,7 @@ impl VaultFacadeImpl of VaultFacadeTrait { let contract_address = self.get_option_round_address(round_id); let round = IOptionRoundDispatcher { contract_address }; - round.unsold_liquidity() + round.get_unsold_liquidity() } /// Liquidity @@ -292,7 +247,7 @@ impl VaultFacadeImpl of VaultFacadeTrait { // For LPs fn get_lp_locked_balance(ref self: VaultFacade, liquidity_provider: ContractAddress) -> u256 { - self.vault_dispatcher.get_lp_locked_balance(liquidity_provider) + self.vault_dispatcher.get_account_locked_balance(liquidity_provider) } fn get_lp_locked_balances( @@ -312,11 +267,11 @@ impl VaultFacadeImpl of VaultFacadeTrait { } fn get_lp_queued_bps(ref self: VaultFacade, liquidity_provider: ContractAddress) -> u16 { - self.vault_dispatcher.get_lp_queued_bps(liquidity_provider) + self.vault_dispatcher.get_account_queued_bps(liquidity_provider) } fn get_lp_stashed_balance(ref self: VaultFacade, liquidity_provider: ContractAddress) -> u256 { - self.vault_dispatcher.get_lp_stashed_balance(liquidity_provider) + self.vault_dispatcher.get_account_stashed_balance(liquidity_provider) } @@ -353,7 +308,7 @@ impl VaultFacadeImpl of VaultFacadeTrait { } fn get_lp_unlocked_balance(ref self: VaultFacade, liquidity_provider: ContractAddress) -> u256 { - self.vault_dispatcher.get_lp_unlocked_balance(liquidity_provider) + self.vault_dispatcher.get_account_unlocked_balance(liquidity_provider) } fn get_lp_unlocked_balances( @@ -373,7 +328,7 @@ impl VaultFacadeImpl of VaultFacadeTrait { } fn get_lp_total_balance(ref self: VaultFacade, liquidity_provider: ContractAddress) -> u256 { - self.vault_dispatcher.get_lp_total_balance(liquidity_provider) + self.vault_dispatcher.get_account_total_balance(liquidity_provider) } @@ -381,8 +336,8 @@ impl VaultFacadeImpl of VaultFacadeTrait { fn get_lp_locked_and_unlocked_balance( ref self: VaultFacade, liquidity_provider: ContractAddress ) -> (u256, u256) { - let locked = self.vault_dispatcher.get_lp_locked_balance(liquidity_provider); - let unlocked = self.vault_dispatcher.get_lp_unlocked_balance(liquidity_provider); + let locked = self.vault_dispatcher.get_account_locked_balance(liquidity_provider); + let unlocked = self.vault_dispatcher.get_account_unlocked_balance(liquidity_provider); (locked, unlocked) } @@ -407,9 +362,9 @@ impl VaultFacadeImpl of VaultFacadeTrait { fn get_lp_locked_and_unlocked_and_stashed_balance( ref self: VaultFacade, liquidity_provider: ContractAddress ) -> (u256, u256, u256) { - let locked = self.vault_dispatcher.get_lp_locked_balance(liquidity_provider); - let unlocked = self.vault_dispatcher.get_lp_unlocked_balance(liquidity_provider); - let stashed = self.vault_dispatcher.get_lp_stashed_balance(liquidity_provider); + let locked = self.vault_dispatcher.get_account_locked_balance(liquidity_provider); + let unlocked = self.vault_dispatcher.get_account_unlocked_balance(liquidity_provider); + let stashed = self.vault_dispatcher.get_account_stashed_balance(liquidity_provider); (locked, unlocked, stashed) } @@ -436,36 +391,40 @@ impl VaultFacadeImpl of VaultFacadeTrait { // For Vault fn get_total_locked_balance(ref self: VaultFacade) -> u256 { - self.vault_dispatcher.get_total_locked_balance() + self.vault_dispatcher.get_vault_locked_balance() } // @note replace this with get_vault_unlocked_balance fn get_total_unlocked_balance(ref self: VaultFacade) -> u256 { - self.vault_dispatcher.get_total_unlocked_balance() + self.vault_dispatcher.get_vault_unlocked_balance() } fn get_total_stashed_balance(ref self: VaultFacade) -> u256 { - self.vault_dispatcher.get_total_stashed_balance() + self.vault_dispatcher.get_vault_stashed_balance() + } + + fn get_vault_queued_bps(ref self: VaultFacade) -> u16 { + self.vault_dispatcher.get_vault_queued_bps() } // @note replace this with get_vault_locked_and_unlocked_balance fn get_total_balance(ref self: VaultFacade) -> u256 { - self.vault_dispatcher.get_total_balance() + self.vault_dispatcher.get_vault_total_balance() } // @note replace this with get_vault_locked_and_unlocked_balances fn get_total_locked_and_unlocked_balance(ref self: VaultFacade) -> (u256, u256) { - let locked = self.vault_dispatcher.get_total_locked_balance(); - let unlocked = self.vault_dispatcher.get_total_unlocked_balance(); + let locked = self.vault_dispatcher.get_vault_locked_balance(); + let unlocked = self.vault_dispatcher.get_vault_unlocked_balance(); (locked, unlocked) } fn get_total_locked_and_unlocked_and_stashed_balance( ref self: VaultFacade ) -> (u256, u256, u256) { - let locked = self.vault_dispatcher.get_total_locked_balance(); - let unlocked = self.vault_dispatcher.get_total_unlocked_balance(); - let stashed = self.vault_dispatcher.get_total_stashed_balance(); + let locked = self.vault_dispatcher.get_vault_locked_balance(); + let unlocked = self.vault_dispatcher.get_vault_unlocked_balance(); + let stashed = self.vault_dispatcher.get_vault_stashed_balance(); (locked, unlocked, stashed) } @@ -478,18 +437,12 @@ impl VaultFacadeImpl of VaultFacadeTrait { } fn get_market_aggregator(ref self: VaultFacade) -> ContractAddress { - self.vault_dispatcher.get_market_aggregator() - } - - // Manager of the vault - // @note implementation not discussed yet - fn get_vault_manager(ref self: VaultFacade) -> ContractAddress { - self.vault_dispatcher.vault_manager() + self.vault_dispatcher.get_market_aggregator_address() } // Eth contract address fn get_eth_address(ref self: VaultFacade) -> ContractAddress { - self.vault_dispatcher.eth_address() + self.vault_dispatcher.get_eth_address() } fn get_auction_run_time(ref self: VaultFacade) -> u64 { diff --git a/src/tests/utils/helpers/event_helpers.cairo b/src/tests/utils/helpers/event_helpers.cairo index 33ccb1c7..2c17d725 100644 --- a/src/tests/utils/helpers/event_helpers.cairo +++ b/src/tests/utils/helpers/event_helpers.cairo @@ -50,14 +50,14 @@ fn assert_events_equal, +Drop>(actual: T, expected: T) { // Check AuctionStart emits correctly fn assert_event_auction_start( - option_round_address: ContractAddress, total_options_available: u256 + option_round_address: ContractAddress, starting_liquidity: u256, options_available: u256 ) { // @note Confirm this works (fix from discord), then work into other event assertions (should handle manual ones as well) // @note Reminder to clear event logs at the end of the accelerators match pop_log::(option_round_address) { Option::Some(e) => { let expected = OptionRound::Event::AuctionStarted( - OptionRound::AuctionStarted { total_options_available } + OptionRound::AuctionStarted { starting_liquidity, options_available } ); assert_events_equal(e, expected); }, @@ -66,14 +66,20 @@ fn assert_event_auction_start( } // Check AuctionAcceptedBid emits correctly -fn assert_event_auction_bid_accepted( - contract: ContractAddress, account: ContractAddress, amount: u256, price: u256, nonce: u32 +fn assert_event_auction_bid_placed( + contract: ContractAddress, + account: ContractAddress, + bid_id: felt252, + amount: u256, + price: u256, + bid_tree_nonce_now: u64 ) { match pop_log::(contract) { Option::Some(e) => { - let expected = OptionRound::Event::BidAccepted( - OptionRound::BidAccepted { account, amount, price, nonce } + let expected = OptionRound::Event::BidPlaced( + OptionRound::BidPlaced { account, bid_id, amount, price, bid_tree_nonce_now } ); + //println!("expected:\n{}\n{}\n{}\n{}\n{}", Into::::into(account), bid_id, amount, price, bid_tree_nonce_now); assert_events_equal(e, expected); }, Option::None => { panic(array!['Could not find event']); }, @@ -83,18 +89,14 @@ fn assert_event_auction_bid_accepted( fn assert_event_auction_bid_updated( contract: ContractAddress, account: ContractAddress, - old_amount: u256, - old_price: u256, - new_amount: u256, - new_price: u256, - id: felt252 + bid_id: felt252, + price_increase: u256, + bid_tree_nonce_now: u64, ) { match pop_log::(contract) { Option::Some(e) => { let expected = OptionRound::Event::BidUpdated( - OptionRound::BidUpdated { - id, account, old_amount, old_price, new_amount, new_price - } + OptionRound::BidUpdated { account, bid_id, price_increase, bid_tree_nonce_now } ); assert_events_equal(e, expected); }, @@ -119,12 +121,15 @@ fn assert_event_auction_bid_updated( // Check AuctionEnd emits correctly fn assert_event_auction_end( - option_round_address: ContractAddress, clearing_price: u256, total_options_sold: u256 + option_round_address: ContractAddress, + options_sold: u256, + clearing_price: u256, + unsold_liquidity: u256 ) { match pop_log::(option_round_address) { Option::Some(e) => { let expected = OptionRound::Event::AuctionEnded( - OptionRound::AuctionEnded { clearing_price, total_options_sold } + OptionRound::AuctionEnded { options_sold, clearing_price, unsold_liquidity } ); assert_events_equal(e, expected); }, @@ -135,17 +140,12 @@ fn assert_event_auction_end( // Check OptionSettle emits correctly // @dev Settlment price is the price determining the payout for the round fn assert_event_option_settle( - option_round_address: ContractAddress, - total_payout: u256, - payout_per_option: u256, - settlement_price: u256 + option_round_address: ContractAddress, settlement_price: u256, payout_per_option: u256, ) { match pop_log::(option_round_address) { Option::Some(e) => { let expected = OptionRound::Event::OptionRoundSettled( - OptionRound::OptionRoundSettled { - total_payout, payout_per_option, settlement_price - } + OptionRound::OptionRoundSettled { settlement_price, payout_per_option } ); assert_events_equal(e, expected); }, @@ -155,12 +155,12 @@ fn assert_event_option_settle( // Check UnusedBidsRefunded emits correctly fn assert_event_unused_bids_refunded( - contract: ContractAddress, account: ContractAddress, amount: u256 + contract: ContractAddress, account: ContractAddress, refunded_amount: u256 ) { match pop_log::(contract) { Option::Some(e) => { let expected = OptionRound::Event::UnusedBidsRefunded( - OptionRound::UnusedBidsRefunded { account, amount } + OptionRound::UnusedBidsRefunded { account, refunded_amount } ); assert_events_equal(e, expected); }, @@ -169,7 +169,7 @@ fn assert_event_unused_bids_refunded( } fn assert_event_options_tokenized( - contract: ContractAddress, account: ContractAddress, amount: u256 + contract: ContractAddress, account: ContractAddress, minted_amount: u256 ) { // We pop here twice since the method fires a ERC20 transfer event before the OptionsTokenized event match pop_log::(contract) { @@ -177,7 +177,7 @@ fn assert_event_options_tokenized( match pop_log::(contract) { Option::Some(e) => { let expected = OptionRound::Event::OptionsMinted( - OptionRound::OptionsMinted { account, amount } + OptionRound::OptionsMinted { account, minted_amount } ); assert_events_equal(e, expected); }, @@ -189,12 +189,15 @@ fn assert_event_options_tokenized( } // Check OptionsExercised emits correctly fn assert_event_options_exercised( - contract: ContractAddress, account: ContractAddress, num_options: u256, amount: u256 + contract: ContractAddress, + account: ContractAddress, + number_of_options: u256, + exercised_amount: u256 ) { match pop_log::(contract) { Option::Some(e) => { let expected = OptionRound::Event::OptionsExercised( - OptionRound::OptionsExercised { account, num_options, amount } + OptionRound::OptionsExercised { account, number_of_options, exercised_amount } ); assert_events_equal(e, expected); }, @@ -302,8 +305,8 @@ fn assert_event_queued_liquidity_collected( ) { match pop_log::(vault) { Option::Some(e) => { - let expected = Vault::Event::QueuedLiquidityCollected( - Vault::QueuedLiquidityCollected { account, amount, vault_stashed_balance_now } + let expected = Vault::Event::StashWithdrawn( + Vault::StashWithdrawn { account, amount, vault_stashed_balance_now } ); assert_events_equal(e, expected); @@ -317,14 +320,14 @@ fn assert_event_withdrawal_queued( vault: ContractAddress, account: ContractAddress, bps: u16, - account_queued_amount_now: u256, - vault_queued_amount_now: u256 + account_queued_liquidity_now: u256, + vault_queued_liquidity_now: u256 ) { match pop_log::(vault) { Option::Some(e) => { let expected = Vault::Event::WithdrawalQueued( Vault::WithdrawalQueued { - account, bps, account_queued_amount_now, vault_queued_amount_now + account, bps, account_queued_liquidity_now, vault_queued_liquidity_now } ); diff --git a/src/tests/utils/helpers/setup.cairo b/src/tests/utils/helpers/setup.cairo index f87f6322..99655867 100644 --- a/src/tests/utils/helpers/setup.cairo +++ b/src/tests/utils/helpers/setup.cairo @@ -43,10 +43,7 @@ use pitch_lake_starknet::{ utils::{ lib::{ structs::{OptionRoundParams}, - test_accounts::{ - weth_owner, vault_manager, liquidity_providers_get, option_bidders_get, - bystander - }, + test_accounts::{weth_owner, liquidity_providers_get, option_bidders_get, bystander}, variables::{week_duration, decimals}, }, helpers::{ @@ -118,7 +115,6 @@ fn deploy_vault( calldata.append_serde(1000); calldata.append_serde(1000); calldata.append_serde(eth_address); - calldata.append_serde(vault_manager()); calldata.append_serde(vault_type); calldata.append_serde(mk_agg_address); // needed ? calldata.append_serde(OptionRound::TEST_CLASS_HASH); @@ -209,7 +205,7 @@ fn setup_facade_vault_type(vault_type: VaultType) -> (VaultFacade, MarketAggrega // Each round will settle with 10 gwei TWAP // Each round will use 10 gwei as the latest TWAP when round starts let current_round_address = vault_dispatcher - .get_round_address(vault_dispatcher.current_round_id()); + .get_round_address(vault_dispatcher.get_current_round_id()); let current_round = IOptionRoundDispatcher { contract_address: current_round_address }; let mut TWAP_end = current_round.get_auction_start_date(); let mut TWAP_start = TWAP_end - Vault::TWAP_DURATION; @@ -244,7 +240,7 @@ fn setup_facade_vault_type(vault_type: VaultType) -> (VaultFacade, MarketAggrega //Supply and approve option_bidders let current_round_address = vault_dispatcher - .get_round_address(vault_dispatcher.current_round_id()); + .get_round_address(vault_dispatcher.get_current_round_id()); eth_supply_and_approve_all_bidders(current_round_address, eth_dispatcher.contract_address); // Supply eth to test accounts and approve option round 1 to spend ob eth @@ -300,7 +296,7 @@ fn deploy_custom_option_round( .expect('Deploy Custom Round Failed'); let vault_dispatcher = IVaultDispatcher { contract_address: vault_address }; - eth_supply_and_approve_all_bidders(contract_address, vault_dispatcher.eth_address()); + eth_supply_and_approve_all_bidders(contract_address, vault_dispatcher.get_eth_address()); // Clear the event log clear_event_logs(array![contract_address]); diff --git a/src/tests/vault/liquidity_providers/deposit_tests.cairo b/src/tests/vault/liquidity_providers/deposit_tests.cairo index 98dd13a7..6e3eb149 100644 --- a/src/tests/vault/liquidity_providers/deposit_tests.cairo +++ b/src/tests/vault/liquidity_providers/deposit_tests.cairo @@ -18,7 +18,7 @@ use pitch_lake_starknet::{ assert_event_transfer, pop_log, assert_no_events_left, assert_event_option_settle, assert_event_option_round_deployed, assert_event_vault_deposit, assert_event_auction_start, - assert_event_auction_bid_accepted, assert_event_auction_end, + assert_event_auction_bid_placed, assert_event_auction_end, assert_event_vault_withdrawal, assert_event_unused_bids_refunded, assert_event_options_exercised, }, diff --git a/src/tests/vault/liquidity_providers/withdrawal_queue_tests.cairo b/src/tests/vault/liquidity_providers/withdrawal_queue_tests.cairo index faed48f0..a14a8bc2 100644 --- a/src/tests/vault/liquidity_providers/withdrawal_queue_tests.cairo +++ b/src/tests/vault/liquidity_providers/withdrawal_queue_tests.cairo @@ -368,6 +368,109 @@ fn test_queued_amount_is_0_after_round_settles() { assert_eq!(queued_after_settle.span(), array![0, 0, 0].span()); } +// Test vault queued bps is accurate +#[test] +#[available_gas(300000000)] +fn test_vault_queued_bps_is_accurate() { + let (mut vault, _) = setup_facade(); + let mut current_round = vault.get_current_round(); + let liquidity_providers = liquidity_providers_get(3).span(); + let deposit_amounts = array![100 * decimals(), 200 * decimals(), 300 * decimals()].span(); + accelerate_to_auctioning_custom(ref vault, liquidity_providers, deposit_amounts); + accelerate_to_running_custom( + ref vault, + array![option_bidder_buyer_1()].span(), + array![current_round.get_total_options_available() / 2].span(), + array![current_round.get_reserve_price()].span() + ); + + let bps_multi = array![3333, 6666, 9999].span(); + vault.queue_multiple_withdrawals(liquidity_providers, bps_multi); + + let vault_bps = vault.get_vault_queued_bps(); + let total_queued = { + let mut total = 0; + let mut i = 0; + while i < bps_multi + .len() { + let deposit_amount = *deposit_amounts.at(i); + let bps = *bps_multi.at(i); + total += (deposit_amount * bps.into()) / BPS.into(); + + i += 1; + }; + total + }; + let expected_vault_bps = (total_queued * BPS.into()) / current_round.starting_liquidity(); + + assert_eq!(vault_bps.into(), expected_vault_bps); +} + +// Test vault queued bps changes correctly +#[test] +#[available_gas(300000000)] +fn test_vault_queued_bps_changes_correctly() { + let (mut vault, _) = setup_facade(); + let mut current_round = vault.get_current_round(); + let liquidity_providers = liquidity_providers_get(3).span(); + let deposit_amounts = array![100 * decimals(), 200 * decimals(), 300 * decimals()].span(); + accelerate_to_auctioning_custom(ref vault, liquidity_providers, deposit_amounts); + accelerate_to_running_custom( + ref vault, + array![option_bidder_buyer_1()].span(), + array![current_round.get_total_options_available() / 2].span(), + array![current_round.get_reserve_price()].span() + ); + + let bps_multi1 = array![3333, 6666, 9999].span(); + vault.queue_multiple_withdrawals(liquidity_providers, bps_multi1); + + let bps_multi2 = array![123, 456, 789].span(); + vault.queue_multiple_withdrawals(liquidity_providers, bps_multi2); + let vault_bps2 = vault.get_vault_queued_bps(); + + let total_queued = { + let mut total = 0; + let mut i = 0; + while i < bps_multi2 + .len() { + let deposit_amount = *deposit_amounts.at(i); + let bps = *bps_multi2.at(i); + total += (deposit_amount * bps.into()) / BPS.into(); + + i += 1; + }; + total + }; + let expected_vault_bps = (total_queued * BPS.into()) / current_round.starting_liquidity(); + + assert_eq!(vault_bps2.into(), expected_vault_bps); + let bps_multi3 = array![5555, 6666, 8888].span(); + vault.queue_multiple_withdrawals(liquidity_providers, bps_multi3); + let vault_bps3 = vault.get_vault_queued_bps(); + + let total_queued = { + let mut total = 0; + let mut i = 0; + while i < bps_multi3 + .len() { + let deposit_amount = *deposit_amounts.at(i); + let bps = *bps_multi3.at(i); + total += (deposit_amount * bps.into()) / BPS.into(); + + i += 1; + }; + total + }; + let expected_vault_bps = (total_queued * BPS.into()) / current_round.starting_liquidity(); + + assert_eq!(vault_bps3.into(), expected_vault_bps); +} + + +// @note add test that vault queued bps is 0 after settle + +// @note add test that vault queued bps resets if user changes their bps +/- // Test queuing 0 puts nothing in stash #[test] diff --git a/src/tests/vault/state_transition/auction_end_tests.cairo b/src/tests/vault/state_transition/auction_end_tests.cairo index f5738348..5569fe5b 100644 --- a/src/tests/vault/state_transition/auction_end_tests.cairo +++ b/src/tests/vault/state_transition/auction_end_tests.cairo @@ -126,7 +126,7 @@ fn test_auction_ended_option_round_event() { // Check the event emits correctly assert(clearing_price > 0, 'clearing price shd be > 0'); assert_event_auction_end( - current_round.contract_address(), clearing_price, total_options_sold + current_round.contract_address(), total_options_sold, clearing_price, 0 ); accelerate_to_settled(ref vault, current_round.get_strike_price() * 2); diff --git a/src/tests/vault/state_transition/auction_start_tests.cairo b/src/tests/vault/state_transition/auction_start_tests.cairo index 3c980d90..6240a9e4 100644 --- a/src/tests/vault/state_transition/auction_start_tests.cairo +++ b/src/tests/vault/state_transition/auction_start_tests.cairo @@ -101,9 +101,12 @@ fn test_auction_started_option_round_event() { while rounds_to_run > 0_u32 { let mut current_round = vault.get_current_round(); let total_options_available = accelerate_to_auctioning(ref vault); + let starting_liquidity = current_round.starting_liquidity(); // Check the event emits correctly - assert_event_auction_start(current_round.contract_address(), total_options_available); + assert_event_auction_start( + current_round.contract_address(), starting_liquidity, total_options_available + ); accelerate_to_running(ref vault); accelerate_to_settled(ref vault, 2 * current_round.get_strike_price()); diff --git a/src/tests/vault/state_transition/option_settle_tests.cairo b/src/tests/vault/state_transition/option_settle_tests.cairo index 4823cb75..d9710c7b 100644 --- a/src/tests/vault/state_transition/option_settle_tests.cairo +++ b/src/tests/vault/state_transition/option_settle_tests.cairo @@ -109,12 +109,10 @@ fn test_option_round_settled_event() { let settlement_price = round.get_strike_price() + rounds_to_run.into(); clear_event_logs(array![round.contract_address()]); let total_payout = accelerate_to_settled(ref vault, settlement_price); - let individual_payout = total_payout / round.total_options_sold(); + let payout_per_option = total_payout / round.total_options_sold(); // Check the event emits correctly - assert_event_option_settle( - round.contract_address(), total_payout, individual_payout, settlement_price - ); + assert_event_option_settle(round.contract_address(), settlement_price, payout_per_option); rounds_to_run -= 1; } diff --git a/src/types.cairo b/src/types.cairo index cd5af5d1..1b266a9d 100644 --- a/src/types.cairo +++ b/src/types.cairo @@ -24,7 +24,7 @@ mod Errors { const BidAmountZero: felt252 = 'Bid amount cannot be 0'; const BidBelowReservePrice: felt252 = 'Bid price below reserve price'; const CallerNotBidOwner: felt252 = 'Caller is not bid owner'; - const BidCannotBeDecreased: felt252 = 'A bid cannot decrease'; + const BidMustBeIncreased: felt252 = 'Bid updates must increase price'; // Refunding bids & tokenizing options const AuctionNotEnded: felt252 = 'Auction has not ended yet'; const OptionRoundNotSettled: felt252 = 'Option round not settled yet'; @@ -81,11 +81,11 @@ struct OptionRoundConstructorParams { // The struct for a bid placed in a round's auction #[derive(Copy, Drop, Serde, starknet::Store, PartialEq, Display)] struct Bid { - id: felt252, - nonce: u64, + bid_id: felt252, owner: ContractAddress, amount: u256, price: u256, + tree_nonce: u64, } // Allows Bids to be sorted using >, >=, <, <= @@ -99,8 +99,8 @@ impl BidPartialOrdTrait of PartialOrd { } else if lhs.price > rhs.price { false } else { - assert(lhs.nonce != rhs.nonce, Errors::BidsShouldNotHaveSameTreeNonce); - lhs.nonce > rhs.nonce + assert(lhs.tree_nonce != rhs.tree_nonce, Errors::BidsShouldNotHaveSameTreeNonce); + lhs.tree_nonce > rhs.tree_nonce } } @@ -111,8 +111,8 @@ impl BidPartialOrdTrait of PartialOrd { } else if lhs.price < rhs.price { false } else { - assert(lhs.nonce != rhs.nonce, Errors::BidsShouldNotHaveSameTreeNonce); - lhs.nonce < rhs.nonce + assert(lhs.tree_nonce != rhs.tree_nonce, Errors::BidsShouldNotHaveSameTreeNonce); + lhs.tree_nonce < rhs.tree_nonce } } @@ -133,12 +133,12 @@ impl BidPartialOrdTrait of PartialOrd { impl BidDisplay of Display { fn fmt(self: @Bid, ref f: Formatter) -> Result<(), Error> { let str: ByteArray = format!( - "ID:{}\nNonce:{}\nOwner:{}\nAmount:{}\n Price:{}", - *self.id, - *self.nonce, + "Bid ID:{}\nOwner:{}\nAmount:{}\nPrice:{}\nTree Nonce:{}", + *self.bid_id, Into::::into(*self.owner), *self.amount, *self.price, + *self.tree_nonce, ); f.buffer.append(@str); Result::Ok(()) diff --git a/src/vault/contract.cairo b/src/vault/contract.cairo index 5d3f1bd7..2dc0f3fd 100644 --- a/src/vault/contract.cairo +++ b/src/vault/contract.cairo @@ -32,6 +32,7 @@ mod Vault { // @withdraw_checkpoints: Withdraw checkpoints: (liquidity_provider) -> round_id // @total_unlocked_balance: Total unlocked liquidity // @total_locked_balance: Total locked liquidity + // @total_stashed_balance: Total stashed liquidity // @premiums_collected:The amount of premiums a liquidity provider collects from each round: (liquidity_provider, round_id) -> collected_amount // @current_option_round_id: The id of the current option round // @round_addresses: Mapping of round id -> round address @@ -40,32 +41,29 @@ mod Vault { #[storage] struct Storage { /// - vault_type: VaultType, - vault_manager: ContractAddress, - market_aggregator: ContractAddress, + market_aggregator_address: ContractAddress, eth_address: ContractAddress, option_round_class_hash: ClassHash, + round_addresses: LegacyMap, /// + vault_type: VaultType, round_transition_period: u64, auction_run_time: u64, option_run_time: u64, /// current_round_id: u256, - round_addresses: LegacyMap, /// positions: LegacyMap<(ContractAddress, u256), u256>, /// - total_locked_balance: u256, - total_unlocked_balance: u256, - total_stashed_balance: u256, - /// - withdraw_checkpoints: LegacyMap, - queue_checkpoints: LegacyMap, + vault_locked_balance: u256, + vault_unlocked_balance: u256, + vault_stashed_balance: u256, /// - premiums_moved: LegacyMap<(ContractAddress, u256), bool>, + position_checkpoints: LegacyMap, + stash_checkpoints: LegacyMap, + is_premium_moved: LegacyMap<(ContractAddress, u256), bool>, /// - round_queued_liquidity: LegacyMap, - user_queued_liquidity: LegacyMap<(ContractAddress, u256), (u16, u256)>, + queued_liquidity: LegacyMap<(ContractAddress, u256), u256>, } // ************************************************************************* @@ -78,15 +76,13 @@ mod Vault { auction_run_time: u64, option_run_time: u64, eth_address: ContractAddress, - vault_manager: ContractAddress, vault_type: VaultType, - market_aggregator: ContractAddress, + market_aggregator_address: ContractAddress, option_round_class_hash: ClassHash, ) { self.eth_address.write(eth_address); - self.vault_manager.write(vault_manager); self.vault_type.write(vault_type); - self.market_aggregator.write(market_aggregator); + self.market_aggregator_address.write(market_aggregator_address); self.option_round_class_hash.write(option_round_class_hash); self.round_transition_period.write(round_transition_period); self.auction_run_time.write(auction_run_time); @@ -104,10 +100,15 @@ mod Vault { Deposit: Deposit, Withdrawal: Withdrawal, WithdrawalQueued: WithdrawalQueued, - QueuedLiquidityCollected: QueuedLiquidityCollected, + StashWithdrawn: StashWithdrawn, OptionRoundDeployed: OptionRoundDeployed, } + // @dev Emitted when a deposit is made for an account + // @member account: The account the deposit was made for + // @member amount: The amount deposited + // @member: account_unlocked_balance_now: The account's unlocked balance after the deposit + // @member: vault_unlocked_balance_now: The vault's unlocked balance after the deposit #[derive(Drop, starknet::Event, PartialEq)] struct Deposit { #[key] @@ -117,6 +118,11 @@ mod Vault { vault_unlocked_balance_now: u256, } + // @dev Emitted when an account makes a withdrawal + // @member account: The account that made the withdrawal + // @member amount: The amount withdrawn + // @member account_unlocked_balance_now: The account's unlocked balance after the withdrawal + // @member vault_unlocked_balance_now: The vault's unlocked balance after the withdrawal #[derive(Drop, starknet::Event, PartialEq)] struct Withdrawal { #[key] @@ -126,26 +132,41 @@ mod Vault { vault_unlocked_balance_now: u256, } - // @dev Emitted when a liquidity provider queues a withdrawal + // @dev Emitted when an account queues a withdrawal + // @member account: The account that queued the withdrawal + // @member bps: The BPS % of the account's remaining liquidity to stash + // @member account_queued_liquidity_now: The account's starting liquidity queued after the withdrawal + // @member vault_queued_liquidity_now: The vault's starting liquidity queued after the withdrawal #[derive(Drop, starknet::Event, PartialEq)] struct WithdrawalQueued { #[key] account: ContractAddress, bps: u16, - account_queued_amount_now: u256, - vault_queued_amount_now: u256, + account_queued_liquidity_now: u256, + vault_queued_liquidity_now: u256, } - // @dev Emitted when a liquidity provider claims stashed liquidity + // @dev Emitted when an account withdraws their stashed liquidity + // @member account: The account that withdrew the stashed liquidity + // @member amount: The amount withdrawn + // @member vault_stashed_balance_now: The vault's stashed balance after the withdrawal #[derive(Drop, starknet::Event, PartialEq)] - struct QueuedLiquidityCollected { + struct StashWithdrawn { #[key] account: ContractAddress, amount: u256, vault_stashed_balance_now: u256, } - + // @dev Emitted when a new option round is deployed + // @member round_id: The id of the deployed round + // @member address: The address of the deployed round + // @member reserve_price: The reserve price for the deployed round + // @member strike_price: The strike price for the deployed round + // @member cap_level: The cap level for the deployed round + // @member auction_start_date: The auction start date for the deployed round + // @member auction_end_date: The auction end date for the deployed round + // @member option_settlement_date: The option settlement date for the deployed round #[derive(Drop, starknet::Event, PartialEq)] struct OptionRoundDeployed { round_id: u256, @@ -167,21 +188,17 @@ mod Vault { // READS // *********************************** - /// Other - - fn vault_manager(self: @ContractState) -> ContractAddress { - self.vault_manager.read() - } + /// - fn vault_type(self: @ContractState) -> VaultType { + fn get_vault_type(self: @ContractState) -> VaultType { self.vault_type.read() } - fn get_market_aggregator(self: @ContractState) -> ContractAddress { - self.market_aggregator.read() + fn get_market_aggregator_address(self: @ContractState) -> ContractAddress { + self.market_aggregator_address.read() } - fn eth_address(self: @ContractState) -> ContractAddress { + fn get_eth_address(self: @ContractState) -> ContractAddress { self.eth_address.read() } @@ -197,9 +214,7 @@ mod Vault { self.round_transition_period.read() } - /// Rounds /// - - fn current_round_id(self: @ContractState) -> u256 { + fn get_current_round_id(self: @ContractState) -> u256 { self.current_round_id.read() } @@ -207,247 +222,136 @@ mod Vault { self.round_addresses.read(option_round_id) } - /// Liquidity /// + /// Liquidity - fn get_total_locked_balance(self: @ContractState) -> u256 { - self.total_locked_balance.read() + fn get_vault_total_balance(self: @ContractState) -> u256 { + self.get_vault_locked_balance() + + self.get_vault_unlocked_balance() + + self.get_vault_stashed_balance() } - fn get_total_unlocked_balance(self: @ContractState) -> u256 { - self.total_unlocked_balance.read() + fn get_vault_locked_balance(self: @ContractState) -> u256 { + self.vault_locked_balance.read() } - fn get_total_queued_balance(self: @ContractState) -> u256 { - self.round_queued_liquidity.read(self.current_round_id.read()) + fn get_vault_unlocked_balance(self: @ContractState) -> u256 { + self.vault_unlocked_balance.read() } - fn get_total_stashed_balance(self: @ContractState) -> u256 { - self.total_stashed_balance.read() + fn get_vault_stashed_balance(self: @ContractState) -> u256 { + self.vault_stashed_balance.read() } - fn get_total_balance(self: @ContractState,) -> u256 { - self.get_total_locked_balance() - + self.get_total_unlocked_balance() - + self.get_total_stashed_balance() - } + fn get_vault_queued_bps(self: @ContractState) -> u16 { + // @dev Get the liquidity locked at the start of the current round + let total_liq = self + .get_round_dispatcher(self.current_round_id.read()) + .get_starting_liquidity(); + // @dev Get the amount queued + let queued_liq = self + .queued_liquidity + .read((get_contract_address(), self.current_round_id.read())); - // @dev Get the amount of liquidity an account locked at the start of the current round - fn get_lp_starting_balance(self: @ContractState, account: ContractAddress) -> u256 { - self.get_realized_deposit_for_current_round(account) + // @dev Calculate the queued BPS %, avoiding division by 0 + match total_liq.is_zero() { + true => 0, + false => self.divide_into_bps(queued_liq, total_liq) + } } - - // @dev Get the amount of liquidity an account has locked at the current time - fn get_lp_locked_balance(self: @ContractState, account: ContractAddress) -> u256 { - let current_round_id = self.current_round_id.read(); - let current_round = self.get_round_dispatcher(current_round_id); - let state = current_round.get_state(); - match state { - // @dev If the current round is Open, all liquidity is unlocked - OptionRoundState::Open => { 0 }, - // @dev If the current round is Auctioning | Running, the account's locked balance is proportional - // to the the liquidity they locked at the start of the round - // @dev - _ => { - let round_starting_liq = current_round.starting_liquidity(); - let total_locked_liq = self.total_locked_balance.read(); - let current_round_deposit = self - .get_realized_deposit_for_current_round(account); - - (total_locked_liq * current_round_deposit) / round_starting_liq - }, - } + fn get_account_total_balance(self: @ContractState, account: ContractAddress) -> u256 { + self.get_account_locked_balance(account) + + self.get_account_unlocked_balance(account) + + self.get_account_stashed_balance(account) } - // @dev Get the amount of liquidity an account has unlocked at the current time - fn get_lp_unlocked_balance(self: @ContractState, account: ContractAddress) -> u256 { - // @dev Get the account's calculated current round deposit, and upcoming round deposit - let current_round_id = self.current_round_id.read(); - let current_round = self.get_round_dispatcher(current_round_id); - let current_round_deposit = self.get_realized_deposit_for_current_round(account); - let upcoming_round_deposit = self.positions.read((account, current_round_id + 1)); + fn get_account_locked_balance(self: @ContractState, account: ContractAddress) -> u256 { + // @dev Get the liquidity locked at the start of the current round + let total_liq = self + .get_round_dispatcher(self.current_round_id.read()) + .get_starting_liquidity(); + // @dev Get the liquidity the account locked at the start of the current round + let account_liq = self.get_realized_deposit_for_current_round(account); + // @dev Get the liquidity currently locked + let locked_liq = self.vault_locked_balance.read(); - let state = current_round.get_state(); - match state { - // @dev If Open, the current round's deposit is unlocked and there is no upcoming round - OptionRoundState::Open => { current_round_deposit }, - // @dev If Auctioning | Running, the current round's deposit is locked, but the upcoming round's - // deposit and any premiums and unsold liquidity from the current round are unlocked - _ => { - // @dev Get the amount of premium and unsold liquidity the account has unlocked; 0 - // if Auctioning or the liquiidty was moved as a deposit for the upcoming round - let premiums_and_unsold_liq = self - .get_liquidity_unlocked_for_account_in_round( - account, current_round_deposit, current_round_id - ); - - upcoming_round_deposit + premiums_and_unsold_liq - }, + // @dev Calculate how much belongs to the account, avoiding division by 0 + match total_liq.is_zero() { + true => 0, + false => (locked_liq * account_liq) / total_liq } } - // @dev Get how much liquidity is queued for stashing in the current round - // @return The BPS percentage being queued for - fn get_lp_queued_bps(self: @ContractState, account: ContractAddress) -> u16 { - let (bps, _) = self.user_queued_liquidity.read((account, self.current_round_id.read())); - bps - } + fn get_account_unlocked_balance(self: @ContractState, account: ContractAddress) -> u256 { + // @dev Get the account's refreshed upcoming round deposit + let (_, upcoming_round_deposit) = self.get_refreshed_position(account); + upcoming_round_deposit + } - // @dev Get the stashed liquidity an account can collect - fn get_lp_stashed_balance(self: @ContractState, account: ContractAddress) -> u256 { + fn get_account_stashed_balance(self: @ContractState, account: ContractAddress) -> u256 { // @dev Sum the account's stashed amounts for each round after the last collection round - // @dev Sum to the previous round because the current round will be on-going + // to the previous round + let current_round_id = self.current_round_id.read(); + let mut i = self.stash_checkpoints.read(account) + 1; let mut total = 0; - let mut i = self.queue_checkpoints.read(account) + 1; - let current_round_id = self.current_round_id(); while i < current_round_id { - // @dev Get the account's remaining liquidity that was stashed - let (_, account_queued_liq) = self.user_queued_liquidity.read((account, i)); - if account_queued_liq.is_non_zero() { - let (round_starting_liq, round_remaining_liq, _) = self.get_round_outcome(i); - - total += (round_remaining_liq * account_queued_liq) / round_starting_liq; + // @dev Get the liquidity the account queued + let queued_liq = self.queued_liquidity.read((account, i)); + if queued_liq.is_non_zero() { + // @dev Get the round's starting and remaining liquidity + let (starting_liq, remaining_liq, _) = self.get_round_outcome(i); + // @dev Calculate the amount of remaining liquidity that was stashed for the account, + // avoiding division by 0 + if starting_liq.is_non_zero() { + let stashed_liq = (remaining_liq * queued_liq) / starting_liq; + total += stashed_liq; + } } + i += 1; }; total } - fn get_lp_total_balance(self: @ContractState, account: ContractAddress) -> u256 { - self.get_lp_locked_balance(account) - + self.get_lp_unlocked_balance(account) - + self.get_lp_stashed_balance(account) - } - - // *********************************** - // WRITES - // *********************************** - - /// State Transition /// - - // FOSSIL - // Update the current option round's parameters if there are newer values - // @note Return to this during fossil integration - fn update_round_params(ref self: ContractState) { - let current_round_id = self.current_round_id(); - let current_round = self.get_round_dispatcher(current_round_id); - - let cap_level = self.fetch_cap_level_for_round(current_round_id); - let reserve_price = self.fetch_reserve_price_for_round(current_round_id); - // @note needs to be updated to most recent set range - let twap_end = current_round.get_auction_start_date(); - let twap_start = twap_end - TWAP_DURATION; - let current_avg_basefee = self.fetch_TWAP_for_time_period(twap_start, twap_end); - let volatility = self.fetch_volatility_for_round(current_round_id); - let strike_price = calculate_strike_price( - self.vault_type.read(), current_avg_basefee, volatility - ); - - current_round.update_round_params(reserve_price, cap_level, strike_price); - } - - // @return The total options available in the auction - fn start_auction(ref self: ContractState) -> u256 { - // @dev Start the current round's auction + fn get_account_queued_bps(self: @ContractState, account: ContractAddress) -> u16 { + // @dev Get the liquidity locked at the start of the current round let current_round_id = self.current_round_id.read(); - let current_round = self.get_round_dispatcher(current_round_id); - let unlocked_liquidity = self.get_total_unlocked_balance(); - - // @dev All unlocked liquidity becomes locked - self.total_unlocked_balance.write(0); - self.total_locked_balance.write(unlocked_liquidity); - - current_round.start_auction(unlocked_liquidity) - } - - // @return The clearing price of the auction and number of options that sold - fn end_auction(ref self: ContractState) -> (u256, u256) { - // @dev End the current round's auction - let current_round = self.get_round_dispatcher(self.current_round_id.read()); - let (clearing_price, options_sold) = current_round.end_auction(); - - // @dev Premiums earned are unlocked - let mut unlocked_liquidity = self.get_total_unlocked_balance(); - let earned_premiums = clearing_price * options_sold; - unlocked_liquidity += earned_premiums; - - // @dev If there is unsold_liquidity, it becomes unlocked - let unsold_liquidity = current_round.unsold_liquidity(); - if unsold_liquidity.is_non_zero() { - unlocked_liquidity += unsold_liquidity; - self.total_locked_balance.write(self.get_total_locked_balance() - unsold_liquidity); + let total_liq = self.get_realized_deposit_for_current_round(account); + // @dev Get the amount the account queued + let queued_liq = self.queued_liquidity.read((account, current_round_id)); + + // @dev Calculate the BPS % of the starting liquidity that is queued, avoiding division by 0 + match total_liq.is_zero() { + true => 0, + false => self.divide_into_bps(queued_liq, total_liq) } - - self.total_unlocked_balance.write(unlocked_liquidity); - - (clearing_price, options_sold) } - // @return The total payout and settlement price for the option round - fn settle_option_round(ref self: ContractState) -> (u256, u256) { - // @dev Settle the round - let current_round_id = self.current_round_id.read(); - let current_round = self.get_round_dispatcher(current_round_id); - // FOSSIL - let to = current_round.get_option_settlement_date(); - let from = to - TWAP_DURATION; - let settlement_price = self.fetch_TWAP_for_time_period(from, to); - let (total_payout, settlement_price) = current_round - .settle_option_round(settlement_price); - - // @dev The remaining liquidity becomes unlocked except for the stashed amount - let starting_liq = current_round.starting_liquidity(); - let unsold_liq = current_round.unsold_liquidity(); - let remaining_liq = starting_liq - unsold_liq - total_payout; - - // @dev Stashed liquidity - let starting_liq_queued = self.round_queued_liquidity.read(current_round_id); - let remaining_liq_stashed = (remaining_liq * starting_liq_queued) / starting_liq; - let remaining_liq_not_stashed = remaining_liq - remaining_liq_stashed; - - // @dev All of the remaining locked liquidity becomes unlocked - self.total_locked_balance.write(0); - self - .total_stashed_balance - .write(self.get_total_stashed_balance() + remaining_liq_stashed); - self - .total_unlocked_balance - .write(self.get_total_unlocked_balance() + remaining_liq_not_stashed); - - // @dev Transfer payout from the vault to the just settled round, - if (total_payout > 0) { - self.get_eth_dispatcher().transfer(current_round.contract_address, total_payout); - } - - // @dev Deploy next option round contract & update the current round id - self.deploy_next_round(settlement_price); - - (total_payout, settlement_price) - } + // *********************************** + // WRITES + // *********************************** - /// Liquidity Provider /// + /// Account functions - // @dev Caller adds liquidity to an account's upcoming round deposit - fn deposit_liquidity( - ref self: ContractState, amount: u256, account: ContractAddress - ) -> u256 { - // @dev Update the account's current and upcoming round deposit + // @dev Caller deposits liquidity for an account in the upcoming round + fn deposit(ref self: ContractState, amount: u256, account: ContractAddress) -> u256 { + // @dev Update the account's current and upcoming round deposits self.refresh_position(account); let upcoming_round_id = self.get_upcoming_round_id(); let upcoming_round_deposit = self.positions.read((account, upcoming_round_id)); let account_unlocked_balance_now = upcoming_round_deposit + amount; self.positions.write((account, upcoming_round_id), account_unlocked_balance_now); - // @dev Transfer the liquidity from the caller to this contract + // @dev Transfer the deposit amount from the caller to this contract let eth = self.get_eth_dispatcher(); eth.transfer_from(get_caller_address(), get_contract_address(), amount); - // @dev Update the total unlocked balance of the Vault - let vault_unlocked_balance_now = self.total_unlocked_balance.read() + amount; - self.total_unlocked_balance.write(vault_unlocked_balance_now); + // @dev Update the vault's unlocked balance + let vault_unlocked_balance_now = self.vault_unlocked_balance.read() + amount; + self.vault_unlocked_balance.write(vault_unlocked_balance_now); // @dev Emit deposit event self @@ -462,25 +366,28 @@ mod Vault { ) ); + // @dev Return the account's unlocked balance after the deposit account_unlocked_balance_now } - // @dev Caller takes liquidity from their upcoming round deposit - fn withdraw_liquidity(ref self: ContractState, amount: u256) -> u256 { + // @dev Caller withdraws liquidity from the upcoming round + fn withdraw(ref self: ContractState, amount: u256) -> u256 { // @dev Update the account's upcoming round deposit let account = get_caller_address(); self.refresh_position(account); let upcoming_round_id = self.get_upcoming_round_id(); let upcoming_round_deposit = self.positions.read((account, upcoming_round_id)); - // @dev The account can only withdraw <= the upcoming round deposit + // @dev Check the caller is not withdrawing more than their upcoming round deposit assert(amount <= upcoming_round_deposit, Errors::InsufficientBalance); + + // @dev Update the account's upcoming round deposit let account_unlocked_balance_now = upcoming_round_deposit - amount; self.positions.write((account, upcoming_round_id), account_unlocked_balance_now); - // @dev Update the total unlocked balance of the Vault - let vault_unlocked_balance_now = self.get_total_unlocked_balance() - amount; - self.total_unlocked_balance.write(vault_unlocked_balance_now); + // @dev Update the vault's unlocked balance + let vault_unlocked_balance_now = self.vault_unlocked_balance.read() - amount; + self.vault_unlocked_balance.write(vault_unlocked_balance_now); // @dev Transfer the liquidity from the caller to this contract let eth = self.get_eth_dispatcher(); @@ -499,16 +406,10 @@ mod Vault { ) ); + // @dev Return the account's unlocked balance after the withdrawal account_unlocked_balance_now } - // Stash the value of the position at the start of the current round - // Ignore unsold, it will be handled later, this will be - // prev round remaining balance + current round deposit - // Should be able to do x + y for any round/id state, modifty the 'calculate_value_of_position_from_checkpoint_to_round' - // function to handle when r0 is passed/traverssed - // @amount is the Total amount to stash, allowing a user to set an updated - // @param BPS: The percentage points <= 10,000 the account queues to stash when the round settles fn queue_withdrawal(ref self: ContractState, bps: u16) { // @dev If the current round is Open, there is no locked liqudity to queue, exit early let current_round_id = self.current_round_id.read(); @@ -518,60 +419,54 @@ mod Vault { return; } - // @dev An account can only queue <= 10,000 BPS + // @dev Check the caller is not queueing more than the max BPS assert(bps.into() <= BPS, Errors::QueueingMoreThanPositionValue); - // @dev Get the user's starting deposit for the current round + // @dev Get the caller's calculated current round deposit let account = get_caller_address(); self.refresh_position(account); let current_round_deposit = self.get_realized_deposit_for_current_round(account); // @dev Calculate the starting liquidity for the account being queued - let account_queued_amount_now = (current_round_deposit * bps.into()) / BPS.into(); - - // @dev The caller could be increasing or decreasing their already queued amount - // so we need to update the total queued balance for the round accordingly - let round_previously_queued_amount = self.round_queued_liquidity.read(current_round_id); - let (_, account_queued_amount_before) = self - .user_queued_liquidity + let account_queued_liquidity_now = (current_round_deposit * bps.into()) / BPS.into(); + + // @dev Calculate the's starting liquidity for the vault being queued + let vault_previously_queued_liquidity = self + .queued_liquidity + .read((get_contract_address(), current_round_id)); + let account_previously_queued_liquidity = self + .queued_liquidity .read((account, current_round_id)); - let vault_queued_amount_now = round_previously_queued_amount - - account_queued_amount_before - + account_queued_amount_now; - self.round_queued_liquidity.write(current_round_id, vault_queued_amount_now); + let vault_queued_liquidity_now = vault_previously_queued_liquidity + - account_previously_queued_liquidity + + account_queued_liquidity_now; - // @dev Update queued amount for the liquidity provider in the current round - self - .user_queued_liquidity - .write((account, current_round_id), (bps, account_queued_amount_now)); + // @dev Update the vault and account's queued liquidity + let vault = get_contract_address(); + self.queued_liquidity.write((vault, current_round_id), vault_queued_liquidity_now); + self.queued_liquidity.write((account, current_round_id), account_queued_liquidity_now); // @dev Emit withdrawal queued event self .emit( Event::WithdrawalQueued( WithdrawalQueued { - account, bps, account_queued_amount_now, vault_queued_amount_now + account, bps, account_queued_liquidity_now, vault_queued_liquidity_now } ) ); } + fn withdraw_stash(ref self: ContractState, account: ContractAddress) -> u256 { + // @dev Get how much the account has stashed + let amount = self.get_account_stashed_balance(account); - // @note add event - // Liquidity provider withdraws their stashed (queued) withdrawals - // Sums stashes from checkpoint -> prev round and sends them to caller - // resets checkpoint to current round so that next time the count starts from the current round - // @note update total stashed - fn claim_queued_liquidity(ref self: ContractState, account: ContractAddress) -> u256 { - // @dev How much does the liquidity provider have stashed - let amount = self.get_lp_stashed_balance(account); + // @dev Update the account's stash checkpoint + self.stash_checkpoints.write(account, self.current_round_id.read() - 1); // @dev Update the vault's total stashed - let vault_stashed_balance_now = self.total_stashed_balance.read() - amount; - self.total_stashed_balance.write(vault_stashed_balance_now); - - // @dev Update the liquidity provider's stash checkpoint - self.queue_checkpoints.write(account, self.current_round_id.read() - 1); + let vault_stashed_balance_now = self.vault_stashed_balance.read() - amount; + self.vault_stashed_balance.write(vault_stashed_balance_now); // @dev Transfer the stashed balance to the liquidity provider let eth = self.get_eth_dispatcher(); @@ -580,28 +475,117 @@ mod Vault { // @dev Emit stashed withdrawal event self .emit( - Event::QueuedLiquidityCollected( - QueuedLiquidityCollected { account, amount, vault_stashed_balance_now } + Event::StashWithdrawn( + StashWithdrawn { account, amount, vault_stashed_balance_now } ) ); amount } + /// State transitions - /// OTHER (FOR NOW) /// - // @note remove these + fn start_auction(ref self: ContractState) -> u256 { + // @dev Update all unlocked liquidity to locked + let unlocked_liquidity_before_auction = self.vault_unlocked_balance.read(); + self.vault_locked_balance.write(unlocked_liquidity_before_auction); + self.vault_unlocked_balance.write(0); - fn convert_position_to_lp_tokens(ref self: ContractState, amount: u256) {} + // @dev Start the current round's auction and return the total options available + self + .get_round_dispatcher(self.current_round_id.read()) + .start_auction(unlocked_liquidity_before_auction) + } - fn convert_lp_tokens_to_position( - ref self: ContractState, source_round: u256, amount: u256 - ) {} + fn end_auction(ref self: ContractState) -> (u256, u256) { + // @dev End the current round's auction + let current_round = self.get_round_dispatcher(self.current_round_id.read()); + let (clearing_price, options_sold) = current_round.end_auction(); - fn convert_lp_tokens_to_newer_lp_tokens( - ref self: ContractState, source_round: u256, target_round: u256, amount: u256 - ) -> u256 { - 1 + // @dev Calculate the total premium and add it to the total unlocked liquidity + let mut unlocked_liquidity = self.vault_unlocked_balance.read(); + let total_premium = clearing_price * options_sold; + unlocked_liquidity += total_premium; + + // @dev If there is unsold liquidity it becomes unlocked + let unsold_liquidity = current_round.get_unsold_liquidity(); + if unsold_liquidity.is_non_zero() { + unlocked_liquidity += unsold_liquidity; + self + .vault_locked_balance + .write(self.vault_locked_balance.read() - unsold_liquidity); + } + + // @dev Update the vault's unlocked balance + self.vault_unlocked_balance.write(unlocked_liquidity); + + // @dev Return the clearing price of the auction and the number of options sold + (clearing_price, options_sold) + } + + fn settle_round(ref self: ContractState) -> u256 { + // @dev Settle the current round + let current_round_id = self.current_round_id.read(); + let current_round = self.get_round_dispatcher(current_round_id); + // FOSSIL + let to = current_round.get_option_settlement_date(); + let from = to - TWAP_DURATION; + let settlement_price = self.fetch_TWAP_for_time_period(from, to); + let total_payout = current_round.settle_round(settlement_price); + + // @dev Calculate the remaining liquidity after the round settles + let starting_liq = current_round.get_starting_liquidity(); + let unsold_liq = current_round.get_unsold_liquidity(); + let remaining_liq = starting_liq - unsold_liq - total_payout; + + // @dev Calculate the amount of liquidity that was not stashed by liquidity providers, + // avoiding division by 0 + let vault = get_contract_address(); + let starting_liq_queued = self.queued_liquidity.read((vault, current_round_id)); + let remaining_liq_stashed = match starting_liq.is_zero() { + true => 0, + false => (remaining_liq * starting_liq_queued) / starting_liq + }; + let remaining_liq_not_stashed = remaining_liq - remaining_liq_stashed; + + // @dev All of the remaining liquidity becomes unlocked and any stashed liquidity is set + // aside and no longer participates in the protocol + self.vault_locked_balance.write(0); + self + .vault_stashed_balance + .write(self.vault_stashed_balance.read() + remaining_liq_stashed); + self + .vault_unlocked_balance + .write(self.vault_unlocked_balance.read() + remaining_liq_not_stashed); + + // @dev Transfer payout from the vault to the just settled round, + if (total_payout > 0) { + self.get_eth_dispatcher().transfer(current_round.contract_address, total_payout); + } + + // @dev Deploy next option round contract & update the current round id + self.deploy_next_round(settlement_price); + + // Return the total payout for the option round + total_payout + } + + // @note will probably remove this + fn update_round_params(ref self: ContractState) { + let current_round_id = self.current_round_id.read(); + let current_round = self.get_round_dispatcher(current_round_id); + + let cap_level = self.fetch_cap_level_for_round(current_round_id); + let reserve_price = self.fetch_reserve_price_for_round(current_round_id); + let twap_end = current_round.get_auction_start_date(); + let twap_start = twap_end - TWAP_DURATION; + let current_avg_basefee = self.fetch_TWAP_for_time_period(twap_start, twap_end); + let volatility = self.fetch_volatility_for_round(current_round_id); + let strike_price = calculate_strike_price( + self.vault_type.read(), current_avg_basefee, volatility + ); + + current_round.update_round_params(reserve_price, cap_level, strike_price); } } @@ -610,28 +594,63 @@ mod Vault { // ************************************************************************* #[generate_trait] impl InternalImpl of VaultInternalTrait { - // Get a dispatcher for the ETH contract + /// Get contract dispatchers + fn get_eth_dispatcher(self: @ContractState) -> ERC20ABIDispatcher { - let eth_address: ContractAddress = self.eth_address(); - ERC20ABIDispatcher { contract_address: eth_address } + ERC20ABIDispatcher { contract_address: self.eth_address.read() } + } + + fn get_market_aggregator_dispatcher(self: @ContractState) -> IMarketAggregatorDispatcher { + IMarketAggregatorDispatcher { contract_address: self.market_aggregator_address.read() } } - // Get a dispatcher for the Vault fn get_round_dispatcher(self: @ContractState, round_id: u256) -> IOptionRoundDispatcher { - let round_address = self.round_addresses.read(round_id); - IOptionRoundDispatcher { contract_address: round_address } + IOptionRoundDispatcher { contract_address: self.round_addresses.read(round_id) } } - fn deploy_first_round(ref self: ContractState) { - let now = starknet::get_block_timestamp(); - let TWAP_end_date = now; - let TWAP_start_date = now - TWAP_DURATION; - let current_avg_basefee = self - .fetch_TWAP_for_time_period(TWAP_start_date, TWAP_end_date); + /// Basic helpers - self.deploy_next_round(current_avg_basefee); + fn get_upcoming_round_id(self: @ContractState) -> u256 { + let current_round_id = self.current_round_id.read(); + match self.get_round_dispatcher(current_round_id).get_state() { + OptionRoundState::Open => current_round_id, + _ => current_round_id + 1 + } } + fn get_round_outcome(self: @ContractState, round_id: u256) -> (u256, u256, u256) { + let round = self.get_round_dispatcher(round_id); + assert!( + round_id < self.current_round_id.read(), "Round must be settled to get outcome" + ); + + // @dev Get the round's details + let round_starting_liq = round.get_starting_liquidity(); + let round_unsold_liq = round.get_unsold_liquidity(); + let round_premiums = round.get_total_premium(); + let round_payout = round.get_total_payout(); + + // @dev Calculate the round's remaining and earned liquidity + let remaining_liq = round_starting_liq - round_payout - round_unsold_liq; + let round_earned_liq = round_premiums + round_unsold_liq; + + // Return the starting, remaining, and earned liquidity for a settled round + (round_starting_liq, remaining_liq, round_earned_liq) + } + + + // @dev Divide numerator by denominator and turn into a u16 BPS + fn divide_into_bps(self: @ContractState, numerator: u256, denominator: u256) -> u16 { + assert!( + numerator <= denominator, + "Numerator must be less than or equal to the denominator to fit into BPS" + ); + + ((BPS.into() * numerator) / denominator).try_into().unwrap() + } + + /// Deploying rounds + fn calculate_dates(self: @ContractState) -> (u64, u64, u64) { let now = starknet::get_block_timestamp(); let auction_start_date = now + self.round_transition_period.read(); @@ -641,23 +660,30 @@ mod Vault { (auction_start_date, auction_end_date, option_settlement_date) } - // Deploy the next option round contract, update the current round id & round address mapping - // @note will need to add current_vol as well + fn deploy_first_round(ref self: ContractState) { + let now = starknet::get_block_timestamp(); + let TWAP_end_date = now; + let TWAP_start_date = now - TWAP_DURATION; + let current_avg_basefee = self + .fetch_TWAP_for_time_period(TWAP_start_date, TWAP_end_date); + + self.deploy_next_round(current_avg_basefee); + } + fn deploy_next_round(ref self: ContractState, current_avg_basefee: u256) { - // The constructor params for the next round + // @dev Create this round's constructor args let mut calldata: Array = array![]; - // The Vault's address - let vault_address = get_contract_address(); - // Vault address & round id - // The round id for the next round + // @dev Get this vault's address + let vault_address: ContractAddress = get_contract_address(); + // @dev Cauclulate this round's id let round_id: u256 = self.current_round_id.read() + 1; - // Dates + // @dev Calcualte this round's dates let (auction_start_date, auction_end_date, option_settlement_date) = self .calculate_dates(); - // Reserve price, cap level, & strike price adjust these to take to and from + // @dev Fetch this round's reserve price and cap level let reserve_price = self.fetch_reserve_price_for_round(round_id); let cap_level = self.fetch_cap_level_for_round(round_id); - // @dev Calculate strike price based on current avg basefee and Vault's type + // @dev Calculate this round's strike price let volatility = self.fetch_volatility_for_round(round_id); let strike_price = calculate_strike_price( self.vault_type.read(), current_avg_basefee, volatility @@ -672,17 +698,18 @@ mod Vault { calldata.append_serde(cap_level); calldata.append_serde(strike_price); - // Deploy the next option round contract + // @dev Deploy the round let (address, _) = deploy_syscall( self.option_round_class_hash.read(), 'some salt', calldata.span(), false ) .expect(Errors::OptionRoundDeploymentFailed); - // Update the current round id & round address mapping + // @dev Update the current round id self.current_round_id.write(round_id); + // @dev Set this round address self.round_addresses.write(round_id, address); - // Emit option round deployed event + // @dev Emit option round deployed event self .emit( Event::OptionRoundDeployed( @@ -700,134 +727,7 @@ mod Vault { ); } - // @dev Get the amount of liquidity unlocked for an account after a round's auction - // @param account: The account in question - // @param account_staring_liq: The amount of liquidity the account locked at the start of the round - // @param round_id: The round to lookup - // @note Returns 0 if the round is Open | Running - // @note Returns 0 if the unlocked liq was moved as a deposit into the next round - fn get_liquidity_unlocked_for_account_in_round( - self: @ContractState, - account: ContractAddress, - account_starting_liq: u256, - round_id: u256 - ) -> u256 { - let round = self.get_round_dispatcher(round_id); - let state = round.get_state(); - // @dev If the round is Open | Auctioning, there are no premiums/unsold liquidity yet - if state == OptionRoundState::Open || state == OptionRoundState::Auctioning { - 0 - } else { - if self.premiums_moved.read((account, round_id)) { - 0 - } else { - // @dev How much unlockable liquidity is there in the round - let round_starting_liq = round.starting_liquidity(); - let round_unlocked_liq = round.total_premiums() + round.unsold_liquidity(); - - // @dev Liquidity provider's share of the unlocked liquidity - (round_unlocked_liq * account_starting_liq) / round_starting_liq - } - } - } - - // @dev Get the amount of liquidity that remained for an account after a round that was not stashed - // @param account: The account in question - // @param account_staring_liq: The amount of liquidity the account locked at the start of the round - // @param round_id: The round to lookup - // @note Returns 0 if the round is not Settled - // @return The remaining liquidity not stashed - fn get_liquidity_that_remained_in_round_unstashed( - self: @ContractState, - account: ContractAddress, - account_starting_liq: u256, - round_id: u256 - ) -> u256 { - let round = self.get_round_dispatcher(round_id); - let state = round.get_state(); - // @dev If the round is not Settled then remaining liquiditiy is not known yet - if state != OptionRoundState::Settled { - 0 - } else { - // @dev How much remaining liquidity was there in the round - let (round_starting_liq, round_remaining_liq, _) = self.get_round_outcome(round_id); - - // @dev How much did the account stash - let (_, account_amount_queued) = self - .user_queued_liquidity - .read((account, round_id)); - let account_remaining_liq_stashed = (round_remaining_liq * account_amount_queued) - / round_starting_liq; - - // @dev How much did the account not stash - let account_remaining_liq = (round_remaining_liq * account_starting_liq) - / round_starting_liq; - let account_remaining_liq_not_stashed = account_remaining_liq - - account_remaining_liq_stashed; - - account_remaining_liq_not_stashed - } - } - - - // @dev Returns the value of the user's position at the start of the current round - fn get_realized_deposit_for_current_round( - self: @ContractState, account: ContractAddress - ) -> u256 { - // @dev Calculate the value of the liquidity provider's position from the round - // after their withdraw checkpoint to the end of the previous round - let mut realized_position = 0; - let mut i = self.withdraw_checkpoints.read(account) + 1; - let current_round_id = self.current_round_id.read(); - while i < current_round_id { - // @dev The position's value at start of this round includes deposits into the round - realized_position += self.positions.read((account, i)); - - // @dev How much liquidity became unlocked for the account in this round - let account_unlocked_liq = self - .get_liquidity_unlocked_for_account_in_round(account, realized_position, i); - - // @dev How much liquidity remained for the account in this round - let account_remaining_liq = self - .get_liquidity_that_remained_in_round_unstashed(account, realized_position, i); - - realized_position = account_unlocked_liq + account_remaining_liq; - - i += 1; - }; - - // @dev Add in the liquidity provider's deposit into the current round - let current_round_deposit = self.positions.read((account, current_round_id)); - realized_position + current_round_deposit - } - - // Returns the starting, remaining, and earned liquidity for a round - fn get_round_outcome(self: @ContractState, round_id: u256) -> (u256, u256, u256) { - let round = self.get_round_dispatcher(round_id); - assert!( - round_id < self.current_round_id.read(), "Round must be settled to get outcome" - ); - - // @dev This round's details - let round_starting_liq = round.starting_liquidity(); - let round_unsold_liq = round.unsold_liquidity(); - let round_premiums = round.total_premiums(); - let round_payout = round.total_payout(); - - // @dev The remaining liquidity at the end of this round - let remaining_liq = round_starting_liq - round_payout - round_unsold_liq; - // @dev The amount of premiums/unsold liquidity the liquidity provider gained this round - let round_earned_liq = round_premiums + round_unsold_liq; - - (round_starting_liq, remaining_liq, round_earned_liq) - } - - // @note Fetch values upon deployment, if there are newer (less stale) vaules at the time of auction start, - // we use the newer values to set the params - - fn get_market_aggregator_dispatcher(self: @ContractState) -> IMarketAggregatorDispatcher { - IMarketAggregatorDispatcher { contract_address: self.get_market_aggregator() } - } + /// Fossil fn fetch_reserve_price_for_round(self: @ContractState, round_id: u256) -> u256 { let mk_agg = self.get_market_aggregator_dispatcher(); @@ -869,46 +769,171 @@ mod Vault { } } - // @dev Combine deposits from the last checkpoint into a single deposit for the current round, - // also if there are premiums/unsold liq collectable, add them as a deposit for the upcoming round - fn refresh_position(ref self: ContractState, account: ContractAddress) { - // @dev Calculate the account's position at start of the current round + /// Position management + + // @dev Calculate the account's starting deposit for the current round + fn get_realized_deposit_for_current_round( + self: @ContractState, account: ContractAddress + ) -> u256 { + // @dev Calculate the value of the account's deposit from the round after their + // deposit checkpoint to the start of the current round let current_round_id = self.current_round_id.read(); - let current_round_deposit = self.get_realized_deposit_for_current_round(account); + let mut i = self.position_checkpoints.read(account) + 1; + let mut realized_deposit = 0; + while i < current_round_id { + // @dev Increment the realized deposit by the account's deposit in this round + realized_deposit += self.positions.read((account, i)); - // @dev Update the account's current round deposit and checkpoint - self.withdraw_checkpoints.write(account, current_round_id - 1); - self.positions.write((account, self.current_round_id()), current_round_deposit); + // @dev Get the liquidity that became unlocked for the account in this round + let account_unlocked_liq = self + .get_liquidity_unlocked_for_account_in_round(account, realized_deposit, i); + + // @dev Get the liquidity that remained for the account in this round + let account_remaining_liq = self + .get_account_liquidity_that_remained_in_round_unstashed( + account, realized_deposit, i + ); - // @dev Move the account's unlocked liquidity (premiums and unsold liquidty) - // as a deposit into the next round if not already moved + realized_deposit = account_unlocked_liq + account_remaining_liq; + + i += 1; + }; + + // @dev Add in the liquidity provider's current round deposit + realized_deposit + self.positions.read((account, current_round_id)) + } + + + // @dev Calculate the account's starting deposit for the current round and their deposit + // for the upcoming round + fn get_refreshed_position(self: @ContractState, account: ContractAddress) -> (u256, u256) { + // @dev Calculate the account's deposit at start of the current round + let current_round_id = self.current_round_id.read(); + let current_round_deposit = self.get_realized_deposit_for_current_round(account); let state = self.get_round_dispatcher(current_round_id).get_state(); - if state == OptionRoundState::Running { - if !self.premiums_moved.read((account, current_round_id)) { - let account_unlocked_liq = self - .get_liquidity_unlocked_for_account_in_round( - account, current_round_deposit, current_round_id - ); - let upcoming_round_deposit = self + match state { + // @dev If the current round is Open, it is also the upcoming round + OptionRoundState::Open => (current_round_deposit, current_round_deposit), + // @dev Else, there is an upcoming round + _ => { + // @dev Get the account's upcoming round deposit + let mut upcoming_round_deposit = self .positions .read((account, current_round_id + 1)); + // @dev There are only premium/unsold liquidity after the auction ends + if state == OptionRoundState::Running { + // @dev Adds 0 if the premium/unsold liquidity was already moved as a deposit + // into the upcoming round + upcoming_round_deposit += self + .get_liquidity_unlocked_for_account_in_round( + account, current_round_deposit, current_round_id + ); + } + + (current_round_deposit, upcoming_round_deposit) + }, + } + } + + // @dev Combine deposits from the last checkpoint into a single deposit for the current round, + // and if there are premiums/unsold liquidity collectable, add them as a deposit for the + // upcoming round + fn refresh_position(ref self: ContractState, account: ContractAddress) { + // @dev Get the refreshed current and upcoming round deposits + let current_round_id = self.current_round_id.read(); + let (current_round_deposit, upcoming_round_deposit) = self + .get_refreshed_position(account); + + // @dev Update the account's current round deposit and checkpoint + if current_round_deposit != self.positions.read((account, current_round_id)) { + self.positions.write((account, current_round_id), current_round_deposit); + } + if current_round_id - 1 != self.position_checkpoints.read(account) { + self.position_checkpoints.write(account, current_round_id - 1); + } - self.premiums_moved.write((account, current_round_id), true); - self + // @dev If the current round is Running, there could be premiums/unsold liquidity to + // to move to the upcoming round + if self + .get_round_dispatcher(current_round_id) + .get_state() == OptionRoundState::Running { + if !self.is_premium_moved.read((account, current_round_id)) { + self.is_premium_moved.write((account, current_round_id), true); + if upcoming_round_deposit != self .positions - .write( - (account, current_round_id + 1), - upcoming_round_deposit + account_unlocked_liq - ); + .read((account, current_round_id + 1)) { + self + .positions + .write((account, current_round_id + 1), upcoming_round_deposit); + } } } } - fn get_upcoming_round_id(self: @ContractState) -> u256 { - let current_round_id = self.current_round_id.read(); - match self.get_round_dispatcher(current_round_id).get_state() { - OptionRoundState::Open => current_round_id, - _ => current_round_id + 1 + // @dev Get the premium and unsold liquidity unlocked for an account after a round's auction + // @param account: The account in question + // @param account_staring_liq: The liquidity the account locked at the start of the round + // @param round_id: The round to lookup + // @note Returns 0 if the round is Open | Running + // @note Returns 0 if the unlocked liq was moved as a deposit into the next round (refreshed) + fn get_liquidity_unlocked_for_account_in_round( + self: @ContractState, + account: ContractAddress, + account_starting_liq: u256, + round_id: u256 + ) -> u256 { + // @dev If the round is Open | Auctioning, there are no premiums/unsold liquidity yet, return 0 + // @dev If the unlocked liquidity was moved as a deposit into the next round, return 0 + let round = self.get_round_dispatcher(round_id); + let state = round.get_state(); + if state == OptionRoundState::Open + || state == OptionRoundState::Auctioning + || self.is_premium_moved.read((account, round_id)) { + 0 + } else { + // @dev How much unlockable liquidity is there in the round + let round_starting_liq = round.get_starting_liquidity(); + let round_unlocked_liq = round.get_total_premium() + round.get_unsold_liquidity(); + + // @dev Liquidity provider's share of the unlocked liquidity, avoiding division by 0 + match round_starting_liq.is_zero() { + true => 0, + false => { (round_unlocked_liq * account_starting_liq) / round_starting_liq } + } + } + } + + // @dev Get the liquidity that remained for an account after a round settled that was not stashed + // @param account: The account in question + // @param account_staring_liq: The liquidity the account locked at the start of the round + // @param round_id: The round to lookup + // @note Returns 0 if the round is not Settled + fn get_account_liquidity_that_remained_in_round_unstashed( + self: @ContractState, + account: ContractAddress, + account_starting_liq: u256, + round_id: u256 + ) -> u256 { + // @dev Return 0 if the round is not Settled + if self.get_round_dispatcher(round_id).get_state() != OptionRoundState::Settled { + 0 + } else { + // @dev Get the round's starting and remaining liquidity + let (round_starting_liq, round_remaining_liq, _) = self.get_round_outcome(round_id); + + // @dev Calculate the amount of liquidity the account stashed + let account_liq_queued = self.queued_liquidity.read((account, round_id)); + let account_remaining_liq_stashed = (round_remaining_liq * account_liq_queued) + / round_starting_liq; + + // @dev Calculate the amount of liquidity the account did not stashed + let account_remaining_liq = (round_remaining_liq * account_starting_liq) + / round_starting_liq; + let account_remaining_liq_not_stashed = account_remaining_liq + - account_remaining_liq_stashed; + + // @dev Return the remaining liquidity not stashed + account_remaining_liq_not_stashed } } } diff --git a/src/vault/interface.cairo b/src/vault/interface.cairo index 2735f3ad..e3e04040 100644 --- a/src/vault/interface.cairo +++ b/src/vault/interface.cairo @@ -10,144 +10,100 @@ use pitch_lake_starknet::{ trait IVault { /// Reads /// - /// Other + // @dev Get the type of vault (ITM | ATM | OTM) + fn get_vault_type(self: @TContractState) -> VaultType; - // Get the vault's manaager address - // @dev Better access control ? (oz permits ?) - fn vault_manager(self: @TContractState) -> ContractAddress; + // @dev Get the market aggregator's address + fn get_market_aggregator_address(self: @TContractState) -> ContractAddress; - // Get the type of vault (ITM | ATM | OTM) - fn vault_type(self: @TContractState) -> VaultType; + // @dev Get the ETH address + fn get_eth_address(self: @TContractState) -> ContractAddress; - // Get the market aggregator address - fn get_market_aggregator(self: @TContractState) -> ContractAddress; - - // Get the ETH address - fn eth_address(self: @TContractState) -> ContractAddress; - - // Get the amount of time an auction runs for + // @dev Get the amount of time an auction runs for fn get_auction_run_time(self: @TContractState) -> u64; - // Get the amount of time an option round runs for + // @dev Get the amount of time an option round runs for fn get_option_run_time(self: @TContractState) -> u64; // Get the amount of time till starting the next round's auction fn get_round_transition_period(self: @TContractState) -> u64; - // @note Add getters for auction run time & option run time - // - need to also add to facade, then use in tests for the (not yet created) setters (A1.1) - - /// Rounds - // @return the current option round id - fn current_round_id(self: @TContractState) -> u256; + fn get_current_round_id(self: @TContractState) -> u256; // @return the contract address of the option round fn get_round_address(self: @TContractState, option_round_id: u256) -> ContractAddress; /// Liquidity - // For LPs // + // @dev The total liquidity in the Vault + fn get_vault_total_balance(self: @TContractState) -> u256; - // Get the liquidity an lp had at the beginning of the current round - fn get_lp_starting_balance(self: @TContractState, account: ContractAddress) -> u256; + // @dev The total liquidity locked in the Vault + fn get_vault_locked_balance(self: @TContractState) -> u256; + // @dev The total liquidity unlocked in the Vault + fn get_vault_unlocked_balance(self: @TContractState) -> u256; - // Get the liquidity an lp has locked - fn get_lp_locked_balance(self: @TContractState, account: ContractAddress) -> u256; + // @dev The total liquidity stashed in the Vault + fn get_vault_stashed_balance(self: @TContractState) -> u256; - // Get the liquidity an LP has unlocked - fn get_lp_unlocked_balance(self: @TContractState, account: ContractAddress) -> u256; + // @dev The total % (bps) queued for withdrawal once the current round settles + fn get_vault_queued_bps(self: @TContractState) -> u16; - // Get the liquidity an LP queued for stashing in a round - fn get_lp_queued_bps(self: @TContractState, account: ContractAddress) -> u16; + // @dev The total liquidity for an account + fn get_account_total_balance(self: @TContractState, account: ContractAddress) -> u256; - // Get the liquidity an LP has stashed in the vault from withdrawal queues - fn get_lp_stashed_balance(self: @TContractState, account: ContractAddress) -> u256; + // @dev The liquidity locked for an account + fn get_account_locked_balance(self: @TContractState, account: ContractAddress) -> u256; - // Get the total liquidity an LP has in the protocol - fn get_lp_total_balance(self: @TContractState, account: ContractAddress) -> u256; + // @dev The liquidity unlocked for an account + fn get_account_unlocked_balance(self: @TContractState, account: ContractAddress) -> u256; - // For Vault // + // @dev The liquidity stashed for an account + fn get_account_stashed_balance(self: @TContractState, account: ContractAddress) -> u256; - // Get the total liquidity locked - fn get_total_locked_balance(self: @TContractState) -> u256; + // @dev The account's % (bps) queued for withdrawal once the current round settles + fn get_account_queued_bps(self: @TContractState, account: ContractAddress) -> u16; - // Get the total liquidity unlocked - fn get_total_unlocked_balance(self: @TContractState) -> u256; + /// Writes /// - // Get the total liquidity queued for stashing in a round - fn get_total_queued_balance(self: @TContractState) -> u256; + /// Account functions - // Get the total liquidity stashed - fn get_total_stashed_balance(self: @TContractState) -> u256; + // @dev The caller adds liquidity for an account's upcoming round deposit (unlocked balance) + // @param amount: The amount of liquidity to deposit + // @return The account's updated unlocked position + fn deposit(ref self: TContractState, amount: u256, account: ContractAddress) -> u256; + // @dev The caller takes liquidity from their upcoming round deposit (unlocked balance) + // @param amount: The amount of liquidity to withdraw + // @return The caller's updated unlocked position + fn withdraw(ref self: TContractState, amount: u256) -> u256; - // Get the total liquidity in the protocol - fn get_total_balance(self: @TContractState) -> u256; + // @dev The caller queues a % of their locked balance to be stashed once the current round settles + // @param bps: The percentage points <= 10,000 the account queues to stash when the round settles + fn queue_withdrawal(ref self: TContractState, bps: u16); - /// Writes /// + // @dev The caller withdraws all of an account's stashed liquidity for the account + // @param account: The account to withdraw stashed liquidity for + // @return The amount withdrawn + fn withdraw_stash(ref self: TContractState, account: ContractAddress) -> u256; - /// State transition + /// State transitions - /// Update the params of the current round if there are newer data from Fossil + // Update the params of the current round if there are newer data from Fossil + // @note Will probably remove this fn update_round_params(ref self: TContractState); - // Start the auction on the next round as long as the current round is Settled and the - // round transition period has passed. Deploys the next next round and updates the current/next pointers. - // @return the total options available in the auction + // @dev Start the current round's auction + // @return The total options available in the auction fn start_auction(ref self: TContractState) -> u256; - // End the auction in the current round as long as the current round is Auctioning and the auction - // bidding period has ended. - // @return the clearing price of the auction - // @return the total options sold in the auction (@note keep or drop ?) + // @dev Ends the current round's auction + // @return The clearing price and total options sold fn end_auction(ref self: TContractState) -> (u256, u256); - // Settle the current option round as long as the current round is Running and the option expiry time has passed. - // @return The total payout of the option round - fn settle_option_round(ref self: TContractState) -> (u256, u256); - - /// LP functions - - // Caller withdraws liquidity from their unlocked balance - // @return The liquidity provider's updated unlocked position - fn deposit_liquidity(ref self: TContractState, amount: u256, account: ContractAddress) -> u256; - - // Caller withdraws from the vault - // @return The liquidity provider's updated unlocked position - fn withdraw_liquidity(ref self: TContractState, amount: u256) -> u256; - - // Caller queues a portion of their currently locked liquidity to be stashed aside, to not roll over - // into the next round - fn queue_withdrawal(ref self: TContractState, bps: u16); - - // Liquidity provider withdraws their stashed (queued) withdrawals - fn claim_queued_liquidity(ref self: TContractState, account: ContractAddress) -> u256; - - /// LP token related - - // LP converts their collateral into LP tokens - // @note all at once or can LP convert a partial amount ? - // - logically i'm pretty sure they could do a partial amount (collecting all rewards in either case) - fn convert_position_to_lp_tokens(ref self: TContractState, amount: u256); - - // LP converts their (source_round) LP tokens into a position in the current round - // @dev Premiums/unsold from the source round are not counted - fn convert_lp_tokens_to_position(ref self: TContractState, source_round: u256, amount: u256); - - // LP token owner converts an amount of source round tokens to target round tokens - // @dev Rx tokens do not include premiums/unsold from rx (above) - // This is not a problem for token -> position, but is a problem for - // token -> token because when rx tokens convert to ry, the ry tokens should - // be able to collect ry premiums but will not be able to (above) - // @dev Ry must be running or settled. This way we can know the premiums that the rY tokens earned in the round, and collect them - // as a deposit into the next round. We need to collect these rY premiums because the LP tokens need to represent the value of a - // deposit in the round net any premiums from the round. - // @dev If we do not collect the premiums for rY upon conversion, they would be lost. - // @return the amount of target round tokens received - // @dev move entry point to LPToken ? - fn convert_lp_tokens_to_newer_lp_tokens( - ref self: TContractState, source_round: u256, target_round: u256, amount: u256 - ) -> u256; + // @dev Settle the current round + // @return The total payout for the round + fn settle_round(ref self: TContractState) -> u256; }