From 201acc9d435f4a2a528fcae029e4a6ac6be4f8cc Mon Sep 17 00:00:00 2001 From: Matt Carter <96356887+0xDegenDeveloper@users.noreply.github.com> Date: Thu, 18 Jul 2024 08:21:47 -0400 Subject: [PATCH] update return types for contracts (#130) * update return types for contracts - update tests - update facades - slight gas optimizing * Compiled both types files into 1 * fix imports * fixed comments and imports * re-org repo * Delete library.cairo * Fixed imports from merge commits --- src/contracts/lp_token.cairo | 8 +- src/contracts/pitch_lake.cairo | 32 +- src/contracts/vault/types.cairo | 30 -- src/lib.cairo | 44 ++- .../components => library}/eth.cairo | 1 + .../red_black_tree.cairo | 9 +- src/{contracts/utils => library}/utils.cairo | 0 .../option_round/contract.cairo | 371 ++++++++++-------- .../option_round/interface.cairo | 40 +- src/tests/deployment/constructor_tests.cairo | 23 +- ...itializing_option_round_params_tests.cairo | 9 +- src/tests/misc/lp_token/lp_token_tests.cairo | 2 +- src/tests/misc/pitch_lake_test.cairo | 3 +- .../option_buyers/bidding_tests.cairo | 99 ++--- .../exercise_options_tests.cairo | 9 +- .../option_buyers/refunding_bids_tests.cairo | 9 +- .../tokenizing_options_tests.cairo | 53 ++- .../option_buyers/update_bids_tests.cairo | 86 ++-- .../rb_tree/rb_tree_mock_contract.cairo | 20 +- .../rb_tree/rb_tree_stress_tests.cairo | 80 ++-- .../option_round/rb_tree/rb_tree_tests.cairo | 46 +-- .../caller_is_not_vault_tests.cairo | 26 +- .../calculated_payout_tests.cairo | 2 +- .../utils/facades/option_round_facade.cairo | 230 +++++------ src/tests/utils/facades/sanity_checks.cairo | 5 +- src/tests/utils/facades/vault_facade.cairo | 116 +++--- src/tests/utils/helpers/accelerators.cairo | 33 +- src/tests/utils/helpers/event_helpers.cairo | 7 +- src/tests/utils/helpers/setup.cairo | 29 +- .../liquidity_providers/deposit_tests.cairo | 10 +- .../liquidity_providers/withdraw_tests.cairo | 13 +- .../state_transition/auction_end_tests.cairo | 42 +- .../auction_start_tests.cairo | 38 +- .../option_settle_tests.cairo | 39 +- src/{contracts/option_round => }/types.cairo | 148 +++---- src/{contracts => }/vault/contract.cairo | 189 ++++----- src/{contracts => }/vault/interface.cairo | 20 +- 37 files changed, 870 insertions(+), 1051 deletions(-) delete mode 100644 src/contracts/vault/types.cairo rename src/{contracts/components => library}/eth.cairo (99%) rename src/{contracts/components => library}/red_black_tree.cairo (99%) rename src/{contracts/utils => library}/utils.cairo (100%) rename src/{contracts => }/option_round/contract.cairo (82%) rename src/{contracts => }/option_round/interface.cairo (82%) rename src/{contracts/option_round => }/types.cairo (53%) rename src/{contracts => }/vault/contract.cairo (84%) rename src/{contracts => }/vault/interface.cairo (91%) diff --git a/src/contracts/lp_token.cairo b/src/contracts/lp_token.cairo index f314300e..28b1d4db 100644 --- a/src/contracts/lp_token.cairo +++ b/src/contracts/lp_token.cairo @@ -4,9 +4,11 @@ use traits::{Into, TryInto}; use openzeppelin::token::erc20::interface::ERC20ABIDispatcher; use openzeppelin::utils::serde::SerializedAppend; -use pitch_lake_starknet::contracts::option_round::types::{OptionRoundState}; -use pitch_lake_starknet::contracts::market_aggregator::{ - IMarketAggregator, IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait +use pitch_lake_starknet::{ + contracts::market_aggregator::{ + IMarketAggregator, IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait + }, + types::{OptionRoundState} }; // @note Events for tokeninzing/positionizing in this contract or vault? diff --git a/src/contracts/pitch_lake.cairo b/src/contracts/pitch_lake.cairo index 53429104..cd01883e 100644 --- a/src/contracts/pitch_lake.cairo +++ b/src/contracts/pitch_lake.cairo @@ -1,23 +1,4 @@ -// https://www.sciencedirect.com/book/9780123745071/auction-theory -// https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4123018 - -// TODO: -// underlying -// setting expiry -// setting strike price -// collateralization -// settlement -// premium -// batch auction -// historical volatility -// liquidity provision -// option minting -// liquidity roll-over -// reserve price (this will be difficult?) -// liquidity cap -// fossil -use pitch_lake_starknet::contracts::vault::interface::{IVaultDispatcher}; - +use pitch_lake_starknet::vault::interface::{IVaultDispatcher}; #[starknet::interface] trait IPitchLake { @@ -28,13 +9,10 @@ trait IPitchLake { #[starknet::contract] mod PitchLake { - use starknet::{ContractAddress}; - use starknet::contract_address::ContractAddressZeroable; - use pitch_lake_starknet::contracts::vault::{ - contract::Vault, interface::{IVault, IVaultDispatcher} - }; - use pitch_lake_starknet::contracts::market_aggregator::{ - IMarketAggregator, IMarketAggregatorDispatcher + use starknet::{ContractAddress, contract_address::ContractAddressZeroable}; + use pitch_lake_starknet::{ + vault::{interface::{IVault, IVaultDispatcher}}, + contracts::market_aggregator::{IMarketAggregator, IMarketAggregatorDispatcher} }; #[storage] diff --git a/src/contracts/vault/types.cairo b/src/contracts/vault/types.cairo deleted file mode 100644 index 64d6f338..00000000 --- a/src/contracts/vault/types.cairo +++ /dev/null @@ -1,30 +0,0 @@ -use starknet::ContractAddress; -use starknet::Event; -use pitch_lake_starknet::contracts::option_round::contract::OptionRound; - -#[derive(starknet::Store, Copy, Drop, Serde, PartialEq)] -enum VaultType { - InTheMoney, - AtTheMoney, - OutOfMoney, -} - - -#[derive(Copy, Drop, Serde)] -enum VaultError { - // Error from OptionRound contract - OptionRoundError: OptionRound::OptionRoundError, - // Withdrawal exceeds unlocked position - InsufficientBalance, -} - - -//Traits -impl VaultErrorIntoFelt252Trait of Into { - fn into(self: VaultError) -> felt252 { - match self { - VaultError::OptionRoundError(e) => { e.into() }, - VaultError::InsufficientBalance => { 'Vault: Insufficient balance' } - } - } -} diff --git a/src/lib.cairo b/src/lib.cairo index b978b403..7305e649 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,26 +1,28 @@ -mod contracts { - mod utils { - mod utils; - } - mod components { - mod eth; - mod red_black_tree; - } - mod pitch_lake; - mod vault { - mod contract; - mod interface; - mod types; - } - mod option_round { - mod contract; - mod interface; - mod types; - } +mod vault { + mod contract; + mod interface; +} - mod market_aggregator; - mod lp_token; +mod option_round { + mod contract; + mod interface; +} + +mod library { + mod eth; + mod red_black_tree; + mod utils; } +mod types; + #[cfg(test)] mod tests; + + +// @note Refactor these into their own modules +mod contracts { + mod pitch_lake; + mod market_aggregator; + mod lp_token; +} diff --git a/src/contracts/components/eth.cairo b/src/library/eth.cairo similarity index 99% rename from src/contracts/components/eth.cairo rename to src/library/eth.cairo index 06edcb28..7b0eabe3 100644 --- a/src/contracts/components/eth.cairo +++ b/src/library/eth.cairo @@ -9,6 +9,7 @@ mod Eth { use openzeppelin::token::erc20::ERC20Component; use starknet::ContractAddress; + component!(path: ERC20Component, storage: erc20, event: ERC20Event); #[storage] struct Storage { diff --git a/src/contracts/components/red_black_tree.cairo b/src/library/red_black_tree.cairo similarity index 99% rename from src/contracts/components/red_black_tree.cairo rename to src/library/red_black_tree.cairo index 9f3b1094..f2a8718f 100644 --- a/src/contracts/components/red_black_tree.cairo +++ b/src/library/red_black_tree.cairo @@ -1,13 +1,14 @@ -use pitch_lake_starknet::contracts::{components::red_black_tree, option_round::types::{Bid}}; +use pitch_lake_starknet::{library::red_black_tree, types::{Bid}}; use starknet::ContractAddress; -const BLACK: bool = false; -const RED: bool = true; #[starknet::component] pub mod RBTreeComponent { - use super::{BLACK, RED, Bid, ContractAddress}; + use super::{Bid, ContractAddress}; use core::{array::ArrayTrait, option::OptionTrait, traits::{IndexView, TryInto}}; + const BLACK: bool = false; + const RED: bool = true; + #[storage] struct Storage { root: felt252, diff --git a/src/contracts/utils/utils.cairo b/src/library/utils.cairo similarity index 100% rename from src/contracts/utils/utils.cairo rename to src/library/utils.cairo diff --git a/src/contracts/option_round/contract.cairo b/src/option_round/contract.cairo similarity index 82% rename from src/contracts/option_round/contract.cairo rename to src/option_round/contract.cairo index 7230a332..d5fd3de7 100644 --- a/src/contracts/option_round/contract.cairo +++ b/src/option_round/contract.cairo @@ -1,28 +1,24 @@ #[starknet::contract] mod OptionRound { - use core::array::ArrayTrait; - use core::starknet::event::EventEmitter; - use core::option::OptionTrait; - use core::fmt::{Display, Formatter, Error}; + use core::{ + array::ArrayTrait, fmt::{Display, Error, Formatter}, num::traits::one::One, + option::OptionTrait, starknet::event::EventEmitter, + }; use openzeppelin::token::erc20::{ - ERC20Component, interface::{IERC20Metadata, ERC20ABIDispatcher, ERC20ABIDispatcherTrait,} + ERC20Component, interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Metadata}, }; - use starknet::{ContractAddress, get_caller_address, get_contract_address, get_block_timestamp}; - use pitch_lake_starknet::contracts::{ - market_aggregator::{IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait}, - { - components::red_black_tree::{RBTreeComponent, RBTreeComponent::Node}, - utils::utils::{min, max} + use pitch_lake_starknet::{ + library::{ + utils::{max, min}, red_black_tree::{RBTreeComponent, RBTreeComponent::Node} + }, + option_round::interface::IOptionRound, + vault::{interface::{IVaultDispatcher, IVaultDispatcherTrait},}, + types::{ + Bid, Errors, OptionRoundConstructorParams, OptionRoundState, SettleOptionRoundParams, + StartAuctionParams, VaultType }, - vault::{interface::{IVaultDispatcher, IVaultDispatcherTrait}, types::VaultType}, - option_round::{ - interface::IOptionRound, - types::{ - Bid, OptionRoundState, OptionRoundConstructorParams, StartAuctionParams, - SettleOptionRoundParams, OptionRoundError - } - } }; + use starknet::{get_block_timestamp, get_caller_address, get_contract_address, ContractAddress,}; // ERC20 Component component!(path: ERC20Component, storage: erc20, event: ERC20Event); @@ -376,26 +372,26 @@ mod OptionRound { // 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. - fn get_refundable_bids_for(self: @ContractState, option_buyer: ContractAddress) -> u256 { // Get the refundable, tokenizable, and partially sold bid ids let (mut tokenizable_bids, mut refundable_bids, partial_bid_id) = self .inspect_options_for(option_buyer); - // Check and sum bids that are not refunded yet + // Total refundable balance let mut refundable_balance = 0; - let clearing_price = self.get_auction_clearing_price(); + // Add refundable balance from Partial Bid if it's there - if (partial_bid_id != 0) { + if (partial_bid_id.is_non_zero()) { let partial_bid: Bid = self.bids_tree._find(partial_bid_id); - //Since only clearing_bid can be partially sold, the clearing_bid_amount_sold is saved on the tree + // @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(); if (!partial_bid.is_refunded) { refundable_balance += (partial_bid.amount - options_sold) * partial_bid.price; } } - // Add refundable balance from all (not already refunded) refundable bids + + // Add refundable balances from all (not already refunded) refundable bids loop { match refundable_bids.pop_front() { Option::Some(bid) => { @@ -406,15 +402,14 @@ mod OptionRound { Option::None => { break; } } }; - // Add refundable balance from all (not already refunded) over bids - // @dev An over bid in this context is when a bid's price is > the clearing price - - //Add difference from tokenizable bids only if the state is not open or auctioning + // Add refundable balances from all (not already refunded) over bids + // @dev An over bid in this context is when a bid's price is > the clearing price + let clearing_price = self.get_auction_clearing_price(); loop { match tokenizable_bids.pop_front() { Option::Some(bid) => { - if (!bid.is_refunded) { + if (bid.price > clearing_price && !bid.is_refunded) { refundable_balance += bid.amount * (bid.price - clearing_price) } }, @@ -471,7 +466,7 @@ mod OptionRound { // u256: number of options held by option_buyer // #Description // Returns sum of total tokenizable options and option round tokens held by option_buyer - // + // fn get_total_options_balance_for( self: @ContractState, option_buyer: ContractAddress ) -> u256 { @@ -547,7 +542,7 @@ mod OptionRound { // #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 + // @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 @@ -558,19 +553,19 @@ mod OptionRound { // 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, params: StartAuctionParams - ) -> Result { + fn start_auction(ref self: ContractState, params: StartAuctionParams) -> u256 { // Assert caller is Vault - if (!self.is_caller_the_vault()) { - return Result::Err(OptionRoundError::CallerIsNotVault); - } + self.assert_caller_is_vault(); // Assert state is Open - if (self.get_state() != OptionRoundState::Open) { - return Result::Err(OptionRoundError::AuctionAlreadyStarted); - } + assert(self.get_state() == OptionRoundState::Open, Errors::AuctionAlreadyStarted); + // Assert now is >= auction start date + let now = get_block_timestamp(); + let start_date = self.get_auction_start_date(); + assert(now >= start_date, Errors::AuctionStartDateNotReached); + + // Destructure params let StartAuctionParams { total_options_available, starting_liquidity, reserve_price, @@ -578,13 +573,6 @@ mod OptionRound { strike_price } = params; - // Assert now is >= auction start date - let now = get_block_timestamp(); - let start_date = self.get_auction_start_date(); - if (now < start_date) { - return Result::Err(OptionRoundError::AuctionStartDateNotReached); - } - // Set starting liquidity & total options available self.starting_liquidity.write(starting_liquidity); self.bids_tree.total_options_available.write(total_options_available); @@ -594,22 +582,25 @@ mod OptionRound { self.cap_level.write(cap_level); self.strike_price.write(strike_price); - // Update state to Auctioning - self.state.write(OptionRoundState::Auctioning); - // Update auction end date if the auction starts later than expected - self.auction_end_date.write(self.get_auction_end_date() + now - start_date); + if (now > start_date) { + let end_date = self.get_auction_end_date(); + self.auction_end_date.write(end_date + now - start_date); + } + + // Update state to Auctioning + self.set_state(OptionRoundState::Auctioning); // Emit auction start event self .emit( Event::AuctionStarted( - AuctionStarted { total_options_available: params.total_options_available } + AuctionStarted { total_options_available: total_options_available } ) ); // Return the total options available - Result::Ok(total_options_available) + total_options_available } @@ -622,29 +613,23 @@ mod OptionRound { // 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) -> Result<(u256, u256), OptionRoundError> { + fn end_auction(ref self: ContractState) -> (u256, u256) { // Assert caller is Vault - if (!self.is_caller_the_vault()) { - return Result::Err(OptionRoundError::CallerIsNotVault); - } + self.assert_caller_is_vault(); // Assert state is Auctioning - if (self.get_state() != OptionRoundState::Auctioning) { - return Result::Err(OptionRoundError::NoAuctionToEnd); - } + assert(self.get_state() == OptionRoundState::Auctioning, Errors::NoAuctionToEnd); // Assert now is >= auction end date let now = get_block_timestamp(); let end_date = self.get_auction_end_date(); - if (now < end_date) { - return Result::Err(OptionRoundError::AuctionEndDateNotReached); - } - - // Update state to Running - self.state.write(OptionRoundState::Running); + assert(now >= end_date, Errors::AuctionEndDateNotReached); // Update option settlement date if the auction ends later than expected - self.option_settlement_date.write(self.get_option_settlement_date() + now - end_date); + if (now > end_date) { + let settlement_date = self.get_option_settlement_date(); + self.option_settlement_date.write(settlement_date + now - end_date); + } // Calculate clearing price & total options sold let (clearing_price, total_options_sold) = self.update_clearing_price(); @@ -653,12 +638,14 @@ mod OptionRound { let eth = self.get_eth_dispatcher(); eth.transfer(self.vault_address(), self.total_premiums()); + // Update state to Running + self.set_state(OptionRoundState::Running); + // Emit auction ended event - // @note Should we emit total options sold ? self.emit(Event::AuctionEnded(AuctionEnded { clearing_price })); // Return clearing price & total options sold - Result::Ok((clearing_price, total_options_sold)) + (clearing_price, total_options_sold) } // fn settle_option_round @@ -670,39 +657,35 @@ mod OptionRound { // 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 - - fn settle_option_round( - ref self: ContractState, params: SettleOptionRoundParams - ) -> Result { + fn settle_option_round(ref self: ContractState, params: SettleOptionRoundParams) -> u256 { // Assert caller is Vault - if (!self.is_caller_the_vault()) { - return Result::Err(OptionRoundError::CallerIsNotVault); - } + self.assert_caller_is_vault(); // Assert now is >= option settlement date let now = get_block_timestamp(); - if (now < self.get_option_settlement_date()) { - return Result::Err(OptionRoundError::OptionSettlementDateNotReached); - } + let settlement_date = self.get_option_settlement_date(); + assert(now >= settlement_date, Errors::OptionSettlementDateNotReached); // Assert state is Running - if (self.get_state() != OptionRoundState::Running) { - return Result::Err(OptionRoundError::OptionRoundAlreadySettled); - } + assert( + self.get_state() == OptionRoundState::Running, Errors::OptionRoundAlreadySettled + ); - // Update state to Settled - self.state.write(OptionRoundState::Settled); + // Destructure params + let SettleOptionRoundParams { settlement_price } = params; // Calculate and set total payout - let SettleOptionRoundParams { settlement_price } = params; let total_payout = self.calculate_payout(settlement_price); self.total_payout.write(total_payout); + // Update state to Settled + self.set_state(OptionRoundState::Settled); + // Emit option settled event self.emit(Event::OptionRoundSettled(OptionRoundSettled { settlement_price })); // Return total payout - Result::Ok(total_payout) + total_payout } /// Option bidder functions @@ -719,32 +702,30 @@ mod OptionRound { // 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 - - fn place_bid( - ref self: ContractState, amount: u256, price: u256 - ) -> Result { + fn place_bid(ref self: ContractState, amount: u256, price: u256) -> Bid { + // Assert state is Auctioning + // @note BidRejcted event if reverted txns emit events, dont think they do + assert( + self.get_state() == OptionRoundState::Auctioning, Errors::BiddingWhileNotAuctioning + ); + // Assert now is < auction end date + // @dev This is for if a bid is placed before the end_auction function is called + // the auction end date has passed + assert( + get_block_timestamp() < self.get_auction_end_date(), + Errors::BiddingWhileNotAuctioning + ); + + // Assert bid if for more than 0 options + // @note BidRejcted event if reverted txns emit events, dont think they do + assert(amount.is_non_zero(), Errors::BidAmountZero); + + // Assert bid price is above reserve price + // @note BidRejcted event if reverted txns emit events, dont think they do + assert(price >= self.get_reserve_price(), Errors::BidBelowReservePrice); + + // Insert bid into storage (red-black tree) and update bidder nonce let bidder = get_caller_address(); - - //Assert round is auctioning - if (self.get_state() != OptionRoundState::Auctioning - || self.get_auction_end_date() < get_block_timestamp()) { - self.emit(Event::BidRejected(BidRejected { account: bidder, amount, price })); - return Result::Err(OptionRoundError::BiddingWhileNotAuctioning); - } - - //Assert bid if for more than 0 options - if (amount.is_zero()) { - self.emit(Event::BidRejected(BidRejected { account: bidder, amount, price })); - return Result::Err(OptionRoundError::BidAmountZero); - } - //Assert bid price is above reserve price - if (price < self.get_reserve_price()) { - self.emit(Event::BidRejected(BidRejected { account: bidder, amount, price })); - return Result::Err(OptionRoundError::BidBelowReservePrice); - } - - //Create and store bid, then update bidder nonce - let nonce = self.bidder_nonces.read(bidder); let bid = Bid { id: self.create_bid_id(bidder, nonce), @@ -758,11 +739,15 @@ mod OptionRound { self.bids_tree._insert(bid); self.bidder_nonces.write(bidder, nonce + 1); - //Transfer Eth + // Transfer Eth let eth_dispatcher = self.get_eth_dispatcher(); eth_dispatcher.transfer_from(bidder, get_contract_address(), amount * price); + + // Emit bid accepted event self.emit(Event::BidAccepted(BidAccepted { nonce, account: bidder, amount, price })); - Result::Ok(bid) + + // Return the bid + bid } // fn update_bid @@ -780,24 +765,24 @@ mod OptionRound { // 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 - ) -> Result { - //Assert round is still auctioning - if (self.get_state() != OptionRoundState::Auctioning) { - return Result::Err(OptionRoundError::BiddingWhileNotAuctioning); - } + ) -> Bid { + // Assert round is Auctioning + assert( + self.get_state() == OptionRoundState::Auctioning, Errors::BiddingWhileNotAuctioning + ); + // Assert caller owns the bid being updated let old_node: Node = self.bids_tree.tree.read(bid_id); - //Assert bid owner is the caller - if (old_node.value.owner != get_caller_address()) { - return Result::Err(OptionRoundError::CallerNotBidOwner); - } - //Assert new bid is > old bid + assert(old_node.value.owner == get_caller_address(), Errors::CallerNotBidOwner); + + // Assert new bid is > old bid let mut old_bid: Bid = old_node.value; - if (new_amount < old_bid.amount || new_price < old_bid.price) { - return Result::Err(OptionRoundError::BidCannotBeDecreased); - } + assert( + new_amount >= old_bid.amount && new_price >= old_bid.price, + Errors::BidCannotBeDecreased + ); - //Update bid + // Update bid let mut new_bid: Bid = old_bid; new_bid.amount = new_amount; new_bid.price = new_price; @@ -805,7 +790,7 @@ mod OptionRound { self.bids_tree._delete(bid_id); self.bids_tree._insert(new_bid); - //Charge the difference + // Charge the difference let difference = (new_amount * new_price) - (old_bid.amount * old_bid.price); let eth_dispatcher = self.get_eth_dispatcher(); eth_dispatcher.transfer_from(get_caller_address(), get_contract_address(), difference); @@ -819,13 +804,13 @@ mod OptionRound { account: get_caller_address(), old_amount: old_bid.amount, old_price: old_bid.price, - new_amount: new_bid.amount, - new_price: new_bid.price + new_amount: new_amount, + new_price: new_price } ) ); - Result::Ok(new_bid) + new_bid } // fn refund_unused_bids @@ -839,20 +824,21 @@ mod OptionRound { // 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 - ) -> Result { + fn refund_unused_bids(ref self: ContractState, option_bidder: ContractAddress) -> u256 { + // Assert state is Running or Settled let state = self.get_state(); - if (state == OptionRoundState::Auctioning || state == OptionRoundState::Open) { - return Result::Err(OptionRoundError::AuctionNotEnded); - } - // Get the refundable, tokenizable, and partially sold bid ids + assert( + state == OptionRoundState::Running || state == OptionRoundState::Settled, + Errors::AuctionNotEnded + ); + + // Get the refundable & tokenizable bids, and the partially sold bid id if it exists let (mut tokenizable_bids, mut refundable_bids, partial_bid_id) = self .inspect_options_for(option_bidder); - // Check and sum bids that are not refunded yet + // Total refunable balance let mut refundable_balance = 0; - let clearing_price = self.get_auction_clearing_price(); + // Add refundable balance from Partial Bid if it's there if (partial_bid_id != 0) { let mut partial_bid: Bid = self.bids_tree._find(partial_bid_id); @@ -865,7 +851,8 @@ mod OptionRound { refundable_balance += (partial_bid.amount - options_sold) * partial_bid.price; } } - // Add refundable balance from all (not already refunded) refundable bids + + // Add refundable balances from all (not already refunded) refundable bids loop { match refundable_bids.pop_front() { Option::Some(bid) => { @@ -879,12 +866,14 @@ mod OptionRound { Option::None => { break; } } }; - // Add refundable balance from all (not already refunded) over bids + + // Add refundable balances from all (not already refunded) over-bids // @dev An over bid in this context is when a bid's price is > the clearing price + let clearing_price = self.get_auction_clearing_price(); loop { match tokenizable_bids.pop_front() { Option::Some(bid) => { - if (!bid.is_refunded) { + if (bid.price > clearing_price && !bid.is_refunded) { let mut refundable_bid: Bid = self.bids_tree._find(bid.id); refundable_bid.is_refunded = true; self.bids_tree._update(refundable_bid.id, refundable_bid); @@ -894,9 +883,12 @@ mod OptionRound { Option::None => { break; } } }; + + // Transfer the refundable balance to the bidder let eth_dispatcher = self.get_eth_dispatcher(); eth_dispatcher.transfer(option_bidder, refundable_balance); + // Emit bids refunded event self .emit( Event::UnusedBidsRefunded( @@ -904,7 +896,7 @@ mod OptionRound { ) ); - Result::Ok(refundable_balance) + refundable_balance } // fn exercise_options @@ -919,26 +911,32 @@ mod OptionRound { // 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) -> Result { + fn exercise_options(ref self: ContractState) -> u256 { + // Assert the round is Settled + assert(self.get_state() == OptionRoundState::Settled, Errors::OptionRoundNotSettled); + + // Get the refundable & tokenizable bids, and the partially sold bid id if it exists let option_buyer = get_caller_address(); - if (self.get_state() != OptionRoundState::Settled) { - return Result::Err(OptionRoundError::OptionRoundNotSettled); - } let (mut tokenizable_bids, _, partial_bid_id) = self.inspect_options_for(option_buyer); + + // Total options to exercisable let mut options_to_exercise = 0; - //Check and sum bids that are not tokenized yet - //Add options balance from Partial Bid if it's there + + // Add tokenizable options from Partial Bid if it's there if (partial_bid_id.is_non_zero()) { let mut partial_bid: Bid = self.bids_tree._find(partial_bid_id); //Since only clearing_bid can be partially sold, the clearing_bid_amount_sold is saved on the tree if (!partial_bid.is_tokenized) { + // @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(); options_to_exercise += options_sold; partial_bid.is_tokenized = true; self.bids_tree._update(partial_bid_id, partial_bid); } } + + // Add tokenizable options from all (not already tokenized) tokenizable bids loop { match tokenizable_bids.pop_front() { Option::Some(mut bid) => { @@ -952,26 +950,36 @@ mod OptionRound { Option::None => { break; } } }; - let token_balance = self.erc20.ERC20_balances.read(option_buyer); - self.burn(option_buyer, token_balance); - options_to_exercise += token_balance; - let eth_dispatcher = self.get_eth_dispatcher(); - let amount_eth = options_to_exercise - * self.total_payout() + + // Account for the tokenized options held by the bidder if there are any + let erc20_option_balance = self.erc20.ERC20_balances.read(option_buyer); + if (erc20_option_balance.is_non_zero()) { + options_to_exercise += erc20_option_balance; + self.burn(option_buyer, erc20_option_balance); + } + + // The bidder's share of the total payout + let share_of_payout = self.total_payout() + * options_to_exercise / self.bids_tree.get_total_options_sold(); - eth_dispatcher.transfer(option_buyer, amount_eth); + + // Transfer the payout share to the bidder + let eth = self.get_eth_dispatcher(); + eth.transfer(option_buyer, share_of_payout); + + // Emit options exercised event self .emit( Event::OptionsExercised( OptionsExercised { account: option_buyer, num_options: options_to_exercise, - amount: amount_eth + amount: share_of_payout } ) ); - Result::Ok(amount_eth) + share_of_payout } // fn tokenize_options @@ -982,31 +990,39 @@ mod OptionRound { // #Description // Mint ERC20 tokens for winning bids // Checks that state is 'Ended' or after - // Gets tokenizable and partial tokenizable bids from internal helper, + // 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 tokenize_options(ref self: ContractState) -> Result { - let option_buyer = get_contract_address(); - //Check that the round is past auctioning state + fn tokenize_options(ref self: ContractState) -> u256 { + // Assert the round is Running or Settled let state = self.get_state(); - if (state == OptionRoundState::Auctioning || state == OptionRoundState::Open) { - return Result::Err(OptionRoundError::AuctionNotEnded); - } + assert( + state == OptionRoundState::Running || state == OptionRoundState::Settled, + Errors::AuctionNotEnded + ); + + // Get the refundable & tokenizable bids, and the partially sold bid id if it exists + let option_buyer = get_contract_address(); let (mut tokenizable_bids, _, partial_bid_id) = self.inspect_options_for(option_buyer); + + // Total tokenizable options let mut options_to_mint = 0; - //Check and sum bids that are not tokenized yet - //Add options balance from Partial Bid if it's there + + // Add tokenizable options from Partial Bid if it's there if (partial_bid_id.is_non_zero()) { let mut partial_bid: Bid = self.bids_tree._find(partial_bid_id); //Since only clearing_bid can be partially sold, the clearing_bid_amount_sold is saved on the tree if (!partial_bid.is_tokenized) { + // @dev Only the clearing_bid can be partially sold, the clearing_bid_amount_sold is saved on the tree let options_sold = self.bids_tree.clearing_bid_amount_sold.read(); options_to_mint += options_sold; partial_bid.is_tokenized = true; self.bids_tree._update(partial_bid_id, partial_bid); } } + + // Add tokenizable options from all (not already tokenized) tokenizable bids loop { match tokenizable_bids.pop_front() { Option::Some(mut bid) => { @@ -1020,14 +1036,19 @@ mod OptionRound { Option::None => { break; } } }; + + // Mint the options to the bidder self.mint(option_buyer, options_to_mint); + + // Emit options tokenized event self .emit( Event::OptionsTokenized( OptionsTokenized { account: option_buyer, amount: options_to_mint } ) ); - Result::Ok(options_to_mint) + + options_to_mint } } @@ -1040,6 +1061,11 @@ mod OptionRound { get_caller_address() == self.vault_address.read() } + // Assert that the caller is the Vault + fn assert_caller_is_vault(self: @ContractState) { + assert(get_caller_address() == self.vault_address(), Errors::CallerIsNotVault); + } + // Create the contract's ERC20 name and symbol fn get_name_symbol(self: @ContractState, round_id: u256) -> (ByteArray, ByteArray) { let name: ByteArray = format!("Pitch Lake Option Round {round_id}"); @@ -1047,6 +1073,11 @@ mod OptionRound { return (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() diff --git a/src/contracts/option_round/interface.cairo b/src/option_round/interface.cairo similarity index 82% rename from src/contracts/option_round/interface.cairo rename to src/option_round/interface.cairo index 4ff55f1d..e89d1272 100644 --- a/src/contracts/option_round/interface.cairo +++ b/src/option_round/interface.cairo @@ -1,13 +1,13 @@ use starknet::{ContractAddress, StorePacking}; use openzeppelin::token::erc20::interface::ERC20ABIDispatcher; -use pitch_lake_starknet::contracts::{ - market_aggregator::{IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait}, - option_round::{ - contract::OptionRound, - types::{ - OptionRoundState, StartAuctionParams, SettleOptionRoundParams, - OptionRoundConstructorParams, Bid, - } +use pitch_lake_starknet::{ + option_round::{contract::OptionRound,}, + contracts::{ + market_aggregator::{IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait}, + }, + types::{ + OptionRoundState, StartAuctionParams, SettleOptionRoundParams, OptionRoundConstructorParams, + Bid, } }; @@ -123,20 +123,16 @@ trait IOptionRound { // Try to start the option round's auction // @return the total options available in the auction - fn start_auction( - ref self: TContractState, params: StartAuctionParams - ) -> Result; + fn start_auction(ref self: TContractState, params: StartAuctionParams) -> 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 ?) - fn end_auction(ref self: TContractState) -> Result<(u256, u256), OptionRound::OptionRoundError>; + 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, params: SettleOptionRoundParams - ) -> Result; + fn settle_option_round(ref self: TContractState, params: SettleOptionRoundParams) -> u256; /// Option bidder functions @@ -147,27 +143,23 @@ trait IOptionRound { // @return if the bid was accepted or rejected // @note check all tests match new format (option amount, option price) - fn place_bid( - ref self: TContractState, amount: u256, price: u256 - ) -> Result; + 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 - ) -> Result; + ) -> 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 - ) -> Result; + fn refund_unused_bids(ref self: TContractState, option_bidder: 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 - fn exercise_options(ref self: TContractState) -> Result; + fn exercise_options(ref self: TContractState) -> u256; // Convert options won from auction into erc20 tokens - fn tokenize_options(ref self: TContractState) -> Result; + fn tokenize_options(ref self: TContractState) -> u256; } diff --git a/src/tests/deployment/constructor_tests.cairo b/src/tests/deployment/constructor_tests.cairo index fac7e9b1..060d3efa 100644 --- a/src/tests/deployment/constructor_tests.cairo +++ b/src/tests/deployment/constructor_tests.cairo @@ -5,20 +5,17 @@ use starknet::{ testing::{set_block_timestamp, set_contract_address} }; use pitch_lake_starknet::{ + library::eth::Eth, types::{OptionRoundState, OptionRoundConstructorParams}, + vault::{ + contract::Vault, + interface::{ + IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcherTrait + } + }, + option_round::{ + contract::{OptionRound,}, interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}, + }, contracts::{ - components::eth::Eth, - vault::{ - contract::Vault, - interface::{ - IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, - IVaultSafeDispatcherTrait - } - }, - option_round::{ - contract::{OptionRound,}, - interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}, - types::{OptionRoundState, OptionRoundConstructorParams,} - }, market_aggregator::{ IMarketAggregator, IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait, IMarketAggregatorSafeDispatcher, IMarketAggregatorSafeDispatcherTrait diff --git a/src/tests/deployment/initializing_option_round_params_tests.cairo b/src/tests/deployment/initializing_option_round_params_tests.cairo index 476dcae6..4e261b54 100644 --- a/src/tests/deployment/initializing_option_round_params_tests.cairo +++ b/src/tests/deployment/initializing_option_round_params_tests.cairo @@ -7,16 +7,14 @@ use starknet::{ use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; use pitch_lake_starknet::{ + library::eth::Eth, + vault::{contract::Vault, interface::{IVaultDispatcher, IVaultDispatcherTrait},}, + option_round::interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait}, contracts::{ - components::eth::Eth, pitch_lake::{ IPitchLakeDispatcher, IPitchLakeSafeDispatcher, IPitchLakeDispatcherTrait, PitchLake, IPitchLakeSafeDispatcherTrait }, - vault::{ - contract::Vault, interface::{IVaultDispatcher, IVaultDispatcherTrait}, types::VaultType - }, - option_round::interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait}, }, tests::{ utils::{ @@ -34,6 +32,7 @@ use pitch_lake_starknet::{ }, }, }, + types::VaultType }; use debug::PrintTrait; diff --git a/src/tests/misc/lp_token/lp_token_tests.cairo b/src/tests/misc/lp_token/lp_token_tests.cairo index 355eafe1..6784efdc 100644 --- a/src/tests/misc/lp_token/lp_token_tests.cairo +++ b/src/tests/misc/lp_token/lp_token_tests.cairo @@ -5,7 +5,7 @@ use starknet::{ }; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; use pitch_lake_starknet::{ - contracts::components::eth::Eth, + library::eth::Eth, tests::{ utils::{ helpers::{ diff --git a/src/tests/misc/pitch_lake_test.cairo b/src/tests/misc/pitch_lake_test.cairo index 169b226f..1586a35b 100644 --- a/src/tests/misc/pitch_lake_test.cairo +++ b/src/tests/misc/pitch_lake_test.cairo @@ -71,14 +71,15 @@ use starknet::{ }; use openzeppelin::utils::serde::SerializedAppend; use pitch_lake_starknet::{ + vault::{interface::{IVault, IVaultDispatcher, IVaultDispatcherTrait}}, contracts::{ pitch_lake::{ IPitchLake, IPitchLakeDispatcher, IPitchLakeDispatcherTrait, IPitchLakeSafeDispatcher, IPitchLakeSafeDispatcherTrait, PitchLake, }, - vault::{types::{VaultType}, interface::{IVault, IVaultDispatcher, IVaultDispatcherTrait}}, }, tests::utils::helpers::setup::{deploy_vault, deploy_market_aggregator, deploy_pitch_lake}, + types::{VaultType}, }; use debug::PrintTrait; diff --git a/src/tests/option_round/option_buyers/bidding_tests.cairo b/src/tests/option_round/option_buyers/bidding_tests.cairo index 6be6c29f..11172ea0 100644 --- a/src/tests/option_round/option_buyers/bidding_tests.cairo +++ b/src/tests/option_round/option_buyers/bidding_tests.cairo @@ -6,19 +6,15 @@ use starknet::{ }; use openzeppelin::{token::erc20::interface::{ERC20ABIDispatcherTrait,},}; use pitch_lake_starknet::{ - contracts::{ - components::eth::Eth, - vault::{ - contract::Vault, - interface::{ - IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, - IVaultSafeDispatcherTrait - } - }, - option_round::{ - contract::OptionRound, types::OptionRoundState, - interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait} - }, + library::eth::Eth, types::{OptionRoundState, Errors, BidDisplay}, + vault::{ + contract::Vault, + interface::{ + IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcherTrait + } + }, + option_round::{ + contract::OptionRound, interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait}, }, tests::{ utils::{ @@ -134,16 +130,14 @@ fn test_bid_price_below_reserve_fails() { let bid_amount = options_available; clear_event_logs(array![current_round.contract_address()]); - // Check bid rejected event - match current_round.place_bid_raw(bid_amount, bid_price, bidder) { - Result::Ok(_) => { panic!("Bid should have failed"); }, - Result::Err(_) => { - // Check bid rejected event - assert_event_auction_bid_rejected( - current_round.contract_address(), bidder, bid_amount, bid_price - ); - } - } + // Check txn revert reason + current_round + .place_bid_expect_error(bid_amount, bid_price, bidder, Errors::BidBelowReservePrice); +// @note Circle back depending if reverted txns emit events +// Check bid rejected event +//assert_event_auction_bid_rejected( +// current_round.contract_address(), bidder, bid_amount, bid_price +//); } // Test bidding before auction starts fails @@ -153,25 +147,20 @@ fn test_bid_before_auction_starts_failure() { let (mut vault, _) = setup_facade(); accelerate_to_auctioning(ref vault); accelerate_to_running(ref vault); - accelerate_to_settled(ref vault, 0); + accelerate_to_settled(ref vault, 1); // Try to place bid before auction starts let mut round2 = vault.get_current_round(); let bidder = option_bidder_buyer_1(); let bid_price = round2.get_reserve_price(); let bid_amount = round2.get_total_options_available(); - - // Check bid rejected event clear_event_logs(array![round2.contract_address()]); - match round2.place_bid_raw(bid_amount, bid_price, option_bidder_buyer_1()) { - Result::Ok(_) => { panic!("Bid should have failed"); }, - Result::Err(_) => { - // Check bid rejected event - assert_event_auction_bid_rejected( - round2.contract_address(), bidder, bid_amount, bid_price - ); - } - } + + // Check txn revert reason + round2.place_bid_expect_error(bid_amount, bid_price, bidder, Errors::BiddingWhileNotAuctioning); +// @note Circle back depending if reverted txns emit events +// Check bid rejected event +// assert_event_auction_bid_rejected(round2.contract_address(), bidder, bid_amount, bid_price); } // Test bidding after auction ends fails @@ -190,18 +179,15 @@ fn test_bid_after_auction_ends_failure() { let bidder = option_bidder_buyer_1(); let bid_price = round2.get_reserve_price(); let bid_amount = round2.get_total_options_available(); - // Check bid rejected event clear_event_logs(array![round2.contract_address()]); - match round2.place_bid_raw(bid_amount, bid_price, option_bidder_buyer_1()) { - Result::Ok(_) => { panic!("Bid should have failed"); }, - Result::Err(_) => { - // Check bid rejected event - assert_event_auction_bid_rejected( - round2.contract_address(), bidder, bid_amount, bid_price - ); - } - } + // Check txn revert reason + round2.place_bid_expect_error(bid_amount, bid_price, bidder, Errors::BiddingWhileNotAuctioning); +// @note Circle back depending if reverted txns emit events +// Check bid rejected event +//assert_event_auction_bid_rejected( +// round2.contract_address(), bidder, bid_amount, bid_price +//); } // Test bidding after auction end date fail (if end_auction() is not called first) @@ -220,18 +206,14 @@ fn test_bid_after_auction_end_failure_2() { let bidder = option_bidder_buyer_1(); let bid_price = round2.get_reserve_price(); let bid_amount = round2.get_total_options_available(); - - // Check bid rejected event clear_event_logs(array![round2.contract_address()]); - match round2.place_bid_raw(bid_amount, bid_price, option_bidder_buyer_1()) { - Result::Ok(_) => { panic!("Bid should have failed"); }, - Result::Err(_) => { - // Check bid rejected event - assert_event_auction_bid_rejected( - round2.contract_address(), bidder, bid_amount, bid_price - ); - } - } + + round2.place_bid_expect_error(bid_amount, bid_price, bidder, Errors::BiddingWhileNotAuctioning); +// @note Circle back depending if reverted txns emit events +// Check bid rejected event +// assert_event_auction_bid_rejected( +// round2.contract_address(), bidder, bid_amount, bid_price +// ); } /// Event Tests /// @@ -365,7 +347,10 @@ fn test_failed_bid_nonce_unchanged() { let nonce_before = current_round.get_bidding_nonce_for(bidder); if (i % 2 == 1) { //Failed bid in alternate rounds and check nonce update - let _ = current_round.place_bid_raw(bid_amount, bid_price - 1, bidder); + let _ = current_round + .place_bid_expect_error( + bid_amount, bid_price - 1, bidder, Errors::BidBelowReservePrice + ); let nonce_after = current_round.get_bidding_nonce_for(bidder); assert(nonce_before == nonce_after, 'Nonce Mismatch'); } else { diff --git a/src/tests/option_round/option_buyers/exercise_options_tests.cairo b/src/tests/option_round/option_buyers/exercise_options_tests.cairo index 7a3942ad..7f82d6b7 100644 --- a/src/tests/option_round/option_buyers/exercise_options_tests.cairo +++ b/src/tests/option_round/option_buyers/exercise_options_tests.cairo @@ -2,7 +2,7 @@ use core::traits::Into; use starknet::testing::{set_block_timestamp, set_contract_address}; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; use pitch_lake_starknet::{ - contracts::option_round::types::{OptionRoundError, OptionRoundErrorIntoFelt252}, + types::{Errors}, tests::{ utils::{ helpers::{ @@ -65,11 +65,8 @@ fn test_exercise_options_before_round_settles_fails() { accelerate_to_running(ref vault); // Try to exercise before round settles let mut current_round = vault.get_current_round(); - let mut expected_error = OptionRoundError::OptionRoundNotSettled; - match current_round.exercise_options_raw(option_bidder_buyer_1()) { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err == expected_error, 'Error misMatch'); }, - } + current_round + .exercise_options_expect_error(option_bidder_buyer_1(), Errors::OptionRoundNotSettled); } /// Event Tests /// diff --git a/src/tests/option_round/option_buyers/refunding_bids_tests.cairo b/src/tests/option_round/option_buyers/refunding_bids_tests.cairo index f2f9bbb8..8f473c4f 100644 --- a/src/tests/option_round/option_buyers/refunding_bids_tests.cairo +++ b/src/tests/option_round/option_buyers/refunding_bids_tests.cairo @@ -2,7 +2,7 @@ use core::traits::TryInto; use starknet::{ContractAddress, testing::{set_block_timestamp, set_contract_address}}; use openzeppelin::token::erc20::interface::{ERC20ABI, ERC20ABIDispatcher, ERC20ABIDispatcherTrait,}; use pitch_lake_starknet::{ - contracts::{option_round::types::OptionRoundError}, + types::{Errors}, tests::{ utils::{ helpers::{ @@ -97,12 +97,7 @@ fn test_refunding_bids_before_auction_end_fails() { let (_, _, mut current_round) = place_incremental_bids_internal(ref vault, option_bidders); // Try to refund bids before auction ends - let expected_error = OptionRoundError::AuctionNotEnded; - let res = current_round.refund_bid_raw(*option_bidders[0]); - match res { - Result::Ok(_) => { panic!("Should throw error"); }, - Result::Err(e) => { assert(e == expected_error, 'Error Mismatch'); } - } + current_round.refund_bid_expect_error(*option_bidders[0], Errors::AuctionNotEnded); } diff --git a/src/tests/option_round/option_buyers/tokenizing_options_tests.cairo b/src/tests/option_round/option_buyers/tokenizing_options_tests.cairo index 6a4146c1..f9c77bb9 100644 --- a/src/tests/option_round/option_buyers/tokenizing_options_tests.cairo +++ b/src/tests/option_round/option_buyers/tokenizing_options_tests.cairo @@ -1,27 +1,25 @@ -use pitch_lake_starknet::tests::{ - utils::{ - helpers::{ - accelerators::{ - accelerate_to_running_custom, accelerate_to_auctioning, - accelerate_to_running_custom_option_round, +use pitch_lake_starknet::{ + types::Errors, + tests::{ + utils::{ + helpers::{ + accelerators::{ + accelerate_to_running_custom, accelerate_to_auctioning, + accelerate_to_running_custom_option_round, + }, + setup::{setup_facade, deploy_custom_option_round}, + general_helpers::{get_erc20_balance, assert_two_arrays_equal_length}, + event_helpers::{assert_event_options_tokenized, clear_event_logs} + }, + lib::{test_accounts::{option_bidders_get, option_bidder_buyer_1},}, + facades::{ + vault_facade::{VaultFacade, VaultFacadeTrait}, + option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait, OptionRoundParams} }, - setup::{setup_facade, deploy_custom_option_round}, - general_helpers::{get_erc20_balance, assert_two_arrays_equal_length}, - event_helpers::{assert_event_options_tokenized, clear_event_logs} - }, - lib::{test_accounts::{option_bidders_get, option_bidder_buyer_1},}, - facades::{ - vault_facade::{VaultFacade, VaultFacadeTrait}, - option_round_facade::{ - OptionRoundFacade, OptionRoundFacadeTrait, OptionRoundParams, OptionRoundError - } }, - }, + } }; use starknet::{contract_address_const, testing::{set_block_timestamp}}; -// Test options can be tokenized -// Test options cannot be tokenized twice -// Test option balance returns erc20 balance and non tokenized balances // Test tokenizing options mints option tokens #[test] @@ -107,18 +105,11 @@ fn test_tokenizing_options_before_auction_end_fails() { let (mut vault, _) = setup_facade(); let mut current_round = vault.get_current_round(); let option_bidder = option_bidder_buyer_1(); - let expected_error = OptionRoundError::AuctionNotEnded; - let res = current_round.tokenize_options_raw(option_bidder); - match res { - Result::Ok(_) => { panic!("Should throw error") }, - Result::Err(e) => { assert(e == expected_error, 'Error mismatch') } - } + let err = Errors::AuctionNotEnded; + + current_round.tokenize_options_expect_error(option_bidder, err); accelerate_to_auctioning(ref vault); - let res = current_round.tokenize_options_raw(option_bidder); - match res { - Result::Ok(_) => { panic!("Should throw error") }, - Result::Err(e) => { assert(e == expected_error, 'Error mismatch') } - } + current_round.tokenize_options_expect_error(option_bidder, err); } 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 c51a2bf9..0a1a151b 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::{ - contracts::{option_round::types::OptionRoundError}, + types::Errors, tests::{ utils::{ helpers::{ @@ -8,7 +8,8 @@ use pitch_lake_starknet::{ event_helpers::{assert_event_auction_bid_updated, clear_event_logs}, }, lib::{ - test_accounts::{option_bidders_get, option_bidder_buyer_1}, variables::{decimals}, + test_accounts::{option_bidders_get, option_bidder_buyer_1, option_bidder_buyer_2}, + variables::{decimals}, }, facades::{ vault_facade::{VaultFacade, VaultFacadeTrait}, @@ -26,18 +27,16 @@ fn test_update_bids_amount_cannot_be_decreased() { let reserve_price = current_round.get_reserve_price(); // Place bids - let option_buyer = option_bidder_buyer_1(); + let bidder = option_bidder_buyer_1(); let bid_price = reserve_price; let mut bid_amount = options_available; - let bid_id = current_round.place_bid(bid_amount, bid_price, option_buyer); + let bid_id = current_round.place_bid(bid_amount, bid_price, bidder); // Update bid to lower price - let expected_error: felt252 = OptionRoundError::BidCannotBeDecreased.into(); - let res = current_round.update_bid_raw(bid_id, bid_amount - 1, bid_price); - match res { - Result::Ok(_) => panic!("Error expected"), - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + current_round + .update_bid_expect_error( + bid_id, bid_amount - 1, bid_price, bidder, Errors::BidCannotBeDecreased + ); } @@ -50,18 +49,16 @@ fn test_update_bids_price_cannot_be_decreased() { let reserve_price = current_round.get_reserve_price(); // Place bids - let option_buyer = option_bidder_buyer_1(); + let bidder = option_bidder_buyer_1(); let bid_price = reserve_price; let mut bid_amount = options_available; - let bid_id = current_round.place_bid(bid_amount, bid_price, option_buyer); + let bid_id = current_round.place_bid(bid_amount, bid_price, bidder); // Update bid to lower price - let expected_error: felt252 = OptionRoundError::BidCannotBeDecreased.into(); - let res = current_round.update_bid_raw(bid_id, bid_amount, bid_price - 1); - match res { - Result::Ok(_) => panic!("Error expected"), - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + current_round + .update_bid_expect_error( + bid_id, bid_amount, bid_price - 1, bidder, Errors::BidCannotBeDecreased + ); } #[test] @@ -106,18 +103,16 @@ fn test_update_bids_amount_cannot_be_decreased_event_if_price_is_increased() { let reserve_price = current_round.get_reserve_price(); // Place bids - let option_buyer = option_bidder_buyer_1(); + let bidder = option_bidder_buyer_1(); let bid_price = reserve_price; let mut bid_amount = options_available; - let bid_id = current_round.place_bid(bid_amount, bid_price, option_buyer); + let bid_id = current_round.place_bid(bid_amount, bid_price, bidder); - // Update bid to lower price and >> amount - let res = current_round.update_bid_raw(bid_id, bid_amount - 1, 10 * bid_price); - let expected_error: felt252 = OptionRoundError::BidCannotBeDecreased.into(); - match res { - Result::Ok(_) => panic!("Error expected"), - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + // 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] @@ -129,17 +124,38 @@ fn test_update_bids_price_cannot_be_decreased_event_if_amount_is_increased() { let reserve_price = current_round.get_reserve_price(); // Place bids - let option_buyer = option_bidder_buyer_1(); + let bidder = option_bidder_buyer_1(); let bid_price = reserve_price; let mut bid_amount = options_available; - let bid_id = current_round.place_bid(bid_amount, bid_price, option_buyer); + let bid_id = current_round.place_bid(bid_amount, bid_price, bidder); // Update bid to lower price and >> amount - let res = current_round.update_bid_raw(bid_id, 10 * bid_amount, bid_price - 1); - let expected_error: felt252 = OptionRoundError::BidCannotBeDecreased.into(); - match res { - Result::Ok(_) => panic!("Error expected"), - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + current_round + .update_bid_expect_error( + bid_id, 10 * bid_amount, bid_price - 1, bidder, Errors::BidCannotBeDecreased + ); +} + +// Test that bid cannot be updated by a non owner +#[test] +#[available_gas(50000000)] +fn test_update_bids_must_be_called_by_bid_owner() { + 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 non_bidder = option_bidder_buyer_2(); + let bid_price = reserve_price; + let mut bid_amount = options_available; + let bid_id = 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 + ); } diff --git a/src/tests/option_round/rb_tree/rb_tree_mock_contract.cairo b/src/tests/option_round/rb_tree/rb_tree_mock_contract.cairo index cf16a787..50780430 100644 --- a/src/tests/option_round/rb_tree/rb_tree_mock_contract.cairo +++ b/src/tests/option_round/rb_tree/rb_tree_mock_contract.cairo @@ -1,4 +1,4 @@ -use pitch_lake_starknet::{contracts::{option_round::{types::{Bid}}}}; +use pitch_lake_starknet::types::Bid; #[starknet::interface] trait IRBTreeMockContract { @@ -12,15 +12,15 @@ trait IRBTreeMockContract { #[starknet::contract] mod RBTreeMockContract { - use pitch_lake_starknet::contracts::components::red_black_tree::RBTreeComponent; - use pitch_lake_starknet::{contracts::{option_round::{types::Bid}}}; + use pitch_lake_starknet::library::red_black_tree::RBTreeComponent; + use pitch_lake_starknet::types::Bid; component!(path: RBTreeComponent, storage: rb_tree, event: RBTreeEvent); impl RBTreeImpl = RBTreeComponent::RBTreeImpl; impl RBTreeTestingImpl = RBTreeComponent::RBTreeTestingImpl; impl RBTreeOperationsImpl = RBTreeComponent::RBTreeOperationsImpl; - + #[storage] struct Storage { @@ -37,27 +37,27 @@ mod RBTreeMockContract { #[abi(embed_v0)] impl RBTreeMockContractImpl of super::IRBTreeMockContract { // Tree main functions - fn insert(ref self:ContractState, value: Bid) { + fn insert(ref self: ContractState, value: Bid) { self.rb_tree._insert(value); } - + fn find(self: @ContractState, bid_id: felt252) -> Bid { self.rb_tree._find(bid_id) } - + fn delete(ref self: ContractState, bid_id: felt252) { self.rb_tree._delete(bid_id); } - + // Tree testing functions fn get_tree_structure(self: @ContractState) -> Array> { self.rb_tree._get_tree_structure() } - + fn is_tree_valid(self: @ContractState) -> bool { self.rb_tree._is_tree_valid() } - + // Internal function for testing fn add_node(ref self: ContractState, value: Bid, color: bool, parent: felt252) -> felt252 { self.rb_tree._add_node(value, color, parent) 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 07c68bf0..f3af9aa2 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 @@ -1,20 +1,16 @@ +use core::pedersen::pedersen; use pitch_lake_starknet::{ + types::Bid, tests::{ utils::helpers::setup::setup_rb_tree_test, option_round::rb_tree::{ - rb_tree_tests::{ - insert, mock_address, MOCK_ADDRESS, delete, create_bid - }, + rb_tree_tests::{insert, mock_address, MOCK_ADDRESS, delete, create_bid}, rb_tree_mock_contract::{ IRBTreeMockContractDispatcher, IRBTreeMockContractDispatcherTrait } } }, - contracts::{ - option_round::types::Bid, - }, }; -use core::pedersen::pedersen; #[test] #[available_gas(50000000000)] @@ -22,7 +18,9 @@ use core::pedersen::pedersen; fn test_add_1_to_100_delete_100_to_1() { let rb_tree = setup_rb_tree_test(); let mut i = 1; - while i <= 100 { + while + i <= 100 + { insert(rb_tree, i, i.try_into().unwrap()); println!("Inserted: {:?}", i); let is_tree_valid = rb_tree.is_tree_valid(); @@ -31,7 +29,9 @@ fn test_add_1_to_100_delete_100_to_1() { }; i = 100; - while i >= 1 { + while + i >= 1 + { let id = poseidon::poseidon_hash_span( array![mock_address(MOCK_ADDRESS).into(), i.try_into().unwrap()].span() ); @@ -52,7 +52,9 @@ fn test_add_1_to_100_delete_100_to_1() { fn test_add_1_to_100_delete_1_to_100() { let rb_tree = setup_rb_tree_test(); let mut i = 1; - while i <= 100 { + while + i <= 100 + { insert(rb_tree, i, i.try_into().unwrap()); println!("Inserted: {:?}", i); let is_tree_valid = rb_tree.is_tree_valid(); @@ -61,7 +63,9 @@ fn test_add_1_to_100_delete_1_to_100() { }; i = 1; - while i <= 100 { + while + i <= 100 + { let id = poseidon::poseidon_hash_span( array![mock_address(MOCK_ADDRESS).into(), i.try_into().unwrap()].span() ); @@ -97,47 +101,47 @@ fn testing_random_insertion_and_deletion() { let mut i: u32 = 0; - while i < no_of_nodes - .try_into() - .unwrap() { - let price = random(i.try_into().unwrap()); - let nonce = i.try_into().unwrap(); + while + i < no_of_nodes.try_into().unwrap() + { + let price = random(i.try_into().unwrap()); + let nonce = i.try_into().unwrap(); - let new_bid = create_bid(price.try_into().unwrap(), nonce); + let new_bid = create_bid(price.try_into().unwrap(), nonce); - rb_tree.insert(new_bid); + rb_tree.insert(new_bid); - inserted_node_ids.append(new_bid.id); + inserted_node_ids.append(new_bid.id); - let bid = rb_tree.find(new_bid.id); - println!("Inserting price {}", bid.price); + let bid = rb_tree.find(new_bid.id); + println!("Inserting price {}", bid.price); - assert(bid.price == price.try_into().unwrap(), 'Insertion error'); + assert(bid.price == price.try_into().unwrap(), 'Insertion error'); - let is_tree_valid = rb_tree.is_tree_valid(); - assert(is_tree_valid, 'Tree is not valid'); + let is_tree_valid = rb_tree.is_tree_valid(); + assert(is_tree_valid, 'Tree is not valid'); - i += 1; - }; + i += 1; + }; let mut j: u32 = 0; - while j < no_of_nodes - .try_into() - .unwrap() { - let bid_id = inserted_node_ids.at(j); + while + j < no_of_nodes.try_into().unwrap() + { + let bid_id = inserted_node_ids.at(j); - delete(rb_tree, *bid_id); + delete(rb_tree, *bid_id); - let found_bid = rb_tree.find(*bid_id); + let found_bid = rb_tree.find(*bid_id); - assert(found_bid.id == 0, 'Bid delete error'); + assert(found_bid.id == 0, 'Bid delete error'); - let is_tree_valid = rb_tree.is_tree_valid(); - assert(is_tree_valid, 'Tree is not valid'); + let is_tree_valid = rb_tree.is_tree_valid(); + assert(is_tree_valid, 'Tree is not valid'); - println!("Deleted node no. {}", j); + println!("Deleted node no. {}", j); - j += 1; - } + j += 1; + } } 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 aab193a8..d8438f55 100644 --- a/src/tests/option_round/rb_tree/rb_tree_tests.cairo +++ b/src/tests/option_round/rb_tree/rb_tree_tests.cairo @@ -1,21 +1,15 @@ -use core::traits::TryInto; +use core::pedersen::pedersen; +use starknet::{contract_address_const, ContractAddress}; use pitch_lake_starknet::{ - contracts::{ - option_round::{ - types::{Bid}, - }, - }, + types::{Bid}, tests::{ option_round::{ rb_tree::rb_tree_mock_contract::{ IRBTreeMockContractDispatcher, IRBTreeMockContractDispatcherTrait } - } - } + },utils::helpers::setup::setup_rb_tree_test + }, }; -use starknet::{contract_address_const, ContractAddress}; -use core::pedersen::pedersen; -use pitch_lake_starknet::tests::utils::helpers::setup::setup_rb_tree_test; const BLACK: bool = false; const RED: bool = true; @@ -1284,13 +1278,14 @@ fn compare_tree_structures( let mut i = 0; // Compare outer array - while i < actual - .len() { - let actual_inner = actual[i]; - let expected_inner = expected[i]; - compare_inner(actual_inner, expected_inner); - i += 1; - } + while + i < actual.len() + { + let actual_inner = actual[i]; + let expected_inner = expected[i]; + compare_inner(actual_inner, expected_inner); + i += 1; + } } fn compare_inner(actual: @Array<(u256, bool, u128)>, expected: @Array<(u256, bool, u128)>) { @@ -1300,13 +1295,14 @@ fn compare_inner(actual: @Array<(u256, bool, u128)>, expected: @Array<(u256, boo let mut i = 0; - while i < actual - .len() { - let actual_tuple = *actual[i]; - let expected_tuple = *expected[i]; - compare_tuple(actual_tuple, expected_tuple); - i += 1; - } + while + i < actual.len() + { + let actual_tuple = *actual[i]; + let expected_tuple = *expected[i]; + compare_tuple(actual_tuple, expected_tuple); + i += 1; + } } fn compare_tuple(actual: (u256, bool, u128), expected: (u256, bool, u128)) { diff --git a/src/tests/option_round/state_transition/caller_is_not_vault_tests.cairo b/src/tests/option_round/state_transition/caller_is_not_vault_tests.cairo index c7f020ac..d18a60a5 100644 --- a/src/tests/option_round/state_transition/caller_is_not_vault_tests.cairo +++ b/src/tests/option_round/state_transition/caller_is_not_vault_tests.cairo @@ -1,6 +1,6 @@ use starknet::{get_block_timestamp, testing::{set_contract_address, set_block_timestamp}}; use pitch_lake_starknet::{ - contracts::{option_round::types::{StartAuctionParams, OptionRoundError}}, + types::{StartAuctionParams, Errors}, tests::{ utils::{ helpers::{ @@ -25,7 +25,7 @@ use pitch_lake_starknet::{ }; const salt: u64 = 0x123; -const expected_err: OptionRoundError = OptionRoundError::CallerIsNotVault; +const err: felt252 = Errors::CallerIsNotVault; // Test that only the vault can start an auction #[test] @@ -39,19 +39,17 @@ fn test_only_vault_can_start_auction() { set_contract_address(other_vault.contract_address()); - match round_to_start - .start_auction_raw( + round_to_start + .start_auction_expect_error( StartAuctionParams { total_options_available: 1, starting_liquidity: 1, reserve_price: 123, cap_level: 123, strike_price: 123, - } - ) { - Result::Ok(_) => panic!("Expected Result::Err(e)"), - Result::Err(e) => { assert(e == expected_err, 'error mismatch'); } - } + }, + err + ) } // @note Modify to check the Result of the function to be Result::Err(e) @@ -69,10 +67,7 @@ fn test_only_vault_can_end_auction() { set_block_timestamp(current_round.get_auction_end_date() + 1); set_contract_address(other_vault.contract_address()); - match current_round.end_auction_raw() { - Result::Ok(_) => panic!("Expected Result::Err(e)"), - Result::Err(e) => { assert(e == expected_err, 'error mismatch'); } - } + current_round.end_auction_expect_error(err); } // Test that only the vault can settle an option round @@ -89,9 +84,6 @@ fn test_only_vault_can_settle_option_round() { set_block_timestamp(current_round.get_option_settlement_date()); set_contract_address(other_vault.contract_address()); - match current_round.settle_option_round_raw(0x123) { - Result::Ok(_) => panic!("Expected Result::Err(e)"), - Result::Err(e) => { assert(e == expected_err, 'error mismatch'); } - } + current_round.settle_option_round_expect_error(0x123, err); } diff --git a/src/tests/option_round/state_transition/option_settle/calculated_payout_tests.cairo b/src/tests/option_round/state_transition/option_settle/calculated_payout_tests.cairo index 698bcaa1..c2757465 100644 --- a/src/tests/option_round/state_transition/option_settle/calculated_payout_tests.cairo +++ b/src/tests/option_round/state_transition/option_settle/calculated_payout_tests.cairo @@ -1,5 +1,5 @@ use pitch_lake_starknet::{ - contracts::utils::utils::{min, max}, + library::utils::{min, max}, tests::{ utils::{ helpers::{ diff --git a/src/tests/utils/facades/option_round_facade.cairo b/src/tests/utils/facades/option_round_facade.cairo index d6e02d63..5eed3e41 100644 --- a/src/tests/utils/facades/option_round_facade.cairo +++ b/src/tests/utils/facades/option_round_facade.cairo @@ -1,20 +1,22 @@ //Helper functions for posterity use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; -use pitch_lake_starknet::contracts::vault::{ - interface::{ - IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcherTrait, - }, - contract::Vault, types::VaultType -}; use starknet::{ContractAddress, testing::{set_contract_address}}; use pitch_lake_starknet::{ - contracts::option_round::{ - types::{OptionRoundError, OptionRoundErrorIntoFelt252}, + types::{VaultType, Errors}, + option_round::{ interface::{ IOptionRoundDispatcher, IOptionRoundDispatcherTrait, OptionRoundState, StartAuctionParams, SettleOptionRoundParams, OptionRoundConstructorParams, Bid, + IOptionRoundSafeDispatcher, IOptionRoundSafeDispatcherTrait, } }, + vault::{ + interface::{ + IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, + IVaultSafeDispatcherTrait, + }, + contract::Vault, + }, tests::{ utils::{ helpers::{ @@ -24,7 +26,7 @@ use pitch_lake_starknet::{ lib::{test_accounts::{vault_manager, bystander}, structs::{OptionRoundParams}}, facades::sanity_checks, } - } + }, }; #[derive(Drop)] @@ -34,66 +36,63 @@ struct OptionRoundFacade { #[generate_trait] impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { + fn get_safe_dispatcher(ref self: OptionRoundFacade) -> IOptionRoundSafeDispatcher { + IOptionRoundSafeDispatcher { contract_address: self.contract_address() } + } + /// Writes /// /// State transition - // @dev These functions are only accesible to the vault. They are included - // to test this. // Start the next option round's auction fn start_auction(ref self: OptionRoundFacade, params: StartAuctionParams,) -> u256 { - let res = self.start_auction_raw(params); - match res { - Result::Ok(total_options_available) => sanity_checks::start_auction( - ref self, total_options_available - ), - Result::Err(e) => { panic(array![e.into()]) } - } - } - - fn start_auction_raw( - ref self: OptionRoundFacade, params: StartAuctionParams, - ) -> Result { - self.option_round_dispatcher.start_auction(params) + let total_options_available = self.option_round_dispatcher.start_auction(params); + sanity_checks::start_auction(ref self, total_options_available) } // End the current option round's auction fn end_auction(ref self: OptionRoundFacade) -> (u256, u256) { - let res = self.end_auction_raw(); - match res { - Result::Ok(( - clearing_price, total_options_sold - )) => sanity_checks::end_auction(ref self, clearing_price, total_options_sold), - Result::Err(e) => panic(array![e.into()]), - } - } - - fn end_auction_raw(ref self: OptionRoundFacade) -> Result<(u256, u256), OptionRoundError> { - self.option_round_dispatcher.end_auction() + let (clearing_price, total_options_sold) = self.option_round_dispatcher.end_auction(); + sanity_checks::end_auction(ref self, clearing_price, total_options_sold) } // Settle the current option round fn settle_option_round(ref self: OptionRoundFacade, settlement_price: u256) -> u256 { - let res = self.settle_option_round_raw(settlement_price); - let res = match res { - Result::Ok(total_payout) => sanity_checks::settle_option_round(ref self, total_payout), - Result::Err(e) => panic(array![e.into()]), - }; + let total_payout = self + .option_round_dispatcher + .settle_option_round(SettleOptionRoundParams { settlement_price }); - //Get next round id and approvals for next round - let vault_address = self.vault_address(); - let vault_dispatcher = IVaultDispatcher { contract_address: vault_address }; + // Set ETH approvals for next round + let vault_dispatcher = IVaultDispatcher { contract_address: self.vault_address() }; let next_round_address = vault_dispatcher.get_option_round_address(self.get_round_id() + 1); eth_supply_and_approve_all_bidders(next_round_address, vault_dispatcher.eth_address()); - res + + sanity_checks::settle_option_round(ref self, total_payout) } - fn settle_option_round_raw( - ref self: OptionRoundFacade, settlement_price: u256, - ) -> Result { - self - .option_round_dispatcher + #[feature("safe_dispatcher")] + fn start_auction_expect_error( + ref self: OptionRoundFacade, params: StartAuctionParams, error: felt252, + ) { + let safe_option_round = self.get_safe_dispatcher(); + safe_option_round.start_auction(params).expect_err(error); + } + + #[feature("safe_dispatcher")] + fn end_auction_expect_error(ref self: OptionRoundFacade, error: felt252) { + let safe_option_round = self.get_safe_dispatcher(); + safe_option_round.end_auction().expect_err(error); + } + + + #[feature("safe_dispatcher")] + fn settle_option_round_expect_error( + ref self: OptionRoundFacade, settlement_price: u256, error: felt252, + ) { + let safe_option_round = self.get_safe_dispatcher(); + safe_option_round .settle_option_round(SettleOptionRoundParams { settlement_price }) + .expect_err(error); } @@ -105,14 +104,10 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { ref self: OptionRoundFacade, amount: u256, price: u256, bidder: ContractAddress, ) -> felt252 { set_contract_address(bidder); - let res = self.place_bid_raw(amount, price, bidder); - match res { - Result::Ok(bid) => { sanity_checks::place_bid(ref self, bidder, bid.id) }, - Result::Err(e) => panic(array![e.into()]), - } + let bid: Bid = self.option_round_dispatcher.place_bid(amount, price); + sanity_checks::place_bid(ref self, bidder, bid.id) } - // Place multiple bids for multiple option bidders // @return: Array of bid ids fn place_bids( @@ -129,11 +124,10 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { Option::Some(bidder) => { let bid_amount = amounts.pop_front().unwrap(); let bid_price = prices.pop_front().unwrap(); - // Make bid - let res = self.place_bid(*bid_amount, *bid_price, *bidder); + let bid_id = self.place_bid(*bid_amount, *bid_price, *bidder); // Append result - results.append(res); + results.append(bid_id); }, Option::None => { break (); } } @@ -143,77 +137,87 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { // Place a bid for an option bidder // @return: An result for whether the bid was accepted or rejected - fn place_bid_raw( + #[feature("safe_dispatcher")] + fn place_bid_expect_error( ref self: OptionRoundFacade, amount: u256, price: u256, option_bidder_buyer: ContractAddress, - ) -> Result { + error: felt252, + ) { set_contract_address(option_bidder_buyer); - self.option_round_dispatcher.place_bid(amount, price) + let safe_option_round = self.get_safe_dispatcher(); + safe_option_round.place_bid(amount, price).expect_err(error); } // Place multiple bids for multiple option bidders // @return: An result for whether the bids were accepted or rejected - fn place_bids_raw( + #[feature("safe_dispatcher")] + fn place_bids_expect_error( ref self: OptionRoundFacade, mut amounts: Span, mut prices: Span, mut bidders: Span, - ) -> Array> { + mut errors: Span, + ) { assert_two_arrays_equal_length(bidders, amounts); assert_two_arrays_equal_length(bidders, prices); - let mut results = array![]; + let safe_option_round = self.get_safe_dispatcher(); loop { match bidders.pop_front() { Option::Some(bidder) => { + set_contract_address(*bidder); let bid_amount = amounts.pop_front().unwrap(); let bid_price = prices.pop_front().unwrap(); + let error = errors.pop_front().unwrap(); // Make bid - let res = self.place_bid_raw(*bid_amount, *bid_price, *bidder); - // Append result - results.append(res); + safe_option_round.place_bid(*bid_amount, *bid_price).expect_err(*error); }, Option::None => { break (); } } }; - results } - + // Update a bid for an option bidder + // @return: The updated bid fn update_bid(ref self: OptionRoundFacade, id: felt252, amount: u256, price: u256) -> Bid { - let res = self.option_round_dispatcher.update_bid(id, amount, price); - match res { - Result::Ok(bid) => { sanity_checks::update_bid(ref self, id, bid) }, - Result::Err(e) => panic(array![e.into()]) - } + let bid = self.get_bid_details(id); + let bidder = bid.owner; + set_contract_address(bidder); + let updated_bid = self.option_round_dispatcher.update_bid(id, amount, price); + sanity_checks::update_bid(ref self, id, updated_bid) } - fn update_bid_raw( - ref self: OptionRoundFacade, id: felt252, amount: u256, price: u256, - ) -> Result { - self.option_round_dispatcher.update_bid(id, amount, price) + // @note add bidder as param for testing + #[feature("safe_dispatcher")] + fn update_bid_expect_error( + ref self: OptionRoundFacade, + id: felt252, + amount: u256, + price: u256, + bidder: ContractAddress, + error: felt252, + ) { + let safe_option_round = self.get_safe_dispatcher(); + safe_option_round.update_bid(id, amount, price).expect_err(error); } + // Refunds all unused bids of an option bidder // @return: The amount refunded - // @note: Call using bystander ? fn refund_bid(ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress) -> u256 { set_contract_address(option_bidder_buyer); let refundable_balance = self.get_refundable_bids_for(option_bidder_buyer); - let res = self.option_round_dispatcher.refund_unused_bids(option_bidder_buyer); - match res { - Result::Ok(amount) => { - sanity_checks::refund_bid(ref self, amount, refundable_balance) - }, - Result::Err(e) => panic(array![e.into()]), - } + let refunded_amount = self.option_round_dispatcher.refund_unused_bids(option_bidder_buyer); + sanity_checks::refund_bid(ref self, refunded_amount, refundable_balance) } - fn refund_bid_raw( - ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress - ) -> Result { - self.option_round_dispatcher.refund_unused_bids(option_bidder_buyer) + #[feature("safe_dispatcher")] + fn refund_bid_expect_error( + ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress, error: felt252, + ) { + let safe_option_round = self.get_safe_dispatcher(); + safe_option_round.refund_unused_bids(option_bidder_buyer).expect_err(error); } // Refunds all unused bids of multiple option bidders // @return: The amounts refunded @@ -235,22 +239,19 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { // @return: The payout amount // @note: Call using bystander ? fn exercise_options(ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress) -> u256 { - let individual_payout = self.get_payout_balance_for(option_bidder_buyer); set_contract_address(option_bidder_buyer); - let res = self.option_round_dispatcher.exercise_options(); - match res { - Result::Ok(payout) => sanity_checks::exercise_options( - ref self, payout, individual_payout - ), - Result::Err(e) => panic(array![e.into()]), - } + let individual_payout = self.get_payout_balance_for(option_bidder_buyer); + let exercised_amount = self.option_round_dispatcher.exercise_options(); + sanity_checks::exercise_options(ref self, exercised_amount, individual_payout) } - fn exercise_options_raw( - ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress - ) -> Result { + #[feature("safe_dispatcher")] + fn exercise_options_expect_error( + ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress, error: felt252, + ) { set_contract_address(option_bidder_buyer); - self.option_round_dispatcher.exercise_options() + let safe_option_round = self.get_safe_dispatcher(); + safe_option_round.exercise_options().expect_err(error); } // Exercise options for multiple option buyers @@ -268,25 +269,26 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { payouts } + // Tokenize options for an option buyer + // @return: The amount of options minted fn tokenize_options(ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress) -> u256 { + set_contract_address(option_bidder_buyer); let option_erc20_balance_before = get_erc20_balance( self.contract_address(), option_bidder_buyer ); - set_contract_address(option_bidder_buyer); - let res = self.option_round_dispatcher.tokenize_options(); - match res { - Result::Ok(options_minted) => sanity_checks::tokenize_options( - ref self, option_bidder_buyer, option_erc20_balance_before, options_minted, - ), - Result::Err(e) => panic(array![e.into()]), - } + let options_minted = self.option_round_dispatcher.tokenize_options(); + sanity_checks::tokenize_options( + ref self, option_bidder_buyer, option_erc20_balance_before, options_minted, + ) } - fn tokenize_options_raw( - ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress - ) -> Result { + #[feature("safe_dispatcher")] + fn tokenize_options_expect_error( + ref self: OptionRoundFacade, option_bidder_buyer: ContractAddress, error: felt252, + ) { set_contract_address(option_bidder_buyer); - self.option_round_dispatcher.tokenize_options() + let safe_option_round = self.get_safe_dispatcher(); + safe_option_round.tokenize_options().expect_err(error); } /// Reads /// diff --git a/src/tests/utils/facades/sanity_checks.cairo b/src/tests/utils/facades/sanity_checks.cairo index ca86122f..84c43a3f 100644 --- a/src/tests/utils/facades/sanity_checks.cairo +++ b/src/tests/utils/facades/sanity_checks.cairo @@ -1,8 +1,7 @@ use starknet::{ContractAddress, testing::{set_contract_address}}; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; use pitch_lake_starknet::{ - //vault::{IVaultDispatcherTrait}, - contracts::option_round::{interface::IOptionRoundDispatcherTrait, types::Bid}, + types::Bid, option_round::{interface::IOptionRoundDispatcherTrait}, tests::{ utils::{ helpers::{ @@ -104,7 +103,7 @@ fn withdraw( ref vault: VaultFacade, liquidity_provider: ContractAddress, unlocked_amount: u256 ) -> u256 { let expected_unlocked_amount = vault.get_lp_unlocked_balance(liquidity_provider); - assert(unlocked_amount == expected_unlocked_amount, 'Deposit sanity check fail'); + assert(unlocked_amount == expected_unlocked_amount, 'Withdraw sanity check fail'); expected_unlocked_amount } diff --git a/src/tests/utils/facades/vault_facade.cairo b/src/tests/utils/facades/vault_facade.cairo index 2951f1cf..ecf88d6b 100644 --- a/src/tests/utils/facades/vault_facade.cairo +++ b/src/tests/utils/facades/vault_facade.cairo @@ -1,11 +1,15 @@ use starknet::{ContractAddress, testing::{set_contract_address, set_block_timestamp}}; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use pitch_lake_starknet::{ + vault::{ + interface::{ + IVaultDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcher, IVaultSafeDispatcherTrait + } + }, + option_round::{ + contract::OptionRound, interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,} + }, contracts::{ - vault::{types::VaultError, interface::{IVaultDispatcher, IVaultDispatcherTrait}}, - option_round::{ - contract::OptionRound, interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,} - }, market_aggregator::{ MarketAggregator, IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait }, @@ -38,20 +42,20 @@ struct VaultFacade { #[generate_trait] impl VaultFacadeImpl of VaultFacadeTrait { + fn get_safe_dispatcher(ref self: VaultFacade) -> IVaultSafeDispatcher { + IVaultSafeDispatcher { contract_address: self.contract_address() } + } + /// Writes /// /// LP functions 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 res = self.vault_dispatcher.deposit_liquidity(amount, liquidity_provider); - - match res { - Result::Ok(updated_unlocked_position) => { - sanity_checks::deposit(ref self, liquidity_provider, updated_unlocked_position) - }, - Result::Err(e) => panic(array![e.into()]), - } + let updated_unlocked_position = self + .vault_dispatcher + .deposit_liquidity(amount, liquidity_provider); + sanity_checks::deposit(ref self, liquidity_provider, updated_unlocked_position) } fn deposit_multiple( @@ -73,20 +77,19 @@ impl VaultFacadeImpl of VaultFacadeTrait { updated_unlocked_positions } - fn withdraw_raw( - ref self: VaultFacade, amount: u256, liquidity_provider: ContractAddress - ) -> Result { + #[feature("safe_dispatcher")] + fn withdraw_expect_error( + ref self: VaultFacade, amount: u256, liquidity_provider: ContractAddress, error: felt252, + ) { set_contract_address(liquidity_provider); - self.vault_dispatcher.withdraw_liquidity(amount) + let safe_vault = self.get_safe_dispatcher(); + safe_vault.withdraw_liquidity(amount).expect_err(error); } fn withdraw(ref self: VaultFacade, amount: u256, liquidity_provider: ContractAddress) -> u256 { - match self.withdraw_raw(amount, liquidity_provider) { - Result::Ok(updated_unlocked_position) => sanity_checks::withdraw( - ref self, liquidity_provider, updated_unlocked_position - ), - Result::Err(e) => panic(array![e.into()]), - } + set_contract_address(liquidity_provider); + let updated_unlocked_position = self.vault_dispatcher.withdraw_liquidity(amount); + sanity_checks::withdraw(ref self, liquidity_provider, updated_unlocked_position) } fn withdraw_multiple( @@ -95,17 +98,17 @@ impl VaultFacadeImpl of VaultFacadeTrait { mut liquidity_providers: Span ) -> Array { assert_two_arrays_equal_length(liquidity_providers, amounts); - let mut spreads = array![]; + let mut unlocked_bals = array![]; loop { match liquidity_providers.pop_front() { Option::Some(liquidity_provider) => { let amount = amounts.pop_front().unwrap(); - spreads.append(self.withdraw(*amount, *liquidity_provider)); + unlocked_bals.append(self.withdraw(*amount, *liquidity_provider)); }, Option::None => { break (); } }; }; - spreads + unlocked_bals } /// State transition @@ -113,63 +116,51 @@ impl VaultFacadeImpl of VaultFacadeTrait { fn start_auction(ref self: VaultFacade) -> u256 { // @dev Using bystander as caller so that gas fees do not throw off balance calculations set_contract_address(bystander()); - let res = self.vault_dispatcher.start_auction(); - match res { - Result::Ok(total_options_available) => { - let mut current_round = self.get_current_round(); - sanity_checks::start_auction(ref current_round, total_options_available) - }, - Result::Err(e) => panic(array![e.into()]), - } + let mut current_round = self.get_current_round(); + let total_options_available = self.vault_dispatcher.start_auction(); + sanity_checks::start_auction(ref current_round, total_options_available) } - fn start_auction_raw(ref self: VaultFacade) -> Result { + #[feature("safe_dispatcher")] + fn start_auction_expect_error(ref self: VaultFacade, error: felt252) { // @dev Using bystander as caller so that gas fees do not throw off balance calculations set_contract_address(bystander()); - self.vault_dispatcher.start_auction() + let safe_vault = self.get_safe_dispatcher(); + safe_vault.start_auction().expect_err(error); } fn end_auction(ref self: VaultFacade) -> (u256, u256) { // @dev Using bystander as caller so that gas fees do not throw off balance calculations set_contract_address(bystander()); - let res = self.vault_dispatcher.end_auction(); - match res { - Result::Ok(( - clearing_price, total_options_sold - )) => { - let mut current_round = self.get_current_round(); - sanity_checks::end_auction(ref current_round, clearing_price, total_options_sold) - }, - Result::Err(e) => panic(array![e.into()]), - } + let (clearing_price, total_options_sold) = self.vault_dispatcher.end_auction(); + let mut current_round = self.get_current_round(); + sanity_checks::end_auction(ref current_round, clearing_price, total_options_sold) } - fn end_auction_raw(ref self: VaultFacade) -> Result<(u256, u256), VaultError> { + #[feature("safe_dispatcher")] + fn end_auction_expect_error(ref self: VaultFacade, error: felt252) { set_contract_address(bystander()); - self.vault_dispatcher.end_auction() + let safe_vault = self.get_safe_dispatcher(); + safe_vault.end_auction().expect_err(error); } fn settle_option_round(ref self: VaultFacade) -> u256 { // @dev Using bystander as caller so that gas fees do not throw off balance calculations set_contract_address(bystander()); let mut current_round = self.get_current_round(); - let res = self.vault_dispatcher.settle_option_round(); - - let res = match res { - Result::Ok(total_payout) => sanity_checks::settle_option_round( - ref current_round, total_payout - ), - Result::Err(e) => panic(array![e.into()]), - }; + let total_payout = self.vault_dispatcher.settle_option_round(); + sanity_checks::settle_option_round(ref current_round, total_payout); let next_round_address = self.get_option_round_address(current_round.get_round_id() + 1); eth_supply_and_approve_all_bidders(next_round_address, self.get_eth_address()); - res + total_payout } - fn settle_option_round_raw(ref self: VaultFacade) -> Result { + #[feature("safe_dispatcher")] + fn settle_option_round_expect_error(ref self: VaultFacade, error: felt252) { set_contract_address(bystander()); - self.vault_dispatcher.settle_option_round() + let safe_vault = self.get_safe_dispatcher(); + safe_vault.settle_option_round().expect_err(error); } /// Fossil @@ -209,14 +200,9 @@ impl VaultFacadeImpl of VaultFacadeTrait { liquidity_provider: ContractAddress ) -> u256 { set_contract_address(liquidity_provider); - let res = self + self .vault_dispatcher - .convert_lp_tokens_to_newer_lp_tokens(source_round, target_round, amount); - - match res { - Result::Ok(lp_tokens) => lp_tokens, - Result::Err(e) => panic(array![e.into()]), - } + .convert_lp_tokens_to_newer_lp_tokens(source_round, target_round, amount) } /// Reads /// diff --git a/src/tests/utils/helpers/accelerators.cairo b/src/tests/utils/helpers/accelerators.cairo index e811fc33..2c55d54e 100644 --- a/src/tests/utils/helpers/accelerators.cairo +++ b/src/tests/utils/helpers/accelerators.cairo @@ -3,23 +3,21 @@ use starknet::{ testing::{set_block_timestamp, set_contract_address} }; use pitch_lake_starknet::{ + types::{StartAuctionParams, OptionRoundState, VaultType}, + vault::{contract::Vault, interface::{IVaultDispatcher, IVaultDispatcherTrait}}, + option_round::{ + contract::{OptionRound}, + interface::{ + IOptionRoundDispatcher, IOptionRoundDispatcherTrait, IOptionRoundSafeDispatcher, + IOptionRoundSafeDispatcherTrait + }, + }, contracts::{ market_aggregator::{ MarketAggregator, IMarketAggregator, IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait, IMarketAggregatorSafeDispatcher, IMarketAggregatorSafeDispatcherTrait }, - vault::{ - contract::Vault, types::VaultType, interface::{IVaultDispatcher, IVaultDispatcherTrait} - }, - option_round::{ - contract::{OptionRound}, - interface::{ - IOptionRoundDispatcher, IOptionRoundDispatcherTrait, IOptionRoundSafeDispatcher, - IOptionRoundSafeDispatcherTrait - }, - types::{StartAuctionParams, OptionRoundState,} - }, }, tests::{ utils::{ @@ -143,7 +141,6 @@ fn accelerate_to_running_custom_option_round( set_contract_address(vault_address); set_block_timestamp(auction_start_date + 1); - //Should this be called from the option round?? option_round .start_auction( StartAuctionParams { @@ -157,7 +154,7 @@ fn accelerate_to_running_custom_option_round( // Make bids let mut option_bidders = option_bidders_get(bid_amounts.len()).span(); - option_round.place_bids_raw(bid_amounts, bid_prices, option_bidders); + option_round.place_bids(bid_amounts, bid_prices, option_bidders); // End auction set_contract_address(vault_address); @@ -205,10 +202,7 @@ fn timeskip_past_round_transition_period(ref self: VaultFacade) { fn timeskip_and_start_auction(ref self: VaultFacade) -> u256 { timeskip_past_round_transition_period(ref self); set_contract_address(bystander()); - match self.vault_dispatcher.start_auction() { - Result::Ok(options_available) => options_available, - Result::Err(e) => panic(array!['Error:', e.into()]) - } + self.vault_dispatcher.start_auction() } // Jump to the auction end date and end the auction @@ -216,10 +210,7 @@ fn timeskip_and_end_auction(ref self: VaultFacade) -> (u256, u256) { let mut current_round = self.get_current_round(); set_block_timestamp(current_round.get_auction_end_date() + 1); set_contract_address(bystander()); - match self.vault_dispatcher.end_auction() { - Result::Ok((clearing_price, options_sold)) => (clearing_price, options_sold), - Result::Err(e) => panic(array!['Error:', e.into()]) - } + self.vault_dispatcher.end_auction() } // Jump to the option expriry date and settle the round diff --git a/src/tests/utils/helpers/event_helpers.cairo b/src/tests/utils/helpers/event_helpers.cairo index ad09f6f1..ba3b0e58 100644 --- a/src/tests/utils/helpers/event_helpers.cairo +++ b/src/tests/utils/helpers/event_helpers.cairo @@ -1,8 +1,9 @@ use core::array::SpanTrait; use starknet::{testing, ContractAddress,}; -use pitch_lake_starknet::contracts::{vault::contract::Vault, option_round::contract::OptionRound,}; -use openzeppelin::token::erc20::{ERC20Component, ERC20Component::Transfer}; -use openzeppelin::{utils::serde::SerializedAppend,}; +use openzeppelin::{ + utils::serde::SerializedAppend, token::erc20::{ERC20Component, ERC20Component::Transfer} +}; +use pitch_lake_starknet::{vault::contract::Vault, option_round::contract::OptionRound,}; use debug::PrintTrait; // Helpers diff --git a/src/tests/utils/helpers/setup.cairo b/src/tests/utils/helpers/setup.cairo index 8db67c4f..f785a4bf 100644 --- a/src/tests/utils/helpers/setup.cairo +++ b/src/tests/utils/helpers/setup.cairo @@ -11,8 +11,17 @@ use openzeppelin::{ token::erc20::{ERC20Component, interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait,}} }; use pitch_lake_starknet::{ + types::{StartAuctionParams, OptionRoundState, VaultType}, + library::{eth::Eth}, + vault::{contract::Vault, interface::{IVaultDispatcher, IVaultDispatcherTrait}}, + option_round::{ + contract::OptionRound, + interface::{ + IOptionRoundDispatcher, IOptionRoundDispatcherTrait, IOptionRoundSafeDispatcher, + IOptionRoundSafeDispatcherTrait, + }, + }, contracts::{ - components::{eth::Eth}, pitch_lake::{ IPitchLakeDispatcher, IPitchLakeSafeDispatcher, IPitchLakeDispatcherTrait, PitchLake, IPitchLakeSafeDispatcherTrait @@ -22,20 +31,14 @@ use pitch_lake_starknet::{ IMarketAggregatorDispatcherTrait, IMarketAggregatorSafeDispatcher, IMarketAggregatorSafeDispatcherTrait }, - vault::{ - contract::Vault, types::VaultType, interface::{IVaultDispatcher, IVaultDispatcherTrait} - }, - option_round::{ - contract::OptionRound, - interface::{ - IOptionRoundDispatcher, IOptionRoundDispatcherTrait, IOptionRoundSafeDispatcher, - IOptionRoundSafeDispatcherTrait, - }, - types::{StartAuctionParams, OptionRoundState,} - }, }, tests::{ - option_round::rb_tree::{rb_tree_mock_contract::{RBTreeMockContract, IRBTreeMockContractDispatcher, IRBTreeMockContractDispatcherTrait }}, + option_round::rb_tree::{ + rb_tree_mock_contract::{ + RBTreeMockContract, IRBTreeMockContractDispatcher, + IRBTreeMockContractDispatcherTrait + } + }, utils::{ lib::{ structs::{OptionRoundParams}, diff --git a/src/tests/vault/liquidity_providers/deposit_tests.cairo b/src/tests/vault/liquidity_providers/deposit_tests.cairo index 8da30552..12ba51c6 100644 --- a/src/tests/vault/liquidity_providers/deposit_tests.cairo +++ b/src/tests/vault/liquidity_providers/deposit_tests.cairo @@ -7,13 +7,9 @@ use starknet::{ }; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; use pitch_lake_starknet::{ - contracts::{ - components::eth::Eth, vault::contract::Vault, - option_round::{ - types::{OptionRoundState}, - interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,} - }, - }, + types::{OptionRoundState}, vault::contract::Vault, + option_round::{interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}}, + library::eth::Eth, tests::{ utils::{ helpers::{ diff --git a/src/tests/vault/liquidity_providers/withdraw_tests.cairo b/src/tests/vault/liquidity_providers/withdraw_tests.cairo index d65a4618..c1c7015d 100644 --- a/src/tests/vault/liquidity_providers/withdraw_tests.cairo +++ b/src/tests/vault/liquidity_providers/withdraw_tests.cairo @@ -7,7 +7,7 @@ use starknet::{ testing::{set_block_timestamp, set_contract_address} }; use pitch_lake_starknet::{ - contracts::{components::eth::Eth, vault::{contract::Vault, types::VaultError}}, + types::Errors, library::eth::Eth, vault::{contract::Vault}, tests::{ utils::{ helpers::{ @@ -60,11 +60,10 @@ fn test_withdrawing_more_than_unlocked_balance_fails() { // Try to withdraw more than unlocked balance let unlocked_balance = vault.get_lp_unlocked_balance(liquidity_provider); - let expected_error: felt252 = VaultError::InsufficientBalance.into(); - match vault.withdraw_raw(unlocked_balance + 1, liquidity_provider) { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(e) => { assert(e.into() == expected_error, 'Error mismatch'); } - } + vault + .withdraw_expect_error( + unlocked_balance + 1, liquidity_provider, Errors::InsufficientBalance + ); } @@ -124,7 +123,7 @@ fn test_withdrawing_from_vault_eth_transfer() { let vault_balance_before = eth.balance_of(vault.contract_address()); // Withdraw from vault - vault.withdraw_multiple(deposit_amounts, liquidity_providers,); + vault.withdraw_multiple(deposit_amounts, liquidity_providers); let total_withdrawals = sum_u256_array(deposit_amounts); // Liquidity provider and vault eth balances after withdrawal diff --git a/src/tests/vault/state_transition/auction_end_tests.cairo b/src/tests/vault/state_transition/auction_end_tests.cairo index fac7f386..aaccace9 100644 --- a/src/tests/vault/state_transition/auction_end_tests.cairo +++ b/src/tests/vault/state_transition/auction_end_tests.cairo @@ -6,20 +6,14 @@ use starknet::{ }; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; use pitch_lake_starknet::{ - contracts::{ - components::eth::Eth, - vault::{ - contract::Vault, - interface::{ - IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, - IVaultSafeDispatcherTrait, VaultError - } - }, - option_round::{ - interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}, - types::{OptionRoundState, OptionRoundError} + library::eth::Eth, types::{OptionRoundState, Errors}, + vault::{ + contract::Vault, + interface::{ + IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcherTrait }, }, + option_round::{interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,},}, tests::{ utils::{ helpers::{ @@ -70,11 +64,7 @@ fn test_ending_auction_before_it_starts_fails() { let (mut vault_facade, _) = setup_facade(); // Try to end auction before it starts - let expected_error: felt252 = OptionRoundError::NoAuctionToEnd.into(); - match vault_facade.end_auction_raw() { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + vault_facade.end_auction_expect_error(Errors::NoAuctionToEnd); } // @note This test will not pass until auction start is implemented @@ -86,11 +76,7 @@ fn test_ending_auction_before_auction_end_date_fails() { accelerate_to_auctioning(ref vault); // Try to end auction before auction end date - let expected_error: felt252 = OptionRoundError::AuctionEndDateNotReached.into(); - match vault.end_auction_raw() { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + vault.end_auction_expect_error(Errors::AuctionEndDateNotReached); } // Test ending the auction after it already ended fails @@ -100,11 +86,7 @@ fn test_ending_auction_while_round_running_fails() { let (mut vault_facade, _) = setup_test_running(); // Try to end auction after it has already ended - let expected_error: felt252 = OptionRoundError::NoAuctionToEnd.into(); - match vault_facade.end_auction_raw() { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + vault_facade.end_auction_expect_error(Errors::NoAuctionToEnd); } // Test ending the auction after the auction ends fails (next state) @@ -117,11 +99,7 @@ fn test_ending_auction_while_round_settled_fails() { accelerate_to_settled(ref vault_facade, 0); // Try to end auction before round transition period is over - let expected_error: felt252 = OptionRoundError::NoAuctionToEnd.into(); - match vault_facade.end_auction_raw() { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + vault_facade.end_auction_expect_error(Errors::NoAuctionToEnd); } diff --git a/src/tests/vault/state_transition/auction_start_tests.cairo b/src/tests/vault/state_transition/auction_start_tests.cairo index 2446c0fe..17baefcf 100644 --- a/src/tests/vault/state_transition/auction_start_tests.cairo +++ b/src/tests/vault/state_transition/auction_start_tests.cairo @@ -5,20 +5,14 @@ use starknet::{ }; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; use pitch_lake_starknet::{ - contracts::{ - components::eth::Eth, - vault::{ - contract::Vault, - interface::{ - IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, - IVaultSafeDispatcherTrait - } - }, - option_round::{ - interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}, - types::{OptionRoundState, OptionRoundError} - }, + types::{OptionRoundState, Errors}, library::eth::Eth, + vault::{ + contract::Vault, + interface::{ + IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcherTrait + } }, + option_round::{interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,},}, tests::{ utils::{ helpers::{ @@ -63,11 +57,7 @@ fn test_starting_auction_while_round_auctioning_fails() { accelerate_to_auctioning(ref vault_facade); // Try to start auction while round is Auctioning - let expected_error: felt252 = OptionRoundError::AuctionAlreadyStarted.into(); - match vault_facade.start_auction_raw() { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + vault_facade.start_auction_expect_error(Errors::AuctionAlreadyStarted); } // Test starting an auction after one ends fails @@ -79,11 +69,7 @@ fn test_starting_auction_while_round_running_fails() { accelerate_to_running(ref vault_facade); // Try to start auction while round is Running - let expected_error: felt252 = OptionRoundError::AuctionAlreadyStarted.into(); - match vault_facade.start_auction_raw() { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + vault_facade.start_auction_expect_error(Errors::AuctionAlreadyStarted); } // Test starting an auction before the round transition period is over fails @@ -96,11 +82,7 @@ fn test_starting_auction_while_round_settled_before_round_transition_period_over accelerate_to_settled(ref vault_facade, 0); // Try to start auction while round is Settled, before round transition period is over - let expected_error: felt252 = OptionRoundError::AuctionStartDateNotReached.into(); - match vault_facade.start_auction_raw() { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + vault_facade.start_auction_expect_error(Errors::AuctionStartDateNotReached); } diff --git a/src/tests/vault/state_transition/option_settle_tests.cairo b/src/tests/vault/state_transition/option_settle_tests.cairo index d4a2bba0..2acdd04a 100644 --- a/src/tests/vault/state_transition/option_settle_tests.cairo +++ b/src/tests/vault/state_transition/option_settle_tests.cairo @@ -7,19 +7,15 @@ use openzeppelin::{ utils::serde::SerializedAppend, token::erc20::interface::{ERC20ABIDispatcherTrait} }; use pitch_lake_starknet::{ + types::Errors, library::eth::Eth, + vault::{ + contract::Vault, + interface::{ + IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcherTrait + } + }, + option_round::{interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}}, contracts::{ - vault::{ - contract::Vault, - interface::{ - IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, - IVaultSafeDispatcherTrait - } - }, - components::eth::Eth, - option_round::{ - types::OptionRoundError, - interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,} - }, market_aggregator::{ IMarketAggregator, IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait, IMarketAggregatorSafeDispatcher, IMarketAggregatorSafeDispatcherTrait @@ -76,11 +72,7 @@ fn test_settling_option_round_while_round_auctioning_fails() { accelerate_to_auctioning(ref vault_facade); // Settle option round before auction ends - let expected_error: felt252 = OptionRoundError::OptionSettlementDateNotReached.into(); - match vault_facade.settle_option_round_raw() { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + vault_facade.settle_option_round_expect_error(Errors::OptionSettlementDateNotReached); } // Test settling an option round before the option expiry date fails @@ -90,11 +82,7 @@ fn test_settling_option_round_before_settlement_date_fails() { let (mut vault_facade, _) = setup_test_running(); // Settle option round before expiry - let expected_error: felt252 = OptionRoundError::OptionSettlementDateNotReached.into(); - match vault_facade.settle_option_round_raw() { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + vault_facade.settle_option_round_expect_error(Errors::OptionSettlementDateNotReached); } // Test settling an option round while round settled fails @@ -107,14 +95,9 @@ fn test_settling_option_round_while_settled_fails() { accelerate_to_settled(ref vault_facade, 0x123); // Settle option round after it has already settled - let expected_error: felt252 = OptionRoundError::OptionRoundAlreadySettled.into(); - match vault_facade.settle_option_round_raw() { - Result::Ok(_) => { panic!("Error expected") }, - Result::Err(err) => { assert(err.into() == expected_error, 'Error Mismatch') } - } + vault_facade.settle_option_round_expect_error(Errors::OptionRoundAlreadySettled); } - /// Event Tests /// // Test settling an option round emits the correct event diff --git a/src/contracts/option_round/types.cairo b/src/types.cairo similarity index 53% rename from src/contracts/option_round/types.cairo rename to src/types.cairo index 083c68d4..08273a46 100644 --- a/src/contracts/option_round/types.cairo +++ b/src/types.cairo @@ -1,18 +1,58 @@ -use starknet::ContractAddress; +use starknet::{ContractAddress, Event}; use core::fmt::{Formatter, Error, Display}; +use pitch_lake_starknet::option_round::contract::OptionRound; + +/// Contract errors +mod Errors { + /// Vault Errors /// + const InsufficientBalance: felt252 = 'Insufficient unlocked balance'; + /// OptionRound Errors /// + const CallerIsNotVault: felt252 = 'Caller not the Vault'; + const AuctionAlreadyStarted: felt252 = 'Auction already started'; + const AuctionStartDateNotReached: felt252 = 'Auction start date not reached'; + const NoAuctionToEnd: felt252 = 'No auction to end'; + const AuctionEndDateNotReached: felt252 = 'Auction end date not reached'; + const AuctionNotEnded: felt252 = 'Auction has not ended yet'; + const OptionRoundAlreadySettled: felt252 = 'Option round already settled'; + const OptionSettlementDateNotReached: felt252 = 'Settlement date not reached'; + const OptionRoundNotSettled: felt252 = 'Option round not settled yet'; + const BidBelowReservePrice: felt252 = 'Bid price below reserve price'; + const BidAmountZero: felt252 = 'Bid amount cannot be 0'; + const BiddingWhileNotAuctioning: felt252 = 'Can only bid while auctioning'; + const CallerNotBidOwner: felt252 = 'Caller is not bid owner'; + const BidCannotBeDecreased: felt252 = 'A bid cannot decrease'; +} + +/// An enum for each type of Vault +#[derive(starknet::Store, Copy, Drop, Serde, PartialEq)] +enum VaultType { + InTheMoney, + AtTheMoney, + OutOfMoney, +} + +// An enum for each state an option round can be in +#[derive(Copy, Drop, Serde, PartialEq, starknet::Store)] +enum OptionRoundState { + Open, // Accepting deposits, waiting for auction to start + Auctioning, // Auction is on going, accepting bids + Running, // Auction has ended, waiting for option round expiry date to settle + Settled, // Option round has settled, remaining liquidity has rolled over to the next round +} + +/// OptionRound structs // The parameters needed to construct an option round // @param vault_address: The address of the vault that deployed this round // @param round_id: The id of the round (the first round in a vault is round 0) - #[derive(Copy, Drop, Serde, starknet::Store, PartialEq)] struct OptionRoundConstructorParams { vault_address: ContractAddress, round_id: u256, } -// The parameters sent from the vault (fossil) to start the auction +// The parameters sent from a Vault to start a round's auction #[derive(Copy, Drop, Serde, starknet::Store, PartialEq)] struct StartAuctionParams { total_options_available: u256, @@ -22,12 +62,14 @@ struct StartAuctionParams { strike_price: u256, } +// The parameters sent from a Vault to settle a round #[derive(Copy, Drop, Serde, starknet::Store, PartialEq)] struct SettleOptionRoundParams { settlement_price: u256 } +// The struct for a bid placed in a round's auction #[derive(Copy, Drop, Serde, starknet::Store, PartialEq, Display)] struct Bid { id: felt252, @@ -39,56 +81,11 @@ struct Bid { is_refunded: bool, } - -#[derive(Copy, Drop, starknet::Store, PartialEq)] -struct LinkedBids { - bid: felt252, - previous: felt252, - next: felt252 -} - - -// The states an option round can be in -// @note Should we move these into the contract or separate file ? -#[derive(Copy, Drop, Serde, PartialEq, starknet::Store)] -enum OptionRoundState { - Open, // Accepting deposits, waiting for auction to start - Auctioning, // Auction is on going, accepting bids - Running, // Auction has ended, waiting for option round expiry date to settle - Settled, // Option round has settled, remaining liquidity has rolled over to the next round -} - - -#[derive(Copy, Drop, Serde, PartialEq)] -enum OptionRoundError { - // All state transitions - CallerIsNotVault, - // Starting auction - AuctionAlreadyStarted, - AuctionStartDateNotReached, - // Ending auction - NoAuctionToEnd, - AuctionEndDateNotReached, - AuctionNotEnded, - // Settling round - OptionRoundAlreadySettled, - OptionSettlementDateNotReached, - OptionRoundNotSettled, - // Placing bids - BidBelowReservePrice, - BidAmountZero, - BiddingWhileNotAuctioning, - CallerNotBidOwner, - // Editing bids - BidCannotBeDecreased, -} - - -//TRAITS - -//Bid Traits +// Allows Bids to be sorted using >, >=, <, <= +// Bids with higher prices are ranked higher, if prices are equal, bids with a lower nonce are ranked higher +// Meaning if two bids have the same price, the one that was placed first is ranked higher than the later one impl BidPartialOrdTrait of PartialOrd { - // @return if lhs < rhs + /// < fn lt(lhs: Bid, rhs: Bid) -> bool { if lhs.price < rhs.price { true @@ -99,13 +96,7 @@ impl BidPartialOrdTrait of PartialOrd { } } - - // @return if lhs <= rhs - fn le(lhs: Bid, rhs: Bid) -> bool { - (lhs < rhs) || (lhs == rhs) - } - - // @return if lhs > rhs + /// > fn gt(lhs: Bid, rhs: Bid) -> bool { if lhs.price > rhs.price { true @@ -116,22 +107,27 @@ impl BidPartialOrdTrait of PartialOrd { } } - // @return if lhs >= rhs + /// <= + fn le(lhs: Bid, rhs: Bid) -> bool { + (lhs < rhs) || (lhs == rhs) + } + + + /// >= fn ge(lhs: Bid, rhs: Bid) -> bool { (lhs > rhs) || (lhs == rhs) } } +// Allows Bids to be printed using println! impl BidDisplay of Display { fn fmt(self: @Bid, ref f: Formatter) -> Result<(), Error> { - let owner: ContractAddress = *self.owner; - let owner_felt: felt252 = owner.into(); let str: ByteArray = format!( "ID:{}\nNonce:{}\nOwner:{}\nAmount:{}\n Price:{}\nTokenized:{}\nRefunded:{}", *self.id, *self.nonce, - owner_felt, + Into::::into(*self.owner), *self.amount, *self.price, *self.is_tokenized, @@ -142,7 +138,7 @@ impl BidDisplay of Display { } } -//OptionRoundStateTrait +// Allows OptionRoundStates to be printed using println! impl OptionRoundStateDisplay of Display { fn fmt(self: @OptionRoundState, ref f: Formatter) -> Result<(), Error> { let str: ByteArray = match self { @@ -155,27 +151,3 @@ impl OptionRoundStateDisplay of Display { Result::Ok(()) } } - - -//OptionRoundError Traits - -impl OptionRoundErrorIntoFelt252 of Into { - fn into(self: OptionRoundError) -> felt252 { - match self { - OptionRoundError::CallerIsNotVault => 'OptionRound: Caller not Vault', - OptionRoundError::AuctionStartDateNotReached => 'OptionRound: Auction start fail', - OptionRoundError::AuctionAlreadyStarted => 'OptionRound: Auction start fail', - OptionRoundError::AuctionEndDateNotReached => 'OptionRound: Auction end fail', - OptionRoundError::AuctionNotEnded => 'Auction has not ended', - OptionRoundError::NoAuctionToEnd => 'OptionRound: No auction to end', - OptionRoundError::OptionSettlementDateNotReached => 'OptionRound: Option settle fail', - OptionRoundError::OptionRoundNotSettled => 'OptionRound:Round not settled', - OptionRoundError::OptionRoundAlreadySettled => 'OptionRound: Option settle fail', - OptionRoundError::BidBelowReservePrice => 'OptionRound: Bid below reserve', - OptionRoundError::BidAmountZero => 'OptionRound: Bid amount zero', - OptionRoundError::BiddingWhileNotAuctioning => 'OptionRound: No auction running', - OptionRoundError::BidCannotBeDecreased => 'OptionRound: New bid too low', - OptionRoundError::CallerNotBidOwner => 'OptionROund: Caller not owner', - } - } -} diff --git a/src/contracts/vault/contract.cairo b/src/vault/contract.cairo similarity index 84% rename from src/contracts/vault/contract.cairo rename to src/vault/contract.cairo index b15e0bc9..d51244f0 100644 --- a/src/contracts/vault/contract.cairo +++ b/src/vault/contract.cairo @@ -8,17 +8,16 @@ mod Vault { token::erc20::{ERC20Component, interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait,}}, utils::serde::SerializedAppend }; - use pitch_lake_starknet::contracts::{ - vault::{types::{VaultType, VaultError}, interface::IVault}, + use pitch_lake_starknet::{ + vault::interface::IVault, option_round::{ - contract::OptionRound, - types::{ - OptionRoundErrorIntoFelt252, OptionRoundConstructorParams, StartAuctionParams, - SettleOptionRoundParams, OptionRoundState - }, - interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait}, + contract::OptionRound, interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait}, }, - market_aggregator::{IMarketAggregatorDispatcher} + contracts::{market_aggregator::{IMarketAggregatorDispatcher}}, + types::{ + OptionRoundConstructorParams, StartAuctionParams, SettleOptionRoundParams, + OptionRoundState, VaultType, Errors + } }; // The type of vault @@ -298,7 +297,7 @@ mod Vault { /// State transition - fn start_auction(ref self: ContractState) -> Result { + fn start_auction(ref self: ContractState) -> u256 { // Get a dispatcher for the current option round let current_round_id = self.current_option_round_id.read(); let current_round = self.get_round_dispatcher(current_round_id); @@ -310,12 +309,19 @@ mod Vault { let total_options_available = self .calculate_total_options_available(starting_liquidity); - // @note replace with individual getters, add strike price + // Update total_locked_liquidity + self.total_locked_balance.write(starting_liquidity); + + // Update total_unlocked_liquidity + self.total_unlocked_balance.write(0); + + // Fetch params to start the auction let reserve_price = self.fetch_reserve_price(); let cap_level = self.fetch_cap_level(); let strike_price = self.fetch_strike_price(); - // Try to start the auction on the current round - let res = current_round + + // Start the auction on the current round and return the total options available + current_round .start_auction( StartAuctionParams { total_options_available, @@ -324,74 +330,54 @@ mod Vault { cap_level, strike_price } - ); - match res { - Result::Ok(total_options_available) => { - // Update total_locked_liquidity - self.total_locked_balance.write(starting_liquidity); - - // Update total_unlocked_liquidity - self.total_unlocked_balance.write(0); - - // Return the total options available - Result::Ok(total_options_available) - }, - Result::Err(err) => { Result::Err(VaultError::OptionRoundError(err)) } - } + ) } - fn end_auction(ref self: ContractState) -> Result<(u256, u256), VaultError> { + fn end_auction(ref self: ContractState) -> (u256, u256) { // Get a dispatcher for the current round let current_round_id = self.current_option_round_id(); let current_round = self.get_round_dispatcher(current_round_id); - // Try to end the auction on the option round - let res = current_round.end_auction(); - match res { - Result::Ok(( - clearing_price, total_options_sold - )) => { - // Amount of liquidity currently locked and unlocked - let mut locked_liquidity = self.get_total_locked_balance(); - let mut unlocked_liquidity = self.get_total_unlocked_balance(); - - // Premiums earned from the auction are unlocked for liquidity providers to withdraw - unlocked_liquidity += current_round.total_premiums(); - - // Handle any unsold liquidity - let total_options_available = current_round.get_total_options_available(); - if (total_options_sold < total_options_available) { - // Number of options that did not sell - let unsold_options = total_options_available - total_options_sold; - - // Portion of the locked liquidity these unsold options represent - // @note Consider adding precision factor - let unsold_liquidity = (locked_liquidity * unsold_options) - / total_options_available; - - // Decrement locked liquidity by the unsold liquidity and - // update the storage variable - locked_liquidity -= unsold_liquidity; - self.total_locked_balance.write(locked_liquidity); - - // Increment unlocked liquidity by the unsold liquidity - unlocked_liquidity += unsold_liquidity; - - // Store how the unsold liquidity for this round for future balance calculations - self.unsold_liquidity.write(current_round_id, unsold_liquidity); - } + // End the auction on the option round + let (clearing_price, total_options_sold) = current_round.end_auction(); + + // Get the amount of liquidity currently locked & unlocked + let mut locked_liquidity = self.get_total_locked_balance(); + let mut unlocked_liquidity = self.get_total_unlocked_balance(); + + // Premiums earned from the auction are unlocked for liquidity providers to withdraw + unlocked_liquidity += current_round.total_premiums(); + + // Handle any unsold liquidity + let total_options_available = current_round.get_total_options_available(); + if (total_options_sold < total_options_available) { + // Number of options that did not sell + let unsold_options = total_options_available - total_options_sold; + + // Portion of the locked liquidity these unsold options represent + let unsold_liquidity = (locked_liquidity * unsold_options) + / total_options_available; + + // Decrement locked liquidity by the unsold liquidity and + // update the storage variable + locked_liquidity -= unsold_liquidity; + self.total_locked_balance.write(locked_liquidity); - // Update the total_unlocked_balance storage variable - self.total_unlocked_balance.write(unlocked_liquidity); + // Increment unlocked liquidity by the unsold liquidity + unlocked_liquidity += unsold_liquidity; - // Return the clearing_price & total_options_sold - return Result::Ok((clearing_price, total_options_sold)); - }, - Result::Err(e) => { Result::Err(VaultError::OptionRoundError(e)) } + // Store how much liquidity goes unsold for future balance calculations + self.unsold_liquidity.write(current_round_id, unsold_liquidity); } + + // Update the total_unlocked_balance storage variable + self.total_unlocked_balance.write(unlocked_liquidity); + + // Return the clearing_price & total_options_sold + (clearing_price, total_options_sold) } - fn settle_option_round(ref self: ContractState) -> Result { + fn settle_option_round(ref self: ContractState) -> u256 { // Get a dispatcher for the current option round let current_round_id = self.current_option_round_id(); let current_round_dispatcher = self.get_round_dispatcher(current_round_id); @@ -399,37 +385,30 @@ mod Vault { // Fetch the price to settle the option round let settlement_price = self.fetch_settlement_price(); - // Try to settle the option round - let res = current_round_dispatcher + // Settle the option round + let total_payout = current_round_dispatcher .settle_option_round(SettleOptionRoundParams { settlement_price }); - match res { - Result::Ok(total_payout) => { - // @dev Checking if payout > 0 to save gas if there is no payout - let mut remaining_liquidity = self.get_total_locked_balance(); - if (total_payout > 0) { - // Transfer total payout from the vault to the settled option round - let eth_dispatcher = self.get_eth_dispatcher(); - eth_dispatcher - .transfer(current_round_dispatcher.contract_address, total_payout); - // The remaining liquidity for a round is how much was locked minus the total payout - remaining_liquidity -= total_payout; - } - // The locked liquidity becomes 0 and the remaining liquidity becomes unlocked - self.total_locked_balance.write(0); - let total_unlocked_balance_before = self.get_total_unlocked_balance(); - self - .total_unlocked_balance - .write(remaining_liquidity + total_unlocked_balance_before); - - // Deploy next option round contract, update current round id & round address mapping - self.deploy_next_round(); + // @dev The remaining liquidity for a round is how much was locked minus the total payout + let mut remaining_liquidity = self.get_total_locked_balance(); - // Return the total payout of the option round - Result::Ok(total_payout) - }, - Result::Err(err) => { Result::Err(VaultError::OptionRoundError(err)) }, + // If there is a payout, transfer it from the vault to the settled option round + if (total_payout > 0) { + let eth_dispatcher = self.get_eth_dispatcher(); + eth_dispatcher.transfer(current_round_dispatcher.contract_address, total_payout); + remaining_liquidity -= total_payout; } + + // The remaining liquidity becomes unlocked and the locked liquidity becomes 0 + let total_unlocked_balance_before = self.get_total_unlocked_balance(); + self.total_unlocked_balance.write(total_unlocked_balance_before + remaining_liquidity); + self.total_locked_balance.write(0); + + // Deploy next option round contract, update current round id & round address mapping + self.deploy_next_round(); + + // Return the total payout + total_payout } /// Liquidity provider functions @@ -437,7 +416,7 @@ mod Vault { // Caller deposits liquidity on behalf of the liquidity provider for the upcoming round fn deposit_liquidity( ref self: ContractState, amount: u256, liquidity_provider: ContractAddress - ) -> Result { + ) -> u256 { // The liquidity provider's total unlocked balance before and after the deposit let lp_unlocked_balance_before = self.get_lp_unlocked_balance(liquidity_provider); let lp_unlocked_balance_after = lp_unlocked_balance_before + amount; @@ -476,11 +455,11 @@ mod Vault { ); // Return the liquidity provider's updated unlocked balance - Result::Ok(lp_unlocked_balance_after) + lp_unlocked_balance_after } // Caller withdraws liquidity from their unlocked balance - fn withdraw_liquidity(ref self: ContractState, amount: u256) -> Result { + fn withdraw_liquidity(ref self: ContractState, amount: u256) -> u256 { // Get the liquidity provider's unlocked balance broken up into its components let liquidity_provider = get_caller_address(); let (remaining_liquidity, collectable_balance, upcoming_round_deposit) = self @@ -490,9 +469,7 @@ mod Vault { + upcoming_round_deposit; // Assert the amount being withdrawn is <= the liquidity provider's unlocked balance - if (amount > lp_unlocked_balance) { - return Result::Err(VaultError::InsufficientBalance); - } + assert(amount <= lp_unlocked_balance, Errors::InsufficientBalance); // If the amount being withdrawn is <= the upcoming round deposit, we only need to update the // liquidity provider's position in storage for the upcoming round @@ -564,7 +541,7 @@ mod Vault { ); // Return the value of the caller's unlocked position after the withdrawal - Result::Ok(updated_lp_unlocked_balance) + updated_lp_unlocked_balance } /// LP token related @@ -577,8 +554,8 @@ mod Vault { fn convert_lp_tokens_to_newer_lp_tokens( ref self: ContractState, source_round: u256, target_round: u256, amount: u256 - ) -> Result { - Result::Ok(1) + ) -> u256 { + 1 } } diff --git a/src/contracts/vault/interface.cairo b/src/vault/interface.cairo similarity index 91% rename from src/contracts/vault/interface.cairo rename to src/vault/interface.cairo index 156acaf2..d897c501 100644 --- a/src/contracts/vault/interface.cairo +++ b/src/vault/interface.cairo @@ -1,8 +1,8 @@ use starknet::{ContractAddress}; -use pitch_lake_starknet::contracts::{ - option_round::types::{StartAuctionParams, OptionRoundState}, - market_aggregator::{IMarketAggregator, IMarketAggregatorDispatcher}, - vault::{contract::Vault, types::{VaultType, VaultError}}, +use pitch_lake_starknet::{ + vault::{contract::Vault}, + contracts::{market_aggregator::{IMarketAggregator, IMarketAggregatorDispatcher},}, + types::{VaultType, StartAuctionParams, OptionRoundState} }; // The interface for the vault contract @@ -102,17 +102,17 @@ trait IVault { // 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 - fn start_auction(ref self: TContractState) -> Result; + 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 ?) - fn end_auction(ref self: TContractState) -> Result<(u256, u256), VaultError>; + 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) -> Result; + fn settle_option_round(ref self: TContractState) -> u256; /// LP functions @@ -120,11 +120,11 @@ trait IVault { // @return The liquidity provider's updated unlocked position fn deposit_liquidity( ref self: TContractState, amount: u256, liquidity_provider: ContractAddress - ) -> Result; + ) -> u256; // Liquidity provider withdraws from the vailt // @return The liquidity provider's updated unlocked position - fn withdraw_liquidity(ref self: TContractState, amount: u256) -> Result; + fn withdraw_liquidity(ref self: TContractState, amount: u256) -> u256; /// LP token related @@ -152,5 +152,5 @@ trait IVault { // @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 - ) -> Result; + ) -> u256; }